啟動 / 附加過程#
注意:在 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
幀指針→
(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.
修改被調試程序的二進制文件#
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 分析
軟件 “破解”