1. 程式人生 > 其它 >GCC內聯彙編

GCC內聯彙編

1. gcc內聯彙編格式

__asm_- __volatile__(指令部: 輸出部: 輸入部: 損壞部)

gcc內聯彙編在處理器變數和暫存器上提供了一個模板和一些約束條件:

(1) 在指令部(Assembler Template)中數字前加上%,如%0、%1等,表示需要使用暫存器的樣板運算元。若指令部中用到幾個不同的運算元,就說明有幾個變數需要和暫存器結合。

(2) 輸出部(Output Operands) 用於描述在指令部中可以修改的C語言變數以及約束條件。每個輸出約束通常以"="或"+"號開頭,"="號表示被修飾的運算元只有可寫屬性,"+"號表示被修飾的運算元具有可讀可寫屬性。然後是一個字母(表示對運算元型別的說明),接著是關於變數結合的約束。輸出部可以是空的。

(3) 輸入部(Input Operands) 用來描述在指令部只讀取的C語言變數及約束條件。輸入部描述的引數只有只讀屬性,不要試圖修改輸入部引數的內容,因為gcc編譯器假定輸入部的引數內容在內嵌彙編之前和之後都是一致的。在輸入部中不能使用"="號或者"+"號約束條件,否則就會編譯報錯。輸入部可以是空的。

(4) 損壞部(Clobbers) 一般以"memory"結束。"memory"告訴gcc編譯器,內聯彙編程式碼改變了記憶體中的值,強迫編譯器在執行該彙編程式碼前儲存所有快取的值,在執行完彙編程式碼之後重新載入該值,目的是防止編譯亂序。"cc"表示內嵌程式碼修改了狀態暫存器的相關標誌位。


2. 例子

(1) 例1:

static inline unsigned long arch_local_irq_save(void)
{
    unsigned long flags;

    asm volatile(
    "mrs    %0, daif", //讀取PSTAT暫存器中的DAIF域到flags變數
    "msr    daifset, #2", //關閉IRQ
    : "=r" (flags)
    :
    : "memory");

    return flags;
}

先看輸出部,%0 運算元對應 "=r"(flags),即flags變數,其中"="表示被修飾的運算元的屬性是隻寫。"r"表示使用一個通用暫存器。

接著看輸入部,上例中輸入部為空,沒有指定引數。最後看損壞部,以"memory"結束。

該函式主要用於把PSTATE暫存器中的DAIF域儲存到臨時變數flags中,然後關閉IRQ。

在輸出部和輸入部使用"%"來表示引數序號,比"%0"表示第一個引數,"%1"表示第2個引數。

(2) 例2:

為了增強程式碼的可讀性,可以使用匯編符號名字來替代%表示的運算元,如下面add函式:

int add(int i, int j)
{
    int ret;

    asm volatile(
    "add    %w[result], %w[input_i], %w[input_j]"
    : [result] "=r" (ret)
    : [input_i] "r" (i), [input_j] "r" (j)
    );

    return ret;
}

書上Demo編譯驗證不通過,報錯:

$ gcc main.c -o pp
main.c: Assembler messages:
main.c:8: Error: number of operands mismatch for `add'

上述是個簡單的gcc內聯彙編的例子,主要功能是將i和j的值相加返回結果。先看輸出部,表示只定義了一個運算元。"[result]"表示定義了一個彙編符號運算元,符號名為result,它對應"=r"(ret),使用函式中定義的ret變數。在彙編程式碼中對應%w[result],其中w表示ARM64中的32位通用暫存器。再看輸入部,定義了兩個運算元,同樣使用匯編符號運算元的方式來定義。第一個彙編符號運算元是input_i對應形參i,第二個彙編符號運算元是input_j對應形參j。

3. gcc內聯彙編操作符和修飾符

操作符/修飾符    說明
=                被修飾的運算元只寫
+                被修飾的運算元具有可讀可寫屬性
&                被修飾的運算元只能作為輸出

4. ARM64特有操作符和修飾符

操作符/修飾符    說明
k                SP暫存器
w                浮點暫存器、SIMD、SVE暫存器
Upl                使用P0到P7中任意一個SVE暫存器
Upa                使用P0到P15中任意一個SVE暫存器
Input            整數,常常用於ADD指令
J                整數,常常用於SUB指令
K                整數,常常用於32位邏輯指令
L                整數,常常用於64位邏輯指令
M                整數,常常用於32位的MOV指令
N                整數,常常用於64位的MOV指令
S                絕對符號地址或標籤引用
Y                浮點數,其值為0
Z                整數,其值為0
Ush                表示一個符號的PC相對偏移量的高位部分(大於等於12bit的部分),這個PC相對偏移介於0-4GB
Q                表示沒有使用偏移量的單一暫存器的記憶體地址
Ump                一個適用於SI/DI/SF和DF模式下的載入-儲存指令的記憶體地址。

參考:《奔跑吧Linux核心》第2版,此書不怎麼樣。