精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

最牛X的 GCC 內聯匯編

開發 開發工具
本文將講解 GCC 提供的內聯匯編特性的用途和用法。對于閱讀這篇文章,這里只有兩個前提要求,很明顯,就是 x86 匯編語言和 C 語言的基本認識。

 正如大家知道的,在C語言中插入匯編語言,其是Linux中使用的基本匯編程序語法。本文將講解 GCC 提供的內聯匯編特性的用途和用法。對于閱讀這篇文章,這里只有兩個前提要求,很明顯,就是 x86 匯編語言和 C 語言的基本認識。

1. 簡介

1.1 版權許可

Copyright (C) 2003 Sandeep S.

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

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

1.2 反饋校正

請將反饋和批評一起提交給 Sandeep.S。我將感謝任何一個指出本文檔中錯誤和不準確之處的人;一被告知,我會馬上改正它們。

1.3 致謝

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

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

[[171876]]

2. 概覽

在這里,我們將學習 GCC 內聯匯編。這里內聯表示的是什么呢?

我們可以要求編譯器將一個函數的代碼插入到調用者代碼中函數被實際調用的地方。這樣的函數就是內聯函數。這聽起來和宏差不多?這兩者確實有相似之處。

內聯函數的優點是什么呢?

這種內聯方法可以減少函數調用開銷。同時如果所有實參的值為常量,它們的已知值可以在編譯期允許簡化,因此并非所有的內聯函數代碼都需要被包含進去。代碼大小的影響是不可預測的,這取決于特定的情況。為了聲明一個內聯函數,我們必須在函數聲明中使用 "inline" 關鍵字。

現在我們正處于一個猜測內聯匯編到底是什么的點上。它只不過是一些寫為內聯函數的匯編程序。在系統編程上,它們方便、快速并且極其有用。我們主要集中學習(GCC)內聯匯編函數的基本格式和用法。為了聲明內聯匯編函數,我們使用 "asm" 關鍵詞。

內聯匯編之所以重要,主要是因為它可以操作并且使其輸出通過 C 變量顯示出來。正是因為此能力, "asm" 可以用作匯編指令和包含它的 C 程序之間的接口。

3. GCC 匯編語法

[[171877]]

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

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

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

Intel 語法中的 "Op-code dst src" 變為 AT&T 語法中的 "Op-code src dst"。

2).寄存器命名

寄存器名稱有 "%" 前綴,即如果必須使用 "eax",它應該用作 "%eax"。

3).立即數

AT&T 立即數以 "$" 為前綴。靜態 "C" 變量也使用 "$" 前綴。在 Intel 語法中,十六進制常量以 "h" 為后綴,然而 AT&T 不使用這種語法,這里我們給常量添加前綴 "0x"。所以,對于十六進制,我們首先看到一個 "$",然后是 "0x",最后才是常量。

4).操作數大小

在 AT&T 語法中,存儲器操作數的大小取決于操作碼名字的最后一個字符。操作碼后綴 ’b’ 、’w’、’l’ 分別指明了字節(8位)、字(16位)、長型(32位)存儲器引用。Intel 語法通過給存儲器操作數添加 "byte ptr"、 "word ptr" 和 "dword ptr" 前綴來實現這一功能。

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

5).存儲器操作數

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

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

需要牢記的一點是,當一個常量用于 disp 或 scale,不能添加 "$" 前綴。

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

  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. 基本內聯

基本內聯匯編的格式非常直接了當。它的基本格式為:

asm("匯編代碼");

示例

  1. asm("movl %ecx %eax"); /* 將 ecx 寄存器的內容移至 eax */ 
  2.  
  3. __asm__("movb %bh (%eax)"); /* 將 bh 的一個字節數據 移至 eax 寄存器指向的內存 */ 

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

示例

  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)"); 

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

5. 擴展匯編

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

asm ( 匯編程序模板

: 輸出操作數 /* 可選的 */

: 輸入操作數 /* 可選的 */

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

);

匯編程序模板由匯編指令組成。每一個操作數由一個操作數約束字符串所描述,其后緊接一個括弧括起的 C 表達式。冒號用于將匯編程序模板和第一個輸出操作數分開,另一個(冒號)用于將最后一個輸出操作數和第一個輸入操作數分開(如果存在的話)。逗號用于分離每一個組內的操作數??偛僮鲾档臄的肯拗圃? 10 個,或者機器描述中的任何指令格式中的最大操作數數目,以較大者為準。

