基於《Debug Hacks》實戰經驗,總結 x86 32 位 Linux 下段錯誤調試核心知識體系:棧幀結構、core 文件生成、GDB 核心命令,並深度剖析**棧溢出、返回地址被修改、非法內存訪問、雙重釋放、死鎖、死循環**六大典型故障的定位思路與實戰操作。
調用者棧帧 (Caller Frame)
+------------------+
| 參數 N | ← 高地址
| ... |
| 參數 2 |
| 參數 1 |
+------------------+
| 返回地址 (RA) | ← CALL 指令壓入
+------------------+
| 舊 EBP (FP) | ← 被調用者 prologue 壓入
+------------------+ ← EBP 指向這裡 (幀基址)
| 局部變量 1 |
| ... | ← 低地址方向增長
| 局部變量 N |
+------------------+ ← ESP 指向這裡 (棧頂)
被調用者棧帧 (Callee Frame)
關鍵:參數**從右向左**壓棧(`func(a,b)` 先壓 `b` 再壓 `a`),返回地址在參數上方,EBP 鏈接調用鏈。
ulimit -c unlimited # 允許生成無限大 core 文件
# 需寫權限、磁盤空間足夠、非 setuid 程序
# 生成路徑:/proc/sys/kernel/core_pattern 控制
| 命令 | 作用 |
|---|---|
| `bt` / `where` | 顯示調用棧回溯 |
| `info registers` | 查看寄存器 (EIP, ESP, EBP, EAX...) |
| `x/20x $esp` | 以十六進制查看棧內存 (20字) |
| `info files` / `info proc mappings` | 查看進程內存映射段 |
| `break *0xaddr` | 地址斷點 |
| `watch *0xaddr` | 內存監視點 (寫/讀/訪問) |
| `set $var = value` | 修改寄存器/變量值 |
| `commands N` | 斷點腳本自動化 |
現象:遞歸無出口/大局部數組 → `Segmentation fault`。
定位:Core 文件中 `$esp` 超出棧段範圍(`info proc mappings` 顯示棧段如 `0xbf45a000-0xbfe5a000`,`$esp=0xbf45a000` 已達底部)。
對策:遞歸改迭代、大數組放堆、編譯器 `-fstack-protector`、信號處理函數用 `sigaltstack` 預留備用棧。
現象:`bt` 顯示 `??` 或無關函數地址,棧幀鏈斷裂。
定位:`x/s $esp` 將棧頂作字符串打印,若輸出可識別字符串(如 `"book cat dog..."`),即定位到 `strcpy` 等未檢查長度的複製操作。
實戰:`char buf[5]; strcpy(buf, "book cat dog...")` → 溢出覆蓋 RA。
防護:用 `strncpy`/`snprintf`、編譯器 `-fstack-protector-strong`、地址隨機化 (ASLR)、NX 位(棧不可執行)。
現象:跳轉到異常地址(如 `0x08`),`bt` 顯示跳轉指令前一條 `call *%eax`,`%eax` 值被篡改。
定位:在被篡改指針變量地址設**寫監視點** `watch *0xaddr`,運行至觸發,`bt` 定位修改指針的代碼行。
實戰:`int *p = malloc(4); free(p); *p = 1;` → `watch *p` 捕獲釋放後寫入。
現象**:`free(): double free detected in tcache 2` 或運行中隨機崩潰。
定位**:`MALLOC_CHECK_=1 ./prog` 觸發 glibc 內置檢測輸出回溯;或在 `free` 設斷點腳本記錄調用棧,離線分析同地址被釋放兩次。
修復**:釋放後指針置 `NULL`、所有權轉移語義(unique_ptr)、靜態分析工具。
現象**:程序卡住,`ps -T` 顯示線程狀態 `S` (睡眠)。
定位**:`gdb attach PID` → `info threads` → `thread apply all bt` 找卡在 `pthread_mutex_lock` 的線程。
實戰**:在 `lock`/`unlock` 設斷點腳本 `commands` 打印棧,發現同一線程重複 `lock`(遞歸調用同一鎖函數)。
防護**:鎖層級順序、嘗試鎖 `pthread_mutex_trylock`、超時鎖、靜態分析(Helgrind/TSan)。
現象**:CPU 100%,程序不退出。
定位**:`gdb attach` → `bt` 定位循環函數 → **Debug 版單步調試** (`step`/`next`) 觀察循環變量不變化。
實戰**:消息解析循環 `len -= length; p += length;` 中 `length=0` 導致無限循環。
修復**:加長度合法性斷言 `assert(length > 0)`。
Linux C/C++ 調試核心在於**理解棧幀結構、善用 Core 文件、精通 GDB 斷點/監視點/腳本**。六大典型故障各有定位「指紋」:`$esp` 越界→棧溢出、`bt` 亂碼→RA被改、監視點捕獲→野指針/雙重釋放、全線程 `bt` 找鎖→死鎖、單步觀察變量不變→死循環。建立「現象→指紋→工具→定位」的肌肉記憶,是從「會用 GDB」到「調試高手」的關鍵躍遷。