Linux gdb调试底层原理

@TOC

前言

linux下gdb调试程序操作过程参考本人文章:gdb调试操作; 这里不再叙述;

本文主要内容是介绍GDB本地调试的底层调试原理,我们来看一下GDB是通过什么机制来控制被调试程序的执行顺序;

总结部分是断点调试的底层原理,可以直接跳转过去先看看大概框架!

本地调试

本地调试:调试程序和被调试程序运行在同一台电脑中

图片

远程调试

远程调试:调试程序运行在一台电脑中,被调试程序运行在另一台电脑中。

图片

关于可视化调试程序并不是重点,它只是一个用来封装GDB的外壳而已,我们通过它和gdb调试程序交互。

我们既可以用黑乎乎的终端窗口来手动输入调试命令;也可以选择集成开发环境(IDE),这个IDE中已经嵌入了器调试,


GDB调试指令

随便贴几个指令,等会可以介绍到;

图片

每一个调试指令都有很多的命令选项,例如断点相关的就包括:设置断点、删除断点、条件断点、临时停用启用等等。这篇文章的重点是理解gdb底层的调试机制,所以应用层的这些指令的使用方法就不再列出了,网络上的资源很多。


GDB与被调试程序之间的关系

为了方便描述,先写一个最最简单的C程序:

#include <stdio.h>

int main(int argc, char *argv[])
{
    
    
    int a = 1;
    int b = 2;
    int c = a + b;
    printf("c = %d \n", c);
    return 0;
}

编译命令:

$ gcc -g test.c -o test //记得-g选项,生成debug版的可执行程序

我们对可执行程序 test 进行调试,输入命令:

$ gdb ./test

输出如下:

图片

在最后一行可以看到光标在闪烁,这是gdb程序在等着我们给它下达调试命令呢。

当上面这个黑乎乎的终端窗口在执行gdb ./test的时候,在操作系统里发生了很多复杂的事情:


GDB调试程序时执行的两个动作

系统首先会启动gdb调试进程,这个进程会调用系统函数fork()来创建一个子进程,这个时候子进程做两件事情:

  1. 调用系统函数ptrace(PTRACE_TRACEME,[其他参数]) 让父进程gdb追踪自己;
  2. 进而通过exec来加载、执行可执行程序test,那么test程序就在这个子exec加载的子进程中开始执行了

图片

ptrace系统调用

铺垫了半天,终于轮到主角登场了,那就是系统调用函数ptrace(其中的参数后面会解释),正是在它的帮助下,gdb才拥有了强大的调试能力。函数原型是:

#include <sys/ptrace.h>
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);

ptrace系统函数是Linux内核提供的一个用于进程跟踪的系统调用,通过它,一个进程(gdb)可以读写另外一个进程(test)的指令空间、数据空间、堆栈和寄存器的值。而且gdb进程接管了test进程的所有信号,也就是说系统向test进程发送的所有信号,都被gdb进程接收到,这样一来,test进程的执行就被gdb控制了,从而达到输入各种gdb指令,调试控制程序的目的。

注意,ptrace是子进程调用的,让父进程gdb追踪自己,然后exec替换目标调试程序;


也就是说,如果没有gdb调试,操作系统与目标进程之间是直接交互的;

如果使用gdb来调试程序,那么操作系统发送给目标进程的信号就会被gdb截获,gdb根据信号的属性来决定:在继续运行目标程序时是否把当前截获的信号转交给目标程序,如此一来,目标程序就在gdb发来的信号指挥下进行相应的动作

图片

剖析GDB如何实现断点指令(调试的底层原理)

曾经面试被问到,调试的程序为何在断点处停下?这个经常使用但是不知道底层的点,让我苦恼了很久,今天配合gdb进程和源进程的关系,可以好好分析一下断点的底层原理!


以下代码实例,gdb使用break(b)命令来研究下底层调试原理:

#include <stdio.h>

int main(int argc, char *argv[])
{
    
    
    int a = 1;
    int b = 2;
    int c = a + b;
    printf("c = %d \n", c);
    return 0;
}
  • gcc -S test.c; cat test.S 查看反汇编代码

图片

上面说到,在执行gdb ./test之后,gdb就会fork出一个子进程,这个子进程首先调用ptrace然后exec执行test程序,这样就准备好调试环境了。

在调试窗口输入设置断点指令“break 5”,此时gdb做2件事情:

  1. 对第5行源码所对应的第10行汇编代码存储到断点链表中
  2. 在汇编代码的第10行,插入中断指令INT3,也就是说:汇编代码中的第10行被替换为INT3。

