1、一 函数说明
1. 函数使用说明
名字
ptrace – 进程跟踪
形式
#include
2、 PTRACE_TRACEME 本进程被其父进程所跟踪。其父进程应该希望跟踪子进程。 PTRACE_PEEKTEXT, PTRACE_PEEKDATA 从内存地址中读取一个字节,内存地址由addr给出。 PTRACE_PEEKUSR 从USER区域中读取一个字节,偏移量为addr。 PTRACE_POKETEXT, PTRACE_POKEDATA 往内存地址中写入一个字节。内存地址由addr给出。 PTRACE_POKEUSR 往USER区域中写入一个字节。偏移量为addr。 PTRACE_SYSCALL, PTRACE_CONT 重新运行。 PT
3、RACE_KILL 杀掉子进程,使它退出。 PTRACE_SINGLESTEP 设置单步执行标志 PTRACE_ATTACH 跟踪指定pid 进程。 PTRACE_DETACH 结束跟踪 Intel386特有: PTRACE_GETREGS 读取寄存器 PTRACE_SETREGS 设置寄存器 PTRACE_GETFPREGS 读取浮点寄存器 PTRACE_SETFPREGS 设置浮点寄存器 init进程不可以使用此函数 返回值 成功返回0。错误返回-1。errno被设置。 错误 EPERM 特殊进程不可以被跟踪或进程已经被跟
4、踪。 ESRCH 指定的进程不存在 EIO 请求非法 2. 功能详细描述 1) PTRACE_TRACEME 形式:ptrace(PTRACE_TRACEME,0 ,0 ,0) 描述:本进程被其父进程所跟踪。其父进程应该希望跟踪子进程。 2) PTRACE_PEEKTEXT, PTRACE_PEEKDATA 形式:ptrace(PTRACE_PEEKTEXT, pid, addr, data) ptrace(PTRACE_PEEKDATA, pid, addr, data) 描述:从内存地址中读取一个字节,pid表示被跟踪的子进程,内存地址由ad
5、dr给出,data为用户变量地址用于返回读到的数据。在Linux(i386)中用户代码段与用户数据段重合所以读取代码段和数据段数据处理是一样的。 3) PTRACE_POKETEXT, PTRACE_POKEDATA 形式:ptrace(PTRACE_POKETEXT, pid, addr, data) ptrace(PTRACE_POKEDATA, pid, addr, data) 描述:往内存地址中写入一个字节。pid表示被跟踪的子进程,内存地址由addr给出,data为所要写入的数据。 4) PTRACE_PEEKUSR 形式:ptrace(PTRACE_P
6、EEKUSR, pid, addr, data) 描述:从USER区域中读取一个字节,pid表示被跟踪的子进程,USER区域地址由addr给出,data为用户变量地址用于返回读到的数据。USER结构为core文件的前面一部分,它描述了进程中止时的一些状态,如:寄存器值,代码、数据段大小,代码、数据段开始地址等。在Linux(i386)中通过PTRACE_PEEKUSER和PTRACE_POKEUSR可以访问USER结构的数据有寄存器和调试寄存器。 5) PTRACE_POKEUSR 形式:ptrace(PTRACE_POKEUSR, pid, addr, data) 描述:往U
7、SER区域中写入一个字节,pid表示被跟踪的子进程,USER区域地址由addr给出,data为需写入的数据。 6) PTRACE_CONT 形式:ptrace(PTRACE_CONT, pid, 0, signal) 描述:继续执行。pid表示被跟踪的子进程,signal为0则忽略引起调试进程中止的信号,若不为0则继续处理信号signal。 7) PTRACE_SYSCALL 形式:ptrace(PTRACE_SYS, pid, 0, signal) 描述:继续执行。pid表示被跟踪的子进程,signal为0则忽略引起调试进程中止的信号,若不为0则继续处理信号signal
8、与PTRACE_CONT不同的是进行系统调用跟踪。在被跟踪进程继续运行直到调用系统调用开始或结束时,被跟踪进程被中止,并通知父进程。 8) PTRACE_KILL 形式:ptrace(PTRACE_KILL,pid) 描述:杀掉子进程,使它退出。pid表示被跟踪的子进程。 9) PTRACE_SINGLESTEP 形式:ptrace(PTRACE_KILL, pid, 0, signle) 描述:设置单步执行标志,单步执行一条指令。pid表示被跟踪的子进程。signal为0则忽略引起调试进程中止的信号,若不为0则继续处理信号signal。当被跟踪进程单步执行完一个指
9、令后,被跟踪进程被中止,并通知父进程。 10) PTRACE_ATTACH 形式:ptrace(PTRACE_ATTACH,pid) 描述:跟踪指定pid 进程。pid表示被跟踪进程。被跟踪进程将成为当前进程的子进程,并进入中止状态。 11) PTRACE_DETACH 形式:ptrace(PTRACE_DETACH,pid) 描述:结束跟踪。 pid表示被跟踪的子进程。结束跟踪后被跟踪进程将继续执行。 12) PTRACE_GETREGS 形式:ptrace(PTRACE_GETREGS, pid, 0, data) 描述:读取寄存器值,pid表示被跟踪的子
10、进程,data为用户变量地址用于返回读到的数据。此功能将读取所有17个基本寄存器的值。 13) PTRACE_SETREGS 形式:ptrace(PTRACE_SETREGS, pid, 0, data) 描述:设置寄存器值,pid表示被跟踪的子进程,data为用户数据地址。此功能将设置所有17个基本寄存器的值。 14) PTRACE_GETFPREGS 形式:ptrace(PTRACE_GETFPREGS, pid, 0, data) 描述:读取浮点寄存器值,pid表示被跟踪的子进程,data为用户变量地址用于返回读到的数据。此功能将读取所有浮点协处理器387的所有寄存器
11、的值。 15) PTRACE_SETFPREGS 形式:ptrace(PTRACE_SETREGS, pid, 0, data) 描述:设置浮点寄存器值,pid表示被跟踪的子进程,data为用户数据地址。此功能将设置所有浮点协处理器387的所有寄存器的值。 二 80386的调试设施 80386提供的调试设施包括: l 一字节的陷阱指令 l 单步指令 l 断点检测 l 任务切换时的自陷 1. 调试断点 断点设施是80386为调试程序提供的最重要的功能。 一个断点,允许编程人员对特定的线性地址设置特定的条件;当程序访问到该线性地址并满足特定的条件时,即跳转到异
12、常处理程序。80386可支持同时设置四个断点条件,编程人员可在程序中的四个位置设置条件,使其转向异常处理程序。这四个断点的每一个断点,都可以是如下三种不同类型的任何一种: 只在指令地址与断点地址一致时,断点有效。 数据写入地址与断点地址一致时,断点有效。 数据读出地址或数据写入地址与断点地址一致时,断点有效。 1) 调试寄存器 为支持提供四个调试断点,在80386中增加了八个寄存器,编号为DR0至DR7。这八个寄存器中由四个用于断点,两个用于控制,另两个保留未用。对这八个寄存器的访问,只能在0级特权级进行。在其它任何特权级对这八个寄存器中的任意一个寄存器进行读或写访问,都
13、将产生无效操作码异常。此外,这八个寄存器还可用DR6及DR7中的BD位和GD位进行进一步的保护,使其即使是在0级也不能进行读出或写入。 对这些寄存器的访问使用通常的MOV指令: MOV reg Dri 该指令将调试寄存器i中的内容读至通用寄存器reg中; MOV Dri reg 该指令将通用寄存器reg中的内容写至调试寄存器i中。此处i的取值可以为0至7中的任意值。 图表示了这八个调试寄存器。这些寄存器的功能如下: DR0—DR3 寄存器DR0—DR3包含有与四个断点条件的每一个相联系的线性地址(断点条件则在DR7中)。因为这里使用的是线性地址,所以,断点设施的操作,无论分页
14、机制是否启用,都是相同的。 DR4—DR5 保留。 DR6 DR6是调试状态寄存器。当一个调试异常产生时,处理器设置DR6的相应位,用于指示调试异常发生的原因,帮助调试异常处理程序分析、判断,以及作出相应处理。 DR7 DR7是调试控制寄存器。分别对应四个断点寄存器的控制位,对断点的启用及断点类型的选择进行控制。所有断点寄存器的保护也在此寄存器中规定。 DR6各位的功能 B0—B3 当断点线性地址寄存器规定的条件被检测到时,将对应的B0—B3位置1。置位B0—B3与断点条件是否被启用无关。即B0—B3的某位被置1,并不表示要进行对应的断点异常处理。 BD 如下一条指令
15、要对八个调试寄存器之一进行读或写时,则在指令的边界BD位置1。在一条指令内,每当即将读写调试寄存器时,也BD位置1。BD位置1与DR7中GD位启用与否无关。 BS 如果单步异常发生时,BS位被置1。单步条件由EFLAGS寄存器中的TF位启用。如果程序由于单步条件进入调试处理程序,则BS位被置1。与DR6中的其它位不同的是,BS位只在单步陷阱实际发生时才置位,而不是检测到单步条件就置位。 BT BT位对任务切换导致TSS中的调试陷阱位被启用而造成的调试异常,指示其原因。对这一条件,在DR7中没有启用位。 DR6中的各个标志位,在处理机的各种清除操作中不受影响,因此,调试异常处理程序在运
16、行以前,应清除DR6,以避免下一次检测到异常条件时,受到原来的DR6中状态位的影响。 DR7各位的功能 LEN LEN为一个两位的字段,用以指示断点的长度。每一断点寄存器对应一个这样的字段,所以共有四个这样的字段分别对应四个断点寄存器。LEN的四种译码状态对应的断点长度如下 LEN 说明 0 0 断点为一字节 0 1 断点为两字节 1 0 保留 1 1 断点为四字节 这里,如果断点是多字节长度,则必须按对应多字节边界进行对齐。如果对应断点是一个指令地址,则LEN必须为00 RWE RWE也是两位的字段,用以指示引起断点异常的访问类型。共有四个RWE字段分别对应四
17、个断点寄存器,RWE的四种译码状态对应的访问类型如下 RWE 说明 0 0 指令 0 1 数据写 1 0 保留 1 1 数据读和写 GE/LE GE/LE为分别指示准确的全局/局部数据断点。如果GE或LE被置位,则处理器将放慢执行速度,使得数据断点准确地把产生断点的指令报告出来。如果这些位没有置位,则处理器在执行数据写的指令接近执行结束稍前一点报告断点条件。建议读者每当启用数据断点时,启用LE或GE。降低处理机执行速度除稍微降低一点性能以外,不会引起别的问题。但是,对速度要求严格的代码区域除外。这时,必须禁用GE及LE,并且必须容许某些不太精确的调试异常报告。 L0—L
18、3/G0—G3 L0—L3及G0—G3位分别为四个断点寄存器的局部及全局启用信号。如果有任一个局部或全局启用位被置位,则由对应断点寄存器DRi规定的断点被启用。 GD GD位启用调试寄存器保护条件。注意,处理程序在每次转入调试异常处理程序入口处清除GD位,从而使处理程序可以不受限制地访问调试寄存器。 前述的各个L位(即LE,L0—L3)是有关任务的局部位,使调试条件只在特定的任务启用。而各个G位(即GD,G0—G3)是全局的,调试条件对系统中的所有任务皆有效。在每次任务切换时,处理器都要清除L位。 2) 断点地址识别 LEN字段及断点线性地址的组合,规定调试异常检查的四个线性地
19、址的范围。上面已经提到,断点线性地址必须对齐于LEN规定的多字节长度的相应长度边界。事实上,处理器在检查断点时,根据LEN规定的长度,忽略线性地址的相应低位。例如,当LEN=11时,线性地址的最低两位被忽略,即把线性地址最低两位视为00,因而按四字节边界对齐。而当LEN=01时,线性地址的最低位被忽略,即把线性地址的最低位视为0,因而按两字节边界对齐。 对于由断点线性地址及LEN规定的地址范围内类型正确的任何字节的访问都产生异常,数据的访问及指令的取出,都要按所有四个断点地址范围进行检查。如果断点地址范围的任何字节匹配,访问的类型也匹配,则断点异常被报告。 下表给出了识别数据断点的几个
20、离子,这里假设所有断点被启用,而且设置了正确的访问类型。 地址 长度 长度 断点 寄存器内容 DR0 DR1 DR2 DR3 00FF02 00CC32 0D0004 01FF00 1 2 4 4 Len=00 Len=01 Len=11 Len=11 引起异常的 访问 00FF02 00CC33 0D0007 00FEFF 01FF00 01FF03 1 1 2 4 4 4 B0=1 B1=1 B2=1 B0=1 B3=1 B3=1 不引起异常 的访问 00FF01 00FF
21、00 00CC34 01FEFF 0D0000 1 2 1 1 4 3) 代码断点与数据断点的比较 指令访问断点与数据访问断点之间有如下几点区别: 1. 在RWE字段的设置不同。指令断点,RWE=0;数据断点,RWE≠0。 2. LEN的设置不同。指令断点的长度只能是00即一字节;数据断点的长度可以是1、2、4字节。由于很多指令的长度超过一字节(事实上,指令长度为1—15字节),所以指令断点必须设置在指令的第一个字节。 3. 指令断点属故障类型,数据断点属陷阱类型。指令断点故障在指令执行前被检查及报告。数据断点陷阱则在对断点位置进行读和写之后才被报告。可
22、以看出,数据断点陷阱对断点数据未进行写保护。要保护断点的数据不会被写入操作破坏,必须先在调试处理程序中保存断点原来数据的拷贝。 由于指令断点在指令执行之前被报告,因此,很明显,对该指令不能简单的重新执行。因为每一次新的执行都简单地重复产生故障,所以,如果调试处理程序不禁用断点,则这种故障就会形成无限地循环。为解决这一问题,就需用到80386中EFLAGS的RF位。当RF位置位时,任何指令断点都被忽略,因此,在RF位保持为置位状态时,指令断点将不再起作用。但RF位的置位状态不会长久保持。事实上,处理器的内部逻辑保证,在任何一条指令成功完成后,都将RF位清零,因此,RF位的置位状态最多只保持一条
23、指令的时间。也就是说,在RF位置位后的下一条指令,指令断点不起作用,这样只要在重新执行指令之前,将RF置1,即可保证该指令断点不会形成无限循环,而且,也不影响紧接的下一条指令也设置指令断点。 RF位的置位,不是用某一个操作直接将EFLAGS的RF位置1来完成。每当进入一个故障处理程序,处理器保存中断现场时,需把断点等信息压栈。当把EFLAGS寄存器压栈时,推入栈中的EFLAGS的RF位是1,因此用IRET指令推出故障处理程序时,从栈中弹出的EFLAGS寄存器标志位中的RF为1,从而将RF位置位。 2. TSS中的调度陷阱 每当通过TSS发生任务切换时,TSS中的T位使调试处理程序被
24、调用,这就为调试程序管理某些任务的活动提供了一种方便的方法。DR6中的BT位指示对该位的检测,DR7中对该位没有特别的启用位。 如果调试处理程序是通过任务门使用的,则不能设置对应TSS的调试陷阱位。否则,将发生调试处理程序的无限循环。 3. INT3 一个断点指令提供调试程序的另一种方法。按这种方法,要求作为断点指令的第一个字节用INT3指令替代。因此,程序执行到预先需要的断点处遇到断点指令,并进入INT3处理程序。在一些使用INT3显然不足的地方还需使用断点寄存器。这样的情况有: 1. 由ROM提供的代码中,不可能插入INT3指令。 2. 由于使用了INT3,原来的程序代码
25、被修改,使执行此代码的其它任务也被中断。 3. INT3不能执行数据断点。 在另外一些情况下,使用INT3则很有用: 1. 单步及断点设施仅仅进入调试程序,而对调试处理程序的调试,INT3则是唯一方便的方法。 2. 代码中可以插入任意数量的INT3指令,而断点设施只能提供最多四个断点。 3. 早期86系列的各种型号处理器,没有80386提供的断点设施,INT3指令在这些处理器中是执行任何断点的唯一方法。 概括地说,除了某些特别情况之外,建议使用INT3指令在代码中执行断点,保留断点寄存器用于数据断点。 4. 程序的步进执行 单步功能对程序调试者来说,是一个方便的调试手段。
26、通过一条一条地执行指令,对操作数据、操作指令及操作结果地观察和分析,可以帮助调试人员判断出执行某一指令时,是否发生了硬件错误,或是否软件逻辑错误。80386的单步功能通过陷阱来实现。单步陷阱在EFLAGS寄存器中的TF位置位时启用。在一条指令开始执行时,如果有TF=1,则在指令执行的末尾产生调试异常,并进入调试处理程序。在这里,“指令开始执行时,TF=1”这一条件是重要的。有此条件的限制,使TF位置位1的指令不会产生单步陷阱。每次产生单步陷阱之后,在进入调试处理程序之前要将TF位清除。此外,在处理中断或异常时,也清除TF位。 如果外部中断与单步中断同时发生,则单步中断被优先处理,并清除TF
27、在调试处理程序第一条指令执行之前,如仍有悬挂的中断请求,则响应并处理中断。因此,中断处理是在没有单步启用的情况下完成的。如果希望在中断处理程序中使用单步功能。则需先把中断处理程序的第一条指令设置为断点,当程序运行到断点处停下来之后,再启用单步功能。 三 代码分析 在Linux核心源代码中,与完成ptrace功能相关的代码有: l sys_ptrace函数,完成ptrace系统调用的代码。 l 为完成sys_ptrace功能所需调用的一些辅助函数,寄存器读写函数和内存读写函数。 l 信号处理函数中,对被调试进程的处理(中止其运行、继续运行)。 l syscall_trace函数
28、完成了系统调用调试下的处理。 l 调试陷阱处理(异常1处理),完成单步执行和断点中断处理。 l execve系统调用中对被调试进程装入后中止的实现。 1. sys_ptrace函数 ptrace系统调用在核心对应的处理函数为sys_ptrace()(/linux/arch/i386/kernel/ptrace.c)。sys_ptrace函数完成了ptrace系统调用功能。ptrace函数的总体流程如下: 程序和说明注释如下: asmlinkage int sys_ptrace(long request, long pid, long addr, long data) {
29、 struct task_struct *child; struct user * dummy = NULL; unsigned long flags; int i, ret; lock_kernel(); ret = -EPERM; if (request == PTRACE_TRACEME) { 。。。PTRACE_TRACEME处理 } ret = -ESRCH; read_lock(&tasklist_lock); child = find_task_by_pid(pid); /* 查找task结构 */ rea
30、d_unlock(&tasklist_lock); if (!child) /* 没有找到task结构,所名给定pid错误 */ goto out; ret = -EPERM; if (pid == 1) /* init进程不能调试 */ goto out; if (request == PTRACE_ATTACH) { 。。。PTRACE_ATTACH处理 } ret = -ESRCH; if (!(child->flags & PF_PTRACED)) /* 进程没有被跟踪,不能执行其它功能 */
31、 goto out; if (child->state != TASK_STOPPED) { if (request != PTRACE_KILL) /* 除PTRACE_KILL外的其它功能要求 */ goto out; /* 要求进程状态为TASK_STOPPED */ } if (child->p_pptr != current) /* 被跟踪进程要求为当前进程的子进程 */ goto out; switch (request) { case PTRACE_PEEKTEXT: case PTRACE_PEE
32、KDATA: { 。。。PTRACE_PEEKTEXT,PTRACE_PEEKDATA处理 } case PTRACE_PEEKUSR: { 。。。PTRACE_PEEKUSR处理 } case PTRACE_POKETEXT: case PTRACE_POKEDATA:{ 。。。PTRACE_POKETEXT,PTRACE_POKEDATA处理 } case PTRACE_POKEUSR: { 。。。PTRACE_POKEUSR处理 } case PTRACE_SYSCALL: case PTRACE_C
33、ONT: 。。。PTRACE_SYSCALL,PTRACE_CONT处理 } case PTRACE_KILL: { 。。。PTRACE_KILL处理 } case PTRACE_SINGLESTEP: { 。。。PTRACE_SINGLESTEP处理 } case PTRACE_DETACH: 。。。PTRACE_DETACH处理 } case PTRACE_GETREGS: 。。。PTRACE_GETREGS处理 }; case PTRACE_SETREGS: 。。。
34、PTRACE_SETREGS处理 }; case PTRACE_GETFPREGS: 。。。PTRACE_GETFPREGS处理 }; case PTRACE_SETFPREGS: 。。。PTRACE_SETFPREGS处理 }; default: ret = -EIO; goto out; } out: unlock_kernel(); return ret; } 1) PTRACE_TRACEME处理 说明:此处理使当前进程进入调试状态。进程是否为调试状态由进程的标志PF_PTRACED
35、表示。 流程: 程序: if (request == PTRACE_TRACEME) { if (current->flags & PF_PTRACED) /* 是否已经被跟踪 */ goto out; current->flags |= PF_PTRACED; /* 设置跟踪标志 */ ret = 0; goto out; } 2) PTRACE_ATTACH处理 说明:此处理设置开始调试某一进程,此进程可以是任何进程(init进程除外)。对某一进程的调试需有对这一进程操作的权限。不能调试自身进程。一个进程不能ATTACH
36、多次。 为完成对一个进程的调试设置,首先设置进程标志置PF_PTRACED。再将需调试的进程设置为当前进程的子进程。最后向它发信号SIGSTOP中止它的运行,使它进入调试状态。 流程: 程序: if (request == PTRACE_ATTACH) { if (child == current) /* 不能调试自身进程 */ goto out; if ((!child->dumpable || (current->uid != child->euid) || (current->uid != child->suid)
37、 || (current->uid != child->uid) || (current->gid != child->egid) || (current->gid != child->sgid) || (!cap_issubset(child->cap_permitted, current->cap_permitted)) || (current->gid != child->gid)) && !capable(CAP_SYS_PTRACE)) goto out; /* 检验用户权限 */
38、 if (child->flags & PF_PTRACED) /* 一个进程不能被attach多次 */ goto out; child->flags |= PF_PTRACED; /* 设置进程标志位PF_PTRACED */ write_lock_irqsave(&tasklist_lock, flags); if (child->p_pptr != current) { /* 设置进程为当前进程的子进程 */ REMOVE_LINKS(child); child->p_pptr = current; SET_LINKS
39、child); } write_unlock_irqrestore(&tasklist_lock, flags); send_sig(SIGSTOP, child, 1); /* 发送SIGSTOP信号,中止它运行 */ ret = 0; goto out; } 3) PTRACE_PEEKTEXT,PTRACE_PEEKDATA处理 说明:在Linux(i386)中,用户代码段和用户数据段是重合的所以PTRACE_PEEKTEXT,PTRACE_PEEKDATA的处理是相同的。在其它CPU或操作系统上有可能是分开的,那要分开处理。读写用户段数据通
40、过read_long()和write_long()两个辅助函数完成,具体函数过程参见两函数分析。 流程: 程序: case PTRACE_PEEKTEXT: case PTRACE_PEEKDATA: { unsigned long tmp; down(&child->mm->mmap_sem); ret = read_long(child, addr, &tmp); /* 读取数据 */ up(&child->mm->mmap_sem); if (ret >= 0) ret = put_user(tmp,(unsi
41、gned long *) data); /* 返回结果 */ goto out; } 4) PTRACE_POKETEXT,PTRACE_POKEDATA处理 说明:与PTRACE_PEEKTEXT,PTRACE_PEEKDATA处理相反,此处理为写进程内存(详见上) 流程: 程序: case PTRACE_POKETEXT: case PTRACE_POKEDATA: down(&child->mm->mmap_sem); ret = write_long(child,addr,data); /* 修改数据 */ u
42、p(&child->mm->mmap_sem); goto out; 5) PTRACE_PEEKUSR处理 说明:在Linux(i386)中,读写USER区域的数据值有用户寄存器和调试寄存器的值。用户寄存器包括17个寄存器,它们分别是EBX、ECX、EDX、ESI、EDI、EBP、EAX、DS、ES、FS、GS、ORIG_EAX、EIP、CS、EFLAGS、ESP、SS。这些寄存器的读写由辅助函数putreg()和getreg()函数完成,具体实现参见两函数分析。调试寄存器为DR0—DR7。其中DR4和DR5为系统保留的寄存器,不可以写。DR0—DR3中的断点地址必须在用户的3G
43、空间内,在核心内存设置断点非法。DR7中的RWE与LEN数据位必须合法(LEN≠10保留、RWE≠10保留、RWE=00时LEN=00指令断点为一字节)。 流程: 程序: case PTRACE_PEEKUSR: { unsigned long tmp; ret = -EIO; if ((addr & 3) || addr < 0 || /* 越界或字节未对齐出错 */ addr > sizeof(struct user) - 3) goto out; tmp = 0; /* Default return
44、condition */ if(addr < 17*sizeof(long)) /* 读取基本寄存器值 */ tmp = getreg(child, addr); if(addr >= (long) &dummy->u_debugreg[0] && addr <= (long) &dummy->u_debugreg[7]){ addr -= (long) &dummy->u_debugreg[0]; addr = addr >> 2; tmp = child->tss.debugreg[addr]; /* 读取
45、调试寄存器值 */ }; ret = put_user(tmp,(unsigned long *) data); /* 返回结果 */ goto out; } 6) PTRACE_POKEUSR处理 说明:与PTRACE_PEEKUSR处理相反,此处理为写USER区域(详见上)。 流程: 程序: case PTRACE_POKEUSR: ret = -EIO; if ((addr & 3) || addr < 0 || /* 越界或字节未对齐出错 */ addr > sizeof(struct
46、user) - 3) goto out; if (addr < 17*sizeof(long)) { ret = putreg(child, addr, data); /* 写基本寄存器值 */ goto out; } if(addr >= (long) &dummy->u_debugreg[0] && addr <= (long) &dummy->u_debugreg[7]){ if(addr == (long) &dummy->u_debugreg[4]) return -EIO; /* 写DR4
47、出错 */ if(addr == (long) &dummy->u_debugreg[5]) return -EIO; /* 写DR5出错 */ if(addr < (long) &dummy->u_debugreg[4] && ((unsigned long) data) >= TASK_SIZE-3) return -EIO; /* 断点地址越界出错 */ ret = -EIO; if(addr == (long) &dummy->u_debugreg[7]) { /* 写DR7 */
48、 data &= ~DR_CONTROL_RESERVED; for(i=0; i<4; i++) if ((0x5f54 >> ((data >> (16 + 4*i)) & 0xf)) & 1) goto out; /* LEN RWE非法出错 */ }; addr -= (long) &dummy->u_debugreg; addr = addr >> 2; child->tss.debugreg[addr] = data; /* 写调试寄存器值 */ ret
49、 = 0; goto out; }; ret = -EIO; goto out; 7) PTRACE_SYSCALL,PTRACE_CONT处理 说明:PTRACE_SYSCALL和PTRACE_CONT有着相同的处理,都是让子进程继续运行,其区别PTRACE_SYSCALL设置了进程标志PF_TRACESYS。这样可以使进程在下一次系统调用开始或结束时中止运行。继续执行要保证清除单步执行标志。用户参数data为用户提供的信号,希望子进程继续处理此信号。如果为0则不处理,如果不为0则在唤醒子进程后向子进程发送此信号(在do_signal()和sys
50、call_trace()函数中完成)。 流程: 程序: case PTRACE_SYSCALL: case PTRACE_CONT: long tmp; ret = -EIO; if ((unsigned long) data > _NSIG) /* 信号超过范围 */ goto out; if (request == PTRACE_SYSCALL) child->flags |= PF_TRACESYS; /* 设置PF_TRACESYS标志 */ else child->flags &=






