GCC如何编译内嵌汇编代码
生活随笔
收集整理的這篇文章主要介紹了
GCC如何编译内嵌汇编代码
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
內(nèi)核代碼絕大部分使用C?
語言編寫,只有一小部分使用匯編語言編寫,例如與特定體系結(jié)構(gòu)相關(guān)的代碼和對性能影響很大的代碼。GCC提供了內(nèi)嵌匯編的功能,可以在C代碼中直接內(nèi)嵌匯編語言語句,大大方便了程序設(shè)計。?
簡單的內(nèi)嵌匯編很容易理解?
例:?
__asm__?
__volatile__("hlt");?
“__asm__”表示后面的代碼為內(nèi)嵌匯編,“asm”是“__asm__”的別名。?
“__volatile__”表示編譯器不要優(yōu)化代碼,后面的指令保留原樣,?
“volatile”是它的別名。括號里面是匯編指令。?
2.2 內(nèi)嵌匯編舉例在內(nèi)嵌匯編中,可以將C?
語言表達(dá)式指定為匯編指令的操作數(shù),而且不用去管如何將C?
語言表達(dá)式的值讀入哪個寄存器,以及如何將計算結(jié)果寫回C?
變量,你只要告訴程序中C語言表達(dá)式與匯編指令操作數(shù)之間的對應(yīng)關(guān)系即可, GCC?
會自動插入代碼完成必要的操作。?
使用內(nèi)嵌匯編,要先編寫匯編指令模板,然后將C語言表達(dá)式與指令的操作數(shù)相關(guān)聯(lián),并告訴?
GCC對這些操作有哪些限制條件。例如在下面的匯編語句:?
__asm__ __violate__?
("movl %1,%0" : "=r" (result) : "m" (input));?
“movl %1,%0”是指令模板;“%0”和“%1”代表指令的操作數(shù),稱為占位符,內(nèi)嵌匯編靠它們將C?
語言表達(dá)式與指令操作數(shù)相對應(yīng)。指令模板后面用小括號括起來的是C?
語言表達(dá)式,本例中只有兩個:“result”和“input”,他們按照出現(xiàn)的順序分別與指令操作?
數(shù)“%0”,“%1,”對應(yīng);注意對應(yīng)順序:第一個C表達(dá)式對應(yīng)“%0”;第二個表達(dá)式對應(yīng)“%1?
”,依次類推,操作數(shù)至多有10個,分別用“%0”,“%1”….“%9,”表示。在每個操作數(shù)前?
面有一個用引號括起來的字符串,字符串的內(nèi)容是對該操作數(shù)的限制或者說要求。“result”前面?
的限制字符串是“=r”,其中“=”表示“result”是輸出操作數(shù),“r?
”表示需要將“result”與某個通用寄存器相關(guān)聯(lián),先將操作數(shù)的值讀入寄存器,然后?
在指令中使用相應(yīng)寄存器,而不是“result”本身,當(dāng)然指令執(zhí)行完后需要將寄存器中的值?
存入變量“result”,從表面上看好像是指令直接對“result”進(jìn)行操作,實際上GCC?
做了隱式處理,這樣我們可以少寫一些指令。“input”前面的“r”表示該表達(dá)式需要先放入?
某個寄存器,然后在指令中使用該寄存器參加運(yùn)算。?
我們將上面的內(nèi)嵌代碼放到一個C源文件中,然后使用gcc –c–S得到該C?
文件源代碼相對應(yīng)的匯編代碼,然后查看一下匯編代碼,看看GCC是如何處理的。?
C源文件如下內(nèi)容如下,注意該代碼沒有實際意義,僅僅作為例子。?
extern int?
input,result;?
void test(void)?
{?
input?
= 1;?
__asm__ __volatile__ ("movl %1,%0" :?
"=r" (result) : "r" (input));?
return?
;?
}?
對應(yīng)的匯編代碼如下;?
行號 代碼 解釋?
1?
7?
8 movl $1, input 對應(yīng)C語言語句input = 1;?
9 input, %eax?
10 #APP GCC插入的注釋,表示內(nèi)嵌匯編開始?
11 movl %eax,%eax 我們的內(nèi)嵌匯編語句?
12 #NO_APP GCC 插入的注釋,表示內(nèi)嵌匯編結(jié)束?
13 movl %eax, result 將結(jié)果存入result變量?
14?
-?
18?
。。。。。。?
從匯編代碼可以看出,第9行和第13行是GCC,自動增加的代碼,GCC?
根據(jù)限定字符串決定如何處理C表達(dá)式,本例兩個表達(dá)式都被指定為“r”型,所以先使用指令:?
movl input, %eax?
將input讀入寄存器%eax;GCC,也指定一個寄存器與輸出變量result?
相關(guān),本例也是%eax,等得到操作結(jié)果后再使用指令:?
movl %eax, result?
將寄存器的值寫回C變量result中。從上面的匯編代碼我們可以看出與result?
和input,相關(guān)連的寄存器都是%eax,GCC使用%eax,替換內(nèi)嵌匯編指令模板中的?
%0,%1?
movl %eax,%eax?
顯然這一句可以不要。但是沒有優(yōu)化,所以這一句沒有被去掉。?
由此可見,C表達(dá)式或者變量與寄存器的關(guān)系由GCC自動處理,我們只需使用限制字符串指導(dǎo)GCC?
如何處理即可。限制字符必須與指令對操作數(shù)的要求相匹配,否則產(chǎn)生的匯編代碼?
將會有錯,讀者可以將上例中的兩個“r”,都改為“m”(m,表示操作數(shù)放在內(nèi)存,而不是寄?
存器中),編譯后得到的結(jié)果是:?
movl input, result?
很明顯這是一條非法指令,因此限制字符串必須與指令對操作數(shù)的要求匹配。例如指令movl?
允許寄存器到寄存器,立即數(shù)到寄存器等,但是不允許內(nèi)存到內(nèi)存的操作,因此兩個操作數(shù)?
不能同時使用“m”作為限定字符。?
2.3 語法?
內(nèi)嵌匯編語法如下:?
__asm__(?
匯編語句模板:?
輸出部分:?
輸入部分:?
破壞描述部分)?
共四個部分:匯編語句模板,輸出部分,輸入部分,破壞描述部分,各部分使用“:”格?
開,匯編語句模板必不可少,其他三部分可選,如果使用了后面的部分,而前面部分為空,?
也需要用“:”格開,相應(yīng)部分內(nèi)容為空。例如:?
__asm__ __volatile__(?
"cli":?
:?
:"memory")?
2.3.1 匯編語句模板?
匯編語句模板由匯編語句序列組成,語句之間使用“;”、“\n”或“\n\t”分開。?
指令中的操作數(shù)可以使用占位符引用C語言變量,操作數(shù)占位符最多10個,名稱如下:%0,%1…,%9。?
指令中使用占位符表示的操作數(shù),總被視為long型(4,個字節(jié)),但對其施加的操作?
根據(jù)指令可以是字或者字節(jié),當(dāng)把操作數(shù)當(dāng)作字或者字節(jié)使用時,默認(rèn)為低字或者低字節(jié)。?
對字節(jié)操作可以顯式的指明是低字節(jié)還是次字節(jié)。方法是在%和序號之間插入一個字母,?
“b”代表低字節(jié),“h”代表高字節(jié),例如:%h1。?
2.3.2 輸出部分?
輸出部分描述輸出操作數(shù),不同的操作數(shù)描述符之間用逗號格開,每個操作數(shù)描述符由限定字符串和?
C語言變量組成。每個輸出操作數(shù)的限定字符串必須包含“=”表示他是一個輸出操作數(shù)。?
例:?
__asm__ __volatile__("pushfl ; popl %0 ; cli":"=g" (x) )?
描述符字符串表示對該變量的限制條件,這樣GCC就可以根據(jù)這些條件決定如何?
分配寄存器,如何產(chǎn)生必要的代碼處理指令操作數(shù)與C表達(dá)式或C變量之間的聯(lián)系。?
2.3.3 輸入部分?
輸入部分描述輸入操作數(shù),不同的操作數(shù)描述符之間使用逗號格開,每個操作數(shù)描述符由?
限定字符串和C語言表達(dá)式或者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位設(shè)為1。第一個占位符%0與C,語言變量ADDR?
對應(yīng),第二個占位符%1與C,語言變量nr對應(yīng)。因此上面的匯編語句代碼與下面的偽代碼等價:?
btsl nr, ADDR,該指令的兩個操作數(shù)不能全是內(nèi)存變量,因此將nr的限定字符串指定為“Ir”,?
將nr,與立即數(shù)或者寄存器相關(guān)聯(lián),這樣兩個操作數(shù)中只有ADDR為內(nèi)存變量。?
2.3.4 限制字符?
2.3.4.1 限制字符列表?
限制字符有很多種,有些是與特定體系結(jié)構(gòu)相關(guān),此處僅列出常用的限定字符和i386?
中可能用到的一些常用的限定符。它們的作用是指示編譯器如何處理其后的C?
語言變量與指令操作數(shù)之間的關(guān)系,例如是將變量放在寄存器中還是放在內(nèi)存中等,?
下表列出了常用的限定字母。?
分類?
限定符 描述 通用寄存器?
“a”將輸入變量放入eax?
這里有一個問題:假設(shè)eax已經(jīng)被使用,那怎么辦??
其實很簡單:因為GCC知道eax已經(jīng)被使用,它在這段匯編代碼的起始處插入一條?
語句pushl %eax,將eax內(nèi)容保存到堆棧,然后在這段代碼結(jié)束處再增加一條?
語句popl %eax,恢復(fù)eax的內(nèi)容?
“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位的寄存器(uselong longs)?
“m”內(nèi)存變量?
“o”操作數(shù)為內(nèi)存變量,但是其尋址方式是偏移量類型,也即是基址尋址,或者是基址加變址尋址?
“V”操作數(shù)為內(nèi)存變量,但尋址方式不是偏移量類型?
“,” 操作數(shù)為內(nèi)存變量,但尋址方式為自動增量?
“p”操作數(shù)是一個合法的內(nèi)存地址(指針)?
寄存器或內(nèi)存?
“g” 將輸入變量放入eax,ebx,ecx ,edx中的一個或者作為內(nèi)存變量?
“X”操作數(shù)可以是任何類型?
立即數(shù)?
“I” 0-31 之間的立即數(shù)(用于32位移位指令)?
“J” 0-63 之間的立即數(shù)(用于64 位移位指令)?
“N” 0-255 ,之間的立即數(shù)(用于out 指令)?
“i” 立即數(shù)?
“n” 立即數(shù),有些系統(tǒng)不支持除字以外的立即數(shù),這些系統(tǒng)應(yīng)該使用“n”而不是“i”?
匹配?
“0”,“1 ,”... “9 ”?
表示用它限制的操作數(shù)與某個指定的操作數(shù)匹配,也即該操作數(shù)就是指定的那個操作數(shù),?
例如用“0 ”去描述“%1”操作數(shù),那么“%1”引用的其實就是“%0”操作數(shù),注意作為?
限定符字母的0-9 ,與指令中的“%0”-“%9”的區(qū)別,前者描述操作數(shù),后者代表操作數(shù)。?
后面有詳細(xì)描述 & 該輸出操作數(shù)不能使用過和輸入操作數(shù)相同的寄存器?
后面有詳細(xì)描述?
操作數(shù)類型?
“=” 操作數(shù)在指令中是只寫的(輸出操作數(shù))?
“+” 操作數(shù)在指令中是讀寫類型的(輸入輸出操作數(shù))?
浮點(diǎn)數(shù)?
“f”?
浮點(diǎn)寄存器?
“t”第一個浮點(diǎn)寄存器?
“u”第二個浮點(diǎn)寄存器?
“G”標(biāo)準(zhǔn)的80387?
浮點(diǎn)常數(shù)?
% 該操作數(shù)可以和下一個操作數(shù)交換位置?
例如addl的兩個操作數(shù)可以交換順序(當(dāng)然兩個操作數(shù)都不能是立即數(shù))?
# 部分注釋,從該字符到其后的逗號之間所有字母被忽略?
* 表示如果選用寄存器,則其后的字母被忽略?
現(xiàn)在繼續(xù)看上面的例子,?
"=m" (ADDR)表示ADDR為內(nèi)存變量(“m”),而且是輸出變量(“=”);"Ir" (nr)表示nr,為?
0-31之間的立即數(shù)(“I”)或者一個寄存器操作數(shù)(“r”)。?
2.3.4.2?
匹配限制符?
I386?
指令集中許多指令的操作數(shù)是讀寫型的(讀寫型操作數(shù)指先讀取原來的值然后參加運(yùn)算,最后?
將結(jié)果寫回操作數(shù)),例如addl %1,%0,它的作用是將操作數(shù)%0與操作數(shù)%1的和存入操作數(shù)%0,?
因此操作數(shù)%0是讀寫型操作數(shù)。老版本的GCC對這種類型操作數(shù)的支持不是很好,它將操作數(shù)嚴(yán)格?
分為輸入和輸出兩種,分別放在輸入部分和輸出部分,而沒有一個單獨(dú)部分描述讀寫型操作數(shù),?
因此在GCC中讀寫型的操作數(shù)需要在輸入和輸出部分分別描述,靠匹配限制符將兩者關(guān)聯(lián)到一起?
注意僅在輸入和輸出部分使用相同的C變量,但是不用匹配限制符,產(chǎn)生的代碼很可能不對,后?
面會分析原因。?
匹配限制符是一位數(shù)字:“0”、“1”……“9,”,分別表示它限制的C表達(dá)式分別與?
占位符%0,%1,……%9對應(yīng)的C變量匹配。例如使用“0”作為%1,的限制字符,那么?
%0和%1表示同一個C,變量。?
看一下下面的代碼就知道為什么要將讀寫型操作數(shù),分別在輸入和輸出部分加以描述。?
該例功能是求input+result的和,然后存入result:?
extern int input,result;?
void test_at_t()?
{?
result= 0;?
input = 1;?
__asm__?
__volatile__ ("addl %1,%0":"=r"(result): "r"(input));?
}?
對應(yīng)的匯編代碼為:?
movl $0,_result?
movl $1,_input?
movl _input,%edx /APP?
addl %edx,%eax /NO_APP?
movl %eax,%edx?
movl %edx,_result?
input 為輸入型變量,而且需要放在寄存器中,GCC給它分配的寄存器是%edx,在執(zhí)行addl之前%edx,?
的內(nèi)容已經(jīng)是input的值。可見對于使用“r”限制的輸入型變量或者表達(dá)式,在使用之前GCC會插入?
必要的代碼將他們的值讀到寄存器;“m”型變量則不需要這一步。讀入input后執(zhí)行addl,顯然%eax?
的值不對,需要先讀入result的值才行。再往后看:movl %eax,%edx和movl %edx,_result?
的作用是將結(jié)果存回result,分配給result的寄存器與分配給input的一樣,都是%edx。?
綜上可以總結(jié)出如下幾點(diǎn):?
1. 使用“r”限制的輸入變量,GCC先分配一個寄存器,然后將值讀入寄存器,最后?
用該寄存器替換占位符;?
2. 使用“r”限制的輸出變量,GCC會分配一個寄存器,然后用該寄存器替換占位符,?
但是在使用該寄存器之前并不將變量值先讀入寄存器,GCC認(rèn)為所有輸出變量以前的?
值都沒有用處,不讀入寄存器(可能是因為AT&T匯編源于CISC架構(gòu)處理器的匯編語言?
,在CISC處理器中大部分指令的輸入輸出明顯分開,而不像RISC那樣一個操作數(shù)既?
做輸入又做輸出,例如add r0,r1,r2,r0,和r1是輸入,r2是輸出,輸入和輸出分開,?
沒有使用輸入輸出型操作數(shù),這樣我們就可以認(rèn)為r2對應(yīng)的操作數(shù)原來的值沒有用處,?
也就沒有必要先將操作數(shù)的值讀入r2,因為這是浪費(fèi)處理器的CPU周期),最后GCC插入代碼,?
將寄存器的值寫回變量;?
3. 輸入變量使用的寄存器在最后一處使用它的指令之后,就可以挪做其他用處,因為?
已經(jīng)不再使用。例如上例中的%edx。在執(zhí)行完addl之后就作為與result對應(yīng)的寄存器。?
因為第二條,上面的內(nèi)嵌匯編指令不能奏效,因此需要在執(zhí)行addl之前把result的值讀入?
寄存器,也許再將result放入輸入部分就可以了(因為第一條會保證將result?
先讀入寄存器)。修改后的指令如下(為了更容易說明問題將input限制符由“r,”改為“m”):?
extern int input,result;?
void test_at_t()?
{?
result = 0;?
input = 1;?
__asm__?
__volatile__ ("addl %2,%0":"=r"(result):"r"(result),"m"(input));?
}?
看上去上面的代碼可以正常工作,因為我們知道%0和%1都和result相關(guān),應(yīng)該使用同一個?
寄存器,但是GCC并不去判斷%0和%1,是否和同一個C表達(dá)式或變量相關(guān)聯(lián)(這樣易于產(chǎn)生與?
內(nèi)嵌匯編相應(yīng)的匯編代碼),因此%0和%1使用的寄存器可能不同。我們看一下匯編代碼就知道了。?
movl $0,_result?
movl $1,_input?
movl _result,%edx /APP?
addl _input,%eax /NO_APP?
movl %eax,%edx?
movl %edx,_result?
現(xiàn)在在執(zhí)行addl之前將result的值被讀入了寄存器%edx,但是addl指令的操作數(shù)%0?
卻成了%eax,而不是%edx,與預(yù)料的不同,這是因為GCC給輸出和輸入部分的變量分配了不同?
的寄存器,GCC沒有去判斷兩者是否都與result相關(guān),后面會講GCC如何翻譯內(nèi)嵌匯編,看完之后?
就不會驚奇啦。?
使用匹配限制符后,GCC知道應(yīng)將對應(yīng)的操作數(shù)放在同一個位置(同一個寄存器或者同一個?
內(nèi)存變量)。使用匹配限制字符的代碼如下:?
extern int input,result;?
void test_at_t()?
{?
result = 0;?
input = 1;?
__asm__?
__volatile__ ("addl %2,%0":"=r"(result):"0"(result),"m"(input));?
}?
輸入部分中的result用匹配限制符“0”限制,表示%1與%0,代表同一個變量,?
輸入部分說明該變量的輸入功能,輸出部分說明該變量的輸出功能,兩者結(jié)合表示result?
是讀寫型。因為%0和%1,表示同一個C變量,所以放在相同的位置,無論是寄存器還是內(nèi)存。?
相應(yīng)的匯編代碼為:?
movl $0,_result?
movl $1,_input?
movl _result,%edx?
movl %edx,%eax /APP?
addl _input,%eax /NO_APP?
movl %eax,%edx?
movl %edx,_result?
可以看到與result相關(guān)的寄存器是%edx,在執(zhí)行指令addl之前先從%edx將result讀入%eax,?
執(zhí)行之后需要將結(jié)果從%eax讀入%edx,最后存入result中。這里我們可以看出GCC?
處理內(nèi)嵌匯編中輸出操作數(shù)的一點(diǎn)點(diǎn)信息:addl并沒有使用%edx,可見它不是簡單的用result?
對應(yīng)的寄存器%edx去替換%0,而是先分配一個寄存器,執(zhí)行運(yùn)算,最后才將運(yùn)算結(jié)果存入?
對應(yīng)的變量,因此GCC是先看該占位符對應(yīng)的變量的限制符,發(fā)現(xiàn)是一個輸出型寄存器變量,?
就為它分配一個寄存器,此時沒有去管對應(yīng)的C變量,最后GCC,知道還要將寄存器的值寫回變量,?
與此同時,它發(fā)現(xiàn)該變量與%edx關(guān)聯(lián),因此先存入%edx,再存入變量。?
至此讀者應(yīng)該明白了匹配限制符的意義和用法。在新版本的GCC中增加了一個限制字符“+”,?
它表示操作數(shù)是讀寫型的,GCC知道應(yīng)將變量值先讀入寄存器,然后計算,最后寫回變量,而?
無需在輸入部分再去描述該變量。?
例;?
extern int input,result;?
void test_at_t()?
{?
result = 0;?
input = 1;?
__asm__?
__volatile__ ("addl %1,%0":"+r"(result):"m"(input));?
}?
此處用“+”替換了“=”,而且去掉了輸入部分關(guān)于result的描述,產(chǎn)生的匯編代碼如下:?
movl $0,_result?
movl $1,_input?
movl _result,%eax /APP?
addl _input,%eax /NO_APP?
movl %eax,_result?
L2:?
movl %ebp,%esp?
處理的比使用匹配限制符的情況還要好,省去了好幾條匯編代碼。?
2.3.4.3 “&”限制符?
限制符“&”在內(nèi)核中使用的比較多,它表示輸入和輸出操作數(shù)不能使用相同的寄存器,?
這樣可以避免很多錯誤。?
舉一個例子,下面代碼的作用是將函數(shù)foo的返回值存入變量ret中:?
__asm__ ( “call foo;movl %%edx,%1”, :”=a”(ret) : ”r”(bar) );?
我們知道函數(shù)的int型返回值存放在%eax中,但是gcc編譯的結(jié)果是輸入和輸出同時使用了?
寄存器%eax,如下:?
movl bar, %eax?
#APP?
call foo?
movl %ebx,%eax?
#NO_APP?
movl %eax, ret?
結(jié)果顯然不對,原因是GCC并不知道%eax中的值是我們所要的。避免這種情況的方法是使用“&”?
限定符,這樣bar就不會再使用%eax寄存器,因為已被ret指定使用。?
_asm__ ( “call foo;movl %%edx,%1”,:”=&a”(ret) : ”r”(bar) );?
2.3.5 破壞描述部分?
2.3.5.1 寄存器破壞描述符?
通常編寫程序只使用一種語言:高級語言或者匯編語言。高級語言編譯的步驟大致如下:?
l?
預(yù)處理;?
l?
編譯?
l?
匯編?
l?
鏈接?
我們這里只關(guān)心第二步編譯(將C代碼轉(zhuǎn)換成匯編代碼):因為所有的代碼都是用高級語言編寫,?
編譯器可以識別各種語句的作用,在轉(zhuǎn)換的過程中所有的寄存器都由編譯器決定如何分配使用,?
它有能力保證寄存器的使用不會沖突;也可以利用寄存器作為變量的緩沖區(qū),因為寄存器的訪問?
速度比內(nèi)存快很多倍。如果全部使用匯編語言則由程序員去控制寄存器的使用,只能靠程序員去?
保證寄存器使用的正確性。但是如果兩種語言混用情況就變復(fù)雜了,因為內(nèi)嵌的匯編代碼可以直接?
使用寄存器,而編譯器在轉(zhuǎn)換的時候并不去檢查內(nèi)嵌的匯編代碼使用了哪些寄存器(因為很難檢測?
匯編指令使用了哪些寄存器,例如有些指令隱式修改寄存器,有時內(nèi)嵌的匯編代碼會調(diào)用其他子過程,?
而子過程也會修改寄存器),因此需要一種機(jī)制通知編譯器我們使用了哪些寄存器(程序員自己知道?
內(nèi)嵌匯編代碼中使用了哪些寄存器),否則對這些寄存器的使用就有可能導(dǎo)致錯誤,修改描述部分?
可以起到這種作用。當(dāng)然內(nèi)嵌匯編的輸入輸出部分指明的寄存器或者指定為“r”,“g”型由編譯器?
去分配的寄存器就不需要在破壞描述部分去描述,因為編譯器已經(jīng)知道了。?
破壞描述符由逗號格開的字符串組成,每個字符串描述一種情況,一般是寄存器名;除寄存器外?
還有“memory”。例如:“%eax”,“%ebx”,“memory”等。?
下面看個例子就很清楚為什么需要通知GCC內(nèi)嵌匯編代碼中隱式(稱它為隱式是因為GCC并不知道)?
使用的寄存器。?
在內(nèi)嵌的匯編指令中可能會直接引用某些寄存器,我們已經(jīng)知道AT&T格式的匯編語言中,寄存器?
名以“%”作為前綴,為了在生成的匯編程序中保留這個“%”號,在asm語句中對寄存器的?
引用必須用“%%”作為寄存器名稱的前綴。原因是“%”在asm,內(nèi)嵌匯編語句中的作用與“\”在C?
語言中的作用相同,因此“%%”轉(zhuǎn)換后代表“%”。?
例(沒有使用修改描述符):?
int main(void)?
{?
int input, output,temp;?
input = 1;?
__asm__ __volatile__ ("movl $0, %%eax;\n\t?
movl %%eax, %1;\n\t?
movl %2, %%eax;\n\t?
movl %%eax, %0;\n\t"?
:"=m"(output),"=m"(temp) /* output */?
:"r"(input) /* input */?
);?
return 0;?
}?
這段代碼使用%eax作為臨時寄存器,功能相當(dāng)于C代碼:“temp = 0;output=input”,?
對應(yīng)的匯編代碼如下:?
movl $1,-4(%ebp)?
movl -4(%ebp),%eax /APP?
movl $0, %eax;?
movl %eax, -12(%ebp);?
movl %eax, %eax;?
movl %eax, -8(%ebp); /NO_APP?
顯然GCC給input分配的寄存器也是%eax,發(fā)生了沖突,output的值始終為0,而不是input。?
使用破壞描述后的代碼:?
int main(void)?
{?
int input, output,temp;?
input = 1;?
__asm__ __volatile__?
( "movl $0, %%eax;\n\t?
movl %%eax, %1;\n\t?
movl %2, %%eax;\n\t?
movl %%eax, %0;\n\t"?
:"=m"(output),"=m"(temp) /* output */?
:"r"(input) /* input */?
:"eax"); /* 描述符 */?
return 0;?
}?
對應(yīng)的匯編代碼:?
movl $1,-4(%ebp)?
movl -4(%ebp),%edx /APP?
movl $0, %eax;?
movl %eax, -12(%ebp);?
movl %edx, %eax;?
movl %eax, -8(%ebp); /NO_APP?
通過破壞描述部分,GCC得知%eax已被使用,因此給input分配了%edx。在使用內(nèi)嵌匯編時請記?
住一點(diǎn):盡量告訴GCC盡可能多的信息,以防出錯。?
如果你使用的指令會改變CPU的條件寄存器cc,需要在修改描述部分增加“cc”。?
2.3.5.2 memory破壞描述符?
“memory”比較特殊,可能是內(nèi)嵌匯編中最難懂部分。為解釋清楚它,先介紹一下編譯器的?
優(yōu)化知識,再看C關(guān)鍵字volatile。最后去看該描述符。?
2.3.5.2.1 編譯器優(yōu)化介紹?
內(nèi)存訪問速度遠(yuǎn)不及CPU處理速度,為提高機(jī)器整體性能,在硬件上引入硬件高速緩存Cache,?
加速對內(nèi)存的訪問。另外在現(xiàn)代CPU中指令的執(zhí)行并不一定嚴(yán)格按照順序執(zhí)行,沒有相關(guān)性?
的指令可以亂序執(zhí)行,以充分利用CPU的指令流水線,提高執(zhí)行速度。以上是硬件級別的優(yōu)化。?
再看軟件一級的優(yōu)化:一種是在編寫代碼時由程序員優(yōu)化,另一種是由編譯器進(jìn)行優(yōu)化。編譯器?
優(yōu)化常用的方法有:將內(nèi)存變量緩存到寄存器;調(diào)整指令順序充分利用CPU指令流水線,常見的?
是重新排序讀寫指令。?
對常規(guī)內(nèi)存進(jìn)行優(yōu)化的時候,這些優(yōu)化是透明的,而且效率很好。由編譯器優(yōu)化或者硬件重新排序引起的問題的解決辦法是在從硬件(或者其他處理器)的角度看必須以特定順序執(zhí)行的操作之間設(shè)置內(nèi)存屏障(memory barrier),linux提供了一個宏解決編譯器的執(zhí)行順序問題。?
void Barrier(void)?
這個函數(shù)通知編譯器插入一個內(nèi)存屏障,但對硬件無效,編譯后的代碼會把當(dāng)前CPU?
寄存器中的所有修改過的數(shù)值存入內(nèi)存,需要這些數(shù)據(jù)的時候再重新從內(nèi)存中讀出。?
2.3.5.2.2 C 語言關(guān)鍵字volatile?
C 語言關(guān)鍵字volatile(注意它是用來修飾變量而不是上面介紹的__volatile__)表明某個變量?
的值可能在外部被改變,因此對這些變量的存取不能緩存到寄存器,每次使用時需要重新存取。?
該關(guān)鍵字在多線程環(huán)境下經(jīng)常使用,因為在編寫多線程的程序時,同一個變量可能被多個線程修?
改,而程序通過該變量同步各個線程,例如:?
DWORD __stdcall threadFunc(LPVOID signal)?
{?
int* intSignal=reinterpret_cast(signal);?
*intSignal=2;?
while(*intSignal!=1)?
sleep(1000);?
return 0;?
}?
該線程啟動時將intSignal置為2,然后循環(huán)等待直到intSignal為1,時退出。顯然intSignal?
的值必須在外部被改變,否則該線程不會退出。但是實際運(yùn)行的時候該線程卻不會退出,即使?
在外部將它的值改為1,看一下對應(yīng)的偽匯編代碼就明白了:?
mov ax,signal?
label:?
if(ax!=1)?
goto label?
對于C編譯器來說,它并不知道這個值會被其他線程修改。自然就把它c(diǎn)ache在寄存器里面。記住,C?
編譯器是沒有線程概念的!這時候就需要用到volatile。volatile的本意是指:這個值可能會在?
當(dāng)前線程外部被改變。也就是說,我們要在threadFunc中的intSignal前面加上volatile?
關(guān)鍵字,這時候,編譯器知道該變量的值會在外部改變,因此每次訪問該變量時會重新讀取,所作?
的循環(huán)變?yōu)槿缦旅鎮(zhèn)未a所示:?
label:?
mov ax,signal?
if(ax!=1)?
goto label?
2.3.5.2.3 Memory?
有了上面的知識就不難理解Memory?
修改描述符了,Memory描述符告知GCC:?
(1)不要將該段內(nèi)嵌匯編指令與前面的指令重新排序;也就是在執(zhí)行內(nèi)嵌匯編代碼之前,?
它前面的指令都執(zhí)行完畢。?
(2)不要將變量緩存到寄存器,因為這段代碼可能會用到內(nèi)存變量,而這些內(nèi)存變量會?
以不可預(yù)知的方式發(fā)生改變,因此GCC插入必要的代碼先將緩存到寄存器的變量值寫回內(nèi)存,?
如果后面又訪問這些變量,需要重新訪問內(nèi)存。?
如果匯編指令修改了內(nèi)存,但是GCC本身卻察覺不到,因為在輸出部分沒有描述,?
此時就需要在修改描述部分增加“memory”,告訴GCC內(nèi)存已經(jīng)被修改,GCC得知這個信息后,?
就會在這段指令之前,插入必要的指令將前面因為優(yōu)化Cache到寄存器中的變量值先寫回內(nèi)存,?
如果以后又要使用這些變量再重新讀取。?
例:?
………..?
Char test[100];?
char a;?
char c;?
c = 0;?
test[0] = 1;?
……..?
a = test [0];?
……?
__asm__(?
"cld\n\t"?
"rep\n\t"?
"stosb"?
: /* no output */?
: "a" (c),"D" (test),"c" (100)?
:?
"cx","di","memory");?
……….?
// 我們知道test[0] 已經(jīng)修改,所以重新讀取?
a=test[0];?
……?
這段代碼中的匯編指令功能與?
memset?
相當(dāng),也就是相當(dāng)于調(diào)用了memset(test,0,100);它使用stosb修改了test?
數(shù)組的內(nèi)容,但是沒有在輸入或輸出部分去描述操作數(shù),因為這兩條指令都不需要?
顯式的指定操作數(shù),因此需要增加“memory”通知GCC。現(xiàn)在假設(shè):GCC在優(yōu)化時將test[0]?
放到了%eax寄存器,那么test[0] = 1對應(yīng)于%eax=1,a = test [0]被換為a=%eax?
,如果在那段匯編指令中不使用“memory”,Gcc,不知道現(xiàn)在test[0]?
的值已經(jīng)被改變了(如果整段代碼都是我們自己使用匯編編寫,我們自己當(dāng)然知道?
這些內(nèi)存的修改情況,我們也可以人為的去優(yōu)化,但是現(xiàn)在除了我們編寫的那一小段外,?
其他匯編代碼都是GCC?
生成的,它并沒有那么智能,知道這段代碼會修改test[0]),結(jié)果其后的a=test[0]?
,轉(zhuǎn)換為匯編后卻是a=%eax,因為GCC不知道顯式的改變了test數(shù)組,結(jié)果出錯了。?
如果增加了“memory”修飾符,GCC知道:?
“這段代碼修改了內(nèi)存,但是也僅此而已,它并不知道到底修改了哪些變量”,?
因此他將以前因優(yōu)化而緩存到寄存器的變量值全部寫回內(nèi)存,從內(nèi)嵌匯編開始,如果后面?
的代碼又要存取這些變量,則重新存取內(nèi)存(不會將讀寫操作映射到以前緩存的那個寄存器)。?
這樣上面那段代碼最后一句就不再是%eax=1,而是test[0] = 1。?
這兩條對實現(xiàn)臨界區(qū)至關(guān)重要,第一條保證不會因為指令的重新排序?qū)⑴R界區(qū)內(nèi)的代碼調(diào)?
到臨界區(qū)之外(如果臨界區(qū)內(nèi)的指令被重排序放到臨界區(qū)之外,What will happen?),?
第二條保證在臨界區(qū)訪問的變量的值,肯定是最新的值,而不是緩存在?
寄存器中的值,否則就會導(dǎo)致奇怪的錯誤。例如下面的代碼:?
int del_timer(struct timer_list * timer)?
{?
int?
ret = 0;?
if?
(timer->next) {?
unsigned?
long flags;?
struct?
timer_list * next;?
save_flags(flags);?
cli();?
// 臨界區(qū)開始?
if?
((next = timer->next) != NULL) {?
(next->prev = timer->prev)->next = next;?
timer->next = timer->prev = NULL;?
ret = 1;?
} // 臨界區(qū)結(jié)束?
restore_flags(flags);?
}?
return?
ret;?
}?
它先判斷timer->next?
的值,如果是空直接返回,無需進(jìn)行下面的操作。如果不是空,則進(jìn)入臨界區(qū)進(jìn)行操作,但是cli()?
的實現(xiàn)(見下面)沒有使用“memory”,timer->next的值可能會被緩存到寄存器中,?
后面if ((next =timer->next) != NULL)會從寄存器中讀取timer->next的值,如果?
在if (timer->next)之后,進(jìn)入臨界區(qū)之前,timer->next的值可能被在外部改變,?
這時肯定會出現(xiàn)異常情況,而且這種情況很難Debug。但是如果cli使用“memory”,?
那么if ((next = timer->next) !=NULL)語句會重新從內(nèi)存讀取timer->next的值,而不會從寄存器?
中取,這樣就不會出現(xiàn)問題啦。?
2.4 版內(nèi)核中cli和sti的代碼如下:?
#define __cli()?
__asm__?
__volatile__("cli": : :"memory")?
#define __sti()?
__asm__?
__volatile__("sti": : :"memory")?
通過上面的例子,讀者應(yīng)該知道,為什么指令沒有修改內(nèi)存,但是卻使用“memory?
”修改描述符的原因了吧。應(yīng)從指令的上下文去理解為什么要這樣做。?
使用“volatile”也可以達(dá)到這個目的,但是我們在每個變量前增加該關(guān)鍵字,?
不如使用“memory”方便。?
2.4 GCC如何編譯內(nèi)嵌匯編代碼?
GCC 編譯內(nèi)嵌匯編代碼的步驟如下:?
1.輸入變量與占位符?
根據(jù)限定符和破壞描述部分,為輸入和輸出部分的變量分配合適的寄存器,如果限定符指定為立即數(shù)?
(“i”),或內(nèi)存變量(“m”),則不需要該步驟,如果限定符沒有具體指定輸入操作數(shù)的?
類型(如“g”),GCC會視需要決定是否將該操作數(shù)輸入到某個寄存器。這樣每個占位符都與某個?
寄存器、內(nèi)存變量或立即數(shù)形成了一一對應(yīng)的關(guān)系。對分配了寄存器的輸入變量需要增加代碼?
將它的值讀入寄存器。另外還要根據(jù)破壞描述符的部分增加額外代碼。?
2.指令模板部分?
然后根據(jù)這種一一對應(yīng)的關(guān)系,用這些寄存器、內(nèi)存變量或立即數(shù)來取代匯編代碼中的占位符。?
3.變量輸出?
語言編寫,只有一小部分使用匯編語言編寫,例如與特定體系結(jié)構(gòu)相關(guān)的代碼和對性能影響很大的代碼。GCC提供了內(nèi)嵌匯編的功能,可以在C代碼中直接內(nèi)嵌匯編語言語句,大大方便了程序設(shè)計。?
簡單的內(nèi)嵌匯編很容易理解?
例:?
__asm__?
__volatile__("hlt");?
“__asm__”表示后面的代碼為內(nèi)嵌匯編,“asm”是“__asm__”的別名。?
“__volatile__”表示編譯器不要優(yōu)化代碼,后面的指令保留原樣,?
“volatile”是它的別名。括號里面是匯編指令。?
2.2 內(nèi)嵌匯編舉例在內(nèi)嵌匯編中,可以將C?
語言表達(dá)式指定為匯編指令的操作數(shù),而且不用去管如何將C?
語言表達(dá)式的值讀入哪個寄存器,以及如何將計算結(jié)果寫回C?
變量,你只要告訴程序中C語言表達(dá)式與匯編指令操作數(shù)之間的對應(yīng)關(guān)系即可, GCC?
會自動插入代碼完成必要的操作。?
使用內(nèi)嵌匯編,要先編寫匯編指令模板,然后將C語言表達(dá)式與指令的操作數(shù)相關(guān)聯(lián),并告訴?
GCC對這些操作有哪些限制條件。例如在下面的匯編語句:?
__asm__ __violate__?
("movl %1,%0" : "=r" (result) : "m" (input));?
“movl %1,%0”是指令模板;“%0”和“%1”代表指令的操作數(shù),稱為占位符,內(nèi)嵌匯編靠它們將C?
語言表達(dá)式與指令操作數(shù)相對應(yīng)。指令模板后面用小括號括起來的是C?
語言表達(dá)式,本例中只有兩個:“result”和“input”,他們按照出現(xiàn)的順序分別與指令操作?
數(shù)“%0”,“%1,”對應(yīng);注意對應(yīng)順序:第一個C表達(dá)式對應(yīng)“%0”;第二個表達(dá)式對應(yīng)“%1?
”,依次類推,操作數(shù)至多有10個,分別用“%0”,“%1”….“%9,”表示。在每個操作數(shù)前?
面有一個用引號括起來的字符串,字符串的內(nèi)容是對該操作數(shù)的限制或者說要求。“result”前面?
的限制字符串是“=r”,其中“=”表示“result”是輸出操作數(shù),“r?
”表示需要將“result”與某個通用寄存器相關(guān)聯(lián),先將操作數(shù)的值讀入寄存器,然后?
在指令中使用相應(yīng)寄存器,而不是“result”本身,當(dāng)然指令執(zhí)行完后需要將寄存器中的值?
存入變量“result”,從表面上看好像是指令直接對“result”進(jìn)行操作,實際上GCC?
做了隱式處理,這樣我們可以少寫一些指令。“input”前面的“r”表示該表達(dá)式需要先放入?
某個寄存器,然后在指令中使用該寄存器參加運(yùn)算。?
我們將上面的內(nèi)嵌代碼放到一個C源文件中,然后使用gcc –c–S得到該C?
文件源代碼相對應(yīng)的匯編代碼,然后查看一下匯編代碼,看看GCC是如何處理的。?
C源文件如下內(nèi)容如下,注意該代碼沒有實際意義,僅僅作為例子。?
extern int?
input,result;?
void test(void)?
{?
input?
= 1;?
__asm__ __volatile__ ("movl %1,%0" :?
"=r" (result) : "r" (input));?
return?
;?
}?
對應(yīng)的匯編代碼如下;?
行號 代碼 解釋?
1?
7?
8 movl $1, input 對應(yīng)C語言語句input = 1;?
9 input, %eax?
10 #APP GCC插入的注釋,表示內(nèi)嵌匯編開始?
11 movl %eax,%eax 我們的內(nèi)嵌匯編語句?
12 #NO_APP GCC 插入的注釋,表示內(nèi)嵌匯編結(jié)束?
13 movl %eax, result 將結(jié)果存入result變量?
14?
-?
18?
。。。。。。?
從匯編代碼可以看出,第9行和第13行是GCC,自動增加的代碼,GCC?
根據(jù)限定字符串決定如何處理C表達(dá)式,本例兩個表達(dá)式都被指定為“r”型,所以先使用指令:?
movl input, %eax?
將input讀入寄存器%eax;GCC,也指定一個寄存器與輸出變量result?
相關(guān),本例也是%eax,等得到操作結(jié)果后再使用指令:?
movl %eax, result?
將寄存器的值寫回C變量result中。從上面的匯編代碼我們可以看出與result?
和input,相關(guān)連的寄存器都是%eax,GCC使用%eax,替換內(nèi)嵌匯編指令模板中的?
%0,%1?
movl %eax,%eax?
顯然這一句可以不要。但是沒有優(yōu)化,所以這一句沒有被去掉。?
由此可見,C表達(dá)式或者變量與寄存器的關(guān)系由GCC自動處理,我們只需使用限制字符串指導(dǎo)GCC?
如何處理即可。限制字符必須與指令對操作數(shù)的要求相匹配,否則產(chǎn)生的匯編代碼?
將會有錯,讀者可以將上例中的兩個“r”,都改為“m”(m,表示操作數(shù)放在內(nèi)存,而不是寄?
存器中),編譯后得到的結(jié)果是:?
movl input, result?
很明顯這是一條非法指令,因此限制字符串必須與指令對操作數(shù)的要求匹配。例如指令movl?
允許寄存器到寄存器,立即數(shù)到寄存器等,但是不允許內(nèi)存到內(nèi)存的操作,因此兩個操作數(shù)?
不能同時使用“m”作為限定字符。?
2.3 語法?
內(nèi)嵌匯編語法如下:?
__asm__(?
匯編語句模板:?
輸出部分:?
輸入部分:?
破壞描述部分)?
共四個部分:匯編語句模板,輸出部分,輸入部分,破壞描述部分,各部分使用“:”格?
開,匯編語句模板必不可少,其他三部分可選,如果使用了后面的部分,而前面部分為空,?
也需要用“:”格開,相應(yīng)部分內(nèi)容為空。例如:?
__asm__ __volatile__(?
"cli":?
:?
:"memory")?
2.3.1 匯編語句模板?
匯編語句模板由匯編語句序列組成,語句之間使用“;”、“\n”或“\n\t”分開。?
指令中的操作數(shù)可以使用占位符引用C語言變量,操作數(shù)占位符最多10個,名稱如下:%0,%1…,%9。?
指令中使用占位符表示的操作數(shù),總被視為long型(4,個字節(jié)),但對其施加的操作?
根據(jù)指令可以是字或者字節(jié),當(dāng)把操作數(shù)當(dāng)作字或者字節(jié)使用時,默認(rèn)為低字或者低字節(jié)。?
對字節(jié)操作可以顯式的指明是低字節(jié)還是次字節(jié)。方法是在%和序號之間插入一個字母,?
“b”代表低字節(jié),“h”代表高字節(jié),例如:%h1。?
2.3.2 輸出部分?
輸出部分描述輸出操作數(shù),不同的操作數(shù)描述符之間用逗號格開,每個操作數(shù)描述符由限定字符串和?
C語言變量組成。每個輸出操作數(shù)的限定字符串必須包含“=”表示他是一個輸出操作數(shù)。?
例:?
__asm__ __volatile__("pushfl ; popl %0 ; cli":"=g" (x) )?
描述符字符串表示對該變量的限制條件,這樣GCC就可以根據(jù)這些條件決定如何?
分配寄存器,如何產(chǎn)生必要的代碼處理指令操作數(shù)與C表達(dá)式或C變量之間的聯(lián)系。?
2.3.3 輸入部分?
輸入部分描述輸入操作數(shù),不同的操作數(shù)描述符之間使用逗號格開,每個操作數(shù)描述符由?
限定字符串和C語言表達(dá)式或者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位設(shè)為1。第一個占位符%0與C,語言變量ADDR?
對應(yīng),第二個占位符%1與C,語言變量nr對應(yīng)。因此上面的匯編語句代碼與下面的偽代碼等價:?
btsl nr, ADDR,該指令的兩個操作數(shù)不能全是內(nèi)存變量,因此將nr的限定字符串指定為“Ir”,?
將nr,與立即數(shù)或者寄存器相關(guān)聯(lián),這樣兩個操作數(shù)中只有ADDR為內(nèi)存變量。?
2.3.4 限制字符?
2.3.4.1 限制字符列表?
限制字符有很多種,有些是與特定體系結(jié)構(gòu)相關(guān),此處僅列出常用的限定字符和i386?
中可能用到的一些常用的限定符。它們的作用是指示編譯器如何處理其后的C?
語言變量與指令操作數(shù)之間的關(guān)系,例如是將變量放在寄存器中還是放在內(nèi)存中等,?
下表列出了常用的限定字母。?
分類?
限定符 描述 通用寄存器?
“a”將輸入變量放入eax?
這里有一個問題:假設(shè)eax已經(jīng)被使用,那怎么辦??
其實很簡單:因為GCC知道eax已經(jīng)被使用,它在這段匯編代碼的起始處插入一條?
語句pushl %eax,將eax內(nèi)容保存到堆棧,然后在這段代碼結(jié)束處再增加一條?
語句popl %eax,恢復(fù)eax的內(nèi)容?
“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位的寄存器(uselong longs)?
“m”內(nèi)存變量?
“o”操作數(shù)為內(nèi)存變量,但是其尋址方式是偏移量類型,也即是基址尋址,或者是基址加變址尋址?
“V”操作數(shù)為內(nèi)存變量,但尋址方式不是偏移量類型?
“,” 操作數(shù)為內(nèi)存變量,但尋址方式為自動增量?
“p”操作數(shù)是一個合法的內(nèi)存地址(指針)?
寄存器或內(nèi)存?
“g” 將輸入變量放入eax,ebx,ecx ,edx中的一個或者作為內(nèi)存變量?
“X”操作數(shù)可以是任何類型?
立即數(shù)?
“I” 0-31 之間的立即數(shù)(用于32位移位指令)?
“J” 0-63 之間的立即數(shù)(用于64 位移位指令)?
“N” 0-255 ,之間的立即數(shù)(用于out 指令)?
“i” 立即數(shù)?
“n” 立即數(shù),有些系統(tǒng)不支持除字以外的立即數(shù),這些系統(tǒng)應(yīng)該使用“n”而不是“i”?
匹配?
“0”,“1 ,”... “9 ”?
表示用它限制的操作數(shù)與某個指定的操作數(shù)匹配,也即該操作數(shù)就是指定的那個操作數(shù),?
例如用“0 ”去描述“%1”操作數(shù),那么“%1”引用的其實就是“%0”操作數(shù),注意作為?
限定符字母的0-9 ,與指令中的“%0”-“%9”的區(qū)別,前者描述操作數(shù),后者代表操作數(shù)。?
后面有詳細(xì)描述 & 該輸出操作數(shù)不能使用過和輸入操作數(shù)相同的寄存器?
后面有詳細(xì)描述?
操作數(shù)類型?
“=” 操作數(shù)在指令中是只寫的(輸出操作數(shù))?
“+” 操作數(shù)在指令中是讀寫類型的(輸入輸出操作數(shù))?
浮點(diǎn)數(shù)?
“f”?
浮點(diǎn)寄存器?
“t”第一個浮點(diǎn)寄存器?
“u”第二個浮點(diǎn)寄存器?
“G”標(biāo)準(zhǔn)的80387?
浮點(diǎn)常數(shù)?
% 該操作數(shù)可以和下一個操作數(shù)交換位置?
例如addl的兩個操作數(shù)可以交換順序(當(dāng)然兩個操作數(shù)都不能是立即數(shù))?
# 部分注釋,從該字符到其后的逗號之間所有字母被忽略?
* 表示如果選用寄存器,則其后的字母被忽略?
現(xiàn)在繼續(xù)看上面的例子,?
"=m" (ADDR)表示ADDR為內(nèi)存變量(“m”),而且是輸出變量(“=”);"Ir" (nr)表示nr,為?
0-31之間的立即數(shù)(“I”)或者一個寄存器操作數(shù)(“r”)。?
2.3.4.2?
匹配限制符?
I386?
指令集中許多指令的操作數(shù)是讀寫型的(讀寫型操作數(shù)指先讀取原來的值然后參加運(yùn)算,最后?
將結(jié)果寫回操作數(shù)),例如addl %1,%0,它的作用是將操作數(shù)%0與操作數(shù)%1的和存入操作數(shù)%0,?
因此操作數(shù)%0是讀寫型操作數(shù)。老版本的GCC對這種類型操作數(shù)的支持不是很好,它將操作數(shù)嚴(yán)格?
分為輸入和輸出兩種,分別放在輸入部分和輸出部分,而沒有一個單獨(dú)部分描述讀寫型操作數(shù),?
因此在GCC中讀寫型的操作數(shù)需要在輸入和輸出部分分別描述,靠匹配限制符將兩者關(guān)聯(lián)到一起?
注意僅在輸入和輸出部分使用相同的C變量,但是不用匹配限制符,產(chǎn)生的代碼很可能不對,后?
面會分析原因。?
匹配限制符是一位數(shù)字:“0”、“1”……“9,”,分別表示它限制的C表達(dá)式分別與?
占位符%0,%1,……%9對應(yīng)的C變量匹配。例如使用“0”作為%1,的限制字符,那么?
%0和%1表示同一個C,變量。?
看一下下面的代碼就知道為什么要將讀寫型操作數(shù),分別在輸入和輸出部分加以描述。?
該例功能是求input+result的和,然后存入result:?
extern int input,result;?
void test_at_t()?
{?
result= 0;?
input = 1;?
__asm__?
__volatile__ ("addl %1,%0":"=r"(result): "r"(input));?
}?
對應(yīng)的匯編代碼為:?
movl $0,_result?
movl $1,_input?
movl _input,%edx /APP?
addl %edx,%eax /NO_APP?
movl %eax,%edx?
movl %edx,_result?
input 為輸入型變量,而且需要放在寄存器中,GCC給它分配的寄存器是%edx,在執(zhí)行addl之前%edx,?
的內(nèi)容已經(jīng)是input的值。可見對于使用“r”限制的輸入型變量或者表達(dá)式,在使用之前GCC會插入?
必要的代碼將他們的值讀到寄存器;“m”型變量則不需要這一步。讀入input后執(zhí)行addl,顯然%eax?
的值不對,需要先讀入result的值才行。再往后看:movl %eax,%edx和movl %edx,_result?
的作用是將結(jié)果存回result,分配給result的寄存器與分配給input的一樣,都是%edx。?
綜上可以總結(jié)出如下幾點(diǎn):?
1. 使用“r”限制的輸入變量,GCC先分配一個寄存器,然后將值讀入寄存器,最后?
用該寄存器替換占位符;?
2. 使用“r”限制的輸出變量,GCC會分配一個寄存器,然后用該寄存器替換占位符,?
但是在使用該寄存器之前并不將變量值先讀入寄存器,GCC認(rèn)為所有輸出變量以前的?
值都沒有用處,不讀入寄存器(可能是因為AT&T匯編源于CISC架構(gòu)處理器的匯編語言?
,在CISC處理器中大部分指令的輸入輸出明顯分開,而不像RISC那樣一個操作數(shù)既?
做輸入又做輸出,例如add r0,r1,r2,r0,和r1是輸入,r2是輸出,輸入和輸出分開,?
沒有使用輸入輸出型操作數(shù),這樣我們就可以認(rèn)為r2對應(yīng)的操作數(shù)原來的值沒有用處,?
也就沒有必要先將操作數(shù)的值讀入r2,因為這是浪費(fèi)處理器的CPU周期),最后GCC插入代碼,?
將寄存器的值寫回變量;?
3. 輸入變量使用的寄存器在最后一處使用它的指令之后,就可以挪做其他用處,因為?
已經(jīng)不再使用。例如上例中的%edx。在執(zhí)行完addl之后就作為與result對應(yīng)的寄存器。?
因為第二條,上面的內(nèi)嵌匯編指令不能奏效,因此需要在執(zhí)行addl之前把result的值讀入?
寄存器,也許再將result放入輸入部分就可以了(因為第一條會保證將result?
先讀入寄存器)。修改后的指令如下(為了更容易說明問題將input限制符由“r,”改為“m”):?
extern int input,result;?
void test_at_t()?
{?
result = 0;?
input = 1;?
__asm__?
__volatile__ ("addl %2,%0":"=r"(result):"r"(result),"m"(input));?
}?
看上去上面的代碼可以正常工作,因為我們知道%0和%1都和result相關(guān),應(yīng)該使用同一個?
寄存器,但是GCC并不去判斷%0和%1,是否和同一個C表達(dá)式或變量相關(guān)聯(lián)(這樣易于產(chǎn)生與?
內(nèi)嵌匯編相應(yīng)的匯編代碼),因此%0和%1使用的寄存器可能不同。我們看一下匯編代碼就知道了。?
movl $0,_result?
movl $1,_input?
movl _result,%edx /APP?
addl _input,%eax /NO_APP?
movl %eax,%edx?
movl %edx,_result?
現(xiàn)在在執(zhí)行addl之前將result的值被讀入了寄存器%edx,但是addl指令的操作數(shù)%0?
卻成了%eax,而不是%edx,與預(yù)料的不同,這是因為GCC給輸出和輸入部分的變量分配了不同?
的寄存器,GCC沒有去判斷兩者是否都與result相關(guān),后面會講GCC如何翻譯內(nèi)嵌匯編,看完之后?
就不會驚奇啦。?
使用匹配限制符后,GCC知道應(yīng)將對應(yīng)的操作數(shù)放在同一個位置(同一個寄存器或者同一個?
內(nèi)存變量)。使用匹配限制字符的代碼如下:?
extern int input,result;?
void test_at_t()?
{?
result = 0;?
input = 1;?
__asm__?
__volatile__ ("addl %2,%0":"=r"(result):"0"(result),"m"(input));?
}?
輸入部分中的result用匹配限制符“0”限制,表示%1與%0,代表同一個變量,?
輸入部分說明該變量的輸入功能,輸出部分說明該變量的輸出功能,兩者結(jié)合表示result?
是讀寫型。因為%0和%1,表示同一個C變量,所以放在相同的位置,無論是寄存器還是內(nèi)存。?
相應(yīng)的匯編代碼為:?
movl $0,_result?
movl $1,_input?
movl _result,%edx?
movl %edx,%eax /APP?
addl _input,%eax /NO_APP?
movl %eax,%edx?
movl %edx,_result?
可以看到與result相關(guān)的寄存器是%edx,在執(zhí)行指令addl之前先從%edx將result讀入%eax,?
執(zhí)行之后需要將結(jié)果從%eax讀入%edx,最后存入result中。這里我們可以看出GCC?
處理內(nèi)嵌匯編中輸出操作數(shù)的一點(diǎn)點(diǎn)信息:addl并沒有使用%edx,可見它不是簡單的用result?
對應(yīng)的寄存器%edx去替換%0,而是先分配一個寄存器,執(zhí)行運(yùn)算,最后才將運(yùn)算結(jié)果存入?
對應(yīng)的變量,因此GCC是先看該占位符對應(yīng)的變量的限制符,發(fā)現(xiàn)是一個輸出型寄存器變量,?
就為它分配一個寄存器,此時沒有去管對應(yīng)的C變量,最后GCC,知道還要將寄存器的值寫回變量,?
與此同時,它發(fā)現(xiàn)該變量與%edx關(guān)聯(lián),因此先存入%edx,再存入變量。?
至此讀者應(yīng)該明白了匹配限制符的意義和用法。在新版本的GCC中增加了一個限制字符“+”,?
它表示操作數(shù)是讀寫型的,GCC知道應(yīng)將變量值先讀入寄存器,然后計算,最后寫回變量,而?
無需在輸入部分再去描述該變量。?
例;?
extern int input,result;?
void test_at_t()?
{?
result = 0;?
input = 1;?
__asm__?
__volatile__ ("addl %1,%0":"+r"(result):"m"(input));?
}?
此處用“+”替換了“=”,而且去掉了輸入部分關(guān)于result的描述,產(chǎn)生的匯編代碼如下:?
movl $0,_result?
movl $1,_input?
movl _result,%eax /APP?
addl _input,%eax /NO_APP?
movl %eax,_result?
L2:?
movl %ebp,%esp?
處理的比使用匹配限制符的情況還要好,省去了好幾條匯編代碼。?
2.3.4.3 “&”限制符?
限制符“&”在內(nèi)核中使用的比較多,它表示輸入和輸出操作數(shù)不能使用相同的寄存器,?
這樣可以避免很多錯誤。?
舉一個例子,下面代碼的作用是將函數(shù)foo的返回值存入變量ret中:?
__asm__ ( “call foo;movl %%edx,%1”, :”=a”(ret) : ”r”(bar) );?
我們知道函數(shù)的int型返回值存放在%eax中,但是gcc編譯的結(jié)果是輸入和輸出同時使用了?
寄存器%eax,如下:?
movl bar, %eax?
#APP?
call foo?
movl %ebx,%eax?
#NO_APP?
movl %eax, ret?
結(jié)果顯然不對,原因是GCC并不知道%eax中的值是我們所要的。避免這種情況的方法是使用“&”?
限定符,這樣bar就不會再使用%eax寄存器,因為已被ret指定使用。?
_asm__ ( “call foo;movl %%edx,%1”,:”=&a”(ret) : ”r”(bar) );?
2.3.5 破壞描述部分?
2.3.5.1 寄存器破壞描述符?
通常編寫程序只使用一種語言:高級語言或者匯編語言。高級語言編譯的步驟大致如下:?
l?
預(yù)處理;?
l?
編譯?
l?
匯編?
l?
鏈接?
我們這里只關(guān)心第二步編譯(將C代碼轉(zhuǎn)換成匯編代碼):因為所有的代碼都是用高級語言編寫,?
編譯器可以識別各種語句的作用,在轉(zhuǎn)換的過程中所有的寄存器都由編譯器決定如何分配使用,?
它有能力保證寄存器的使用不會沖突;也可以利用寄存器作為變量的緩沖區(qū),因為寄存器的訪問?
速度比內(nèi)存快很多倍。如果全部使用匯編語言則由程序員去控制寄存器的使用,只能靠程序員去?
保證寄存器使用的正確性。但是如果兩種語言混用情況就變復(fù)雜了,因為內(nèi)嵌的匯編代碼可以直接?
使用寄存器,而編譯器在轉(zhuǎn)換的時候并不去檢查內(nèi)嵌的匯編代碼使用了哪些寄存器(因為很難檢測?
匯編指令使用了哪些寄存器,例如有些指令隱式修改寄存器,有時內(nèi)嵌的匯編代碼會調(diào)用其他子過程,?
而子過程也會修改寄存器),因此需要一種機(jī)制通知編譯器我們使用了哪些寄存器(程序員自己知道?
內(nèi)嵌匯編代碼中使用了哪些寄存器),否則對這些寄存器的使用就有可能導(dǎo)致錯誤,修改描述部分?
可以起到這種作用。當(dāng)然內(nèi)嵌匯編的輸入輸出部分指明的寄存器或者指定為“r”,“g”型由編譯器?
去分配的寄存器就不需要在破壞描述部分去描述,因為編譯器已經(jīng)知道了。?
破壞描述符由逗號格開的字符串組成,每個字符串描述一種情況,一般是寄存器名;除寄存器外?
還有“memory”。例如:“%eax”,“%ebx”,“memory”等。?
下面看個例子就很清楚為什么需要通知GCC內(nèi)嵌匯編代碼中隱式(稱它為隱式是因為GCC并不知道)?
使用的寄存器。?
在內(nèi)嵌的匯編指令中可能會直接引用某些寄存器,我們已經(jīng)知道AT&T格式的匯編語言中,寄存器?
名以“%”作為前綴,為了在生成的匯編程序中保留這個“%”號,在asm語句中對寄存器的?
引用必須用“%%”作為寄存器名稱的前綴。原因是“%”在asm,內(nèi)嵌匯編語句中的作用與“\”在C?
語言中的作用相同,因此“%%”轉(zhuǎn)換后代表“%”。?
例(沒有使用修改描述符):?
int main(void)?
{?
int input, output,temp;?
input = 1;?
__asm__ __volatile__ ("movl $0, %%eax;\n\t?
movl %%eax, %1;\n\t?
movl %2, %%eax;\n\t?
movl %%eax, %0;\n\t"?
:"=m"(output),"=m"(temp) /* output */?
:"r"(input) /* input */?
);?
return 0;?
}?
這段代碼使用%eax作為臨時寄存器,功能相當(dāng)于C代碼:“temp = 0;output=input”,?
對應(yīng)的匯編代碼如下:?
movl $1,-4(%ebp)?
movl -4(%ebp),%eax /APP?
movl $0, %eax;?
movl %eax, -12(%ebp);?
movl %eax, %eax;?
movl %eax, -8(%ebp); /NO_APP?
顯然GCC給input分配的寄存器也是%eax,發(fā)生了沖突,output的值始終為0,而不是input。?
使用破壞描述后的代碼:?
int main(void)?
{?
int input, output,temp;?
input = 1;?
__asm__ __volatile__?
( "movl $0, %%eax;\n\t?
movl %%eax, %1;\n\t?
movl %2, %%eax;\n\t?
movl %%eax, %0;\n\t"?
:"=m"(output),"=m"(temp) /* output */?
:"r"(input) /* input */?
:"eax"); /* 描述符 */?
return 0;?
}?
對應(yīng)的匯編代碼:?
movl $1,-4(%ebp)?
movl -4(%ebp),%edx /APP?
movl $0, %eax;?
movl %eax, -12(%ebp);?
movl %edx, %eax;?
movl %eax, -8(%ebp); /NO_APP?
通過破壞描述部分,GCC得知%eax已被使用,因此給input分配了%edx。在使用內(nèi)嵌匯編時請記?
住一點(diǎn):盡量告訴GCC盡可能多的信息,以防出錯。?
如果你使用的指令會改變CPU的條件寄存器cc,需要在修改描述部分增加“cc”。?
2.3.5.2 memory破壞描述符?
“memory”比較特殊,可能是內(nèi)嵌匯編中最難懂部分。為解釋清楚它,先介紹一下編譯器的?
優(yōu)化知識,再看C關(guān)鍵字volatile。最后去看該描述符。?
2.3.5.2.1 編譯器優(yōu)化介紹?
內(nèi)存訪問速度遠(yuǎn)不及CPU處理速度,為提高機(jī)器整體性能,在硬件上引入硬件高速緩存Cache,?
加速對內(nèi)存的訪問。另外在現(xiàn)代CPU中指令的執(zhí)行并不一定嚴(yán)格按照順序執(zhí)行,沒有相關(guān)性?
的指令可以亂序執(zhí)行,以充分利用CPU的指令流水線,提高執(zhí)行速度。以上是硬件級別的優(yōu)化。?
再看軟件一級的優(yōu)化:一種是在編寫代碼時由程序員優(yōu)化,另一種是由編譯器進(jìn)行優(yōu)化。編譯器?
優(yōu)化常用的方法有:將內(nèi)存變量緩存到寄存器;調(diào)整指令順序充分利用CPU指令流水線,常見的?
是重新排序讀寫指令。?
對常規(guī)內(nèi)存進(jìn)行優(yōu)化的時候,這些優(yōu)化是透明的,而且效率很好。由編譯器優(yōu)化或者硬件重新排序引起的問題的解決辦法是在從硬件(或者其他處理器)的角度看必須以特定順序執(zhí)行的操作之間設(shè)置內(nèi)存屏障(memory barrier),linux提供了一個宏解決編譯器的執(zhí)行順序問題。?
void Barrier(void)?
這個函數(shù)通知編譯器插入一個內(nèi)存屏障,但對硬件無效,編譯后的代碼會把當(dāng)前CPU?
寄存器中的所有修改過的數(shù)值存入內(nèi)存,需要這些數(shù)據(jù)的時候再重新從內(nèi)存中讀出。?
2.3.5.2.2 C 語言關(guān)鍵字volatile?
C 語言關(guān)鍵字volatile(注意它是用來修飾變量而不是上面介紹的__volatile__)表明某個變量?
的值可能在外部被改變,因此對這些變量的存取不能緩存到寄存器,每次使用時需要重新存取。?
該關(guān)鍵字在多線程環(huán)境下經(jīng)常使用,因為在編寫多線程的程序時,同一個變量可能被多個線程修?
改,而程序通過該變量同步各個線程,例如:?
DWORD __stdcall threadFunc(LPVOID signal)?
{?
int* intSignal=reinterpret_cast(signal);?
*intSignal=2;?
while(*intSignal!=1)?
sleep(1000);?
return 0;?
}?
該線程啟動時將intSignal置為2,然后循環(huán)等待直到intSignal為1,時退出。顯然intSignal?
的值必須在外部被改變,否則該線程不會退出。但是實際運(yùn)行的時候該線程卻不會退出,即使?
在外部將它的值改為1,看一下對應(yīng)的偽匯編代碼就明白了:?
mov ax,signal?
label:?
if(ax!=1)?
goto label?
對于C編譯器來說,它并不知道這個值會被其他線程修改。自然就把它c(diǎn)ache在寄存器里面。記住,C?
編譯器是沒有線程概念的!這時候就需要用到volatile。volatile的本意是指:這個值可能會在?
當(dāng)前線程外部被改變。也就是說,我們要在threadFunc中的intSignal前面加上volatile?
關(guān)鍵字,這時候,編譯器知道該變量的值會在外部改變,因此每次訪問該變量時會重新讀取,所作?
的循環(huán)變?yōu)槿缦旅鎮(zhèn)未a所示:?
label:?
mov ax,signal?
if(ax!=1)?
goto label?
2.3.5.2.3 Memory?
有了上面的知識就不難理解Memory?
修改描述符了,Memory描述符告知GCC:?
(1)不要將該段內(nèi)嵌匯編指令與前面的指令重新排序;也就是在執(zhí)行內(nèi)嵌匯編代碼之前,?
它前面的指令都執(zhí)行完畢。?
(2)不要將變量緩存到寄存器,因為這段代碼可能會用到內(nèi)存變量,而這些內(nèi)存變量會?
以不可預(yù)知的方式發(fā)生改變,因此GCC插入必要的代碼先將緩存到寄存器的變量值寫回內(nèi)存,?
如果后面又訪問這些變量,需要重新訪問內(nèi)存。?
如果匯編指令修改了內(nèi)存,但是GCC本身卻察覺不到,因為在輸出部分沒有描述,?
此時就需要在修改描述部分增加“memory”,告訴GCC內(nèi)存已經(jīng)被修改,GCC得知這個信息后,?
就會在這段指令之前,插入必要的指令將前面因為優(yōu)化Cache到寄存器中的變量值先寫回內(nèi)存,?
如果以后又要使用這些變量再重新讀取。?
例:?
………..?
Char test[100];?
char a;?
char c;?
c = 0;?
test[0] = 1;?
……..?
a = test [0];?
……?
__asm__(?
"cld\n\t"?
"rep\n\t"?
"stosb"?
: /* no output */?
: "a" (c),"D" (test),"c" (100)?
:?
"cx","di","memory");?
……….?
// 我們知道test[0] 已經(jīng)修改,所以重新讀取?
a=test[0];?
……?
這段代碼中的匯編指令功能與?
memset?
相當(dāng),也就是相當(dāng)于調(diào)用了memset(test,0,100);它使用stosb修改了test?
數(shù)組的內(nèi)容,但是沒有在輸入或輸出部分去描述操作數(shù),因為這兩條指令都不需要?
顯式的指定操作數(shù),因此需要增加“memory”通知GCC。現(xiàn)在假設(shè):GCC在優(yōu)化時將test[0]?
放到了%eax寄存器,那么test[0] = 1對應(yīng)于%eax=1,a = test [0]被換為a=%eax?
,如果在那段匯編指令中不使用“memory”,Gcc,不知道現(xiàn)在test[0]?
的值已經(jīng)被改變了(如果整段代碼都是我們自己使用匯編編寫,我們自己當(dāng)然知道?
這些內(nèi)存的修改情況,我們也可以人為的去優(yōu)化,但是現(xiàn)在除了我們編寫的那一小段外,?
其他匯編代碼都是GCC?
生成的,它并沒有那么智能,知道這段代碼會修改test[0]),結(jié)果其后的a=test[0]?
,轉(zhuǎn)換為匯編后卻是a=%eax,因為GCC不知道顯式的改變了test數(shù)組,結(jié)果出錯了。?
如果增加了“memory”修飾符,GCC知道:?
“這段代碼修改了內(nèi)存,但是也僅此而已,它并不知道到底修改了哪些變量”,?
因此他將以前因優(yōu)化而緩存到寄存器的變量值全部寫回內(nèi)存,從內(nèi)嵌匯編開始,如果后面?
的代碼又要存取這些變量,則重新存取內(nèi)存(不會將讀寫操作映射到以前緩存的那個寄存器)。?
這樣上面那段代碼最后一句就不再是%eax=1,而是test[0] = 1。?
這兩條對實現(xiàn)臨界區(qū)至關(guān)重要,第一條保證不會因為指令的重新排序?qū)⑴R界區(qū)內(nèi)的代碼調(diào)?
到臨界區(qū)之外(如果臨界區(qū)內(nèi)的指令被重排序放到臨界區(qū)之外,What will happen?),?
第二條保證在臨界區(qū)訪問的變量的值,肯定是最新的值,而不是緩存在?
寄存器中的值,否則就會導(dǎo)致奇怪的錯誤。例如下面的代碼:?
int del_timer(struct timer_list * timer)?
{?
int?
ret = 0;?
if?
(timer->next) {?
unsigned?
long flags;?
struct?
timer_list * next;?
save_flags(flags);?
cli();?
// 臨界區(qū)開始?
if?
((next = timer->next) != NULL) {?
(next->prev = timer->prev)->next = next;?
timer->next = timer->prev = NULL;?
ret = 1;?
} // 臨界區(qū)結(jié)束?
restore_flags(flags);?
}?
return?
ret;?
}?
它先判斷timer->next?
的值,如果是空直接返回,無需進(jìn)行下面的操作。如果不是空,則進(jìn)入臨界區(qū)進(jìn)行操作,但是cli()?
的實現(xiàn)(見下面)沒有使用“memory”,timer->next的值可能會被緩存到寄存器中,?
后面if ((next =timer->next) != NULL)會從寄存器中讀取timer->next的值,如果?
在if (timer->next)之后,進(jìn)入臨界區(qū)之前,timer->next的值可能被在外部改變,?
這時肯定會出現(xiàn)異常情況,而且這種情況很難Debug。但是如果cli使用“memory”,?
那么if ((next = timer->next) !=NULL)語句會重新從內(nèi)存讀取timer->next的值,而不會從寄存器?
中取,這樣就不會出現(xiàn)問題啦。?
2.4 版內(nèi)核中cli和sti的代碼如下:?
#define __cli()?
__asm__?
__volatile__("cli": : :"memory")?
#define __sti()?
__asm__?
__volatile__("sti": : :"memory")?
通過上面的例子,讀者應(yīng)該知道,為什么指令沒有修改內(nèi)存,但是卻使用“memory?
”修改描述符的原因了吧。應(yīng)從指令的上下文去理解為什么要這樣做。?
使用“volatile”也可以達(dá)到這個目的,但是我們在每個變量前增加該關(guān)鍵字,?
不如使用“memory”方便。?
2.4 GCC如何編譯內(nèi)嵌匯編代碼?
GCC 編譯內(nèi)嵌匯編代碼的步驟如下:?
1.輸入變量與占位符?
根據(jù)限定符和破壞描述部分,為輸入和輸出部分的變量分配合適的寄存器,如果限定符指定為立即數(shù)?
(“i”),或內(nèi)存變量(“m”),則不需要該步驟,如果限定符沒有具體指定輸入操作數(shù)的?
類型(如“g”),GCC會視需要決定是否將該操作數(shù)輸入到某個寄存器。這樣每個占位符都與某個?
寄存器、內(nèi)存變量或立即數(shù)形成了一一對應(yīng)的關(guān)系。對分配了寄存器的輸入變量需要增加代碼?
將它的值讀入寄存器。另外還要根據(jù)破壞描述符的部分增加額外代碼。?
2.指令模板部分?
然后根據(jù)這種一一對應(yīng)的關(guān)系,用這些寄存器、內(nèi)存變量或立即數(shù)來取代匯編代碼中的占位符。?
3.變量輸出?
按照輸出限定符的指定將寄存器的內(nèi)容輸出到某個內(nèi)存變量中,如果輸出操作數(shù)的限定符指定為內(nèi)存變量(“m”),則該步驟被省略。
實例:比如需要在C程序中打印LR(r14)的值,那直接執(zhí)行如下:
long ret_reg;
asm volatile("mov ?%0, r14" : "=r" (ret_reg));
printk("enten into %s, the ret_reg is 0x%.8x\n", __func__, ret_reg);
另外可以看《LINUX內(nèi)核源代碼情景分析》
總結(jié)
以上是生活随笔為你收集整理的GCC如何编译内嵌汇编代码的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《Linux内核设计与实现》读书笔记(二
- 下一篇: 表扬好还是批评好?