GDB调试
gcc -o main main.c -g 生成带调试信息的可调试版本
1
2
3
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE= Debug
make
gdb filename 指定调试的文件
hip程序
hipcc demo.cpp -o demo -g,然后hipgdbhipcc demo.cpp -o demo -gdwarf-4,然后gdb打日志
HIP_LOG_LEVEL=4 HIP_MODULE_MASK=0x7fffffff HIP_ENABLE_LIST="hip:hsa:thunk" ./demo 设置了HIP的日志级别和模块掩码,生成demo_xxx.nano文件
hiplogdump sort demo_xxx.nano >log.txt
gdb [exec file] [core file]:调试 coredump 内核转储文件,进入后通常先用 bt 查看调用栈信息。
r 或 run:启动程序运行
参数传递
1
2
3
4
5
6
7
8
# 1.启动时传入
gdb --args demo 1 2 3
# 2. 启动后传入
gdb demo
set args 1 2 3
# 3.启动后执行传入
gdb demo
r 1 2 3
附加进程
1
2
gdb attach -p <进程ID> # gdb 附加到正在运行的进程
gdb --pid <进程ID>
断点操作
设置断点
1
2
3
4
5
6
7
8
b demo.cpp:123 # b 文件:行号
b function_name # 所有同名函数
b class1::function_name # 指定函数
b +5 # 偏移量打断点,当前73行, +5到78行
b demo.cpp:123 if i == 3 # b 断点 条件 满足条件命中断点
b *0x11111 # 指令地址设置断点(调试程序没有符号信息时)使用p function_name获得函数地址0x11111,在断点然后r运行
rb funtion_name* # 正则表达式
tb # 临时断点
删除断点(delete是全局的)
1
2
3
4
5
6
delete #删除所有断点
delete 5 # 删除5号断点
delete 5 6 # 删除5号和6号断点
delete 5-8 11-13 #删除指定范围断点
clear function_name # 删除指定函数断点
clear demo.cpp:123 # 删除指定行号断点
查看断点
1
2
3
info b # 常用
info: info i 两种方式
breakpoint: breakpoint break b 三种方式
启用/禁用断点
1
2
3
4
5
enable/disable 断点编号 #启用/禁用断点,可以单一编号如`1`,也可以范围`2-7`
enable once 断点编号 # 启用一次后自动禁用
enable delete 断点编号 # 启用后删除
enable count 数量N 断点编号 # 启用断点并命中N次
ignore 断点编号 次数N # 忽略断点前N次命中
观察点
1
2
3
4
5
6
watch 变量/表达式 # 观察点,监视变量
watch 变量/表达式 thread N # 监视N号线程变量
rwatch 变量/表达式 # 读取观察点,变量或表达式被读取时,程序中断
awatch 变量/表达式 # 读写观察点,无论变量或表达式被读取还是写入时,程序都中断
info watchpoints # 查看所有观察点
delete/disable/enable 观察点编号 # 删除/禁用/启用观察点
捕获点
C++异常
动态库载入
执行
s 步进 finish跳出
n :跳过 (next)
c :继续(continue)
jump N:跳到第N行 ,或者函数
where :显示当前执行的具体函数和代码行
!ls: 使用 !command 来执行 shell 命令, 也可以shell ls
反向执行
1
2
3
4
5
record # 开始记录,后续才能进行反向执行
rn/reverse-next
rc/reverse-continue
reverse-finish
record stop
跳过
1
2
3
skip function_name # 跳过某个函数
skip file aaa.cpp # 跳过文件
skip -gfi path/*.* # 按 glob 跳过路径下所有文件
查看显示
info args :进入一个函数查看参数信息
info locals :查看局部变量值
info functions :查看有哪些函数
窗口显示
gdb -tui filename # 显示代码窗口
1
2
3
4
5
6
7
tui enable # 显示 ctrl + x 再按a关闭打开窗口
layout src # 显示源码
layout asm # 显示汇编
layout split # 显示源代码和汇编
layout regs # 显示寄存器
refresh # 刷新屏幕 ctrl + l
update # 更新源代码
查看源代码
1
2
3
4
5
6
7
8
9
10
11
12
l # 查看上下文(list) 默认当前代码行的前5行和后5行
set listsize 20 # 设置显示行数 20行
list demo.cpp:123 # 查看指定文件指定行代码
list function_name # 查看指定函数的源代码
# 搜索源代码
search 正则表达式
forward-search 正则表达式 # 正向搜索
reverse-search 正则表达式 # 反向搜索
show directories
directory path2/
查看/修改变量的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
print 变量 # 打印变量
p 变量
p 变量名 = 值 # 打印赋值结果,同时修改变量值
# 一些内嵌函数
p sizeof( a)
p strcmp( "123" , "12" )
p strlen( "string" )
# 查看结构体/ 类的值
set print null-stop # 设置字符串显示规则,遇到结束符时停止显示
set print pretty # 美化,格式化结构体
p new_node->Name # 查看结构体/类单个成员
p *new_node # 查看整个结构体/类
# 查看数组
set print array # 控制数组显示
set print array-indexes # 显示数组索引
# 查看联合体
set print union
# 自动显示变量值,和断点类似
display 变量名
display { var1, var2, var3} # 多变量名时,长度要相同
undisplay 变量编号 # 取消自动显示,info display可查看编号
enable/disable display 变量编号 # 启用/禁用自动显示
# gdb内source py脚本
# 加载 libstdc++ 的 pretty-printer 来美化 STL 容器
source /usr/share/gdb/auto-load/usr/lib64/libstdc++.so.6.0.19-gdb.py
# 无法美化打印情况, 比如查看map底层结构
# 1. 先查看 _M_header._M_parent(根节点地址)和 _M_node_count(元素个数)
p your_map # _M_header._M_parent(根节点地址)和 _M_node_count(元素个数)
# 2. 定义节点指针
set $node = ( std::_Rb_tree_node<std::pair<const Key, Value> >*) 根节点地址
# 3. 获取键值对
set $pair = ( std::pair<const Key, Value>*)( & $node ->_M_storage)
# 4. 打印键和值
p $pair ->first
p $pair ->second
# 5. 遍历其他节点时,对左右子节点重复步骤 2-4
p $node ->_M_left
p $node ->_M_right
查看变量类型
1
2
3
4
5
6
7
8
9
10
11
# ptype /选项 变量或类型 查看各个变量类型
ptype node_head # 查看变量类型,显示成员名称和类型
# 选项
/r # 原始数据显示,不会代替一些typedef定义
/m # 查看类时,只显示类的成员变量
/M # 显示类的方法(默认)
/t # 不打印类中的typedef数据
/o # 打印结构体字段偏移量和大小信息
whatis 变量或表达式 # 查看变量类型
set print object on
查看内存
1
2
3
4
5
6
7
# x /选项 地址 查看各个变量内存信息
const char* str = "test" ;
x str # 默认16进制显示,内存存储内容和"test"相反(小端存储) 0x74736574
x /s str # 直接显示内容 "test"
x /d str # 十进制显示
x /4d str # 十进制显示,显示宽度为4
# 变量非指针类型,如int, 先p &value_name, 使用x查看
进程内存映射
1
2
info proc mappings # 查看进程虚拟地址空间映射(vmmap)
# 输出包括:start addr, end addr, size, perms, offset, dev, inode, pathname
查看寄存器
指令寄存器 $rip(32 位下通常对应 $eip)指向当前执行位置
栈指针寄存器$rsp指向当前栈顶
通用寄存器存储一些变量值,函数参数及返回值等
1
2
3
4
5
6
7
8
9
10
info registers # 简写 i r
info registers rax # 显示特定寄存器值
info all-registers # 显示所有寄存器值
function_test( int a, const char* str)
调用function_test( 10, "test" )
在常见的 x86_64 System V ABI 下,第一个参数通常在 ` rdi` ,第二个参数通常在 ` rsi`
i r rdi
i r rsi
x /s $rsi # 查看寄存器值
修改寄存器
1
2
3
4
info line 15 # 查看15行的起始地址和结束地址 0003 -> 0005 [start, end)
info line 16 # 查看16行的起始地址和结束地址 0005 -> 0006
set $rip = 0x5 # 修改指令地址前务必确认地址有效,否则容易直接跑飞
set $rip = 0x3
查看汇编
1
2
3
4
5
starti #开始执行程序并停在第一个汇编指令处
layout asm #显示汇编窗口
si #单步
set disassembly-flavor intel
disassemble /mr ./demo #查看反汇编代码
查看调用栈
1
2
3
4
5
6
bt # 查看回溯 backtrace
bt 2 # 只显示两个栈帧
f 2 # frame切换栈帧,查看调试位置
up/down 2 # 基于当前帧来切换
f 帧地址 # 通过帧地址切换
info frame # 查看帧信息
多线程调试
1
2
3
4
5
6
7
8
9
10
11
12
info threads # 查看线程, *号表示当前线程
thread find xxx # 查找线程
thread name xxx # 给当前线程命名,便于识别
thread N # 切换线程
b M thread N # 为N号线程M行设置断点
thread apply N command # 为N号线程执行command命令
thread apply all bt # 查看所用线程堆栈信息
set scheduler-locking off| on| step # 控制单步时其他线程是否继续运行
show print thread-events # 显示GDB检测到线程已经启动和退出时是否打印信息
set print thread-events on| off # 设置是否打印线程启动/退出日志
p mutex_xx # 查看锁的Owner ID
多进程调试
1
2
3
4
5
6
7
8
info inferiors # 查看进程
inferior N # 切换相应进程
detach inferiors N # 让某个 inferior 脱离调试
add-inferior # 添加一个新的 inferior
remove-inferiors # 删除 inferior
set follow-fork-mode child # 设置调试子进程
set detach-on-fork off # 对所有的进程进行调试
set schedule-multiple on
内存检查
1
2
3
4
call malloc_stats() # glibc malloc 统计信息
call malloc_info( 0, stdout) # 输出 XML 格式的分配器信息
# 需要安装 libasan 即AddressSanitizer
g++ -fsanitize= address -g -o demo ./demo.cpp
coredump调试
1
2
3
4
5
6
7
8
9
10
11
12
13
# 在调试界面生成coredump文件
ps aux | grep ./demo # 查看进程号
gdb attach -p <进程ID> # 附加到进程
gcore ****.core # gcore/generate-core-file 为活着的进程创建core dump文件
detach
q
# 配置并生成coredump文件
ulimit -c unlimited
/etc/security/limits.conf 添加 ` soft core unlimited`
echo -e "/root/corefile/core-%e-%s-%p-%t" > /proc/sys/kernel/core_pattern
# %e进程名称。%s崩溃信号,%p进程id,%t时间戳
# 调试
gdb [ exec file] [ core file] # 调式coredump内核转储文件,直接进去`bt`查看调用栈信息。
发行版调试
1
2
3
4
5
# 从调试版中提取调试符号
objcopy --only-keep-debug demo demo.symbol # 生成调试符号表
gdb --symbol= demo.symbol --exec= demo_release # 加上调试符号调试发行版
gdb --symbol= demo --exec= demo_release # 直接使用调试版作为符号源
远程调试
1
2
3
4
5
6
7
8
9
10
# 被调试机器/服务器端
apt install gdbserver # 安装gdbserver
gdbserver ip:port ./demo # 启动gdbserver
# 客户端
gdb
> target remote ip:port
# 已经启动的程序,服务器端
gdbserver ip:port --attach <pid>
其他
1
2
3
4
// int 3 用于触发调试中断
asm {
int 3 ;
}
参考阅读
C/C++代码调试的艺术(第2版) (豆瓣) (douban.com)
GDB 高级调试-多线程、后台调试、多进程、反向调试
GDB调试-从入门实践到原理
100个gdb小技巧
GDB官方手册
Debug Info
参考阅读
DWARF标准