C表達式中的彙編指令

C 表達式中的彙編指令

asm 爲 gcc 中的關鍵字,asm 表達式爲在 C代碼中嵌套匯編指令,該表達式只是單純的替換出彙編代碼,並不對彙編代碼的含義進行解析。html

asm 表達式有兩種形式,第二種 asm-qualifiers 包含了 goto 語句。
第一種形式爲常見的用法,AssemblerTemplate 和 OutputOperands 必須存在, 其中 Clobbers 存在須要 InputOperands 也出現。express

asm asm-qualifiers ( AssemblerTemplate 
                 : OutputOperands 
                 [ : InputOperands
                 [ : Clobbers ] ])

asm asm-qualifiers ( AssemblerTemplate 
                      : 
                      : InputOperands
                      : Clobbers
                      : GotoLabels)

Qualifiers 的類型編程

  • volatile, 避免編譯器的優化
  • inline, 內斂限定符,最小的體積
  • goto, 包含跳轉指令

參數編輯器

  • AssemblerTemplate
    • 彙編指令模板是包含彙編器指令的文字字符串,編輯器替換引用輸入,編譯器不會解析該指令的含義。
  • OutputOperands
    • 由 AssemblerTemplate 中的指令修改的C變量的逗號分隔列表,容許使用空列表。
  • InputOperands
    • 由 AssemblerTemplate 中的指令讀取的C變量的逗號分隔列表,容許使用空列表。
  • Clobbers
    • 用逗號分隔的寄存器列表或由 AssemblerTemplate 修改的值,不能出如今 OutputOperands 和 InputOperands 中被說起,容許使用空列表。
  • GotoLabels
    • 當使用asm的goto形式時,此部分包含 AssemblerTemplate 中的代碼可能跳轉到的全部C標籤的列表。

AssemblerTemplate

彙編指令由一個字符串給出,多條彙編指令結合在一塊兒使用的時候,中間以 \r\t 隔開,如優化

asm("inc %0\n\tinc %0" : "=r"(res) : "0"(res));

/APP
# 11 "asm.c" 1
    inc %rax
    inc %rax
# 0 "" 2
/NO_APPs

須要轉義的字符:%, =, {, }, |this

故在ATT彙編中,對寄存器進行操做的須要雙 %%, 如 inc %%rax.spa

OutputOperands

操做數之間用逗號分隔。 每一個操做數具備如下格式:指針

[ [asmSymbolicName] ] constraint (cvariablename)
  • asmSymbolicName
    • 爲操做數指定名稱,格式爲 %[name]
    // res = num
    asm("movq %[num], %[res]" : [res] "=r"(res) : [num] "m"(num));
    • 若是未指定名稱使用數字, 從 output 域開始,第一個參數爲 %0, 一次類推, 這裏的 res 爲 %0, num 爲 %1
    // res = num
    asm("movq %1, %0" : "=r"(res) : "m"(num));
  • constraint
    • 一個字符串常量,用於指定對操做數的存儲的 約束, 須要以 "=" 或 "+" 開頭
  • cvariablename
    • 指定一個C左值表達式來保存輸出,一般是一個變量名。 括號是語法的必需部分

第一個參數爲增長可讀性使用的,如今咱們有代碼以下code

int64_t res;
int64_t num = 1;

asm("movq %[num], %[res]" : [res] "=r"(res) : [num] "m"(num));
asm("movq %1, %0" : "=r"(res) : "m"(num));
asm("movq %1, %0" : "=m"(res) : "m"(num));
asm("movq %1, %0" : "=r"(res) : "r"(num));

// 對應的彙編代碼, 只保留asm表達式中的代碼
# 13 "asm.c" 1
    movq -16(%rbp), %rax  // asm-1
 # 0 "" 2
/NO_APP

/APP
 # 15 "asm.c" 1
    movq -16(%rbp), %rax  // asm-2
 # 0 "" 2
/NO_APP

/APP
 # 17 "asm.c" 1
    movq -16(%rbp), -8(%rbp)  // asm-3
 # 0 "" 2
/NO_APP

/APP
 # 19 "asm.c" 1
    movq %rax, %rax  // asm-4
 # 0 "" 2
/NO_APP
  1. 使用名稱替換和數字替換效果同樣,見 asm-1 和 asm-2
  2. 約束的用法,這裏使用比較簡單通用的的兩種狀況,r 爲經過寄存器尋址操做,m 經過內存尋址操做,因此看到當約束了 r 就對應寄存器的操做。
  3. 結果保存在 res 也就是 cvariablename 中

InputOperands

輸入操做數使C變量和表達式中的值可用於彙編代碼。htm

[ [asmSymbolicName] ] constraint (cexpression)
  • asmSymbolicName 和輸出列表的用法徹底一致
  • constraint 約束不能使用 =+. 可使用 "0", 這代表在輸出約束列表中(從零開始)的條目,指定的輸入必須與輸出約束位於同一位置。
    ```c
    int64_t res = 3;
    int64_t num = 1;
    asm("addq %1, %0" : "=g"(res) : "0"(num));

    // 輸入輸出位置相同
    movq $3, -8(%rbp)
    movq $1, -16(%rbp)
    movq -16(%rbp), %rax
    /APP
    # 32 "asm.c" 1
    addq %rax, %rax
    # 0 "" 2
    /NO_APP
    ```
  • cexpression 能夠不爲左值,做爲彙編表達式的輸入值便可

Clobbers

破壞列表,主要用於指示編譯器生成的彙編指令。

