何時需要看 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 / popStack 操作

關鍵概念#

  • Register — CPU 內部的少量儲存空間(x86: eax, edx 等;ARM: r0-r15
  • Frame pointerebpfp,用來存取 local 變數和引數(以偏移量定址)
  • Return value — 通常存在 eax(x86)或 r0(ARM)register 中

在 Debugger 中檢視 Assembly#

  • Visual StudioDebug - Windows - DisassemblyAlt-8),搭配 Debug - Windows - RegistersAlt-5
  • gdbdisplay/i $pc 顯示每條反組譯指令,用 stepinexti 逐指令執行,info registersdisplay $eax 查看 register 值
  • Eclipse — 安裝 bytecode plugin 來查看 JVM bytecode

要知道函式的回傳值,只需在函式即將返回到呼叫者時,查看 return value register(eaxr0)的值即可。

檢視 Raw Memory#

了解資料在記憶體中的實際表示也很有用,特別是處理來自磁碟或其他程序的 binary 資料時。

各工具的操作方式#

  • Visual StudioDebug - Windows - MemoryAlt-6),輸入 buffer 名稱或位址
  • EclipseWindow - Show View - Other - Debug - Memory
  • gdbx/ 指令,格式為 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,否則會誤讀資料的值。

重點回顧#

  • 當程式碼行為不符預期時,透過反組譯的機器指令來真正理解程式在做什麼
  • 查看 eaxr0 register 可以得知函式的回傳值
  • 透過檢視 raw memory 來理解資料的實際內部表示,注意 endianness 差異