汇编函数调用的传参规则

0x01 一般流程

函数调用一般有个模板。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
push es
mov ebp,esp
//这里提升堆栈
sub esp,0x40
//这里是开辟缓冲区,不同编译器开辟的缓冲区大小不同。
push edi
push esi
push ebx
//保留现场
lea edi, dword ptr ss:[ebp-0x40]
mov ecx,0x10
mov eax,0xcccccccc
rep stos dowrd ptr es:[edi]
//填充缓冲区
--------------------------------------------------------
//这里是写函数的功能
--------------------------------------------------------
pop ebx
pop esi
pop edi
//恢复现场
mov esp,ebp
pop ebp
ret

中间的ccc…就是填充缓冲区。填充后可以用来写入局部变量。
EBP后面的高址,存有恢复用的EIP,和call函数前push的参数。
EBP前面的低址,用来存局部变量。

根据函数调用约定的不同,堆栈平衡的方式不同
如果是__cedcl约定,是在母函数中平衡堆栈,就是函数调用完返回后,在调用者里add esp,xx来平衡堆栈。

0x02 调用约定

有几种函数调用约定,不同的调用约定,参数的传递,堆栈的平衡方式不同。下面是三种常见的调用约定:

__cedcl 约定:

参数从右往左,依次入栈。堆栈平衡在母函数中完成。
在函数调用前,将参数压栈。
push xxx
push xxx

然后call 函数。
//函数执行完毕
add esp ,xx
//在母函数中平衡堆栈

stdcall 约定:

参数从右往左依次入栈,在子函数中 平衡堆栈。
参数还是在调用前push 入栈。
平衡堆栈时,在 ret 后加个 xx 恢复堆栈
例如 ret 0x8,相当于 pop eip add esp,0x8

fastcall 约定:

参数从右往左依次入栈,在子函数中 平衡堆栈
参数1个或者两个用 寄存器 传参,多于两个,多余两个的部分还是压栈传参。
例如 (int a,int b,int c)
push c
mov eax,b
mov ecx,a
然后再call 函数,最后在子函数中平衡堆栈。

————————————————
版权声明:本文为CSDN博主「dittozzz」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_43394612/article/details/84332149

0x03 64位传参

linux:

摘自:http://abcdxyzk.github.io/blog/2012/11/23/assembly-args/

当参数少于7个时, 参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。
当参数为7个以上时, 前 6 个与前面一样, 但后面的依次从 “右向左” 放入栈中,即和32位汇编一样。

参数个数大于 7 个的时候:

1
2
3
4
5
H(a, b, c, d, e, f, g, h);
a->%rdi, b->%rsi, c->%rdx, d->%rcx, e->%r8, f->%r9
h->8(%esp)
g->(%esp)
call H

附64位寄存器表:

image-20220510114555550

windows:

摘抄:https://www.cnblogs.com/iBinary/p/10959444.html

x64调用示例:

1
2
3
4
5
6
7
8
sub rsp,0x28

mov r9,1
mov r8,2
mov rdx,3
mov rcx,4
call xxx
add rsp,0x28

传参方式:
首先说明一下,在X64下,是寄存器传参. 前4个参数分别是 rcx rdx r8 r9进行传参.多余的通过栈传参.从右向左入栈

申请参数预留空间:
在x64下,在调用一个函数的时候,会申请一个参数预留空间.用来保存我们的参数.比如以前我们通过push压栈
参数的值.相应的栈就会抬高.其实x64下,一样会申请.只不过这个地方在进函数的时候并没有值.进入函数之后才会将寄存器的值在拷贝到这个栈中.其实就相当于你还是push了.只不过我是外边申请空间,内部进行赋值。

如下:

1
2
3
4
5
6
7
8
9
10
11
12
sub rsp,0x28          //申请的栈空间为0x28,就相当于我们push rcx rdx r8 r9.只不过只是申请.
call xxxx
add rsp,0x28

xxx //函数内部

mov [rsp - 8],rcx
mov [rsp - 0x10],rdx
mov [rsp - 0x18],r8
mov [rsp - 0x20],r9

xxx

总结:

  1. 在调用函数之前,会申请参数预留空间.(rcx,rdx,r8,r9)
  2. 函数内部,会将寄存器传参的值(rcx,rdx,r8,r9)保存到我们申请的预留空间中.
    上面这两步其实就相当于x86下的 push r9 push r8 push rdx,push rcx
  3. 调用约定是__fastcall.传参有rcx rdx,平栈是按照c调用约定平栈. 也就是调用者平栈.