偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

最牛X的 GCC 內(nèi)聯(lián)匯編

開(kāi)發(fā) 開(kāi)發(fā)工具
本文將講解 GCC 提供的內(nèi)聯(lián)匯編特性的用途和用法。對(duì)于閱讀這篇文章,這里只有兩個(gè)前提要求,很明顯,就是 x86 匯編語(yǔ)言和 C 語(yǔ)言的基本認(rèn)識(shí)。

 正如大家知道的,在C語(yǔ)言中插入?yún)R編語(yǔ)言,其是Linux中使用的基本匯編程序語(yǔ)法。本文將講解 GCC 提供的內(nèi)聯(lián)匯編特性的用途和用法。對(duì)于閱讀這篇文章,這里只有兩個(gè)前提要求,很明顯,就是 x86 匯編語(yǔ)言和 C 語(yǔ)言的基本認(rèn)識(shí)。

1. 簡(jiǎn)介

1.1 版權(quán)許可

Copyright (C) 2003 Sandeep S.

本文檔自由共享;你可以重新發(fā)布它,并且/或者在遵循自由軟件基金會(huì)發(fā)布的 GNU 通用公共許可證下修改它;也可以是該許可證的版本 2 或者(按照你的需求)更晚的版本。

發(fā)布這篇文檔是希望它能夠幫助別人,但是沒(méi)有任何擔(dān)保;甚至不包括可售性和適用于任何特定目的的擔(dān)保。關(guān)于更詳細(xì)的信息,可以查看 GNU 通用許可證。

1.2 反饋校正

請(qǐng)將反饋和批評(píng)一起提交給 Sandeep.S。我將感謝任何一個(gè)指出本文檔中錯(cuò)誤和不準(zhǔn)確之處的人;一被告知,我會(huì)馬上改正它們。

1.3 致謝

我對(duì)提供如此棒的特性的 GNU 人們表示真誠(chéng)的感謝。感謝 Mr.Pramode C E 所做的所有幫助。感謝在 Govt Engineering College 和 Trichur 的朋友們的精神支持和合作,尤其是 Nisha Kurur 和 Sakeeb S 。 感謝在 Gvot Engineering College 和 Trichur 的老師們的合作。

另外,感謝 Phillip , Brennan Underwood 和 colin@nyx.net ;這里的許多東西都厚顏地直接取自他們的工作成果。

[[171876]]

2. 概覽

在這里,我們將學(xué)習(xí) GCC 內(nèi)聯(lián)匯編。這里內(nèi)聯(lián)表示的是什么呢?

我們可以要求編譯器將一個(gè)函數(shù)的代碼插入到調(diào)用者代碼中函數(shù)被實(shí)際調(diào)用的地方。這樣的函數(shù)就是內(nèi)聯(lián)函數(shù)。這聽(tīng)起來(lái)和宏差不多?這兩者確實(shí)有相似之處。

內(nèi)聯(lián)函數(shù)的優(yōu)點(diǎn)是什么呢?

這種內(nèi)聯(lián)方法可以減少函數(shù)調(diào)用開(kāi)銷(xiāo)。同時(shí)如果所有實(shí)參的值為常量,它們的已知值可以在編譯期允許簡(jiǎn)化,因此并非所有的內(nèi)聯(lián)函數(shù)代碼都需要被包含進(jìn)去。代碼大小的影響是不可預(yù)測(cè)的,這取決于特定的情況。為了聲明一個(gè)內(nèi)聯(lián)函數(shù),我們必須在函數(shù)聲明中使用 "inline" 關(guān)鍵字。

現(xiàn)在我們正處于一個(gè)猜測(cè)內(nèi)聯(lián)匯編到底是什么的點(diǎn)上。它只不過(guò)是一些寫(xiě)為內(nèi)聯(lián)函數(shù)的匯編程序。在系統(tǒng)編程上,它們方便、快速并且極其有用。我們主要集中學(xué)習(xí)(GCC)內(nèi)聯(lián)匯編函數(shù)的基本格式和用法。為了聲明內(nèi)聯(lián)匯編函數(shù),我們使用 "asm" 關(guān)鍵詞。

內(nèi)聯(lián)匯編之所以重要,主要是因?yàn)樗梢圆僮鞑⑶沂蛊漭敵鐾ㄟ^(guò) C 變量顯示出來(lái)。正是因?yàn)榇四芰Γ?"asm" 可以用作匯編指令和包含它的 C 程序之間的接口。

3. GCC 匯編語(yǔ)法

[[171877]]

Linux上的 GNU C 編譯器 GCC ,使用 AT&T / UNIX 匯編語(yǔ)法。在這里,我們將使用 AT&T 語(yǔ)法 進(jìn)行匯編編碼。如果你對(duì) AT&T 語(yǔ)法不熟悉的話,請(qǐng)不要緊張,我會(huì)教你的。AT&T 語(yǔ)法和 Intel 語(yǔ)法的差別很大。我會(huì)給出主要的區(qū)別。

1).源操作數(shù)和目的操作數(shù)順序

AT&T 語(yǔ)法的操作數(shù)方向和 Intel 語(yǔ)法的剛好相反。在Intel 語(yǔ)法中,第一操作數(shù)為目的操作數(shù),第二操作數(shù)為源操作數(shù),然而在 AT&T 語(yǔ)法中,第一操作數(shù)為源操作數(shù),第二操作數(shù)為目的操作數(shù)。也就是說(shuō),

