1. 程式人生 > >【軟體開發底層知識修煉】二十一 ABI-應用程式二進位制介面一

【軟體開發底層知識修煉】二十一 ABI-應用程式二進位制介面一

文章目錄

1 什麼是ABI(Application Binary Interface)

ABI,就是應用程式二進位制介面。乍一聽,可能不知道是啥。後面會慢慢深入理解。

那麼ABI大概包括哪些內容呢?

  • 資料型別的大小,資料的對其方式
  • 函式呼叫時發生的呼叫約定。比如引數的入棧順序啊等
  • 系統呼叫的編號,以及系統呼叫的方式。不同的系統可能系統函式的中斷號不同等
  • 目標檔案的二進位制格式,程式庫的格式等。比如Linux下的ELF格式的檔案與Windows下的可執行檔案就有區別。

以上說的那些區別,說的窄一些就是不同的系統平臺,雖然寫的C語言可能一樣,但是最終編譯成的二進位制可執行檔案肯定是在格式上有一定的區別。當然在記憶體對齊啊,函式引數入棧順序,系統呼叫上等等等都有可能存在區別。

我們可以看到,這些區別都比較接近底層,不像API,API是原始碼層面不一樣。比如不同的作業系統的API可能就有一定的區別。但是也很有可能不同的作業系統的API一樣,但是ABI就不一樣。總結來說API與ABI具有以下特徵:

  • ABI和API是不同層面的規範
  • ABI是二進位制層面的規範
  • API是原始碼層面的規範
  • ABI和API沒有直接的聯絡
  • 遵循相同的ABI的系統,API可能不一樣
  • 所提供的API相同的系統,遵循的ABI可能相同

我們要注意一點:ABI與API沒有必然的關係

1 ABI的廣義與俠義概念

  • 廣義上ABI 的概念

泛指應用程式在二進位制層面應該遵循的規範

  • 狹義上ABI的概念

特指

  • 某個具體硬體平臺的ABI規範文件
  • 某個具體作業系統平臺的ABI規範文件
  • 某個具體虛擬機器平臺的ABI規範文件

2 什麼是EABI(Embedded Application Binary Interface)

EABI就是:嵌入式應用程式二進位制介面

EABI與ABI基本相同。我們只需要注意一點不同:EABI的應用程式中可以直接使用特權指令。至於什麼是特權指令。可以轉移到我的另一個部落格專欄系列:《手寫作業系統》專欄中查詢。

3 ABI規範示例

上面說了那麼多概念,可能也沒能讓小白明白什麼是ABI規範。下面給一個例子來說明。

我們之前學過C/C++內嵌彙編,如果不懂還是需要去看前面的文章。

為什麼下面的程式碼能夠以0作為退出碼結束程式的執行?

在這裡插入圖片描述

為什麼呢?為什麼上述程式碼就是代表sys_exit的系統呼叫呢?這其實就是一種規定。你如果是規定人員你也刻意規定2是sys_exit的系統呼叫編號。

但是別人就規定編號1是sys_exit的系統呼叫編號。並且規定在發生這個系統呼叫時eax存的是系統呼叫編號,而ebx存的是退出碼,這裡我們的退出碼是0. 那麼我們寫這種內嵌彙編就要按照別人的ABI規範來。這是沒辦反的,除非你有能力去修改這種規範。

那麼上面就是一種ABI規範。這個例子如果還是不能讓你理解,也沒關係,下面還有程式碼的例子

4 ABI的其他方面

4.1 ABI定義的基礎資料型別

ABI定義了基礎資料型別的大小。

我們都知道在我們電腦上寫的程式int是4位元組。因為我們在Intel的X86平臺。如果你足夠牛逼你也可以自己造硬體自己規定ABI規範,你可以將int定義為100位元組都沒人管你。

當然這是很難的。我們還是先來看看X86平臺下,ABI規範定義的基礎資料型別的大小吧。如下圖:

在這裡插入圖片描述

上面那些,我想大家都背過。現在知道怎麼來了的吧。

4.2 ABI與移植性

首先先看下面的一張架構圖:

在這裡插入圖片描述

  • 上面的圖很容易看懂。

  • ABI規範的基礎資料型別及大小:在硬體CPU上面,肯定是ABI規範的基礎資料型別大小。隨著CPU硬體架構的不同,基礎資料型別可能也不同。

  • 型別適配層: 但是我們還要寫程式,為了方便寫程式,在ABI規範的上面肯定要有一個適配層。這個適配層是將ABI規範層重新定義成我們方便寫程式碼的樣子。並且平臺改變時只需要重新定義型別適配層。typedef就是屬於型別適配層。型別及大小不隨平臺而改變。

  • 應用程式框架層與應用程式邏輯層:我們寫程式碼最接近的就是這兩層了,尤其是邏輯層,你寫程式碼寫的大多是邏輯層。牛逼點的可能去寫框架。

  • 上面的型別適配層可能沒有講明白。其實就是針對ABI規範的基礎資料型別,自己給重新定義一下。比如基礎資料型別int是4位元組,我們可以使用typedef int INT,這樣我們以後寫程式碼可以就完全使用INT代表4位元組的整形。
    但是如果換了一個平臺,假如這個平臺沒有int基礎資料型別,假設它有一個叫做newint
    的基礎資料型別是4位元組整形(當然這是我們假設的,實際上沒有)。此時我們只需要將typedef int INT修改為typedef newint INT。而我們的應用程式框架和應用程式程式碼,就完全不用修改。這大大地提高了我們的應用程式的移植性。

