1、申 请 I D:WIN_富贵儿
2、个人邮箱:1291186889@qq.com
3、原创技术文章,如下
# 依赖库
linenoise: 用来处理命令行输入的库
libelfin:用来解析elf文件,并从中提取 DWARF 调试信息
#
# 命令minigdb
# 设置断点
break func_name
break hello.cpp:4
break 0x555555555ef
# 继续运行
cont
# 读写内存
memory read 0x555555555ef
memory write 0x555555555ef 0x55555
# 查看函数局部变量
variables
# step over
next
# 单步执行一个机器指令
stepi
# step in
step
# step out
finish
# 读取寄存器的值
register dump
register read rip
# 写寄存器的值
register write rip 0x5555555ef
# 打印堆栈信息
backtrace
#
# 将调试信息打印出来 dwarf(调试信息)
dwarfdump ./hello
readelf -w hello >> hello_dwarf.txt
# 原理简要说明
本调试器所有的功能核心基于 ptrace 接口实现的
long ptrace(enum __ptrace_request request,pid_t pid,void *addr,void *data);
# 请求类型如下:
PTRACE_TRACEME
ptrace(PTRACE_TRACEME,0 ,0 ,0)
本进程被其父进程所跟踪。其父进程应该希望跟踪子进程。
PTRACE_PEEKTEXT, PTRACE_PEEKDATA
ptrace(PTRACE_PEEKTEXT, pid, addr, data)
ptrace(PTRACE_PEEKDATA, pid, addr, data)
从内存地址中读取一个字节,pid表示被跟踪的子进程,内存地址由addr给出,data为用户变量地址用于返回读到的数据。在Linux(i386)中用户代码段与用户数据段重合所以读取代码段和数据段数据处理是一样的。
PTRACE_POKETEXT, PTRACE_POKEDATA
ptrace(PTRACE_POKETEXT, pid, addr, data)
ptrace(PTRACE_POKEDATA, pid, addr, data)
往内存地址中写入一个字节。pid表示被跟踪的子进程,内存地址由addr给出,data为所要写入的数据。
PTRACE_PEEKUSR
ptrace(PTRACE_PEEKUSR, pid, addr, data)
从USER区域中读取一个字节,pid表示被跟踪的子进程,USER区域地址由addr给出,data为用户变量地址用于返回读到的数据。USER结构为core文件的前面一部分,它描述了进程中止时的一些状态,如:寄存器值,代码、数据段大小,代码、数据段开始地址等。在Linux(i386)中通过PTRACE_PEEKUSER和PTRACE_POKEUSR可以访问USER结构的数据有寄存器和调试寄存器。
PTRACE_POKEUSR
ptrace(PTRACE_POKEUSR, pid, addr, data)
往USER区域中写入一个字节,pid表示被跟踪的子进程,USER区域地址由addr给出,data为需写入的数据。
PTRACE_CONT
ptrace(PTRACE_CONT, pid, 0, signal)
继续执行。pid表示被跟踪的子进程,signal为0则忽略引起调试进程中止的信号,若不为0则继续处理信号signal。
PTRACE_SYSCALL
ptrace(PTRACE_SYS, pid, 0, signal)
继续执行。pid表示被跟踪的子进程,signal为0则忽略引起调试进程中止的信号,若不为0则继续处理信号signal。与PTRACE_CONT不同的是进行系统调用跟踪。在被跟踪进程继续运行直到调用系统调用开始或结束时,被跟踪进程被中止,并通知父进程。
PTRACE_KILL
ptrace(PTRACE_KILL,pid)
杀掉子进程,使它退出。pid表示被跟踪的子进程。
PTRACE_SINGLESTEP
ptrace(PTRACE_KILL, pid, 0, signle)
设置单步执行标志,单步执行一条指令。pid表示被跟踪的子进程。signal为0则忽略引起调试进程中止的信号,若不为0则继续处理信号signal。当被跟踪进程单步执行完一个指令后,被跟踪进程被中止,并通知父进程。
PTRACE_ATTACH
ptrace(PTRACE_ATTACH,pid)
跟踪指定pid 进程。pid表示被跟踪进程。被跟踪进程将成为当前进程的子进程,并进入中止状态。
PTRACE_DETACH
ptrace(PTRACE_DETACH,pid)
结束跟踪。 pid表示被跟踪的子进程。结束跟踪后被跟踪进程将继续执行。
PTRACE_GETREGS
ptrace(PTRACE_GETREGS, pid, 0, data)
读取寄存器值,pid表示被跟踪的子进程,data为用户变量地址用于返回读到的数据。此功能将读取所有17个基本寄存器的值。
PTRACE_SETREGS
ptrace(PTRACE_SETREGS, pid, 0, data)
设置寄存器值,pid表示被跟踪的子进程,data为用户数据地址。此功能将设置所有17个基本寄存器的值。
PTRACE_GETFPREGS
ptrace(PTRACE_GETFPREGS, pid, 0, data)
读取浮点寄存器值,pid表示被跟踪的子进程,data为用户变量地址用于返回读到的数据。此功能将读取所有浮点协处理器387的所有寄存器的值。
PTRACE_SETFPREGS
ptrace(PTRACE_SETREGS, pid, 0, data)
设置浮点寄存器值,pid表示被跟踪的子进程,data为用户数据地址。此功能将设置所有浮点协处理器387的所有寄存器的值。
#Debug 原理
# 一、软件DEBUG调试
> DEBUG调试使用硬件中断与软件中断两种方式。
## 1、调试断点 硬件断点
一个断点,允许编程人员对特定的线性地址设置特定的条件,当程序访问该线性地址,并满足特定的条件时,将跳入异常处理程序。
80836板子支持同时被设置4个断点条件,编程人员在程序中的四个位置设置条件,使其转向异常处理程序。
四个断点均可支持3种断点类型。
* 指令地址与断点地址一致时,断点有效。
* 数据写入地址与断点地址一致时,断点有效。
* 数据读出或写入地址与断点地址一致时,断点有效。
### 1.1 调试寄存器
> 为支持提供四个调试断点,80386中增加了8个寄存器,DR0 至 DR7. 八个寄存器,4个用于断点,2个用于控制,另外两个保留未用。
> 对这8个寄存器的访问,只能在0级特权级进行。其他特权级对这8个寄存器的访问都将造成 无效操作码异常。此外,这八个寄存器还可用DR6及DR7中的BD位和GD位进行进一步保护,使其即使在0级也不能读出或者写入。
> 对这些寄存器的访问,使用通常的MOV指令,例如将调试寄存器DRi中的内容,读到通用寄存器reg中。
```c
MOV Dri reg
```
> ****************************图中表示了这八个调试寄存器*********************
> ![](https://img-blog.csdn.net/20170305165533855?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMjQxNzM4MA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast "可选标题")
> 图中表示这8个调试寄存器,这些寄存器的功能如下:
>> DR0-DR3: 包含与四个断点条件相联系的线性地址(断点条件在DR7中)。因为这里使用的是线性地址(基地址+偏移地址),所以断点设施的操作,无论分页机制是否启用,都是相同的。
>> DR4-DR5:保留
>> DR6: DR6是调试状态寄存器。当一个调试异常产生时,处理器设置DR6的相应位,用于指示调式异常发生的原因,帮助调试异常处理程序分析、判断、以及做出相应的处理。
>> DR7是调试控制寄存器,分别对应四个断点寄存器的控制位,
>> + 对断点的启用
>> + 断点类型的选择
>> + 断点寄存器的保户
>>> DR6各位的功能
>>>> B0-B3: 当断点线性寄存器规定的条件被检测到时,将对应的B0-B3位置1。置位B0—B3与断点条件是否被启用无关。即B0—B3的某位被置1,并不表示要进行对应的断点异常处理。
>>>> BD : 如下一条指令要对8个调试寄存器之1进行读或者写时,则在指令的边界BD位置1.在一条指令内,每当即将读写调试寄存器时,也在BD位置1.BD位置1与DR7中GD位启用与否无关。
>>>> BS: 如果单步异常发生时,BS位被置1.单步条件由EFLAGS寄存器中的TF位启用。如果程序因为单步条件进入调试处理程序时,BS位也被置1。与DR6 不同之处在于 ,BS位只在单步陷阱实际发生时才置位,而不是检测单步条件就置位。
>>>> BT: BT位对任务切换导致TSS中的调试陷阱被启用而造成的调试异常,指示其原因 。对于这一条件,在DR7中没有启用位。DR6中的各个标志位,在处理机(调试异常处理程序)的各种清除操作中不受影响,因此,调试异常处理程序在运行以前,应清除DR6,以避免下一次检测到异常条件时,受到原来的DR6中状态位的影响。
>>> DR7中各位的功能
>>>> LEN LEN为一个两位的字段,用以指示断点的长度。每一个断点寄存器对应一个这样的字段,所以共有四个这样的字段分别对应四个断点寄存器。LEN的四种译码状态的断点长度如下:
>>>>> (1): 00 断点为1个字节
>>>>> (2): 01 断点为2个字节
>>>>> (3): 10 保留
>>>>> (4): 11 断点为4个字节
>>>> 这里的断点是多字节长度,则必须按对应多字节边界进行对齐。如果对应断点是一个指令地址,则LEN必须为00
>>>> RWE RWE也是两位的字段,用以指示引起断点异常的访问类型。共有四个RWE字段分别对应四个断点寄存器,RWE的四种译码状态对应的访问类型如下:
>>>>> (1): 00 指令
>>>>> (2): 01 数据写
>>>>> (3): 10 保留
>>>>> (4): 11 数据读和写
>>>> GE/LE GE/LE为分别指示准确的全局/局部数据断点。如果GE或LE被置位,则处理器将放慢执行速度,使得数据断点准确地把产生断点的指令报告出来。如果这些位没有置位,则处理器在执行数据写的指令接近执行结束稍前一点报告断点条件。建议读者每当启用数据断点时,启用LE或GE。降低处理机执行速度除稍微降低一点性能以外,不会引起别的问题。但是,对速度要求严格的代码区域除外。这时,必须禁用GE及LE,并且必须容许某些不太精确的调试异常报告。
>>>> L0—L3/G0—G3 L0—L3及G0—G3位分别为四个断点寄存器的局部及全局启用信号。如果有任一个局部或全局启用位被置位,则由对应断点寄存器DRi规定的断点被启用。
>>>> GD GD位启用调试寄存器保护条件。注意,处理程序在每次转入调试异常处理程序入口处清除GD位,从而使处理程序可以不受限制地访问调试寄存器.
>>>> 前述的各个L位(即LE,L0—L3)是有关任务的局部位,使调试条件只在特定的任务启用。而各个G位(即GD,G0—G3)是全局的,调试条件对系统中的所有任务皆有效。在每次任务切换时,处理器都要清除L位。
### 1.2 断点地址识别
> LEN字段及断点线性地址的组合,规定调试异常检查的四个线性地址的范围。上面已经提到,断点线性地址必须对齐于LEN规定的多字节长度的相应长度边界。事实上,处理器在检查断点时,根据LEN规定的长度,忽略线性地址的相应低位。例如,当LEN=11时,线性地址的最低两位被忽略,即把线性地址最低两位视为00,因而按四字节边界对齐。而当LEN=01时,线性地址的最低位被忽略,即把线性地址的最低位视为0,因而按两字节边界对齐 .
> 对于由断点线性地址及LEN规定的地址范围内类型正确的任何字节的访问都产生异常,数据的访问及指令的取出,都要按所有四个断点地址范围进行检查。如果断点地址范围的任何字节匹配,访问的类型也匹配,则断点异常被报告。
> 下表给出了识别数据断点的几个例子,这里假设所有断点被启用,而且设置了正确的访问类型。
> ![](https://img-blog.csdn.net/20170305170737052?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMjQxNzM4MA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast "识别数据断点的例子")
### 1.3 代码断点与数据断点比较
> 指令访问断点与数据访问断点之间有如下几点区别:
>> + 在RWE字段的设置不同。指令断点,RWE=0;数据断点,RWE≠0。
>> + LEN的设置不同。指令断点的长度只能是00即一字节;数据断点的长度可以是1、2、4字节。由于很多指令的长度超过一字节(事实上,指令长度为1—15字节),所以指令断点必须设置在指令的第一个字节。
> 由于指令断点在指令执行之前被报告,因此,很明显,对该指令不能简单的重新执行。因为每一次新的执行都简单地重复产生故障,所以,如果调试处理程序不禁用断点,则这种故障就会形成无限地循环。为解决这一问题,就需用到80386中EFLAGS的RF位。当RF位置位时,任何指令断点都被忽略,因此,在RF位保持为置位状态时,指令断点将不再起作用。但RF位的置位状态不会长久保持。事实上,处理器的内部逻辑保证,在任何一条指令成功完成后,都将RF位清零,因此,RF位的置位状态最多只保持一条指令的时间。也就是说,在RF位置位后的下一条指令,指令断点不起作用,这样只要在重新执行指令之前,将RF置1,即可保证该指令断点不会形成无限循环,而且,也不影响紧接的下一条指令也设置指令断点。
> RF位的置位,不是用某一个操作直接将EFLAGS的RF位置1来完成。每当进入一个故障处理程序,处理器保存中断现场时,需把断点等信息压栈。当把EFLAGS寄存器压栈时,推入栈中的EFLAGS的RF位是1,因此用IRET指令推出故障处理程序时,从栈中弹出的EFLAGS寄存器标志位中的RF为1,从而将RF位置位.
## 2 INT3 软件中断
### 2.1软件中断与硬件中断使用场景
> 一个断点指令提供调试程序的另一种方法。按这种方法,要求作为断点指令的第一个字节用INT3指令替代。因此,程序执行到预先需要的断点处遇到断点指令,并进入INT3处理程序。在一些使用INT3显然不足的地方还需使用断点寄存器。这样的情况有:
>> 1.由ROM提供的代码中,不可能插入INT3指令。
>> 2.由于使用了INT3,原来的程序代码被修改,使执行此代码的其它任务也被中断。
>> 3.INT3不能执行数据断点
> 在另外一些情况下,使用INT3则很有用:
>> 1.单步及断点设施仅仅进入调试程序,而对调试处理程序的调试,INT3则是唯一方便的方法。
>> 2.代码中可以插入任意数量的INT3指令,而断点设施只能提供最多四个断点。
>> 3. 早期86系列的各种型号处理器,没有80386提供的断点设施,INT3指令在这些处理器中是执行任何断点的唯一方法。
>> 概括地说,除了某些特别情况之外,建议使用INT3指令在代码中执行断点,保留断点寄存器用于数据断点。
## 3 ptrace 源码分析
> ptrace是linux系统中为了调试专门设立的一种系统调用。要想调试调试一个进程,有两种请求方式:
>> PTRACE_TRACEME和PTRACE_ATTACH。这两种方式的主要区别可以概括为:PTRACE_TRACEME是子进程主动申请被TRACE。而PTRACE_ATTACH是父进程自己要attach到子进程,相当于子进程是被动的trace。
### 3.1 PTRACE_TRACEME
> 函数处理流程:将进行一系列判断之后(父进程是否能对子进程进行跟踪的合法性检查),将子进程链接到父进程的ptrace链表中。
> 函数源代码如下:
```c
static int ptrace_traceme(void)
{
int ret = -EPERM;
write_lock_irq(&tasklist_lock); //自旋锁,访问临界资源。
/* Are we already being traced? */
if (!current->ptrace) { //判断当前进程有没有被跟宗
ret = security_ptrace_traceme(current->parent);
/*
* Check PF_EXITING to ensure ->real_parent has not passed
* exit_ptrace(). Otherwise we don't report the error but
* pretend ->real_parent untraces us right after return.
*/
if (!ret && !(current->real_parent->flags & PF_EXITING)) {
current->ptrace = PT_PTRACED;
ptrace_link(current, current->real_parent);
}
}
write_unlock_irq(&tasklist_lock);
return ret;
}
```
> 该函数,没有使子进程停止,真正导致子进程停止的是exec系统调用,该系统调用成功之后,内核会判断该进程是否被ptrace跟踪,如果被跟踪的话,内核将向该进程发送SIGTRAP信号。该信号将导致当前进程停止。
> 代码如下:
```C
static inline void ptrace_event(int event, unsigned long message)
{
if (unlikely(ptrace_event_enabled(current, event))) {
current->ptrace_message = message;
ptrace_notify((event << 8) | SIGTRAP);
} else if (event == PTRACE_EVENT_EXEC) {
/* legacy EXEC report via SIGTRAP */
if ((current->ptrace & (PT_PTRACED|PT_SEIZED)) == PT_PTRACED)
send_sig(SIGTRAP, current, 0);
}
}
```
> SIGTRAP信号是专门为debug设计的,当内核踩中断点的时候(断点就是int 3,相应的回调函数就是do_trap),就会发送这个信号.
> do_trap代码如下:
```C
asmlinkage void do_trap(struct pt_regs *regs, unsigned long address)
{
siginfo_t info;
memset(&info, 0, sizeof(info));
info.si_signo = SIGTRAP;
info.si_code = TRAP_TRACE;
info.si_addr = (void *)address;
force_sig_info(SIGTRAP, &info, current);
regs->pc += 4;
}
```
### 3.2 PTRACE_ATTACH
> 函数处理流程如下:
>> 1. 判断请求是PTRACE_SEIZE还是PTRACE_ATTACH,如果ptrace请求为PTRACE_SEIZE,则检查其参数是否正确,参数有误则退出
>> 2. 判断task进程是否为kernel thread(PF_KTHREAD),调用same_thread_group(task,current),判断task是否和current进程在同一个线程组,查看current进程是否有权限追踪task进程,不符合要求则退出
>> 3. 设置子进程task->ptrace = PT_TRACED,被跟踪状态,如果当前进程拥有CAP_SYS_PTRACED,设置task->ptrace |= PT_TRACE_CAP
>> 4. 调用__ptrace_link(task, current),将task->ptrace_entry链接到current->ptraced链表中
>> 5. 如果是PTRACE_ATTACH请求(PTRACE_SEIZE请求不会停止被追踪进程),则调用send_sig_info(SIGSTOP,SEND_SIG_FORCED, task);发送SIGSTOP信号,中止task运行,设置task->state为TASK_STOPPED
>> 6. 等待task->jobctl的JOBCTL_TRAPPING_BIT位被清零,阻塞时进程状态被设置为TASK_UNINTERRUPTIBLE并引发进程调度
>> PTRACE_ATTACH处理的方式与PTRACE_TRACEME处理的方式不同,PTRACE_ATTACH会使父进程向子进程发送SIGTRAP信号,如果子进程停止,父进程的wait操作则会被唤醒,从而成功attach。
>> 而PTRACE_TRACEME只是表明该进程(child)想被trace的意愿。如果一个进程调用了PTRACE_TRACEME,那么该进程处理信号的方式将会变得不同。比如:如果一个进程正在运行,此时输入ctrl+c(SIGINT),则该进程直接退出。但是,如果该进程中有ptrace(PTRACE_TRACEME,0,NULL,NULL)。即该进程主动要求被跟踪,那么,当输入CTRL+C时,该进程将会处于stopped的状态。
> 综上,子进程收到SIGTRAP信号之后,就会进入 stopped状态, 父进程的wait()函数就会返回。父进程就可以使用ptrace系统调用所提供的不同的参数实现各种不同的功能了
### 3.3 PTRACE_PEEKTEXT,PTRACE_PEEKDATA
> 在Linux(i386)中,用户代码段和用户数据段是重合的所以PTRACE_PEEKTEXT,PTRACE_PEEKDATA的处理是相同的。在其它CPU或操作系统上有可能是分开的,那要分开处理。读写用户段数据通过read_long()和write_long()两个辅助函数完成,具体函数过程参见两函数分析.
> read_long()和write_long()是内存读写辅助函数
> 在sys_ptrace函数中对用户空间的访问通过辅助函数write_long()和read_long()函数完成的。访问进程空间的内存是通过调用Linux的分页管理机制完成的。从要访问进程的task结构中读出对进程内存的描述mm结构,并依次按页目录、中间页目录、页表的顺序查找到物理页,并进行读写操作。函数put_long()和get_long()完成的是对一个页内数据的读写操作。而write_long()和read_long()函数考虑了,所要访问的数据在两页之间,这时则需对两页分别调用put_long()和get_long()函数完成其功能
> get_long源码:
```c
static unsigned long get_long(struct task_struct * tsk,
struct vm_area_struct * vma, unsigned long addr)
{
pgd_t * pgdir;
pmd_t * pgmiddle;
pte_t * pgtable;
unsigned long page;
repeat:
pgdir = pgd_offset(vma->vm_mm, addr); /* 查找页目录 */
if (pgd_none(*pgdir)) {
handle_mm_fault(tsk, vma, addr, 0); /* 缺页处理 */
goto repeat;
}
if (pgd_bad(*pgdir)) {
printk("ptrace: bad page directory %08lx\n", pgd_val(*pgdir));
pgd_clear(pgdir); /* 页出错 */
return 0;
}
pgmiddle = pmd_offset(pgdir, addr); /* 查找中间页目录 */
if (pmd_none(*pgmiddle)) {
handle_mm_fault(tsk, vma, addr, 0); /* 缺页处理 */
goto repeat;
}
if (pmd_bad(*pgmiddle)) {
printk("ptrace: bad page middle %08lx\n", pmd_val(*pgmiddle));
pmd_clear(pgmiddle); /* 页出错 */
return 0;
}
pgtable = pte_offset(pgmiddle, addr); /* 查找页表 */
if (!pte_present(*pgtable)) {
handle_mm_fault(tsk, vma, addr, 0); /* 缺页处理 */
goto repeat;
}
page = pte_page(*pgtable);
if (MAP_NR(page) >= max_mapnr)
return 0; /* 越界出错 */
page += addr & ~PAGE_MASK;
return *(unsigned long *) page;
}
```
> put_long源代码如下:
```c
static void put_long(struct task_struct * tsk, struct vm_area_struct * vma, unsigned long addr,
unsigned long data)
{
pgd_t *pgdir;
pmd_t *pgmiddle;
pte_t *pgtable;
unsigned long page;
repeat:
pgdir = pgd_offset(vma->vm_mm, addr); /* 查找页目录 */
if (!pgd_present(*pgdir)) {
handle_mm_fault(tsk, vma, addr, 1); /* 缺页处理 */
goto repeat;
}
if (pgd_bad(*pgdir)) {
printk("ptrace: bad page directory %08lx\n", pgd_val(*pgdir));
pgd_clear(pgdir); /* 页出错 */
return;
}
pgmiddle = pmd_offset(pgdir, addr); /* 查找中间页目录 */
if (pmd_none(*pgmiddle)) {
handle_mm_fault(tsk, vma, addr, 1); /* 缺页处理 */
goto repeat;
}
if (pmd_bad(*pgmiddle)) {
printk("ptrace: bad page middle %08lx\n", pmd_val(*pgmiddle));
pmd_clear(pgmiddle); /* 页出错 */
return;
}
pgtable = pte_offset(pgmiddle, addr); /* 查找页表 */
if (!pte_present(*pgtable)) {
handle_mm_fault(tsk, vma, addr, 1);
goto repeat;
}
page = pte_page(*pgtable); /* 读页 */
if (!pte_write(*pgtable)) { /* 是否可写 */
handle_mm_fault(tsk, vma, addr, 1); /* 页出错 */
goto repeat;
}
if (MAP_NR(page) < max_mapnr)
(unsigned long ) (page + (addr & ~PAGE_MASK)) = data; /* 写数据 */
set_pte(pgtable, pte_mkdirty(mk_pte(page, vma->vm_page_prot)));
flush_tlb();
}
```
> read_long源码:
```C
static int read_long(struct task_struct * tsk, unsigned long addr,
unsigned long * result)
{
struct vm_area_struct * vma = find_extend_vma(tsk, addr); /* 查找对应的VMA */
if (!vma)
return -EIO; /* 出错 */
if ((addr & ~PAGE_MASK) > PAGE_SIZE-sizeof(long)) { /* 是否跨页访问 */
unsigned long low,high;
struct vm_area_struct * vma_high = vma;
if (addr + sizeof(long) >= vma->vm_end) { /* 是否跨VMA访问 */
vma_high = vma->vm_next; /* 获得下一个VMA */
if (!vma_high || vma_high->vm_start != vma->vm_end)
return -EIO; /* 出错 */
}
low = get_long(tsk, vma, addr & ~(sizeof(long)-1)); /* 低字节 */
high = get_long(tsk, vma_high, (addr+sizeof(long)) & ~(sizeof(long)-1));
/* 高字节 */
switch (addr & (sizeof(long)-1)) { /* 重新组装数据 */
case 1:
low >>= 8;
low |= high << 24;
break;
case 2:
low >>= 16;
low |= high << 16;
break;
case 3:
low >>= 24;
low |= high << 8;
break;
}
*result = low;
} else
*result = get_long(tsk, vma, addr); /* 非跨页访问 */
return 0;
}
```
> write_long源代码
```c
tatic int write_long(struct task_struct * tsk, unsigned long addr,
unsigned long data)
{
struct vm_area_struct * vma = find_extend_vma(tsk, addr); /* 查找对应的VMA */
if (!vma)
return -EIO; /* 出错 */
if ((addr & ~PAGE_MASK) > PAGE_SIZE-sizeof(long)) {
unsigned long low,high;
struct vm_area_struct * vma_high = vma;
if (addr + sizeof(long) >= vma->vm_end) { /* 是否跨VMA访问 */
vma_high = vma->vm_next; /* 获得下一个VMA */
if (!vma_high || vma_high->vm_start != vma->vm_end)
return -EIO; /* 出错 */
}
low = get_long(tsk, vma, addr & ~(sizeof(long)-1)); /* 获得原来低字节数据 */
high = get_long(tsk, vma_high, (addr+sizeof(long)) & ~(sizeof(long)-1));
/* 获得原来高字节数据 */
switch (addr & (sizeof(long)-1)) { /* 重新组装要回写的数据 */
case 0:
low = data;
break;
case 1:
low &= 0x000000ff;
low |= data << 8;
high &= ~0xff;
high |= data >> 24;
break;
case 2:
low &= 0x0000ffff;
low |= data << 16;
high &= ~0xffff;
high |= data >> 16;
break;
case 3:
low &= 0x00ffffff;
low |= data << 24;
high &= ~0xffffff;
high |= data >> 8;
break;
}
put_long(tsk, vma, addr & ~(sizeof(long)-1),low); /* 写低字节数据 */
put_long(tsk, vma_high, (addr+sizeof(long)) & ~(sizeof(long)-1),high);
/* 写高字节数据 */
} else
put_long(tsk, vma, addr, data); /* 非跨页访问 */
return 0;
}
```
### 3.4 PTRACE_POKETEXT,PTRACE_POKEDATA
> 与PTRACE_PEEKTEXT,PTRACE_PEEKDATA处理相反,此处理为写进程内存.
> 源码如下:
```c
case PTRACE_POKETEXT:
case PTRACE_POKEDATA:
down(&child->mm->mmap_sem);
ret = write_long(child,addr,data); /* 修改数据 */
up(&child->mm->mmap_sem);
goto out;
```
mingdb源码:
链接:https://pan.baidu.com/s/14GLFEXv-TdkZ6GhtJwl0SQ
提取码:freg |
|