在这一小节中,主要讲解一下GDB调试过程中的一些基础命令,以便大家能够快速上手GDB调试。
一、参数传递
有些程序在启动时需要接收命令行参数。例如:
./program arg1 arg2 arg3
在程序内部,这些参数通常会通过main函数的参数接收:
int main(int argc, char *argv[])
{// argc 表示参数个数// argv 表示参数内容
}
当我们使用GDB调试这类程序时,也需要把这些启动参数传递给被调试程序。GDB中常见的参数传递方式主要有以下三种。
1、启动GDB时直接传入参数
第一种方式是在启动GDB时使用--args选项:
gdb --args program arg1 arg2 arg3
例如:
gdb --args ./main hello 100
进入GDB后,直接使用run命令即可按照指定参数运行程序:
(gdb) run
这种方式比较直观,适合在启动GDB前就已经确定程序参数的场景。
需要注意的是,--args后面的内容会被GDB当作被调试程序及其参数处理。其中第一个参数是程序名,后面的内容才是传递给程序的命令行参数。
2、进入 GDB 后使用set args设置参数
第二种方式是先启动GDB,然后在GDB内部使用set args命令设置程序参数:
gdb program
进入 GDB 后:
(gdb) set args arg1 arg2 arg3
(gdb) run
例如:
gdb ./main(gdb) set args hello 100
(gdb) run
这种方式适合在GDB中多次调试同一个程序,并且希望固定使用某一组参数的情况。
使用set args设置参数后,后续再次执行run时,如果没有重新指定参数,GDB会继续使用这组参数:
(gdb) run
(gdb) run
上面两次运行都会使用之前通过set args设置的参数。
如果想查看当前设置的参数,可以使用:
(gdb) show args
如果想清空参数,可以执行:
(gdb) set args
如果想设置第二次的参数与第一次不同,可以在再次调用set args覆盖第一次的设置即可。
3、使用 run 命令时临时传入参数
第三种方式是在执行 run 命令时直接传入参数:
gdb program
进入 GDB 后:
(gdb) r arg1 arg2 arg3
例如:
gdb ./main
(gdb) run hello 100
这种方式适合临时修改程序参数,或者每次运行时都想尝试不同参数的情况。
在实际调试中,最常用的是直接在run命令后面传入参数,例如r arg1 arg2。这种方式最灵活,适合调试过程中反复修改参数。
另外一个需要注意的是GDB传递参数时,参数之间默认使用空格分隔。如果某个参数中本身包含空格,需要使用引号将其包裹起来。
例如:
(gdb) run 1 2 "3 4"
此时程序接收到的参数为:
argv[1] = "1"
argv[2] = "2"
argv[3] = "3 4"
其中,"3 4" 会作为一个完整参数传入程序,而不是被拆分成两个参数。需要注意的是,引号只是用于分组,通常不会作为参数内容的一部分传入程序。
二、附加到进程
attach主要用于调试已经运行的进程,尤其适合后台服务、守护进程、长时间运行程序、多线程程序和不方便重新启动的程序。
其中最重要的一个使用场景是调试已经运行中的进程,尤其是程序进入卡死、假死、死循环或死锁状态时。如果程序已经启动,并且运行一段时间后卡住,此时直接重新用GDB启动程序可能无法快速复现问题。更合适的方式是找到该进程的PID,然后使用GDB attach到这个进程中,查看它当前的执行上下文,进而找到出现问题的地方。
gdb attach <pid>
gdb --pid <pid>
调试流程:
1、启动应用程序
2、使用gdb --pid [pid]命令将gdb附加到进程
3、附加到进程之后,正在运行的进程会中断,之后可以进行调试与断点设置,然后执行c命令让终端的进程继续执行
4、调试完成后可使用detach命令剥离程序(程序还会继续运行,不会被GDB留在暂停状态)
5、之后可以执行quit退出GDB
注意:若出现无法attach某一个进程的情况,如下所示
Could not attach to process. If your uid matches the uid of the target
process, check the setting of /proc/sys/kernel/yama/ptrace_scope, or try
again as the root user. For more details, see /etc/sysctl.d/10-ptrace.conf
ptrace: Operation not permitted.
解决方法为:
sudo nvim /etc/sysctl.d/10-ptrace.conf
将该文件的最后一行修改为kernel.yama.ptrace_scope = 0并重启系统。
三、基础调试执行操作
在 GDB 中,程序运行到断点后,并不是只能一次性执行完,而是可以通过不同的执行命令控制程序的运行过程。常用的执行命令包括 next、step、finish 和 continue。
下面先准备一个简单的测试程序:
#include <stdio.h>int add(int a, int b)
{int sum = a + b;return sum;
}void print_result(int value)
{printf("result = %d\n", value);
}int main()
{int x = 10;int y = 20;int ret = add(x, y);print_result(ret);return 0;
}
编译完成程序之后,启动程序的GDB调试如下图所示

该调试过程,现在main函数开头打了一个断点,然后在该源文件的地17行打了一个断点,之后全速运行程序,可以看见程序在第16行中断了下来。
1、单步执行(step-over)
next(n)命令为单步执行命令,不会进入函数内部,遇到函数直接执行跳过函数。

可以通过上面的示例程序看见,在第19行代码,使用n命令,直接运行到了第21行,跳过了add函数的执行。
2、逐语句执行(step-into)
step(s)命令,遇到函数之后,使用s命令进入函数进行执行。

同样的是前面的代码例子,如果在第19行使用s命令,会跳转到add函数内部,继续执行。add函数执行完了之后,会回到函数调用处。
3、退出函数
finish命令,使用finish退出当前函数调试状态,进入上一个函数调用的地方。

同样的也是这个例子,如果在第19行进入了add函数之后,直接使用finish命令,可以提前结束该函数的过程,直接返回。
4、逐过程执行
continue(c)命令,用来逐过程全速执行程序,直到遇到断点停下,但是在程序还没启动的时候,无法使用c,即他和run命令不同,必须先run运行程序之后才能使用c命令
