gdb调试命令总结gdb调试命令的使用及总结GDB调试命令




gdb调试命令总结gdb调试命令的使用及总结GDB调试命令

2022-07-21 2:25:37 网络知识 官方管理员

使用GNU调试器来解决你的代码问题。

gdb调试命令总结(gdb调试命令的使用及总结)(1)

GNU调试器常以它的命令gdb称呼它,它是一个交互式的控制台,可以帮助你浏览源代码、分析执行的内容,其本质上是对错误的应用程序中出现的问题进行逆向工程。

故障排除的麻烦在于它很复杂。GNU调试器并不是一个特别复杂的应用程序,但如果你不知道从哪里开始,甚至不知道何时和为何你可能需要求助于GDB来进行故障排除,那么它可能会让人不知所措。如果你一直使用print、echo或printf语句来调试你的代码,当你开始思考是不是还有更强大的东西时,那么本教程就是为你准备的。

有错误的代码

要开始使用GDB,你需要一些代码。这里有一个用C++写的示例应用程序(如果你一般不使用C++编写程序也没关系,在所有语言中原理都是一样的),其来源于猜谜游戏系列中的一个例子。

#include

这个代码示例中有一个bug,但它确实可以编译(至少在GCC5的时候)。如果你熟悉C++,你可能已经看到了,但这是一个简单的问题,可以帮助新的GDB用户了解调试过程。编译并运行它就可以看到错误:

$g++-obuggyexample.cpp$./buggyHelloworld.Segmentationfault

排除段故障

从这个输出中,你可以推测变量alpha的设置是正确的,因为否则的话,你就不会看到它后面的那行代码执行。当然,这并不总是正确的,但这是一个很好的工作理论,如果你使用printf作为日志和调试器,基本上也会得出同样的结论。从这里,你可以假设bug在于成功打印的那一行之后的某行。然而,不清楚错误是在下一行还是在几行之后。

GNU调试器是一个交互式的故障排除工具,所以你可以使用gdb命令来运行错误的代码。为了得到更好的结果,你应该从包含有调试符号的源代码中重新编译你的错误应用程序。首先,看看GDB在不重新编译的情况下能提供哪些信息:

$gdb./buggyReadingsymbolsfrom./buggy...done.(gdb)startTemporarybreakpoint1at0x400a44Startingprogram:/home/seth/demo/buggyTemporarybreakpoint1,0x0000000000400a44inmain(gdb)

当你以一个二进制可执行文件作为参数启动GDB时,GDB会加载该应用程序,然后等待你的指令。因为这是你第一次在这个可执行文件上运行GDB,所以尝试重复这个错误是有意义的,希望GDB能够提供进一步的见解。很直观,GDB用来启动它所加载的应用程序的命令就是start。默认情况下,GDB内置了一个断点,所以当它遇到你的应用程序的main函数时,它会暂停执行。要让GDB继续执行,使用命令continue:

(gdb)continueContinuing.Helloworld.ProgramreceivedsignalSIGSEGV,Segmentationfault.0x00007ffff71c0c0binvfprintffrom/lib64/libc.so.6(gdb)

毫不意外:应用程序在打印“Helloworld”后不久就崩溃了,但GDB可以提供崩溃发生时正在发生的函数调用。这有可能就足够你找到导致崩溃的bug,但为了更好地了解GDB的功能和一般的调试过程,想象一下,如果问题还没有变得清晰,你想更深入地挖掘这段代码发生了什么。

用调试符号编译代码

要充分利用GDB,你需要将调试符号编译到你的可执行文件中。你可以用GCC中的-g选项来生成这个符号:

$g++-odebuggyexample.cpp$./debuggyHelloworld.Segmentationfault

将调试符号编译到可执行文件中的结果是得到一个大得多的文件,所以通常不会分发它们,以增加便利性。然而,如果你正在调试开源代码,那么用调试符号重新编译测试是有意义的:

$ls-l*buggy**cpp-rw-r--r--310Feb1908:30debug.cpp-rwxr-xr-x11624Feb1910:27buggy*-rwxr-xr-x22952Feb1910:53debuggy*

用GDB调试

加载新的可执行文件(本例中为debuggy)以启动GDB:

$gdb./debuggyReadingsymbolsfrom./debuggy...done.(gdb)startTemporarybreakpoint1at0x400a44Startingprogram:/home/seth/demo/debuggyTemporarybreakpoint1,0x0000000000400a44inmain(gdb)

如前所述,使用start命令进行:

(gdb)startTemporarybreakpoint1at0x400a48:filedebug.cpp,line9.Startingprogram:/home/sek/demo/debuggyTemporarybreakpoint1,mainatdebug.cpp:99srand(time(NULL));(gdb)

