[Linux內核系列—C語言中內嵌彙編 asm volatile ]https://cn.aliyun.com/jiaocheng/68768.html
[ linux源碼閱讀筆記 asm函數]https://www.cnblogs.com/elnino/p/4313340.html
static inline char * strcpy(char * dest, const char *src)
{
char *xdest = dest;
__asm__ __volatile__
("1: \tmoeb %1@+, %0@+\n\t" "jne 1b" //這個冒號不是分隔符
: "=a" (dest) , "=a" (stc)
: "0"(dest), "1" (src)
: "memory");
return xdest;
}
asm volatile(“xxx”);
“asm” 表示後面的代碼爲內嵌彙編,
"asm"是"asm"的別名。
“volatile” 表示編譯器不要優化代碼,後面的指令 保留原樣,“volatile"是它的別名。括號裏面是彙編指令。
內嵌彙編語法如下:
__asm__(彙編語句模板: 輸出部分: 輸入部分: 破壞描述部分)
共四個部分:彙編語句模板,輸出部分,輸入部分,破壞描述部分,各部分使用”:“格開,彙編語句模板必不可少,其他三部分可選,如果使用了後面的部分,而前面部分爲空,也需要用”:"格開,相應部分內容爲空。例如:
__asm__ __volatile__("cli": : :"memory")
1、彙編語句模板
彙編語句模板由彙編語句序列組成,語句之間使用";"、"/n"或"/n/t"分開。指令中的操作數可以使用佔位符引用C語言變量,操作數佔位符最多10個,名稱如下:%0,%1,…,%9。指令中使用佔位符表示的操作數,總被視爲long型(4個字節),但對其施加的操作根據指令可以是字或者字節,當把操作數當作字或者字節使用時,默認爲低字或者低字節。對字節操作可以顯式的指明是低字節還是次字節。方法是在%和序號之間插入一個字母,"b"代表低字節,"h"代表高字節,例如:%h1。
2、輸出部分
輸出部分描述輸出操作數,不同的操作數描述符之間用逗號格開,每個操作數描述符由限定字符串和C 語言變量組成。每個輸出操作數的限定字符串必須包含"="表示他是一個輸出操作數。
例:
__asm__ __volatile__("pushfl ; popl %0 ; cli":"=g" (x) )
描述符字符串表示對該變量的限制條件,這樣GCC 就可以根據這些條件決定如何分配寄存器,如何產生必要的代碼處理指令操作數與C表達式或C變量之間的聯繫。
3、輸入部分
輸入部分描述輸入操作數,不同的操作數描述符之間使用逗號格開,每個操作數描述符由限定字符串和C語言表達式或者C語言變量組成。
例1 :
__asm__ __volatile__ ("lidt %0" : : "m" (real_mode_idt));
例二(bitops.h):
Static __inline__ void __set_bit(int nr, volatile void * addr)
{
__asm__(
"btsl %1,%0"
:"=m" (ADDR)
:"Ir" (nr));
}
後 例功能是將(*addr)的第nr位設爲1。第一個佔位符%0與C 語言變量ADDR對應,第二個佔位符%1與C語言變量nr對應。因此上面的彙編語句代碼與下面的僞代碼等價:btsl nr, ADDR,該指令的兩個操作數不能全是內存變量,因此將nr的限定字符串指定爲"Ir",將nr 與立即數或者寄存器相關聯,這樣兩個操作數中只有ADDR爲內存變量。
4、限制字符
4.1、限制字符列表
限制字符有很多種,有些是與特定體系結構相關,此處僅列出常用的限定字符和i386中可能用到的一些常用的限定符。它們的作用是指示編譯器如何處理其後的C語言變量與指令操作數之間的關係。
分類 | 限定符 | 描述 |
---|---|---|
通用寄存器 | “a” | 將輸入變量放入eax 這裏有一個問題:假設eax已經被使用,那怎麼辦? 其實很簡單:因爲GCC 知道eax 已經被使用,它在這段彙編代碼的起始處 插入一條語句pushl %eax,將eax 內容保存到堆棧, 然 後在這段代碼結束處再增加一條語句popl %eax,恢復eax的內容 |
“b” | 將輸入變量放入ebx | |
“c” | 將輸入變量放入ecx | |
“d” | 將輸入變量放入edx | |
“s” | 將輸入變量放入esi | |
“d” | 將輸入變量放入edi | |
“q” | 將輸入變量放入eax,ebx,ecx,edx中的一個 | |
“r” | 將輸入變量放入通用寄存器,也就是eax,ebx,ecx,edx,esi,edi中的一個 "A"把eax和edx合成一個64 位的寄存器(use long longs) |
|
內存 | “m” | 內存變量 |
“o” | 操作數爲內存變量,但是其尋址方式是偏移量類型, 也即是基址尋址,或者是基址加變址尋址 | |
“V” | 操作數爲內存變量,但尋址方式不是偏移量類型 | |
" " | 操作數爲內存變量,但尋址方式爲自動增量 | |
“p” | 操作數是一個合法的內存地址(指針) | |
寄存器或內存 | “g” | 將輸入變量放入eax,ebx,ecx,edx中的一個 或者作爲內存變量 |
“X” | 操作數可以是任何類型 | |
立即數 | “I” | 0-31之間的立即數(用於32位移位指令) |
“J” | 0-63之間的立即數(用於64位移位指令) | |
“N” | 0-255之間的立即數(用於out指令) | |
“i” | 立即數 | |
“n” | 立即數,有些系統不支持除字以外的立即數, 這些系統應該使用"n"而不是"i" | |
匹配 | " 0 ", | 表示用它限制的操作數與某個指定的操作數匹配, |
“1” … | 也即該操作數就是指定的那個操作數,例如"0" | |
“9” | 去描述"%1"操作數,那麼"%1"引用的其實就是"%0"操作數, 注意作爲限定符字母的0-9 與 指令中的"%0"-"%9"的區別,前者描述操作數,後者代表操作數。 |
|
&; | 該輸出操作數不能使用過和輸入操作數相同的寄存器 | |
操作數類型 | “=” | 操作數在指令中是隻寫的(輸出操作數) |
“+” | 操作數在指令中是讀寫類型的(輸入輸出操作數) | |
浮點數 | “f” | 浮點寄存器 |
“t” | 第一個浮點寄存器 | |
“u” | 第二個浮點寄存器 | |
“G” | 標準的80387浮點常數 | |
% | 該操作數可以和下一個操作數交換位置 例如addl的兩個操作數可以交換順序 (當然兩個操作數都不能是立即數) |
|
# | 部分註釋,從該字符到其後的逗號之間所有字母被忽略 | |
* | 表示如果選用寄存器,則其後的字母被忽略 |
修飾符 | 輸入/輸出 | 意義 |
---|---|---|
= | O | 表示此Output操作表達式是Write-Only的。 |
+ | O | 表示此Output操作表達式是Read-Write的。 |
& | O | 表示此Output操作表達式獨佔爲其指定的寄存器。 |
% | I | 表示此Input 操作表達式中的C/C++表達式可以和下一 個Input操作表達式中的C/C++表達式互換 |
5、破壞描述部分
破壞描述符用於通知編譯器我們使用了哪些寄存器或內存,由逗號格開的字符串組成,每個字符串描述一種情況,一般是寄存器名;除寄存器外還有"memory"。例如:"%eax","%ebx","memory"等。
"memory"比較特殊,可能是內嵌彙編中最難懂部分。爲解釋清楚它,先介紹一下編譯器的優化知識,再看C關鍵字volatile。最後去看該描述符。
彙編指令速查