GDB 入门--断点, 信息浏览, 运行过程控制
[TOC]
本文介绍 GDB 中比较核心的操作: 断点操作, 信息浏览, 运行过程控制的入门.
断点
GDB 调试器支持在程序中打 4 种断点: 普通断点(breakpoints) , 观察断点(watchpoints), 捕捉断点(catchpoints), 跟踪断点(tracepoints).
每个断点都有其编号(记为 bnum, 正整数序列), 方便后面的管理与操作.
普通断点
创建断点: break
( b
) 命令
- 位置断点:
(gdb) break location
. location 可以为以下类型.
linenum: 是一个整数, 表示当前代码文件的行号. 如何知道程序中各行代码都有对应的行号 => 可通过执行
l
命令看到.+ offset - offset
: offset 为整数, +offset 表示以当前程序暂停位置为准, 向后数 offset 行处打断点 . -offset 表示以当前程序暂停位置为准, 向前数 offset 行处打断点.filename::linenum
: 文件 filename 中的第 linenum 行打断点. 如果 filename 是相对路径, 会匹配所有的尾部与 filename 一致的文件. For example, if filename isgcc/expr.c
, then it will match source file name of/build/trunk/gcc/expr.c
, but not/build/trunk/libcpp/expr.c
or/build/trunk/gcc/x-expr.c
.function
: 表示程序中包含的函数的函数名, 即break
命令会在该函数内部的开头位置打断点, 程序会执行到该函数第一行代码处暂停. 包括::
修饰的范围. 也是贪心地匹配:break func
会匹配A::B::func
与B::func
.- 不贪心的选项:
break -qualified func
sets a breakpoint on a free-function namedfunc
ignoring any C++ class methods and namespace functions calledfunc
. 贪心的意义: 在 C/C++ 中除了使用 static/命名空间等方式限制函数的链接性, 函数一般都是具有外部链接性的, 也就是很多文件都可以去使用函数, 为了方便对跨文件的所有函数进行追踪, 默认设置成贪心的.
- 不贪心的选项:
function:label
: 在 function 中 label 所在的行.filename:function
: filename 中的 function 入口.label
: 从当前栈帧匹配某个函数中含有 label 的行.address
: 内存地址, 可以是变量/函数/寄存器的地址.
- 条件断点:
(gdb) break location if cond
每次程序执行到 location 位置时都计算 cond 的值, 如果为 True, 则程序在该位置暂停 . 反之, 程序继续执行.
创建断点: tbreak
命令
打的断点仅会作用 1 次.
用法同 break
.
创建断点: rbreak
命令
rbreak
命令的作用对象是 C/C++ 程序中的函数, 它会在指定函数的开头位置打断点.
1 | (gdb) rbreak regex |
- regex 为一个正则表达式, 程序中函数的函数名只要满足 regex 条件,
tbreak
命令就会其内部的开头位置打断点. - 断点不会自动消失.
注意: 通过行号进行设置断点的一个弊端是, 如果你更改了源程序, 那么之前设置的断点就可能不是你想要的了.
一些特殊情况下的断点
在函数的第一条汇编命令打断点
- 通常给函数打断点的命令:
b func
, 不会把断点设置在汇编命令层次函数的开头. 如果要把断点设置在汇编命令层次函数的开头b *func
.
- 通常给函数打断点的命令:
在匿名空间设置断点
1
2
3
4
5
6
7
8
9
10
11
12
13namespace Foo
{
void foo()
{
}
}
namespace
{
void bar()
{
}
}对 namespace Foo 中的 foo 函数设置断点
1
(gdb) b Foo::foo
对匿名空间中的 bar 函数设置断点
1
(gdb) b (anonymous namespace)::bar
在程序地址上打断点
- 当调试汇编程序, 或者没有调试信息的程序时, 经常需要在程序地址上打断点, 方法为
b *address
.1
2
3
4
5
6
7
80000000000400522 <main>:
400522: 55 push %rbp
400523: 48 89 e5 mov %rsp,%rbp
400526: 8b 05 00 1b 00 00 mov 0x1b00(%rip),%eax # 40202c <he+0xc>
40052c: 85 c0 test %eax,%eax
40052e: 75 07 jne 400537 <main+0x15>
400530: b8 7c 06 40 00 mov $0x40067c,%eax
400535: eb 05 jmp 40053c <main+0x1a>1
(gdb) b *0x400522
- 当调试汇编程序, 或者没有调试信息的程序时, 经常需要在程序地址上打断点, 方法为
查看断点信息
1 | (gdb) info breakpoint [bnum] |
bnum
作为可选参数, 为某个断点的编号.输出信息含义
- 断点编号( Num ) , 断点类型( Type ) , 是临时断点还是永久断点( Disp ) , 目前是启用状态还是禁用状态( Enb ) , 断点的位置( Address ) , 断点当前的状态( 作用的行号 , 已经命中的次数等, 用 What 列表示 ).
删除断点
clear
命令
删除指定位置处的所有断点.1
(gdb) clear location
delete
(d
) 命令
1 | delete breakpoints [bnum] |
不指定 bnum 参数, 则 delete
命令会删除当前程序中存在的所有断点.
禁用/启用断点
enable
命令
激活用 bnum… 参数指定的多个断点, 如果不设定 bnum…, 表示激活所有禁用的断点.
1 | enable breakpoints [bnum...] |
临时激活以 bnum… 为编号的多个断点, 但断点只能使用 1 次, 之后会自动回到禁用状态.
1 | enable breakpoints once [bnum...] |
临时激活以 bnum… 为编号的多个断点, 断点可以使用 count 次, 之后进入禁用状态.
1 | enable breakpoints count [bnum...] |
激活 bnum.. 为编号的多个断点, 但断点只能使用 1 次, 之后会被永久删除.
1 | enable breakpoints delete [bnum...] |
disable
命令
1 | disable breakpoints [bnum...] |
观察断点: watch
命令
监控某个变量或者表达式的值的变化情况.
1 | (gdb) watch cond |
分为 3 类命令:
rwatch
命令: 只要程序中出现读取目标变量( 表达式 )的值的操作, 程序就会停止运行.awatch
命令: 只要程序中出现读取目标变量( 表达式 )的值或者改变值的操作, 程序就会停止运行.watch
命令: 只有当被监控变量( 表达式 )的值发生改变, 程序才会停止运行.
几点需要注意:
- 当监控的变量( 表达式 )为局部变量( 表达式 )时, 一旦局部变量( 表达式 )失效, 则监控操作也随即失效.
- 如果监控的是一个指针变量( 例如
*p
), 则watch *p
和watch p
是有区别的, 前者监控的是p
所指数据的变化情况, 而后者监控的是p
指针本身有没有改变指向.
这 3 个监控命令还可以用于监控数组中元素值的变化情况, 例如对于 a[10]
这个数组, watch a
表示只要 a
数组中存储的数据发生改变, 程序就会停止执行.
watch
命令的 2 种实现原理
硬件观察点 Hardware watchpoint
- 系统会为 GDB 调试器提供少量的寄存器( 例如 32 位的 Intel x86 处理器提供有 4 个调试寄存器 ), 每个寄存器都可以作为一个观察点协助 GDB 完成监控任务.
- 基于寄存器个数的限制, 如果调试环境中设立的硬件观察点太多, 则有些可能会失去作用.
- 可能会出现: 无法使用硬件观察点监控数据类型占用字节数较多的变量( 表达式 ). 比如说, 某些操作系统中, GDB 调试器最多只能监控 4 个字节长度的数据, 这意味着 C/C++ 中 double 类型的数据是无法使用硬件观察点监测的. 这种情况下, 可以考虑将其换成占用字符串少的 float 类型.
软件观察点 software watchpoint
- GDB 调试器会以单步执行的方式运行程序, 并且每行代码执行完毕后, 都会检测该目标变量( 表达式 )的值是否发生改变, 如果改变则程序执行停止.
- 这种”实时”的判别方式, 一定程度上会影响程序的执行效率.
GDB 会优先尝试建立硬件观察点, 只有当前环境不支持硬件观察点时, 才会建立软件观察点.
强制 GDB 调试器只建立软件观察点
1
set can-use-hw-watchpoints 0
awatch
和rwatch
命令只能设置硬件观察点, 如果系统不支持或者借助如上命令禁用, 则 GDB 调试器会打印如下信息: Expression cannot be implemented with read/access watchpoint.
异常捕捉断点: catch
命令
捕捉断点监控异常的发生
1 | (gdb) catch exception |
exception 类型 | 含义 |
---|---|
throw(exception) | 当程序中抛出 exception 指定类型异常时, 程序停止执行. 如果不指定异常类型( 即省略 exception ), 则表示只要程序发生异常, 程序就停止执行. |
catch(exception) | 当程序中捕获到 exception 异常时, 程序停止执行. exception 参数也可以省略, 表示无论程序中捕获到哪种异常, 程序都暂停执行. |
load(regexp) unload(regexp) |
其中, regexp 表示目标动态库的名称, load 命令表示当 regexp 动态库加载时程序停止执行 . unload 命令表示当 regexp 动态库被卸载时, 程序暂停执行. regexp 参数也可以省略, 此时只要程序中某一动态库被加载或卸载, 程序就会暂停执行. |
当前 GDB 调试器对监控 C++ 程序中异常的支持还有待完善. 要求 GCC 编译器的版本最低为 4.8. 因为匹配过程需要借助 libstdc++ 库中的一些 SDT 探针, 而这些探针最早出现在 GCC 4.8 版本中.
catch
无法捕获以交互方式引发的异常.
对特定函数设置 catchpoint
catch fork
命令为fork
调用设置 catchpoint. 当fork
调用发生后, gdb 会暂停程序的运行.catch vfork
命令为vfork
调用设置c catchpoint. 当vfork
调用发生后, gdb 会暂停程序的运行.catch exec
命令为exec
系列系统调用设置 catchpoint. 当exec
调用发生后, gdb 会暂停程序的运行.- 目前只有 HP-UX 和 GNU/Linux 支持.
为系统调用设置 catchpoint
1 | catch syscall [name | number] |
例子:
1 | (gdb) catch syscall mmap |
当 mmap
调用发生后, gdb 会暂停程序的运行.
系统调用和编号的映射参考具体的 xml 文件: Ubuntu 下在 /usr/local/share/gdb/syscalls
文件夹下的 amd64-linux.xml
.
通过为 ptrace
调用设置 catchpoint 破解 anti-debugging 的程序
代码例子
1 |
|
有些程序不想被 gdb 调试, 它们就会在程序中调用 ptrace
函数, 一旦返回失败, 就证明程序正在被 gdb 等类似的程序追踪, 所以就直接退出.
1 | (gdb) start |
破解这类程序的办法就是为 ptrace
调用设置 catchpoint, 通过修改 ptrace
的返回值, 达到目的.
1 | (gdb) catch syscall ptrace |
通过修改 rax
寄存器的值, 达到修改返回值的目的, 从而让 gdb 可以继续调试程序.
tcatch
命令
只监控一次.
up
命令
当 catch
命令捕获到指定的 exception 时, 程序暂停执行的位置往往位于某个系统库( 例如 libstdc++ )中. up
命令, 即可返回发生 exception 的源代码处.
条件设置: condition
命令
为现有的普通断点, 观察断点以及捕捉断点添加条件表达式, 也可以对条件断点的条件表达式进行修改.
- 添加或修改 expression 条件表达式.
1
(gdb) condition bnum expression
- 删除 bnum 编号断点的条件表达式, 使其变成普通的无条件断点.
1
(gdb) condition bnum
不同类型的断点设置条件
条件断点
(gdb) break location if cond
观察条件断点
(gdb) watch expr if cond
捕捉条件断点
- 无法直接生成, 需要借助
condition
命令为现有捕捉断点增添一个 cond 表达式, 才能使其变成条件断点.
- 无法直接生成, 需要借助
ignore
命令
使目标断点暂时失去作用, 当断点失效的次数超过指定次数时, 断点的功能会自动恢复.
1 | ignore bnum count |
跟踪断点: trace
与 collect
命令
在不打断原程序运行的前提下, 检测运行情况. 比较适合注重实时性的场景. 这里不展开.
断点的保存与读取
应用场景: 很多情况下我们都是 debug 后修改代码再次 debug 确认修改的效果, 这时候多次打的断点是一样的, 因此反复打同样断点, 进行重复操作是浪费时间, 我们希望将断点信息保存下来, 下次直接读取.
- 保存
1 | save breakpoints [filename] |
将当下所有的断点信息(4 类)包括断点附带的条件判断, 有效的次数等保存到 filename 文件里.
注意, watchpoints 里面的一些局部变量可能再次读入后无效, 因为再次运行时不一定会执行到上次的地方.
filename 文件是文本(相当于配置文件/脚本文件), 可以按需更改, 修改断点信息, 增加删除断点等.
- 读取
1
source [-s] [-v] filename
信息浏览
打印变量/表达式值
print
命令
- 手动打印一次:
print
(p
)
1 | (gdb) print expr |
- 打印更多信息/格式
1 | (gdb) print [options --] [/fmt] expr |
options 参数和 /fmt 或者 expr 之间, 必须用–( 2 个 - 字符 )分隔.
options: 选项可以控制 print 命令输出指定内容的变量或者表达式的值. 一些常用选项:
-address on|off
: 查看某一指针变量的值时, 是否同时打印其占用的内存地址, 默认值为 on. 该选项等同于单独执行set print address on|off
命令.-array on|off
: 是否以便于阅读的格式输出数组中的元素, 默认值为 off. 该选项等同于单独执行set printf array on|off
命令.-array-indexes on|off
: 对于非字符类型数组, 在打印数组中每个元素值的同时, 是否同时显示每个元素对应的数组下标, 默认值为 off. 该选项等同于单独执行set print array-indexes on|off
命令.-pretty on|off
: 以便于阅读的格式打印某个结构体变量的值, 默认值为 off. 该选项等同于单独执行set print pretty on|off
命令.fmt: 指定输出变量或表达式值时所采用的格式.
@
运算符
输出数组中指定区域的元素.
1 | (gdb) print first@len |
参数 first 用于指定数组查看区域内的首个元素的值, 参数 len 用于命令自 first 元素开始查看的元素个数.
::
运算符
与 C++ 中的作用域解析符类似. 明确指定要查看的目标变量或表达式或行号所属的文件/函数.
1 | (gdb) print file::variable |
应用举例: 打印静态变量的值
代码: 两个源文件中存在相同名称的静态变量
1 | /* main.c */ |
如果直接打印静态变量, 则结果并不一定是你想要的:
1 | # 第一次 |
显式地指定文件名(上下文)就可以明确到底是哪个了.
1 | (gdb) p 'static-1.c'::var |
打印大数组中的内容
在gdb中, 如果要打印大数组的内容, 缺省最多会显示200个元素.
1 | int main() |
设置最大限制数
1 | (gdb) set print elements number-of-elements #指定数目 |
1 | (gdb) print array |
打印索引下标
前面介绍 print
函数提到的 (gdb) set print array-indexes on
1 | (gdb) p num |
自动打印: display
每当程序暂停执行( 例如单步执行 )时, GDB 调试器都会自动帮我们打印出来, 而 print
命令则不会.
1 | (gdb) display expr |
fmt 用于指定输出变量或表达式的格式
选项 | 含义 |
---|---|
/x |
以十六进制的形式打印出整数. |
/d |
以有符号 , 十进制的形式打印出整数. |
/u |
以无符号 , 十进制的形式打印出整数. |
/o |
以八进制的形式打印出整数. |
/t |
以二进制的形式打印出整数. |
/f |
以浮点数的形式打印变量或表达式的值. |
/c |
以字符形式打印变量或表达式的值. |
注意: display 和 /fmt 之间不要留有空格.
自动显示列表
对于使用 display
命令查看的目标变量或表达式, 都会被记录在一张列表.info dispaly
:打印此表
1 | (gdb) info display |
- Num 列为各变量或表达式的编号, GDB 调试器为每个变量或表达式都分配有唯一的编号 .
- Enb 列表示当前各个变量( 表达式 )是处于激活状态还是禁用状态.
- Expression 列: 表示查看的变量或表达式.
删除自动显示
1 | (gdb) undisplay num |
禁用自动显示
1 | (gdb) disable display num |
栈信息查看
栈帧的概念
程序执行时调用了多少个函数, 就会相应产生多少个栈帧, 其中每个栈帧自函数调用时生成, 函数调用结束后自动销毁.main()
主函数对应的栈帧, 又称为初始帧或者最外层的帧.
不同操作系统为栈帧选定地址标识符的规则不同.
GDB 调试器会按照既定规则对它们进行编号: 当前正被调用函数对应的栈帧的编号为 0, 调用它的函数对应栈帧的编号为 1, 以此类推.
frame
命令
查看当前栈帧
1 | (gdb) info frame |
显示内容如下:
- 当前栈帧的编号, 以及栈帧的地址.
- 当前栈帧对应函数的存储地址, 以及该函数被调用时的代码存储的地址.
- 当前函数的调用者, 对应的栈帧的地址.
- 编写此栈帧所用的编程语言.
- 函数参数的存储地址以及值.
- 函数中局部变量的存储地址.
- 栈帧中存储的寄存器变量.
查看指定帧
1 | (gdb) frame spec |
- 通过栈帧的编号指定. 0 为当前被调用函数对应的栈帧号, 最大编号的栈帧对应的函数通常就是
main()
函数. - 借助栈帧的地址指定. 栈帧地址可以通过
info frame
命令打印出的信息中看到. - 通过函数的函数名指定. 注意, 如果是类似递归函数, 其对应多个栈帧的话, 通过此方法指定的是编号最小的那个栈帧.
up
和 down
命令
1 | (gdb) up n |
n 为整数, 默认值为 1. 该命令表示在当前栈帧编号( 假设为 m )的基础上, 选定 m+n / m-n 为编号的栈帧作为新的当前栈帧.
backtrace
命令
1 | (gdb) backtrace [-full] [n] |
- n: 可选, 一个整数值, 当为正整数时, 表示打印最里层的 n 个栈帧的信息 . n 为负整数时, 那么表示打印最外层 n 个栈帧的信息.
- -full: 可选, 打印栈帧信息的同时, 打印出局部变量的值.
例子
1 |
|
在函数 fun_a
里打上断点, 当程序断住时, 显示调用栈信息:
1 | (gdb) bt |
用 bt full
命令显示各个函数的局部变量值:
1 | (gdb) bt full |
使用 bt full n
, 从内向外显示 n 个栈桢, 及其局部变量, 例如:
1 | (gdb) bt full 2 |
bt full -n
从外向内显示n个栈桢, 及其局部变量, 例如:
1 | (gdb) bt full -2 |
打印尾调用堆栈帧信息
启动 尾调用(Tail call) 优化(介绍可以参考链接). 本部分是对 optimized code 进行 debug 设置的一个例子, gdb 中有更多的针对优化后代码进行 debug 的场景.
1 | gcc -g -O -o test test.c |
例子
1 |
|
查看 main 函数汇编代码. 可以看到 main函数直接调用了函数 a, 根本看不到函数 b 和函数 c 的影子.
1 | (gdb) disassemble main |
在函数 a 入口处打上断点, 程序停止后, 打印堆栈帧信息. 看不到尾调用的相关信息.
1 | (gdb) i frame |
设置 debug entry-values
选项为非 0 的值, 这样除了输出正常的函数堆栈帧信息以外, 还可以输出尾调用的相关信息. 可以看到输出了”tailcall: initial:”信息.
1 | (gdb) set debug entry-values 1 |
函数相关变量查看
- 查看当前函数各个参数的值
1 | (gdb) info args |
- 只查看当前函数中各局部变量的值
1 | (gdb) info locals |
打印进程内存信息
如果想查看进程的内存映射信息, 可以使用 i proc mappings
命令.
例子:
1 | (gdb) i proc mappings |
也可以用 i files
(还有一个同样作用的命令: i target
)命令, 它可以更详细地输出进程的内存信息, 包括引用的动态链接库等等.
1 | (gdb) i files |
打印特定范围内存的值: x
命令
格式为 x/nfu addr
. 含义为以 f
格式打印从 addr
开始的 n
个长度单元为 u
的内存值.
参数:
n
: 输出单元的个数.f
: 输出格式. 比如x
是以 16 进制形式输出,o
是以 8 进制形式输出, 等等.u
: 标明一个单元的长度.b
是一个 byte,h
是两个 byte(halfword),w
是四个 byte(word),g
是八个 byte(giant word).
例子: 以无符号 10 进制格式打印数组 a 前 16 个 byte 的值:
1 | (gdb) x/16tb a |
打印程序动态分配内存的信息
gdb 是不支持打印动态分配的内存的, 可以通过自定义的命令实现此功能.
例子代码:
1 |
|
编译
1 | g++ -g -fpermissive t.cpp |
自定义 GDB 命令
1 | define mallocinfo |
运行过程
1 | Temporary breakpoint 5, main () at a.c:7 |
可以看到 gdb 输出了动态分配内存的变化信息.
打印变量的类型和所在文件
1 | (gdb) whatis he |
复合类型详细的类型信息:
1 | (gdb) ptype he |
查看定义该变量的文件: i variables regex
1 | (gdb) i variables he |
用正则表达式进行约束后:
1 | (gdb) i variables ^he$ |
i variables
不会显示局部变量.
C++ 中按照派生类型打印对象
例子代码
1 |
|
在 gdb 中, 当打印一个对象时, 缺省是按照声明的类型进行打印:
1 | (gdb) frame |
明确派生类类型: set print object on
.
1 | (gdb) set print object on |
当打印对象类型信息时, 该设置也会起作用:
1 | (gdb) whatis p |
打印源代码行: list
( l
) 命令
list
(简写为 l
)命令来显示源代码以及行号.list
命令可以指定行号, 函数:
1 | (gdb) l 24 |
指定向前或向后打印:
1 | (gdb) l - |
指定范围:
1 | (gdb) l 1,10 |
info
(i
) 命令
显示一些关于被调试程序的通用信息(Generic command for showing things about the program being debugged).
help info
产生的详细命令一览
1 | info address -- Describe where symbol SYM is stored |
常用介绍
i registers
- 不包括浮点寄存器和向量寄存器的内容. 使用
i all-registers
命令, 可以输出所有寄存器的内容. - 单个寄存器的值, 可以使用
i registers regname
或者p $regname
.
- 不包括浮点寄存器和向量寄存器的内容. 使用
i sharedlibrary [regex]
- 显示程序加载的共享链接库信息, 其中 regex 可以是正则表达式.
i functions [regex]
- 列出可执行文件的所有函数名称.
- 列出函数原型以及不带调试信息的函数.
show
命令
显示 gdb 本身的相关信息(Generic command for showing things about the debugger). 很多 show
命令其实是和 set
命令对应的. 这里不展示其命令 list.
常用介绍
show version
: 查看 gdb 版本信息.show architecture
: 显示执行程序的架构, 例如 i386:x86-64.
运行过程控制
跳转
单步调试
next
(n
)- step out: 当遇到包含调用函数的语句时, 无论函数内部包含多少行代码,
next
命令都会一步执行完.
- step out: 当遇到包含调用函数的语句时, 无论函数内部包含多少行代码,
step
- step in: 所执行的代码行中包含函数时, 会进入该函数内部, 并在函数第一行代码处停止执行.
until
(u)
GDB 调试器快速运行完当前的循环体, 并运行至循环体外停止.
1
(gdb) until
- 注意,
until
命令并非任何情况下都会发挥这个作用, 只有当执行至循环体尾部( 最后一行代码 )时,until
才会发生此作用 . 反之,until
命令和next
命令的功能一样, 只是单步执行程序.
- 注意,
指示 GDB 调试器直接执行至指定位置后停止
1 | (gdb) until location |
jump
命令
略过某些代码, 直接跳到 location 处的代码继续执行程序.
1 | (gdb) jump location |
注意代码本身的逻辑, 随意跳转可能会导致程序崩溃或出现调试过程中产生的 Bug(实际运行可能不会产生).jump
跳转到的位置后续没有断点, 那么 GDB 会直接执行自跳转处开始的后续代码.
修改程序中的值
改变字符串的值
set VAR="STRING"
设置.- 也可以通过访问内存地址的方法改变字符串的值.
1 | Temporary breakpoint 2, main () at a.c:5 |
在改变字符串的值时候, 一定要注意内存越界的问题.
设置变量的值
set var VAR=expr
set {type}address=expr
: 给存储地址在 address, 变量类型为 type 的变量赋值.
1 | Breakpoint 2, func () at a.c:5 |
- 寄存器也可以作为变量, 因此同样可以修改寄存器的值:
set var $REG = 8
.
1 | Breakpoint 2, func () at a.c:5 |
修改 PC
寄存器的值
1 |
|
1 | (gdb) disassemble main |
通过 info line 6
和 info line 7
命令可以知道两条 a++;
语句的汇编命令起始地址与结束地址, 进而可以跳过其中一个.
断点处执行命令改变程序的执行
1 |
|
可以看到, 当程序运行到断点处, 会自动把变量 n 的值修改为0, 然后继续执行.
1 | (gdb) b drawing |
如果你在调试一个大程序, 重新编译一次会花费很长时间, 比如调试编译器的 bug, 那么你可以用这种方式在 gdb 中先实验性的修改下试试, 而不需要修改源码, 重新编译.
修改被调试程序的二进制文件
例子代码
1 |
|
gdb 默认是以只读方式加载程序的. 有 2 种方式可以修改程序的二进制文件.
- 命令行选项指定为可写:
1
2
3$ gcc -write ./a.out
(gdb) show write
Writing into executable and core files is on. - 运行中设置
1
2(gdb) set write on
(gdb) file ./a.out
查看反汇编
1 | (gdb) disassemble /mr drawing |
修改二进制代码(注意大小端和命令长度):
1 | (gdb) set variable *(short*)0x400651=0x0ceb |
可以看到, 条件跳转命令 je
已经被改为无条件跳转 jmp
了.
退出, 运行一下
1 | $ ./a.out |
call
或 print
命令的跳转功能
使用 gdb 调试程序运行时, 可以使用 call
或 print
命令直接调用特定函数执行. 注意当前上下文是否支持对特定函数的支持.
GDB 入门--断点, 信息浏览, 运行过程控制