前言

gdb 的核心技术就是使用 ptrace 系统调用。

ptrace

NAME ptrace - process traceSYNOPSIS #include  long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);DESCRIPTION Theptrace()systemcall provides a means by which one process (the "tracer") may observe and control the execution of another process (the "tracee"), and examine and change the tracee's memory and registers.It is primarily used to implement breakpoint debugging and system call tracing.

ptrace() 系统调用提供了一种方法,通过它,一个进程(“tracer”)可以观察和控制另一个进程(“tracee”)的执行,并且可以检查和改变 tracee 的内存和寄存器。它主要用于实现断点调试和系统调用跟踪。

ptrace() 的第一个参数 request 是枚举类型,其值和含义如下

PTRACE_TRACEME, 本进程被其父进程所跟踪。其父进程应该希望跟踪子进程PTRACE_PEEKTEXT,从内存地址中读取一个字节,内存地址由addr给出PTRACE_PEEKDATA,同上PTRACE_PEEKUSER,可以检查用户态内存区域(USER area),从USER区域中读取一个字节,偏移量为addrPTRACE_POKETEXT,往内存地址中写入一个字节。内存地址由addr给出PTRACE_POKEDATA,往内存地址中写入一个字节。内存地址由addr给出PTRACE_POKEUSER,往USER区域中写入一个字节,偏移量为addrPTRACE_GETREGS,读取寄存器PTRACE_GETFPREGS,读取浮点寄存器PTRACE_SETREGS,设置寄存器PTRACE_SETFPREGS,设置浮点寄存器PTRACE_CONT,重新运行PTRACE_SYSCALL,重新运行PTRACE_SINGLESTEP,设置单步执行标志PTRACE_ATTACH,追踪指定pid的进程PTRACE_DETACH,结束追踪

这里主要介绍 PTRACE_TRACEMEPTRACE_ATTACH,它们分别对应两种追踪模式。

PTRACE_TRACEME

ptrace(PTRACE_TRACEME, 0, 0, 0)
通常为被追踪者使用,用来指示此进程将由其父进程跟踪。示例如下:
被追踪进程 loop.c

#include #include #include #include int main(int argc, char *argv[]){int i = 0;printf("pid = %d\n", getpid());while (1) {printf("hello world %d\n", i++);sleep(1);}return EXIT_SUCCESS;}

ptrace6.c

#include #include #include #include #include #include #include int main(int argc, char *argv[]){pid_t pid;int status;if (argc < 2) {fprintf(stderr, "Usage: %s  [args...]\n", argv[0]);return 1;}pid = fork();if (pid == 0) {// Child processptrace(PTRACE_TRACEME, 0, NULL, NULL);execvp(argv[1], argv + 1);perror("execvp");exit(1);} else if (pid > 0) {// Parent processwaitpid(pid, &status, 0);printf("Child started, pid = %d\n", pid);printf("chld got dignal:%s\n", strsignal(WSTOPSIG(status)));printf("子进程暂停 2s\n");sleep(2);printf("子进程继续执行 5s\n");ptrace(PTRACE_CONT, pid, NULL, NULL);sleep(5);printf("子进程暂停 3s\n");// 任何发给子进程的信号 signal(SIGKILL 除外)将导致子进程暂停运行kill(pid, SIGTRAP);sleep(3);printf("子进程继续执行 2s\n");ptrace(PTRACE_CONT, pid, NULL, NULL);sleep(2);printf("杀死子进程\n");kill(pid, SIGKILL);} else {perror("fork");return 1;}return 0;}

编译、运行

gcc loop.c -o loop.outgcc ptrace6.c -o ptrace6.out -Wall$ ./ptrace6.out ./loop.outChild started, pid = 979394chld got dignal:Trace/breakpoint trap子进程暂停 2s子进程继续执行 5spid = 979394hello world 0hello world 1hello world 2hello world 3hello world 4子进程暂停 3s子进程继续执行 2shello world 5hello world 6hello world 7杀死子进程

父进程 fork 出一个子进程,子进程调用 PTRACE_TRACEME,表明这个进程由它的父进程来跟踪。
任何发给子进程的信号 signal(SIGKILL 除外)将导致子进程暂停运行,而它的父进程会通过 wait() 获得通知。
另外,子进程调用 exec 会使操作系统产生一个 SIGTRAP 信号发送给子进程,这让父进程有机会在新程序开始执行之前获得对子进程的控制权。

PTRACE_ATTACH

此追踪模式将主动发送一个停止信号给目标进程(ATTACH 模式),使目标程序暂停下来,进而进行调试。
ptrace7.c

#include #include #include #include #include #include #include int main(int argc, char *argv[]){pid_t pid;printf("请输入进程号:\n");scanf("%d", &pid);printf("子进程暂停 2s\n");ptrace(PTRACE_ATTACH, pid, NULL, NULL);sleep(2);printf("子进程继续执行 5s\n");ptrace(PTRACE_CONT, pid, NULL, NULL);sleep(5);printf("子进程暂停 3s\n");kill(pid, SIGTRAP);sleep(3);printf("子进程继续执行 2s\n");ptrace(PTRACE_CONT, pid, NULL, NULL);sleep(2);printf("结束对子进程的控制\n");ptrace(PTRACE_DETACH, pid, NULL, NULL);return 0;}

编译、运行

gcc ptrace7.c -o ptrace7.out -Wall

番外

t+

当子进程被追踪并且暂停时,处于 t+ 状态,t 表示被跟踪状态(TASK_TRACED),+ 表示位于前台的进程组。

liyongj+9802430.00.0 2496 576 pts/93 t+ 17:59 0:00 ./loop.out

Q:TASK_TRACED 状态的进程会被 CPU 调度吗?

A:TASK_TRACED 状态的进程不会被CPU调度。在 Linux 中,当一个进程被设置为 TASK_TRACED 状态时,它会暂停执行,并等待调试器或其它进程唤醒。操作系统会将该进程从可调度进程队列中移除,直到被唤醒后再次加入可调度队列。

S+

当子进程运行时,处于 S+ 状态,S 表示可中断的睡眠状态。

liyongj+9802430.00.0 2496 576 pts/93 S+ 17:59 0:00 ./loop.out