gdb最佳实践手册

launch/attach process

注意：gdb 中只需输入完整命令的前几个字母甚至首字母即可。前提是缩写不和其它命令有歧义

Copy
gdb ./a.out # 从头调试尚未运行的程序

gdb a <pid> # 绑定正在运行的进程
gdb -p <pid> # 同上一条

gdb -q a
(gdb) attach <pid> # 也可以先启动再绑定

(gdb) detach # 如果通过gdb attach调试，detach后，原进程将继续执行
(gdb) q # 退出gdb

----------------静默启动选项：直接进入gdb，而非打印一堆版本版权信息
gdb -silent
gdb -q
#可在~/.bashrc中，为gdb设置一个别名：
alias gdb="gdb -q"

----------------静默退出设置:gdb退出时会提示:是否退出调试的程序
#A debugging session is active.
#
# Inferior 1 [process 29686 ] will be killed.

#Quit anyway? (y or n) n

#静默退出:也可把这个命令加到.gdbinit文件里。
(gdb) set confirm off

----------------gdb启动时，指定被调试程序的参数
gdb -args ./a.out a b c
----------------也可在gdb中，通过命令来设置
(gdb) set args a b c
(gdb) show args
Argument list to give program being debugged when it is started is "a b c".
----------------还可在运行程序时指定
(gdb) r a b #这似乎会被保存为参数列表，下次r时可能会带保存的参数列表，建议(gdb) show args看看

----------------进入gdb后再构建程序
(gdb) shell
(gdb) make CFLAGS="-g -O0"

用自动获取 pid 的脚本启动 attach debugging

Copy
#!/bin/bash
# 指示使用 Bash 解释器来执行脚本
# 保存为 agdb.sh，并添加可执行权限（chmod +x xgdb.sh）
# 用法：./agdb.sh <program_name>

# 获取程序名，即第一个参数
prog_bin=\$1

# 检查是否传入了程序名
if [ -z "$prog_bin" ]; then
echo "请传入程序的名称或路径！"
exit -1
fi

# 获取程序的 PID
running_name=$(basename $prog_bin) # 提取程序名
pid=$(pidof $running_name)

# 检查PID是否为空
if [ -z "$pid" ]; then
echo "没有找到运行中的进程：$running_name"
exit -1
fi

# 附加到进程
echo "正在附加到进程 PID: $pid..."
gdb -q -p $pid

远程调试

Copy
(gdb) i target #显示调试目标平台的信息
Remote target #或Local target
Connected to remote target via <connection info> #端口号
Architecture: <arch info> #目标架构（如x86、ARM 等）

(gdb) target remote localhost:25000 #连接gdbsever

gdb 临时环境变量

在 GDB 中设置只对 debugging function 生效的环境变量。通常是临时的，仅在调试会话期间有效。

Copy
(gdb) set env PATH=$PATH:/path/to/some/library
(gdb) set env LD_PRELOAD=$LD_PRELOAD:/path/to/some/library

(gdb) show env LD_PRELOAD #查看gdb设置的临时环境变量
(gdb) unset env LD_PRELOAD

也可以在.gdbinit配置 gdb 启动时运行初始化命令：

Copy
set env LD_PRELOAD=$LD_PRELOAD:/path/to/some/library

运行控制

Copy
(gdb) r arg1 arg2 ... # 重新开始运行二进制
(gdb) start # 用于开始执行程序，并在程序的第一条指令处暂停执行，比如停在main{
(gdb) stop # 暂停运行
(gdb) c # 继续执行
(gdb) n # 单步执行，遇到函数则跳过
(gdb) s # 单步执行，遇到函数则跳入函数体

(gdb) until line # 运行直到到达指定行

breakpoints 设置

Copy
-----------------------------------breakpoints set
(gdb) b <file:line> # 设置文件中某行断点;通过行号设置断点的弊端:如果更改了源程序，那么之前设置的断点就可能不是你想要的
(gdb) b <file:function> # 设置文件中某函数断点

(gdb) b <namespace::class::function> # 设置某类的成员函数断点
(gdb) b (anonymous namespace)::<func> # 设置匿名空间中的函数断点

(gdb) b <location> <thread-id> # 设置某个线程在某处的断点
(gdb) b <location> if <condition> # 设置某处的条件断点

(gdb) b *0x400522 # 调试汇编或无调试信息的程序时，经常要在程序地址上打断点

(gdb) tb a.c:15 # tb打只生效一次的临时断点，断点命中一次后，就被删掉

-----------------------------------breakpoints 删除/启用/禁用
(gdb) d <break-id> # 删除某断点
(gdb) disable <break-id> # 禁用某断点
(gdb) enable <break-id> # 启用某断点

-----------------------------------breakpoints忽略，可用于调试循环
(gdb) ignore 1 5 # 忽略断点1前五次触发
(gdb) ignore 1 0 # 取消忽略

-----------------------------------breakpoints 设置自动执行命令
(gdb) command <break-id> # 修改已存在的断点命令也是这条
>silent # 静默执行命令
>if x > 10
>p # 运行到断点处时自动打印变量
>end

(gdb) i breakpoints # 用于查看断点命令
(gdb) delete # 用于删除断点命令

-----------------------------------breakpoints环境保存
(gdb) save breakpoints my_breakpoints.txt # 将当前的断点保存到路径my_breakpoints.txt

(gdb) source my_breakpoints.txt # 在下次启动 GDB 调试时，加载my_breakpoints.txt保存的断点

在程序入口处打断点

