gdb调试器的原理

       上次实习生面试被追问gdb问题,现在来总结下。

       当运行gdb,接着通过命令行去run一个程序的时候,gdb会执行如下几个操作:

①    通过fork()系统调用创建一个新的进程,一开始是gdb的进程,现在要fork出一个函数去执行被调试的程序。

②    在新创建的子进程中执行下述操作ptrace(PTRACE_TRACEME,0,0,0);这样这个新创建出来的子进程就能被父进程追踪。

③    在子进程中通过execv()系统调用加载被调试的函数。

这样建立出来的子进程其收到的所有信号除了SIGKILL之外都将被gdb截获,因此gdb有机会对信号进行相应的处理。

       先不去管ptrace是如何实现父子进程间的交互的,首先去考虑下如何实现断点功能,以x86为例,向某个地址打入断点,实际上就是往该地址写入断点指令INT 3,即0XCC。由于INT 3指令的设计目的就是中断到调试器,因此,CPU执行这条指令的过程也就是产生断点异常(breakpoint exception,简称BP)并转去执行异常处理例程的过程。在跳转到处理例程之前,CPU会保存当前的执行上下文,包括段寄存器、程序指针寄存器等内容。INT 3异常的处理函数是操作系统的内核函数(KiTrap03)。因此执行INT 3会导致CPU执行KiTrap03例程。这个内核例程会把这个异常通过调试子系统以调试事件的形式分发给用户模式的调试器,也就是gdb, gdb捕获到到这个SIGTRAP信号,根据实际程序当前停止位置查询gdb维护的断点链表,弱发现在该地址链表里面存在断点,则可判定为断点命中。当gdb判断出这次SIGTRAP是断点命中之后就会转入等待用户输入进行下一步处理,否则继续。为什么会停止下来呢?因为当使用了ptrace跟踪后,所有发送给被跟踪的子进程的信号(除了SIGKILL),都会被转发给父进程,而子进程则会被阻塞,这时子进程的状态就会被系统标注为TASK_TRACED。而父进程收到信号后,就可以对停止下来的子进程进行检查和修改,然后让子进程继续运行。

       上次面试的时候我在回答gdb的实现的时候也提到了ptrace,但ptrace是如何实现的呢,进程的地址空间是独立的,那调试进程如何获得被调试进程相关变量的值,以及如何实现对相关变量的修改。

       其实主要和ptrace的一些参数有关。

PTRACE_PEEKTEXT, PTRACE_PEEKDATA,PTRACE_PEEKUSER等

这些宏用来读取子进程的内存和用户态空间(user space).PTRACE_PEEKTEXT和PTRACE_PEEKDATA从子进程内存读取数据,两者功能是相同的.PTRACE_PEEKUSER从子进程的user space读取数据.它们读一个字节的数据,保存在临时的数据结构中,然后使用put_user()(它从内核态空间读一个字符串到用户态空间)将需要的 数据写入参数data,返回0表示成功。

对PTRACE_PEEKTEXT和PTRACE_PEEKDATA而言,参数addr是子进程内存中将被读取的数据的地址.对PTRACE_PEEKUSER来说,参数addr是子进程用户态空间的偏移量,此时data被无视。

        而在对子进程数据访问过程中,ptrace函数中对用户空间的访问通过辅助函数write_long()和read_long()函数完成的。访问进程空间的内存是通过调用Linux的分页管理机制完成的。从要访问进程的task结构中读出对进程内存的描述mm结构,并依次按页目录、中间页目录、页表的顺序查找到物理页,并进行读写操作。函数put_long()和get_long()完成的是对一个页内数据的读写操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值