使用GDB
學習組合語言的過程中利用GDB了解執行狀態是必要的,了解實際運作中做了什麼事而變成了什麼,對於熟悉有巨大的幫助。
另外現在已經有發達的LLM,問ai各種為什麼的問題及Google也同樣重要。
GDB的作用是將可執行文件反組譯成組合語言來做分析,是編寫組合語言過程中良好的debug工具。
對著我們的第一個程式打開gdb
gdb filename常用指令:
b 設定斷點r 執行disas 顯示當前位置代碼i r 顯示當前所有暫存器的內容i r rax 顯示rax暫存器的內容n 執行高階語言源文件一行不進入函數ni 執行組合語言一行不進入函數s 執行高階語言源文件一行si 執行組合語言一行x/gx 查看記憶體內容 8bytes hex格式首先設定斷點,linux的程式進入點默認是_start函數
接著執行到中斷處,並顯示當前位置代碼內容
b _startrdisas可以看到顯示了跟我們寫的相似的代碼,不過指令是gdb用的風格
另外有些暫存器被改成32位元的e開頭而不是64位元的r,是nasm處理後的結果
首先第一行
=> mov rax, 1在執行前我們可以先用 i r rax 看看它原本的值
由於這一段原本就是組合語言,所以執行下一行可以用si或s都一樣
再看看rax改變後的值
接著把mov rsi, 0x402000這行執行掉,再看看rsi的內容
i r rsi這行原本是將hello的記憶體位址放到rsi
也就是0x402000存著Hello World!\n
使用
x/gx 0x402000或x/gx $rsi來存取該記憶體位址
x/gx $rsi就像是對指針做*rsi取值,在GDB代入暫存器要以$開頭
w , o l l e H0x402000: 0x57202c6f6c6c6548再看看下面單個byte
x/bx $rsi也可以按enter自動遞增x/bx $rsi+1...x/bx $rsi+7然後把下一段8bytes一起印出來
x/2gx $rsi w , o l l e H \n! d l r o0x402000: 0x57202c6f6c6c6548 0x00000a21646c726fx/的用法是 x/數量長度格式 地址 取得該記憶體位址的內容
長度
b 1 byteh 2 bytesw 4 bytesg 8 bytes單位
x hexd decimalu unsign decimalo octalt binaryc chars stringi asm code記不住沒關係,數字類全用x/gx,字串則用x/s
現在試試 x/s $rsi,這行會取字串直到出現0 (00, \0, Ascii 0, NUL 同義)
但是hello並沒有以0結尾,所以在gdb x/s可能顯示多餘的內容,而實際執行結果並不影響
如何閱讀記憶體
Section titled “如何閱讀記憶體”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,再往上是內核使用的空間