跳到內容

使用GDB

學習組合語言的過程中利用GDB了解執行狀態是必要的,了解實際運作中做了什麼事而變成了什麼,對於熟悉有巨大的幫助。
另外現在已經有發達的LLM,問ai各種為什麼的問題及Google也同樣重要。

GDB的作用是將可執行文件反組譯成組合語言來做分析,是編寫組合語言過程中良好的debug工具。

對著我們的第一個程式打開gdb

Terminal window
gdb filename

常用指令:

b 設定斷點
r 執行
disas 顯示當前位置代碼
i r 顯示當前所有暫存器的內容
i r rax 顯示rax暫存器的內容
n 執行高階語言源文件一行不進入函數
ni 執行組合語言一行不進入函數
s 執行高階語言源文件一行
si 執行組合語言一行
x/gx 查看記憶體內容 8bytes hex格式

首先設定斷點,linux的程式進入點默認是_start函數
接著執行到中斷處,並顯示當前位置代碼內容

Terminal window
b _start
r
disas

可以看到顯示了跟我們寫的相似的代碼,不過指令是gdb用的風格
另外有些暫存器被改成32位元的e開頭而不是64位元的r,是nasm處理後的結果

首先第一行

=> mov rax, 1

在執行前我們可以先用 i r rax 看看它原本的值
由於這一段原本就是組合語言,所以執行下一行可以用si或s都一樣
再看看rax改變後的值

接著把mov rsi, 0x402000這行執行掉,再看看rsi的內容

Terminal window
i r rsi

這行原本是將hello的記憶體位址放到rsi
也就是0x402000存著Hello World!\n

使用

Terminal window
x/gx 0x402000
x/gx $rsi

來存取該記憶體位址
x/gx $rsi就像是對指針做*rsi取值,在GDB代入暫存器要以$開頭

w , o l l e H
0x402000: 0x57202c6f6c6c6548

再看看下面單個byte

Terminal window
x/bx $rsi
也可以按enter自動遞增
x/bx $rsi+1
...
x/bx $rsi+7

然後把下一段8bytes一起印出來

Terminal window
x/2gx $rsi
w , o l l e H \n! d l r o
0x402000: 0x57202c6f6c6c6548 0x00000a21646c726f

x/的用法是 x/數量長度格式 地址 取得該記憶體位址的內容
長度

b 1 byte
h 2 bytes
w 4 bytes
g 8 bytes

單位

x hex
d decimal
u unsign decimal
o octal
t binary
c char
s string
i asm code

記不住沒關係,數字類全用x/gx,字串則用x/s
現在試試 x/s $rsi,這行會取字串直到出現0 (00, \0, Ascii 0, NUL 同義)
但是hello並沒有以0結尾,所以在gdb x/s可能顯示多餘的內容,而實際執行結果並不影響

1 byte = 2 hex
一次看兩個hex,兩兩一組,一條8 bytes之中每組hex從右邊看到左邊,每條8 bytes由左到右上到下
x/gx 一次顯示8 bytes也就是8組hex,剛好是一個暫存器的完整大小
以hex格式輸出的原因是易於閱讀,如果用二進位輸出0和1在當前64位環境難以閱讀

現代cpu的位址匯流排48 bits,資料匯流排64 bits
這表示一個指令操作比如mov,最多可以搬動8 bytes大小,剛好是rax長度
不過設計上也有別的cpu資料匯流排跟暫存器不一致,實際動作可能就要分兩次

位址匯流排48 bits等於6 bytes等於12個hex
理論記憶體範圍是0~0x0000FFFFFFFFFFFF,但這部份完全取決於作業系統
在linux用戶可用的就到0x7FFFFFFFFFFF,再往上是內核使用的空間