banner
Zein

Zein

x_id

gdb最佳實踐手冊

啟動 / 附加過程#

注意:在 gdb 中只需輸入完整命令的前幾個字母甚至首字母即可。前提是縮寫不和其它命令有歧義

gdb ./a.out       # 從頭調試尚未運行的程序

gdb a <pid>       # 綁定正在運行的進程
gdb -p <pid>      # 同上一條

gdb -q a
(gdb) attach <pid>      # 也可以先啟動再綁定

(gdb) detach            # 如果通過gdb attach調試,detach後,原進程將繼續執行
(gdb) q                 # 退出gdb

----------------靜默啟動選項:直接進入gdb,而非打印一堆版本版權信息
gdb -silent
gdb -q
#可在~/.bashrc中,為gdb設置一個別名:
alias gdb="gdb -q"

----------------靜默退出設置:gdb退出時會提示:是否退出調試的程序
#A debugging session is active.
#
#    Inferior 1 [process 29686    ] will be killed.

#Quit anyway? (y or n) n

#靜默退出:也可把這個命令加到.gdbinit文件裡。
(gdb) set confirm off

----------------gdb啟動時,指定被調試程序的參數
gdb -args ./a.out a b c
----------------也可在gdb中,通過命令來設置
(gdb) set args a b c
(gdb) show args
Argument list to give program being debugged when it is started is "a b c".
----------------還可在運行程序時指定
(gdb) r a b          #這似乎會被保存為參數列表,下次r時可能會帶保存的參數列表,建議(gdb) show args看看

----------------進入gdb後再構建程序
(gdb) shell
(gdb) make CFLAGS="-g -O0"

用自動獲取 pid 的腳本啟動 attach debugging#

#!/bin/bash
# 指示使用 Bash 解釋器來執行腳本
# 保存為 agdb.sh,並添加可執行權限(chmod +x xgdb.sh)
# 用法:./agdb.sh <program_name>

# 獲取程序名,即第一個參數
prog_bin=\$1

# 檢查是否傳入了程序名
if [ -z "$prog_bin" ]; then
  echo "請傳入程序的名稱或路徑!"
  exit -1
fi

# 獲取程序的 PID
running_name=$(basename $prog_bin)  # 提取程序名
pid=$(pidof $running_name)

# 檢查PID是否為空
if [ -z "$pid" ]; then
  echo "沒有找到運行中的進程:$running_name"
  exit -1
fi

# 附加到進程
echo "正在附加到進程 PID: $pid..."
gdb -q -p $pid

遠程調試#

(gdb) i target              #顯示調試目標平台的信息
Remote target               #或Local target
  Connected to remote target via <connection info>   #端口號
  Architecture: <arch info>                          #目標架構(如x86、ARM 等)

(gdb) target remote localhost:25000                  #連接gdbsever

gdb 臨時環境變量#

在 GDB 中設置只對 debugging function 生效的環境變量。通常是臨時的,僅在調試會話期間有效。

(gdb) set env PATH=$PATH:/path/to/some/library
(gdb) set env LD_PRELOAD=$LD_PRELOAD:/path/to/some/library

(gdb) show env LD_PRELOAD            #查看gdb設置的臨時環境變量
(gdb) unset env LD_PRELOAD

也可以在.gdbinit配置 gdb 啟動時運行初始化命令:

set env LD_PRELOAD=$LD_PRELOAD:/path/to/some/library

運行控制#

(gdb) r arg1 arg2 ...             # 重新開始運行二進制
(gdb) start                   # 用於開始執行程序,並在程序的第一條指令處暫停執行,比如停在main{
(gdb) stop                        # 暫停運行
(gdb) c                           # 繼續執行
(gdb) n                           # 單步執行,遇到函數則跳過
(gdb) s                           # 單步執行,遇到函數則跳入函數體

(gdb) until  line                 # 運行直到到達指定行

斷點設置#

-----------------------------------斷點設置
(gdb) b <file:line>                    # 設置文件中某行斷點;通過行號設置斷點的弊端:如果更改了源程序,那麼之前設置的斷點就可能不是你想要的
(gdb) b <file:function>                # 設置文件中某函數斷點

(gdb) b <namespace::class::function>   # 設置某類的成員函數斷點
(gdb) b (anonymous namespace)::<func>  # 設置匿名空間中的函數斷點

(gdb) b <location> <thread-id>         # 設置某個線程在某處的斷點
(gdb) b <location>  if <condition>     # 設置某處的條件斷點

(gdb) b *0x400522                      # 調試匯編或無調試信息的程序時,經常要在程序地址上打斷點

(gdb) tb a.c:15                        # tb打只生效一次的臨時斷點,斷點命中一次後,就被刪掉

-----------------------------------斷點刪除/啟用/禁用
(gdb) d <break-id>                      # 刪除某斷點
(gdb) disable   <break-id>              # 禁用某斷點
(gdb) enable    <break-id>              # 啟用某斷點

-----------------------------------斷點忽略,可用於調試循環
(gdb) ignore 1 5                        # 忽略斷點1前五次觸發
(gdb) ignore 1 0                        # 取消忽略

-----------------------------------斷點設置自動執行命令
(gdb) command <break-id>           # 修改已存在的斷點命令也是這條
>silent                            # 靜默執行命令
>if x > 10
>p <var>                           # 運行到斷點處時自動打印變量<var>
>end

(gdb) i breakpoints                # 用於查看斷點命令
(gdb) delete                       # 用於刪除斷點命令

-----------------------------------斷點環境保存
(gdb) save breakpoints my_breakpoints.txt           # 將當前的斷點保存到路徑my_breakpoints.txt

(gdb) source my_breakpoints.txt           # 在下次啟動 GDB 調試時,加載my_breakpoints.txt保存的斷點

在程序入口處打斷點#

