网易首页 > 网易号 > 正文 申请入驻

手把手教你使用 GNU 调试器 | Linux 中国

0
分享至

导读:GNU 调试器是一个发现程序缺陷的强大工具。

本文字数:9815,阅读时长大约: 14分钟

GNU 调试器是一个发现程序缺陷的强大工具。

如果你是一个程序员,想在你的软件增加某些功能,你首先考虑实现它的方法:例如写一个方法、定义一个类,或者创建新的数据类型。然后你用编译器或解释器可以理解的编程语言来实现这个功能。但是,如果你觉得你所有代码都正确,但是编译器或解释器依然无法理解你的指令怎么办?如果软件大多数情况下都运行良好,但是在某些环境下出现缺陷怎么办?这种情况下,你得知道如何正确使用调试器找到问题的根源。

GNU 调试器(GNU Project Debugger)( www.gnu.org)是一个发现项目缺陷的强大工具。它通过追踪程序运行过程中发生了什么来帮助你发现程序错误或崩溃的原因。(LCTT 校注:GDB 全程是“GNU Project Debugger”,即 “GNU 项目调试器”,但是通常我们简称为“GNU 调试器”)

本文是 GDB 基本用法的实践教程。请跟随示例,打开命令行并克隆此仓库:

  1. git clone https://github.com/hANSIc99/core_dump_example.git

快捷方式

GDB 的每条命令都可以缩短。例如:显示设定的断点的info break命令可以被缩短为i break。你可能在其他地方看到过这种缩写,但在本文中,为了清晰展现使用的函数,我将所写出整个命令。

命令行参数

你可以将 GDB 附加到每个可执行文件。进入你克隆的仓库(core_dump_example),运行make进行编译。你现在能看到一个名为coredump的可执行文件。(更多信息,请参考我的文章《 opensource.com》。)

要将 GDB 附加到这个可执行文件,请输入:gdb coredump

你的输出应如下所示:

gdb coredump output

返回结果显示没有找到调试符号。

调试信息是目标文件(object file)(可执行文件)的组成部分,调试信息包括数据类型、函数签名、源代码和操作码之间的关系。此时,你有两种选择:

◈ 继续调试汇编代码(参见下文“无符号调试”)

◈ 使用调试信息进行编译,参见下一节内容

使用调试信息进行编译

为了在二进制文件中包含调试信息,你必须重新编译。打开Makefile,删除第 9 行的注释标签(#)后重新编译:

  1. CFLAGS =-Wall -Werror -std=c++11 -g

-g告诉编译器包含调试信息。运行make clean,接着运行make,然后再次调用 GDB。你得到如下输出后就可以调试代码了:

GDB output with symbols

新增的调试信息会增加可执行文件的大小。在这种情况下,执行文件增加了 2.5 倍(从 26,088 字节 增加到 65,480 字节)。

输入run -c1,使用-c1开关启动程序。当程序运行到达State_4时将崩溃:

gdb output crash on c1 switch

你可以检索有关程序的其他信息,info source命令提供了当前文件的信息:

gdb info source output

◈ 101 行代码

◈ 语言: C++

◈ 编译器(版本、调优、架构、调试标志、语言标准)

◈ 调试格式: dwarfstd.org

◈ 没有预处理器宏指令(使用 GCC 编译时,宏仅在 sourceware.org 时可用)。

info shared命令打印了动态库列表机器在虚拟地址空间的地址,它们在启动时被加载到该地址,以便程序运行:

gdb info shared output

如果你想了解 Linux 中的库处理方式,请参见我的文章 opensource.com。

调试程序

你可能已经注意到,你可以在 GDB 中使用run命令启动程序。run命令接受命令行参数,就像从控制台启动程序一样。-c1开关会导致程序在第 4 阶段崩溃。要从头开始运行程序,你不用退出 GDB,只需再次运行run命令。如果没有-c1开关,程序将陷入死循环,你必须使用Ctrl+C来结束死循环。

gdb output stopped by sigint

你也可以一步一步运行程序。在 C/C++ 中,入口是main函数。使用list main命令打开显示main函数的部分源代码:

gdb output list main

main函数在第 33 行,因此可以输入break 33在 33 行添加断点:

gdb output breakpoint added

输入run运行程序。正如预期的那样,程序在main函数处停止。输入layout src并排查看源代码:

gdb output break at main

你现在处于 GDB 的文本用户界面(TUI)模式。可以使用键盘向上和向下箭头键滚动查看源代码。

GDB 高亮显示当前执行行。你可以输入nextn)命令逐行执行命令。如果你没有指定新的命令,GBD 会执行上一条命令。要逐行运行代码,只需按回车键。