Copy
strip a.out #去掉调试信息
-----------------------------获取程序入口1：用 readelf 命令查看 ELF 文件头，找到 Entry point address：
readelf -h a.out
#Entry point address: 0x400440
-----------------------------获取程序入口2：(gdb) info files，找到 Entry point address：
(gdb) info files
#Entry point address: 0x400440
-----------------------------获取程序入口3：objdump -f a.out，找到 Entry point address：
objdump -f a.out
#Entry point address: 0x400440

-----------------------------在程序入口处打断点
(gdb) b *0x400440

快速尝试不同输入

在不修改源码，不重新编译的情况下，通过断点命令 + 反向调试为函数尝试不同输入

Copy
(gdb) b drawing
Breakpoint 1 at 0x40064d: file win.c, line 6.
(gdb) command 1
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>silent # 避免在断点停下时输出信息。
>set variable n = 0 # 修改输入
>c
>end

#看了结果后反向执行再改

watchpoint 设置

写观察点：程序在某个变量（观察点）的值变化时暂停。
读观察点：当发生读取变量行为时，程序暂停

硬件观察点：设置观察点时，GDB 使用硬件级监控功能追踪变量值变化。不影响程序性能。但硬件观察点数量可能有上限，超限时，GDB 会使用软件观察点。
软件观察点：如硬件不支持观察点或已达到硬件观察点限制，GDB 用软件方式实现。通常影响程序运行速度，因为涉及到不断地检查和管理内存。

Copy
(gdb) watch a # 设置变量a为写观察点
(gdb) wa a thread 2 # 只在 thread2线程 触发 a变量的观察点
(gdb) rw a # 读观察点，只对硬件观察点生效
(gdb) aw a # 读写观察点

(gdb) delete watchpoint 2

(gdb) disable watchpoint 2 # 禁用观察点2
(gdb) enable watchpoint 2

(gdb) set can-use-hw-watchpoints off # 禁用硬件观察点
(gdb) set can-use-hw-watchpoints on

-------------------------------用内存地址设置观察点
(gdb) p &a # 列出a的地址
\$1 = (int *) 0x6009c8

(gdb) watch *(int*)0x6009c8 # 用内存地址的解引用设置观察点

catchpoint 设置

catchpoint 用于捕捉特定事件发生（如信号、函数调用、系统调用等）时中断程序执行；catchpoint 默认是持久的，会在程序每次触发对应事件时中断。

Copy
(gdb) tcatch fork # 设置为只捕获一次的临时捕捉点

(gdb) catch fork # 捕获 fork() syscall；GNU/Linux支持这个功能。其他平台是否支持查官方gdb手册
(gdb) catch vfork
(gdb) catch exec
(gdb) catch syscall # 捕捉所有syscall
(gdb) catch syscall [syscall | number] # 捕获 指定名称或编号 的syscall

破解 anti-debugging

有些程序不想被 gdb 调试，就在程序中调用 “ptrace” 函数，一旦返回失败，就证明程序正在被 gdb 等类似的程序追踪，直接退出。

破解这类程序的办法就是为ptrace调用设置catchpoint，然后修改ptrace返回值

Copy
----------------------------eg
#include <sys/ptrace.h>
#include <stdio.h>
int main(){
if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0 ) {
printf("Gdb is debugging me, exit.\n");
return 1;
}
printf("No debugger, continuing\n");
return 0;
}

----------------------------(gdb) catch syscall ptrace
(gdb) catch syscall ptrace
Catchpoint 2 (syscall 'ptrace' [101])
(gdb) r # 重新运行
Starting program: /data2/home/nanxiao/a

Catchpoint 2 (call to syscall ptrace), 0x00007ffff7b2be9c in ptrace () from /lib64/libc.so.6
(gdb) c
Continuing.

Catchpoint 2 (returned from syscall ptrace), 0x00007ffff7b2be9c in ptrace () from /lib64/libc.so.6
(gdb) set $rax = 0 #Linux64 syscall返回值存在rax
(gdb) c
Continuing.
No debugger, continuing
[Inferior 1 (process 11491) exited normally]

修改 machine states

Copy
(gdb) set $var=XXX # 设置gdb变量
----------------修改变量
(gdb) set var <varname> = <val> # 修改变量

(gdb) set file::array="Jil" # 修改字符串数组值，字符串常量在只读数据段，不可修改
(gdb) p &array # 修改内存的方式修改变量，但内存越界很危险
(gdb) set {char[4]} 0x80477a4 = "Jil" # set {type}address=expr

----------------修改register
(gdb) set var $eax = 8 # 修改返回值寄存器eax
(gdb) set var $pc=0x08050949 # 只要你知道某条指令的地址，就能通过修改PC指令下一步跳转过去

----------------修改

----------------修改

----------------修改

跳转执行

调试常规方法是设置断点并重新运行程序，跳过不影响结果的中间 machine states 修改过程，以及非关键过程到某位置执行，可节约时间；

gdb 跳转指令的原理是修改程序计数器 PC，使程序跳到指定行继续执行。jump 跳到指定执行点不暂停，若想 gdb 在该执行点暂停，需手动设置断点。

Copy
(gdb) tb <file>:<num> # tb打只生效一次的临时断点，断点命中一次后，就被删掉

(gdb) j <num> # 跳转到指定行号
(gdb) j <func>
(gdb) j <file>:<num>
(gdb) j <file>:<func>
(gdb) j *addr # 跳转到某地址指向的指令

(gdb) j +<num> # 向后跳转n行
(gdb) j -<num> # 向前跳转n行

Time Travel Debugging

记录每条 gdb 指令引起的 machine states 变化 (如变量、寄存器、内存的数据变化等)，默认存入 gdb_record.process_id。当需回溯至过去状态时，调试器会按相反顺序逐条指令恢复这些状态

使用场景：
1）很难复现的 BUG，逻辑非常复杂的问题
2）多线程并发问题
3）内存相关的问题，如内存踩踏，内存泄漏、重复释放等
4）调用栈显示全是问号的问题