Intel 語(yǔ)法中的 "Op-code dst src" 變?yōu)?AT&T 語(yǔ)法中的 "Op-code src dst"。

2).寄存器命名

寄存器名稱(chēng)有 "%" 前綴,即如果必須使用 "eax",它應(yīng)該用作 "%eax"。

3).立即數(shù)

AT&T 立即數(shù)以 "$" 為前綴。靜態(tài) "C" 變量也使用 "$" 前綴。在 Intel 語(yǔ)法中,十六進(jìn)制常量以 "h" 為后綴,然而 AT&T 不使用這種語(yǔ)法,這里我們給常量添加前綴 "0x"。所以,對(duì)于十六進(jìn)制,我們首先看到一個(gè) "$",然后是 "0x",最后才是常量。

4).操作數(shù)大小

在 AT&T 語(yǔ)法中,存儲(chǔ)器操作數(shù)的大小取決于操作碼名字的最后一個(gè)字符。操作碼后綴 ’b’ 、’w’、’l’ 分別指明了字節(jié)(8位)、字(16位)、長(zhǎng)型(32位)存儲(chǔ)器引用。Intel 語(yǔ)法通過(guò)給存儲(chǔ)器操作數(shù)添加 "byte ptr"、 "word ptr" 和 "dword ptr" 前綴來(lái)實(shí)現(xiàn)這一功能。

因此,Intel的 "mov al, byte ptr foo" 在 AT&T 語(yǔ)法中為 "movb foo, %al"。

5).存儲(chǔ)器操作數(shù)

在 Intel 語(yǔ)法中,基址寄存器包含在 "[" 和 "]" 中,然而在 AT&T 中,它們變?yōu)?"(" 和 ")"。另外,在 Intel 語(yǔ)法中, 間接內(nèi)存引用為

"section:[base + index*scale + disp]",在 AT&T中變?yōu)?"section:disp(base, index, scale)"。

需要牢記的一點(diǎn)是,當(dāng)一個(gè)常量用于 disp 或 scale,不能添加 "$" 前綴。

現(xiàn)在我們看到了 Intel 語(yǔ)法和 AT&T 語(yǔ)法之間的一些主要差別。我僅僅寫(xiě)了它們差別的一部分而已。關(guān)于更完整的信息,請(qǐng)參考 GNU 匯編文檔?,F(xiàn)在為了更好地理解,我們可以看一些示例。

  1. +------------------------------+------------------------------------+ 
  2. |       Intel Code             |      AT&T Code                     | 
  3. +------------------------------+------------------------------------+ 
  4. | mov     eax,1                |  movl    $1,%eax                   |    
  5. | mov     ebx,0ffh             |  movl    $0xff,%ebx                |    
  6. int     80h                  |  int     $0x80                     |    
  7. | mov     ebx, eax             |  movl    %eax, %ebx                | 
  8. | mov     eax,[ecx]            |  movl    (%ecx),%eax               | 
  9. | mov     eax,[ebx+3]          |  movl    3(%ebx),%eax              |  
  10. | mov     eax,[ebx+20h]        |  movl    0x20(%ebx),%eax           | 
  11. add     eax,[ebx+ecx*2h]     |  addl    (%ebx,%ecx,0x2),%eax      | 
  12. | lea     eax,[ebx+ecx]        |  leal    (%ebx,%ecx),%eax          | 
  13. | sub     eax,[ebx+ecx*4h-20h] |  subl    -0x20(%ebx,%ecx,0x4),%eax | 
  14. +------------------------------+------------------------------------+ 

4. 基本內(nèi)聯(lián)

基本內(nèi)聯(lián)匯編的格式非常直接了當(dāng)。它的基本格式為:

asm("匯編代碼");

示例

  1. asm("movl %ecx %eax"); /* 將 ecx 寄存器的內(nèi)容移至 eax */ 
  2.  
  3. __asm__("movb %bh (%eax)"); /* 將 bh 的一個(gè)字節(jié)數(shù)據(jù) 移至 eax 寄存器指向的內(nèi)存 */ 

你可能注意到了這里我使用了 "asm" 和 "__asm__"。這兩者都是有效的。如果關(guān)鍵詞 "asm" 和我們程序的一些標(biāo)識(shí)符沖突了,我們可以使用 "__asm__"。如果我們的指令多于一條,我們可以每個(gè)一行,并用雙引號(hào)圈起,同時(shí)為每條指令添加 ’/n’ 和 ’/t’ 后綴。這是因?yàn)?gcc 將每一條當(dāng)作字符串發(fā)送給as(GAS)(LCTT 譯注: GAS 即 GNU 匯編器),并且通過(guò)使用換行符/制表符發(fā)送正確格式化后的行給匯編器。

示例

  1. __asm__ ("movl %eax, %ebx/n/t" 
  2.  
  3. "movl $56, %esi/n/t" 
  4.  
  5. "movl %ecx, $label(%edx,%ebx,$4)/n/t" 
  6.  
  7. "movb %ah, (%ebx)"); 

