GDB 支持的语言

Ada,Assembly,C,C++,D,Fortran,Haskell,Go,Objective-C,OpenCL C,Modula-2,Pascal,Rust。

gdb实现原理

调试器与进程建立联系

使用linux中ptrace()系统调用

使用时会建立ptracer和ptracee关系,从而进行交互。

软断点

gdb通过对代码进行修改,将目标代码修改为0xcc程序码,触发一个调试终端,发送sigtrap信号然后程序暂停执行,调试器接收到信号后,把0xcc恢复,然后将pc指针回退一步,等待进一步指令,如果不取消断点,执行完当前命令后,Tracer又会重新插入

基础使用

语法规则

1
${register} #代表使用该寄存器地址
1
*{address} #该地址指向的值
1
{num}x #16进制数字
1
!{command}#在进入路径处执行命令,但不能使用cd等改变gdb目标地址
1
# 在‘#’进行注释

gdb启动程序

gdb要跟踪程序存在三种方法

gdb中加载

优点

  1. 简单调试易于使用
  2. 可以在main或_start函数前下断点

缺点

  1. 默认未开启ASLR随机化
  2. 启动行为存在差异
  3. 内存布局存在差异
  4. 环境变量纯在差异

方法

  1. 打开gdb时设置参数

    1
    2
    gdb -q "./pwn" #无参数
    gdb -ex "run" --args 【文件名】 【参数1】 【参数2】...

    这里延申一下, -ex指令可以实现进入后执行引号内的命令

  2. 进入gdb中进行调用

    1. 设置程序文件

      1
      file 【文件名】
    2. 设置参数

      1
      set args 【参数1】 【参数2】...
      1
      set args # 清空参数

attach附加进程

优点

  1. 环境和远程靶机相似性高
    1. 默认开启ASLR地址随机化
    2. 启动行为基本一致
    3. 环境变量基本一致
    4. 内存布局基本一致
  2. 调试结束后不影响程序进行

缺点

程序交互前无法下断点

方法

1
pwndbg> attach 【PID】

gdbserver用过网络协议调试

优点

  1. 跨机器调试
  2. 使用docker启动gdbserver调试
  3. 与IDA Pro等联动调试

缺点

  1. 程序交互麻烦
  2. 程序启动不方便

方法

靶机(gdbserver)

1
【port】 【文件】【参数1】 【参数2】

主机(gdb)

1
target remote:【IP】:【port】
  • 默认监听0.0.0.0
  • target remote默认连接localhost

退出和日志

退出GDB

直接关闭进程退出

1
quit

关闭进程

1
kill 

断开附加

1
detach

暂停(终止)进程查看面板

1
Ctrl + C

记录日志

开启记录设置

1
set logging on

关闭

1
set logging on

面板介绍

image-20240429190809526.png

调试信息

载入的源文件·

image-20240429190947729.png

这一段是固定的

image-20240429191044779.png

它说明了各种数据对应的颜色,方便分别下方数据

  • 黄色:栈中数据(栈段)
  • 蓝色:堆中数据
  • 红色:代码段数据
  • 紫色:数据段数据
  • 下划线:可读可写可执行的数据
  • 白色:只读

寄存器

image-20240429191739279.png

  • 寄存器名称
  • 寄存器储存数据
  • 解引用的数据

向右箭头代表可以继续解引用的数据,想左代表不能继续解引用

汇编

image-20240429192045856.png

  • 程序运行到当前的地址<对应当前函数编译地址>
  • 反汇编指令

绿色代表执行到的位置

栈帧信息

image-20240429192645761.png

  • 相对于sp的偏移/8(序号): 16进制的偏移量 (默认为+)
  • 相对于rbp的偏移
  • bp的地址
  • 数据

调用循序(系统栈)

image-20240429193514357.png

上下之间的关系是callee->caller的关系

最下方是最开始的函数,最上方为当前函数

断点

指定程序在执行过程中的某个特定点暂停执行。调试器可以检测此时程序状态,包括变量的值执行路径等。

1
break 或 b #当前pc处断点
1
break {function} 或 b在函数function处下断点,会搜索所有符号并断点
1
break *{address} #在地址
1
break X.c {lines} #在源码X.c文件的lines处下断点
1
tbreak {express} 或 tb #设置临时断点
1
rbreak {regexpr} 或rb #设置满足正则匹配的所有断点
1
hbreak  {expr} 或 hb #使用硬件断点
1
condition {id} {expr} #设置id条件断点,只要条件生效时才发生
1
break {exp} if {cond} #设置条件断点,只要满足条件时才方式
1
info breakpoints 或 info b #显示所有断点/观察点/捕获点
1
delete {id} #删除id的断点/观察点/捕获点
1
delete #清空断点/观察点/捕获点
1
clear {expr} #根据表达式清空断点/观察点/捕获点
1
enable {id} #启用id断点/观察点/捕获点
1
disable {id} #关闭id断点/观察点/捕获点
1
ignore {id} {count} #忽略id断点/观察点/捕获点count次