有时,你会发现文本的输出有点显示不正常:

gdb output corrupted

如果发生这种情况,请按Ctrl+L重置屏幕。

使用Ctrl+X+A可以随时进入和退出 TUI 模式。你可以在手册中找到 sourceware.org 。

要退出 GDB,只需输入quit

设置监察点

这个示例程序的核心是一个在无限循环中运行的状态机。n_state变量枚举了当前所有状态:

  1. while(true){

  2. switch(n_state){

  3. case State_1:

  4. std::cout << "State_1 reached" << std::flush;

  5. n_state = State_2;

  6. break;

  7. case State_2:

  8. std::cout << "State_2 reached" << std::flush;

  9. n_state = State_3;

  10. break;

  11. (.....)

  12. }

  13. }

如果你希望当n_state的值为State_5时停止程序。为此,请在main函数处停止程序并为n_state设置监察点:

  1. watch n_state == State_5

只有当所需的变量在当前上下文中可用时,使用变量名设置监察点才有效。

当你输入continue继续运行程序时,你会得到如下输出:

gdb output stop on watchpoint_1

如果你继续运行程序,当监察点表达式评估为false时 GDB 将停止:

gdb output stop on watchpoint_2

你可以为一般的值变化、特定的值、读取或写入时来设置监察点。

更改断点和监察点

输入info watchpoints打印先前设置的监察点列表:

gdb output info watchpoints

删除断点和监察点

如你所见,监察点就是数字。要删除特定的监察点,请先输入delete后输入监察点的编号。例如,我的监察点编号为 2;要删除此监察点,输入delete 2

注意: 如果你使用delete而没有指定数字,所有 监察点和断点将被删除。

这同样适用于断点。在下面的截屏中,我添加了几个断点,输入info breakpoint打印断点列表:

gdb output info breakpoints

要删除单个断点,请先输入delete后输入断点的编号。另外一种方式:你可以通过指定断点的行号来删除断点。例如,clear 78命令将删除第 78 行设置的断点号 7。

禁用或启用断点和监察点

除了删除断点或监察点之外,你可以通过输入disable,后输入编号禁用断点或监察点。在下文中,断点 3 和 4 被禁用,并在代码窗口中用减号标记:

disabled breakpoints

也可以通过输入类似disable 2 - 4修改某个范围内的断点或监察点。如果要重新激活这些点,请输入enable,然后输入它们的编号。

条件断点

首先,输入delete删除所有断点和监察点。你仍然想使程序停在main函数处,如果你不想指定行号,可以通过直接指明该函数来添加断点。输入break main从而在main函数处添加断点。

输入run从头开始运行程序,程序将在main函数处停止。

main函数包括变量n_state_3_count,当状态机达到状态 3 时,该变量会递增。

基于n_state_3_count的值添加一个条件断点,请输入:

  1. break 54 if n_state_3_count == 3

Set conditional breakpoint

继续运行程序。程序将在第 54 行停止之前运行状态机 3 次。要查看n_state_3_count的值,请输入:

  1. print n_state_3_count

print variable

使断点成为条件断点

你也可以使现有断点成为条件断点。用clear 54命令删除最近添加的断点,并通过输入break 54命令添加一个简单的断点。你可以输入以下内容使此断点成为条件断点:

  1. condition 3 n_state_3_count == 9

3指的是断点编号。

modify breakpoint

在其他源文件中设置断点

如果你的程序由多个源文件组成,你可以在行号前指定文件名来设置断点,例如,break main. cpp:54

捕捉点

除了断点和监察点之外,你还可以设置捕获点。捕获点适用于执行系统调用、加载共享库或引发异常等事件。

要捕获用于写入 STDOUT 的write系统调用,请输入:

  1. catch syscall write

catch syscall write output

每当程序写入控制台输出时,GDB 将中断执行。

在手册中,你可以找到一整章关于 sourceware.org 的内容。

评估和操作符号

print命令可以打印变量的值。一般语法是print <表达式> <值>。修改变量的值,请输入:

  1. set variable .

在下面的截屏中,我将变量n_state_3_count的值设为123

catch syscall write output

/x表达式以十六进制打印值;使用&运算符,你可以打印虚拟地址空间内的地址。

如果你不确定某个符号的数据类型,可以使用whatis来查明。

whatis output

如果你要列出main函数范围内可用的所有变量,请输入info scope main:

