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 的類型編程
參數編輯器
彙編指令由一個字符串給出,多條彙編指令結合在一塊兒使用的時候,中間以 \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
操做數之間用逗號分隔。 每一個操做數具備如下格式:指針
[ [asmSymbolicName] ] constraint (cvariablename)
%[name]
// res = num asm("movq %[num], %[res]" : [res] "=r"(res) : [num] "m"(num));
// res = num asm("movq %1, %0" : "=r"(res) : "m"(num));
第一個參數爲增長可讀性使用的,如今咱們有代碼以下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
r
爲經過寄存器尋址操做,m
經過內存尋址操做,因此看到當約束了 r
就對應寄存器的操做。輸入操做數使C變量和表達式中的值可用於彙編代碼。htm
[ [asmSymbolicName] ] constraint (cexpression)
constraint 約束不能使用 =
和 +
. 可使用 "0", 這代表在輸出約束列表中(從零開始)的條目,指定的輸入必須與輸出約束位於同一位置。
```c
int64_t res = 3;
int64_t num = 1;
asm("addq %1, %0" : "=g"(res) : "0"(num));
cexpression 能夠不爲左值,做爲彙編表達式的輸入值便可
破壞列表,主要用於指示編譯器生成的彙編指令。
從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");
編譯器爲了破壞列表項的值受到破壞,當這些條目是寄存器時,不對其進行使用;爲特殊參數時,從新刷新獲得最新的值。
約束名 | 說明 |
---|---|
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 | 容許使用有效內存地址的操做數 |
標識符 | 說明 |
---|---|
= | 表示此操做數是由該指令寫入的:先前的值將被丟棄並由新數據替換 |
+ | 表示該操做數由指令讀取和寫入 |
& | 表示(在特定替代方法中)此操做數是早期指令操做數,它是在使用輸入操做數完成指令以前寫入的,故輸入操做數部分不能分配與輸出操做數相同的寄存器 |
% | 表示該操做數與後續操做數的可交換指令 |
x86 的內存屏障指令。
```c
// 避免編譯器的優化,聲明此處內存可能發生破壞
#define barrier() asm volatile("" ::: "memory")
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__.