跳转至

gdb教程

1.1 基本调试指令

(gdb)表示已进入调试程序,不代表实际指令,[command]中表示指令的全拼 1. 进入gdb调试:gdb 可执行程序 2. 设置参数【可选】:set args 参数1 参数2 ... 参数n 3. 开始执行程序 (start 将会在程序入口 main 函数打断点,再运行 run 停在 main 函数上):(gdb) start 4. 列出带行号的源代码(运行处上下几行):(gdb) l[list] 5. 打断点:(gdb) b[break] 行号 6. (重新)运行程序到下一个断点处停止:(gdb) r[run] 7. 继续到下一个断点处停下:(gdb) c[continue] 8. 往下执行一行代码(遇到函数不进入函数内部):(gdb) n[next] 9. 往下执行一行代码(遇到函数进入函数内部):(gdb) s[step] 10. 打印变量值:(gdb) p[print] 变量名 11. 终止调试:(gdb) q[quit]

1.2 break断点

1.2.1 打断点

有3种方式设置断点: 1. 根据函数名:b 命名空间::函数名 1. b nm::fun 2. 根据行号:b 【文件名(含路径)】:行号 1. b src/test.cc:30 3. 根据地址:b *地址 1. b *0x67aa88

1.2.2 删除断点

  1. delete [bp number]:不指定断点号,将删除所有断点
  2. clear [bp number]:不指定断点号,将删除所有断点

1.2.3 从文件中读取断点信息

  1. 将断点信息保存到指定文件:save breakpoints file-name-to-save
  2. 从指定文件读取断点信息:source file-name-to-save

1.2.4 tbreak

  • 功能:一次性断点,触发一次后就会自动删除
  • 语法:tbreak args

1.3 调用指定函数

  1. 使用call或print来执行函数:(gdb) call sleep(1)或者(gdb) printf sleep(1)

1.4 堆栈帧

  • 解释:GDB 调试器会按照既定规则对它们进行编号:当前正被调用函数对应的栈帧的编号为 0,调用它的函数对应栈帧的编号为 1,以此类推。
  • 查看当前堆栈信息:(gdb) bt
  • 跳到堆栈某一帧函数:(gdb) f[frame] 堆栈帧编号
  • 基于当前帧前进一个帧(即帧编号+1):(gbd) up [number默认1]
  • 基于当前帧后退一个帧(即帧编号-1):(gdb) down [number默认1]
  • 查看当前帧存储的信息:(gdb) info frame
  • 查看当前帧函数参数值:(gdb) info args
  • 查看当前帧局部变量值:(gdb) info locals

1.5 info查看信息

info简写是i 1. 打印所有函数名称:(gdb) info functions 2. 打印当前函数局部变量:(gdb) info locals 3. 打印当前函数堆栈帧信息:(gdb) info frame 4. 打印寄存器信息:(gdb) info registers 5. 打印线程信息:(gdb) info threads 6. 打印进程信息:(gdb) info inferiors

1.6 调试运行中的进程

  1. 在终端输入gdb进入gdb模式:gdb
  2. 连接运行中的进程(指定进程id):(gdb) attach processID
  3. 使用其它调试指令

1.7 设置调试程序的参数

有3种方式可以设置程序参数: 1. 启动gdb时:如$ gdb -args ./a.out a b c 2. 进入gdb后:如(gdb) set args a b c 3. run时:如(gdb) run a b c

1.7.1 显示参数

  • 语法:(gdb) show args

1.8 调试多进程

1.8.1 显示进程

  • (gdb) info inferiors:查看所有进程

1.8.2 切换进程调试

  • (gdb) inferiors 进程编号:切换到某一个进程上进行调试

1.8.3 父子进程调试切换

  • (gdb) set follow-fork-mode parent:调试父进程(默认行为)
  • (gdb) set follow-fork-mode child:调试子进程

1.8.4 多进程运行模式

  • (gdb) set detach-on-fork on:调试当前进程时,其它进程也会执行
  • (gdb) set detach-on-fork off:调试当前进程时,其它进程暂停执行

1.8.5 显示进程调试模式

  • (gdb) show detach-on-fork:显示多进程运行模式
    • on:表示调试当前进程时,其它进程也会执行
    • off:表示调试当前进程时,其它进程暂停执行
  • (gdb) show follow-fork-mode:显示调试父进程还是子进程
    • parent:调试父进程
    • child:调试子进程

1.9 调试多线程

1.9.1 显示线程

  1. (gdb) info threads

1.9.2 切换线程

  1. (gdb) thread id

1.9.3 单进程下线程运行模式

默认我们调试线程时,所有线程都会执行,如果我们只想要当前线程执行,可以如下设置。 - (gdb) set scheduler-locking [on|off|step] - off:不锁定任何线程,也就是所有线程都执行。 - on:锁定当前线程,使用next、step只有当前线程会执行。 - step:在执行 step 的时候,只有当前线程会执行,执行 next 时,所有线程都会执行 (这是默认值)。