如果在代碼中,我們涉及到一些寄存器(即改變其內(nèi)容),但在沒(méi)有恢復(fù)這些變化的情況下從匯編中返回,這將會(huì)導(dǎo)致一些意想不到的事情。這是因?yàn)?GCC 并不知道寄存器內(nèi)容的變化,這會(huì)導(dǎo)致問(wèn)題,特別是當(dāng)編譯器做了某些優(yōu)化。在沒(méi)有告知 GCC 的情況下,它將會(huì)假設(shè)一些寄存器存儲(chǔ)了一些值——而我們可能已經(jīng)改變卻沒(méi)有告知 GCC——它會(huì)像什么事都沒(méi)發(fā)生一樣繼續(xù)運(yùn)行(LCTT 譯注:什么事都沒(méi)發(fā)生一樣是指GCC不會(huì)假設(shè)寄存器裝入的值是有效的,當(dāng)退出改變了寄存器值的內(nèi)聯(lián)匯編后,寄存器的值不會(huì)保存到相應(yīng)的變量或內(nèi)存空間)。我們所可以做的是使用那些沒(méi)有副作用的指令,或者當(dāng)我們退出時(shí)恢復(fù)這些寄存器,要不就等著程序崩潰吧。這是為什么我們需要一些擴(kuò)展功能,擴(kuò)展匯編給我們提供了那些功能。

5. 擴(kuò)展匯編

在基本內(nèi)聯(lián)匯編中,我們只有指令。然而在擴(kuò)展匯編中,我們可以同時(shí)指定操作數(shù)。它允許我們指定輸入寄存器、輸出寄存器以及修飾寄存器列表。GCC 不強(qiáng)制用戶必須指定使用的寄存器。我們可以把頭疼的事留給 GCC ,這可能可以更好地適應(yīng) GCC 的優(yōu)化。不管怎么說(shuō),基本格式為:

asm ( 匯編程序模板

: 輸出操作數(shù) /* 可選的 */

: 輸入操作數(shù) /* 可選的 */

: 修飾寄存器列表 /* 可選的 */

);

匯編程序模板由匯編指令組成。每一個(gè)操作數(shù)由一個(gè)操作數(shù)約束字符串所描述,其后緊接一個(gè)括弧括起的 C 表達(dá)式。冒號(hào)用于將匯編程序模板和第一個(gè)輸出操作數(shù)分開(kāi),另一個(gè)(冒號(hào))用于將最后一個(gè)輸出操作數(shù)和第一個(gè)輸入操作數(shù)分開(kāi)(如果存在的話)。逗號(hào)用于分離每一個(gè)組內(nèi)的操作數(shù)。總操作數(shù)的數(shù)目限制在 10 個(gè),或者機(jī)器描述中的任何指令格式中的最大操作數(shù)數(shù)目,以較大者為準(zhǔn)。

如果沒(méi)有輸出操作數(shù)但存在輸入操作數(shù),你必須將兩個(gè)連續(xù)的冒號(hào)放置于輸出操作數(shù)原本會(huì)放置的地方周?chē)?/p>

示例:

  1. asm ("cld/n/t" 
  2. "rep/n/t" 
  3. "stosl" 
  4. : /* 無(wú)輸出寄存器 */ 
  5. "c" (count), "a" (fill_value), "D" (dest) 
  6. "%ecx""%edi" 
  7. ); 

現(xiàn)在來(lái)看看這段代碼是干什么的?以上的內(nèi)聯(lián)匯編是將 "fill_value" 值連續(xù) "count" 次拷貝到寄存器 "edi" 所指位置(LCTT 譯注:每執(zhí)行 stosl 一次,寄存器 edi 的值會(huì)遞增或遞減,這取決于是否設(shè)置了 direction 標(biāo)志,因此以上代碼實(shí)則初始化一個(gè)內(nèi)存塊)。 它也告訴 gcc 寄存器 "ecx" 和 "edi" 一直無(wú)效。為了更加清晰地說(shuō)明,讓我們?cè)倏匆粋€(gè)示例。

  1. int a=10, b; 
  2. asm ("movl %1, %%eax; 
  3. movl %%eax, %0;" 
  4. :"=r"(b) /* 輸出 */  
  5. :"r"(a) /* 輸入 */ 
  6. :"%eax" /* 修飾寄存器 */  
  7. ); 

這里我們所做的是使用匯編指令使 ’b’ 變量的值等于 ’a’ 變量的值。一些有意思的地方是:

  • "b" 為輸出操作數(shù),用 %0 引用,并且 "a" 為輸入操作數(shù),用 %1 引用。
  • "r" 為操作數(shù)約束。之后我們會(huì)更詳細(xì)地了解約束(字符串)。目前,"r" 告訴 GCC 可以使用任一寄存器存儲(chǔ)操作數(shù)。輸出操作數(shù)約束應(yīng)該有一個(gè)約束修飾符 "=" 。這修飾符表明它是一個(gè)只讀的輸出操作數(shù)。
  • 寄存器名字以兩個(gè) % 為前綴。這有利于 GCC 區(qū)分操作數(shù)和寄存器。操作數(shù)以一個(gè) % 為前綴。
  • 第三個(gè)冒號(hào)之后的修飾寄存器 %eax 用于告訴 GCC %eax 的值將會(huì)在 "asm" 內(nèi)部被修改,所以 GCC 將不會(huì)使用此寄存器存儲(chǔ)任何其他值。