观察点

监视数据读写事件,当对应事件发生时,程序停止运行

1
watch *{address} 或 w #当地址对应地址数据发生改变或修改时,程序中断
1
watch *(int*) {address} #监视四字节读写
1
watch *(long long int*) {address} #监视四字节读写
1
rwatch 或 rw #当对应地址读取时中断
1
awatch 或 aw #读或者写的时候中断
1
info watchpoints #查看所有观察点/断点/捕获点

捕获点

监视程序的系统调用、信号、异常事件

1
catch exec #设置捕获点,捕获系统调用
1
catch fork/vfork #设置捕获fork/vfork系统调用
1
catch syscall #设置调用号为syscall的系统调用
1
catch signal {name/id} #捕获对应id/name的信号
1
catch assert #捕获失败断言
1
info tracepoints

信号和核心转储

信号

进程间的通信方式,用于查询的状态、信息的命令,gdb可以截获或者伪造,命令终止的根本原理就是调试器伪造终止信号发送给进程

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
SIGHUP       1          /* Hangup (POSIX).  */                          终止进程     终端线路挂断
SIGINT 2 /* Interrupt (ANSI). */ 终止进程 中断进程 Ctrl+C
SIGQUIT 3 /* Quit (POSIX). */ 建立CORE文件终止进程,并且生成core文件 Ctrl+\
SIGILL 4 /* Illegal instruction (ANSI). */ 建立CORE文件,非法指令
SIGTRAP 5 /* Trace trap (POSIX). */ 建立CORE文件,跟踪自陷
SIGABRT 6 /* Abort (ANSI). */
SIGIOT 6 /* IOT trap (4.2 BSD). */ 建立CORE文件,执行I/O自陷
SIGBUS 7 /* BUS error (4.2 BSD). */ 建立CORE文件,总线错误
SIGFPE 8 /* Floating-point exception (ANSI). */ 建立CORE文件,浮点异常
SIGKILL 9 /* Kill, unblockable (POSIX). */ 终止进程 杀死进程
SIGUSR1 10 /* User-defined signal 1 (POSIX). */ 终止进程 用户定义信号1
SIGSEGV 11 /* Segmentation violation (ANSI). */ 建立CORE文件,段非法错误
SIGUSR2 12 /* User-defined signal 2 (POSIX). */ 终止进程 用户定义信号2
SIGPIPE 13 /* Broken pipe (POSIX). */ 终止进程 向一个没有读进程的管道写数据
SIGALARM 14 /* Alarm clock (POSIX). */ 终止进程 计时器到时
SIGTERM 15 /* Termination (ANSI). */ 终止进程 软件终止信号
SIGSTKFLT 16 /* Stack fault. */
SIGCLD SIGCHLD /* Same as SIGCHLD (System V). */
SIGCHLD 17 /* Child status has changed (POSIX). */ 忽略信号 当子进程停止或退出时通知父进程
SIGCONT 18 /* Continue (POSIX). */ 忽略信号 继续执行一个停止的进程
SIGSTOP 19 /* Stop, unblockable (POSIX). */ 停止进程 非终端来的停止信号
SIGTSTP 20 /* Keyboard stop (POSIX). */ 停止进程 终端来的停止信号 Ctrl+Z
SIGTTIN 21 /* Background read from tty (POSIX). */ 停止进程 后台进程读终端
SIGTTOU 22 /* Background write to tty (POSIX). */ 停止进程 后台进程写终端
SIGURG 23 /* Urgent condition on socket (4.2 BSD). */ 忽略信号 I/O紧急信号
SIGXCPU 24 /* CPU limit exceeded (4.2 BSD). */ 终止进程 CPU时限超时
SIGXFSZ 25 /* File size limit exceeded (4.2 BSD). */ 终止进程 文件长度过长
SIGVTALRM 26 /* Virtual alarm clock (4.2 BSD). */ 终止进程 虚拟计时器到时
SIGPROF 27 /* Profiling alarm clock (4.2 BSD). */ 终止进程 统计分布图用计时器到时
SIGWINCH 28 /* Window size change (4.3 BSD, Sun). */ 忽略信号 窗口大小发生变化
SIGPOLL SIGIO /* Pollable event occurred (System V). */
SIGIO 29 /* I/O now possible (4.2 BSD). */ 忽略信号 描述符上可以进行I/O
SIGPWR 30 /* Power failure restart (System V). */
SIGSYS 31 /* Bad system call. */
SIGUNUSED 31