如果沒有輸出操作數但存在輸入操作數,你必須將兩個連續的冒號放置于輸出操作數原本會放置的地方周圍。

示例:

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

現在來看看這段代碼是干什么的?以上的內聯匯編是將 "fill_value" 值連續 "count" 次拷貝到寄存器 "edi" 所指位置(LCTT 譯注:每執行 stosl 一次,寄存器 edi 的值會遞增或遞減,這取決于是否設置了 direction 標志,因此以上代碼實則初始化一個內存塊)。 它也告訴 gcc 寄存器 "ecx" 和 "edi" 一直無效。為了更加清晰地說明,讓我們再看一個示例。

  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" 為輸出操作數,用 %0 引用,并且 "a" 為輸入操作數,用 %1 引用。
  • "r" 為操作數約束。之后我們會更詳細地了解約束(字符串)。目前,"r" 告訴 GCC 可以使用任一寄存器存儲操作數。輸出操作數約束應該有一個約束修飾符 "=" 。這修飾符表明它是一個只讀的輸出操作數。
  • 寄存器名字以兩個 % 為前綴。這有利于 GCC 區分操作數和寄存器。操作數以一個 % 為前綴。
  • 第三個冒號之后的修飾寄存器 %eax 用于告訴 GCC %eax 的值將會在 "asm" 內部被修改,所以 GCC 將不會使用此寄存器存儲任何其他值。

當 “asm” 執行完畢, "b" 變量會映射到更新的值,因為它被指定為輸出操作數。換句話說, “asm” 內 "b" 變量的修改應該會被映射到 “asm” 外部。

現在,我們可以更詳細地看看每一個域。

1.匯編程序模板

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

2.操作數

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

如果我們使用的操作數多于一個,那么每一個操作數用逗號隔開。

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

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

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

所以現在我們來關注一些示例。我們想要求一個數的5次方結果。為了計算該值,我們使用 "lea" 指令。

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

這里我們的輸入為 x。我們不指定使用的寄存器。 GCC 將會選擇一些輸入寄存器,一個輸出寄存器,來做我們預期的工作。如果我們想要輸入和輸出放在同一個寄存器里,我們也可以要求 GCC 這樣做。這里我們使用那些讀-寫操作數類型。這里我們通過指定合適的約束來實現它。

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

現在輸出和輸出操作數位于同一個寄存器。但是我們無法得知是哪一個寄存器?,F在假如我們也想要指定操作數所在的寄存器,這里有一種方法。

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

在以上三個示例中,我們并沒有在修飾寄存器列表里添加任何寄存器,為什么?在頭兩個示例, GCC 決定了寄存器并且它知道發生了什么改變。在最后一個示例,我們不必將 'ecx' 添加到修飾寄存器列表(LCTT 譯注: 原文修飾寄存器列表這個單詞拼寫有錯,這里已修正),gcc 知道它表示 x。因此,因為它可以知道 "ecx" 的值,它就不被當作修飾的(寄存器)了。

3.修飾寄存器列表

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

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

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

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

  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 ...?

如果你熟悉內核源碼或者類似漂亮的代碼,你一定見過許多聲明為 "volatile" 或者 "__volatile__"的函數,其跟著一個 "asm" 或者 "__asm__"。我之前提到過關鍵詞 "asm" 和 "__asm__"。那么什么是 "volatile" 呢?

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

如果擔心發生沖突,請使用 "__volatile__"。

如果我們的匯編只是用于一些計算并且沒有任何副作用,不使用 "volatile" 關鍵詞會更好。不使用 "volatile" 可以幫助 gcc 優化代碼并使代碼更漂亮。

在“一些實用的訣竅”一節中,我提供了多個內聯匯編函數的例子。那里我們可以了解到修飾寄存器列表的細節。

6. 更多關于約束

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

6.1 常用約束/strong>

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

1. 寄存器操作數約束

當使用這種約束指定操作數時,它們存儲在通用寄存器(GPR)中。請看下面示例:

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

這里,變量 myval 保存在寄存器中,寄存器 eax 的值被復制到該寄存器中,并且 myval 的值從寄存器更新到了內存。當指定 "r" 約束時, gcc 可以將變量保存在任何可用的 GPR 中。要指定寄存器,你必須使用特定寄存器約束直接地指定寄存器的名字。它們為:

  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. 內存操作數約束

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

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

3. 匹配(數字)約束

在某些情況下,一個變量可能既充當輸入操作數,也充當輸出操作數??梢酝ㄟ^使用匹配約束在 "asm" 中指定這種情況。

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