info scope main output

DW_OP_fbreg值是指基于当前子程序的堆栈偏移量。

或者,如果你已经在一个函数中并且想要列出当前堆栈帧上的所有变量,你可以使用info locals:

info locals output

查看手册以了解更多 sourceware.org 的内容。

附加调试到一个正在运行的进程

gdb attach <进程 ID>命令允许你通过指定进程 ID(PID)附加到一个已经在运行的进程进行调试。幸运的是,coredump程序将其当前 PID 打印到屏幕上,因此你不必使用 man7.org 或 man7.org 手动查找 PID。

启动coredump应用程序的一个实例:

  1. ./coredump

coredump application

操作系统显示 PID 为2849。打开一个单独的控制台窗口,移动到coredump应用程序的根目录,然后用 GDB 附加到该进程进行调试:

  1. gdb attach 2849

attach GDB to coredump

当你用 GDB 附加到进程时,GDB 会立即停止进程运行。输入layout srcbacktrace来检查调用堆栈:

layout src and backtrace output

输出显示在main.cpp第 92 行调用std::this_thread::sleep_for<...>(. ..)函数时进程中断。

只要你退出 GDB,该进程将继续运行。

你可以在 GDB 手册中找到有关 sourceware.org 的更多信息。

在堆栈中移动

在命令窗口,输入up两次可以在堆栈中向上移动到main.cpp:

moving up the stack to main.cpp

通常,编译器将为每个函数或方法创建一个子程序。每个子程序都有自己的栈帧,所以在栈帧中向上移动意味着在调用栈中向上移动。

你可以在手册中找到有关 sourceware.org 的更多信息。

指定源文件

当调试一个已经在运行的进程时,GDB 将在当前工作目录中寻找源文件。你也可以使用 ftp.gnu.org 手动指定源目录。

评估转储文件

阅读 opensource.com 了解有关此主题的信息。

参考文章太长,简单来说就是:

1. 假设你使用的是最新版本的 Fedora

2. 使用-c1开关调用 coredump:coredump -c1

Crash meme

1. 使用 GDB 加载最新的转储文件:coredumpctl debug

2. 打开 TUI 模式并输入layout src

coredump output

backtrace的输出显示崩溃发生在距离main.cpp五个栈帧之外。回车直接跳转到main.cpp中的错误代码行:

up 5 output

看源码发现程序试图释放一个内存管理函数没有返回的指针。这会导致未定义的行为并引起SIGABRT

无符号调试

如果没有源代码,调试就会变得非常困难。当我在尝试解决逆向工程的挑战时,我第一次体验到了这一点。了解一些 en.wikipedia.org 的知识会很有用。

我们用例子看看它是如何运行的。

找到根目录,打开Makefile,然后像下面一样编辑第 9 行:

  1. CFLAGS =-Wall -Werror -std=c++11 #-g

要重新编译程序,先运行make clean,再运行make,最后启动 GDB。该程序不再有任何调试符号来引导源代码的走向。

no debugging symbols

info file命令显示二进制文件的内存区域和入口点:

info file output

.text区段始终从入口点开始,其中包含实际的操作码。要在入口点添加断点,输入break *0x401110然后输入run开始运行程序:

breakpoint at the entry point

要在某个地址设置断点,使用取消引用运算符*来指定地址。

选择反汇编程序风格

在深入研究汇编之前,你可以选择要使用的 en.wikipedia.org。GDB 默认是 AT&T,但我更喜欢 Intel 语法。变更风格如下:

  1. set disassembly-flavor intel

changing assembly flavor

现在输入layout asm调出汇编代码窗口,输入layout reg调出寄存器窗口。你现在应该看到如下输出:

layout asm and layout reg output

保存配置文件

尽管你已经输入了许多命令,但实际上还没有开始调试。如果你正在大量调试应用程序或尝试解决逆向工程的难题,则将 GDB 特定设置保存在文件中会很有用。

该项目的 GitHub 存储库中的 github.com 配置文件包含最近使用的命令:

  1. set disassembly-flavor intel

  2. set write on

  3. break *0x401110

  4. run -c2

  5. layout asm

  6. layout reg

set write on命令使你能够在程序运行期间修改二进制文件。

退出 GDB 并使用配置文件重新启动 GDB :gdb -x gdbinit coredump

阅读指令

应用c2开关后,程序将崩溃。程序在入口函数处停止,因此你必须写入continue才能继续运行:

continuing execution after crash