核心转储

程序崩溃时生成的文件,这个文件包含了程序崩溃时内存镜像和寄存器状态。

GDB可以使用核心转储文件来后续调试分析。

开启核心转储

1
ulimit -c 【字节大小】# 关闭设置为0,unlimited为无限制

这个命令只会在当前终端有效,退出终端或者打开一个新的终端时是无效的。因此可以在将上述配置加入到 /etc/profile 中可以持续生效

分析核心转储文件

运行程序出错后会在程序中生成名为core的文件

在调试gdb时加参数

1
--core 【文件名】

即可分析核心存储文件,注意,分析核心转储时一定要运行原先程序文件

调试

调试过程中使用的命令

启动类

1
run 【参数1】【参数2】【参数3】# 运行当前程序,若不设置参数则使用set args设置的参数
1
start 【参数1】【参数2】【参数3】#运行当前程序,在main处终止
1
starti 【参数1】【参数2】【参数3】 #运行当前程序,在_start处终止

运行类

步入

1
step {count} 或 s {count} #执行一行源码,会进入函数中,count为步数,默认为一

这里的步数不能简单的理解为步长,因为它遇见函数不是越过而是进入

1
stepi {cout} 或 si {count} #执行一行汇编,会进入函数中,count为步数,默认为一

步过

1
next {count} 或 n {count} #执行一行源码,不会会进入函数中,count为步数,默认为一
1
nexti {cout} 或 ni {count} #执行一行汇编,不会进入函数中,count为步数,默认为一

多次执行

1
until 或 u #快速运行完当前的循环体,并运行至循环体外停止。
1
finish #执行结束当前函数

断点命中后继续执行

1
continue 或 c 

观测类

查看设置

1
procinfo #查看当前进程信息,以及设置设置
1
xinfo {地址} #查看目标地址信息
1
show directories #显示当前源码搜索目录
1
2
list 或 l #列处当前pc处源码,默认前5行
list *{address} 查看地址处源码

数据

1
vmmap 查看段权限
1
stack {count} #查看栈信息,默认长度0x40
1
p *(指针类型) {address} #查看设置地址以规定类型的值,默认类型(void)
1
p  {address} #打印当前地址及类型
1
【类型】/count {address} #从address处以该类型查看count个数据

Format letters are o(octal), x(hex), d(decimal), u(unsigned decimal), t(binary), f(float), a(address), i(instruction), c(char), s(string) and z(hex, zero padded on the left)

Size letters are b(byte), h(halfword), w(word), g(giant, 8 bytes)

设置类

1
file {filepath} #加载可执行文件,会从当前路径和
1
directory{dirname} 或 dir #添加dirname为源码搜索目录,没参数复位源码搜索路径
1
core-file {filepath} #加载core文件用于调试
1
exec-file {object} #制定与调试可执行文件
1
add-symbol-file{filepath} -s {section-name} {address}添加调试文件并指定段名和加载地址,用于多核

多线程

查看可切换调试的线程:info threads

(2)切换调试的线程:thread 线程id

(3)只运行当前线程:set scheduler-locking on

(4)运行全部的线程:set scheduler-locking off

(5)指定某线程执行某gdb命令:thread apply 线程id gdb_cmd

(6)全部的线程执行某gdb命令:thread apply all gdb_cmd

多进程和fork

方法1:attach pid

如果fork后的命令存在暂停或其他等待,可以考虑使用attach pid 来再次attach

方法2:detach-on-fork

我们可以通过命令

1
show detach-on-fork

来查看现在的模式,如果返回值为off则fork后新的子进程不会执行,而是等待,否则继续执行,当然我们也可以通过以下命令来更改设置

1
set detach-on-fork off/on

设置完后,经过fork容然默认进入子进程,但此时的父进程是attach的状态,我们可以通过命令来查看我们现在存在的进程的进程

1
info inferiors

然后通过inferior命令进行切换

1
inferior <id>

这样后即可调试父进程

方法3:

如果我们只想针对父fork进行调试的情况下,我们可以在fork前使用

1
set follow-fork-mode [parent|child|ask]

来进行设置通过设立来确定我们fork后将attach哪一个进程

gdb-multiarch调试不同架构

当gdb去调试qemu或者其他不同架构设备时,直接调试是无法成功的,需要使用gdb-multiarch(需要下载)来进行调试

使用方法

1
gdb-multiarch -q 文件

设置架构

1
set architecture arm # 设置架构

连接目标机器

1
target remote host:port

引用参考

  1. roderick01师傅的【快来pwn1pwn】视频
  2. Linux下signal信号汇总 - 撒欢 - 博客园 (cnblogs.com)
  3. Linux系统调试篇——核心转储(core dump) - 知乎 (zhihu.com)