分类: C/C++
2011-11-10 17:36:16
一、保护原则
首先要明白ABI(Application Binary Interface, 应用程序二进制接口)主要是一个针对编译器的限定,它用以保证不同的编译器编译出的二进制代码以及同一编译器编译出的不同文件的代码之间可以安全交互。所以,如果我们要用高级语言和汇编混合编程,这里有些约定就必须明确。
举个例子,大家都知道在C语言中,EAX寄存器被用来存放返回值。同时大家也知道当一个函数调用发生时,被调用函数往往要先保存堆栈状态(通常是ESP与EBP),在函数返回前必须恢复堆栈。
这里就引出了一个问题,为什么这里EAX寄存器不需要保护而ESP和EBP必须要保护呢?
当然ESP和EBP必须保护的理由是显而易见的,因为函数调用前后的堆栈位置不能丢失。那么其他的寄存器又怎么样呢,到底ABI是怎么对这些寄存器进行限制的呢?
OK,让我们逐个来看。
1. EAX
被作为函数返回值。对于调用者来说,如果在调用后必须用到调用前的EAX的值,则调用者必须自己事先保存EAX,即使被调用函数没有返回值也必须遵守这个原则。
而对于被调用函数,可以随意的使用EAX,不需要对其作任何保护。
2. EBX
对于地址无关代码(position-independent code),该寄存器用被来存放全局偏移表基地址,并可以在函数间传递。
但不管是否地址无关代码,ABI都规定调用者不需要保护EBX寄存器,如果被调用函数用到了该寄存器,则被调用函数必须自己保护它。
3. ECX ,EDX
类似于EAX,调用者在调用后如果需要用到调用前的这些寄存器的值,则调用者必须自己负责保护它们。
被调用函数可以随意使用这些寄存器。
4. ESI , EDI
调用者不需要保护它们。如果被调用函数需要占用这些寄存器则必须对其进行保护。
5. EBP , ESP
被用来维护调前后的堆栈平衡,因此被调用函数必须负责保护它们。
6. EFLAGS
ABI规定进入一个函数前调用者必须保证方向标志DF必须为0(正向),而一个函数在返回前该函数也必须保证DF为0。
而对于其他的标志位同EAX一样,被调用者不需要保护它们。
其他诸如浮点寄存器的规则就不介绍了。
二、调用规约(calling convention)
注意本文仅限于介绍C/C++的调用规约(包含唯一已知的通用规约__stdcall),由于个人能力有限,对于其他语言尚无法给出结论。如果有这方面的高手望赐教!
1. 微软的__cdecl 与GNU的__attribute__ ((cdecl))
__cdecl想必大家都不陌生了,gcc出于兼容,可以使用__attribute__ ((cdecl))达到同样的效果。
该种规约不使用任何寄存器传递参数,参数全部在栈上(从右到左依次入栈),其规则完全符合ABI规定。
2. 微软的__stdcall 与GNU的__attribute__ ((stdcall))
同__attribute__ ((cdecl)),__attribute__ ((stdcall))也是用来兼容微软的。
在寄存器保护问题上同__cdecl,不使用任何寄存器传递参数且符合ABI。
3. 微软的__fastcall 与GNU的__attribute__ ((fastcall))
同前,__attribute__ ((fastcall))用来兼容微软。
该方法使用ECX和EDX传递前两个参数(从左到右),其余参数在栈上(从右到左依次入栈)。
根据ABI,调用者不需要保护ECX和EDX,因此该规约并不违反ABI。
4. thiscall 调用规约(仅C++)
必须注意,微软的thiscall和g++的thiscall在二进制上是不兼容的。
微软的thiscall采用ECX作为this指针,其余参数全部在栈上(从右到左依次入栈)。
而g++的thiscall就是__cdecl,它把this指针当作第一个参数,连同其他参数一起被放在栈上(从右到左依次入栈)。
显然这两种方法都不违背ABI。
5. 微软__declspec(naked) 与GNU的__attribute__ ((naked))
__attribute__ ((naked))是g++对微软的兼容。
对于这种类型的函数,对于调用者(C/C++程序),编译器保证其生成代码是符合ABI规定的。
但是被调用函数也就是naked函数本身,编写者必须自己实现对ABI的兼容。
6. GNU的__attribute__ ((regparm(n)))
在i386下这里的n取值只能是0、1、2、3。它表示该函数使用几个寄存器来传递参数。
当n=0时,所有参数都在栈上(从右到左依次入栈)。也就是__cdecl。
当n=1时,第1个参数在EAX,其余参数在栈上(从右到左依次入栈)。
当n=2时,第1个参数在EAX,第2个参数在EDX,其余参数在栈上(从右到左依次入栈)。
当n=3时,第1个参数在EAX,第2个参数在EDX,第3个参数在ECX,其余参数在栈上(从右到左依次入栈)。
由于EAX、EDX、ECX都是不需要被调用者保护的寄存器,所以这里也不违背ABI规定。
7. Linux内核(i386)的asmlinkage 与fastcall
asmlinkage被定义为__attribute__ ((regparm(0)))。
fastcall被定义为__attribute__ ((regparm(3)))。