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位的时候一致.

猜你喜欢

转载自blog.csdn.net/wdjjwb/article/details/76504285
今日推荐