调试器原理之ptrace调用学习

时间:2020-11-26 18:56:32
        其实很早以前就对调试器技术感兴趣了。以前玩板子的时候用了JTAG,当时我觉得这东西好神奇。前面我下载了一份GDB源码,可惜弄了几天都没有看出门道。昨天瞄了一眼《开源应用程序架构》,不出意外的看到了GDB。里面说在Linux下面调试器的功能主要都是靠ptrace调用实现的。我突然觉得有戏,感觉找到了点希望(不得不说,网上真心没找到GDB实现方面的资料,顶多是使用教程)。然后我就和ptrace干上了。后来就查到了一份名为《Linux源码分析-PTRACE》的文档,大致看了一遍后解决了我的不少疑问。下面是我摘抄的总结和自己的一点理解。
       调试进程的控制
       1) 终止进程运行
       在使用调试器调试程序时,被调试程序被中断的条件有:: 
       1. 调试器设置的断点(指令断点和数据断点)满足条件。
       2. 进程收到一个信号(SIGKILL除外)。 
       3. 单步调用完成。 
       4. 系统调用调试下,进入或离开系统调用。

                A. 断点  设置断点是调试器中的一个重要功能。80386提供了两种方式,INT3和利用调试寄存器。  如果使用INT3方式设置断点,则调试器通过ptrace的PTRACE_POKETEXT功能在断点处插入INT3单字节指令。当进程运行到断点时(INT3处),则系统进入异常3的处理。 若使用调试寄存器,则调试器通过调用ptrace(PTRACE_POKEUSR,pid,0,data)在DR0-DR3寄存器设置与四个断点条件的每一个相联系的线性地址在DR7中设置断点条件。被跟踪进程运行到断点处时,CPU产生异常 1,从而转至函数do_debug处理。由于子进程在调试状态下属于正常调试异常,所以do_debug函数处理中产生SIGTRAP信号,为处理这个信号,进入do_signal,使被调试进程停止,并通知调试器(父进程),此时得到子进程终止原因为SIGTRAP。
                B. 信号  在有些情况之下,要求调试器调试某进程时,当进程收到某一信号的时候中断进程运行。如:被调试进程在某处运算错误,进程会接收到SIGFPE信号,在正常运行状况下,会Coredump,而调试的情况下则希望在产生错误代码处停止运行,可以让用户调试错误原因。  对于已经被调试的进程(PF_PTRACED标志置位),当受到任何信号(SIGKILL除外)会中止其运行,并通知调试器(父进程)。(详见do_signal分析)
                C. 单步执行  单步执行也是一种使进程中止的情况。当用户调用ptrace的PTRACE_SINGLESTEP功能时,ptrace处理中,将用户态标志寄存器EFLAG中TF标志为置位,并让进程继续运行(具体分析见ptrace函数分析)。当进程回到用户态运行了一条指令后,CPU产生异常 1,从而转至函数do_debug处理。由于子进程在调试状态下属于正常调试异常,所以do_debug函数处理中产生SIGTRAP信号,为处理这个信号,进入do_signal,使被调试进程停止,并通知调试器(父进程),此时得到子进程终止原因为SIGTRAP。 
                D. 系统调用调试  对程序的调试,有时希望对系统调用进程跟踪。当程序进行系统调用时中断其运行。ptrace提供PTRACE_SYSCALL功能完成此功能。在ptrace调用中设置了进程标志PF_TRACESYS,表示进程对系统调用进行跟踪,并继续执行进程(具体分析见ptrace函数分析)。直到进程调用系统调用时,则中止其运行,并通知调试器(父进程)。(详见syscall_trace分析)
       2) 继续进程执行 
       让中断的进程继续执行,ptrace提供三种功能 
       1. 继续执行(PTRACE_CONT) 
       2. 系统调用调试(PTRACE_SYSCALL) 
       3. 单步执行(PTRACE_SINGLESTEP) 
        三种功能的区别在于PTRACE_CONT功能让进程继续执行直到下一个断点或收到一个信号会中止进程运行。PTRACE_SYSCALL功能让进程继续执行增加了一个中止条件,进程调用系统调用。PTRACE_SINGLESTEP功能让进程继续执行,只执行一个机器指令,则就中止其运行。 当被调试进程因为受到一个信号而中止时,这个信号并没有被处理。如果希望继续运行进程时继续处理这个信号,则在上述三个ptrace功能调用时,最后一个参数data设置要继续处理的信号。这种情况出现在,如:中止进程运行的信号为用户自定义信号,用户想继续运行进程,而不要忽略用户信号处理。有时,用户希望忽略其信号处理,这时则参数data设置为0,这种情况出现在,如:由于算术错误接收到SIGFPE信号使进程中止,而用户发现了错误,重新设置了正确的值,然后希望其继续执行,这时SIGFPE信号则需要忽略。
        根据ptrace的具体工作原理,我们知道控制被调试进程的关键在于两点。第一、按照我们的需要产生异常使被调试程序停下来,无论是使用INT3还是调试寄存器。然后在异常处理函数中发出信号,这些信号可以被调试器接收。第二、使用内存映射的方法读取被调试程序的数据空间,实现对被调试程序的信息监控。
        还有一个值得注意的问题是信号处理函数执行的时机。信号函数会在执行流从内核空间返回用户空间前夕判断并执行。一个被调试程序执行完异常处理后,它必须返回用户空间继续执行。这可以保证信号处理一定会优先于用户空间的代码运行。在通常的情况下,这个信号处理函数会导致被调试程序停止,同时父进程(调试器)也会收到这一信号。