1.9.4 多进程下线程运行模式

  • 当某个程序有多个进程时(inferior),且每一个进程有多个线程时,默认只会执行当前进程下的线程。
  • (gdb) set schedule-multiple on:同时运行所有进程的所有线程(前提scheduler-locking 是off状态,或者step时,不是使用step命令运行)
  • (gdb) set schedule-multiple off:其它进程的线程不会运行(默认行为)

1.9.5 查看线程模式

  1. (gdb) show scheduler-locking
  2. (gdb) show schedule-multiple

1.10 watch监听变量

  • 功能:当一个监听的变量值发生变化时,程序会在触发变量改变的位置处暂停,方便知道变量值是被那一段代码修改的。

1.10.1 添加监听点

  • (gdb) watch variable:指定监控的变量名
  • (gdb) watch *(data type*)address:指定监控的变量地址
  • 示例
(gdb) watch file_size
Hardware watchpoint 3: file_size
(gdb) p &file_size
$1 = (uint64_t *) 0x7fffffffd228
(gdb) watch *(uint64_t *) 0x7fffffffd228
Hardware watchpoint 2: *(uint64_t *) 0x7fffffffd228

1.10.2 显示监听点列表

  • (gdb) info watchpoints
  • 示例
(gdb) info watchpoints
Num     Type           Disp Enb Address            What
2       hw watchpoint  keep y                      *(uint64_t *) 0x7fffffffd228
3       hw watchpoint  keep y                      file_size

1.10.3 删除监听点

监听点是一种特殊的断点,删除监听点和删除断点的方法一样。
  • (gdb) d watchpoints
  • 示例