strip a.out                #去掉調試信息
-----------------------------獲取程序入口1:用 readelf 命令查看 ELF 文件頭,找到 Entry point address:
readelf -h a.out
#Entry point address:               0x400440
-----------------------------獲取程序入口2:(gdb) info files,找到 Entry point address:
(gdb) info files
#Entry point address:               0x400440
-----------------------------獲取程序入口3:objdump -f a.out,找到 Entry point address:
objdump -f a.out
#Entry point address:               0x400440

-----------------------------在程序入口處打斷點
(gdb) b *0x400440

快速嘗試不同輸入#

在不修改源碼,不重新編譯的情況下,通過斷點命令 + 反向調試為函數嘗試不同輸入

(gdb) b drawing
Breakpoint 1 at 0x40064d: file win.c, line 6.
(gdb) command 1
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>silent                         # 避免在斷點停下時輸出信息。
>set variable n = 0             # 修改輸入
>c
>end

#看了結果後反向執行再改

觀察點設置#

寫觀察點:程序在某個變量(觀察點)的值變化時暫停。
讀觀察點:當發生讀取變量行為時,程序暫停

硬件觀察點:設置觀察點時,GDB 使用硬件級監控功能追蹤變量值變化。不影響程序性能。但硬件觀察點數量可能有上限,超限時,GDB 會使用軟件觀察點。
軟件觀察點:如硬件不支持觀察點或已達到硬件觀察點限制,GDB 用軟件方式實現。通常影響程序運行速度,因為涉及到不斷地檢查和管理內存。

(gdb) watch a                            # 設置變量a為寫觀察點
(gdb) wa a thread 2                      # 只在 thread2線程 觸發 a變量的觀察點
(gdb) rw a                               # 讀觀察點,只對硬件觀察點生效
(gdb) aw a                               # 讀寫觀察點

(gdb) delete watchpoint 2

(gdb) disable watchpoint 2               # 禁用觀察點2
(gdb) enable watchpoint 2

(gdb) set can-use-hw-watchpoints off     # 禁用硬件觀察點
(gdb) set can-use-hw-watchpoints on

-------------------------------用內存地址設置觀察點
(gdb) p &a                               # 列出a的地址
\$1 = (int *) 0x6009c8 <a>

(gdb) watch *(int*)0x6009c8              # 用內存地址的解引用設置觀察點

捕獲點設置#

catchpoint 用於捕捉特定事件發生(如信號、函數調用、系統調用等)時中斷程序執行;catchpoint 默認是持久的,會在程序每次觸發對應事件時中斷。

(gdb) tcatch fork       # 設置為只捕獲一次的臨時捕捉點

(gdb) catch fork        # 捕獲 fork() syscall;GNU/Linux支持這個功能。其他平台是否支持查官方gdb手冊
(gdb) catch vfork
(gdb) catch exec
(gdb) catch syscall                         # 捕獲所有syscall
(gdb) catch syscall [syscall | number]      # 捕獲 指定名稱或編號 的syscall

破解 anti-debugging#

有些程序不想被 gdb 調試,就在程序中調用 “ptrace” 函數,一旦返回失敗,就證明程序正在被 gdb 等類似的程序追蹤,直接退出。

破解這類程序的辦法就是為ptrace調用設置catchpoint,然後修改ptrace返回值

----------------------------eg
#include <sys/ptrace.h>
#include <stdio.h>
int main(){
    if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0 ) {
        printf("Gdb is debugging me, exit.\n");
        return 1;
    }
    printf("No debugger, continuing\n");
    return 0;
}

----------------------------(gdb) catch syscall ptrace
(gdb) catch syscall ptrace
Catchpoint 2 (syscall 'ptrace' [101])
(gdb) r                                                                                               # 重新運行
Starting program: /data2/home/nanxiao/a

Catchpoint 2 (call to syscall ptrace), 0x00007ffff7b2be9c in ptrace () from /lib64/libc.so.6
(gdb) c
Continuing.

Catchpoint 2 (returned from syscall ptrace), 0x00007ffff7b2be9c in ptrace () from /lib64/libc.so.6
(gdb) set $rax = 0                                                                                    #Linux64 syscall返回值存在rax
(gdb) c
Continuing.
No debugger, continuing
[Inferior 1 (process 11491) exited normally]

修改 machine states#

(gdb) set $var=XXX                # 設置gdb變量
----------------修改變量
(gdb) set var <varname> = <val>   # 修改變量

(gdb) set file::array="Jil"             # 修改字符串數組值,字符串常量在只讀數據段,不可修改
(gdb) p &array                          # 修改內存的方式修改變量,但內存越界很危險
(gdb) set {char[4]} 0x80477a4 = "Jil"   # set {type}address=expr

----------------修改register
(gdb) set var $eax = 8          # 修改返回值寄存器eax
(gdb) set var $pc=0x08050949    # 只要你知道某條指令的地址,就能通過修改PC指令下一步跳轉過去

----------------修改

----------------修改

----------------修改

跳轉執行#

調試常規方法是設置斷點並重新運行程序,跳過不影響結果的中間 machine states 修改過程,以及非關鍵過程到某位置執行,可節省時間;

gdb 跳轉指令的原理是修改程序計數器 PC,使程序跳到指定行繼續執行。jump 跳到指定執行點不暫停,若想 gdb 在該執行點暫停,需手動設置斷點。

(gdb) tb <file>:<num>       # tb打只生效一次的臨時斷點,斷點命中一次後,就被刪掉

(gdb) j <num>               # 跳轉到指定行號
(gdb) j <func>
(gdb) j <file>:<num>
(gdb) j <file>:<func>
(gdb) j *addr               # 跳轉到某地址指向的指令

(gdb) j +<num> # 向後跳轉n行
(gdb) j -<num> # 向前跳轉n行

時間旅行調試#

記錄每條 gdb 指令引起的 machine states 變化 (如變量、寄存器、內存的數據變化等),默認存入 gdb_record.process_id。當需回溯至過去狀態時,調試器會按相反順序逐條指令恢復這些狀態

使用場景:
1)很難復現的 BUG,邏輯非常複雜的問題
2)多線程並發問題
3)內存相關的問題,如內存踩踏,內存泄漏、重複釋放等
4)調用棧顯示全是問號的問題

