應用二進位介面 ABI(概覽)
/ ay-bee-eye /
如果說 API 是原始碼層級的協議(要呼叫什麼),那麼 ABI 就是編譯後位元組層級的協議(那些機器碼實際上如何咬合在一起)。ABI 是 application binary interface(應用二進位介面)的縮寫。兩段已編譯好的程式碼——比如你的程式與一個函式庫——要能正確互相呼叫,就得在一大堆低階機制上取得共識,而 ABI 正是那一整套二進位層級的慣例。
ABI 釘死了哪些東西?例如:引數如何傳給函式(用哪些 CPU 暫存器或堆疊位置)、回傳值放在哪、呼叫進行時堆疊如何排佈、每個資料型別多大、它的位元組在記憶體中如何排列與對齊,以及名稱如何編碼好讓連結器能配對。這些都不出現在你的原始碼裡;編譯器依平台的 ABI 替你挑定。一次呼叫的兩邊必須用相同的 ABI,否則位元組會被誤解——一邊把引數放進某個暫存器,另一邊卻從不去讀它。
為何重要:ABI 正是你能連結一個自己沒建過、預先編譯好的函式庫的原因,也是為什麼只要 ABI 沒變,一個更新過的共享函式庫就能繼續服務舊程式。它也是為什麼編譯好的可執行檔綁定某個平台:它把那個平台的 ABI 烤了進去。關鍵對比:API 在你編譯時被檢查(原始碼層級,誤用會在建構時就被打斷),而 ABI 在你編譯之後才見真章,當各自獨立建出的二進位檔必須咬合時——ABI 不相符產生的是當機與資料毀損,而不是整齊的編譯器錯誤。
在 64 位元 Linux 上,System V ABI 規定函式的第一個整數引數放進 rdi 暫存器、回傳值則由 rax 帶回。你的編譯器產出遵循該規則的程式碼,你連結的函式庫也是——所以一次呼叫就是能成功,即便兩邊都沒看過對方的原始碼。
ABI 釘死那些看不見的二進位規則——例如哪個暫存器存放引數。
別把 ABI 跟 API 搞混。API 破裂會以編譯錯誤現身;ABI 破裂則在執行期以當機或亂碼現身,因為原始碼照樣編譯得過,編譯出來的零件卻不再對二進位排佈取得共識。同一個函式庫,用不同 ABI 重新編譯,可能就無法與舊的二進位檔連結或一起執行。