Linux C: 内嵌汇编语法
? ? 學內嵌匯編首先知道編譯器的編譯流程,內嵌匯編就是嵌套在高級程序語言中的匯編語言。在cpp 文件轉成 .s 匯編文件時,內嵌匯編保持不動,只有高級程序語言會編譯成匯編合成在.s文件中。下面的鏈接將了C的源碼是怎么變成匯編碼:
? ? 《Linux C:匯編碼的生成 》https://blog.csdn.net/superSmart_Dong/article/details/115920429
目錄
一、基本匯編
二、擴展匯編
1)占位符和操作數變量:
2)輸出操作數:
3)輸入操作數:
4)Clobbers:
5)GotoLabel:
三、擴展匯編的轉義
四、多匯編方言模板
五、常用的操作數類型
六、給C代碼命名一個asm匯編變量名
?
?
一、基本匯編
? ? ? 基本匯編就是純匯編語言,而擴展匯編在基本匯編上加了些功能,例如可用占位符實現從C程序中向內嵌匯編中傳遞/輸出變量值,而不用去分析當前代碼的堆棧結構。也可以不用關心具體用哪些寄存器合適。先來看看基本的內嵌匯編
int main(){int aaa = 1 ,bbb=2;__asm__ __volatile__ ("movl $88,-12(%ebp) \n\t""movl $66,-16(%ebp) \n\t");cout<<aaa<<" "<<bbb; //輸出 88 66return 0; }? ? ? ? 內嵌匯編用? asm() 關鍵字來表明括號內寫的是匯編碼,每段匯編碼當用字符串引號來引起來,在編譯時直接套在編譯后的匯編文件中。由于計算機的原因,大多數系統用換行符作為匯編語言一條語句的結束符,而有些系統用分號';'表示,而也有些系統的分號是作為注釋符號。所以每個系統下的匯編語言語法可能并不統一。
? ? ? ? 在上述代碼的匯編代碼塊中,%ebp 表示 ebp寄存器中對應的內存地址,而-12(%ebp)表示ebp寄存器中對應的內存地址再往低地址偏移12個字節。在我的系統中該地址對應的變量是aaa的地址值。上段代碼在我的WINDOWS上可以正常執行而在我的Linux上執行會出現段錯誤,代碼要改成如下方式才有同樣的效果。之所以無法兼容,是因為每個系統編譯出來的堆棧情況可能會不一樣,用的寄存器也不一樣。
#include "iostream"using namespace std; int main(){int aaa = 1 ,bbb=2;__asm__ __volatile__ ("movl $88,-8(%rbp) \n\t""movl $66,-4(%rbp) \n\t");cout<<aaa<<" "<<bbb;return 0; }對應的匯編碼
.....pushq %rbpmovq %rsp, %rbpsubq $16, %rspmovl $1, -8(%rbp)movl $2, -4(%rbp) #APP # 9 "main.cpp" 1movl $88,-8(%rbp)movl $66,-4(%rbp)# 0 "" 2 #NO_APPmovl -8(%rbp), %eaxmovl %eax, %esi.....? ? 可以看出不同系統下的寄存器可能不同,例如fp,bp,ebp,rbp都是不同系統下功能類似的寄存器。也可以看出堆棧情況的表達方式也和WINDOWS下的不一樣了。基本內嵌匯編可以寫在函數體外部,但在不同系統的寄存器,匯編語法,堆棧分配情況不一樣,用相同代碼的兼容性問題就要考慮非常多,并且操作起來也十分的不方便。擴展內嵌匯編有些許的改善,雖然它必須寫在C的函數體中。
二、擴展匯編
asm asm-qualifiers ( AssemblerTemplate : OutputOperands : InputOperands : Clobbers : GotoLabels )asm-qualifiers :? __volatile__修飾詞就不需要多講了,不加volatile 編譯器會幫你優化一些操作,刪除一些沒用的代碼。 goto修飾詞,用來配合內容中的GotoLabel
在小括號內的文本如果發現冒號‘:’則該語句塊視為擴展匯編。內容由5部分組成:
1)用來寫代碼的匯編代碼模板?AssemblerTemplate,
2)替換掉代碼中的占位符的輸出變量OutputOperands,
3)替換掉代碼中的占位符的輸入變量 InputOperands ,
4)排除掉InputOperands 和OutputOperands 中自動匹配寄存器規則中的寄存器集合。
5)GotoLabels,替換掉代碼中的跳轉標簽,通常用“ %l ” 開頭
看看下述代碼:
int test(){ cout<<" test call \t";return 20;} int main(){int aaa = 1 ,bbb=2;__asm__ __volatile__ ("popl %%eax \n\t""pushl %[asmlabel] \n\t""call *%2 \n\t""movl %1 ,%0\n\t":"=rm" (aaa):"r" (bbb),"b"(test),[asmlabel]"A"(2):"%eax");cout<<aaa<<" "<<bbb;return 0; }1)占位符和操作數變量:
擴展匯編從輸出操作數,輸入操作數,gotolabel中去替代asm依次代碼中的占位符,從%0開始。?"=r" (aaa) 替代了 %0 ,? "r"(bbb)替代了%1? ,? “b”(test)替代了 %2 , “A”(2)替代了%3...依次類推。用占位符實現代碼塊外部向asm內部傳遞值。如果覺得數數字麻煩,可以給操作數命名一個變量,用%[操作數變量名] 來代替占位符中的部分。
2)輸出操作數:
格式為 [操作數變量] “約束”(變量值)。操作數變量,這個可缺省。何為約束?由于匯編命令通常需要用寄存器或者內存地址來進行運算,而不是變量名。所以當C程序向asm內部傳遞數值時,需要指明該變量用哪些寄存器或者內存去存放這個值。其中 “r”代表寄存器的泛型,“m”代表內存的泛型, “rm”就是從寄存器或者內存中挑出任意一個來存儲變量值。輸出操作數的約束必須要有前綴,前綴有兩種,“=”代表asm的操作會覆蓋原先的變量值(相當于引用傳遞),“+”單純的讀寫寄存器或內存,不去主動覆蓋原來的變量值。當然,原先變量存在寄存器中,結果asm把該寄存器的原數據丟失則另說。
3)輸入操作數:
格式為 [操作數變量] “約束”(表達式)。操作數變量稍后再講,這個可缺省。它沒有"+"或者“=”這樣的前綴。只負責傳值。
4)Clobbers:
在輸出/輸入操作數中如果存在泛型,則Clobbers的作用就是編譯器在挑選具體的寄存器時,將Clobbers中的寄存器列表排除在挑選規則之外。例如上述程序指定了“%eax” 那么, 在輸出/輸入操作數 中的 "r" 就不會去選擇 eax寄存器了。
5)GotoLabel:
?由于ASM語句塊中看不見其他塊中的Label標簽,標簽名可能會在編譯中發生變化。所以直接在基礎匯編代碼中直接寫標簽名會造成標簽名不一致情況。gcc可以對列出的標簽集合,實現C程序和Asm之間的跳轉。
int main(){int aaa = 1 ,bbb=2;__asm__ __volatile__ goto("cmp %1,%0 \n\t""jne %l2": //goto不可以由輸出操作數:"r"(aaa),"r"(bbb):"cc":Lable);cout<<"adadadada\n";Lable:cout<<aaa<<" "<<bbb;return 0; }?
三、擴展匯編的轉義
? ? ?由于%被當成占位符去用了。如果想直接引用寄存器,那么需要輸出兩個“%”
| 擴展匯編符 | 對應的基本匯編符 |
| %% | % |
| %= | = |
| %{ | { |
| %|? | | |
| %} | } |
四、多匯編方言模板
之前說過,因為每個機器上的匯編語法可能會不一樣。所以可以提供多種模板給編譯器挑選。模板的非共同部分用花括號括起來{} ,每個模板直接用豎線"|"分割。? ?例如
?// 等價的intel 寫法__asm__ __volatile__ (...."bt %[Base],%[Offset] \n\t"...);//att寫法__asm__ __volatile__ (...."btl %[Offset], %[Base] \n\t"...);//合并起來就是__asm__ __volatile__ (...."bt {l %[Offset],%[Base] | %[Base],%[Offset]} \n\t"... );五、常用的操作數類型
?
| 代碼 | 操作數 |
| m | 任意內存 |
| r | 任意寄存器 |
| i | 整數立即數 |
| E | float立即數 |
| o | 可偏移的內存地址,通常與'<'或'>' 搭配,m的子集 |
| V | 不可偏移的內存地址,即偏移地址后訪問不合法的內存地址。m的子集 |
| g | 常用寄存器,內存,整數立即數 |
| X | 任何操作數 |
?
六、給C代碼命名一個asm匯編變量名
? ? ?命名方式是在聲明后 加上 asm("asm變量名")的形式進行命名。而命名必須是全局或者靜態的.聲明完后,就可以asm代碼中直接訪問變量名。
#include "iostream" using namespace std; int test()asm("test"); int test() { cout<<" test call \t";return 20;}int main(){static int aaa asm("myvar") = 1;int bbb=2;__asm__ __volatile__ ("call test \n\t" "movl %1 ,%0 \n\t" //movl $2, %r"add $10,%0 \n\t" //add $10,%r ,此時寄存器值為12"movl %0,myvar\n\t" // 此時 myvar 的內存值是12"add myvar,%0" // add myvar,%0 , 此時寄存器值是24,內存值是12:"=r" (aaa) //執行完模板后,%0對應的寄存器替換成aaa,值為24:"r" (bbb),"r0"(test),[asmlabel]"A"(3):"%eax");cout<<aaa<<" "<<bbb; //24 2return 0; }?
總結
以上是生活随笔為你收集整理的Linux C: 内嵌汇编语法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 操作系统原理:进程间通信 IPC
- 下一篇: Linux C : 进程管理实验:创建进