idiv指令进行整数除法运算:RAX寄存器中为被除数,指定参数为除数。商被加载到RAX寄存器中,余数被加载到RDX中。

从寄存器角度,你可以看到RAX包含5,因此你必须找出存储堆栈中位置为rbp-0x4的值。

读取内存

要读取原始内存内容,你必须指定比读取符号更多的参数。在汇编输出中向上滚动一点,可以看到堆栈的划分:

stack division output

你最感兴趣的应该是rbp-0x4的值,因为它是idiv的存储参数。你可以从截图中看到rbp-0x8位置的下一个变量,所以rbp-0x4位置的变量是 4 字节宽。

在 GDB 中,你可以使用x命令查看任何内存内容:

x/ < 可选参数 n、f、u > < 内存地址 addr >

可选参数:

n:单元大小的重复计数(默认值:1)

f:格式说明符,如 en.wikipedia.org

u:单元大小

b:字节

h:半字(2 个字节)

◈ w: 字(4 个字节)(默认)

◈ g: 双字(8 个字节)

要打印rbp-0x4的值,请输入x/u $rbp-4:

print value

如果你能记住这种模式,则可以直接查看内存。参见手册中的 sourceware.org 部分。

操作汇编

子程序zeroDivide()发生运算异常。当你用向上箭头键向上滚动一点时,你会找到下面信息:

  1. 0x401211 <_Z10zeroDividev> push rbp

  2. 0x401212 <_Z10zeroDividev+1> mov rbp,rsp

这被称为 en.wikipedia.org:

1. 调用函数的基指针(rbp)存放在栈上