Copy
(gdb) record save filename # 把程序执行历史状态信息保存到文件，默认名字是gdb_record.process_id
(gdb) record restore filename # 从历史记录文件中恢复状态信息
(gdb) show record full insn-number-max # 查看可记录执行状态信息的最大指令个数，默认200000
(gdb) set record full insn-number-max n # 设置可记录最大指令数n
(gdb) set record full insn-number-max unlimited # 设置可记录最大指令数无限制

(gdb) set exec-direction reverse # 设置程序逆向执行，执行此命令后，常用命令如next, nexti, step, stepi, continue、finish等都变成逆向执行
(gdb) set exec-direction forward # 设置执行方向为正向

(gdb) b n
(gdb) record # 记录程序执行过程中所有状态信息
(gdb) c

(gdb) reverse-search # 反向搜索曾经输入过的命令
(gdb) record goto start # 跳转到记录开始处
(gdb) record goto end # 跳转到记录结束处
(gdb) record goto n # 跳转到记录中的指定位置 n

(gdb) reverse-next # 逆向执行一行代码，遇函数调用不进入
(gdb) reverse-nexti # rni：逆向执行一条指令，遇到函数调用不进入
(gdb) reverse-step # rs：逆向执行一行代码，遇函数调用则进入
(gdb) reverse-stepi # rsi：反向单步执行（机器码级别）
(gdb) reverse-continue # rc：反向继续执行
(gdb) reverse-finish # 逆向执行，一直到函数入口处

(gdb) record stop # 停止记录状态信息

切换 shell 环境执行

Copy
(gdb) shell # 进入shell模式，回到linux终端
(gdb) !<shell_cmd> # 相当于(gdb) shell <shell_cmd>;
(gdb) pwd # 在gdb中切换工作目录
(gdb) cd tmp
(gdb) exit # 退出shell模式，回到gdb命令行

print

输出设置

指定 gdb 输入输出设备

缺省情况下程序的输入输出和 gdb 使用同一个终端

Copy
tty #查看本终端

gdb -tty /dev/pts/2 ./a.out #设置程序的输入输出

(gdb) tty /dev/pts/2 #设置程序的输入输出

打印字符串长度设置

gdb 会限制打印字符串的最大长度。当 gdb 输出信息较多时，gdb 会暂停输出，并会打印 “---Type <return> to continue, or q <return> to quit---”；使用下列命令可修改限制：gdb 就会全部输出，中间不会暂停

Copy
(gdb) show print elements # 显示字符串最大打印长度

(gdb) set print elements <number-of-elements> # 设置字符串最大打印长度

(gdb) set print elements 0 # 取消字符串最大打印长度
(gdb) set print elements unlimited # 同上

(gdb) set pagination off # 取消分页输出
(gdb) set height 0 # 取消输出信息最大高度限制

machine state

Copy
---------------------------------------打印variables/打印stack
(gdb) whatis # 打印变量类型
(gdb) ptype # 打印变量类型定义
(gdb) i variables # 打印当前作用域所有变量所在文件及类型
(gdb) i variables ^$ # ^ 表示以开头；$ 表示以结尾

(gdb) p # 打印变量
(gdb) i locals # 列出当前所处函数栈帧中所有局部变量（包括静态局部变量）
(gdb) i variables # 列出所有的全局和静态变量
(gdb) i variables var # 列出以正则var开头的全局和静态变量
(gdb) p 'file.c':: # 属于不同文件的extern全局/静态变量可能有相同变量名，此时要指定所属文件

(gdb) p sizeof(wchar_t) # print wchar_t类型字节大小
(gdb) x/s str # 打印ASCII字符串或字符数组变量str
(gdb) x/hs str # 打印2字节符串或字符数组变量str
(gdb) x/ws str # 打印4字节符串或字符数组变量str

(gdb) p array[index]@num # 从索引index开始，打印num个连续元素
(gdb) set print array-indexes on # 打印数组时，缺省不打印索引下标；显示索引下标

(gdb) display #跟踪变量或表达式，每次程序暂停时自动显示指定的值；也就是遇到断点、单步执行、或手动暂停时
(gdb) undisplay #取消跟踪变量

---------------------------------------打印protobuf message
(gdb) p .DebugString() # 用DebugString()将proto对象内部结构打印出来;

---------------------------------------打印CPUregister
(gdb) i r # 打印所有CPU寄存器的值，不包括浮点寄存器和向量寄存器
(gdb) i all-registers # 打印所有CPU寄存器
(gdb) i r es # 打印CPU寄存器es的值
(gdb) p $es # 打印CPU寄存器es的值

(gdb) layout regs # tui模式下，显示寄存器窗口
(gdb) tui reg float # 查看浮点寄存器
(gdb) tui reg general # 查看通用寄存器

---------------------------------------打印memmory
# n：正整数，你想查看的内存单元的数量
#
# f：打印格式：
# - x: 十六进制
# - d: 十进制
# - u: 十六进制
# - o: 八进制
# - t: 二进制
# - a: 十六进制
# - c: 字符格式
# - f: 浮点数
#
# u: 单个内存单元大小：
# - b: 单字节
# - h: 双字节
# - w: 四字节
# - g: 八字节
#
# addr: 要打印的内存地址
(gdb) x/<nfu> <addr> # 打印内存地址，<addr>可以是某个变量的指针值；常用x/nxb x/nub x/ntb

和_和和__用于内存调试

Copy
(gdb) x/16xb a # 打印内存
0x7fffffffe4a0: 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07
0x7fffffffe4a8: 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f

(gdb) p $_ # $_存储最后一次用x命令检查的内存地址
\$1 = (int8_t *) 0x7fffffffe4af