當(dāng) “asm” 執(zhí)行完畢, "b" 變量會(huì)映射到更新的值,因?yàn)樗恢付檩敵霾僮鲾?shù)。換句話說(shuō), “asm” 內(nèi) "b" 變量的修改應(yīng)該會(huì)被映射到 “asm” 外部。

現(xiàn)在,我們可以更詳細(xì)地看看每一個(gè)域。

1.匯編程序模板

匯編程序模板包含了被插入到 C 程序的匯編指令集。其格式為:每條指令用雙引號(hào)圈起,或者整個(gè)指令組用雙引號(hào)圈起。同時(shí)每條指令應(yīng)以分界符結(jié)尾。有效的分界符有換行符("/n")和分號(hào)(";")。"/n" 可以緊隨一個(gè)制表符("/t")。我們應(yīng)該都明白使用換行符或制表符的原因了吧(LCTT 譯注:就是為了排版和分隔)?和 C 表達(dá)式對(duì)應(yīng)的操作數(shù)使用 %0、%1 ... 等等表示。

2.操作數(shù)

C 表達(dá)式用作 “asm” 內(nèi)的匯編指令操作數(shù)。每個(gè)操作數(shù)前面是以雙引號(hào)圈起的操作數(shù)約束。對(duì)于輸出操作數(shù),在引號(hào)內(nèi)還有一個(gè)約束修飾符,其后緊隨一個(gè)用于表示操作數(shù)的 C 表達(dá)式。即,“操作數(shù)約束”(C 表達(dá)式)是一個(gè)通用格式。對(duì)于輸出操作數(shù),還有一個(gè)額外的修飾符。約束字符串主要用于決定操作數(shù)的尋址方式,同時(shí)也用于指定使用的寄存器。

如果我們使用的操作數(shù)多于一個(gè),那么每一個(gè)操作數(shù)用逗號(hào)隔開(kāi)。

在匯編程序模板中,每個(gè)操作數(shù)用數(shù)字引用。編號(hào)方式如下。如果總共有 n 個(gè)操作數(shù)(包括輸入和輸出操作數(shù)),那么第一個(gè)輸出操作數(shù)編號(hào)為 0 ,逐項(xiàng)遞增,并且最后一個(gè)輸入操作數(shù)編號(hào)為 n - 1 。操作數(shù)的最大數(shù)目在前一節(jié)我們講過(guò)。

輸出操作數(shù)表達(dá)式必須為左值。輸入操作數(shù)的要求不像這樣嚴(yán)格。它們可以為表達(dá)式。擴(kuò)展匯編特性常常用于編譯器所不知道的機(jī)器指令 ;-)。如果輸出表達(dá)式無(wú)法直接尋址(即,它是一個(gè)位域),我們的約束字符串必須給定一個(gè)寄存器。在這種情況下,GCC 將會(huì)使用該寄存器作為匯編的輸出,然后存儲(chǔ)該寄存器的內(nèi)容到輸出。

正如前面所陳述的一樣,普通的輸出操作數(shù)必須為只寫(xiě)的; GCC 將會(huì)假設(shè)指令前的操作數(shù)值是死的,并且不需要被(提前)生成。擴(kuò)展匯編也支持輸入-輸出或者讀-寫(xiě)操作數(shù)。

所以現(xiàn)在我們來(lái)關(guān)注一些示例。我們想要求一個(gè)數(shù)的5次方結(jié)果。為了計(jì)算該值,我們使用 "lea" 指令。

  1. asm ("leal (%1,%1,4), %0"  
  2. "=r" (five_times_x) 
  3. "r" (x) 
  4. ); 

這里我們的輸入為 x。我們不指定使用的寄存器。 GCC 將會(huì)選擇一些輸入寄存器,一個(gè)輸出寄存器,來(lái)做我們預(yù)期的工作。如果我們想要輸入和輸出放在同一個(gè)寄存器里,我們也可以要求 GCC 這樣做。這里我們使用那些讀-寫(xiě)操作數(shù)類(lèi)型。這里我們通過(guò)指定合適的約束來(lái)實(shí)現(xiàn)它。

  1. asm ("leal (%0,%0,4), %0" 
  2. "=r" (five_times_x) 
  3. "0" (x) 
  4. ); 

現(xiàn)在輸出和輸出操作數(shù)位于同一個(gè)寄存器。但是我們無(wú)法得知是哪一個(gè)寄存器。現(xiàn)在假如我們也想要指定操作數(shù)所在的寄存器,這里有一種方法。

  1. asm ("leal (%%ecx,%%ecx,4), %%ecx"  
  2. "=c" (x) 
  3. "c" (x) 
  4. ); 

在以上三個(gè)示例中,我們并沒(méi)有在修飾寄存器列表里添加任何寄存器,為什么?在頭兩個(gè)示例, GCC 決定了寄存器并且它知道發(fā)生了什么改變。在最后一個(gè)示例,我們不必將 'ecx' 添加到修飾寄存器列表(LCTT 譯注: 原文修飾寄存器列表這個(gè)單詞拼寫(xiě)有錯(cuò),這里已修正),gcc 知道它表示 x。因此,因?yàn)樗梢灾?"ecx" 的值,它就不被當(dāng)作修飾的(寄存器)了。