这一次,自动的main断点可以指明GDB暂停的行号和该行包含的代码。你可以用continue恢复正常操作,但你已经知道应用程序在完成之前就会崩溃,因此,你可以使用next关键字逐行步进检查你的代码:

(gdb)next10intalpha=rand%8;(gdb)next11cout

从这个过程可以确认,崩溃不是发生在设置beta变量的时候,而是执行printf行的时候。这个bug在本文中已经暴露了好几次(破坏者:向printf提供了错误的数据类型),但暂时假设解决方案仍然不明确,需要进一步调查。

设置断点

一旦你的代码被加载到GDB中,你就可以向GDB询问到目前为止代码所产生的数据。要尝试数据自省,通过再次发出start命令来重新启动你的应用程序,然后进行到第11行。一个快速到达11行的简单方法是设置一个寻找特定行号的断点:

(gdb)startTheprogrambeingdebuggedhasbeenstartedalready.Startitfromthebeginning?(yorn)yTemporarybreakpoint2at0x400a48:filedebug.cpp,line9.Startingprogram:/home/sek/demo/debuggyTemporarybreakpoint2,mainatdebug.cpp:99srand(time(NULL));(gdb)break11Breakpoint3at0x400a74:filedebug.cpp,line11.

建立断点后,用continue继续执行:

$g++-obuggyexample.cpp$./buggyHelloworld.Segmentationfault0

现在暂停在第11行,就在alpha变量被设置之后,以及beta被设置之前。

用GDB进行变量自省

要查看一个变量的值,使用print命令。在这个示例代码中,alpha的值是随机的,所以你的实际结果可能与我的不同:

$g++-obuggyexample.cpp$./buggyHelloworld.Segmentationfault1

当然,你无法看到一个尚未建立的变量的值:

$g++-obuggyexample.cpp$./buggyHelloworld.Segmentationfault2

使用流程控制

要继续进行,你可以步进代码行来到达将beta设置为一个值的位置:

$g++-obuggyexample.cpp$./buggyHelloworld.Segmentationfault3

另外,你也可以设置一个观察点,它就像断点一样,是一种控制GDB执行代码流程的方法。在这种情况下,你知道beta变量应该设置为2,所以你可以设置一个观察点,当beta的值发生变化时提醒你:

(gdb)watchbeta>0Hardwarewatchpoint5:beta>0$g++-obuggyexample.cpp$./buggyHelloworld.Segmentationfault00Oldvalue=falseNewvalue=truemainatdebug.cpp:1414printf("alphaissettois%s\n",alpha);(gdb)

你可以用next手动步进完成代码的执行,或者你可以用断点、观察点和捕捉点来控制代码的执行。

用GDB分析数据

你可以以不同格式查看数据。例如,以八进制值查看beta的值:

$g++-obuggyexample.cpp$./buggyHelloworld.Segmentationfault5

要查看其在内存中的地址:

$g++-obuggyexample.cpp$./buggyHelloworld.Segmentationfault6

你也可以看到一个变量的数据类型:

$g++-obuggyexample.cpp$./buggyHelloworld.Segmentationfault7

用GDB解决错误

这种自省不仅能让你更好地了解什么代码正在执行,还能让你了解它是如何执行的。在这个例子中,对变量运行的whatis命令给了你一个线索,即你的alpha和beta变量是整数,这可能会唤起你对printf语法的记忆,使你意识到在你的printf语句中,你必须使用%d来代替%s。做了这个改变,就可以让应用程序按预期运行,没有更明显的错误存在。

当代码编译后发现有bug存在时,特别令人沮丧,但最棘手的bug就是这样,如果它们很容易被发现,那它们就不是bug了。使用GDB是猎取并消除它们的一种方法。

下载我们的速查表

生活的真相就是这样,即使是最基本的编程,代码也会有bug。并不是所有的错误都会导致应用程序无法运行(甚至无法编译),也不是所有的错误都是由错误的代码引起的。有时,bug是基于一个特别有创意的用户所做的意外的选择组合而间歇性发生的。有时,程序员从他们自己的代码中使用的库中继承了bug。无论原因是什么,bug基本上无处不在,程序员的工作就是发现并消除它们。

GNU调试器是一个寻找bug的有用工具。你可以用它做的事情比我在本文中演示的要多得多。你可以通过GNUInfo阅读器来了解它的许多功能:

$g++-obuggyexample.cpp$./buggyHelloworld.Segmentationfault8

无论你是刚开始学习GDB还是专业人员的,提醒一下你有哪些命令是可用的,以及这些命令的语法是什么,都是很有帮助的。

  • 下载GDB速查表


发表评论:

最近发表
网站分类
标签列表