INT 3指令的操作码为0xcc ,是一种专门用来调试的软中断指令,也称断点指令,cpu执行它之后,OS会发送一个SIGTRAP 3号信号给源程序; gdb捕获到之后,检测到断点会将调试的程序“挂起”暂停,进入TASK_TRACED 的T状态,等待gdb程序进一步调试;下面会详细介绍;

图片

然后,在调试窗口继续输入执行指令“run”指令(一直执行,直到遇到断点就暂停),汇编代码中PC指针执行到第10行时,发现是INT3(中断陷阱)指令,于是操作系统就发送一个SIGTRAP信号给test进程

此刻,第10行汇编代码被执行过了,PC指针就指向第11行了

图片

上面已经说过,操作系统发给test的任何信号,都被gdb接管了,也就是说gdb会首先接收到这SIGTRAP个信号,gdb发现当前汇编代码执行的是第10行,于是到断点链表中查找,发现断点链表中存储了第10行的代码说明第10行被设置了断点。于是gdb将调试的test进程挂起(进入TASK_TRACED ; T状态)! 然后又做了2个操作:

  1. 把汇编代码中的第10行"INT3"替换为断点链表中原来的代码
  2. PC 指针回退一步,也即是设置为指向第10 行刚才替换进来的代码

图片

然后,gdb继续等待用户的调试指令… 此时test进程处于挂起状态

此刻,就相当于下一条执行的指令是汇编代码中的第10行,源程序第5行; 从我们调试者角度看,就是被调试程序在源程序第5行断点处暂停了下来,此时我们可以继续输入其他调试指令来debug,比如:查看变量值、info… 查看堆栈信息、bt… 修改局部变量的值等等。也正是因为发现中断之后的恢复替换代码,PC指针回退的操作,保证了我们中断这一行之前替换掉的代码能被正常恢复和执行!


进一步剖析next指令的调试

还是以刚才的源代码和汇编代码为例,假设此时程序停止在源码的第6行,即汇编代码的第11行:

图片

在调试窗口输入单步执行指令next,我们的目的是执行一行代码,也就是把源码中第6行代码执行完,然后停止在第7行。

gdb在接收到next执行时,会计算出第7行源码(执行完第六行应该停在第七行源码的位置),应该对应到汇编代码的第14行,于是gdb就控制汇编代码中的PC指针一直执行,直到第13行执行结束,也就是PC指向第14行时,就停止下来,然后继续等待用户输入调试指令进一步调试。

图片

通过break和next这2个调试指令还有gdb调试进程和源程序的关系,以及OS系统的参与,我们已经明白了gdb中是如何处理调试指令。当然,gdb中的调试指令还有很多,包括更复杂的获取堆栈信息、修改变量的值等等,有兴趣的小伙伴可以继续深入跟踪。


如何对正在运行的进程调试?

上面说的都是对未运行的进程调试,那么怎么对正在运行的服务调试呢?:


如果想对一个已经执行的进程B进行调试,那么就要在gdb这个父进程中调用ptrace(PTRACE_ATTACH,[其他参数])(调试未运行的是子进程调用这个ptrace的,这点区分下);
此时,gdb进程会attach(绑定)到已经执行的进程B,gdb把进程B收养成为自己的子进程,对于子进程B来说等同于它自己进行了一次 PTRACE_TRACEME操作。

此时gdb进程会发送SIGTRAP信号给子进程B,子进程B接收到SIGTRAP信号后,就会暂停执行进入TASK_TRACED状态,表示自己准备好被调试了。等待下一步调试程序gdb的操作

图片


总结

假设我们想在第五行打断点停下,会发生以下事情:

启动gdb+调试程序,gdb调用fork创建子进程,这个子进程调用ptrace系统调用让gdb父进程追踪自己,紧接着进行exec程序替换将调试程序test加载进来,实现了gdb程序对test的控制权;

接着输入 b 5 命令之后 : 1.记录第5行的汇编代码到断点链表(用于确认断点和恢复代码); 2.汇编代码中INT3中断指令替换(一种软中断,中断陷阱);

接着输入 r 指令之后:调试程序运行到了INT3位置以后OS识别到中断INT3,发送SIGTRAP信号,被调试程序gdb先拿到! 在刚才的断点列表检索,发现存在这个设置的断点,直接挂起程序,然后 1.PC–,2.第五行的汇编原始代码换回来,接着等到gdb下一步操作(比如:查看变量值、查看堆栈信息、修改局部变量的值等等。)

猜你喜欢

转载自blog.csdn.net/wtl666_6/article/details/129761646
今日推荐