cdecl调用约定
cdecl调用约定又称为C调用约定,是C语言缺省的调用约定,它的定义语法是:
int function (int a ,int b) //不加修饰就是C调用约定
int __cdecl function(int a,int b)//明确指出C调用约定
相同点:cdecl调用约定的参数压栈顺序是和stdcall是一样的,参数首先由右向左压入堆栈。
不同的是:函数本身不清理堆栈,调用者负责清理堆栈。由于这种变化,C调用约定允许函数的参数的个数是不固定的,这也是C语言的一大特色。对于前面的function函数,使用cdecl后的汇编码变成:
调用者main函数处:
push 2
push 1
call function ;call完成后,ebp已是原来的堆栈基址,而esp也指向了返回地址之上的那个地址,此处就是数字1所在的地址。
add esp, 8
;这里调用者main函数在清理堆栈。执行”add esp, 8”这一句后,使
栈顶指针esp指回到了2和1被压栈之前的状态的栈顶。堆栈被main函
数,即调用者本身清理干净
被调用函数_function处
push ebp 保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可
以在函数退出时恢复
mov ebp, esp 转入新的堆栈(new stack frame)
mov eax, [ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp, cs:eip,
a, b;ebp +8指向a
add eax, [ebp + 0CH] 堆栈中ebp + 12处保存了b
mov esp, ebp 恢复esp
pop ebp 此处一pop,esp的值升4,指向了return address
ret 循着return address回了老家去了。ret要做两个动作:1) pop the return address from the stack. 2)jump to the location that address directs. 因此,ret完后,esp指针的值再增加4个bytes
简要过程图如下所示:
该修饰自动在函数名前加前导的下划线,因此函数名在符号表中被记录为_function
由于参数按照从右向左顺序压栈,因此最开始的参数在最接近栈顶的位置,因此当采用不定个数参数时,第一个参数在栈中的位置肯定能知道,只要不定的参数个数能够根据第一个后者后续的明确的参数确定下来,就可以使用不定参数,例如对于CRT中的sprintf函数,定义为:
int sprintf(char* buffer,const char* format,...)
由于所有的不定参数都可以通过format确定,因此使用不定个数的参数是没有问题的。
fastcall调用约定
fastcall调用约定和stdcall类似,它意味着:
l 函数的第一个和第二个DWORD参数(或者尺寸更小的)通过ecx和edx传递
l 其他参数通过从右向左的顺序压栈
l 被调用函数清理堆栈
函数名修改规则同stdcall
其声明语法为:int fastcall function(int a,int b)
没有评论:
发表评论