x86 和 x64 彙編調用C 函數參數傳遞規則(GCC)

在本文中以一段彙編代碼爲例介紹一下在x86和x64彙編語言中調用C 函數的過程。樣例代碼在ubuntu12.04 i386 環境下調試通過。此外本文還介紹了在將這段樣例代碼移植到X64環境下應該注意的問題。 

樣例代碼的作用是計算兩個整數的除法,並通過C語言的printf函數打印計算結果。

.section .data
dividend:
        .quad 8335
divisor:
        .int 25
quotient:
        .int 0
remainder:
        .int 0
output:
       .asciz "The quotient is %d, and the remainder is %d\n"
.section .text
.globl _start
_start:
        movl dividend, %eax
        movl dividend+4, %edx
        divl divisor
        movl %eax, quotient
        movl %edx, remainder
        pushl remainder
        pushl quotient
        pushl $output
        call printf
        add $12, %esp
        pushl $0
       call exit 

編譯過程如下:

lil@lil-kvm:~/assembly$as -o divtest.o divtest.s

lil@lil-kvm:~/assembly$ld --dynamic-linker /lib/ld-linux.so.2 -lc -o divtest divtest.o

其中-lc 選項表示需要連接libc.so庫,--dynamic-linker /lib/ld-linux.so.2 也必須指定,否則即使連接未報錯,也會在運行時出現bash: ./divtest: No such file or directory 錯誤。

編譯後運行

lil@lil-kvm:~/assembly$./divtest 
The quotient is 333, and the remainder is 10

 然後將彙編代碼在ubuntu 12.04 AMD64環境下編譯

liliang@lil:~/assembly$as -o divtest_i64.o divtest_i64.s 
divtest_i64.s:Assembler messages:

divtest_i64.s:20:Error: invalid instruction suffix for `push'
divtest_i64.s:21:Error: invalid instruction suffix for `push'
divtest_i64.s:22:Error: invalid instruction suffix for `push'
divtest_i64.s:25:Error: invalid instruction suffix for `push'

修改彙編代碼中的相關指令後的代碼如下:

.section .data
dividend:

       .quad 8335
divisor:
       .int 25
quotient:
       .int 0
remainder:
       .int 0
output:
       .asciz  "The quotient is %d, and the remainder is %d\n"
.section .text
.globl _start
_start:
       movl dividend, %eax
       movl dividend + 4, %edx
       divl divisor
       movl %eax, quotient
       movl %edx, remainder
       push remainder
       push quotient
       push $output
       call printf
       add $24, %esp
       push $0
       call exit

進行編譯連接,注意此時的參數--dynamic-linker/lib64/ld-linux-x86-64.so.2

liliang@lil:~/assembly$as-o divtest_i64.o divtest_i64.s 

liliang@lil:~/assembly$ld --dynamic-linker/lib64/ld-linux-x86-64.so.2 -lc -odivtest_i64divtest_i64.o

liliang@lil:~/assembly$ ./divtest_i64 
Segmentation fault (core dumped)

 後又嘗試着修改了幾處指定,均未能解決問題,通過gdb調試幾次將問題鎖定在了call printf, 每次當執行printf時就會爆出異常,開始懷疑也許跟printf的參數有關。

於是用C寫了下面的測試程序ctest.c

#include <stdio.h>

int main(int argc,char* argv[])

{
   int divident = 333;
   int remainder = 10;
   printf("dievident=%d, remainder=%d\n", divident, remainder);
}

 將程序編譯成目標文件,並進行反彙編。

liliang@lil:~/assembly$gcc -c ctest.c
liliang@lil:~/assembly$objdump -d ctest.o


ctest.o:    file format elf64-x86-64

Disassembly of section .text:

0000000000000000<main>:
  0:   55                      push   %rbp
  1:    48 89e5                mov    %rsp,%rbp
  4:    48 83 ec20             sub    $0x20,%rsp
  8:    89 7dec                mov    %edi,-0x14(%rbp)
  b:    48 89 75e0             mov    %rsi,-0x20(%rbp)
  f:    c7 45 f8 4d 01 00 00     movl  $0x14d,-0x8(%rbp)
 16:    c7 45 fc 0a 00 00 00     movl  $0xa,-0x4(%rbp)
 1d:    b8 00 00 00 00          mov    $0x0,%eax
 22:    8b 55fc                mov    -0x4(%rbp),%edx
 25:    8b 4df8                mov    -0x8(%rbp),%ecx
 28:    89 ce                   mov    %ecx,%esi
 2a:    48 89c7                mov    %rax,%rdi
 2d:    b8 00 00 00 00          mov    $0x0,%eax
 32:    e8 00 00 00 00          callq  37 <main+0x37>
 37:   c9                      leaveq 
 38:    c3                      retq

 通過反匯編出來的代碼發現在x64的彙編下,調用printf所用的參數傳遞方式與x86下面有很大的不同,x86下面是通過堆棧來傳遞,而在x64下是用寄存器來傳遞。於是照葫蘆畫瓢將彙編代碼改成了下面的樣子:

.section .data

dividend:
        .quad 8335

divisor:
        .int 25
quotient:
        .int 0
remainder:
        .int 0
output:
       .asciz  "The quotient is %d, and the remainder is %d\n"
.section .text
.globl _start
_start:
        movl dividend, %eax
        movl dividend+4, %edx
        divl divisor
        movl %eax, quotient
        movl %edx, remainder
        movl remainder,%edx
        movl quotient, %esi
        mov $output, %rdi
        callq printf
        mov $0, %rdi
        callq exit

 重新彙編,連接,運行

liliang@lil:~/assembly$as -o divtest_i64.o divtest_i64.s

liliang@lil:~/assembly$ld --dynamic-linker /lib64/ld-linux-x86-64.so.2  -lc  -o divtest_i64 -lc divtest_i64.o 

liliang@lil:~/assembly$./divtest_i64 
The quotient is 333, and the remainder is 10

終得預期結果。

結論:在x64環境下,gcc所用的參數傳遞方式跟在x86下不同,前者用的是寄存器,後者用的是堆棧。

事後百度出來一篇win_hate文章,作者通過實驗列出了在x64下gcc的參數傳遞規則:

我試驗了多個參數的情況,發現一般規則爲, 當參數少於7個時, 參數從左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。當參數爲 7 個以上時,  6 個與前面一樣, 但後面的依次從 "右向左放入棧中。

(1) 參數個數少於7:
f (a, b, c, d, e, f);
a->%rdi, b->%rsi, c->%rdx, d->%rcx, e->%r8, f->%r9

g (a, b)
a->%rdi, b->%rsi

有趣的是實際上將參數放入寄存器的語句是從右到左處理參數表的這點與32位的時候一致.

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章