3.修飾寄存器列表

一些指令會(huì)破壞一些硬件寄存器內(nèi)容。我們不得不在修飾寄存器中列出這些寄存器,即匯編函數(shù)內(nèi)第三個(gè) ’:’ 之后的域。這可以通知 gcc 我們將會(huì)自己使用和修改這些寄存器,這樣 gcc 就不會(huì)假設(shè)存入這些寄存器的值是有效的。我們不用在這個(gè)列表里列出輸入、輸出寄存器。因?yàn)?gcc 知道 “asm” 使用了它們(因?yàn)樗鼈儽伙@式地指定為約束了)。如果指令隱式或顯式地使用了任何其他寄存器,(并且寄存器沒(méi)有出現(xiàn)在輸出或者輸出約束列表里),那么就需要在修飾寄存器列表中指定這些寄存器。

如果我們的指令可以修改條件碼寄存器(cc),我們必須將 "cc" 添加進(jìn)修飾寄存器列表。

如果我們的指令以不可預(yù)測(cè)的方式修改了內(nèi)存,那么需要將 "memory" 添加進(jìn)修飾寄存器列表。這可以使 GCC 不會(huì)在匯編指令間保持緩存于寄存器的內(nèi)存值。如果被影響的內(nèi)存不在匯編的輸入或輸出列表中,我們也必須添加 "volatile" 關(guān)鍵詞。

我們可以按我們的需求多次讀寫(xiě)修飾寄存器。參考一下模板內(nèi)的多指令示例;它假設(shè)子例程 _foo 接受寄存器 "eax" 和 "ecx" 里的參數(shù)。

  1. asm ("movl %0,%%eax; 
  2. movl %1,%%ecx; 
  3. call _foo" 
  4. : /* no outputs */ 
  5. "g" (from), "g" (to
  6. "eax""ecx"  
  7. ); 

4.Volatile ...?

如果你熟悉內(nèi)核源碼或者類(lèi)似漂亮的代碼,你一定見(jiàn)過(guò)許多聲明為 "volatile" 或者 "__volatile__"的函數(shù),其跟著一個(gè) "asm" 或者 "__asm__"。我之前提到過(guò)關(guān)鍵詞 "asm" 和 "__asm__"。那么什么是 "volatile" 呢?

如果我們的匯編語(yǔ)句必須在我們放置它的地方執(zhí)行(例如,不能為了優(yōu)化而被移出循環(huán)語(yǔ)句),將關(guān)鍵詞 "volatile" 放置在 asm 后面、()的前面。以防止它被移動(dòng)、刪除或者其他操作,我們將其聲明為 "asm volatile ( ... : ... : ... : ...);"

如果擔(dān)心發(fā)生沖突,請(qǐng)使用 "__volatile__"。

如果我們的匯編只是用于一些計(jì)算并且沒(méi)有任何副作用,不使用 "volatile" 關(guān)鍵詞會(huì)更好。不使用 "volatile" 可以幫助 gcc 優(yōu)化代碼并使代碼更漂亮。

在“一些實(shí)用的訣竅”一節(jié)中,我提供了多個(gè)內(nèi)聯(lián)匯編函數(shù)的例子。那里我們可以了解到修飾寄存器列表的細(xì)節(jié)。

6. 更多關(guān)于約束

到這個(gè)時(shí)候,你可能已經(jīng)了解到約束和內(nèi)聯(lián)匯編有很大的關(guān)聯(lián)。但我們對(duì)約束講的還不多。約束用于表明一個(gè)操作數(shù)是否可以位于寄存器和位于哪種寄存器;操作數(shù)是否可以為一個(gè)內(nèi)存引用和哪種地址;操作數(shù)是否可以為一個(gè)立即數(shù)和它可能的取值范圍(即值的范圍),等等。

6.1 常用約束/strong>

在許多約束中,只有小部分是常用的。我們來(lái)看看這些約束。

1. 寄存器操作數(shù)約束

當(dāng)使用這種約束指定操作數(shù)時(shí),它們存儲(chǔ)在通用寄存器(GPR)中。請(qǐng)看下面示例:

  1. asm ("movl %%eax, %0/n" :"=r"(myval)); 

這里,變量 myval 保存在寄存器中,寄存器 eax 的值被復(fù)制到該寄存器中,并且 myval 的值從寄存器更新到了內(nèi)存。當(dāng)指定 "r" 約束時(shí), gcc 可以將變量保存在任何可用的 GPR 中。要指定寄存器,你必須使用特定寄存器約束直接地指定寄存器的名字。它們?yōu)椋?/p>

  1. +---+--------------------+ 
  2. | r | Register(s) | 
  3. +---+--------------------+ 
  4. | a | %eax, %ax, %al | 
  5. | b | %ebx, %bx, %bl | 
  6. | c | %ecx, %cx, %cl | 
  7. | d | %edx, %dx, %dl | 
  8. | S | %esi, %si | 
  9. | D | %edi, %di | 
  10. +---+--------------------+ 

2. 內(nèi)存操作數(shù)約束

