GDB 全名為 Global Project DeBug,它可以用來檢視系統執行檔所執行的語法,以及記憶體地址所存的資料,可以用來 Debug ,反組譯,Hack 執行檔等等,Linux 與 Windows 系統皆可以使用這套軟體。
GDB 有以下數種功用
- Debug
- 反組譯
- Hack 執行檔
對於一個已經編譯好的執行檔,因為我們沒有他的原始程式碼,無法直接參透程式的內容,而 GDB 能夠將執行檔的程式轉成組語 (assemble) ,只要你看得懂組語,就能夠猜出原始的程式碼大約會是什麼。
如何開始使用 GDB
首先,我們先寫一段簡單的 c 語言,並正確的編譯 g++ -g main.cc
,請注意一定要加 -g 這個參數, -g 代表 debug 模式,使用 GDB 時會更加好用。
- #include "stdio.h"
- int main () {
- char const *message = "How are you?";
- printf("%s", message);
- return 0;
- }
編譯完成後,會產生一個叫 a.out 的檔案,接著我們用 gdb a.out 就可以開始使用 GDB 囉,下面有執行的範例。
- # gdb a.out
- GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-64.el7
- Copyright (C) 2013 Free Software Foundation, Inc.
- License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
- This is free software: you are free to change and redistribute it.
- There is NO WARRANTY, to the extent permitted by law. Type "show copying"
- and "show warranty" for details.
- This GDB was configured as "x86_64-redhat-linux-gnu".
- For bug reporting instructions, please see:
- <http://www.gnu.org/software/gdb/bugs/>...
- Reading symbols from a.out...(no debugging symbols found)...done.
- (gdb)
GDB 基本指令
[disass] display assemble 列印出組語程式碼
- disass main
- disass /r main
- disass 0x400530,0x400550 (disass start,end)
disass 後面可以接 function 的名稱,當然你要知道有什麼程式裡面有什麼 function 名稱,一般來說 C 語言裡面都會有個 main function ,所以我們可以輸入 disass main 來檢視 main 底下的組語內容。
disass -r
,加上 -r 這個參數,可以把組語的內容用 hex 的方式印出來。
disass -m
,加上 -m 這個參數,可以印出程式原始碼內容,如果你想要這個功能,就得在編譯時先加上 debug 的參數。
disass 0x400530,0x400550
,如果你看某一個區間的組語內容,可以用 disass start address,end address , GDB 就會自動印出兩段 memory address 之間的所有組語內容。
來看一下 disass 執行的結果:
- (gdb) disass main
- Dump of assembler code for function main():
- 0x00000000004005f0 <+0>: push %rbp
- 0x00000000004005f1 <+1>: mov %rsp,%rbp
- 0x00000000004005f4 <+4>: sub $0x10,%rsp
- 0x00000000004005f8 <+8>: movq $0x4006b0,-0x8(%rbp)
- 0x0000000000400600 <+16>: mov -0x8(%rbp),%rax
- 0x0000000000400604 <+20>: mov %rax,%rsi
- 0x0000000000400607 <+23>: mov $0x4006bd,%edi
- 0x000000000040060c <+28>: mov $0x0,%eax
- 0x0000000000400611 <+33>: callq 0x4004d0 <printf@plt>
- 0x0000000000400616 <+38>: mov $0x0,%eax
- 0x000000000040061b <+43>: leaveq
- 0x000000000040061c <+44>: retq
- End of assembler dump.
[b] breakpoint 設定程式暫停點
breakpoint 可以任何設定暫停點,通常我們會在文字比較 (strncmp) 的時候,暫停程式,檢查兩個文字的內容,分別是什麼,breakpoint 有兩種縮寫可以使用, [break] & [b]。
b main
, 當程式執行到 main 這個 function 時,程式會暫停
b *0x00000000004005f0
, 當程式執行到這個記憶體 address 時,程式會暫停
info b
, 列出所有設定過的 breakpoint
delete 1
, 移除第一個 breakpoint ,你必需先用 info b 來看看目前有哪些 breakpoint ,然後再用 delete 1 2 3 來移除 breakpoint 。
來看看 breakpoint 的執行過程:
- (gdb) b main
- Breakpoint 5 at 0x4005f8: file main.cc, line 5.
- (gdb) b *0x00000000004005f0
- Breakpoint 6 at 0x4005f0: file main.cc, line 4.
- (gdb) info b
- Num Type Disp Enb Address What
- 5 breakpoint keep y 0x00000000004005f8 in main() at main.cc:5
- 6 breakpoint keep y 0x00000000004005f0 in main() at main.cc:4
- (gdb) delete 5 6
[si] stepi 執行一行組語
stepi 的縮寫是 si , stepi 代表只執行一行組語指令,每執行一行就會自動暫停。
- (gdb) b main
- (gdb) run
- .
- .
- (gdb) si
- 0x000000000040060c in main ()
- (gdb) si
- 0x0000000000400611 in main ()
- (gdb) si
- 0x00000000004004d0 in printf@plt ()
[ni] nexti 執行一行組語
nexti 的縮寫是 [ni] , next 跟 stepi 很像,也是執行一行組語,但不同的是 ,如果 nexti 遇到要 call 另外一個 function ,那麼它會直接執行到該 function 結束,也就是說 nexti 不會暫停在其它的 function 。
[n] next 執行到下一行 source code
next 的縮寫為 n ,程式碼中的每一行都可以對應到組語中的其中一段程式,有可能一行程式碼對到十行組語,如果你使用 [si] 或 [ni] 都只能一行執行一行組語,而若是我們想直接執行一個程式碼,就可以使用 next ,有一點要特別注意的是,當你 compile 程式碼的時候要使用 g++ -g
,開始 debug mode 才能用這個功能 。
- (gdb) b main
- (gdb) run
- .
- .
- (gdb) n
- 5 char const *message = "How are you?";
- (gdb) n
- 6 printf("%s", message);
[c] continue 執行到下一行 breakpoint
continue 的縮寫是 c ,功能很容易懂,就會直接執行直到下一個 breakpoint ,當然你要記得先設定 breakpoint ,例如 b *0x00000450400
watch 觀察特定變數
watch 可以用來偵測那個變數的值有被修改,當指定的變數被更改時,程式會暫停,並印出更改前後的數值,這個功能一樣要打開 debug 模式才能使用 (g++ -g)。
watch str
, 觀察變數 str
watch (t > 10)
, 觀察變數 t 是否大於 10
來看看 watch 的執行過程:
- (gdb) b main
- (gdb) run
- .
- .
- (gdb) watch message
- Hardware watchpoint 2: message
- (gdb) c
- Continuing.
- Hardware watchpoint 2: message
- Old value = 0x0
- New value = 0x4006f4 "How are you?"
- main () at main2.cc:13
- 13 printf("%s", message);
frame
所有的程式,每一個 function 都會被分配到一個 frame ,每個 frame 都是一個 組語 stack ,存放所有組語指令,然後再一行一行的執行,例如當程式執行到 printf 這個 function 的時候,就會進入該 printf frame 。
frame 1
, 進入 frame 1
up
, 進入上一個 frame
down
, 進入下一個 frame
bt
, backtrace , 列出目前所有的 frame
來看看 frame 的執行過程:
- (gdb) bt
- #0 0x00000000004004d0 in printf@plt ()
- #1 0x0000000000400616 in showPassword () at main2.cc:5
- #2 0x0000000000400625 in main () at main2.cc:11
- (gdb) frame 1
- #1 0x0000000000400616 in showPassword () at main2.cc:5
- (gdb) down
- #0 0x00000000004004d0 in printf@plt ()
- (gdb) up
- #1 0x0000000000400616 in showPassword () at main2.cc:5
print : 印出某個變數或 memory address 的數值
- (gdb) print x
- $1 = 0
printf : 一次印出兩個以上的變數
- (gdb) print "%d,%d\n",x,y
- 5,2
list 顯示目前程式執行到那一行
要使用這個功能的前提是你必須在編譯的時候,帶 -g 這個參數,
- (gdb) list
- 1 #include "stdio.h"
- 2 int main () {
- 3 char const *message = "How are you?";
- 4 printf("%s", message);
- 5
- 6 return 0;
- 7 }
display : 當碰到 breakpoint 就印出某些個變數或 memory address 的數值
until : 執行完當前的迴圈
finish : 執行完當前的 function
GDB test specific process
- sudo gdb -p 21611(process id)
- b file.cc:277 (break point)
- c (continue)
- bt
- ///usr/bin/strace -Ttt -s 1000 -p 6
dump void *
x/20c (*r->useragent_addr)->ipaddr_ptr
使用 nm 查詢 function 在哪一個 shared object 裡:
nm -C -A *.so | grep xxx_function
其它
- x/i $pc 用 hex 印出目前位置
- x/20c $pc
- set $pc=0x0804852b : 修改當前的 memory address ,可以在 call xxx 時使用。
- info frame
- info stack
- info functions 列出所有 function
- set {int}0x8048667=32 : 修改 memory value
相關文章
- GDB online documentation : https://sourceware.org/gdb/onlinedocs/gdb/
- disass : https://sourceware.org/gdb/onlinedocs/gdb/Machine-Code.html
- GDB Project : https://www.gnu.org/software/gdb/
- Reverse Debugging: https://www.gnu.org/software/gdb/news/reversible.html