之前在用C语言编程的时候,有些时候会利用stack的布局做一些操作.我之前在 实现垃圾收集器算法的时候就会使用函数参数的地址,用这个地址作为基址在 stack上寻找我想要的数据. 这篇文章 里面详细介绍了stack的布局,以及编译器在编译函数时自动产生的前言和结语等问题. 对这个垃圾收集算法感兴趣的可以看 这里. 我在写这个算法的时候就发现当使用clang编译器编译后,函数参数在stack中的布局 跟预期的不一样.这是 sf上提出的一个问题.
后来在工作中涉及到了不少关于stack balance的问题,其中有些需求让我不得不 读反编译后的代码.
对于这样一个函数
int add(int a, int b)
{
return a + b;
}
其用clang编译后,反编译结果如下
080483c0 <add>:
80483c0: 55 push %ebp
80483c1: 89 e5 mov %esp,%ebp
80483c3: 83 ec 08 sub $0x8,%esp
80483c6: 8b 45 0c mov 0xc(%ebp),%eax
80483c9: 8b 4d 08 mov 0x8(%ebp),%ecx
80483cc: 89 4d fc mov %ecx,-0x4(%ebp)
80483cf: 89 45 f8 mov %eax,-0x8(%ebp)
80483d2: 8b 45 fc mov -0x4(%ebp),%eax
80483d5: 03 45 f8 add -0x8(%ebp),%eax
80483d8: 83 c4 08 add $0x8,%esp
80483db: 5d pop %ebp
80483dc: c3 ret
80483dd: 0f 1f 00 nopl (%eax)
可以看到中间有4句mov指令做了stack拷贝操作.拷贝后stack布局变成
+---------+
|high |
+---------+
|101 |<-arg2
+---------+
|99 |<-arg1
+---------+
|ret |
+---------+
|ebp |
+---------+
|99 |<-a
+---------+
|101 |<-b
+---------+
|low |
+---------+
这一步拷贝操作导致用参数的地址作为地址来寻址会遇出错.当时在实现垃圾收集 算法的时候还以为clang的参数计算顺序问题,或者是ABI的问题,现在看来还是图 样.
回过头来看这个问题,clang这么做在效率上会有损失,但是的确是符合逻辑的. 这样的一个参数拷贝操作之后,使得函数的参数都在自己的frame当中,而不像之 前一样,函数的参数在其caller的frame中. 这个问题也说明,利用函数参数地址来进行stack上的寻址操作是有潜在风险的.