(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000432273 in rocksdb::EnvNcdfsTest_Basics_Test::TestBody() at env_ncdfs_gtest.cc:79
        breakpoint already hit 1 time
2       hw watchpoint  keep y                      *(uint64_t *) 0x7fffffffd228
3       hw watchpoint  keep y                      file_size
(gdb) d 2
(gdb) d 3
(gdb) info watchpoints
No watchpoints.

1.11 汇编

1.11.1 显示汇编代码

  • 语法:
    • disassemble position:只显示汇编代码
    • disassemble -r position:显示原生指令
    • disassemble -m position:源码和汇编同时显示(不推荐使用,将来会废弃,存在inline函数显示不全的问题)
    • disassemble -s position:源码和汇编同时显示(推荐使用)
  • 指定的汇编位置position有以下几种格式
    • function:如 disassemble main
    • addr-start,addr-end:如disassemble 0x32c4,0x32d4
    • addr-start+length:如disassemble &main+10disassemble $pc - 8

1.11.2 切换汇编指令格式

当今有2中汇编指令格式:
1. Intel 
2. AT&T

示例:Intel和AT&T格式比较

功能:给eax寄存器赋值1

Intel:
      add eax, 1

AT&T:
      addl $1, %eax
  • 语法:
    • set disassembly-flavor intel:启用Intel指令格式
    • set disassembly-flavor att:启用AT&T指令格式

1.11.3 显示汇编指令格式

  • 语法:show disassembly-flavor

1.12 command

  • 功能:为任何断点(或观察点或捕获点)提供一系列命令,以便在程序因该断点而停止时执行。例如,您可能想要打印某些表达式的值,或启用其他断点。

1.12.1 编写断点的command代码

  • 语法:
    1. command开始,以end结束。(command后不加断点号,默认是最近添加的一个断点)
    2. silent:必须是第一条命令,表示到达断点时不打印断点处的代码信息。
    3. ``
(gdb) commands [list…]
… command-list …
end
  • 示例
(gdb) command
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>silent
>set variable n = 0
>continue
>end

1.12.1.1 silent示例

  • 示例:其中第一条时不带silent触发断点,第2条时使用了silent时的执行,2条command都会打印i的值
(gdb) 

Breakpoint 3, rocksdb::EnvNcdfsTest_EBasics_Test::TestBody (this=0xf77970) at env_ncdfs_gtest.cc:654
654         std::string dir = v[i];
i = 0
(gdb) n
i = 0

1.12.1.2 输出指令

  • 官方文档
  • echo text:比如echo This is some text\n
  • printf template, expressions…:比如printf "foo, bar-foo = 0x%x, 0x%x\n", foo, bar-foo
  • output expression
  • eval template, expressions…

1.12.2 显示断点的command代码

  • info break:查看断点信息,如果有command,就会显示出来
  • 示例
(gdb) i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000000000043d48d in rocksdb::EnvNcdfsTest_EBasics_Test::TestBody() at env_ncdfs_gtest.cc:647
        breakpoint already hit 1 time
        silent
        printf "EnvNcdfsTest, EBasics"

1.12.3 删除断点的command代码

  • command后立即end,就会删除端点的command代码
  • 示例
(gdb) commands 3
Type commands for breakpoint(s) 3, one per line.
End with a line saying just "end".
>end

1.13 图形化调试界面

  • 方法一:启动gbd时指定-tui参数,如gdb -tui program
  • 方法二:进入调试后,按Crlt + X + A组合键

1.13.1 4种窗口类型

  • src:源码窗口
  • cmd:命令窗口(输入gdb指令的窗口,默认只打开这个窗口)
  • asm:汇编代码窗口
  • regs:寄存器窗口

1.13.2 layout显示窗口

  • 语法:(gdb) layout name,可使用的name如下
    • next:显示下一个窗口
    • prev:显示上一个窗口
    • src:显示源码窗口、命令窗口
    • asm:显示汇编代码窗口、命令窗口
    • split:显示源码窗口、汇编窗口、命令窗口
    • regs
      • 当在 src 布局时,显示寄存器窗口、源码窗口、命令窗口
      • 当在 asm or split 布局时,显示寄存器窗口、汇编窗口、命令窗口

When in src layout display the register, source, and command windows. When in asm or split layout display the register, assembler, and command windows.

1.13.3 winheight调整窗口大小

  • 语法:
    • (gdb) winheight win_name + count:窗口增加count行
    • (gdb) winheight win_name - count:窗口减少count行
    • win_name有4种:srccmdasmregs

1.13.4 focus更改当前可滚动的 TUI 窗口

当启动多个窗口时,只能有一个窗口是处于可滚动状态,如果此时需要切换滚动窗口,可使用focus命令进行切换
  • 语法:(gdb) focus name,可使用的name如下
    • next:使下一个窗口激活滚动
    • prev:使上一个窗口激活滚动
    • src:使源码窗口激活滚动
    • asm:使汇编代码窗口激活滚动
    • regs:使寄存器窗口激活滚动
    • cmd:使命令窗口激活滚动

1.13.5 refresh刷新窗口

当代码中有打印消息到控制台操作时,会污染窗口,此时使用refresh可以刷新窗口。 - 语法(gdb) refresh

1.14 打印变量值

1.14.1 打印STL数据

1.14.1.1 安装Python插件

  1. Python插件获取
    1. 从github上下载工具libstdc++-v3/python
    2. 或者从gcc源码包根目录下libstdc++-v3/python(在高版本gcc上有)
  2. 编写~/.gdbinit文件,其中sys.path.insert中路径改为插件路径
python
import sys
sys.path.insert(0, '/gcc-12.2.0/libstdc++-v3/python')
from libstdcxx.v6.printers import register_libstdcxx_printers
register_libstdcxx_printers (None)
end
  1. 在进入gdb模式下,可以使用p 变量名来打印STL数据

1.14.2 ptype打印对象类型

  1. ptype 对象名
  2. 示例
(gdb) ptype mm
type = std::map<unsigned long, std::map<unsigned int, unsigned int>>

1.15 高级调试技术

1.15.1 调试coredump文件

  1. gdb 可执行程序 core文件
  2. btwhere查看堆栈信息

1.15.2 修改变量值

  • 语法:
    • set var variable=expr,比如:set var i=1
    • set {type}address=expr,比如:set {int}0x8047a54 = 1,其中0x8047a54地址指向的int类型
    • 同样也可以修改寄存器的值,比如:set var $eax = 8

1.15.3 条件断点

  • 语法:break … if cond
  • 示例:break 20 if i==1:在当前文件第20行且变量i==1时,触发断点

1.15.4 跳转指定位置

跳转命令不会改变当前堆栈帧、堆栈指针、任何内存位置或除程序计数器之外的任何寄存器的内容。

jump的简写是j 跳转到指定行(通常用于错过了调试点,需要回溯)

# 先打断点,再跳转
(gdb) b linenum
(gdb) j linenum

1.15.5 重新加载程序

  • 使用场景:当修改了源代码,编译生成了新的库或者程序时,重新加载。
  • 方法:
    • 执行 runstart 命令,重新执行,同时会提示重新加载库或程序。
    • 或者使用 file 程序/库 重新加载。

2 简写汇总

  • run<=>r:重新运行程序,在第一个断点处暂停。
  • start:重新运行程序,在程序入口main处暂停
  • step<=>s:单步调试,进入函数内部
  • next<=>n:单步调试,不进入函数内部
  • continue<=>c:继续执行,在下一个断点处暂停
  • jump<=>j:跳转代码
  • info<=>i:显示信息
  • break<=>b:打断点
  • list<=>l:显示当前执行位置处上下5行代码
  • frame<=>f:切换堆栈
  • delete<=>d:删除断点
  • print<=>p:打印表达式值
  • until<=>u
  • backtrace<=>bt:查看堆栈信息
  • focus<=>fs:切换激活窗口的滚动
  • watch<=>wa
  • awatch<=>aw
  • rwatch<=>rw
  • ignore<=>ig
  • stepi<=>si
  • tbreak<=>tb
  • finish<=>fin
  • nexti<=>ni
  • winheight<=>win:跳转窗口大小
  • directory<=>dir
  • disassemble<=>disas