背景 #
现网代码一般需要需要长期运行,如果我们想知道某一个时刻程序的状态,用 gdb 就算是一个很好的选择。GDB是一个强大的调试器,用于检测程序中的错误和问题。它可以帮助你跟踪程序的执行,查看变量的值,分析内存,并找出程序崩溃的原因。
我们以下面的代码为例,这段代码就是一个在主线程里设置了一个死循环,它会不断地执行 gdb_test1()
和 gdb_test2()
函数,将局部变量 main_thread_var + 1,并且打印相应的值。在 gdb_test1()
和gdb_test2()
函数内部都会将全局变量 g_gdbVar + 1,并且打印 + 1 后的值。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
unsigned int g_gdbVar;
void gdb_test1() {
g_gdbVar++;
printf("gdbtest func 1, g_gdbVar is %u ...\n", g_gdbVar);
}
void gdb_test2() {
g_gdbVar++;
printf("gdbtest func 2, g_gdbVar is %u ...\n", g_gdbVar);
}
int main() {
g_gdbVar = 0;
unsigned int main_thread_var = 0;
while (1) {
gdb_test1();
sleep(1);
gdb_test2();
sleep(1);
main_thread_var++;
printf("gdbtest 3, main_thread_var is %u ...\n", main_thread_var);
sleep(1);
}
return 0;
}
运行这段代码的效果如下图
进入GDB #
下面我们开始尝试使用GDB,首先需要找到我们运行这段代码对应哪个进程,使用下面的命令来过滤查找。
ps aux | grep gdb_test
ps aux 会列出系统上所有的进程。其中,a 表示显示所有用户的进程,u 表示以用户为主的格式显示进程信息,x 表示显示没有控制终端的进程。
从上图中我们发现进程号是 26186, 然后我们进入 gdb 调试,为了方便这里建议使用 sudo 权限进入。
sudo gdb attach 26186
这个时候我们会发现正在运行的代码已经被我们断住了,它开始停止继续打印
print #
print 命令可以打出当前变量的值,比如我们想要打出全局变量 g_gdbVar 的值可以用下面的命令
print *(unsigned int*)&g_gdbVar
当我们想要看局部变量 main_thread_var 的值就会报错,因为上面静态的全局变量 g_gdbVar 是在数据段的,而局部变量在栈空间,所以 gdb 找不到局部变量的位置。
backtrace #
使用 GDB 的 backtrace
(或简写为 bt
)命令可以查看当前的函数调用堆栈,
step/next #
可以使用 step
或 next
命令来逐步执行代码,观察变量值的变化。
在GDB中,当你使用 step
命令逐行执行程序时,有时会出现显示源代码文件不存在的情况,这可能是因为系统库或特定函数的源代码并不包含在GDB调试信息中,因此GDB无法显示相应的源代码。
在上图所示的情况下,nanosleep.c
和 sleep.c
文件是系统库的文件,不一定包含在GDB的调试信息中。因此,GDB无法显示这些文件的源代码。
尽管GDB无法显示这些文件的源代码,但我们仍然可以通过查看堆栈跟踪来了解程序的执行情况。在堆栈跟踪中,你可以看到调用关系,了解函数是如何相互调用的。
break 和 continue #
break 用来设置断点,
break <line_number> # 在指定行设置断点
break <function_name> # 对函数设置断点
continue 命令继续执行程序直到下一个断点。
在GDB中,可以使用条件断点在满足特定条件时暂停程序的执行。这样可以帮助我们在程序的特定状态或特定条件下进行调试。要设置条件断点,使用 break
命令并在其后加上 if
关键字,然后指定条件。条件可以是任何合法的表达式,当该表达式的值为真时,断点会触发。
break filename.c:linenumber if g_gdbVar == 10 # 对指定行
break function_name if g_gdbVar == 10 # 对函数
delete #
在GDB中,删除断点可以使用 delete
命令。有几种方式可以删除断点:
delete # 删除所有断点
delete <breakpoint_number> 删除特定断点
确保你了解要删除的断点的编号或者类型,以免误删其他重要断点。可以使用 info breakpoints
命令来列出当前设置的断点,以查看它们的编号和类型,然后再使用 delete
命令进行删除。
call #
在 GDB 中可以使用 call
命令来调用函数,并在调试会话中执行该函数。这个命令允许你在程序执行过程中临时调用函数并查看其返回值或对程序状态进行修改。使用 call
命令的基本语法如下
call (void)gdb_test1()
call (void)gdb_test1(arg1, arg2, ...)
在调用结束后,你可以查看函数的返回值以及它对程序状态的影响。
请注意,在调试会话中调用函数可能会改变程序状态,因此在调用前确认你了解函数的行为和可能的影响,以免产生不可预料的结果。
Last modified on 2023-12-03