當(dāng)操作數(shù)位于內(nèi)存時(shí),任何對(duì)它們的操作將直接發(fā)生在內(nèi)存位置,這與寄存器約束相反,后者首先將值存儲(chǔ)在要修改的寄存器中,然后將它寫(xiě)回到內(nèi)存位置。但寄存器約束通常用于一個(gè)指令必須使用它們或者它們可以大大提高處理速度的地方。當(dāng)需要在 “asm” 內(nèi)更新一個(gè) C 變量,而又不想使用寄存器去保存它的值,使用內(nèi)存最為有效。例如,IDTR 寄存器的值存儲(chǔ)于內(nèi)存位置 loc 處:

  1. asm("sidt %0/n" : :"m"(loc)); 

3. 匹配(數(shù)字)約束

在某些情況下,一個(gè)變量可能既充當(dāng)輸入操作數(shù),也充當(dāng)輸出操作數(shù)。可以通過(guò)使用匹配約束在 "asm" 中指定這種情況。

  1. asm ("incl %0" :"=a"(var):"0"(var)); 

在操作數(shù)那一節(jié)中,我們也看到了一些類(lèi)似的示例。在這個(gè)匹配約束的示例中,寄存器 "%eax" 既用作輸入變量,也用作輸出變量。 var 輸入被讀進(jìn) %eax,并且等遞增后更新的 %eax 再次被存儲(chǔ)進(jìn) var。這里的 "0" 用于指定與第 0 個(gè)輸出變量相同的約束。也就是,它指定 var 輸出實(shí)例應(yīng)只被存儲(chǔ)在 "%eax" 中。該約束可用于:

在輸入從變量讀取或變量修改后且修改被寫(xiě)回同一變量的情況

在不需要將輸入操作數(shù)實(shí)例和輸出操作數(shù)實(shí)例分開(kāi)的情況

使用匹配約束最重要的意義在于它們可以有效地使用可用寄存器。

其他一些約束:

  • "m" : 允許一個(gè)內(nèi)存操作數(shù),可以使用機(jī)器普遍支持的任一種地址。
  • "o" : 允許一個(gè)內(nèi)存操作數(shù),但只有當(dāng)?shù)刂肥强善频摹<?,該地址加上一個(gè)小的偏移量可以得到一個(gè)有效地址。
  • "V" : 一個(gè)不允許偏移的內(nèi)存操作數(shù)。換言之,任何適合 "m" 約束而不適合 "o" 約束的操作數(shù)。
  • "i" : 允許一個(gè)(帶有常量)的立即整形操作數(shù)。這包括其值僅在匯編時(shí)期知道的符號(hào)常量。
  • "n" : 允許一個(gè)帶有已知數(shù)字的立即整形操作數(shù)。許多系統(tǒng)不支持匯編時(shí)期的常量,因?yàn)椴僮鲾?shù)少于一個(gè)字寬。對(duì)于此種操作數(shù),約束應(yīng)該使用 'n' 而不是'i'。
  • "g" : 允許任一寄存器、內(nèi)存或者立即整形操作數(shù),不包括通用寄存器之外的寄存器。

以下約束為 x86 特有。

  • "r" : 寄存器操作數(shù)約束,查看上面給定的表格。
  • "q" : 寄存器 a、b、c 或者 d。
  • "I" : 范圍從 0 到 31 的常量(對(duì)于 32 位移位)。
  • "J" : 范圍從 0 到 63 的常量(對(duì)于 64 位移位)。
  • "K" : 0xff。
  • "L" : 0xffff。
  • "M" : 0、1、2 或 3 (lea 指令的移位)。
  • "N" : 范圍從 0 到 255 的常量(對(duì)于 out 指令)。
  • "f" : 浮點(diǎn)寄存器
  • "t" : 第一個(gè)(棧頂)浮點(diǎn)寄存器
  • "u" : 第二個(gè)浮點(diǎn)寄存器
  • "A" : 指定 "a" 或 "d" 寄存器。這主要用于想要返回 64 位整形數(shù),使用 "d" 寄存器保存最高有效位和 "a" 寄存器保存最低有效位。

6.2 約束修飾符

當(dāng)使用約束時(shí),對(duì)于更精確的控制超過(guò)了對(duì)約束作用的需求,GCC 給我們提供了約束修飾符。最常用的約束修飾符為:

  • "=" : 意味著對(duì)于這條指令,操作數(shù)為只寫(xiě)的;舊值會(huì)被忽略并被輸出數(shù)據(jù)所替換。
  • "&" : 意味著這個(gè)操作數(shù)為一個(gè)早期改動(dòng)的操作數(shù),其在該指令完成前通過(guò)使用輸入操作數(shù)被修改了。因此,這個(gè)操作數(shù)不可以位于一個(gè)被用作輸出操作數(shù)或任何內(nèi)存地址部分的寄存器。如果在舊值被寫(xiě)入之前它僅用作輸入而已,一個(gè)輸入操作數(shù)可以為一個(gè)早期改動(dòng)操作數(shù)。

上述的約束列表和解釋并不完整。示例可以讓我們對(duì)內(nèi)聯(lián)匯編的用途和用法更好的理解。在下一節(jié),我們會(huì)看到一些示例,在那里我們會(huì)發(fā)現(xiàn)更多關(guān)于修飾寄存器列表的東西。