(gdb) record save filename                      # 把程序執行歷史狀態信息保存到文件,默認名字是gdb_record.process_id
(gdb) record restore filename                   # 從歷史記錄文件中恢復狀態信息
(gdb) show record full insn-number-max          # 查看可記錄執行狀態信息的最大指令個數,默認200000
(gdb) set record full insn-number-max n         # 設置可記錄最大指令數n
(gdb) set record full insn-number-max unlimited # 設置可記錄最大指令數無限制

(gdb) set exec-direction reverse               # 設置程序逆向執行,執行此命令後,常用命令如next, nexti, step, stepi, continue、finish等都變成逆向執行
(gdb) set exec-direction forward               # 設置執行方向為正向

(gdb) b n
(gdb) record                                   # 記錄程序執行過程中所有狀態信息
(gdb) c

(gdb) reverse-search                           # 反向搜索曾經輸入過的命令
(gdb) record goto start                        # 跳轉到記錄開始處
(gdb) record goto end                          # 跳轉到記錄結束處
(gdb) record goto n                            # 跳轉到記錄中的指定位置 n

(gdb) reverse-next                             # 逆向執行一行代碼,遇函數調用不進入
(gdb) reverse-nexti                            # rni:逆向執行一條指令,遇到函數調用不進入
(gdb) reverse-step                             # rs:逆向執行一行代碼,遇函數調用則進入
(gdb) reverse-stepi                            # rsi:反向單步執行(機器碼級別)
(gdb) reverse-continue                         # rc:逆向繼續執行
(gdb) reverse-finish                           # 逆向執行,一直到函數入口處

(gdb) record stop                              # 停止記錄狀態信息

切換 shell 環境執行#

(gdb) shell                       # 進入shell模式,回到linux終端
(gdb) !<shell_cmd>                # 相當於(gdb) shell <shell_cmd>;
(gdb) pwd                         # 在gdb中切換工作目錄
(gdb) cd tmp
(gdb) exit                        # 退出shell模式,回到gdb命令行

print#

輸出設置#

指定 gdb 輸入輸出設備#

缺省情況下程序的輸入輸出和 gdb 使用同一個終端

tty                             #查看本終端

gdb -tty /dev/pts/2 ./a.out     #設置程序的輸入輸出

(gdb) tty /dev/pts/2            #設置程序的輸入輸出

打印字符串長度設置#

gdb 會限制打印字符串的最大長度。當 gdb 輸出信息較多時,gdb 會暫停輸出,並會打印 “---Type <return> to continue, or q <return> to quit---”;使用下列命令可修改限制:gdb 就會全部輸出,中間不會暫停

(gdb) show print elements                          # 顯示字符串最大打印長度

(gdb) set print elements <number-of-elements>      # 設置字符串最大打印長度

(gdb) set print elements 0                         # 取消字符串最大打印長度
(gdb) set print elements unlimited                 # 同上

(gdb) set pagination off   # 取消分頁輸出
(gdb) set height 0         # 取消輸出信息最大高度限制

machine state#

---------------------------------------打印variables/打印stack
(gdb) whatis <var>                # 打印變量類型
(gdb) ptype <var>                 # 打印變量類型定義
(gdb) i variables                 # 打印當前作用域所有變量所在文件及類型
(gdb) i variables ^<var>$         # ^ 表示以<var>開頭;$ 表示以<var>結尾

(gdb) p <var>                     # 打印變量<var>
(gdb) i locals                    # 列出當前所處函數棧幀中所有局部變量(包括靜態局部變量)
(gdb) i variables                 # 列出所有的全局和靜態變量
(gdb) i variables var             # 列出以正則var開頭的全局和靜態變量
(gdb) p 'file.c'::<var>           # 屬於不同文件的extern全局/靜態變量可能有相同變量名,此時要指定所屬文件

(gdb) p sizeof(wchar_t)           # print wchar_t類型字節大小
(gdb) x/s str                     # 打印ASCII字符串或字符數組變量str
(gdb) x/hs str                    # 打印2字節字符串或字符數組變量str
(gdb) x/ws str                    # 打印4字節字符串或字符數組變量str

(gdb) p array[index]@num          # 從索引index開始,打印num個連續元素
(gdb) set print array-indexes on  # 打印數組時,缺省不打印索引下標;顯示索引下標

(gdb) display <var>                                       #跟蹤變量或表達式,每次程序暫停時自動顯示指定<var>的值;也就是遇到斷點、單步執行、或手動暫停時
(gdb) undisplay <var>                                     #取消跟蹤變量

---------------------------------------打印protobuf message
(gdb) p <var>.DebugString()       # 用DebugString()將proto對象內部結構打印出來;

---------------------------------------打印CPUregister
(gdb) i r                         # 打印所有CPU寄存器的值,不包括浮點寄存器和向量寄存器
(gdb) i all-registers             # 打印所有CPU寄存器
(gdb) i r es                      # 打印CPU寄存器es的值
(gdb) p $es                       # 打印CPU寄存器es的值

(gdb) layout regs                 # tui模式下,顯示寄存器窗口
(gdb) tui reg float               # 查看浮點寄存器
(gdb) tui reg general             # 查看通用寄存器

---------------------------------------打印memmory
# n:正整數,你想查看的內存單元的數量
#
# f:打印格式:
# - x: 十六進制
# - d: 十進制
# - u: 十六進制
# - o: 八進制
# - t: 二進制
# - a: 十六進制
# - c: 字符格式
# - f: 浮點數
#
# u: 單個內存單元大小:
# - b: 單字節
# - h: 雙字節
# - w: 四字節
# - g: 八字節
#
# addr: 要打印的內存地址
(gdb) x/<nfu>  <addr>   # 打印內存地址,<addr>可以是某個變量的指針值;常用x/nxb x/nub x/ntb

和_和和__用於內存調試#