從asm表達式中看到輸出操做數中列出條目的更改編譯器是能夠肯定的,但內聯彙編代碼可能不只對輸出進行了修改。 例如,計算可能須要其餘寄存器,或者處理器可能會因爲特定彙編程序指令而破壞寄存器的值。 爲了將這些更改通知編譯器,在Clobber列表中列出這些會產生反作用的條目。 破壞列表條目能夠是寄存器名稱,也能夠是特殊的破壞列表項(在下面列出)。 每一個內容列表條目都是一個字符串常量,用雙引號引發來並用逗號分隔。

  • 寄存器

    asm volatile("movc3 %0, %1, %2"
        : /* No outputs. */
        : "r"(from), "r"(to), "g"(count)
        : "%rbx", "%rcx", "%rdx", "memory");
    
    /APP
    # 25 "asm.c" 1
        movc3 %rax, %r8, -72(%rbp)
    # 0 "" 2
    /NO_APP

    能夠看到使用到了 rax 寄存器,而後修改程序在 Clobbers 增長 %rax, 結果以下
    ```c
    asm volatile("movc3 %0, %1, %2"
    : /* No outputs. */
    : "r"(from), "r"(to), "g"(count)
    : "%rax", "%rbx", "%rcx", "%rdx", "memory");

    /APP
    # 25 "asm.c" 1
    movc3 %r8, %r9, -72(%rbp)
    # 0 "" 2
    /NO_APP
    ```
    編譯器在產生的彙編代碼中就未使用 %rax 寄存器了。
  • 特殊破壞列表項
    • "cc", 表示彙編代碼修改了標誌寄存器
    • "memory", 爲了確保內存中包含正確的值,編譯器可能須要在執行asm以前將特定的寄存器值刷新到內存中

編譯器爲了破壞列表項的值受到破壞,當這些條目是寄存器時,不對其進行使用;爲特殊參數時,從新刷新獲得最新的值。

約束

  • 一些基礎的約束
約束名 說明
whitespace 空白字符被忽略
m 容許使用內存操做數,以及機器一般支持的任何類型的地址
o 容許使用內存操做數,但前提是地址是可偏移的
V 容許使用內存操做數,不可偏移的內存地址,與 "o'互斥
r 容許在通用寄存器中使用的寄存器操做數,其中能夠指定寄存器,如 a(%rax), b(%rbx)
i 容許使用當即整數操做數
n 容許使用具備已知數值的當即整數操做數, ‘I’, ‘J’, ‘K’, … ‘P’ 更應該使用 n
F 容許使用浮點當即數
g 容許使用任何寄存器,內存或當即數整數操做數,但非通用寄存器除外
X 容許任何操做數, ‘0’, ‘1’, ‘2’, … ‘9’
p 容許使用有效內存地址的操做數
  • 標識符約束
標識符 說明
= 表示此操做數是由該指令寫入的:先前的值將被丟棄並由新數據替換
+ 表示該操做數由指令讀取和寫入
& 表示(在特定替代方法中)此操做數是早期指令操做數,它是在使用輸入操做數完成指令以前寫入的,故輸入操做數部分不能分配與輸出操做數相同的寄存器
% 表示該操做數與後續操做數的可交換指令

內核示例

  1. x86 的內存屏障指令。
    ```c
    // 避免編譯器的優化,聲明此處內存可能發生破壞
    #define barrier() asm volatile("" ::: "memory")

    // 在32位的CPU下,lock 指令爲鎖總線,加上一條內存操做指令就達到了內存屏障的做用,64位的cpu已經有新增的 *fence 指令可使用
    // mb() 執行一個內存屏障做用的指令,爲指定CPU操做;破壞列表聲明 cc memory 指示避免編譯器進行優化
    #ifdef CONFIG_X86_32
    #define mb() asm volatile(ALTERNATIVE("lock; addl $0,-4(%%esp)", "mfence", X86_FEATURE_XMM2) ::: "memory", "cc")
    #define rmb() asm volatile(ALTERNATIVE("lock; addl $0,-4(%%esp)", "lfence", X86_FEATURE_XMM2) ::: "memory", "cc")
    #define wmb() asm volatile(ALTERNATIVE("lock; addl $0,-4(%%esp)", "sfence", X86_FEATURE_XMM2) ::: "memory", "cc")
    #else
    #define mb() asm volatile("mfence":::"memory")
    #define rmb() asm volatile("lfence":::"memory")
    #define wmb() asm volatile("sfence" ::: "memory")
    #endif
    ```
  2. x86 下獲取 current 的值

    DECLARE_PER_CPU(struct task_struct *, current_task);
    
    #define this_cpu_read_stable(var)   percpu_stable_op("mov", var)
    
    static __always_inline struct task_struct *get_current(void)
    {
        return this_cpu_read_stable(current_task);
    }
    
    #define percpu_stable_op(op, var)           \
    ({                          \
        typeof(var) pfo_ret__;              \
        switch (sizeof(var)) {              \
        case 8:                     \
            asm(op "q "__percpu_arg(P1)",%0"    \
            : "=r" (pfo_ret__)          \
            : "p" (&(var)));            \
            break;                  \
        }                       \
        pfo_ret__;                  \
    })

    current_task 爲一個 struct task_struct 類型的指針,追蹤宏調用,在x86-64 下命中了 case 8: 的彙編代碼, 展開的代碼爲
    c asm("mov" "q ""%%""gs" ":" "%" "P1"",%0" : "=r" (pfo_ret__) : "p" (&(current_task))); // 變換一下爲 asm("movq %%gs:%P1, %0" : "=r"(pfo_ret__) : "p"(&(current_task)));
    這行代碼的含義爲將 約束輸入部分必須爲有效的地址(p約束), 將CPU id(經過段寄存器gs和偏移經過GDT獲得,這裏後文分析了)經過寄存器(r約束)賦值給 pfo_ret__.

參考

GCC文檔
C語言ASM彙編內嵌語法zz

相關文章
相關標籤/搜索