說到這裡,應該明白了為什麼本節的小標題叫做ABI與移植性了吧。如果不懂,再看兩遍。

4.3 ABI規定的結構體與聯合體的位元組對齊方式

我們面試經常被問到結構體的對齊方式。這實際上也是ABI規範的。至於為什麼那麼規定,就要涉及到硬體與底層記憶體了。一般就是為了提高CPU的執行效率,也沒什麼神祕的。

下面我們就來看看幾個簡單結構體的記憶體對齊方式:

在這裡插入圖片描述

在這裡插入圖片描述

  • 注意以上四個結構體的對齊方式,實際上就是因為ABI的規範,當然,就算沒有ABI規範,我們應該也能夠知道它的記憶體佈局是上線那樣。畢竟那是最基本的知識。
  • 有一點需要注意的是上面的記憶體圖中從上往下是低地址到高地址。每一行是4位元組大小。
  • 上面的聯合體,實際上只有一行,不是3行12位元組。它是相當於1行一共4位元組。這4位元組供c,s,i公用。

5 不同的ABI規範下的記憶體對齊方式程式碼分析

本節內容以程式碼實際案例來分析,在Linux下與WIindows下的不同ABI對應的不同記憶體對齊方式。

主要是以結構體的記憶體對齊方式來說。

在看具體程式碼之前,首先要知道一個知識點:

  • 位域

所佔用的記憶體是以位元來記的。

5.1 程式碼

  • bit_field.c
#include <stdio.h>

struct {
    short s : 9;
    int j : 9;
    char c;
    short t : 9;
    short u : 9;
    char d;
} s;

int main(int argc, char* argv[])
{
    int i = 0;
    int* p = (int*)&s;

    printf("sizeof = %d\n", sizeof(s));

    s.s = 0x1FF;
    s.j = 0x1FF;
    s.c = 0xFF;
    s.t = 0x1FF;
    s.u = 0x1FF;
    s.d = 0xFF;

    for(i=0; i<sizeof(s)/sizeof(*p); i++)
    {
        printf("%X\n", *p++);
    }
    

    return 0;
}

上述程式碼很容易讀懂。我們主要想研究該程式碼在Linux平臺下與Windows平臺下的記憶體對齊方式的不同。

5.2 Linux平臺下執行結果

首先在Linux下編譯執行程式,得到結果如下:

  • gcc bit_field.c -o test
  • ./test

執行結果為:

sizeof = 12
FF03FFFF
1FF01FF
FF

這個執行結果看起來也不是很容易看明白記憶體到底是怎麼分佈的,將其轉換成記憶體圖就可以明白了:

  • 結構體為:
    在這裡插入圖片描述

  • Linux下的記憶體圖為:
    在這裡插入圖片描述

  • 由上圖可知s,j各佔9個位元,並且它們是挨著的。

  • 由於預設是4位元組對齊,並且第一個4位元組後面還有14個位元,所以還能存放c變數。但是如果把4位元組劃分成4個單獨的位元組這樣看,那麼無疑最後的24bit-31bit是一位元組。所以c放在最後的24bit-31bit,更加符合對齊的意思。雖然是預設4位元組對齊,但是在這4位元組中,也同樣要按位元組對齊。這樣能夠提高CPU的執行效率。FF03FFFF

  • t和u是如上圖存放的。這符合程式的執行結果: 1FF01FF 至於為什麼變數u不放到9bit-17bit,這個就是具體的ABI規範文件規定的。到底原理是什麼就涉及的有點遠了。這裡不在多講,以後有機會會詳細講解。

  • d佔用最後的4位元組。

  • 一共佔用12位元組,這與程式的執行結果是一樣的。

  • 但是注意這是Linux平臺下的記憶體模型。

我們注意到上圖中有一個壓縮儲存,實際上Linux平臺下,記憶體模型都是壓縮儲存。

5.3 Windows平臺下執行結果

在Windows下的vs下執行該程式,結果如下:

  sizeof = 16
  1FF
  1FF
  1FF00FF
  FF01FF

這個執行結果看起來也不是很容易看明白記憶體到底是怎麼分佈的,將其轉換成記憶體圖就可以明白了:

  • 結構體為:
    在這裡插入圖片描述

  • Windows下的記憶體圖為:

在這裡插入圖片描述

  • 在預設4位元組的情況下,Windows下,s佔用4位元組,其實s只佔了9個位元,但是由於位元組對齊,後面的23位元都是空的。1FF
  • j , c, t, u, d也很容易看出來。但是與上一小節的Linux下的有所不同。
  • 上圖中有一個非壓縮儲存。在Windows下就是非壓縮儲存。各個變數離的有一定的距離。
  • 很明顯,在不同的平臺,記憶體對齊方式都是不一樣的。這就是ABI規範的不同帶來的不同。

6 總結

  • 廣義上的ABI指應用程式在二進位制層面需要遵守的約定
  • 狹義上的ABI指一個具體硬體或者作業系統的規範文件
  • ABI定義了基礎資料型別的大小
  • ABI定義了結構體/聯合體的記憶體對齊方式

歡迎與我共同探討各種技術。