Native层无源代码调试主要采用ptrace系统函数,ptrace提供了一种方法,使一个程序能够观察和控制其他程序(被追踪程序),它还能够检查和改变被追踪程序的寄存器和内存,从而实现断点调试、函数调用和系统调用的跟踪。
使用ptrace函数需要跟踪程序(tracee)和跟踪程序(tracer)二者之间附加(attach)。在执行系统调用之前,内核会检查当前程序是否处于跟踪状态。如果程序被跟踪,内核将暂停当前过程,并将控制权交付给跟踪程序,以便跟踪程序修改ptrace函数参数来检查或修改被跟踪过程的寄存器。其ptrace函数调用方式如下:
longptrace(enumptrace_requestrequest、pid_tpid、void*addr、void*data);
四个参数的含义是:
(1)enumptrace_requestrequest:表示ptrace的命令。
(2)pid_tpid进程号来指示ptrace要跟踪的线程。
(3)void*addr:指示需要监控的内存地址。
(4)void*data读取或写入的数据。
一些调试器功能是基于ptrace系统调用的,例如GDB。GDB是GNU发布的强大程序调试工具,用于调试C/C++程序,使程序员在程序执行过程中观察程序内存和寄存器的使用情况。使用ptrace系统调用GDB在调试程序和gdbserver之间建立通信。调试程序通常有两种模式,一种是通过调试器启动程序,另一种是附加到正在运行的程序中。在第一种模式下,调试器启动程序首先通过fork函数创建一个子进程,对子进程执行ptrace(PTRACE_TRACEME,0,0,0),然后通过execv函数在子进程中加载被调试的程序,将该进程执行过程中出现的信号交付给其父进程(调试器)处理,父进程通过wait函数知道当前进程的暂停。在第二种模式下,调试程序被添加到现有的过程中。调试器通过调用ptrace(PTRACE_ATTACH、pid、NULL、0)建立自己的过程与以pid为进程号的过程之间的跟踪关系,使调试程序成为被调试程序的父过程。PTRACE_ATTACH建立的跟踪关系可以通过调用ptrace(PTRACE_DETACH、pid、NULL、0)来解除。