Microsoft函数调用约定
对于所有调用共有的约定:ebx、ebp、esi、edi都是calle-save,即由被调用的函数负责它们的保存(如果被调用函数用到了这些寄存器的话) 先看函数调用发生了什么:(win32下) 1、所有参数提升到4bytes的倍数 2、执行call指令 3、将ebx、ebp、esi、edi保存在栈上,这一步称为function prolog 4、将返回值放在eax中(返回int64放在edx:eax中,返回double放在浮点寄存器中。复杂的数据结构在caller的栈中分配空间,将首地址做为最后压栈的参数(我拿__cdecl试的),callee返回的时候将返回值放在这个指针指向的地址) 5、取出ebx、ebp、esi、edi,这一步称为function epilog 6、清空栈,保持堆栈平衡,这一步称为stack cleanup 具体的调用约定: 1、__cdecl 压栈顺序:从右至左 清栈:函数调用者负责清空,称为caller cleanup 名字修饰(name decoration):在函数名前加上下划线做前缀,比如_foo 注:c/c++函数的缺省调用约定 2、__stdcall 压栈顺序:从右至左 清栈:被调用函数负责清空,称为callee cleanup 名字修饰:在函数名前加上下划线做前缀,名字后用@加上函数参数大小做后缀,如_foo@8 注:WINAPI就是__stdcall的#define 3、__fastcall 压栈顺序:第一个参数放在ecx,第二个参数放在edx,其余从右至左 清栈:callee cleanup 名字修饰:在函数名前加上@做前缀,名字后用@加上函数参数大小做后缀,如@foo@8 注:名字叫fast实际就不一定了…… 4、thiscall 压栈顺序:this指针(cpp非静态成员函数特有)放在ecx,其余从右至左 清栈:callee cleanup 注:cpp非静态成员函数默认的调用约定 5、naked 压栈顺序:从右至左 清栈:caller cleanup 注:VxDs用的调用约定,将__declspec(naked)写在函数定义处(注意,不是声明) 这篇文章把naked作为calling convention: http://www.cs.cornell.edu/courses/cs412/2001sp/resources/microsoft-calling-conventions.htm 但naked应该算不上调用约定了,只是单纯地将c/c++代码翻译成asm,连esp、ebp之间的保存、mov都没有就直接拿ebp来寻址了,函数结束后也不管ret,太naked了! 补充:c允许可变参数(例如printf),为了方便地使用这些函数,所以__cdecl要 1、从右至左压栈(这样才能知道第一个参数在堆栈中的地址) 2、caller cleanup(因为只有caller才知道这次调用push了多少参数 函数调用约定的历史——16位的世界: 对所有的调用约定: 1、用dx:ax或ax保存返回值 2、由于cx不能寻址,所以还需要一个bx来scratch(就是不需要callee-save的寄存器) 具体的调用约定 1、__cdecl 名字修饰在函数前加下划线可能是为了区别函数名和汇编语言的关键字 2、__pascal 自左至右传参、callee cleanup 比较有意思的是名字修饰把函数名转换为大写,嗯,pascal大小写无关…… 几乎所有的win16函数都是用pascal调用约定,因为callee-clean的操作比caller-clean要节省3bytes的空间 3、__fortran 和pascal一样 4、__fastcall 和win32下一样,把ecx、edx换为cx、dx罢了 这个fast可是未必,如果函数中的计算要用到cx、dx就免不了把cx、dx压栈的操作了,所以这种调用约定仅仅可能对短小的leaf function(就是不调用其他函数的函数)快一些,甚至对这类函数都快不了…… |