GDB入门:常用指令、脚本、插件

GNU调试器(英语:GNU Debugger,缩写:gdb)是一个由GNU开源组织发布的、UNIX/LINUX操作 系统下的、基于命令行的、功能强大的程序调试工具。 总的来说,gdb有以下4个功能:

  • 启动程序,并指定可能影响其行为的所有内容
  • 使程序在指定条件下停止
  • 检查程序停止时发生了什么
  • 更改程序中的内容,以便纠正一个bug的影响

gdb需要被调试的程序中含有debugging的信息,因此在用gcc编译文件时,需要增加-g选项来提供gdb需要的调试信息。

gcc [other flags] -g <source files> -o <output file>

使用objcopy来分离调试信息

objcopy --only-keep-debug <output file> xxx.debug

使用strip来删除原文件里的调试信息

strip --strip-debug --strip-unneeded <output file>

GDB常见指令

一般设置

(gdb) start:单步执行,运行程序,停在第一执行语句

(gdb) run:重新开始运行文件,简写r

  • run-text:加载文本文件
  • run-bin:加载二进制文件

(gdb) add-symbol-file filename address:从filename中读取额外的symbol table信息,并加载到指定address,filename被动态加载到address时可以使用

断点设置相关指令

(gdb) break:设置断点

  • 断在具体的函数 break func
  • 断在某一行 break filename:num
  • 断在某一程序地址处 b *address
  • 条件断点 break filename:num if i >= ARRAYSIZE

(gdb) watch my_var 在变量上设置断点,当该变量被修改(write)时断住程序

(gdb) rwatch 在指定内存上设置断点,当该内存被访问(read)时断住程序

  • rwatch *(int *)0xfeedface 指定watch的内存长度

(gdb) awatch *0xfeedface 在指定内存上设置断点,当该内存被访问/修改(read/write)时断住程序

(gdb) info breakpoints 显示所有声明的断点

(gdb) delete 删除断点

程序执行流程相关常用指令

(gdb) continue:运行程序直到下一个断点,简写c

(gdb) next:单步调试(逐过程,函数直接执行,把函数当成一条指令执行),简写n

  • nexti:在汇编指令层面的next ,简写ni

(gdb) step:单步调试(逐过程,进入函数执行),简写s

  • stepi:在汇编指令层面的step ,简写si

(gdb) finish:结束当前函数,返回到函数调用点

(gdb) until lineNo: 跳出loop,运行程序直到lineNo

打印当前程序状态相关常用指令

(gdb) backtrace:查看函数的调用的栈帧和层级关系,简写bt

(gdb) frame:切换函数的栈帧,简写f

(gdb) print:打印值及地址,简写p

  • 以16进制的方式打印变量值 p/x my_var
  • 打印指针指向的内存值 p *p

(gdb) info:查看函数内部局部变量的数值,简写i

  • 查看寄存器的值i register xxx
  • i registers

(gdb) x: 打印指定地址的数据

  • 以16进制打印pc指向的地址值 x/x $pc
  • 打印pc指向的5条指令内容 x/5i $pc
  • 打印testArray中的字符串 x/s testArray

(gdb) show:展示debugger的信息

(gdb) display:追踪查看具体变量值

(gdb) layout xxx:打开TUI(Text User Interface)模式,在debug的时候可以在额外窗口中展示源码内容

  • layout asm: 展示汇编和命令行窗口
  • layout src: 展示源码和命令行窗口
  • layout regs:展示寄存器+源码+命令行窗口
  • layout split:展示汇编+源码+命令行窗口

(gdb) focus name:当有多个窗口时,切换窗口

  • focus [next|prev]: activate next/prev window for scrolling
  • focus cmd: activate command window for scrolling,上下键可以用来切换gdb命令

其他指令

(gdb) help command:查看command相关用法

(gdb) set logging file xxx.txt:gdb输出打印到指定文件

(gdb) set logging [on|off]:打开/关闭logging

(gdb) set logging overwrite [on|off]

GDB脚本的使用

  1. 变量
  • 访问程序参数信息 $argc, $arg0, $arg1
  • 自定义变量 set $my_var=0
  1. 用户自定义命令