(gdb) p $__ # $__存储x命令最后检查的内存数据的值（通常是最后一个字节）
\$2 = 15

--------------------------------------一些 GDB 命令（如 i breakpoint）可能修改 $_ 值。
(gdb) i breakpoint # 这个命令生成一个匿名变量存储断点，地址0x00000000004004a0，值1
Num Type Disp Enb Address What
1 breakpoint keep y 0x00000000004004a0 in main at a.c:13
breakpoint already hit 1 time
(gdb) p $_
\$6 = (void *) 0x4004a0 <main+44>

打印进程的虚拟内存

Copy
(gdb) i proc mappings #显示当前进程的虚拟内存，包括各段的地址范围、大小、偏移量及访问权限等
process 27676 flags:
PR_STOPPED Process (LWP) is stopped
PR_ISTOP Stopped on an event of interest
PR_RLC Run-on-last-close is in effect
PR_MSACCT Microstate accounting enabled
PR_PCOMPAT Micro-state accounting inherited on fork
PR_FAULTED : Incurred a traced hardware fault FLTBPT: Breakpoint trap

Mapped address spaces:
# Start Addr: 段起始地址;End Addr: 段结束地址;Size: 段大小;Offset: 偏移量;Flags: 权限（r 可读，w 可写，x 可执行）
Start Addr End Addr Size Offset Flags
0x8046000 0x8047fff 0x2000 0xfffff000 -s--rwx
0x8050000 0x8050fff 0x1000 0 ----r-x
0x8060000 0x8060fff 0x1000 0 ----rwx
0xfee40000 0xfef4efff 0x10f000 0 ----r-x
0xfef50000 0xfef55fff 0x6000 0 ----rwx
0xfef5f000 0xfef66fff 0x8000 0x10f000 ----rwx
0xfef67000 0xfef68fff 0x2000 0 ----rwx
0xfef70000 0xfef70fff 0x1000 0 ----rwx
0xfef80000 0xfef80fff 0x1000 0 ---sr--
0xfef90000 0xfef90fff 0x1000 0 ----rw-
0xfefa0000 0xfefa0fff 0x1000 0 ----rw-
0xfefb0000 0xfefb0fff 0x1000 0 ----rwx
0xfefc0000 0xfefeafff 0x2b000 0 ----r-x
0xfeff0000 0xfeff0fff 0x1000 0 ----rwx
0xfeffb000 0xfeffcfff 0x2000 0x2b000 ----rwx
0xfeffd000 0xfeffdfff 0x1000 0 ----rwx