在操作數那一節中,我們也看到了一些類似的示例。在這個匹配約束的示例中,寄存器 "%eax" 既用作輸入變量,也用作輸出變量。 var 輸入被讀進 %eax,并且等遞增后更新的 %eax 再次被存儲進 var。這里的 "0" 用于指定與第 0 個輸出變量相同的約束。也就是,它指定 var 輸出實例應只被存儲在 "%eax" 中。該約束可用于:

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

在不需要將輸入操作數實例和輸出操作數實例分開的情況

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

其他一些約束:

  • "m" : 允許一個內存操作數,可以使用機器普遍支持的任一種地址。
  • "o" : 允許一個內存操作數,但只有當地址是可偏移的。即,該地址加上一個小的偏移量可以得到一個有效地址。
  • "V" : 一個不允許偏移的內存操作數。換言之,任何適合 "m" 約束而不適合 "o" 約束的操作數。
  • "i" : 允許一個(帶有常量)的立即整形操作數。這包括其值僅在匯編時期知道的符號常量。
  • "n" : 允許一個帶有已知數字的立即整形操作數。許多系統不支持匯編時期的常量,因為操作數少于一個字寬。對于此種操作數,約束應該使用 'n' 而不是'i'。
  • "g" : 允許任一寄存器、內存或者立即整形操作數,不包括通用寄存器之外的寄存器。

以下約束為 x86 特有。

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

6.2 約束修飾符

當使用約束時,對于更精確的控制超過了對約束作用的需求,GCC 給我們提供了約束修飾符。最常用的約束修飾符為:

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

上述的約束列表和解釋并不完整。示例可以讓我們對內聯匯編的用途和用法更好的理解。在下一節,我們會看到一些示例,在那里我們會發現更多關于修飾寄存器列表的東西。

7. 一些實用的訣竅