(gdb) x/16xb a                             # 打印內存
0x7fffffffe4a0: 0x00    0x01    0x02    0x03    0x04    0x05    0x06    0x07
0x7fffffffe4a8: 0x08    0x09    0x0a    0x0b    0x0c    0x0d    0x0e    0x0f

(gdb) p $_                                # $_存儲最後一次用x命令檢查的內存地址
\$1 = (int8_t *) 0x7fffffffe4af

(gdb) p $__                               # $__存儲x命令最後檢查的內存數據的值(通常是最後一個字節)
\$2 = 15

--------------------------------------一些 GDB 命令(如 i breakpoint)可能修改 $_ 值。
(gdb) i breakpoint                         # 這個命令生成一個匿名變量存儲斷點,地址0x00000000004004a0,值1
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00000000004004a0 in main at a.c:13
        breakpoint already hit 1 time
(gdb) p $_
\$6 = (void *) 0x4004a0 <main+44>

打印進程的虛擬內存#

(gdb) i proc mappings                               #顯示當前進程的虛擬內存,包括各段的地址範圍、大小、偏移量及訪問權限等
process 27676 flags:
PR_STOPPED Process (LWP) is stopped
PR_ISTOP Stopped on an event of interest
PR_RLC Run-on-last-close is in effect
PR_MSACCT Microstate accounting enabled
PR_PCOMPAT Micro-state accounting inherited on fork
PR_FAULTED : Incurred a traced hardware fault FLTBPT: Breakpoint trap

Mapped address spaces:
# Start Addr: 段起始地址;End Addr: 段結束地址;Size: 段大小;Offset: 偏移量;Flags: 權限(r 可讀,w 可寫,x 可執行)
    Start Addr   End Addr       Size     Offset   Flags
     0x8046000  0x8047fff     0x2000 0xfffff000 -s--rwx
     0x8050000  0x8050fff     0x1000          0 ----r-x
     0x8060000  0x8060fff     0x1000          0 ----rwx
    0xfee40000 0xfef4efff   0x10f000          0 ----r-x
    0xfef50000 0xfef55fff     0x6000          0 ----rwx
    0xfef5f000 0xfef66fff     0x8000   0x10f000 ----rwx
    0xfef67000 0xfef68fff     0x2000          0 ----rwx
    0xfef70000 0xfef70fff     0x1000          0 ----rwx
    0xfef80000 0xfef80fff     0x1000          0 ---sr--
    0xfef90000 0xfef90fff     0x1000          0 ----rw-
    0xfefa0000 0xfefa0fff     0x1000          0 ----rw-
    0xfefb0000 0xfefb0fff     0x1000          0 ----rwx
    0xfefc0000 0xfefeafff    0x2b000          0 ----r-x
    0xfeff0000 0xfeff0fff     0x1000          0 ----rwx
    0xfeffb000 0xfeffcfff     0x2000    0x2b000 ----rwx
    0xfeffd000 0xfeffdfff     0x1000          0 ----rwx

(gdb) i files                 #列出各段在內存中的位置,以及所加載共享庫的.text,數據段、未初始化數據段.bss內容映射到虛擬內存的位置
Symbols from "/data1/nan/a".
Unix /proc child process:
    Using the running image of child Thread 1 (LWP 1) via /proc.
    While running this, GDB does not access memory from...
