学习使用 GDB 调试代码
时间:2025-11-05 09:35:31 出处:人工智能阅读(143)

使用 GNU 调试器来解决你的学习代码问题。
GNU 调试器常以它的使用试代命令 gdb 称呼它,它是学习一个交互式的控制台,可以帮助你浏览源代码、使用试代分析执行的学习内容,其本质上是使用试代对错误的应用程序中出现的问题进行逆向工程。
故障排除的学习麻烦在于它很复杂。GNU 调试器 并不是使用试代一个特别复杂的应用程序,但如果你不知道从哪里开始,学习甚至不知道何时和为何你可能需要求助于 GDB 来进行故障排除,使用试代那么它可能会让人不知所措。学习如果你一直使用 print、使用试代echo 或 printf 语句来调试你的学习代码,当你开始思考是使用试代不是还有更强大的东西时,那么本教程就是学习为你准备的。
有错误的代码
要开始使用 GDB,b2b信息网你需要一些代码。这里有一个用 C++ 写的示例应用程序(如果你一般不使用 C++ 编写程序也没关系,在所有语言中原理都是一样的),其来源于 猜谜游戏系列 中的一个例子。
#include <iostream>#include <stdlib.h> //srand#include <stdio.h> //printfusing namespace std;int main () {srand (time(NULL));int alpha = rand() % 8;cout << "Hello world." << endl;int beta = 2;printf("alpha is set to is %sn", alpha);printf("kiwi is set to is %sn", beta); return 0;} // main这个代码示例中有一个 bug,但它确实可以编译(至少在 GCC 5 的时候)。如果你熟悉 C++,你可能已经看到了,但这是一个简单的问题,可以帮助新的 GDB 用户了解调试过程。编译并运行它就可以看到错误:
$ g++ -o buggy example.cpp$ ./buggyHello world.Segmentation fault排除段故障
从这个输出中,你可以推测变量 alpha 的设置是正确的,因为否则的话,你就不会看到它后面的那行代码执行。当然,这并不总是正确的,但这是一个很好的亿华云计算工作理论,如果你使用 printf 作为日志和调试器,基本上也会得出同样的结论。从这里,你可以假设 bug 在于成功打印的那一行之后的某行。然而,不清楚错误是在下一行还是在几行之后。
GNU 调试器是一个交互式的故障排除工具,所以你可以使用 gdb 命令来运行错误的代码。为了得到更好的结果,你应该从包含有调试符号的源代码中重新编译你的错误应用程序。首先,看看 GDB 在不重新编译的情况下能提供哪些信息:
$ gdb ./buggyReading symbols from ./buggy...done.(gdb) startTemporary breakpoint 1 at 0x400a44Starting program: /home/seth/demo/buggyTemporary breakpoint 1, 0x0000000000400a44 in main ()(gdb)当你以一个二进制可执行文件作为参数启动 GDB 时,GDB 会加载该应用程序,然后等待你的指令。因为这是你第一次在这个可执行文件上运行 GDB,所以尝试重复这个错误是有意义的,免费信息发布网希望 GDB 能够提供进一步的见解。很直观,GDB 用来启动它所加载的应用程序的命令就是 start。默认情况下,GDB 内置了一个断点,所以当它遇到你的应用程序的 main 函数时,它会暂停执行。要让 GDB 继续执行,使用命令 continue:
(gdb) continueContinuing.Hello world.Program received signal SIGSEGV, Segmentation fault.0x00007ffff71c0c0b in vfprintf () from /lib64/libc.so.6(gdb)毫不意外:应用程序在打印 “Hello world” 后不久就崩溃了,但 GDB 可以提供崩溃发生时正在发生的函数调用。这有可能就足够你找到导致崩溃的 bug,但为了更好地了解 GDB 的功能和一般的调试过程,想象一下,如果问题还没有变得清晰,你想更深入地挖掘这段代码发生了什么。
用调试符号编译代码
要充分利用 GDB,你需要将调试符号编译到你的可执行文件中。你可以用 GCC 中的 -g 选项来生成这个符号:
$ g++ -o debuggy example.cpp$ ./debuggyHello world.Segmentation fault将调试符号编译到可执行文件中的结果是得到一个大得多的文件,所以通常不会分发它们,以增加便利性。然而,如果你正在调试开源代码,那么用调试符号重新编译测试是有意义的:
$ ls -l *buggy* *cpp-rw-r--r-- 310 Feb 19 08:30 debug.cpp-rwxr-xr-x 11624 Feb 19 10:27 buggy*-rwxr-xr-x 22952 Feb 19 10:53 debuggy*用 GDB 调试
加载新的可执行文件(本例中为 debuggy)以启动 GDB:
$ gdb ./debuggyReading symbols from ./debuggy...done.(gdb) startTemporary breakpoint 1 at 0x400a44Starting program: /home/seth/demo/debuggyTemporary breakpoint 1, 0x0000000000400a44 in main ()(gdb)如前所述,使用 start 命令进行:
(gdb) startTemporary breakpoint 1 at 0x400a48: file debug.cpp, line 9.Starting program: /home/sek/demo/debuggyTemporary breakpoint 1, main () at debug.cpp:99 srand (time(NULL));(gdb)这一次,自动的 main 断点可以指明 GDB 暂停的行号和该行包含的代码。你可以用 continue 恢复正常操作,但你已经知道应用程序在完成之前就会崩溃,因此,你可以使用 next 关键字逐行步进检查你的代码:
(gdb) next10 int alpha = rand() % 8;(gdb) next11 cout << "Hello world." << endl;(gdb) nextHello world.12 int beta = 2;(gdb) next14 printf("alpha is set to is %s\n", alpha);(gdb) nextProgram received signal SIGSEGV, Segmentation fault.0x00007ffff71c0c0b in vfprintf () from /lib64/libc.so.6(gdb)从这个过程可以确认,崩溃不是发生在设置 beta 变量的时候,而是执行 printf 行的时候。这个 bug 在本文中已经暴露了好几次(破坏者:向 printf 提供了错误的数据类型),但暂时假设解决方案仍然不明确,需要进一步调查。
设置断点
一旦你的代码被加载到 GDB 中,你就可以向 GDB 询问到目前为止代码所产生的数据。要尝试数据自省,通过再次发出 start 命令来重新启动你的应用程序,然后进行到第 11 行。一个快速到达 11 行的简单方法是设置一个寻找特定行号的断点:
(gdb) startThe program being debugged has been started already.Start it from the beginning? (y or n) yTemporary breakpoint 2 at 0x400a48: file debug.cpp, line 9.Starting program: /home/sek/demo/debuggyTemporary breakpoint 2, main () at debug.cpp:99 srand (time(NULL));(gdb) break 11Breakpoint 3 at 0x400a74: file debug.cpp, line 11.建立断点后,用 continue 继续执行:
(gdb) continueContinuing.Breakpoint 3, main () at debug.cpp:1111 cout << "Hello world." << endl;(gdb)现在暂停在第 11 行,就在 alpha 变量被设置之后,以及 beta 被设置之前。
用 GDB 进行变量自省
要查看一个变量的值,使用 print 命令。在这个示例代码中,alpha 的值是随机的,所以你的实际结果可能与我的不同:
(gdb) print alpha$1 = 3(gdb)当然,你无法看到一个尚未建立的变量的值:
(gdb) print beta$2 = 0使用流程控制
要继续进行,你可以步进代码行来到达将 beta 设置为一个值的位置:
(gdb) nextHello world.12 int beta = 2;(gdb) next14 printf("alpha is set to is %s\n", alpha);(gdb) print beta$3 = 2另外,你也可以设置一个观察点,它就像断点一样,是一种控制 GDB 执行代码流程的方法。在这种情况下,你知道 beta 变量应该设置为 2,所以你可以设置一个观察点,当 beta 的值发生变化时提醒你:
(gdb) watch beta > 0Hardware watchpoint 5: beta > 0(gdb) continueContinuing.Breakpoint 3, main () at debug.cpp:1111 cout << "Hello world." << endl;(gdb) continueContinuing.Hello world.Hardware watchpoint 5: beta > 0Old value = falseNew value = truemain () at debug.cpp:1414 printf("alpha is set to is %s\n", alpha);(gdb)你可以用 next 手动步进完成代码的执行,或者你可以用断点、观察点和捕捉点来控制代码的执行。
用 GDB 分析数据
你可以以不同格式查看数据。例如,以八进制值查看 beta 的值:
(gdb) print /o beta$4 = 02要查看其在内存中的地址:
(gdb) print /o beta$5 = 0x2你也可以看到一个变量的数据类型:
(gdb) whatis betatype = int用 GDB 解决错误
这种自省不仅能让你更好地了解什么代码正在执行,还能让你了解它是如何执行的。在这个例子中,对变量运行的 whatis 命令给了你一个线索,即你的 alpha 和 beta 变量是整数,这可能会唤起你对 printf 语法的记忆,使你意识到在你的 printf 语句中,你必须使用 %d 来代替 %s。做了这个改变,就可以让应用程序按预期运行,没有更明显的错误存在。
当代码编译后发现有 bug 存在时,特别令人沮丧,但最棘手的 bug 就是这样,如果它们很容易被发现,那它们就不是 bug 了。使用 GDB 是猎取并消除它们的一种方法。
下载我们的速查表
生活的真相就是这样,即使是最基本的编程,代码也会有 bug。并不是所有的错误都会导致应用程序无法运行(甚至无法编译),也不是所有的错误都是由错误的代码引起的。有时,bug 是基于一个特别有创意的用户所做的意外的选择组合而间歇性发生的。有时,程序员从他们自己的代码中使用的库中继承了 bug。无论原因是什么,bug 基本上无处不在,程序员的工作就是发现并消除它们。
GNU 调试器是一个寻找 bug 的有用工具。你可以用它做的事情比我在本文中演示的要多得多。你可以通过 GNU Info 阅读器来了解它的许多功能:
$ info gdb无论你是刚开始学习 GDB 还是专业人员的,提醒一下你有哪些命令是可用的,以及这些命令的语法是什么,都是很有帮助的。
下载 GDB 速查表猜你喜欢
- ubuntu安装了wine qq怎么去卸载呢?下面我们分别来演示如何卸载它们1、安装wine按ctrl+alter+T打开终端输入以下两条命令sudo apt-get updatesodo apt-get install wine安装时间有点长,请耐心的等候2、按钮选择期间有个软件包的配置图像界面,需要用户使用tab键选定ok然后下一个条出另一个框,这里移动左右键盘,选择YES。按下enter键进行安装、、、、、3、安装wine-qq下载wine-qq的网址:http://www.longene.org/download/sudo dpkg -i WineQQ2013-xxx(你下载的QQ web包)4、卸载wine-qqsudo dpkg --purge wine-qq2012-longeneteam
- GO语言学习系列之Go语言面试题(二)
- Go语言开发的消息系统有哪些?
- 【Linux SRE培训】只需三分钟,带你快速掌握Nginx服务!
- 电脑剪影教程(学会使用剪影工具,轻松制作独特而精美的视觉设计)
- Linux运维基础之文件属性block
- 从拼凑式架构到Data Warebase:一场由Agent引发的千亿级数据架构革命
- 【面试题】Linux运维之网路基础有哪些重点?
- 大家好,本教程将学习如何在 ubuntu 15.04 上面安装 puppet,它可以用来管理你的服务器基础环境。puppet 是由 puppet 实验室Puppet Labs开发并维护的一款开源的配置管理软件,它能够帮我们自动化供给、配置和管理服务器的基础环境。不管我们管理的是几个服务器还是数以千计的计算机组成的业务报表体系,puppet 都能够使管理员从繁琐的手动配置调整中解放出来,腾出时间和精力去提系统的升整体效率。它能够确保所有自动化流程作业的一致性、可靠性以及稳定性。它让管理员和开发者更紧密的联系在一起,使开发者更容易产出付出设计良好、简洁清晰的代码。puppet 提供了配置管理和数据中心自动化的两个解决方案。这两个解决方案分别是 puppet 开源版 和 puppet 企业版。puppet 开源版以 Apache 2.0 许可证发布,它是一个非常灵活、可定制的解决方案,设置初衷是帮助管理员去完成那些重复性操作工作。pupprt 企业版是一个全平台复杂 IT 环境下的成熟解决方案,它除了拥有开源版本所有优势以外还有移动端 apps、只有商业版才有的加强支持,以及模块化和集成管理等。Puppet 使用 SSL 证书来认证主控服务器与代理节点之间的通信。本教程将要介绍如何在运行 ubuntu 15.04 的主控服务器和代理节点上面安装开源版的 puppet。在这里,我们用一台服务器做主控服务器master,管理和控制剩余的当作 puppet 代理节点agent node的服务器,这些代理节点将依据主控服务器来进行配置。在 ubuntu 15.04 只需要简单的几步就能安装配置好 puppet,用它来管理我们的服务器基础环境非常的方便。(LCTT 译注:puppet 采用 C/S 架构,所以必须有至少有一台作为服务器,其他作为客户端处理)复制代码代码如下:注意,puppet 主控服务器必使用 8140 端口来运行,所以请务必保证开启8140端口。2. 用 NTP 更新时间复制代码代码如下:17 Jun 00:17:08 ntpdate[882]: adjust time server 66.175.209.17 offset -0.001938 sec 复制代码代码如下:复制代码代码如下:下载完成,我们来安装它:复制代码代码如下:使用 apt 包管理命令更新一下本地的软件源:复制代码代码如下:现在我们就可以安装 puppetmaster-passenger 了复制代码代码如下:提示: 在安装的时候可能会报错:复制代码代码如下:现在我们已经安装好了 puppet 主控服务器。因为我们使用的是配合 apache 的 passenger,由 apache 来控制 puppet 主控服务器,当 apache 运行时 puppet 主控服务器才运行。在开始之前,我们需要通过停止 apache 服务来让 puppet 主控服务器停止运行。复制代码代码如下:复制代码代码如下:在新创建的文件里面添加以下内容:复制代码代码如下:这样在以后的系统软件升级中, puppet 主控服务器将不会跟随系统软件一起升级。5. 配置 Puppet 主控服务器复制代码代码如下:现在来配置该证书,在创建 puppet 主控服务器证书时,我们需要包括代理节点与主控服务器沟通所用的每个 DNS 名称。使用文本编辑器来修改服务器的配置文件 puppet.conf:复制代码代码如下:输出的结果像下面这样复制代码代码如下:在这我们需要注释掉 templatedir 这行使它失效。然后在文件的 [main] 小节的结尾添加下面的信息。复制代码代码如下:编辑完成后保存退出。使用下面的命令来生成一个新的证书。复制代码代码如下:至此,证书已经生成。一旦我们看到 Notice: Starting Puppet master version 3.8.1,就表明证书就已经制作好了。我们按下 CTRL-C 回到 shell 命令行。查看新生成证书的信息,可以使用下面的命令。复制代码代码如下:复制代码代码如下:在刚打开的文件里面添加下面这几行:复制代码代码如下:以上这几行的意思是给代理节点部署 apache web 服务。7. 运行 puppet 主控服务复制代码代码如下:我们 puppet 主控服务器已经运行,不过它还不能管理任何代理节点。现在我们给 puppet 主控服务器添加代理节点.提示: 假如报错Job for apache2.service failed. see systemctl status apache2.service and journalctl -xe for details.复制代码代码如下:使用 apt 包管理命令更新一下本地的软件源:复制代码代码如下:通过远程仓库安装:复制代码代码如下:Puppet 代理默认是不启动的。这里我们需要使用文本编辑器修改 /etc/default/puppet 文件,使它正常工作:复制代码代码如下:更改 START 的值改成 yes 。复制代码代码如下:最后保存并退出。9. 使用 Apt 工具锁定代理软件的版本复制代码代码如下:在新建的文件里面加入如下内容复制代码代码如下:这样 puppet 就不会随着系统软件升级而随意升级了。10. 配置 puppet 代理节点复制代码代码如下:它看起来和服务器的配置文件完全一样。同样注释掉 templatedir 这行。不同的是在这里我们需要删除掉所有关于[master] 的部分。假定主控服务器可以通过名字“puppet-master”访问,我们的客户端应该可以和它相互连接通信。假如不行的话,我们需要使用完整的主机域名 puppetmaster.example.com复制代码代码如下:在文件的结尾增加上面3行,增加之后文件内容像下面这样:复制代码代码如下:最后保存并退出。使用下面的命令来启动客户端软件:复制代码代码如下:假如一切顺利的话,我们不会看到命令行有任何输出。 第一次运行的时候,代理节点会生成一个 ssl 证书并且给服务器发送一个请求,经过签名确认后,两台机器就可以互相通信了。提示: 假如这是你添加的第一个代理节点,建议你在添加其他节点前先给这个证书签名。一旦能够通过并正常运行,回过头来再添加其他代理节点。11. 在主控服务器上对证书请求进行签名复制代码代码如下:因为只设置了一台代理节点服务器,所以我们将只看到一个请求。看起来类似如上,代理节点的完整域名即其主机名。注意有没有“+”号在前面,代表这个证书有没有被签名。使用带有主机名的 puppet cert sign 这个命令来签署这个签名请求,如下:复制代码代码如下:主控服务器现在可以通讯和控制它签名过的代理节点了。假如想签署所有的当前请求,可以使用 -all 选项,如下所示:复制代码代码如下:复制代码代码如下:假如我们想查看所有的签署和未签署的请求,使用下面这条命令:复制代码代码如下:复制代码代码如下:这里向我们展示了主清单如何立即影响到了一个单一的服务器。假如我们打算运行的 puppet 清单与主清单没有什么关联,我们可以简单使用 puppet apply 带上相应的清单文件的路径即可。它仅将清单应用到我们运行该清单的代理节点上。复制代码代码如下:复制代码代码如下:添加下面的内容进去复制代码代码如下:这里的配置显示我们将在名为 puppetnode 和 puppetnode1 的2个指定的节点上面安装 apache 服务。这里可以添加其他我们需要安装部署的具体节点进去。15. 配置清单模块复制代码代码如下:警告: 千万不要在一个已经部署 apache 环境的机器上面使用这个模块,否则它将清空你没有被 puppet 管理的 apache 配置。现在用文本编辑器来修改 site.pp :复制代码代码如下:添加下面的内容进去,在 puppetnode 上面安装 apache 服务。复制代码代码如下:保存退出。然后重新运行该清单来为我们的代理节点部署 apache 配置。总结现在我们已经成功的在 ubuntu 15.04 上面部署并运行 puppet 来管理代理节点服务器的基础运行环境。我们学习了 puppet 是如何工作的,编写清单文件,节点与主机间使用 ssl 证书认证的认证过程。使用 puppet 开源软件配置管理工具在众多的代理节点上来控制、管理和配置重复性任务是非常容易的。