7. 一些實(shí)用的訣竅

現(xiàn)在我們已經(jīng)介紹了關(guān)于 GCC 內(nèi)聯(lián)匯編的基礎(chǔ)理論,現(xiàn)在我們將專(zhuān)注于一些簡(jiǎn)單的例子。將內(nèi)聯(lián)匯編函數(shù)寫(xiě)成宏的形式總是非常方便的。我們可以在 Linux 內(nèi)核代碼里看到許多匯編函數(shù)。(usr/src/linux/include/asm/*.h)。

1).首先我們從一個(gè)簡(jiǎn)單的例子入手。我們將寫(xiě)一個(gè)兩個(gè)數(shù)相加的程序。

  1. int main(void)  
  2.  
  3. int foo = 10, bar = 15; 
  4. __asm__ __volatile__("addl %%ebx,%%eax"  
  5. :"=a"(foo)  
  6. :"a"(foo), "b"(bar)  
  7. );  
  8. printf("foo+bar=%d/n", foo); 
  9. return 0; 

這里我們要求 GCC 將 foo 存放于 %eax,將 bar 存放于 %ebx,同時(shí)我們也想要在 %eax 中存放結(jié)果。'=' 符號(hào)表示它是一個(gè)輸出寄存器。現(xiàn)在我們可以以其他方式將一個(gè)整數(shù)加到一個(gè)變量。

  1. __asm__ __volatile__(  
  2. " lock ;/n"  
  3. " addl %1,%0 ;/n"  
  4. "=m" (my_var)  
  5. "ir" (my_int), "m" (my_var)  
  6. : /* 無(wú)修飾寄存器列表 */  
  7. ); 

這是一個(gè)原子加法。為了移除原子性,我們可以移除指令 'lock'。在輸出域中,"=m" 表明 myvar 是一個(gè)輸出且位于內(nèi)存。類(lèi)似地,"ir" 表明 myint 是一個(gè)整型,并應(yīng)該存在于其他寄存器(回想我們上面看到的表格)。沒(méi)有寄存器位于修飾寄存器列表中。

[[171878]]

2).現(xiàn)在我們將在一些寄存器/變量上展示一些操作,并比較值。

  1. __asm__ __volatile__( "decl %0; sete %1"  
  2. "=m" (my_var), "=q" (cond)  
  3. "m" (my_var)  
  4. "memory"  
  5. ); 

這里,my_var 的值減 1 ,并且如果結(jié)果的值為 0,則變量 cond 置 1。我們可以通過(guò)將指令 "lock;/n/t" 添加為匯編模板的第一條指令以增加原子性。

以類(lèi)似的方式,為了增加 my_var,我們可以使用 "incl %0" 而不是 "decl %0"。

這里需要注意的地方是(i)my_var 是一個(gè)存儲(chǔ)于內(nèi)存的變量。(ii)cond 位于寄存器 eax、ebx、ecx、edx 中的任何一個(gè)。約束 "=q" 保證了這一點(diǎn)。(iii)同時(shí)我們可以看到 memory 位于修飾寄存器列表中。也就是說(shuō),代碼將改變內(nèi)存中的內(nèi)容。

3).如何置 1 或清 0 寄存器中的一個(gè)比特位。作為下一個(gè)訣竅,我們將會(huì)看到它。

  1. __asm__ __volatile__( "btsl %1,%0" 
  2. "=m" (ADDR) 
  3. "Ir" (pos) 
  4. "cc"  
  5. ); 

這里,ADDR 變量(一個(gè)內(nèi)存變量)的 'pos' 位置上的比特被設(shè)置為 1。我們可以使用 'btrl' 來(lái)清除由 'btsl' 設(shè)置的比特位。pos 的約束 "Ir" 表明 pos 位于寄存器,并且它的值為 0-31(x86 相關(guān)約束)。也就是說(shuō),我們可以設(shè)置/清除 ADDR 變量上第 0 到 31 位的任一比特位。因?yàn)闂l件碼會(huì)被改變,所以我們將 "cc" 添加進(jìn)修飾寄存器列表。

4).現(xiàn)在我們看看一些更為復(fù)雜而有用的函數(shù)。字符串拷貝。

  1. static inline char * strcpy(char * dest,const char *src) 
  2. int d0, d1, d2;  
  3. __asm__ __volatile__( "1:/tlodsb/n/t" 
  4. "stosb/n/t"  
  5. "testb %%al,%%al/n/t"  
  6. "jne 1b"  
  7. "=&S" (d0), "=&D" (d1), "=&a" (d2)  
  8. "0" (src),"1" (dest)  
  9. "memory");  
  10. return dest;  

源地址存放于 esi,目標(biāo)地址存放于 edi,同時(shí)開(kāi)始拷貝,當(dāng)我們到達(dá) 0 時(shí),拷貝完成。約束 "&S"、"&D"、"&a" 表明寄存器 esi、edi 和 eax 早期修飾寄存器,也就是說(shuō),它們的內(nèi)容在函數(shù)完成前會(huì)被改變。這里很明顯可以知道為什么 "memory" 會(huì)放在修飾寄存器列表。

