GDB 入门

GDB 入门

GDB (Gnu Debugger)

启动

编译时:gcc <code file> -g

  • 使可执行程序包含调试信息

启动gdb,两种方法:

  • gdb <exe>
  • gdb:先输入gdb
    • file <exe>:使用file打开程序

相关操作:

  • r/run:执行程序
  • q/quit:退出
  • s/set <var> = <value>:设置变量值
  • call:调用函数
  • b/break:设置断点
  • watch <var>:设置观察点

    • 什么是观察点?观察点是如果该点变量有变化,则调试器会暂停并主动提醒你
    • 什么时候可以设置?您只能在变量处于范围内时为其设置观察点。因此,要在另一个函数或内部块中观察某些内容,首先在该范围内设置一个断点,然后当程序在那里暂停时,设置观察点
  • d/delete:删除断点/观察点

    • d:删除全部断点/观察点
    • d <n>:删除第n个断点/观察点
  • c/continue:继续执行
  • n/next:单步调试

    • n <number>
  • s/step:进入

  • l/list:显示接下来要执行的10行代码

    • l <line number>
    • l -显示当前行前面的源程序
    • l +显示当前行后面的源程序
    • set listsize <cout>:设置一行显示源代码的行数
    • show listsize查看当前listsize的设置
    • l <first>,<last>.
    • l ,<last>:显示从当前行到linenume的行之间的源代码
    • l <filename>:<line number>/<function>
    • l <function>
  • p/print <var>/<array>[n]/<struction>.<member>/<function>:查看变量、数组、结构体字段/类成员、函数的值

  • i/info:显示信息
    • i b:显示断电信息
    • i r/reg:显示寄存器信息
  • bt/backtrace:确定你在堆栈中的位置
  • layout(重点推荐)查看并切换到layout,由于命令比较详细因此不进行具体layout介绍
    • layout split:推荐使用这个layout
  • enter(回车):重复执行上一次操作
  • ctrl+c:结束/中断gdb

设置断点

设置断点,两种方法(注意回显的语句是将要执行的语句,而不是已经执行过的语句):

  • b <函数名>:某个函数终端
  • b <codfile>:行号:某一行中断

常见变量设置

  1. 进制设置:
1
set output-radix 16(和set output-radix 10切换回来)

常见问题

段错误和核心转储

当您的程序出现段错误并转储其核心时,该怎么办?最简单的做法是在 gdb 中运行程序并一直执行直到出现段错误。当你的程序崩溃时,GDB 会站出来告诉你它在哪一行崩溃了。通常它将是程序试图操作一些内存损坏变量的行。您可以使用bt(或 backtrace)来确定您在堆栈中的位置,然后使用p(或 print)查看变量,看看它们中的任何一个是否超出了它们的界限或充满了垃圾。作为快捷方式,您可以 gdb 您的程序,然后加载 corefile 以直接进入崩溃。

参考网站

sanitizer - 一种底层的assert

段错误一般是由于非法访存造成的, 一种简单的想法是, 如果我们能在每一次访存之前都用assert()检查一下地址是否越界, 就可以在段错误发生之前捕捉到error了!

虽然我们只需要重点关注指针和数组的访问, 但这样的代码在项目中有很多, 如果要我们手动在这些访问之前添加assert(), 就太麻烦了. 事实上, 最适合做这件事情的是编译器, 因为它能知道指针和数组的访问都在哪里. 而让编译器支持这个功能的是一个叫Address Sanitizer的工具, 它可以自动地在指针和数组的访问之前插入用来检查是否越界的代码. GCC提供了一个-fsanitize=address的编译选项来启用它. menuconfig已经为大家准备好相应选项了, 你只需要打开它:

1
2
Build Options
[*] Enable address sanitizer

然后清除编译结果并重新编译即可.

你可以尝试故意触发一个段错误, 然后阅读一下Address Sanitizer的报错信息. 不过你可能会发现程序的性能有所下降, 这是因为对每一次访存进行检查会带来额外的性能开销. 但作为一个可以帮助你诊断bug的工具, 付出这一点代价还是很值得的, 而且你还是可以在无需调试的时候将其关闭.

事实上, 除了地址越界的错误之外, Address Sanitizer还能检查use-after-free的错误 (即”释放从堆区申请的空间后仍然继续使用”的错误), 你知道它是如何实现这一功能的吗?

更多的sanitizer

事实上, GCC还支持更多的sanitizer, 它们可以检查各种不同的错误, 你可以在man gcc中查阅-fsanitize相关的选项. 如果你的程序在各种sanitizer开启的情况下仍然能正确工作, 就说明你的程序还是有一定质量的.

根据上面的分析, 我们就可以总结出一些调试的建议:

  • 总是使用-Wall-Werror
  • 尽可能多地在代码中插入assert()
  • 调试时先启用sanitizer
  • assert()无法捕捉到error时, 通过printf()输出可疑的变量, 期望能观测到error
  • printf()不易观测error时, 通过GDB理解程序的精确行为

如果你在程序设计课上听说过上述这些建议, 相信你几乎不会遇到运行时错误.

无限循环

无限循环是另一个很难自行查找的错误,但使用 GDB 很容易找到。在 GDB 中加载你的程序,并运行它直到你进入循环。然后按 control-C 强制返回 GDB 提示符。输入 bt,你会被告知你在哪一行。使用list查看代码,看看是否可以找出导致循环的原因。如果不能,请使用n(或next)一次一行地通过循环,同时使用p 检查您的计数器变量。

莫名其妙的错误

大多数错误都不是那么简单的分类。通常,您的程序会无缘无故地提供错误的输出。当您遇到此类问题时,有一些技巧可以派上用场。您可以做的一件事是在您的程序要求输入后立即设置断点,然后继续 执行,定期查看堆栈和变量以确保一切按计划进行。如果您认为问题出在某个特定变量的值并且您的代码难以理解,您可以观察该变量。

下面是一个 GDB 会话的记录,其中包含一个旨在解析正则表达式的程序(该程序最初称为 rep.cc,但您不需要原始源代码来遵循调试流程)。我的评论将以 C++ 评论风格显示

TUI显示错误

  • 进入TUI模式:gdb <main.o> -tui
  • terminial下:ctrl + L刷新界面
  • vscode中:ctrl + k调整
  • 使用dashboard
  • tui模式,
  • 上下按键被锁定到了源码位置,如果想上下更新命令,需要如下快捷键:
    • ctrl + n,下一命令(Next)
    • ctrl + p,上一命令(Prev)

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!