仍是面某M的时候,面试官问我:“用过gdb么?” 答:“用过,调了两年bug了”。“那好,给我解释下gdb是怎么工做的?或者说跟内核什么地方有关系?”。 html
是阿,gdb凭什么能够调试一个程序?凭什么可以接管一个程序的运行?我之前也想过这样的问题,可是后来竟然忘记去查看了。我想到了咱们的二进制翻译器,想到了intel的pin,Dynamo。这些都是将翻译后的代码放到codecache中去运行,而后接管整个程序的执行。gdb是否是也同样呢? linux
若是真是这样,为何我记得用gdb跑一个程序,这个程序会有一个单独的进程?gdb的attach功能又是怎么实现的? 面试
想了想,我仍是没有答上来。面试就是由这么一个又一个细节的小杯具最后聚集成一个大杯具。 多线程
那么,gdb究竟是凭什么接管的一个进程的执行呢?其实,很简单,经过一个系统调用:ptrace。ptrace系统调用的原型以下: 函数
#include <sys/ptrace.h> long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data); |
说明:ptrace系统调用提供了一种方法来让父进程能够观察和控制其它进程的执行,检查和改变其核心映像以及寄存器。 主要用来实现断点调试和系统调用跟踪。(man手册) spa
其实,说到这里,一切原理层面应该都比较明朗了(且先不去管内核中是怎么实现ptrace的)。gdb就是调用这个系统调用,而后经过一些参数来控制其余进程的执行的。 线程
下面咱们来看ptrace函数中request参数的一些主要选项: 翻译
PTRACE_TRACEME: 表示本进程将被其父进程跟踪,交付给这个进程的全部信号,即便信号是忽略处理的(除SIGKILL以外),都将使其中止,父进程将经过wait()获知这一状况。 调试
这是什么意思呢?咱们能够结合到gdb上来看。若是在gdb中run一个程序,首先gdb会fork一个子进程,而后该子进程调用ptrace系统调用,参数就是PTRACE_TRACEME,而后调用一个exec执行程序。基本过程是这样,细节上可能会有出入。须要注意的是,这个选项PTRACE_TRACEME是由子进程调用的而不是父进程! code
如下选项都是由父进程调用:
PTRACE_ATTACH:attach到一个指定的进程,使其成为当前进程跟踪的子进程,而子进程的行为等同于它进行了一次PTRACE_TRACEME操做。可是,须要注意的是,虽然当前进程成为被跟踪进程的父进程,可是子进程使用getppid()的到的仍将是其原始父进程的pid。
这下子gdb的attach功能也就明朗了。当你在gdb中使用attach命令来跟踪一个指定进程/线程的时候,gdb就自动成为改进程的父进程,而被跟踪的进程则使用了一次PTRACE_TRACEME,gdb也就瓜熟蒂落的接管了这个进程。
PTRACE_CONT:继续运行以前中止的子进程。可同时向子进程交付指定的信号。
这个选项呢,其实就至关于gdb中的continue命令。当你使用continue命令以后,一个被gdb中止的进程就能继续执行下去,若是有信号,信号也会被交付给子进程。
除了以上这几个选项,ptrace还有不少其余选项,能够在linux下阅读man手册:man ptrace
须要注意的另外一点是,使用gdb调试过多线程/进程的人应该都知道,当子进程遇到一个信号的时候,gdb就会截获这个信号,并将子进程暂停下来。这是为何呢?
实际上,在使用参数为PTRACE_TRACEME或PTRACE_ATTACH的ptrace系统调用创建调试关系以后,交付给目标程序的任何信号(除SIGKILL以外)都将被gdb先行截获,或在远程调试中被gdbserver截获并通知gdb。gdb所以有机会对信号进行相应处理,并根据信号的属性决定在继续目标程序运行时是否将以前截获的信号实际交付给目标程序。
参考资料:gdb的基本工做原理
《gdb pocket reference》