現在我們已經介紹了關于 GCC 內聯匯編的基礎理論,現在我們將專注于一些簡單的例子。將內聯匯編函數寫成宏的形式總是非常方便的。我們可以在 Linux 內核代碼里看到許多匯編函數。(usr/src/linux/include/asm/*.h)。

1).首先我們從一個簡單的例子入手。我們將寫一個兩個數相加的程序。

  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,同時我們也想要在 %eax 中存放結果。'=' 符號表示它是一個輸出寄存器?,F在我們可以以其他方式將一個整數加到一個變量。

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

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

[[171878]]

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

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

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

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

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

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

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

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

4).現在我們看看一些更為復雜而有用的函數。字符串拷貝。

  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,目標地址存放于 edi,同時開始拷貝,當我們到達 0 時,拷貝完成。約束 "&S"、"&D"、"&a" 表明寄存器 esi、edi 和 eax 早期修飾寄存器,也就是說,它們的內容在函數完成前會被改變。這里很明顯可以知道為什么 "memory" 會放在修飾寄存器列表。

我們可以看到一個類似的函數,它能移動雙字塊數據。注意函數被聲明為一個宏。

  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" /  

這里我們沒有輸出,寄存器 ecx、esi和 edi 的內容發生了改變,這是塊移動的副作用。因此我們必須將它們添加進修飾寄存器列表。

5).在 Linux 中,系統調用使用 GCC 內聯匯編實現。讓我們看看如何實現一個系統調用。所有的系統調用被寫成宏(linux/unistd.h)。例如,帶有三個參數的系統調用被定義為如下所示的宏。

  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); / 

無論何時調用帶有三個參數的系統調用,以上展示的宏就會用于執行調用。系統調用號位于 eax 中,每個參數位于 ebx、ecx、edx 中。最后 "int 0x80" 是一條用于執行系統調用的指令。返回值被存儲于 eax 中。

每個系統調用都以類似的方式實現。Exit 是一個單一參數的系統調用,讓我們看看它的代碼看起來會是怎樣。它如下所示。

  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 的系統調用號是 1,同時它的參數是 0。因此我們分配 eax 包含 1,ebx 包含 0,同時通過 "int $0x80" 執行 "exit(0)"。這就是 exit 的工作原理。

8. 結束語

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

GCC 內聯是一個極大的主題,這篇文章是不完整的。更多關于我們討論過的語法細節可以在 GNU 匯編器的官方文檔上獲取。類似地,要獲取完整的約束列表,可以參考 GCC 的官方文檔。

當然,Linux 內核大量地使用了 GCC 內聯。因此我們可以在內核源碼中發現許多各種各樣的例子。它們可以幫助我們很多。

如果你發現任何的錯別字,或者本文中的信息已經過時,請告訴我們。

責任編輯:武曉燕 來源: Linux就該這么學
相關推薦

2011-01-14 14:39:32

Linux匯編語言

2013-01-08 11:02:26

IBMdW

2017-03-08 13:12:44

編程學習

2012-10-18 18:40:24

2014-04-23 16:31:42

Windows背景音樂

2021-04-27 07:59:11

內聯匯編 C 語言 asm 關鍵字

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

創始人成功創業

2019-07-25 10:45:05

GitHub技巧網站

2010-11-02 10:15:44

HPCNvidiaTesla GPU

2020-12-14 08:48:45

C語言嵌入式gcc

2015-09-10 13:11:20

七牛

2016-04-25 10:47:49

源碼閱讀學習
點贊
收藏

51CTO技術棧公眾號

成人片在线看| 国产一级一片免费播放| aaaa欧美| 亚洲一区二区在线视频| 久久国产手机看片| 亚洲图片中文字幕| 欧美a级在线| 日韩av中文字幕在线播放| av丝袜天堂网| 欧美人动性xxxxz0oz| 国产亚洲综合在线| 亚洲一区二区三区四区视频 | 国产又粗又猛又爽又黄91| 在线电影一区二区| 亚洲免费视频网站| 在线观看日本www| 一区一区三区| 亚洲综合一区二区| 无遮挡亚洲一区| 三级小视频在线观看| 麻豆极品一区二区三区| 久久久免费观看| 日日碰狠狠添天天爽| 啪啪国产精品| 欧美一级搡bbbb搡bbbb| 欧美少妇性生活视频| 青草青在线视频| 丁香花在线观看完整版电影| 日本免费在线观看| 久久99久久99| 欧洲亚洲免费视频| 国产在线视频卡一卡二| 欧美激情理论| 国产亚洲精品激情久久| 国产精品无码网站| 国产91精品入| 7777精品伊人久久久大香线蕉| 男人天堂999| 波多野结衣在线观看| 亚洲三级在线免费| 亚洲国产精品日韩| 国产午夜精品一区理论片| www.亚洲精品| 国产精品果冻传媒潘| 999国产精品视频免费| 美日韩一区二区三区| 日韩美女在线看| 五月天婷婷激情| 国产日韩欧美高清免费| 欧美黄色成人网| 亚洲国产精品免费在线观看| 9999国产精品| 久久亚洲影音av资源网| 国产老头老太做爰视频| 天天色天天射综合网| www国产精品com| 男人的午夜天堂| 久久综合电影| 久久夜色撩人精品| 中文字幕在线2021| 欧美精选一区| 欧美极品美女电影一区| 国产午夜视频在线播放| 亚洲精品裸体| 欧美一级片免费在线| 国产www在线| 裸体素人女欧美日韩| 日韩av免费看| 一级黄色片在线观看| 美女视频一区在线观看| 成人在线国产精品| 精品久久久中文字幕人妻| 国产高清成人在线| 国产在线精品一区二区三区》| 男人天堂网在线视频| 伊人网综合在线| 亚洲一区二区三区在线免费 | 欧美成人午夜激情视频| 亚洲国产精品免费在线观看| 在线播放精品| 欧洲日本亚洲国产区| 这里只有精品免费视频| 九九九久久久精品| 国产精品乱子乱xxxx| 人成免费电影一二三区在线观看| 国产亚洲一本大道中文在线| 午夜精品一区二区三区四区| 91小视频xxxx网站在线| 午夜亚洲福利老司机| 日本黄色三级大片| 四虎视频在线精品免费网址| 日韩欧美精品三级| v8888av| 色喇叭免费久久综合网| 欧美国产欧美亚洲国产日韩mv天天看完整| 日本亚洲欧美在线| 麻豆国产精品官网| 国产亚洲一区在线播放| av影片免费在线观看| 一区二区三区日韩欧美| r级无码视频在线观看| 91av一区| 亚洲精品一区二区三区香蕉| 精品人妻中文无码av在线| 欧美日韩视频一区二区三区| 国产精品99导航| www.黄色一片| 国产精品久久久久久久久搜平片 | 久久综合入口| 亚洲精品天堂| 欧美亚洲综合一区| 丰满岳乱妇一区二区| 欧美h版在线| 青青久久aⅴ北条麻妃| 国产99999| 国产精品女上位| 日韩欧美国产免费| 欧美久久一区二区三区| 中文字幕久久精品| www亚洲视频| 国产999精品久久久久久| 亚洲不卡一卡2卡三卡4卡5卡精品| 爆操欧美美女| 欧美人与禽zozo性伦| 在线观看福利片| 亚洲性图久久| 91免费版网站在线观看| 日本精品在线| 精品视频在线免费| 国产美女精品久久| 亚洲永久网站| 九九久久99| 国产精品69xx| 日韩欧美一级在线播放| 日本 欧美 国产| 久色成人在线| 欧美一级日本a级v片| 亚洲欧洲自拍| 亚洲精品视频久久| 日产亚洲一区二区三区| 懂色av噜噜一区二区三区av| 欧洲精品视频在线| 成人精品在线| 超碰日本道色综合久久综合| 在线播放精品视频| 亚洲国产精华液网站w | 久久精品国产99国产精品澳门 | 亚洲精品久久一区二区三区777| 日韩大片在线| 国产乱肥老妇国产一区二| 国产高清一区在线观看| 色天使色偷偷av一区二区| 偷拍女澡堂一区二区三区| 国产精品婷婷| 狼狼综合久久久久综合网| 中文字幕人成乱码在线观看| 亚洲女人天堂网| 久久久精品视频网站| 26uuu亚洲| 熟妇人妻va精品中文字幕| 国产成人精品免费视| 国产精品久久久久久久久免费看| 国产爆初菊在线观看免费视频网站| 91国模大尺度私拍在线视频| 一级黄色片网址| 毛片av一区二区三区| 国产精品jizz在线观看老狼| 国产情侣一区在线| 久久久久久久97| 婷婷国产在线| 在线看不卡av| 三级黄色录像视频| 国产黄色91视频| 国产二区视频在线| 久久av中文| 91精品久久久久久久久久入口| av在线免费网站| 亚洲二区在线播放视频| 无码人妻av一区二区三区波多野| 国产精品久久久久一区二区三区共| 超碰成人在线播放| 亚洲一级网站| 视频一区二区综合| 日韩不卡在线视频| 91大神福利视频在线| 91在线观看| 欧美成人a∨高清免费观看| yjizz国产| 亚洲欧洲日本在线| 国产高清成人久久| 裸体在线国模精品偷拍| 日本精品久久久久久久久久| 国产成人调教视频在线观看 | 国产精品午夜福利| 亚洲h在线观看| 一本在线免费视频| 不卡的av中国片| 日韩欧美国产片| 亚洲深爱激情| 中日韩在线视频| 天天躁日日躁狠狠躁欧美巨大小说| 国产精品私拍pans大尺度在线 | 国产精品偷伦视频免费观看了| 亚洲欧美网站| 麻豆传媒网站在线观看| 奇米狠狠一区二区三区| 91pron在线| 欧美成人福利| 欧美最顶级的aⅴ艳星| av毛片在线播放| 国产一区二区三区日韩欧美| 欧美天堂在线视频| 在线综合+亚洲+欧美中文字幕| 可以在线观看av的网站| 亚洲精品va在线观看| 免费黄色片网站| 99精品黄色片免费大全| 亚洲视频在线不卡| 奇米色777欧美一区二区| 日韩免费一级视频| 国产专区一区| 国产精品一二三在线观看| 久久综合88| 午夜精品一区二区在线观看| 自拍亚洲一区| 精品伦精品一区二区三区视频| 欧美2区3区4区| 国产日韩视频在线观看| 欧美日韩五码| 日韩美女在线播放| 惠美惠精品网| 91精品国产91| 麻豆视频在线看| 97精品视频在线| 波多野结衣精品| 久久久久国色av免费观看性色 | 668精品在线视频| 菠萝蜜视频在线观看www入口| 欧美国产视频一区二区| 99热国产在线| 欧美日韩成人网| 中国av在线播放| 另类图片亚洲另类| 男人天堂久久久| 日韩中文娱乐网| 色欧美激情视频在线| 日韩中文字在线| 久久bbxx| 欧美猛交ⅹxxx乱大交视频| 久久五月精品| 欧美风情在线观看| 久草免费在线色站| 97久久精品人人澡人人爽缅北| 波多野结衣在线播放| 97精品一区二区视频在线观看| 午夜激情电影在线播放| 日本成熟性欧美| 国产精品蜜月aⅴ在线| 国产成人一区三区| 国精产品一区一区三区四川| 国产精品丝袜久久久久久不卡| 视频欧美精品| av资源站久久亚洲| 国内精品偷拍| 蜜桃麻豆91| 欧美岛国激情| 国产制服91一区二区三区制服| 国产字幕视频一区二区| 欧美成人xxxxx| 日韩不卡一区二区| 色婷婷一区二区三区在线观看| 国产精品夜夜嗨| 亚洲欧美日本一区| 欧美激情综合五月色丁香小说| 中文字幕无码日韩专区免费| 亚洲一区二区三区爽爽爽爽爽| 中文字幕日韩一级| 色八戒一区二区三区| 国产一区二区波多野结衣| 精品国产网站在线观看| 日本在线丨区| 久久人人爽亚洲精品天堂| 日本动漫理论片在线观看网站 | 这里只有精品在线观看| 国产盗摄在线观看| 97国产真实伦对白精彩视频8| 亚洲高清黄色| 147欧美人体大胆444| 亚洲丁香日韩| 男人草女人视频| 日韩国产欧美在线观看| 中国老熟女重囗味hdxx| 久久免费的精品国产v∧| 老湿机69福利| 色哦色哦哦色天天综合| 国产av一区二区三区| 亚洲欧美在线免费观看| 丝袜综合欧美| 国产精品劲爆视频| 粉嫩av一区二区| 影音先锋在线亚洲| 久久婷婷丁香| 肉丝美足丝袜一区二区三区四| 国产亚洲精品免费| 久久精品国产亚洲av麻豆色欲| 欧洲激情一区二区| 无码国产精品高潮久久99| 精品久久久av| 亚洲成人激情社区| 国产精品一区而去| 66久久国产| 午夜在线观看av| 91免费精品国自产拍在线不卡| 看片网站在线观看| 欧美精品久久久久久久多人混战| 污污的视频网站在线观看| 九九久久久久99精品| 国产精品4hu.www| 欧美另类一区| 国产视频一区三区| 国产在线观看免费播放| 国产精品短视频| 欧美在线视频精品| 亚洲久久久久久久久久| www.51av欧美视频| 99久热re在线精品996热视频| 国产精品videosex性欧美| 超碰影院在线观看| 2020国产精品久久精品美国| 国产精品a成v人在线播放| 欧美电影精品一区二区| 国产美女福利在线| 国产在线拍揄自揄视频不卡99| 欧美日韩激情在线一区二区三区| 欧美成人免费在线观看视频| 岛国精品在线观看| 国产小视频在线观看免费| 日韩一级成人av| av在线免费网址| 产国精品偷在线| 一区在线观看| 强迫凌虐淫辱の牝奴在线观看| 亚洲无人区一区| 亚洲国产成人一区二区| 欧美日韩国产成人在线观看| 日韩高清一区| 日韩专区第三页| 成人三级在线视频| 青青操免费在线视频| 亚洲国产天堂网精品网站| 久草在线中文最新视频| 国产一区二区黄色| 亚洲一区国产| 久久av无码精品人妻系列试探| 91国偷自产一区二区三区观看| jizzjizz在线观看| 国产在线播放不卡| 欧美成人嫩草网站| 秘密基地免费观看完整版中文| 午夜精彩视频在线观看不卡| 精品乱码一区二区三四区视频| 国产成+人+综合+亚洲欧洲| 日本精品黄色| 手机av在线网站| 亚洲二区视频在线| 青青草视频在线观看| 国产精品久久婷婷六月丁香| 91欧美大片| 免费黄色a级片| 色婷婷激情一区二区三区| av在线第一页| 91国产在线免费观看| 99日韩精品| 亚洲ⅴ国产v天堂a无码二区| 8x8x8国产精品| 国产精品蜜芽在线观看| 日产精品久久久一区二区| 精品亚洲aⅴ乱码一区二区三区| 黄色小说在线观看视频| 亚洲人成电影网站| 国产精品毛片无码| 日韩小视频在线播放| 国产精品视频第一区| 成人免费观看在线视频| 日韩av片免费在线观看| 无码一区二区三区视频| 国产亚洲色婷婷久久99精品91| 欧美性猛交xxxxxx富婆| 在线观看a级片| 欧美日韩亚洲一区二区三区在线观看| 蜜桃视频在线一区| 国产精品免费av一区二区| 中文欧美日本在线资源| gogo人体一区| 五月婷婷丁香色| 午夜精品福利视频网站| 麻豆av在线导航| 欧美亚洲国产免费| 粉嫩一区二区三区在线看| 黄色av一区二区|