在內(nèi)嵌匯編中,可以將C語言表達(dá)式指定為匯編指令的操作數(shù),而且不用去管如何將C語言表達(dá)式的值讀入哪個(gè)寄存器,以及如何將計(jì)算結(jié)果寫回C 變量,你只要告訴程序中C語言表達(dá)式與匯編指令操作數(shù)之間的對應(yīng)關(guān)系即可, GCC會自動(dòng)插入代碼完成必要的操作。 簡單的內(nèi)嵌匯編例:
__asm__ __volatile__("hlt");"__asm__"表示后面的代碼為內(nèi)嵌匯編,"asm"是"__asm__"的別名。"__volatile__"表示編譯器不要優(yōu)化代碼,后面的指令 保留原樣,"volatile"是它的別名。括號里面是匯編指令。
內(nèi)嵌匯編舉例??
使用內(nèi)嵌匯編,要先編寫匯編指令模板,然后將C語言表達(dá)式與指令的操作數(shù)相關(guān)聯(lián),并告訴GCC對這些操作有哪些限制條件。 例如在下面的匯編語句:?
__asm__ __violate__ ("movl %1,%0" : "=r" (result) : "r" (input));"movl? %1,%0"是指令模板;"%0"和"%1"代表指令的操作數(shù),稱為占位符,內(nèi)嵌匯編靠它們將C 語言表達(dá)式與指令操作數(shù)相對應(yīng)。 ? 指令模板后面用小括號括起來的是C語言表達(dá)式,本例中只有兩個(gè):"result"和"input",他們按照出現(xiàn)的順序分 別與指令操作數(shù)"%0","%1"對應(yīng); 注意對應(yīng)順序:第一個(gè)C 表達(dá)式對應(yīng)"%0";第二個(gè)表達(dá)式對應(yīng)"%1",依次類推,操作數(shù)至多有10 個(gè),分別"%0","%1"...."%9"表示。在每個(gè)操作數(shù)前面有一個(gè)用引號括起來的字符串,字符串的內(nèi)容是對該操作數(shù)的限制或者說要求。 "result"前面的限制字符串是"=r",其中"="表示"result"是輸出操作數(shù),"r" 表示需要將"result"與某個(gè)通用寄存器相關(guān)聯(lián),先將操作數(shù)的值讀入寄存器,然后在指令中使用相應(yīng)寄存器,而不是"result"本身。 當(dāng)然指令執(zhí)行完后需要將寄存器中的值存入變量"result",從表面上看好像是指令直接對"result"進(jìn)行操作,實(shí)際上GCC做了隱式處理,這樣我們可以少寫一 些指令。 "input"前面的"r"表示該表達(dá)式需要先放入某個(gè)寄存器,然后在指令中使用該寄存器參加運(yùn)算。 C表達(dá)式或者變量與寄存器的關(guān)系由GCC自動(dòng)處理,我們只需使用限制字符串指導(dǎo)GCC如何處理即可。 限制字符必須與指令對操作數(shù)的要求相匹配,否則產(chǎn)生的 匯編代碼將會有錯(cuò),讀者可以將上例中的兩個(gè)"r",都改為"m"(m表示操作數(shù)放在內(nèi)存,而不是寄存器中),編譯后得到的結(jié)果是: ?
movl input, result? 很明顯這是一條非法指令,因此限制字符串必須與指令對操作數(shù)的要求匹配。例如指令movl允許寄存器到寄存器,立即數(shù)到寄存器等,但是不允許內(nèi)存到內(nèi)存的操作,因此兩個(gè)操作數(shù)不能同時(shí)使用"m"作為限定字符。 內(nèi)嵌匯編語法如下: ?
__asm__(匯編語句模板: 輸出部分: 輸入部分: 破壞描述部分)? 共四個(gè)部分:匯編語句模板,輸出部分,輸入部分,破壞描述部分,各部分使用":"格開,匯編語句模板必不可少,其他三部分可選,如果使用了后面的部分,而前面部分為空,也需要用":"格開,相應(yīng)部分內(nèi)容為空。 例如:
__asm__ __volatile__("cli": : :"memory")? 匯編語句模板
__asm__ __volatile__("pushfl ; popl %0 ; cli":"=g" (x) )? 描述符字符串表示對該變量的限制條件,這樣GCC 就可以根據(jù)這些條件決定如何分配寄存器,如何產(chǎn)生必要的代碼處理指令操作數(shù)與C表達(dá)式或C變量之間的聯(lián)系。 ?
__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。第一個(gè)占位符%0與C 語言變量ADDR對應(yīng),第二個(gè)占位符%1與C語言變量nr對應(yīng)。 因此上面的匯編語句代碼與下面的偽代碼等價(jià):
btsl nr, ADDR? 該指令的兩個(gè)操作數(shù)不能全是內(nèi)存變量,因此將nr的限定字符串指定為"Ir",將nr 與立即數(shù)或者寄存器相關(guān)聯(lián),這樣兩個(gè)操作數(shù)中只有ADDR為內(nèi)存變量。 ? 限制字符 限制字符有很多種,有些是與特定體系結(jié)構(gòu)相關(guān),此處僅列出常用的限定字符和i386中可能用到的一些常用的限定符。它們的作用是指示編譯器如何處理其后的C語言變量與指令操作數(shù)之間的關(guān)系。 限制字符列表如下:
通用寄存器 "a" 將輸入變量放入eax這里有一個(gè)問題:假設(shè)eax已經(jīng)被使用,那怎么辦?其實(shí)很簡單:因?yàn)镚CC 知道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中的一個(gè) "r" 將輸入變量放入通用寄存器,也就是eax,ebx,ecx,edx,esi,edi中的一個(gè) "A" 把eax和edx合成一個(gè)64 位的寄存器(use long longs) 內(nèi)存 "m" 內(nèi)存變量 "o" 操作數(shù)為內(nèi)存變量,但是其尋址方式是偏移量類型,也即是基址尋址,或者是基址加變址尋址。 "V" 操作數(shù)為內(nèi)存變量,但尋址方式不是偏移量類型 " " 操作數(shù)為內(nèi)存變量,但尋址方式為自動(dòng)增量 "p" 操作數(shù)是一個(gè)合法的內(nèi)存地址(指針) 寄存器或內(nèi)存 "g" 將輸入變量放入eax,ebx,ecx,edx中的一個(gè)或者作為內(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 ", 表示用它限制的操作數(shù)與某個(gè)指定的操作數(shù)匹配, "1" ... 也即該操作數(shù)就是指定的那個(gè)操作數(shù),例如"0" "9" 去描述"%1"操作數(shù),那么"%1"引用的其實(shí)就是"%0"操作數(shù),注意作為限定符字母的0-9與指令中的"%0"-"%9"的區(qū)別,前者描述操作數(shù),后者代表操作數(shù)。 & 該輸出操作數(shù)不能使用過和輸入操作數(shù)相同的寄存器 操作數(shù)類型 "=" 操作數(shù)在指令中是只寫的(輸出操作數(shù)) "+" 操作數(shù)在指令中是讀寫類型的(輸入輸出操作數(shù)) 浮點(diǎn)數(shù) "f" 浮點(diǎn)寄存器 "t" 第一個(gè)浮點(diǎn)寄存器 "u" 第二個(gè)浮點(diǎn)寄存器 "G" 標(biāo)準(zhǔn)的80387浮點(diǎn)常數(shù) % 該操作數(shù)可以和下一個(gè)操作數(shù)交換位置,例如addl的兩個(gè)操作數(shù)可以交換順序(當(dāng)然兩個(gè)操作數(shù)都不能是立即數(shù)) # 部分注釋,從該字符到其后的逗號之間所有字母被忽略 * 表示如果選用寄存器,則其后的字母被忽略? 破壞描述部分 ?
破壞描述符用于通知編譯器我們使用了哪些寄存器或內(nèi)存,由逗號格開的字符串組成,每個(gè)字符串描述一種情況,一般是寄存器名;除寄存器外還有"memory"。 ? 例如:"%eax","%ebx","memory"等。"memory"比較特殊,可能是內(nèi)嵌匯編中最難懂部分。為解釋清楚它,先介紹一下編譯器的優(yōu)化知識,再看C關(guān)鍵字volatile。最后去看該描述符。
void Barrier(void);? 這個(gè)函數(shù)通知編譯器插入一個(gè)內(nèi)存屏障,但對硬件無效,編譯后的代碼會把當(dāng)前CPU寄存器中的所有修改過的數(shù)值存入內(nèi)存,需要這些數(shù)據(jù)的時(shí)候再重新從內(nèi)存中讀出。
? C語言關(guān)鍵字volatile
DWORD __stdcall threadFunc(LPVOID signal){ int* intSignal=reinterpret_cast? 該線程啟動(dòng)時(shí)將intSignal 置為2,然后循環(huán)等待直到intSignal 為1 時(shí)退出。顯然intSignal的值必須在外部被改變,否則該線程不會退出。但是實(shí)際運(yùn)行的時(shí)候該線程卻不會退出,即使在外部將它的值改為1,看一下對應(yīng)的偽匯編代碼就明白了:(signal); *intSignal=2; while(*intSignal!=1) sleep(1000); return 0; }
mov ax,signal label: if(ax!=1) goto label? 對于C編譯器來說,它并不知道這個(gè)值會被其他線程修改。自然就把它c(diǎn)ache在寄存器里面。記住,C 編譯器是沒有線程概念的!這時(shí)候就需要用到volatile。volatile 的本意是指:這個(gè)值可能會在當(dāng)前線程外部被改變。 ? 也就是說,我們要在threadFunc中的intSignal前面加上volatile關(guān)鍵字,這時(shí) 候,編譯器知道該變量的值會在外部改變,因此每次訪問該變量時(shí)會重新讀取,所作的循環(huán)變?yōu)槿缦旅鎮(zhèn)未a所示:???
label: mov ax,signal if(ax!=1) goto label? Memory
編輯:黃飛
評論
查看更多