我們可以看到一個(gè)類(lèi)似的函數(shù),它能移動(dòng)雙字塊數(shù)據(jù)。注意函數(shù)被聲明為一個(gè)宏。

  1. #define mov_blk(src, dest, numwords) /  
  2. __asm__ __volatile__ ( /  
  3. "cld/n/t" /  
  4. "rep/n/t" /  
  5. "movsl" /  
  6. : /  
  7. "S" (src), "D" (dest), "c" (numwords) /  
  8. "%ecx""%esi""%edi" /  

這里我們沒(méi)有輸出,寄存器 ecx、esi和 edi 的內(nèi)容發(fā)生了改變,這是塊移動(dòng)的副作用。因此我們必須將它們添加進(jìn)修飾寄存器列表。

5).在 Linux 中,系統(tǒng)調(diào)用使用 GCC 內(nèi)聯(lián)匯編實(shí)現(xiàn)。讓我們看看如何實(shí)現(xiàn)一個(gè)系統(tǒng)調(diào)用。所有的系統(tǒng)調(diào)用被寫(xiě)成宏(linux/unistd.h)。例如,帶有三個(gè)參數(shù)的系統(tǒng)調(diào)用被定義為如下所示的宏。

  1. type name(type1 arg1,type2 arg2,type3 arg3) /  
  2. { /  
  3. long __res; /  
  4. __asm__ volatile ( "int $0x80" /  
  5. "=a" (__res) /  
  6. "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), / 
  7. "d" ((long)(arg3))); / 
  8. __syscall_return(type,__res); / 

無(wú)論何時(shí)調(diào)用帶有三個(gè)參數(shù)的系統(tǒng)調(diào)用,以上展示的宏就會(huì)用于執(zhí)行調(diào)用。系統(tǒng)調(diào)用號(hào)位于 eax 中,每個(gè)參數(shù)位于 ebx、ecx、edx 中。最后 "int 0x80" 是一條用于執(zhí)行系統(tǒng)調(diào)用的指令。返回值被存儲(chǔ)于 eax 中。

每個(gè)系統(tǒng)調(diào)用都以類(lèi)似的方式實(shí)現(xiàn)。Exit 是一個(gè)單一參數(shù)的系統(tǒng)調(diào)用,讓我們看看它的代碼看起來(lái)會(huì)是怎樣。它如下所示。

  1.  
  2. asm("movl $1,%%eax; /* SYS_exit is 1 */ 
  3. xorl %%ebx,%%ebx; /* Argument is in ebx, it is 0 */ 
  4. int $0x80" /* Enter kernel mode */ 
  5. );  

Exit 的系統(tǒng)調(diào)用號(hào)是 1,同時(shí)它的參數(shù)是 0。因此我們分配 eax 包含 1,ebx 包含 0,同時(shí)通過(guò) "int $0x80" 執(zhí)行 "exit(0)"。這就是 exit 的工作原理。

8. 結(jié)束語(yǔ)

這篇文檔已經(jīng)將 GCC 內(nèi)聯(lián)匯編過(guò)了一遍。一旦你理解了基本概念,你就可以按照自己的需求去使用它們了。我們看了許多例子,它們有助于理解 GCC 內(nèi)聯(lián)匯編的常用特性。

GCC 內(nèi)聯(lián)是一個(gè)極大的主題,這篇文章是不完整的。更多關(guān)于我們討論過(guò)的語(yǔ)法細(xì)節(jié)可以在 GNU 匯編器的官方文檔上獲取。類(lèi)似地,要獲取完整的約束列表,可以參考 GCC 的官方文檔。

當(dāng)然,Linux 內(nèi)核大量地使用了 GCC 內(nèi)聯(lián)。因此我們可以在內(nèi)核源碼中發(fā)現(xiàn)許多各種各樣的例子。它們可以幫助我們很多。

如果你發(fā)現(xiàn)任何的錯(cuò)別字,或者本文中的信息已經(jīng)過(guò)時(shí),請(qǐng)告訴我們。

責(zé)任編輯:武曉燕 來(lái)源: Linux就該這么學(xué)
相關(guān)推薦

2011-01-14 14:39:32

Linux匯編語(yǔ)言

2013-01-08 11:02:26

IBMdW

2012-10-18 18:40:24

2017-03-08 13:12:44

編程學(xué)習(xí)

2014-04-23 16:31:42

Windows背景音樂(lè)

2021-04-27 07:59:11

內(nèi)聯(lián)匯編 C 語(yǔ)言 asm 關(guān)鍵字

2024-09-19 17:52:47

2017-03-22 13:59:19

搜索分析

2015-12-03 10:14:04

2010-07-28 08:47:06

Eclipse插件Eclipse

2009-10-16 09:19:23

史上最牛10大最討厭詞句

2013-07-15 14:51:55

2012-05-01 20:26:01

iPhone

2015-07-10 09:11:06

編程編碼套路

2015-12-03 14:09:28

創(chuàng)始人成功創(chuàng)業(yè)

2019-07-25 10:45:05

GitHub技巧網(wǎng)站

2010-11-02 10:15:44

HPCNvidiaTesla GPU

2020-12-14 08:48:45

C語(yǔ)言嵌入式gcc

2015-09-10 13:11:20

七牛

2016-04-25 10:47:49

源碼閱讀學(xué)習(xí)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)