何時需要看 Assembly Code#
有時你盯著一行簡單的程式碼,明明知道它應該怎麼運作,但它就是不照預期執行。此時可以深入到機器指令層級來觀察,因為在這個層級:
- 每條指令只做一個簡單操作,你可以清楚看到發生了什麼
- 沒有隱藏的抽象層
透過檢視機器碼的執行,你可以發現:
- 不預期的 type cast
- 搞錯的運算子優先序
- overloaded operator 的非預期使用
- 遺漏的大括號
- 不恰當的型別使用(如用
int而非long) - polymorphic routine 的非預期呼叫
- code inlining 導致無法 step into 的情況
理解 Assembly 語言基礎#
兩種主要的 assembly 語法風格:
- AT&T 語法 — Unix 系統常見,用於 ARM 等平台
- Intel 語法 — Windows 系統常見,用於 x86 平台
常見指令#
| 指令 | 功能 |
|---|---|
add | 加法 |
mov | 搬移資料 |
cmp | 比較兩個值 |
call | 呼叫函式 |
jmp / b | 無條件跳躍(迴圈) |
jle / ble | 條件跳躍(小於或等於) |
push / pop | Stack 操作 |
關鍵概念#
- Register — CPU 內部的少量儲存空間(x86:
eax,edx等;ARM:r0-r15) - Frame pointer —
ebp或fp,用來存取 local 變數和引數(以偏移量定址) - Return value — 通常存在
eax(x86)或r0(ARM)register 中
在 Debugger 中檢視 Assembly#
- Visual Studio — Debug - Windows - Disassembly(
Alt-8),搭配 Debug - Windows - Registers(Alt-5) - gdb —
display/i $pc顯示每條反組譯指令,用stepi和nexti逐指令執行,info registers或display $eax查看 register 值 - Eclipse — 安裝 bytecode plugin 來查看 JVM bytecode
要知道函式的回傳值,只需在函式即將返回到呼叫者時,查看 return value register(
eax或r0)的值即可。
檢視 Raw Memory#
了解資料在記憶體中的實際表示也很有用,特別是處理來自磁碟或其他程序的 binary 資料時。
各工具的操作方式#
- Visual Studio — Debug - Windows - Memory(
Alt-6),輸入 buffer 名稱或位址 - Eclipse — Window - Show View - Other - Debug - Memory
- gdb —
x/指令,格式為x/數量格式大小 位址,例如x/10xb &a顯示變數a的 10 個 byte 的十六進位內容
Endianness#
記憶體中整數的 byte 儲存順序有兩種:
- Little-endian — 最低位 byte 在前(Intel、大多數 ARM),例如
0x76543210儲存為0x10 0x32 0x54 0x76 - Big-endian(network byte order)— 最高位 byte 在前(SPARC、PowerPC),也是 TCP/IP 和 Java binary format 使用的格式
檢視 raw memory 時務必注意 endianness,否則會誤讀資料的值。
重點回顧#
- 當程式碼行為不符預期時,透過反組譯的機器指令來真正理解程式在做什麼
- 查看
eax或r0register 可以得知函式的回傳值 - 透過檢視 raw memory 來理解資料的實際內部表示,注意 endianness 差異