日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

C语言内联汇编

發布時間:2024/8/1 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C语言内联汇编 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在閱讀linux 源碼的時候,我們會看到很多C語言內聯匯編的代碼。下面我們集中看看C語言內聯匯編是怎么樣的。

首先,我們得想想為什么會有在C語言里面內聯匯編的需求。
主要有兩個,一個是我們覺得在被頻繁調用的函數,如果使用C寫出來的代碼,可能執行效率達不到我們的預期,于是我們就使用匯編語言來把這個函數的邏輯實現出來,例如qsort函數;
另一個是我們需要使用某些只能通過匯編指令才能實現的功能。可能有人會問,還有C語言無法實現的功能?這還真的有,例如開中斷和關中斷

#define sti() __asm__ ("sti"::) #define cli() __asm__ ("cli"::)

顯然就只能使用匯編指令來開中斷和關中斷了。

現在我們來看看內聯匯編的一些規則。
內聯匯編的一般格式如下:

asm [volatile] ( AssemblerTemplate : [部分1OutputOperands] [ : 部分2InputOperands[ : 部分3Clobbers ] ]);

首先! 內聯匯編是一個statement ,也就是一條語句! 因此,一條內聯匯編代碼后邊,需要跟著一個分號。

asm是關鍵字,告訴編譯器之后緊挨著的第一個小括號內部的就是內聯的匯編代碼,一般也可以把asm寫作__asm__。
volatile 也是關鍵字,如果寫上volatile表示關閉編譯器對這段匯編代碼的優化。我們看看gcc官網對 volatile 的解釋:

GCC’s optimizers sometimes discard asm statements if they determine there is no need for the output variables. Also, the optimizers may move code out of loops if they believe that the code will always return the same result (i.e. none of its input values change between calls). Using the volatile qualifier disables these optimizations. asm statements that have no output operands, including asm goto statements, are implicitly volatile.

也就是說,如何gcc編譯器發現一段內聯匯編代碼的輸出不被使用到,或者它發現在一個循環里面這段代碼一直返回同一個值,那么它就會把這段內聯匯編代碼直接discard. 顯然,對于用一個問題,有千千萬萬種寫法,編譯器只能做一些淺層的優化。當我們的代碼寫的比較復雜時,它將對我們的代碼進行錯誤的優化,這是我們不想看到的,因此一般我們會加上這個volatile參數。

接下來是一個 AssemblerTemplate ,這個匯編模板要求是一個包含匯編指令的字符串,里面可以含有一些指向輸入、輸出操作數的占位符。gcc編譯器通過一定的規則,將模板里面所有的占位符替換掉,并將替換后的結果輸入到匯編器中。

因為匯編模板要求是一個字符串,那么如果我們有多條匯編指令,那怎么辦呢?一個方法是將所有的匯編指令寫在一行,這種方式當然可以,但是代碼不美觀。另外一種方法是利用C語言里面相鄰字符串可以直接拼接成一個長的字符串這條規則,我們可以每一行寫一條匯編指令,然后使用\ 將不同行的匯編指令字符串合并起來就可以了。例如一個合法的匯編模板可以是這樣子的:

"mov %0,%1\n\t " \ "mov %1,%2\n\t"

它其實就是等價于:mov %0,%1\n\t mov %1 ,%2
其中以%開始的%1,%2···加做占位符,它由下面的輸入輸出操作數來決定。既然% 開始的都是占位符,那么我想輸出%怎么辦呢?比如我寫了這么一條匯編模板:

"mov %eax,%ebx\n\t"

這是會報錯的,因為gcc把%eax中的eax當做一個占位符了。此時,我需要使用%%來轉義出%符號
ok,到目前為止,我們可能會對%0,%1,%2···產生疑問,這些占位符是如何與具體的某個數據產生關聯的?
此時就需要介紹輸出操作數和輸入操作數了。
輸出操作數緊跟著匯編模板,之間隔著一個:號。
輸出操作數的格式為:

[asmSymbolicName1] constraint (cvariablename1),[asmSymbolicName1] constraint (cvariablename1)···

第一部分[asmSymbolicName1]叫做asm符號別名,就是相當于給后面的C語言變量cvariablename1設置一個匯編里面使用的別名。在匯編指令里面使用%[別名]來訪問這個變量。
這部分可以省略。另外編譯器默認為內聯匯編的每個輸出、輸入操作數設置一個0,1,2,3,4···的數字別名。按各個操作數出現的次序,依次給這些操作數設置對應序號的數字別名。這些數字別名,在匯編模板里面使用%數字來訪問。
舉個例子:

int sum(int a,int b) {int rst = 0; __asm__ volatile("addl %1,%2\n\t"\"addl %3,%2\n\t"\"mov %2,%[rst]\n\t"\"mov %%eax,%2\n\t":[rst]"+r"(rst):"a" (a),"b"(b),"c"(123456):);return rst; }

輸出操作數有:[rst]"+r"(rst),因此,在匯編模板中%[rst]和%0都是與輸出變量rst綁定。
第二部分是一個約束字符串。約束字符串給出了程序員對編譯器在轉換匯編模板時候的一些建議。注意,只是建議。常見約束有https://gcc.gnu.org/onlinedocs/gcc/Simple-Constraints.html#Simple-Constraints
最后一個(cvariablename1) 通過一個括號將所指向的C語言變量指示出來。
整個輸出操作數的描述可以沒有。
下一部分的輸入操作數的原理同輸出部分。
最后一個所謂的破壞域聲明,這個一般可不填,也只是給編譯器提供的建議而已。

實例分析

#define _set_gate(gate_addr,type,dpl,addr) \ __asm__ ("movw %%dx,%%ax\n\t" \"movw %0,%%dx\n\t" \"movl %%eax,%1\n\t" \"movl %%edx,%2" \: \: "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \"o" (*((char *) (gate_addr))), \"o" (*(4+(char *) (gate_addr))), \"d" ((char *) (addr)),"a" (0x00080000))

輸入操作數描述部分:

:"i" ((short) (0x8000+(dpl<<13)+(type<<8))), \"o" (*((char *) (gate_addr))), \"o" (*(4+(char *) (gate_addr))), \"d" ((char *) (addr)),"a" (0x00080000))

說明:%0 指向表達式((short) (0x8000+(dpl<<13)+(type<<8))),其約束i表明它是一個立即數;
%1指向表達式*((char *) (gate_addr)),其約束o表示這個表達式的值是一個內存地址===>就是會翻譯成一個地址而不是立即數$xxxxxx。
%2指向表達式*(4+(char *) (gate_addr)),其約束o表示這個表達式的值也是一個內存地址。
%3指向表達式((char *) (addr)),其約束d表示匯編指令的在執行前先將(char*)(addr)的值給edx;
%4指向表達式(0x00080000)), 其約束a表示匯編指令在執行前會先將(4+(char*)(addr))的值給eax;

注意:在AT&T匯編中,立即數和直接內存訪問時不同的。
例如:
head.S中

movl $0x10,%eax # reload all the segment registersmov %ax,%ds # after changing gdt. CS was alreadymov %ax,%es # reloaded in 'setup_gdt'mov %ax,%fsmov %ax,%gslss stack_start,%espxorl %eax,%eax 1: incl %eax # check that A20 really IS enabledmovl %eax,0x000000 # loop forever if it isn't

movl $0x10,%eax中的0x10是立即數,因為前面有個$
而movl %eax,0x000000中的0x0000000卻是內存偏移量,表示 ds指示的段基址+0x0000000 所指向的內存區域。

總結

以上是生活随笔為你收集整理的C语言内联汇编的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。