define commandName  
    statement  
    ......  
end 

其中statement可以是任意gdb命令,比如下面的一个自定义命令

define adder
  set $i = 0
  set $sum = 0
  while $i < $argc
    eval "set $sum = $sum + $arg%d", $i
    set $i = $i + 1
  end
  print $sum
end
  1. 使用document commandname来说明用户已经定义的命令commandname的功能,使用help命令可以获得该命令说明。
document adder  
    add up all arguments 
end 
  1. 给breapoint设置自定的command
# at entry point - cmd1
b main
commands 1
  print argc
  continue
end
  1. 脚本文件的注释以#开头
  2. gdb中执行脚本
  • 在运行的gdb中要使用source命令,例如:source xxx.gdb
  • 在gdb启动时,指定运行的脚本gdb <exe_file> --command=xxx.gdb
  • .gdbinit中添加自定义的命令,gdb会在启动之后执行.gdbinit

GDB插件

  1. 强烈推荐gef
  2. pwndbg

QEMU与GDB的交互

通过GDB调试运行在QEMU中的kernel

QEMU支持通过 gdb 的远程连接工具(“gdbstub”)使用 gdb。

  1. QEMU使用tcp协议的1234端口与GDB进行通信,以qemu-system-riscv64为例: qemu-system-riscv64 -nographic -machine virt -kernel vmlinux -bios default -S -s
  • -s选项表示QEMU在TCP协议的1234端口上监听来自 gdb 的传入连接
  • -S选项表示在gdb接入之前,guest不会被启动;即需在gdb中启动程序
  1. 启动GDB,加载内核符号表gdb vmlinux 在GDB中,通过target remote localhost:1234接入QEMU

注意:GDB只能通过虚拟地址访问QEMU的内存

通过GDB调试QEMU源码

  1. 编译QEMU,增加debug选项
git clone git://git.qemu-project.org/qemu.git
cd qemu
# 将代码切换到所需要分析的源码版本,如要分析最新版本,下面一行命令不用执行
git checkout v6.2.0
mkdir build && cd build
../configure --target-list=x86_64-softmmu --enable-debug
make
  1. 通过GDB启动QEMU gdb ./qemu/bin/debug/native/qemu-system-x86_64 在GDB中设置QEMU运行参数
  set args -enable-kvm -cpu host -m size=1G  -nographic -hda bionic-server-cloudimg-amd64.img -serial tcp:127.0.0.1:1234

其中-serial tcp:127.0.0.1:1234 表示重定向QEMU的输出到1234端口

  1. 新建一个新终端,监听4555端口nc -lv 1234,该窗口中会有QEMU的输出
  2. 在GDB中设置断点如b main,运行QEMU程序r
  3. 在GDB中设置handle SIGUSR1 noprint nostop忽略user signal

使用GDB调试Rust

在cargo.toml中加入以下profile,将符号表编入elf内

# The development profile, used for `cargo build`
[profile.dev]
opt-level = 0  # Controls the --opt-level the compiler builds with
debug = true   # Controls whether the compiler passes `-g`
# The release profile, used for `cargo build --release`
[profile.release]
opt-level = 3
debug = false

或是使用rustc -g去编译rs文件

GDB常见报错

  1. Cannot access memory at address xxx
  2. To be continued…

Useful GDB Reference website

  1. 100个gdb小技巧
  2. Debugging with GDB
  3. Extending GDB
  4. gdb-customize it the way you want
  5. GNU GDB Debugger Command Cheat Sheet

Reference

  1. GDB Tutorial: A Walkthrough with Examples
  2. gdb Cheatsheet CSCI0330 Fall 2017
  3. GDB usage – QEMU document
  4. How to generate gcc debug symbol outside the build target?
  5. What are the best ways to automate a GDB debugging session?
  6. 如何写gdb命令脚本
  7. Scripting GDB



Enjoy Reading This Article?

Here are some more articles you might like to read next:

  • Solana Core Concepts: Accounts, Programs, and PDAs
  • Python3 Concurrency: asyncio module/async/await
  • Python Cheat Sheet
  • MIT6.824 Lab2A Raft Leader Election
  • Linux中的top command