Local exec file:
    `/data1/nan/a', file type elf32-i386-sol2.
    Entry point: 0x8050950
    0x080500f4 - 0x08050105 is .interp
    0x08050108 - 0x08050114 is .eh_frame_hdr
    0x08050114 - 0x08050218 is .hash
    0x08050218 - 0x08050418 is .dynsym
    0x08050418 - 0x080507e6 is .dynstr
    0x080507e8 - 0x08050818 is .SUNW_version
    0x08050818 - 0x08050858 is .SUNW_versym
    0x08050858 - 0x08050890 is .SUNW_reloc
    0x08050890 - 0x080508c8 is .rel.plt
    0x080508c8 - 0x08050948 is .plt
    ......
    0xfef5fb58 - 0xfef5fc48 is .dynamic in /usr/lib/libc.so.1
    0xfef5fc80 - 0xfef650e2 is .data in /usr/lib/libc.so.1
    0xfef650e2 - 0xfef650e2 is .bssf in /usr/lib/libc.so.1
    0xfef650e8 - 0xfef65be0 is .picdata in /usr/lib/libc.so.1
    0xfef65be0 - 0xfef666a7 is .data1 in /usr/lib/libc.so.1
    0xfef666a8 - 0xfef680dc is .bss in /usr/lib/libc.so.1

打印 heap#

在.gdbinit 中添加自定義打印內存分配命令

define mallocinfo
  set $__f = fopen("/dev/tty", "w")    # 打開 /dev/tty 文件進行輸出
  call malloc_info(0, $__f)            # 調用 malloc_info 函數將內存分配信息輸出到文件
  call fclose($__f)                    # 關閉文件
end

調試程序並檢查內存分配:

(gdb) mallocinfo
<malloc version="1">                      #使用的內存分配函數的版本
<heap nr="0">                             #第一個堆,多線程程序會為每個線程分配一個獨立的堆,減少線程間競爭

<sizes>                                   #不同大小的內存塊的分配情況,在示例程序中,沒有分配內存塊
</sizes>

<total type="fast" count="0" size="0"/>   #快速分配和其它類型內存塊,count="0"表示當前沒有分配內存塊,size="0"表示占用內存大小為0。
<total type="rest" count="0" size="0"/>   #這部分是針對malloc內部實現的,通常用來優化內存分配,區分不同的內存池。
<system type="current" size="1134592"/>   #當前從os請求並分配的內存
<system type="max" size="1134592"/>       #so允許程序最大能分配的內存,說明程序當前沒有超過系統分配的限制。
<aspace type="total" size="1134592"/>     #地址空間的大小
<aspace type="mprotect" size="1134592"/>  #mprotect 是一個系統調用,用於改變某一段內存的訪問權限,用來保護內存不被非法修改
</heap>

<total type="fast" count="0" size="0"/>
<total type="rest" count="0" size="0"/>
<system type="current" size="1134592"/>
<system type="max" size="1134592"/>
<aspace type="total" size="1134592"/>
<aspace type="mprotect" size="1134592"/>
</malloc>

多態接口中按實參類型打印形參變量#

#include <iostream>       //示例代碼
using namespace std;

class Shape {
public:  virtual void draw () {}
};

class Circle : public Shape {
 int radius;
 public:
  Circle () { radius = 1; }
  void draw () { cout << "drawing a circle...\n"; }
};

class Square : public Shape {
 int height;
 public:
  Square () { height = 2; }
  void draw () { cout << "drawing a square...\n"; }
};

void drawShape (class Shape &p){          //暴露一個多態的接口,依據傳入對象類型,調用不同派生類重載的draw 實現
  p.draw ();
}

int main (void){
  Circle a;
  Square b;
  drawShape (a);
  drawShape (b);
  return 0;
}

gdb 默認按聲明類型打印對象,則打印上面多態接口 p 的類型總是 Shape ;我們希望在這個多態接口函數棧幀中按實參類型打印形參變量

(gdb) set print object on

GDB Pretty Printers#

STL 容器內部使用了複雜的內存佈局,GDB 直接打印 STL 容器會顯示一堆底層實現細節;GCC 提供 Python 腳本 (GDB 7.0 始功能) 增強 GDB 對 C++ 容器的打印功能

gcc 高版本 (>4.6.0) 情況下會自帶 pretty printer:/usr/share/gcc/python
gcc.gnu.org Git - gcc.git/tree - libstdc++-v3/python/libstdcxx/

1) 將 Pretty Printers 腳本加到 cpp 項目的.gdbinit

python
import sys
sys.path.insert(0, '/usr/share/gcc/python')
from libstdcxx.v6.printers import register_libstdcxx_printers
register_libstdcxx_printers (None)
end

2)

(gdb) i pretty-printer      #查看是否成功啟用了STL容器Pretty Printers

(gdb) set print pretty on

dependencies#

共享庫#

(gdb) i sharedlibrary           # 列出程序加載的所有共享鏈接庫
(gdb) i sharedlibrary hiredi    # 列出 符合正則hiredi 的所有共享鏈接庫

#

gcc -g3以下編譯生成的程序不包含預處理器宏信息:

(gdb) p NAME No symbol "NAME" in current context.

如果想在 gdb 中查看宏信息,可以使用 gcc -g3 進行編譯:

(gdb) p NAME
$1 = "Joe"

points#

(gdb) i b                          # 查詢所有斷點

(gdb) i watch                      # 列出所有觀察點

source#

gdb 命令行狀態下 CTRL + X + A 調出代碼窗口 tui,再按一次退出代碼窗口。

(gdb) dir <source_directory>         # 添加查找source的路徑
(gdb) set substitute-path /old /new  # 把舊的source檢索路徑替換為新的路徑

(gdb) l                         # print暫停處代碼
(gdb) l   <function>            # print函數function的代碼
(gdb) l   <file:function>       # print文件file的函數function代碼
(gdb) l   <file:line>           # print文件file中指定行數line代碼
(gdb) l -                       # 向後打印
(gdb) l +

(gdb) focus                     #tui模式
gdb -tui program                #以tui模式啟動
----------------調整tui窗口大小
win <win_name> +7           #win_name可以是src、cmd、asm、regs窗口;+或-顯示的行數

gdb info#

(gdb) show version            #查看gdb版本信息
(gdb) show copying            #查看gdb版權相關信息
(gdb) show warranty           #查看gdb免責聲明

gdb -help            #幫助信息

(gdb) apropos set    #查找所有符合set正則表達式的命令信息
(gdb) help b         #help command命令得到某一個具體命令用法

function debugging#

棧幀跟蹤#

stack frame:棧幀保持函數執行環境,幀指針Frame Pointer 指向當前棧幀底部,棧幀基址 Stack Pointer始終指向棧幀頂部。

每次函數調用,都会在調用棧call stack 上維護一個棧幀stack frame。每個獨立的棧幀一般包括:
棧指針→
)local variables
)register
)Frame Pointer
)return address 當前函數過程的下一條指令地址
) arguments 傳入參數創建的形參變量
)previous Frame Pointer
幀指針→

image

(gdb) i functions          #列出可執行文件被定義的函數,以及引用的的鏈接函數和系統函數
(gdb) i functions thre*    #列出名稱匹配 正則表達式thre 的函數,正則後面的*表示是否帶調試信息

(gdb) bt                      # 打印 程序執行到當前斷點的 函數調用棧
(gdb) frame                   # 顯示當前所在調用棧層次、所在文件及行號等

(gdb) frame 2                 # 選擇 第二層棧幀 ,查看其中變量
(gdb) up 1                    # 切換到上一个棧幀,也就是previous 調用棧幀
(gdb) down 2
(gdb) up-silently n           # 靜默切換版
(gdb) down-silently n
(gdb) frame addr              # 選擇 幀數地址為addr的棧幀 ,也就是下面的called by frame 指向的上一个棧幀的幀底previous Frame Pointer

(gdb) i frame                 # 打印當前選擇的 函數棧幀內容,默認打印最上層
Stack level 0, frame at 0x7fffffffe590:                          #最上層棧幀,棧指針
rip = 0x40054e in func (a.c:5);                                  #return address指向func下一條要執行的指令
saved rip = 0x400577                                             #上一棧幀的return address,即調用func的地方,也是當前函數執行完畢後,應該跳轉到的地址
called by frame at 0x7fffffffe5a0                                #上一个棧幀的幀指針
source language c.                                               #
Arglist at 0x7fffffffe580, args: a=1, b=2                        #形參變量
Locals at 0x7fffffffe580, Previous frame's sp is 0x7fffffffe590  #局部變量,上一个棧幀的棧指針,這裡由於是最上層棧幀,指向自己
Saved registers:                                                 #
rbp at 0x7fffffffe580, rip at 0x7fffffffe588                     #寄存器

---------------------print尾調用堆棧幀
#開啟O1的Tail call優化選項編譯:當一系列函數最後一條指令是調用另外一個函數的情況優化為:函數直接調用最終函數,而中間函數在匯編層面被優化掉了
(gdb) set debug entry-values 1   # 設置:除輸出正常函數堆棧幀信息外,還輸出尾調用相關信息

---------------------打印函數調用棧各層局部變量
(gdb) bt                      # 打印 程序執行到當前斷點的 函數調用棧
#0  fun_a () at a.c:6
#1  0x000109b0 in fun_b () at a.c:12
(gdb) bt full                 # 打印 各層局部變量
#0  fun_a () at a.c:6
        a = 0
#1  0x000109b0 in fun_b () at a.c:12
        b = 1
(gdb) bt full n               # 打印 最上n層的局部變量
(gdb) bt full -n              # 打印 最下n層的局部變量

函數執行控制#

(gdb) finish                        # 運行直到跳出當前函數
(gdb) return val                    # 不執行函數剩下的語句,直接返回val值

(gdb) call func()                   # 停在某處時運行此語句,直接調用執行文件中定義的func函數
(gdb) print func()                  # 停在某處時運行此語句,直接調用執行文件中定義的func函數

進入不帶調試信息的函數#

默認情況下,(gdb) s不會進入不帶調試信息的函數,並如同(gdb) n那樣跳過;

不帶調試信息的原因:
1)沒開啟-g生成調試信息,特別是鏈接的共享庫,提供方編譯時就沒開啟-g
2)-O2-O3優化掉了
3)內聯函數被編譯器嵌入調用位置,因此不會在調試中作為獨立函數出現。可通過編譯選項禁用內聯優化

(gdb) set step-mode on   #設置不跳過沒有調試信息的函數,即使沒調試信息,也可以先進入,然後開啟匯編調試

$_exitcode 用於打印程序退出狀態碼#

(gdb) p $_exitcode

進 / 線程調試#

進 / 線程執行控制#

默認情況下,gdb 檢測到有線程產生和退出時,會打印提示信息

(gdb) set print thread-events off              # 關閉打印提示

#避免在調試一個線程時,其他線程的執行會干擾你的調試過程
(gdb) set scheduler-locking off              # 默認,gdb只控制調試的單線程運行,其他線程的調度是獨立的
(gdb) set scheduler-locking on               # 鎖定調度,當調試一個線程時,其他線程阻塞,直到顯式切換線程或完成調試。
(gdb) set scheduler-locking step             # 用 step 單步調試時,其他線程會被暫停;用 next 調試,其他線程可能繼續執行。

(gdb) thread apply <id1><id2> command    # 指定線程集執行gdb命令
(gdb) thread apply all command           # 指定線程集執行gdb命令

進程調試#

調試多進程程序時,GDB 默認控制父進程運行而子進程獨立運行。

---------------------------------調試非鎖定調度的fork進程:一個進程gdb控制執行,一個獨立運行
(gdb) set follow-fork-mode child    # GDB在fork()暫停,如果(gdb) n跟蹤的就是父進程,此時可以告訴gdb跟蹤子進程;此命令在非linux上不知道是否支持
(gdb) start
(gdb) n

(gdb) set follow-fork-mode parent   # 默認控制父進程運行(默認)

(gdb) i inferiors                   # 顯示當前調試的所有進程,包括父進程和子進程
(gdb) inferior <num>                # 切換到指定編號的進程num調試
(gdb) attach <pid>                  # 相當於切換跟蹤

---------------------------------調試鎖定調度的的fork進程:父子進程由gdb控制為交替執行,並發調度的
(gdb) set detach-on-fork on           # 默認

(gdb) set detach-on-fork off          # 同時調試父子進程
(gdb) start                           # 程序會在fork()調用處停下來。
(gdb) i inferiors                     # 顯示當前調試的所有進程,包括父進程和子進程
(gdb) inferior <num>                  # 切換到指定編號的進程num調試
(gdb) n

---------------------------------調試可並行的父子進程:
(gdb) set schedule-multiple off   # 默認

(gdb) set detach-on-fork off      # 可同時調試父子進程
(gdb) set schedule-multiple on    # 執行到 fork() 時,父子進程會並行運行
(gdb) start

---------------------------------調試多進程
gdb a
(gdb) start
(gdb) add-inferior -copies 2 -exec b       #-copies複製進程數,-exec指定要加載的可執行文件
(gdb) i inferiors
  Num  Description       Executable
  3    <null>            /home/nanxiao/b
  2    <null>            /home/nanxiao/b
* 1    process 1586      /home/nanxiao/a
(gdb) inferior 2                           #調試進程2
(gdb) clone-inferior -copies 1             #克隆當前調試的程序
(gdb) i inferiors
  Num  Description       Executable
  4    <null>            /home/nanxiao/b
  3    <null>            /home/nanxiao/b
* 2    process 1590      /home/nanxiao/b
  1    process 1586      /home/nanxiao/a

打印進程空間#

--------------------------打印調試會話中所有進程空間
(gdb) maint info program-spaces    # 打印調試會話中所有進程空間,包括進程空間號與所綁定的進程id
  Id   Executable
  4    /home/nan/b
        Bound inferiors: ID 4 (process 0)
  3    /home/nan/b
        Bound inferiors: ID 3 (process 0)
* 2    /home/nan/b
        Bound inferiors: ID 2 (process 15902)
  1    /home/nan/a
        Bound inferiors: ID 1 (process 15753)

線程調試#

(gdb) i threads                   # 查看當前程序中的所有線程編號,線程id,當前所處調用棧函數
(gdb) i threads 1 2               # 查看指定線程

(gdb) thread 2                    # GDB 切換到線程 2調試,並顯示該線程當前的調用棧函數

(gdb) wa a thread 2               # 只在 thread2線程 觸發 a變量的觀察點

pstack <pid>      # 可事先dump某個進程下所有線程的thread id和backtrace,方便gdb調試

$_thread 用於線程調試#

#可以幫助你快速定位並發程序中的潛在問題,比如競態條件、死鎖等
(gdb) watch a                        #監控變量a
(gdb) command 2                      #設置當編號2的斷點觸發時自動執行gdb命令:打印修改a的線程
> printf "thread id=%d\n", $_thread  #$_thread 會被替換為當前線程的線程 ID
> end

Solaris 專用線程調試 cmd#

(gdb) maint info sol-threads      # 查看當前程序中的所有線程:類型,編號,對應lwp,,當前線程狀態,,該線程入口函數
user   thread #1, lwp 1, (active)
user   thread #2, lwp 2, (active)    startfunc: monitor_thread
user   thread #3, lwp 3, (asleep)    startfunc: mem_db_thread
- Sleep func: 0x000aa32c          # 如果線程處於休眠狀態,還會顯示休眠函數的地址Sleep func

Asynchronous Debugging#

同步任務是按特定順序執行的操作(如多線程按順序互斥修改內存),常見的同步機制 (鎖機制) 會在執行過程中阻塞其他任務

異步任務是含有需要等待的操作,異步任務要高效利用系統資源:需要在進行需等待的操作時讓出 cpu,然後在操作完成時通過某種機制(如信號,事件循環、回調、線程池等)搶占當前正在執行的任務

signals debugging#

GDB 提供了強大的信號處理控制功能

(gdb) i signals                                             # 查看當前調試進程對每個信號的默認處理方式
Signal        Stop      Print   Pass to program Description # 信號名、捕獲信號是否停止程序、是否打印信息、GDB是否會傳遞給程序的自定義handler處理、描述
SIGHUP        Yes       Yes     Yes             Hangup
SIGINT        Yes       Yes     No              Interrupt   # 這個信號不會傳遞給程序處理,而是用於進程控制的信號
SIGQUIT       Yes       Yes     Yes             Quit
...
SIGALRM       No        No      Yes             Alarm clock

(gdb) handle <Signal> nostop # 捕獲 SIGHUP 信號不停止程序
(gdb) handle <Signal> stop

(gdb) handle <Signal> noprint
(gdb) handle <Signal> print

(gdb) handle <Signal> nopass #是否傳遞給程序,讓程序用自定義handle處理
(gdb) handle <Signal> pass

(gdb) signal <Signal>        # 給給調試程序發送信號,這比換到shell用kill發送方便
(gdb) signal 0               # 當程序暫停後,用這條使程序重新運行

$_siginfo 處理異常退出#

$_siginfo 保存當前收到信號的詳細信息。在處理諸如 SIGSEGV(段錯誤)或 SIGFPE(算術錯誤)等信號時,特別有用

(gdb) ptype $_siginfo
type = struct {
    int si_signo; #信號編號,指示接收到的信號類型。
    int si_errno; #信號相關的錯誤信息(通常為0)。
    int si_code;  #kernel給的附加信息,描述信號的具體原因。
    union {
        int _pad[28];
        struct {...} _kill;
        struct {...} _timer;
        struct {...} _rt;
        struct {...} _sigchld;
        struct {...} _sigfault;
        struct {...} _sigpoll;
    } _sifields; #根據不同類型的信號,包含不同的結構體信息,提供更多信號特定的數據。
}
(gdb) ptype $_siginfo._sifields._sigfault
type = struct {
    void *si_addr;
}
(gdb) p $_siginfo._sifields._sigfault.si_addr    #引發查看段錯誤的內存地址
\$4 = (void *) 0x850e

死鎖調試#

core dump#

ulimit -a
-a  顯示目前資源限制的設定。
-c <core文件上>  設定core文件的最大值,單位為區塊。
-d <數據節區大>  程序數據節區的最大值,單位為KB。
-f <文件大>  shell所能建立的最大文件,單位為區塊。
-H  設定資源的硬性限制,也就是管理員所設下的限制。
-m <內存大>  指定可使用內存的上限,單位為KB。
-n <文件數>  指定同一時間最多可開啟的文件數。
-p <緩衝區大>  指定管道緩衝區的大小,單位512字節。
-s <堆疊大>  指定堆疊的上限,單位  為KB。
-S  設定資源的彈性限制。
-t <CPU時>  指定CPU使用時間的上限,單位為秒。
-u <程序數>  用戶最多可開啟的程序數目。
-v <虛擬內存大>  指定可使用的虛擬內存上限,單位為KB。

一般情況下,當設置了ulimit -c unlimited; 當程序出現嚴重錯誤(如段錯誤、非法指令等)時,將程序 machine states 轉儲到 core 文件,進行故障排查。

ulimit -c            # 查看當前系統是否允許生成 core 文件;返回0表示系統禁用 core 文件生成。輸出正整數表示系統允許生成的cool文件字節大小
ulimit -c unlimited  # 調整生成 core 文件的大小限制
echo "/coredump/core.%e.%p" > /proc/sys/kernel/core_pattern  #編輯/etc/sysctl.conf或/proc/sys/kernel/core_pattern文件設置core文件保存路徑,文件名為core.<程序名>.<PID>

-------------生成core
(gdb) generate-core-file                # 生成core文件,記錄當前調試進程狀態。默認生成的 core 文件名為 core.<PID>
(gdb) generate-core-file my_core_dump   # 為 core 文件指定一個自定義的文件名

(gdb) gcore                             # gcore工具與gdb內部generate-core-file 命令作用一樣,默認生成的 core dump 文件名為 core.<PID>
(gdb) gcore my_core_dump

#gcore工具可對正在運行的進程進行 core dump(即使該進程沒有崩潰),生成該進程的內存快照:
ps aux | grep <your_program>            # 找到進程的pid
gcore <pid>                             # 生成core.<PID>文件

-------------------分析core
gdb -c core.a.1402638140 ./a.out                # 調試./a.out 生成的 core 文件 core
(gdb) bt full                                   # 查看異常的backtrace,查看崩的地方
(gdb) i locals

#或者像下面那樣加載coredump
gdb -q
(gdb) file ./a.out
(gdb) core /coredump/core.a.1402638140

匯編調試#

print assembly#

(gdb) disass                    # print當前匯編指令
(gdb) disassemble func          # print func函數匯編指令
(gdb) disas /m func             # 將源代碼與匯編指令一起顯示
(gdb) disassemble /r func       # 以16進制print func函數機器碼
(gdb) disassemble /mr func      # 匯編+機器碼

(gdb) i line 13                             # 查看該行涉及的匯編指令的起始和結束地址
Line 13 of "foo.c" starts at address 0x4004e9 <main+37> and ends at 0x40050c <main+72>.
(gdb) disassemble 0x4004e9, 0x40050c        # 查看地址範圍對應的匯編指令

(gdb) set disassemble-next-line off
(gdb) set disassemble-next-line on    # 自動反匯編後面要執行的代碼
(gdb) set disassemble-next-line auto  # 後面的代碼沒有源碼的情況下才反匯編後面要執行的代碼,一般是一些沒調試信息的函數
(gdb) set disassembly-flavor intel # intel格式,同理att則為at&t格式
(gdb) display /ni $pc                # 每次回到gdb命令行時,只把將要執行的n條匯編指令打印出來
(gdb) undisplay

(gdb) layout asm                #tui模式下顯示匯編代碼窗口
(gdb) layout split              #tui模式下匯編和source雙窗口

匯編調試運行控制#

(gdb) si    # 單步執行一條匯編指令
(gdb) ni    # 不跳入

在函數的第一條匯編指令打斷點#

-------eg:
#include <stdio.h>
int global_var;

void change_var(){
    global_var=100;
}

int main(void){
    change_var();
    return 0;
}

給函數打斷點的命令:“b func”,不會把斷點設在匯編指令層次函數的開頭:

(gdb) b main
Breakpoint 1 at 0x8050c12: file a.c, line 9.
(gdb) r
Starting program: /data1/nan/a
[Thread debugging using libthread_db enabled]
[New Thread 1 (LWP 1)]
[Switching to Thread 1 (LWP 1)]

Breakpoint 1, main () at a.c:9
9           change_var();
(gdb) disassemble
Dump of assembler code for function main:
   0x08050c0f <+0>:     push   %ebp
   0x08050c10 <+1>:     mov    %esp,%ebp
=> 0x08050c12 <+3>:     call   0x8050c00 <change_var>
   0x08050c17 <+8>:     mov    $0x0,%eax
   0x08050c1c <+13>:    pop    %ebp
   0x08050c1d <+14>:    ret
End of assembler dump.

斷點前加 * 才能在能把斷點設置在匯編指令層次函數的開頭,“b *func”:

(gdb) b *main
Breakpoint 1 at 0x8050c0f: file a.c, line 8.
(gdb) r
Starting program: /data1/nan/a
[Thread debugging using libthread_db enabled]
[New Thread 1 (LWP 1)]
[Switching to Thread 1 (LWP 1)]

Breakpoint 1, main () at a.c:8
8       int main(void){
(gdb) disassemble
Dump of assembler code for function main:
=> 0x08050c0f <+0>:     push   %ebp
   0x08050c10 <+1>:     mov    %esp,%ebp
   0x08050c12 <+3>:     call   0x8050c00 <change_var>
   0x08050c17 <+8>:     mov    $0x0,%eax
   0x08050c1c <+13>:    pop    %ebp
   0x08050c1d <+14>:    ret
End of assembler dump.

修改被調試程序的二進制文件#

修改被調試程序的二進制文件 | 100 個 gdb 小技巧

gdb logging#

gdb 默認不開啟日誌,若開啟了,則當前目錄下生成 gdb.txt 記錄 gdb 命令行所有輸出結果,方便回溯歷史。

(gdb) show logging               #查看日誌設置

(gdb) set logging on             # 設置開啟gdb日誌
(gdb) set logging off
(gdb) set logging file log.txt   # 修改日誌文件路徑為log.txt

(gdb) set logging redirect on    #GDB 會話中的輸出(包括命令的執行結果和調試信息)將被寫入日誌文件,而不是在終端顯示。
(gdb) set logging redirect off

(gdb) set logging overwrite on   #每次啟動 GDB 並開啟日誌記錄時,如果日誌文件已經存在,則以覆寫模式記錄
(gdb) set logging overwrite off  #追加模式記錄

.gdb_history#

gdb 缺省不保存歷史命令;若保存,則歷史命令缺省保存在當前目錄下的.gdb_history

(gdb) set history save on              # 開啟gdb歷史cmd保存
(gdb) set history filename ~/.gdbcmd   # 設置保存路徑

.gdbinit 模板#

gdb 啟動時,會執行 HOME 目錄和當前目錄下.gdbinit 腳本裡的命令

c 項目#

cpp 項目#

# 打印STL容器中的內容
python
import sys
sys.path.insert(0, "/home/xmj/project/gcc-trunk/libstdc++-v3/python")
from libstdcxx.v6.printers import register_libstdcxx_printers
register_libstdcxx_printers (None)
end

# 保存歷史命令
set history filename ~/.gdb_history
set history save on

# 退出時不顯示提示信息
set confirm off

# 按照派生類型打印對象
set print object on

# 打印數組的索引下標
set print array-indexes on

# 每行打印一個結構體成員
set print pretty on

內存調試#

todo#

gdb 多窗口管理
查看對象類型
C++ 跨平台多線程
多線程調試管理
查找線程、線程斷點
為線程執行命令
線程日誌信息控制
調用內部及外部函數
跳過函數
製作、調試發行版
為軟件製作補丁

內存泄漏檢測
內存檢查
遠程調試
死鎖調試
core dump 基礎
棧溢出 core dump 分析
無調試符號 coredump 分析
軟件 “破解”

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。