在gcc中使用intel風格的內聯彙編

很簡單,內聯彙編使用asm(「.intel_syntax noprefix/n」)聲明一下,之後的內聯彙編就能夠用intel風格了,構建可執行文件時給gcc加上-masm=intel參數。
先寫一個小程序測試一下:小程序


[cpp] view plain copyapp

  1. #include <stdio.h>  
  2. int main() {  
  3.         int a = 3;  
  4.         asm(".intel_syntax noprefix/n");  
  5.         asm("mov dword ptr a,10/n");  
  6.         printf("%d/n", a);  
  7.         return 0;  
  8. }  


[root@jcwkylk src]# gcc -masm=intel test.c -o test
/tmp/ccgWTkUF.o: In function `main':
test.c:(.text+0x1a): undefined reference to `a'
collect2: ld returned 1 exit status
出錯,說符號a沒有定義。看看編譯後的結果是什麼樣子:
[root@jcwkylk src]# gcc -S test.c
輸出不長,把test.s的內容所有貼出來:
[c-sharp] view plain copyide

  1.         .file   "test.c"  
  2.         .section        .rodata  
  3. .LC0:  
  4.         .string "%d/n"  
  5.         .text  
  6. .globl main  
  7.         .type   main, @function  
  8. main:  
  9.         leal    4(%esp), %ecx  
  10.         andl    $-16, %esp  
  11.         pushl   -4(%ecx)  
  12.         pushl   %ebp  
  13.         movl    %esp, %ebp  
  14.         pushl   %ecx  
  15.         subl    $36, %esp  
  16.         movl    $3, -8(%ebp)  
  17. #APP  
  18.         .intel_syntax noprefix  
  19.         mov dword ptr a,10  
  20. #NO_APP  
  21.         movl    -8(%ebp), %eax  
  22.         movl    %eax, 4(%esp)  
  23.         movl    $.LC0, (%esp)  
  24.         call    printf  
  25.         movl    $0, %eax  
  26.         addl    $36, %esp  
  27.         popl    %ecx  
  28.         popl    %ebp  
  29.         leal    -4(%ecx), %esp  
  30.         ret  
  31.         .size   main, .-main  
  32.         .ident  "GCC: (GNU) 4.1.1 20061011 (Red Hat 4.1.1-30)"  
  33.         .section        .note.GNU-stack,"",@progbits  


從上面看出來,夾在#APP和#NO_APP之間的部分就是.intel_syntax,它保持了原樣,而代碼中的a本來是個局部變量,只有在函數運行時它纔會動態在棧上分配,使用ebp加上偏移量來訪問它,這就是問題所在。由於全局變量的變量名會保存在符號表中,因此若是要在內聯彙編中使用變量名,也只能使用全局變量的變量名。只爲在內聯彙編中用名稱來訪問變量而把一個局部變量變成全局的是不合理的,因此咱們這裏也用ebp+offset的方式來訪問局部變量。
要這麼作,就得了解gcc編譯時是如何爲函數分配棧的,以及調用函數時寄存器約定是怎樣的。從上面的彙編代碼能夠看出來:
[c-sharp] view plain copy函數

  1. main:  
  2.         leal    4(%esp), %ecx   ; ecx=[esp+4],  
  3.         andl    $-16, %esp  
  4.         pushl   -4(%ecx)  
  5.         pushl   %ebp  
  6.         movl    %esp, %ebp  
  7.         pushl   %ecx  
  8.         subl    $36, %esp  


這幾行代碼用來初始化mai函數的調用棧,和cl編譯器不一樣的是在push ebp前面多出來了幾行,有個esp &= -16的操做,-16=0xfffffff0,這個做用多是爲了對齊,esp應該是保持16字節對齊的。但這些細節在這裏做用都不大。最關鍵的是這三行:
        pushl   %ebp
        movl    %esp, %ebp
        pushl   %ecx
有一個把ecx寄存器壓棧的操做,因此第一個局部變量的起始地址應該是ebp-8。
另外一個注意的地方是printf的調用:
        movl    -8(%ebp), %eax
        movl    %eax, 4(%esp)
        movl    $.LC0, (%esp)
        call    printf
在這個代碼中printf有兩個參數,但卻沒有看到指望的push。gcc用了另一種方法:直接操做esp。上面這三行代碼,首先把[ebp-8]也就是第一個局部變量即a的值給了eax,而後把這個值傳遞到esp+4這個地址指向的內存單元,而後esp指向printf的第一個參數——那個格式控制字符串。以後call printf會把下一條指令的地址壓入棧中,而後跳轉到printf,因此,對printf來講,ebp+4仍然是返回地址,ebp+8仍然是第一個參數,ebp+0xc仍然是第二個參數。一切都沒變。
最後有這一行代碼:movl    $0, %eax
看來返回值仍然是存放在eax寄存器中。
好,如今寫一個比較完整的測試程序:
[cpp] view plain copy測試

  1. #include <stdio.h>  
  2. int add(int a, int b) {  
  3.         return a+b;  
  4. }  
  5. int main() {  
  6.         int a = 3;  
  7.         asm(".intel_syntax noprefix/n");  
  8.         asm("mov dword ptr [ebp-8],10/n");  
  9.         printf("%d/n", a);  
  10.         asm("push dword ptr [ebp-8]/n");  
  11.         asm("push 25/n");  
  12.         asm("call add/n");  
  13.         asm("add esp, 8/n");  
  14.         asm("mov dword ptr [ebp-8], eax/n");  
  15.         printf("%d/n", a);  
  16.         return 0;  
  17. }  


[root@jcwkylk src]# gcc -masm=intel test.c -o test
[root@jcwkylk src]# ./test
10
35spa

相關文章
相關標籤/搜索