x86下C调用风格

本文说的x86指的是32位下的x86,不过据说x86-64也是类似的。

每一种机器本来指令集就不一样,所以本身在不同机器上程序的调用风格就不完全一样。但即使在同一种机器上,如我们常用的x86,不同的高级语言它的调用风格也不一样。比如有些语言可能调用函数前,把状态寄存器压栈,或者把所有寄存器压栈,调用完之后恢复,但有些语言则不会这样做。因此,通常不同高级语言编译出来的目标文件(object file),是不能直接链接的。这种汇编级别的接口,我们有一个词,叫ABI(Application Binary Interface,应用程序二进制接口)。

本文根据《Programming Ground Up》,把C语言在x86下的调用风格作一个小结。

首先是调用者,调用者负责把函数的参数倒序压栈。比如调用者要调用函数func,它要求有N个参数,则调用者在调用函数前把参数按参数N,参数N-1…这样的顺序去压栈。这时程序的堆栈如图1所示。

Picture 1
Picture 1

然后调用者可以调用函数。被调用者则要做以下的工作,首先把%ebp寄存器压栈,然后把%esp拷贝到%ebp。这样以后,函数所有的参数和函数的局部变量,都可以通过%ebp的基址寻址方式去获得。这时程序的堆栈如图2和图3所示。

Picture 2
Picture 2
Picture 3
Picture 3

函数执行完之后,结束之前,也是很关键的一步,就是把%esp和%ebp恢复,同时把返回值放到%eax寄存器。最后调用者负责清理自己压栈的参数。

可见,C语言的调用风格是,%ebp肯定不会改变,但其他寄存器则不保证,所以如果调用函数前正在使用某个寄存器,应该先把它压栈,函数结束后才恢复该寄存器。

另外,Linux的shell在执行程序时,也是遵循同样的风格的。所以程序一开始的时候,0(%esp)就是C语言中的argc,4(%esp)就是C语言中的argv[0],以此类推。

Advertisements

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s