全网最全GDB调试整理
GDB(GNU Debugger)是GNU开源组织发布的一个强大的Linux下的程序调试工具。它主要用来调试C/C++语言写的程序,但也可以调试其他语言程序。
GDB提供了丰富的命令来实现相关功能,如break设置断点、run启动程序、next单步执行、continue继续执行、print打印变量值等。此外,GDB还支持条件断点、断点命令列表、监视点等高级功能,满足开发者在调试过程中的各种需求。
GDB提供了以下调试功能:
一、启动程序:
GDB可以按照自定义的要求启动程序,例如设置参数、环境变量等。
在GDB中,启动程序主要有两种方式:使用run命令或者start命令。
- run命令:这是最常用的启动程序的方式。在GDB中输入run(或者简写为r)后,程序会开始执行。如果程序中设置了断点,那么程序会执行到第一个断点处暂停;如果没有设置断点,程序会一直执行到结束。此外,run命令也可以带参数,这些参数会被传递给被调试的程序。
- start命令:与run命令不同,start命令会执行程序至main()主函数的起始位置,即在main()函数的第一行语句处停止执行(该行代码尚未执行)。可以理解为,使用start命令启动程序,完全等价于先在main()主函数起始位置设置一个断点,然后再使用run命令启动程序。
二、断点管理:
GDB中的断点管理是一项关键功能,允许开发者在特定位置暂停程序执行以进行检查。以下是关于断点管理的一些常用操作和说明:
- 设置断点:
- 使用break或b命令可以在源代码的特定行或函数上设置断点。例如,break filename:linenum在filename文件的linenum行设置断点,而break function_name在给定的function_name函数处设置断点。
- 也可以通过条件表达式来设置断点,格式如break location if condition,其中location可以是行号、函数名等,condition是一个条件表达式。只有当该条件为真时,断点才会生效。
- 查看断点:
- 可以通过info breakpoints命令查看所有设置的断点,包括断点的位置、是否启用、类型、条件和命中次数等信息。
- 删除断点:
- 使用delete或d命令后接断点编号,可以删除特定的断点,例如delete 1删除编号为1的断点。
- 要删除所有断点,可以使用delete命令而不带任何参数。
- 禁用和启用断点:
- 可以使用disable和enable命令后接断点编号来禁用或启用特定的断点。例如,disable 2将禁用编号为2的断点,而enable 2将重新启用它。
- 断点的持久性:
- 在GDB中设置的断点默认情况下是非持久的,也就是说,当你退出GDB并重新启动程序时,这些断点将不再存在。然而,有些GDB版本和前端可能提供了保存和恢复断点的功能,使断点具有持久性。这通常涉及将断点信息保存到一个文件中,并在启动GDB时加载该文件。
- 临时断点:
- 除了普通断点外,GDB还支持所谓的“临时断点”(也称为“一次性断点”)。这种类型的断点在程序到达其位置后会自动删除,因此只会命中一次。这可以通过特定的GDB命令或前端界面来实现。
三、检查程序状态:
当程序被暂停时,GDB可以查看程序中的变量值、内存内容、函数调用栈等信息,帮助开发者了解程序当前的状态。
在GDB中,当程序被暂停时,你可以检查程序的状态,这包括查看变量的值、内存内容、寄存器的内容,以及当前的函数调用栈等信息。
- 查看变量值:使用print命令(或简写为p)可以查看变量的值。例如,print var_name会显示变量var_name的当前值。你也可以使用print命令进行一些复杂的表达式计算,如print var1 + var2。
- 查看内存内容:x命令可以用来检查内存的内容。你可以指定要查看的内存地址和格式。例如,x/16xb address会以16进制格式显示从address开始的16个字节的内容。
- 查看寄存器内容:你可以使用info registers命令查看当前所有寄存器的值。如果你只想查看某个特定的寄存器,可以使用print命令,如print $eax。
- 查看函数调用栈:使用backtrace命令(或简写为bt)可以查看当前的函数调用栈。这会列出从主函数到当前函数的所有函数调用,以及每个函数的参数值。你还可以使用frame命令切换到调用栈中的某个特定函数帧。
- 查看源代码:GDB还可以显示当前执行的源代码行。使用list命令(或简写为l)可以查看当前的源代码,并允许你在源代码中设置断点或查看变量值。
四、单步执行:
GDB支持逐条执行程序中的指令,方便开发者逐步跟踪程序的执行过程。
在GDB中,单步执行是一种重要的调试手段,它允许开发者逐条执行程序中的指令,以便仔细观察程序的行为。GDB提供了几种不同的单步执行命令,以满足不同的调试需求。
- next (或 n):
- 使用next命令可以执行程序中的下一行代码。如果当前行是一个函数调用,next命令会执行整个函数,然后在函数返回后暂停。
- 简而言之,next命令步进到下一行代码,但不进入函数内部。
- step (或 s):
- 与next不同,step命令在执行下一行代码时,如果遇到函数调用,会进入函数内部并逐条执行函数内的指令。
- 使用step命令可以深入函数内部进行调试。
- finish:
- 当已经步入一个函数内部,并希望快速执行完剩余部分时,可以使用finish命令。它会继续执行程序直到从当前函数中返回。
- 这个命令在已经了解函数内部行为,只想快速跳到函数完成后的状态时非常有用。
- continue (或 c):
- 虽然不是传统意义上的单步执行命令,但continue命令在调试过程中也很有用。它会继续执行程序,直到遇到下一个断点或程序结束。
- 在设置了断点后,使用continue可以让程序快速运行到下一个关注点。
- until:
- until命令用于在循环体内单步执行,直到退出循环。如果当前行不在循环体内,则等同于next命令。
- 这个命令在调试循环结构时特别有用,可以避免手动步进整个循环。
- advance:
- advance命令可以指定向前执行到程序的某个位置,这个位置可以是源代码行号、函数名或某个特定的地址。不过请注意,并非所有GDB版本都支持此命令。
五、继续执行:
GDB可以在暂停后继续执行程序,直到遇到下一个断点或程序结束。
在GDB中,继续执行被调试的程序是一个常见的操作,尤其是在设置了断点或进行了单步执行之后。以下是关于如何在GDB中继续执行程序的信息:
continue命令(或简写为c):
这个命令用于继续执行被调试的程序。在程序暂停后(例如,由于遇到了断点或执行了单步调试命令),你可以使用continue命令恢复程序的执行。程序将继续运行,直到遇到下一个断点、接收到一个信号,或者程序正常结束。
示例:
(gdb) continue
或者简写为:
(gdb) c
如果希望程序继续执行并跳过一定数量的断点,可以在continue命令后加上一个数字,表示希望跳过的断点次数(包括当前这一次)。例如:
(gdb) continue 5
上述命令将使程序继续执行,并跳过接下来的5个断点(但请注意,这种用法可能不是GDB的标准功能;标准的GDB通常只支持不带参数的continue命令)。然而,在实际使用中,GDB通常只会继续执行到下一个断点或程序结束,而不支持跳过指定数量的断点。如果需要跳过某些断点,可以考虑临时禁用或删除这些断点。
总的来说,在GDB中使用continue命令是继续执行被调试程序的标准方式。在执行该命令之前,请确保已经设置了适当的断点或其他停止条件,以防止程序无限制地运行下去。
六、修改程序:
GDB允许在调试过程中修改程序中的变量值或内存内容,这对于测试某些特定条件非常有用。
在GDB中,修改程序通常指的是在调试过程中动态地改变变量的值或内存区域的内容。这种能力对于测试和调试非常有用,尤其是在需要模拟特定条件或绕过错误时。
以下是在GDB中修改程序的一些方法:
- 设置变量值:
使用set命令可以修改变量的值。例如,set variable_name = new_value会将variable_name的值更改为new_value。这适用于全局变量和局部静态变量。对于非静态局部变量,你可能需要在正确的栈帧中操作。 - 打印和修改内存:
使用x命令可以查看内存的内容,而set命令也可以用来修改内存。例如,set (int)address = value会将位于address地址的整数设置为value。这种方法需要小心使用,因为它可能破坏程序的数据结构或内存布局。 - 调用函数:
在GDB中,你可以使用call命令调用函数,这可能会改变程序的状态。例如,call function_name(arguments)会调用function_name并传递arguments。调用函数可能会影响程序的行为,因此在使用时需要谨慎。 - 跳转执行:
尽管不常推荐,但在某些情况下,你可能想要改变程序的执行流程。使用jump命令(如果GDB支持的话)可以跳转到程序的另一个位置执行。然而,这可能会破坏程序的正常执行流程,因此应该非常小心地使用。 - 修改寄存器:
在某些体系结构和GDB版本中,你可以使用set命令修改寄存器的值。例如,set $register_name = value会设置寄存器的值。然而,直接修改寄存器通常是一个高级操作,需要对底层硬件和程序执行有深入的了解。
七、查看源代码:
GDB可以显示当前执行的源代码行,并允许开发者在源代码中设置断点或查看变量值。
以下是在GDB中查看源代码的几种方法:
- 使用list命令:
GDB的list命令(或简写为l)可以用来显示当前行的源代码以及周围的代码。你可以指定要显示的行号或函数名。例如,list function_name将显示名为function_name的函数的源代码,而list linenum将显示指定行号linenum周围的代码。如果不带任何参数,list命令通常会显示当前执行点周围的代码。 - 使用TUI模式:
GDB支持一个称为TUI(Text User Interface)的模式,它可以在一个窗口中同时显示源代码和GDB命令提示符。你可以通过按Ctrl+X然后按A(在某些配置中可能是Ctrl+X 2)来启用TUI模式。在这个模式下,源代码会显示在一个单独的窗口中,你可以使用GDB命令进行导航和调试。 - 使用layout src命令:
与TUI模式类似,layout src命令也可以用来在GDB会话中显示源代码窗口。这个命令会改变GDB的界面布局,以便更清晰地查看源代码和其他调试信息。你可以通过再次输入layout src或切换到其他布局来关闭源代码窗口。 - 设置断点并导航:
虽然这不是直接查看源代码的命令,但设置断点并导航到断点位置也是一种有效的方法来查看特定部分的源代码。你可以使用break命令设置断点,然后使用run或continue命令运行程序直到达到断点。一旦程序在断点处暂停,你就可以使用list命令来查看当前位置的源代码。 - 使用GDB前端工具:
除了GDB本身提供的命令之外,还有一些GDB前端工具(如DDD、Nemiver、CGDB等)提供了更丰富的界面和源代码导航功能。这些工具通常提供图形用户界面,使得查看和编辑源代码变得更加直观和方便。
八、反汇编:
GDB可以将程序的机器码反汇编成汇编语言,方便开发者分析程序的底层执行过程。
信号处理:GDB可以捕获并处理程序运行时产生的信号(如SIGSEGV、SIGINT等),帮助开发者定位和处理程序中的异常。
九、多线程调试:
GDB支持多线程程序的调试,可以查看各个线程的状态、切换线程等。
以下是在GDB中进行多线程调试的一些基本步骤和技巧:
- 编译程序以包含调试信息:
在编译程序时,确保使用-g选项来包含调试信息。例如,使用gcc编译器时,可以这样编译程序:
gcc -g -o myprogram myprogram.c -lpthread
这里-lpthread是链接多线程库。
- 启动GDB并加载程序:
使用GDB启动程序,并附加到要调试的进程上:
gdb ./myprogram
- 设置断点:
在GDB中,你可以设置断点来暂停特定线程的执行。你可以使用break命令(或简写为b)来设置断点,并指定线程号、函数名或代码行号。例如:
break function_name
break filename:linenumber
对于特定线程的断点,你可能需要先使用info threads来查看线程列表,然后在特定线程的上下文中设置断点。然而,GDB本身并不直接支持仅针对特定线程的断点。但你可以通过设置条件断点来模拟这个功能。例如,你可以在一个函数内部设置一个断点,并附加一个条件来检查当前线程ID:
break function_name if pthread_self() == some_thread_id
但请注意,pthread_self()返回的是线程ID的类型(通常是pthread_t),它可能不适合直接与某个值比较。你可能需要找到其他方式来识别线程,比如使用线程特定的数据或全局变量。
- 查看线程信息:
使用info threads命令可以查看当前调试程序中的所有线程。GDB会为每个线程分配一个唯一的编号,并显示线程的状态和栈帧信息。 - 切换线程:
使用thread命令加上线程编号可以切换到特定的线程上下文。例如:
thread 2
这会切换到编号为2的线程。
- 步进和继续执行:
你可以使用next、step、continue等命令来控制程序的执行流程。在多线程环境中,这些命令会影响当前选中的线程。其他线程可能会继续并发执行。 - 使用线程相关的命令:
GDB提供了一些与线程相关的命令,如set scheduler-locking,它允许你控制线程调度。例如,你可以将调度器锁定为仅允许当前线程执行:
set scheduler-locking on
但要小心使用,因为这可能会改变程序的实际并发行为。
- 检查共享资源和同步原语:
在多线程程序中,要特别注意对共享资源的访问和同步原语的使用。使用GDB检查互斥锁、条件变量、信号量等的状态和行为。
记住,多线程调试可能会很复杂,因为你需要跟踪多个线程的执行和交互。确保你理解程序的并发模型和同步机制,以便更有效地使用GDB进行调试。
十、调试coredump文件:
当程序崩溃时,GDB可以分析coredump文件,帮助开发者定位崩溃原因
当程序崩溃时,可以使用GNU调试器(GDB)来查看崩溃信息并进行调试。GDB是一个功能强大的调试工具,它允许你在程序崩溃时检查程序的内部状态,包括变量值、函数调用栈等信息。
以下是在Linux系统下使用GDB查看程序崩溃的一般步骤:
- 确保你的程序已经编译为包含调试信息的可执行文件。在编译时,使用带有"-g"选项的编译器来生成调试信息。例如,使用gcc编译器时,可以添加"-g"选项:
gcc -g -o myprogram myprogram.c
- 运行你的程序,如果它崩溃了,它应该会生成一个core dump文件(默认情况下,这个文件可能名为"core"或者类似的名称,但具体名称可能因系统配置而异)。你可以通过修改系统的core dump配置来确保生成这个文件。例如,在bash shell中,你可以运行以下命令来启用core dump:
ulimit -c unlimited
- 这将设置core dump文件的大小为无限制。请注意,这个设置可能只对当前shell会话有效。如果你想让这个设置在系统重启后仍然有效,你可能需要修改系统的配置文件(如"/etc/security/limits.conf")。
使用GDB打开你的程序和一个core dump文件。假设你的程序名为"myprogram",core dump文件名为"core",你可以运行以下命令:
gdb myprogram core
- GDB会加载你的程序和core dump文件,并显示一些初始信息。然后,你可以使用GDB的命令来查看程序的崩溃信息。以下是一些常用的GDB命令:
- bt(backtrace):显示函数调用栈的回溯信息。这将列出程序崩溃时正在执行的函数调用序列。
- frame:选择特定的栈帧。你可以使用这个命令来查看特定函数调用的局部变量和参数。
- list:显示当前函数的源代码。你可以使用这个命令来查看崩溃发生在哪一行代码上。
- print:打印变量的值。你可以使用这个命令来检查崩溃时变量的状态。
- 当你完成调试后,可以使用quit命令退出GDB。
请注意,以上步骤中的具体命令和文件名可能因你的系统和编译环境而异。如果你遇到任何问题或困惑,请参考GDB的官方文档或搜索相关的在线资源来获取更多帮助。