%%
# 纲要
> 主干纲要、Hint/线索/路标
# Q&A
#### 已明确
#### 待明确
> 当下仍存有的疑惑
**❓<font color="#c0504d"> 有什么问题?</font>**
# Buffer
## 闪念
> sudden idea
## 候选资料
> Read it later
%%
# 二进制文件的调试信息
编译 C/C++ 程序时需要带有 **`-g` 选项**(`debug` 模式):**将==调试信息==加入到可执行文件中**,
从而使用 gdb 调试时才能**看见程序的函数名、局部变量名**等,否则显示的将**全是运行时的==内存地址==**。
> [!NOTE] 用 `gdb` 打开二进制文件后,可判断该可执行文件中是否包含 **==调试信息==(debugging symbols)**
>
> (1)无调试信息
>
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-7442928D06F39C0120CCC7265920DCBE.png|547]]
>
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-DE8CEAF997631EE439E19C825F06621F.png|471]]
>
> (2)有调试信息 (编译时带有`-g`选项)
>
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-D8708F1C2C5F237DCD8CB8931519D241.png|543]]
>
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-4340518DDD85BCF7426C16DA0B67AEE1.png|460]]
>
>
> [!caution] 对于未包含调试信息的==外部库函数==, gdb 无法进入这些函数调用内部!
<br><br><br>
# GDB 调试器
**gdb**,全称为**GNU Debugger**,Linux/Unix 的一个 **==命令行模式==的调试器**,主要用于**调试 C/C++ 程序**。(也可用于 Go、Java、Objective-C 等)
gdb 的主要功能:
- **启动程序,暂停在指定位置**:
- 可以指定程序在某个特定的起点或特定的条件下暂停执行。
- **设置断点**:
- 可以在特定的代码行、函数或条件满足时暂停程序执行。
- **单步执行**:
- 可以逐行或逐指令地执行代码,便于精确定位问题。
- **查看程序状态**:
- 可以查看变量、寄存器、内存等程序内部状态,帮助分析程序行为。
- **修改程序状态**:
- 可以在调试时修改变量的值、改变程序的执行路径等,测试不同的代码路径。
- **调试核心转储文件**:
- 可以加载和分析程序崩溃时生成的核心转储文件,了解程序崩溃前的状态。
<br>
# gdb 运行选项参数
- `gdb [OPTIONS] prog`: 启动 gdb 并加载程序 `[prog]`
- `-s file`:读取 file 的**符号表**
- `--args prog [arglist]`:**以参数 `[arglist]` 运行程序**(此项必须放在最后,由此**最后的参数列表都是传递给程序而不是 gdb**)
- `-c file` | `--core=file`: **读取 core dump 文件进行分析**
- `-p <pid>`:附加到 **正在==运行中==的进程** 进行调试
> [!NOTE] `sudo gpt -p <pid>` 等价于进入 gdb 后执行内部命令 `attach <pid>`。
>
> 背后实现是系统调用 `ptrace(PTRACE_ATTACH, pid, NULL, NULL)`,
> 会**向目标进程发送 ==`SIGSTOP` 信号==**,**暂停目标进程**,使其变为`TASK_TRACED` 状态。
>
> gd 通过 `waitpid()` 来**等待目标进程暂停**。
<br>
# gdb 内置命令
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-1158774FD44DF5FE41C06D8BBA853728.png|656]]
- `quit`:退出 gdb
### 设置程序运行参数
- `set agrs <arg>...`: 设置可执行程序的运行参数,**设置完成后再通过 `run` 启动可执行程序**。
```shell
(gdb) set args arg1 arg2 arg3
(gdb) run
```
### 代码查看
- **查看源代码**:`list | l [LINE]>` ,列出**当前执行点 or 指定行 `LINE` 附近的 10 行代码**
- `l`(无参数):向后翻页—**列出 "上一次所列代码" ==之后==的代码**(默认 10 行)
- `l -`: 向前翻页—**列出 "上一次所列代码" ==之前==的代码**(默认 10 行)
- `[LINE]`:目标行可按以下形式给出:
- `LINENUM`:**当前文件中的行号**
- `FUNCTION`:**当前文件中的函数**
- `FILE:LINENUM`:**指定文件中的行号**
- `FILE:FUNCTION`:**指定文件中的函数**
- `*ADDRESS`:**包含该地址指令的代码行**
> [!tip] 使用一次 `f` 命令后,再使用 `l` 会重新定位回到 "当前暂停位置所对应的代码行"
### 反汇编代码
- **反汇编代码**:`disas [地址|函数名]`
- `disas`(无参数):**反汇编当前函数**
- `disas [函数名]`:反汇编**指定函数**
- `disas [地址]`:反汇编**位于指定地址附近的函数**
- `disas [起始地址, 终止地址]`: 反汇编**指定地址范围内的代码**
### 执行相关
- **运行程序/终止运行** :`run|r`,`kill`
- **单步调试**(逐 **==语句==**,会进入函数调用内部):`step | s [N]`
- 参数`[N]`: **执行 N 步**,默认**单步**
- **单步调试**(逐 **==过程==**,不进入函数内部):`next | n [N]`
- 参数`[N]`: **执行 N 步**,默认**单步**
- **单步调试**(逐 **==汇编指令==**,会进入函数调用内部):`stepi | si [N]`:
- 参数`[N]`: **执行 N 步**,默认**单步**
- **单步调试**(逐 **==汇编指令==**,但不进入函数内部):`nexti | ni [N]`
- 参数`[N]`: **执行 N 步**,默认**单步**
- **继续运行,直至==遇见断点==**: `continue | c [N]`:
- 参数`[N]`:表示将**跳过 `N-1` 个断点**,直至遇见第 N 个断点时才暂定
- **继续运行,直至==当前函数返回==**:`finish | fin`
### 当前代码暂停位置
> [!tip] `f` 命令:查看当前所位于的代码位置——语句粒度
>
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-9F2FBCF36ED6DC9FBE2B80EAE83A51AF.png|506]]
>
>
> [!NOTE] `x/i $pc`:查看**当前 "待执行" 的下一条指令**(即程序计数器 `pc` 中的指令)——机器指令粒度
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-80299C709AFC7DEDDBE18E2C11D92046.png|505]]
### 断点相关
- **查看所有断点**:`info breakpoints`,简写为 `i b`
- **设置断点**: `break | br | b [位置]`
- `b [行号|函数名]`
- `b *<地址>`:在指定地址处设置断点
- `b 文件名:[行号|函数名]:标签`,适用于有多个文件的情况
- **设置观察点**:`watch [变量名]`
- 当**指定变量的值发生变化时触发断点**
- **删除断点/观察点**: `delete | del | d [断点序号...]`
- `d` :删除所有断点
- `d watchpoints` :删除所有观察点
- `d <n>`: 删除断点 `n`(按断点编号),
- **启用断点**:`<enable|en> b [n]` 启用全部 or 编号为 `n` 的断点
- **禁用断点**:`<disable|dis> b [n]` 禁用全部 or 编号为 `n` 的断点
> [!example]
>
> ```shell
> # 设置断点
> b main # 在main函数设置端点
> b *main+4 # 在main函数之后的第四个字节地址处设置断点
> b *0x400540 # 在地址0x400540处设置断点
> b factorial.c:fact:the_top # 在"factorial.c"文件中的"fact"函数中的"the_top"标签处设置断点
>
> # 删除断点
> d # 删除所有断点
> d 1 # 删除序号为1的断点
> d 1 2 4 # 删除序号为1,2,4的三个断点
>
> # 禁用/启用断点
> dis b 2 # 禁用序号为2的断点
> en b 2 # 禁用序号为1的断点
> ```
>
### 调用栈相关
- 查看**函数调用栈**:`backtrace | bt`
- `bt full` :显示**各调用栈下的完整局部变量信息**
- **切换到特定栈帧**:`frame|f <栈帧号>`
- **向上移动一个栈帧**:`up`
- **向下移动一个栈帧**:`down`
- 查看当前 **函数栈帧** 信息:
- `info frame`,简写为 **`i f`**,同 `frame | f` 命令
- `info args`:查看当前栈帧内的**所有参数**
- `info locals`:查看当前栈帧内的**所有局部变量**
> [!note]
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-C4588157081F047DCC881080E8006F82.png|780]]
### 变量相关
- **查看变量值**:`print|p <变量名>`
- **查看所有==全局变量==值**:`info variables`
- **查看所有==局部变量==值**:`info locals`
- **设置变量值**:`set var <变量名> = <值>`(在调试过程中修改变量值)
- 设置指针所指内存处值:`set *ptr = 30`;
- **自动打印变量值**:`display <变量名>` (设置后,每次程序暂停时都会**打印变量的值**,可重复该命令设置多个变量的值)
- **移除自动打印**: `undisplay <Number>`
- **查看当前 `display` 列表**:`info display`
### 类型相关
- 查看变量或类类型定义:`ptype <变量名/类型名>`
> [!example]
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-F19DFE8B08C7CDEF4A5D78BDA6B16215.png|723]]
### 查看数据值
- **查看值**:`print|p [/t|/x|/d] <寄存器名 | 变量名 | 立即数 | 指定地址>`
- `/t`:以二进制输出
- `/x`:以十六进制输出
- `/d`:以十进制输出
> [!example]
>
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-CBAB545A9D03FBD6CB644D31D43C30DF.png|539]]
>
### 查看内存内容
`x/[NFS] <地址 | 函数名 | 指针名>` 查看**从==指定内存地址起始==的==指定字节数==的内容**:
- `N`:所要查看/显示的 **==单元==数量**(默认为 1)
- `F`:指定 **==单元==的显示格式**(默认)
- `o` :八进制; `x`:十六进制;`z`:十六进制且补前导 0; `d`:十进制;`b`:二进制
- `f`:浮点数;`a`:地址; `i`:指令;`c`:字符;`s`:字符串
- `S`:指定**单元的==单位==**(以多少字节为一个 **==显示单元==**)
- `b`:字节;
- `h`:半字(2 字节);
- `w`:**==字==**(**==4 字节==**——注意,这里 gdb 的该项参数实际上是 **DWORD 双字 32 位 4 字节**,而不是 16 位 2 字节);
- `g` :**8 字节**(giant word)
> [!example]
>
> - `x/14xb func` 表示查看**函数 `func` 地址起始的 14 个字节内容**,以 16 进制格式显示。
>
> >
>
> - `x/s 0x402378`: 查看**内存地址** `0x402378` 所存的 **"==字符串=="内容**(内存内容按 "字符串" 解释)
>
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-A782D876CC5C170D03EDCA1430E36E52.png|512]]
>
>
> - `x/i 0x402470`:查看**内存地址** `0x402470` 处的所存的 "**==指令==**"(内存内容按 "指令" 解释)
>
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-41533553C78538708D4EC1FF8670AE90.png|324]]
>
> - `x/i $pc` 或 `x/i $rip`:查看**程序计数器 PC**(也即指令**指针寄存器 `%rip`**)中所存的 "**==下一条待执行指令==**"
>
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-26EAE315E446E29F7A63054CE118F197.png|420]]
>
>
> - `x/a 0x402470`:查看**内存地址** `0x402470` 处所存的 "**==地址==**"(内存内容按 "地址" 解释)
>
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-5A7D59DE68B4E7435B74831323FC83BB.png|387]]
>
>
> - `x/w $rbx`:以 4 字节为一个单元,查看**寄存器 `%rax`中 ==所存地址==** 处的 **==内存==内容**
>
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-F4555F54370357C260E9AA07BE2DD396.png|346]]
>
> - `x/6w $rsp`:查看当前**栈指针`%rsp`** 处起始的 **6 个 4 字节数据值**(`%rsp` ~ `%rsp+0x14`)
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-245B2894DF44362F9BA5CD5C599D341B.png|372]]
>
> - `x/10s ptr - 10:`: 查找指针 `ptr` 所指地址负偏移 100 字节后的 10 个字符串内容
>
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-E9BDC02AC27BEAA22A4D0D5E3A926690.png|665]]
>
### 寄存器相关
- **查看所有寄存器的值**:`info registers`,简写为 **`i r`**
- **查看指定寄存器值**:
- `print|p $rax` ,查看寄存器 `%rax` 值(**寄存器名不带`%`**)
- `i f edi`:查询寄存器 `%edi` 的值
- **设置寄存器值**:
- `set $pc = 0x401242`
> [!NOTE] 通过修改程序计数器 `%pc`(即指令指针寄存器`%rip`)跳转执行指定指令
>
> 命令:`set $pc = 0x401242` ,等价于 `set $rip = 0x401242`
>
> 执行后,pc 中下一条指令地址为 `0x401242`,因此**调用 `si` 后将跳转到执行 `0x401242`地址处的指令**。
> [!example] 查看寄存器值
>
> 查看所有寄存器值:
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-5D6049B4F3EB0E65C3B22561A7206EAF.png|376]]
>
> 查看指定寄存器的值:
>
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-E90641F532CD7A61DA8D885A82A34536.png|368]]
>
>
>
### 线程相关
- `thread`: **查看当前线程**
- `info threads`:**查看所有线程信息**
- `thread [n]`:**切换到某个线程**
- `thread aplly [all|n] [command]`:对**所有线程**或**指定线程**执行 gdb 命令
- `interrupt`:**中断正在运行的所有线程**(`continue` 恢复)
- `break [] thread <n>`: 为指定线程设置断点
> [!example] 对所有线程或指定线程应用 gdb 命令
>
> ```shell
> (gdb) thread apply all bt # 查看所有线程的调用栈
> (gdb) thread apply 2 bt # 查看线程2的调用栈
> (gbd) thread apply all next # 所有线程执行一步
> ```
> [!example]
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-E2D9F4ED660F3FE972008B78B962FC95.png|952]]
### 调试运行中的进程
- `attach <pid>`:附加到指定进程,进行调试
- `detach`: 退出 gdb(不终止进程)
- `quit`:退出 gbb 并终止进程
- `ctrl + c`:**暂停进程**(通过 `ptrace(PTRACE_INTERRUPT, pid, 0, 0)` 使进程进入 `Stopped` 暂停状态)
> [!note] 附加到进程进行调试 :
>
> - 方式一:启动时附加 `sudo gdb -p <pid>`
> - 方式二:sudo 启动 gdb 后执行内部命令 `attach <pid>`,即:`(gdb) attach <pid>`
>
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-0F7EB25C08737842D9C886B315C420C9.png|693]]
>
>
> 需要 root 权限运行,否则报错如下:
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-B3D6C2B4473D5A18EA07445EB7DD15D6.png|560]]
>
>
> 同时,**每个进程只能同时被一个 `ptrace`跟踪**,例如 `vscode` debug 跟踪后就无法再用 gdb 进行 attach,会报错如下:
>
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-1DDF2553918213647BE642A89AC497A5.png|536]]
>
## 自定义命令
- `define <命令名>`,随后每行输入一个命令,输入 `end` 结束。
![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-EA9D5A7E7117D44A0F6A2D4BA7342AD6.png|413]]
### 切换到 TUI 界面
- `layout split`:切换到 TUI 界面,可**对照查看==源代码==和==机器指令==**
-
![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-E2E740AC7B4CB8E153CEB2D070E21047.png|509]]
---
![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-E8AD8C012B356452F178666E1EF48F6F.png|559]]
<br><br><br>
# 关于 Core Dump 文件
> Core dump,核心转储文件
core dump **是操作系统在程序异常终止时生成的文件**,包含 **程序在崩溃时的==内存内容==、==寄存器状态==、==调用堆栈==以及其他相关的调试信息**。
Core dump 提供了**程序崩溃时的详细状态信息**,因此**可用于调试寻找程序崩溃原因**。
## 生成 Core Dump 文件
大多数 Linux 发行版**默认关闭了核心转储功能**,需**通过`ulimit`命令启用**:
- `ulimit -c` :查看当前**核心转储功能**是否有效
- `ulimit -c unlimited`:**==启用==核心转储功能**(**仅在==当前 shell 会话==及其子进程有效**)
启用该功能后,当一个程序崩溃时**系统会自动成一个核心转储文件**,通常命名为 `core` 或 `core.<pid>`。
> [!NOTE] `-c` 选项表示**内核转储文件的大小限制**
>
> - 其值为 0 时,表示**内核转储无效**。 => 命令 `ulimit -c 0` 为关闭核心转储功能
> - 其值为 unlimited,表示不限制,故生效。
> [!NOTE] 当一个多进程或多线程程序崩溃时,每个崩溃的**==线程或子进程==**都会生成一个核心转储文件。
> [!example] `core_dump` 文件示例
>
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-6B8B08519D85FA62265E5993FF62C102.png|998]]
>
#### 永久启用 core dump 功能
> [!caution] `ulimit -c unlimited` 仅在 "当前 shell 会话及其子进程" 中有效。
要永久启用系统 core dump 功能,需执行以下命令,向 `/etc/security/limits.conf` 中输入内容[^1]:
```shell
sudo bash -c "cat << EOF > /etc/security/limits.conf
* soft core unlimited
* hard core unlimited
EOF
```
<br>
## 设置 Core Dump 文件生成路径及文件名
核心转储文件的**默认生成路径**为 "**运行程序时的 当前工作目录`pwd`"**。
可通过 `sysctl` 变量 `kernel.core_pattern` **指定核心转储文件的==生成路径和文件名==**:
- `kernel.core_pattern=core.%p.%e.%s.%t`:**在当前工作目录中生成**
- `kernel.core_pattern=/var/core/core.%p.%e.%s.%t`:**在==指定目录==下生成**(指定**绝对路径前缀**,这里为 `/var/core/`)
设置方式为**在 `/etc/sysctl.conf` 文件中指定变量 `kernel.core_pattern` 值**:
- `sudo sh -c 'echo "kernel.core_pattern=core.%p.%e.%s.%t" >> /etc/sysctl.conf'`
![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-7FE817BA24C3E4A2F86787CDE1A84F23.png|886]]
> [!Info] `kernel.core_pattern` 占位符含义
>
> 参见 [^2]
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-5C8D4D7314A4FF5043EED0654CDB6A52.png|513]]
>
<br><br>
## Core Dump 文件未生成的原因
核心转储文件通常会在 "**当前工作目录**" 下生成,如果**设置 `ulimit -c unlimited` 后仍不存在**,
可**检查 Linux 内核参数**—— `cat /proc/sys/kernel/core_pattern` 文件, 其中记录了**核心转储文件的保存路径**。
若显示路径为 `/usr/share/apport/apport`(如下图),表明 **==系统使用了 Apport 错误报告工具来处理核心转储==**,其会**拦截核心转储文件**并生成报告。
![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-A581CED204369199D609FC1F3C6D9AC3.png|826]]
> [!example] 测试核心转储是否可正常生成
>
> 若可正常生成,下述命令执行后,将在当前目录下得到核心转储文件。
>
> ```shell
> sleep 9999 &
> kill -SIGSEGV %1 # 向最近一个放入后台的作业发送 `-SIGSEGV` 段错误信号,触发进程崩溃
> ```
>
>
### 解决方式
###### 方式一:临时禁用 Apport
```shell
# 停止apport 服务
sudo service apport start
# 重新开启apport 服务
sudo service apport stop
```
###### 方式二:修改核心转储的路径
**执行下列命令设置**[^1],设置后 core dump 文件**将会在程序所在目录下生成**。
```shell title:enable_core_dump.sh
# 检查`/etc/sysctl.conf`中是否存在 `kernel.core_pattern`一行内容, 若不存在则向该文件中追加写入.
if ! grep -qi 'kernel.core_pattern' /etc/sysctl.conf; then
sudo sh -c 'echo "kernel.core_pattern=core.%p.%u.%s.%e.%t" >> /etc/sysctl.conf'
sudo sysctl -p # 保证`/etc/sysctl.conf`文件内容立即生效.
fi
ulimit -c unlimited
```
<br>
### 关于 Apport
`apport` 是 Ubuntu 及其衍生发行版中的**一个错误报告和崩溃处理工具**,其会**拦截核心转储、收集程序时崩溃信息并生成错误报告文件**。
主要功能:
1. **崩溃报告**: 当程序崩溃时,`apport` 会拦截核心转储,收集调试信息,并生成详细的崩溃报告。这些报告通常包括调用堆栈、寄存器状态、内存内容和其他有助于诊断的问题信息。
2. **自动报告**: `apport` 可以自动将崩溃报告发送到 Ubuntu 的错误跟踪系统(Launchpad)。这帮助开发者和维护者跟踪和修复系统中的错误。
3. **用户提示**: 在桌面环境中,`apport` 会显示一个对话框,通知用户程序已崩溃,并询问是否要提交错误报告。这让普通用户也能够轻松地报告系统中的问题。
4. **报告管理**: `apport` 提供了命令行工具(如 `apport-cli` 和 `apport-bug`)和一个图形界面(`apport-gtk`),用于查看和管理崩溃报告。
#### 查看崩溃报告
`apport` 生成的崩溃报告通常存储在 `/var/crash` 目录下,
可使用命令行工具 `apport-cli` 查看报告:`sudo apport-cli /var/crash/your_crash_file.crash`
#### 启用/关闭 apport 服务
- 方式一:通过以下命令**启动或停止** **`apport` 服务**:
- `sudo service apport start`
- `sudo service apport stop`
- 方式二:**编辑 `/etc/default/apport` 文件**,设置 `enabled=0` 表示禁用。
<br><br><br>
# gdb 分析核心转储文件 (Core Dump)
使用 GDB 分析核心转储文件——启动 GDB 并**加载 "可执行文件" 及其崩溃时生成的 "核心转储文件"**。
- `gdb <program> <core_dump_file>` (两个命令等价)
- `gdb -c <core_dump_file> <program>`
gdb 需要程序中的**符号表**、**调试信息**(如有)、**地址映射**,从而**解析 core 中的指令地址,确定崩溃发生的位置**。
> [!example] gdb 分析核心转储文件示例一
>
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-69CFC8ED17568C6F05C17B9C2D869D3D.png|713]]
>
> 如上图,在执行文件中第 2 行代码 `*((int*) 0) = 0` 时,**发生了段错误**。
>
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-6652315E1157CE17A16A106EF421CA68.png|721]]
>
> 如上图,发生了**除 0 导致的算术异常**。
> [!example] gdb 分析核心转储文件:示例二
>
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-C9F9A03615D4F7B7F5B349032EE4522B.png|825]]
>
>
<br><br><br>
# gdb 调试备注
> [!NOTE] 若程序**正阻塞** 或陷入**无限循环**,导致 **gbd 命令无响应**,可 `<C-c>` 发送中断信号,查看当前程序执行状态
>
> 若成功中断,则 **gdb 将显示阻塞所在行**(例如系统调用 `read()`, `write()`, `epoll_wait()` 等操作处),如下:
>
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-5FA943462F246B5806DFDDF5FDB521BF.png|730]]
>
> 若 `<C-c>` 无效,则程序可能处于不可中断状态。
> [!NOTE] gdb 中可见**函数接收的 "传入参数值"** 以及该参数的 "**当前值**"
>
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-D219EB5FE4D713DE97F5E7A58A389311.png|896]]
>
> ![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-3820525EDE9BB8620C22688468AFAF02.png|895]]
>
>
#### 调试建议
![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-4912A9FC4F196DF45019FCD56D464A1A.png|500]]
![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-3EF0A27159CF6A31B8EA30CBEEB0F2C5.png|493]]
<br><br><br>
# gdb 多线程调试
### 示例:死锁检测
源代码:
![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-5D5BDC8AE9DF7AD707B47E6C454065EE.png|813]]
运行触发死锁:
![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-E053B5B7C2C49761863CE267A17B9F4B.png|774]]
gdb 调试分析过程:
![[_attachment/05-工具/GNU 工具/gdb 调试器.assets/IMG-gdb 调试器-C22BE3D3F8CA4590E103FE66EE6DEF6C.png|916]]
<br><br><br>
# gdb 实现原理
gdb 主要基于 Linux 提供的 **==`ptrace` 系统调用==** 实现调试功能,该系统调用支持:
- **读取/修改目标进程的内存**;
- **读取/修改目标进程的寄存器**(比如程序计数器 PC、栈指针 SP 等等);
- **设置断点**(修改指令,比如在目标指令位置插入**int3 指令**)。
- **捕获目标进程的信号**(比如 SIGSEGV 段错误)进行处理。
- **单步执行**(single step)、**继续执行**(continue)等。
例如:
- `ptrace(PTRACE_ATTACH, pid, NULL, NULL)` 将 **`gdb` 附加到目标进程上**,系统调用会**向目标进程发送 `SIGSTOP(19)` 信号使==强制暂停==**。
- gdb 通过 `waitpid()` 等待目标进程被暂停;
- `ptrace(PTRACE_CONT, pid, NULL, NULL)` **向目标进程发送 `SIGCONT(18)` 信号**,**让目标进程继续执行**,
#### gdb attach 附加到运行进程的原理
1. gdb 执行 `ptrace(PTRACE_ATTACH, pid, NULL, NULL)` 系统调用,**向目标进程==发送 `SIGSTOP(19)` 信号==使之暂停**。
2. gdb **通过 `waitpid()` 等待目标进程停止**。
此后,gdb 可以继续**通过 `ptrace` 读取目标进程的寄存器、内存,设置断点**等。
#### gdb 设置断点的原理
> 本质上是 "**==修改目标指令==**",将 "**目标地址的机器码**" 替换为 "**==断点中断指令==**"(`int3`)。
1. gdb 设置断点:
1. 使用 `ptrace(PTRACE_PEEKDATA, ...)` **读取目标指令地址的==原始指令字节==**。
2. 替换该地址 "**==第一个字节==**" 为 `0xcc`(即 `int3` 指令)
3. **保存原始字节**,以便后续恢复。
2. 程序运行触发断点——程序执行到 **`int3` 指令**,**==触发 CPU 中断==**,转入内核态,跳转执行**中断处理程序**:
1. 向**触发中断的进程**发送 **==`SIGTRAP(5)`信号==**;
2. **检查目标进程 `task->struct` 中的 ==`ptrace` 状态标志位==**。
3. 内核发现进程**处在 `ptrace` 附加调试状态**,故 **==暂停==(挂起)目标进程**,通知 gdb。
4. gdb **从 `waitpid()` 返回**,检查 **==`SIGTRAP` 信号==** 得知命中断点。
3. gdb **恢复原始指令**
1. gdb 读取**指令指针 `%rip`**(即 PC),令其**回退一步**。
2. gdb 修改目标地址的 `0xCC` **还原为==原始指令==**。
3. gdb 执行 `ptrace(PTRACE_SINGLESTEP, pid, ....)` ,**执行==单步运行==**。
4. **单步运行**了原始指令后,**再次重新向==目标指令的地址==写入 `0xCC`,使断点再次生效**。
> [!example] 示例:gbd 通过 `ptrace` 读取 `%rip`,回退指令指针。
>
> ```c
> struct user_regs_struct regs;
> ptrace(PTRACE_GETREGS, pid, NULL, ®s);
> printf("RIP: 0x%llx\n", regs.rip);
> regs.rip -= 1; // 回退指令指针
> ptrace(PTRACE_SETREGS, pid, NULL, ®s);
> ```
> [!info] x86、x86-64 下的 `int3` 指令
>
> 该指令为 "**==断点中断指令==**",机器码为 `0xCC`,会触发 CPU 中断,对应中断号为 `#BP`(Breakpoint Exception),编号为 `I3`。
>
# DDD 调试器
DDD:GDB 的一个扩展,提供了图形界面
<br><br>
# 参考资料
CoreDump 调试入门:
- [GDB debugging tutorial for beginners - Linux Tutorials - Learn Linux Configuration](https://linuxconfig.org/gdb-debugging-tutorial-for-beginners)
# Footnotes
[^1]: [GDB debugging tutorial for beginners - Linux Tutorials - Learn Linux Configuration](https://linuxconfig.org/gdb-debugging-tutorial-for-beginners)
[^2]: 《Debug Hacks》