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.cor/build/trunk/gcc/x-expr.c.function: 表示程序中包含的函数的函数名, 即break命令会在该函数内部的开头位置打断点, 程序会执行到该函数第一行代码处暂停. 包括::修饰的范围. 也是贪心地匹配:break func会匹配A::B::func与B::func.- 不贪心的选项:
break -qualified funcsets a breakpoint on a free-function namedfuncignoring 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=exprset {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 入门--断点, 信息浏览, 运行过程控制
