launch/attach process#
注意:gdb 中只需输入完整命令的前几个字母甚至首字母即可。前提是缩写不和其它命令有歧义
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#
#!/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
远程调试#
(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 生效的环境变量。通常是临时的,仅在调试会话期间有效。
(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 启动时运行初始化命令:
set env LD_PRELOAD=$LD_PRELOAD:/path/to/some/library
运行控制#
(gdb) r arg1 arg2 ... # 重新开始运行二进制
(gdb) start # 用于开始执行程序,并在程序的第一条指令处暂停执行,比如停在main{
(gdb) stop # 暂停运行
(gdb) c # 继续执行
(gdb) n # 单步执行,遇到函数则跳过
(gdb) s # 单步执行,遇到函数则跳入函数体
(gdb) until line # 运行直到到达指定行
breakpoints 设置#
-----------------------------------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 <var> # 运行到断点处时自动打印变量<var>
>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保存的断点
在程序入口处打断点#
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
快速尝试不同输入#
在不修改源码,不重新编译的情况下,通过断点命令 + 反向调试为函数尝试不同输入
(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 用软件方式实现。通常影响程序运行速度,因为涉及到不断地检查和管理内存。
(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 <a>
(gdb) watch *(int*)0x6009c8 # 用内存地址的解引用设置观察点
catchpoint 设置#
catchpoint
用于捕捉特定事件发生(如信号、函数调用、系统调用等)时中断程序执行;catchpoint
默认是持久的,会在程序每次触发对应事件时中断。
(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
返回值
----------------------------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#
(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 在该执行点暂停,需手动设置断点。
(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)调用栈显示全是问号的问题
(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 环境执行#
(gdb) shell # 进入shell模式,回到linux终端
(gdb) !<shell_cmd> # 相当于(gdb) shell <shell_cmd>;
(gdb) pwd # 在gdb中切换工作目录
(gdb) cd tmp
(gdb) exit # 退出shell模式,回到gdb命令行
print#
输出设置#
指定 gdb 输入输出设备#
缺省情况下程序的输入输出和 gdb 使用同一个终端
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 就会全部输出,中间不会暂停
(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#
---------------------------------------打印variables/打印stack
(gdb) whatis <var> # 打印变量类型
(gdb) ptype <var> # 打印变量类型定义
(gdb) i variables # 打印当前作用域所有变量所在文件及类型
(gdb) i variables ^<var>$ # ^ 表示以<var>开头;$ 表示以<var>结尾
(gdb) p <var> # 打印变量<var>
(gdb) i locals # 列出当前所处函数栈帧中所有局部变量(包括静态局部变量)
(gdb) i variables # 列出所有的全局和静态变量
(gdb) i variables var # 列出以正则var开头的全局和静态变量
(gdb) p 'file.c'::<var> # 属于不同文件的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 <var> #跟踪变量或表达式,每次程序暂停时自动显示指定<var>的值;也就是遇到断点、单步执行、或手动暂停时
(gdb) undisplay <var> #取消跟踪变量
---------------------------------------打印protobuf message
(gdb) p <var>.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
和_和和__用于内存调试#
(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>
打印进程的虚拟内存#
(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 中添加自定义打印内存分配命令
define mallocinfo
set $__f = fopen("/dev/tty", "w") # 打开 /dev/tty 文件进行输出
call malloc_info(0, $__f) # 调用 malloc_info 函数将内存分配信息输出到文件
call fclose($__f) # 关闭文件
end
调试程序并检查内存分配:
(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>
多态接口中按实参类型打印形参变量#
#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 ;我们希望在这个多态接口函数栈帧中按实参类型打印形参变量
(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
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)
(gdb) i pretty-printer #查看是否成功启用了STL容器Pretty Printers
(gdb) set print pretty on
dependencies#
shared library#
(gdb) i sharedlibrary # 列出程序加载的所有共享链接库
(gdb) i sharedlibrary hiredi # 列出 符合正则hiredi 的所有共享链接库
macro#
gcc -g3
以下编译生成的程序不包含预处理器宏信息:
(gdb) p NAME No symbol "NAME" in current context.
如果想在 gdb 中查看宏信息,可以使用 gcc -g3 进行编译:
(gdb) p NAME
$1 = "Joe"
points#
(gdb) i b # 查询所有断点
(gdb) i watch # 列出所有观察点
source#
gdb 命令行状态下 CTRL + X + A 调出代码窗口 tui,再按一次退出代码窗口。
(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#
(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→
(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层的局部变量
函数执行控制#
(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)内联函数被编译器嵌入调用位置,因此不会在调试中作为独立函数出现。可通过编译选项禁用内联优化
(gdb) set step-mode on #设置不跳过没有调试信息的函数,即使没调试信息,也可以先进入,然后开启汇编调试
$_exitcode 用于打印程序退出状态码#
(gdb) p $_exitcode
进 / 线程调试#
进 / 线程执行控制#
默认情况下,gdb 检测到有线程产生和退出时,会打印提示信息
(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 默认控制父进程运行而子进程独立运行。
---------------------------------调试非锁定调度的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/nan/b
3 <null> /home/nan/b
* 2 process 1590 /home/nan/x
1 process 1586 /home/nan/a
打印进程空间#
--------------------------打印调试会话中所有进程空间
(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)
线程调试#
(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 用于线程调试#
#可以帮助你快速定位并发程序中的潜在问题,比如竞态条件、死锁等
(gdb) watch a #监控变量a
(gdb) command 2 #设置当编号2的断点触发时自动执行gdb命令:打印修改a的线程
> printf "thread id=%d\n", $_thread #$_thread 会被替换为当前线程的线程 ID
> end
Solaris 专用线程调试 cmd#
(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 提供了强大的信号处理控制功能
(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
(算术错误)等信号时,特别有用
(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#
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 文件,进行故障排查。
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#
(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双窗口
汇编调试运行控制#
(gdb) si # 单步执行一条汇编指令
(gdb) ni # 不跳入
在函数的第一条汇编指令打断点#
-------eg:
#include <stdio.h>
int global_var;
void change_var(){
global_var=100;
}
int main(void){
change_var();
return 0;
}
给函数打断点的命令:“b func”,不会把断点设在汇编指令层次函数的开头:
(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”:
(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.
修改被调试程序的二进制文件#
gdb logging#
gdb 默认不开启日志,若开启了,则当前目录下生成 gdb.txt 记录 gdb 命令行所有输出结果,方便回溯历史。
(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
(gdb) set history save on # 开启gdb历史cmd保存
(gdb) set history filename ~/.gdbcmd # 设置保存路径
.gdbinit 模板#
gdb 启动时,会执行 HOME 目录和当前目录下.gdbinit 脚本里的命令
c 项目#
cpp 项目#
# 打印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 分析
软件 “破解”