日期:2014-05-16 浏览次数:20775 次
上面探讨了没有使用-fomit-frame-pointer编译选项的程序的栈桢规律,那么如果一个程序是通过-fomit-frame-pointer编译选项来编译,它运行时的栈桢规律有没有可能不同呢?
先看一个例子:
int func( int num ) { int result = num*num; result += num - 1; return result; } void test(int beg, int end ) { int a[10]; for ( int i = 0; i < 10; i++ ) { a[i] = beg + func( i ) + end; } } int main() { test( 5, 100); return 0; }
看一下它们的汇编:
gdb) disassemble func(int) Dump of assembler code for function _Z4funci: 0x08048470 <+0>: sub $0x10,%esp 0x08048473 <+3>: mov 0x14(%esp),%eax 0x08048477 <+7>: imul 0x14(%esp),%eax 0x0804847c <+12>: mov %eax,0xc(%esp) 0x08048480 <+16>: mov 0x14(%esp),%eax 0x08048484 <+20>: sub $0x1,%eax 0x08048487 <+23>: add %eax,0xc(%esp) 0x0804848b <+27>: mov 0xc(%esp),%eax 0x0804848f <+31>: add $0x10,%esp 0x08048492 <+34>: ret End of assembler dump.
(gdb) disassemble test Dump of assembler code for function _Z4testii: 0x08048493 <+0>: sub $0x34,%esp 0x08048496 <+3>: movl $0x0,0x30(%esp) 0x0804849e <+11>: jmp 0x80484c5 <_Z4testii+50> 0x080484a0 <+13>: mov 0x30(%esp),%eax 0x080484a4 <+17>: mov %eax,(%esp) 0x080484a7 <+20>: call 0x8048470 <_Z4funci> 0x080484ac <+25>: mov 0x38(%esp),%edx 0x080484b0 <+29>: add %eax,%edx 0x080484b2 <+31>: mov 0x3c(%esp),%eax 0x080484b6 <+35>: add %eax,%edx 0x080484b8 <+37>: mov 0x30(%esp),%eax 0x080484bc <+41>: mov %edx,0x8(%esp,%eax,4) 0x080484c0 <+45>: addl $0x1,0x30(%esp) 0x080484c5 <+50>: cmpl $0x9,0x30(%esp) 0x080484ca <+55>: setle %al 0x080484cd <+58>: test %al,%al 0x080484cf <+60>: jne 0x80484a0 <_Z4testii+13> 0x080484d1 <+62>: add $0x34,%esp 0x080484d4 <+65>: ret End of assembler dump.
可见,通过-fomit-frame-pointer编译选项编译出来的程序没有
push %ebp mov %esp,%ebp
和
pop %ebp ret
这些开头和结尾的特征指令。而是一进来函数,就立马
0x08048470 <+0>: sub $0x10,%esp // func函数
或
0x08048493 <+0>: sub $0x34,%esp // test函数
来进行分配局部变量空间了,而在退出时则
0x0804848f <+31>: add $0x10,%esp // func函数 0x08048492 <+34>: ret
或
0x080484d1 <+62>: add $0x34,%esp // test函数 0x080484d4 <+65>: ret
来回收局部变量空间和退出函数。
也就是说,函数桢指针的单链表规律在这种情况就不适用了。那么,在这种情况下,栈布局又会有什么规律呢?
在探索这种情况下的规律,先用函数桢指针单链表规律来假设一下在没有用-fomit-frame-pointer编译选项编译出来的程序在执行test到func时候,栈的变化:
1. 在test运行前,栈顶指向着main函数的返回地址ret1
2. test函数执行,把桢指针fp1压入栈,并设置新的桢指针
3. 分配局部变量空间,假设也是分配0x34个字节
4. 压入func函数的参数(由于test只调用一个函数func,所以,直接把参数归并到局部变量空间分配,没有用push指令)
5. 用call指令调用func,往栈里压入test的返回地址ret2
如果用addr(X)表示X所在的地址,从上面6步来看,addr(fp1) –addr( fp2 ) = addr(ret1) – addr( ret2 ) = 0x34 + 4 + 4,刚好是分配的局部变量空间大小与桢指针大小,返回地址ret2大小的和。也就是说,即使不用单链表规律,只要看一下ret2所在函数分配的局部变量空间大小,压入的参数大小,桢指针大小,返回地址ret2的大小,用addr(ret2)加上这个局部变量空间,参数,桢指针大小,返回地址的大小就应该是addr(re