X86&64 calling convention

来源:互联网 发布:什么软件可以视频剪辑 编辑:程序博客网 时间:2024/06/13 00:51

X86&64 calling convention  

2012-04-07 15:00:58|  分类: x86/64 Assembly |举报 |字号 订阅

1. 汇编函数参数传递方式

a. x86:__stdcall,_cdecl,__fastcall

1、__stdcall调用约定相当于16位动态库中经常使用的PASCAL调用约定。在32位的VC++5.0中PASCAL调用约定不再被支持(实际上它已被定义为__stdcall。除了__pascal外,__fortran和__syscall也不被支持),取而代之的是__stdcall调用约定。两者实质上是一致的,即函数的参数自右向左通过栈传递,被调用的函数在返回前清理传送参数的内存栈。

__stdcall是Pascal程序的缺省调用方式,通常用于Win32 Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上"@"和参数的字节数。

2、C调用约定(即用__cdecl关键字说明)按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的(正因为如此,实现可变参数的函数只能使用该调用约定)。另外,在函数名修饰约定方面也有所不同。

_cdecl是C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。VC将函数编译后会在函数名前面加上下划线前缀。是MFC缺省调用约定。 

3、__fastcall调用约定是"人"如其名,它的主要特点就是快,因为它是通过寄存器来传送参数的(实际上,它用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈),在函数名修饰约定方面,它和前两者均不同。

__fastcall方式的函数采用寄存器传递参数,VC将函数编译后会在函数名前面加上"@"前缀,在函数名后加上"@"和参数的字节数。

在x86架构中,_cdecl方式参数传递通过从右到左压栈的方式,函数通过Eax寄存器返回值(除了返回浮点数,会用到ST0寄存器)。在linux中,自从gcc4.5后函数传递栈必须以16-bytes对齐。

如下面例子:

int function_name(int, int, int);

int a, b, c, x;

...

x = function_name(a, b, c);

产生汇编如下:

push c             ; arg 3

push b             ; arg 2

push a             ; arg 1

call function_name ; jump to function_name's code

add esp, 12        ; pop function args (a, b, c) off the stack

mov x, eax         ; fetch function return value

 

b.x86_64

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

2) 参数个数大于 7 个的时候

H(a, b, c, d, e, f, g);

a->%rdi, b->%rsi, c->%rdx, d->%rcx, e->%rax

g->8(%esp)

f->(%esp)

 

2. 实践例子:在汇编中调用c函数

a. x86 : call.s

.section .data

output:

    .ascii "hello world output addr:0x%x\n"

.section .text

.globl main

main:

pushl $output

pushl $output

call printf

addl $8, %esp

movl $1, %eax

movl $0, %ebx

int $0x80

 

#as -o call.o call.s

# ld -dynamic-linker /lib/ld-linux-x86.so.2 -o call_printf -lc call.o

#./call_printf

 

b. x86-64:call.s

.section .data

output:

    .ascii "hello world output addr:0x%x\n"

data:

    .int 0xa445a1a3

.section .text

.globl _start

_start:

mov $output, %rsi

mov $output, %rdi

call printf

movl $1, %eax

movl $0, %ebx

int $0x80

 

#as -o call.o call.s

# ld -dynamic-linker /lib/ld-linux-x86-64.so.2 -o call_printf -lc call.o

#./call_printf

 

3. calling conversion rules

               X8664 calling convention - CR7 - CR7的博客

(1). Caller Rules:图1是x86描述图,图2是x86-64描述图

Before call

a1. 在call函数前保存push会被函数改变的寄存器(返回值或x86-64下的参数传递寄存器):如eax, rdi等

a2. 传递参数给函数,x86通过push方式,x86-64通过寄存器传递方式。

a3. 利用call指令调用函数,该指令把return address 放在参数列表的顶部,然后跳转到函数代码。

After call

b1. 去掉传递参数, x86通过pop, x86-64不用处理

b2. 恢复被使用的寄存器值,通过pop的方式。

例子:

1. x86

    pushl $19     #a2

    pushl $20    #a2

    call add     #a3

    addl $8, %esp  #b1

    movl %eax, %ebx

    movl $1, %eax

    int $0x80

2. x86-64

    movl  $19, %esi     #a2

    movl  $20, %edi     #a2

    call add           #a3

    movl %eax, %ebx

    movl $1, %eax

    int $0x80

(1). Callee Rules

a1. 存储ebp,和esp

pushl %ebp       

movl %esp, %ebp

a2. 在栈中分配本地局部变量  以及保存使用到的非易失寄存器如ebx

a3. 函数体返回值给eax

a4. 恢复esp,ebp以清除本地使用内存(变量),恢复非易失寄存器如ebx。

a5. ret指令结束函数

例子:

X86

.type add, @function

add:

    pushl %ebp        #a1

    movl %esp, %ebp   #a1

    subl $4, %esp     #a2

    movl 8(%ebp), %eax   #使用外部参数 

    movl %eax, -4(%ebp)

    movl 12(%ebp), %eax

    addl %eax, -4(%ebp)  #利用内部变量

    movl -4(%ebp), %eax #a3

    movl %ebp, %esp   #a4

    popl %ebp      #a4

    ret              #a5

X86-64

.type add, @function

add:

    pushl %ebp        #a1

    movl %esp, %ebp   #a1

    subl $4, %esp     #a2

    movl %rdi, -4(%ebp) #使用外部参数和内部变量

    addl %rsi, -4(%ebp)

    movl -4(%ebp), %eax #a3

    movl %ebp, %esp   #a4

    popl %ebp      #a4

    ret              #a5
0 0
原创粉丝点击