2. 栈指针(rsp)的值被加载到基指针(rbp

完全跳过这个子程序。你可以使用backtrace查看调用堆栈。在main函数之前只有一个堆栈帧,所以你可以用一次up回到main:

Callstack assembly

在你的main函数中,你会找到下面信息:

  1. 0x401431 cmp BYTE PTR [rbp-0x12],0x0

  2. 0x401435 je 0x40145f

  3. 0x401437 call 0x401211<_Z10zeroDividev>

子程序zeroDivide()仅在jump equal (je)true时进入。你可以轻松地将其替换为jump-not-equal (jne)指令,该指令的操作码为0x75(假设你使用的是 x86/64 架构;其他架构上的操作码不同)。输入run重新启动程序。当程序在入口函数处停止时,设置操作码:

  1. set *(unsigned char*)0x401435 = 0x75

最后,输入continue。该程序将跳过子程序zeroDivide()并且不会再崩溃。

总结

你会在许多集成开发环境(IDE)中发现 GDB 运行在后台,包括 Qt Creator 和 VSCodium 的 github.com 扩展。

GDB in VSCodium

了解如何充分利用 GDB 的功能很有用。一般情况下,并非所有 GDB 的功能都可以在 IDE 中使用,因此你可以从命令行使用 GDB 的经验中受益。

via:

作者: 选题: 译者: 校对:

本文由 原创编译, 荣誉推出

LCTT 译者 :Maisie-x

翻译: 1.0 篇

贡献: 10 天

2022-07-11

2022-07-20

https://linux.cn/lctt/Maisie-x

欢迎遵照 CC-BY-SA 协议规定转载,

如需转载,请在文章下留言 “ 转载:公众号名称”,

我们将为您添加白名单,授权“ 转载文章时可以修改”。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
闹大了!劳斯莱斯与出租车发生剐蹭,司机当街开骂:你瞎你还开车

闹大了!劳斯莱斯与出租车发生剐蹭,司机当街开骂:你瞎你还开车

花小萌和你聊情感
2024-12-02 21:47:50
药王谷开诊1个多月15名患者死亡,官方通报:已立案,正开展全面调查

药王谷开诊1个多月15名患者死亡,官方通报:已立案,正开展全面调查

平祥生活日志
2024-12-03 02:45:56
两性之间,除了生理上的依赖,男人最在乎女人的这4件事

两性之间,除了生理上的依赖,男人最在乎女人的这4件事

唯唯安
2024-12-01 08:00:03
叙利亚总统最新表态

叙利亚总统最新表态

环球时报新闻
2024-12-01 12:13:18
极氪晒出郭富城提车照 豪华纯电MPV 009开始香港交付

极氪晒出郭富城提车照 豪华纯电MPV 009开始香港交付

手机中国
2024-12-02 14:44:06
NBA 传闻:锡安·威廉姆森的交易价值让鹈鹕队面临残酷的现实

NBA 传闻:锡安·威廉姆森的交易价值让鹈鹕队面临残酷的现实

好火子
2024-12-03 01:49:17
辛瓦尔,加沙真战神

辛瓦尔,加沙真战神

山大星火
2024-10-18 15:09:39
现在应该是美海军近三十年最憋屈的时候!

现在应该是美海军近三十年最憋屈的时候!

华山穹剑
2024-12-02 20:02:57
被恒大坑苦的几大装修老板,只有他提前醒悟,落袋14.6亿安全离场

被恒大坑苦的几大装修老板,只有他提前醒悟,落袋14.6亿安全离场

校长侃财
2024-12-01 11:06:25
英超一场3-0,让曼城成笑柄!4700万放走中场大师:60场独造54球

英超一场3-0,让曼城成笑柄!4700万放走中场大师:60场独造54球

小火箭爱体育
2024-12-02 14:59:05
广东一男子洁身自好,却感染艾滋病,只因生活中的一个坏习惯

广东一男子洁身自好,却感染艾滋病,只因生活中的一个坏习惯

凯裕说故事
2024-09-17 15:25:35
“人老3个坎,熬过就长寿”,“三坎”是哪3年?老年人要注意!

“人老3个坎,熬过就长寿”,“三坎”是哪3年?老年人要注意!

今日养生之道
2024-11-29 15:54:56
维C抗癌再立功!新研究显示,高剂量维C,晚期胰腺癌患者生存期翻倍

维C抗癌再立功!新研究显示,高剂量维C,晚期胰腺癌患者生存期翻倍

医诺维
2024-12-02 17:09:26
杨绛:和周围人搞好关系的秘诀就是,不要和他们分享任何成功的喜悦和开心的事儿!

杨绛:和周围人搞好关系的秘诀就是,不要和他们分享任何成功的喜悦和开心的事儿!

书画艺术收藏
2024-11-29 22:02:58
孟家表示可不追究邢志强刑责,最高检追诉核准被谁瞒天过海骗得?!——内蒙邢志强案件旁听思考(2)

孟家表示可不追究邢志强刑责,最高检追诉核准被谁瞒天过海骗得?!——内蒙邢志强案件旁听思考(2)

天下说法
2024-12-02 22:02:59
马丁:火箭这战绩不该为字母哥而拆队 现在只有约基奇值得梭哈

马丁:火箭这战绩不该为字母哥而拆队 现在只有约基奇值得梭哈

直播吧
2024-12-02 20:42:20
赖清德抵达夏威夷,解放军反制来了,24小时内,中方三次警告美国

赖清德抵达夏威夷,解放军反制来了,24小时内,中方三次警告美国

阿旺视角
2024-12-02 12:04:32
正式册封后!凯特王妃成为“凯瑟琳王后”,眼神也变得凌厉起来!

正式册封后!凯特王妃成为“凯瑟琳王后”,眼神也变得凌厉起来!

金哥说新能源车
2024-12-02 12:42:58
李易峰会玩!酒店选妃看三级片做多人运动,网友安利同款看片APP

李易峰会玩!酒店选妃看三级片做多人运动,网友安利同款看片APP

扒星人
2024-12-01 14:07:26
精辟,霍勒迪与保罗·乔治就谁应该为负荷管理负责展开了实话实说

精辟,霍勒迪与保罗·乔治就谁应该为负荷管理负责展开了实话实说

好火子
2024-12-03 02:51:25
2024-12-03 04:00:49
Linux
Linux
Linux 中国开源社区
8016文章数 73120关注度
往期回顾 全部

科技要闻

钟睒睒喊话后,抖音最新回应!

头条要闻

媒体:菲律宾又向美交"投名状" 跟俄罗斯也"干上了"

头条要闻

媒体:菲律宾又向美交"投名状" 跟俄罗斯也"干上了"

体育要闻

什么?滕哈格还在曼彻斯特?

娱乐要闻

黄子韬徐艺洋官宣结婚,超般配!

财经要闻

刘世锦:扩大消费需求要找准重点或痛点

汽车要闻

小米汽车:11月交付继续超2万辆 全年冲刺13万辆

态度原创

本地
亲子
房产
家居
公开课

本地新闻

云游中国|来伦布夏果感受充满Passion的人生

亲子要闻

终于知道为什么很多人不让老人带孩子了!网友:真的没法信任!

房产要闻

海口楼市开启大反攻!10盘爆卖千套,11月销量榜曝光!

家居要闻

动线自由开合 开放中的私密

公开课

一块玻璃,如何改变人类世界?

无障碍浏览 进入关怀版