(gdb) i files #列出各段在内存中的位置，以及所加载共享库的.text，数据段、未初始化数据段.bss内容映射到虚拟内存的位置
Symbols from "/data1/nan/a".
Unix /proc child process:
Using the running image of child Thread 1 (LWP 1) via /proc.
While running this, GDB does not access memory from...
Local exec file:
`/data1/nan/a', file type elf32-i386-sol2.
Entry point: 0x8050950
0x080500f4 - 0x08050105 is .interp
0x08050108 - 0x08050114 is .eh_frame_hdr
0x08050114 - 0x08050218 is .hash
0x08050218 - 0x08050418 is .dynsym
0x08050418 - 0x080507e6 is .dynstr
0x080507e8 - 0x08050818 is .SUNW_version
0x08050818 - 0x08050858 is .SUNW_versym
0x08050858 - 0x08050890 is .SUNW_reloc
0x08050890 - 0x080508c8 is .rel.plt
0x080508c8 - 0x08050948 is .plt
......
0xfef5fb58 - 0xfef5fc48 is .dynamic in /usr/lib/libc.so.1
0xfef5fc80 - 0xfef650e2 is .data in /usr/lib/libc.so.1
0xfef650e2 - 0xfef650e2 is .bssf in /usr/lib/libc.so.1
0xfef650e8 - 0xfef65be0 is .picdata in /usr/lib/libc.so.1
0xfef65be0 - 0xfef666a7 is .data1 in /usr/lib/libc.so.1
0xfef666a8 - 0xfef680dc is .bss in /usr/lib/libc.so.1

打印 heap

在.gdbinit 中添加自定义打印内存分配命令

Copy
define mallocinfo
set $__f = fopen("/dev/tty", "w") # 打开 /dev/tty 文件进行输出
call malloc_info(0, $__f) # 调用 malloc_info 函数将内存分配信息输出到文件
call fclose($__f) # 关闭文件
end

调试程序并检查内存分配：

Copy
(gdb) mallocinfo
<malloc version="1"> #使用的内存分配函数的版本
<heap nr="0"> #第一个堆，多线程程序会为每个线程分配一个独立的堆，减少线程间竞争

<sizes> #不同大小的内存块的分配情况，在示例程序中，没有分配内存块
</sizes>

<total type="fast" count="0" size="0"/> #快速分配和其它类型内存块，count="0"表示当前没有分配内存块，size="0"表示占用内存大小为0。
<total type="rest" count="0" size="0"/> #这部分是针对malloc内部实现的，通常用来优化内存分配，区分不同的内存池。
<system type="current" size="1134592"/> #当前从os请求并分配的内存
<system type="max" size="1134592"/> #so允许程序最大能分配的内存，说明程序当前没有超过系统分配的限制。
<aspace type="total" size="1134592"/> #地址空间的大小
<aspace type="mprotect" size="1134592"/> #mprotect 是一个系统调用，用于改变某一段内存的访问权限，用来保护内存不被非法修改
</heap>

<total type="fast" count="0" size="0"/>
<total type="rest" count="0" size="0"/>
<system type="current" size="1134592"/>
<system type="max" size="1134592"/>
<aspace type="total" size="1134592"/>
<aspace type="mprotect" size="1134592"/>
</malloc>

多态接口中按实参类型打印形参变量

Copy
#include <iostream> //示例代码
using namespace std;

class Shape {
public: virtual void draw () {}
};

class Circle : public Shape {
int radius;
public:
Circle () { radius = 1; }
void draw () { cout << "drawing a circle...\n"; }
};

class Square : public Shape {
int height;
public:
Square () { height = 2; }
void draw () { cout << "drawing a square...\n"; }
};

void drawShape (class Shape &p){ //暴露一个多态的接口，依据传入对象类型，调用不同派生类重载的draw 实现
p.draw ();
}

int main (void){
Circle a;
Square b;
drawShape (a);
drawShape (b);
return 0;
}

gdb 默认按声明类型打印对象，则打印上面多态接口 p 的类型总是 Shape ；我们希望在这个多态接口函数栈帧中按实参类型打印形参变量

Copy
(gdb) set print object on

GDB Pretty Printers

STL 容器内部使用了复杂的内存布局，GDB 直接打印 STL 容器会显示一堆底层实现细节；GCC 提供 Python 脚本 (GDB 7.0 始功能) 增强 GDB 对 C++ 容器的打印功能

gcc 高版本 (>4.6.0) 情况下会自带 pretty printer：/usr/share/gcc/python
gcc.gnu.org Git - gcc.git/tree - libstdc++-v3/python/libstdcxx/

1) 将 Pretty Printers 脚本加到 cpp 项目的.gdbinit

Copy
python
import sys
sys.path.insert(0, '/usr/share/gcc/python')
from libstdcxx.v6.printers import register_libstdcxx_printers
register_libstdcxx_printers (None)
end

2）

Copy
(gdb) i pretty-printer #查看是否成功启用了STL容器Pretty Printers

(gdb) set print pretty on

dependencies

shared library

Copy
(gdb) i sharedlibrary # 列出程序加载的所有共享链接库
(gdb) i sharedlibrary hiredi # 列出 符合正则hiredi 的所有共享链接库

macro

gcc -g3以下编译生成的程序不包含预处理器宏信息：

(gdb) p NAME No symbol "NAME" in current context.

如果想在 gdb 中查看宏信息，可以使用 gcc -g3 进行编译：

Copy
(gdb) p NAME
$1 = "Joe"

points

Copy
(gdb) i b # 查询所有断点

(gdb) i watch # 列出所有观察点

source

gdb 命令行状态下 CTRL + X + A 调出代码窗口 tui，再按一次退出代码窗口。

Copy
(gdb) dir <source_directory> # 添加查找source的路径
(gdb) set substitute-path /old /new # 把旧的source检索路径替换为新的路径

(gdb) l # print暂停处代码
(gdb) l <function> # print函数function的代码
(gdb) l <file:function> # print文件file的函数function代码
(gdb) l <file:line> # print文件file中指定行数line代码
(gdb) l - # 向后打印
(gdb) l +

(gdb) focus #tui模式
gdb -tui program #以tui模式启动
----------------调整tui窗口大小
win <win_name> +7 #win_name可以是src、cmd、asm、regs窗口；+或-显示的行数

gdb info

Copy
(gdb) show version #查看gdb版本信息
(gdb) show copying #查看gdb版权相关信息
(gdb) show warranty #查看gdb免责声明

gdb -help #帮助信息

(gdb) apropos set #查找所有符合set正则表达式的命令信息
(gdb) help b #help command命令得到某一个具体命令用法

function debugging

栈帧跟踪

stack frame：栈帧保持函数执行环境，帧指针Frame Pointer 指向当前栈帧底部，栈帧基址 Stack Pointer始终指向栈帧顶部。

每次函数调用，都会在调用栈call stack 上维护一个栈帧stack frame。每个独立的栈帧一般包括：
Stack Pointer→
）local variables
）register
）Frame Pointer
）return address 当前函数过程的下一条指令地址
) arguments 传入参数创建的形参变量
）previous Frame Pointer
Frame Pointer→

Copy
(gdb) i functions #列出可执行文件被定义的函数，以及引用的的链接函数和系统函数
(gdb) i functions thre* #列出名称匹配 正则表达式thre 的函数,正则后面的*表示是否带调试信息

(gdb) bt # 打印 程序执行到当前断点的 函数调用栈
(gdb) frame # 显示当前所在调用栈层次、所在文件及行号等

(gdb) frame 2 # 选择 第二层栈帧 ,查看其中变量
(gdb) up 1 # 切换到上一个栈帧，也就是previous 调用栈帧
(gdb) down 2
(gdb) up-silently n # 静默切换版
(gdb) down-silently n
(gdb) frame addr # 选择 帧数地址为addr的栈帧 ，也就是下面的called by frame 指向的上一个栈帧的帧底previous Frame Pointer

(gdb) i frame # 打印当前选择的 函数栈帧内容，默认打印最上层
Stack level 0, frame at 0x7fffffffe590: #最上层栈帧，Stack Pointer
rip = 0x40054e in func (a.c:5); #return address指向func下一条要执行的指令
saved rip = 0x400577 #上一栈帧的return address，即调用func的地方，也是当前函数执行完毕后，应该跳转到的地址
called by frame at 0x7fffffffe5a0 #上一个栈帧的Frame Pointer
source language c. #
Arglist at 0x7fffffffe580, args: a=1, b=2 #形参变量
Locals at 0x7fffffffe580, Previous frame's sp is 0x7fffffffe590 #局部变量，上一个栈帧的Stack Pointer，这里由于是最上层栈帧，指向自己
Saved registers: #
rbp at 0x7fffffffe580, rip at 0x7fffffffe588 #寄存器

---------------------print尾调用堆栈帧
#开启O1的Tail call优化选项编译：当一系列函数最后一条指令是调用另外一个函数的情况优化为：函数直接调用了最终函数，而中间函数在汇编层面被优化掉了
(gdb) set debug entry-values 1 # 设置：除输出正常函数堆栈帧信息外，还输出尾调用相关信息

---------------------打印函数调用栈各层局部变量
(gdb) bt # 打印 程序执行到当前断点的 函数调用栈
#0 fun_a () at a.c:6
#1 0x000109b0 in fun_b () at a.c:12
(gdb) bt full # 打印 各层局部变量
#0 fun_a () at a.c:6
a = 0
#1 0x000109b0 in fun_b () at a.c:12
b = 1
(gdb) bt full n # 打印 最上n层的局部变量
(gdb) bt full -n # 打印 最下n层的局部变量

函数执行控制

Copy
(gdb) finish # 运行直到跳出当前函数
(gdb) return val # 不执行函数剩下的语句，直接返回val值

(gdb) call func() # 停在某处时运行此语句，直接调用执行文件中定义的func函数
(gdb) print func() # 停在某处时运行此语句，直接调用执行文件中定义的func函数

进入不带调试信息的函数

默认情况下，(gdb) s不会进入不带调试信息的函数，并如同(gdb) n那样跳过；

不带调试信息的原因：
1）没开启-g生成调试信息，特别是链接的共享库，提供方编译时就没开启-g
2）-O2 或 -O3优化掉了
3）内联函数被编译器嵌入调用位置，因此不会在调试中作为独立函数出现。可通过编译选项禁用内联优化

Copy
(gdb) set step-mode on #设置不跳过没有调试信息的函数，即使没调试信息，也可以先进入，然后开启汇编调试

$_exitcode 用于打印程序退出状态码

Copy
(gdb) p $_exitcode

进 / 线程调试

进 / 线程执行控制

默认情况下，gdb 检测到有线程产生和退出时，会打印提示信息

Copy
(gdb) set print thread-events off # 关闭打印提示

#避免在调试一个线程时，其他线程的执行会干扰你的调试过程
(gdb) set scheduler-locking off # 默认，gdb只控制调试的单线程运行，其他线程的调度是独立的
(gdb) set scheduler-locking on # 锁定调度，当调试一个线程时，其他线程阻塞，直到显式切换线程或完成调试。
(gdb) set scheduler-locking step # 用 step 单步调试时，其他线程会被暂停；用 next 调试，其他线程可能继续执行。

(gdb) thread apply <id1><id2> command # 指定线程集执行gdb命令
(gdb) thread apply all command # 指定线程集执行gdb命令

进程调试

调试多进程程序时，GDB 默认控制父进程运行而子进程独立运行。

Copy
---------------------------------调试非锁定调度的fork进程：一个进程gdb控制执行，一个独立运行
(gdb) set follow-fork-mode child # GDB在fork()暂停，如果(gdb) n跟踪的就是父进程，此时可以告诉gdb跟踪子进程；此命令在非linux上不知道是否支持
(gdb) start
(gdb) n

(gdb) set follow-fork-mode parent # 默认控制父进程运行（默认）

(gdb) i inferiors # 显示当前调试的所有进程，包括父进程和子进程
(gdb) inferior <num> # 切换到指定编号的进程num调试
(gdb) attach <pid> # 相当于切换跟踪

---------------------------------调试锁定调度的的fork进程：父子进程由gdb控制为交替执行，并发调度的
(gdb) set detach-on-fork on # 默认

(gdb) set detach-on-fork off # 同时调试父子进程
(gdb) start # 程序会在fork()调用处停下来。
(gdb) i inferiors # 显示当前调试的所有进程，包括父进程和子进程
(gdb) inferior <num> # 切换到指定编号的进程num调试
(gdb) n

---------------------------------调试可并行的父子进程：
(gdb) set schedule-multiple off # 默认

(gdb) set detach-on-fork off # 可同时调试父子进程
(gdb) set schedule-multiple on # 执行到 fork() 时，父子进程会并行运行
(gdb) start

---------------------------------调试多进程
gdb a
(gdb) start
(gdb) add-inferior -copies 2 -exec b #-copies复制进程数，-exec指定要加载的可执行文件
(gdb) i inferiors
Num Description Executable
3 <null> /home/nanxiao/b
2 <null> /home/nanxiao/b
* 1 process 1586 /home/nanxiao/a
(gdb) inferior 2 #调试进程2
(gdb) clone-inferior -copies 1 #克隆当前调试的程序
(gdb) i inferiors
Num Description Executable
4 <null> /home/nanxiao/b
3 <null> /home/nanxiao/b
* 2 process 1590 /home/nanxiao/b
1 process 1586 /home/nanxiao/a

打印进程空间

Copy
--------------------------打印调试会话中所有进程空间
(gdb) maint info program-spaces # 打印调试会话中所有进程空间,包括进程空间号与所绑定的进程id
Id Executable
4 /home/nan/b
Bound inferiors: ID 4 (process 0)
3 /home/nan/b
Bound inferiors: ID 3 (process 0)
* 2 /home/nan/b
Bound inferiors: ID 2 (process 15902)
1 /home/nan/a
Bound inferiors: ID 1 (process 15753)

线程调试

Copy
(gdb) i threads # 查看当前程序中的所有线程编号，线程id，当前所处调用栈函数
(gdb) i threads 1 2 # 查看指定线程

(gdb) thread 2 # GDB 切换到线程 2调试，并显示该线程当前的调用栈函数

(gdb) wa a thread 2 # 只在 thread2线程 触发 a变量的观察点

pstack <pid> # 可事先dump某个进程下所有线程的thread id和backtrace，方便gdb调试

$_thread 用于线程调试

Copy
#可以帮助你快速定位并发程序中的潜在问题，比如竞态条件、死锁等
(gdb) watch a #监控变量a
(gdb) command 2 #设置当编号2的断点触发时自动执行gdb命令:打印修改a的线程
> printf "thread id=%d\n", $_thread #$_thread 会被替换为当前线程的线程 ID
> end

Solaris 专用线程调试 cmd

Copy
(gdb) maint info sol-threads # 查看当前程序中的所有线程：类型，编号，对应lwp,，当前线程状态，，该线程入口函数
user thread #1, lwp 1, (active)
user thread #2, lwp 2, (active) startfunc: monitor_thread
user thread #3, lwp 3, (asleep) startfunc: mem_db_thread
- Sleep func: 0x000aa32c # 如果线程处于休眠状态，还会显示休眠函数的地址Sleep func

Asynchronous Debugging

同步任务是按特定顺序执行的操作（如多线程按顺序互斥修改内存），常见的同步机制 (锁机制) 会在执行过程中阻塞其他任务

异步任务是含有需要等待的操作，异步任务要高效利用系统资源：需要在进行需等待的操作时让出 cpu，然后在操作完成时通过某种机制（如信号，事件循环、回调、线程池等）抢占当前正在执行的任务

signals debugging

GDB 提供了强大的信号处理控制功能

Copy
(gdb) i signals # 查看当前调试进程对每个信号的默认处理方式
Signal Stop Print Pass to program Description # 信号名、捕获信号是否停止程序、是否打印信息、GDB是否会传递给程序的自定义handler处理、描述
SIGHUP Yes Yes Yes Hangup
SIGINT Yes Yes No Interrupt # 这个信号不会传递给程序处理，而是用于进程控制的信号
SIGQUIT Yes Yes Yes Quit
...
SIGALRM No No Yes Alarm clock

(gdb) handle <Signal> nostop # 捕获 SIGHUP 信号不停止程序
(gdb) handle <Signal> stop

(gdb) handle <Signal> noprint
(gdb) handle <Signal> print

(gdb) handle <Signal> nopass #是否传递给程序，让程序用自定义handle处理
(gdb) handle <Signal> pass

(gdb) signal <Signal> # 给给调试程序发送信号,这比换到shell用kill发送方便
(gdb) signal 0 # 当程序暂停后，用这条使程序重新运行

$_siginfo 处理异常退出

$_siginfo 保存当前收到信号的详细信息。在处理诸如 SIGSEGV（段错误）或 SIGFPE（算术错误）等信号时，特别有用

Copy
(gdb) ptype $_siginfo
type = struct {
int si_signo; #信号编号，指示接收到的信号类型。
int si_errno; #信号相关的错误信息（通常为0）。
int si_code; #kernel给的附加信息，描述信号的具体原因。
union {
int _pad[28];
struct {...} _kill;
struct {...} _timer;
struct {...} _rt;
struct {...} _sigchld;
struct {...} _sigfault;
struct {...} _sigpoll;
} _sifields; #根据不同类型的信号，包含不同的结构体信息，提供更多信号特定的数据。
}
(gdb) ptype $_siginfo._sifields._sigfault
type = struct {
void *si_addr;
}
(gdb) p $_siginfo._sifields._sigfault.si_addr #引发查看段错误的内存地址
\$4 = (void *) 0x850e

死锁调试

core dump

Copy
ulimit -a
-a 　显示目前资源限制的设定。
-c <core文件上限> 　设定core文件的最大值，单位为区块。
-d <数据节区大小> 　程序数据节区的最大值，单位为KB。
-f <文件大小> 　shell所能建立的最大文件，单位为区块。
-H 　设定资源的硬性限制，也就是管理员所设下的限制。
-m <内存大小> 　指定可使用内存的上限，单位为KB。
-n <文件数目> 　指定同一时间最多可开启的文件数。
-p <缓冲区大小> 　指定管道缓冲区的大小，单位512字节。
-s <堆叠大小> 　指定堆叠的上限，单位 为KB。
-S 　设定资源的弹性限制。
-t <CPU时间> 　指定CPU使用时间的上限，单位为秒。
-u <程序数目> 　用户最多可开启的程序数目。
-v <虚拟内存大小> 　指定可使用的虚拟内存上限，单位为KB。

一般情况下，当设置了ulimit -c unlimited; 当程序出现严重错误（如段错误、非法指令等）时，将程序 machine states 转储到 core 文件，进行故障排查。

Copy
ulimit -c # 查看当前系统是否允许生成 core 文件；返回0表示系统禁用 core 文件生成。输出正整数表示系统允许生成的cool文件字节大小
ulimit -c unlimited # 调整生成 core 文件的大小限制
echo "/coredump/core.%e.%p" > /proc/sys/kernel/core_pattern #编辑/etc/sysctl.conf或/proc/sys/kernel/core_pattern文件设置core文件保存路径,文件名为core.<程序名>.<PID>

-------------生成core
(gdb) generate-core-file # 生成core文件，记录当前调试进程状态。默认生成的 core 文件名为 core.<PID>
(gdb) generate-core-file my_core_dump # 为 core 文件指定一个自定义的文件名

(gdb) gcore # gcore工具与gdb内部generate-core-file 命令作用一样，默认生成的 core dump 文件名为 core.<PID>
(gdb) gcore my_core_dump

#gcore工具可对正在运行的进程进行 core dump（即使该进程没有崩溃），生成该进程的内存快照：
ps aux | grep <your_program> # 找到进程的pid
gcore <pid> # 生成core.<PID>文件

-------------------分析core
gdb -c core.a.1402638140 ./a.out # 调试./a.out 生成的 core 文件 core
(gdb) bt full # 查看异常的backtrace，查看崩的地方
(gdb) i locals

#或者像下面那样加载coredump
gdb -q
(gdb) file ./a.out
(gdb) core /coredump/core.a.1402638140

汇编调试

print assembly

Copy
(gdb) disass # print当前汇编指令
(gdb) disassemble func # print func函数汇编指令
(gdb) disas /m func # 将源代码与汇编指令一起显示
(gdb) disassemble /r func # 以16进制print func函数机器码
(gdb) disassemble /mr func # 汇编+机器码

(gdb) i line 13 # 查看该行涉及的汇编指令的起始和结束地址
Line 13 of "foo.c" starts at address 0x4004e9 <main+37> and ends at 0x40050c <main+72>.
(gdb) disassemble 0x4004e9, 0x40050c # 查看地址范围对应的汇编指令

(gdb) set disassemble-next-line off
(gdb) set disassemble-next-line on # 自动反汇编后面要执行的代码
(gdb) set disassemble-next-line auto # 后面的代码没有源码的情况下才反汇编后面要执行的代码,一般是一些没调试信息的函数
(gdb) set disassembly-flavor intel # intel格式，同理att则为at&t格式
(gdb) display /ni $pc # 每次回到gdb命令行时，只把将要执行的n条汇编指令打印出来
(gdb) undisplay

(gdb) layout asm #tui模式下显示汇编代码窗口
(gdb) layout split #tui模式下汇编和source双窗口

汇编调试运行控制

Copy
(gdb) si # 单步执行一条汇编指令
(gdb) ni # 不跳入

在函数的第一条汇编指令打断点

Copy
-------eg:
#include <stdio.h>
int global_var;

void change_var(){
global_var=100;
}

int main(void){
change_var();
return 0;
}

给函数打断点的命令：“b func”，不会把断点设在汇编指令层次函数的开头:

Copy
(gdb) b main
Breakpoint 1 at 0x8050c12: file a.c, line 9.
(gdb) r
Starting program: /data1/nan/a
[Thread debugging using libthread_db enabled]
[New Thread 1 (LWP 1)]
[Switching to Thread 1 (LWP 1)]

Breakpoint 1, main () at a.c:9
9 change_var();
(gdb) disassemble
Dump of assembler code for function main:
0x08050c0f <+0>: push %ebp
0x08050c10 <+1>: mov %esp,%ebp
=> 0x08050c12 <+3>: call 0x8050c00 <change_var>
0x08050c17 <+8>: mov $0x0,%eax
0x08050c1c <+13>: pop %ebp
0x08050c1d <+14>: ret
End of assembler dump.

断点前加 * 才能在能把断点设置在汇编指令层次函数的开头，“b *func”：

Copy
(gdb) b *main
Breakpoint 1 at 0x8050c0f: file a.c, line 8.
(gdb) r
Starting program: /data1/nan/a
[Thread debugging using libthread_db enabled]
[New Thread 1 (LWP 1)]
[Switching to Thread 1 (LWP 1)]

Breakpoint 1, main () at a.c:8
8 int main(void){
(gdb) disassemble
Dump of assembler code for function main:
=> 0x08050c0f <+0>: push %ebp
0x08050c10 <+1>: mov %esp,%ebp
0x08050c12 <+3>: call 0x8050c00 <change_var>
0x08050c17 <+8>: mov $0x0,%eax
0x08050c1c <+13>: pop %ebp
0x08050c1d <+14>: ret
End of assembler dump.

修改被调试程序的二进制文件

修改被调试程序的二进制文件 | 100 个 gdb 小技巧

gdb logging

gdb 默认不开启日志，若开启了，则当前目录下生成 gdb.txt 记录 gdb 命令行所有输出结果，方便回溯历史。

Copy
(gdb) show logging #查看日志设置

(gdb) set logging on # 设置开启gdb日志
(gdb) set logging off
(gdb) set logging file log.txt # 修改日志文件路径为log.txt

(gdb) set logging redirect on #GDB 会话中的输出（包括命令的执行结果和调试信息）将被写入日志文件，而不是在终端显示。
(gdb) set logging redirect off

(gdb) set logging overwrite on #每次启动 GDB 并开启日志记录时，如果日志文件已经存在，则以覆写模式记录
(gdb) set logging overwrite off #追加模式记录

.gdb_history

gdb 缺省不保存历史命令；若保存，则历史命令缺省保存在当前目录下的.gdb_history

Copy
(gdb) set history save on # 开启gdb历史cmd保存
(gdb) set history filename ~/.gdbcmd # 设置保存路径

.gdbinit 模板

gdb 启动时，会执行 HOME 目录和当前目录下.gdbinit 脚本里的命令

c 项目

cpp 项目

Copy
# 打印STL容器中的内容
python
import sys
sys.path.insert(0, "/home/xmj/project/gcc-trunk/libstdc++-v3/python")
from libstdcxx.v6.printers import register_libstdcxx_printers
register_libstdcxx_printers (None)
end

# 保存历史命令
set history filename ~/.gdb_history
set history save on

# 退出时不显示提示信息
set confirm off

# 按照派生类型打印对象
set print object on

# 打印数组的索引下标
set print array-indexes on

# 每行打印一个结构体成员
set print pretty on

内存调试

todo

gdb 多窗口管理
查看对象类型
C++ 跨平台多线程
多线程调试管理
查找线程、线程断点
为线程执行命令
线程日志信息控制
调用内部及外部函数
跳过函数
制作、调试发行版
为软件制作补丁

内存泄漏检测
内存检查
远程调试
死锁调试
core dump 基础
栈溢出 core dump 分析
无调试符号 coredump 分析
软件 “破解”