前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《coredump问题原理探究》Linux x86版3.5节栈布局之-fomit-frame-pointer编译选项

《coredump问题原理探究》Linux x86版3.5节栈布局之-fomit-frame-pointer编译选项

作者头像
血狼debugeeker
发布2018-09-20 14:31:25
6080
发布2018-09-20 14:31:25
举报
文章被收录于专栏:debugeeker的专栏debugeeker的专栏

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://cloud.tencent.com/developer/article/1344458

上面探讨了没有使用-fomit-frame-pointer编译选项的程序的栈桢规律,那么如果一个程序是通过-fomit-frame-pointer编译选项来编译,它运行时的栈桢规律有没有可能不同呢?

先看一个例子:

代码语言:javascript
复制
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;
}

看一下它们的汇编:

代码语言:javascript
复制
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.
代码语言:javascript
复制
(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编译选项编译出来的程序没有

代码语言:javascript
复制
push   %ebp
mov    %esp,%ebp

代码语言:javascript
复制
pop    %ebp
ret    

这些开头和结尾的特征指令。而是一进来函数,就立马

代码语言:javascript
复制
   0x08048470 <+0>:     sub    $0x10,%esp   	// func函数

代码语言:javascript
复制
   0x08048493 <+0>:     sub    $0x34,%esp	// test函数

来进行分配局部变量空间了,而在退出时则

代码语言:javascript
复制
   0x0804848f <+31>:    add    $0x10,%esp		// func函数
   0x08048492 <+34>:    ret    

代码语言:javascript
复制
   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(ret1)了。

那么,同样的,在使用了-fomit-frame-pointer的情况下,上面5步,就会少掉第2步,也就是说,addr( ret1 ) - addr(ret2)= 0x34 + 4(返回地址大小)。即相邻两个返回地址ret1, ret2, ret1 > ret2, 假设ret2所在的函数分配的局部变量空间为var_size, 在压入ret2前,压入参数的大小为par_size,那么,addr( ret1 ) - addr(ret2 )= var_size +par_size + 4(返回地址大小)。

下面验证一下,在func函数打断点:

代码语言:javascript
复制
(gdb) tbreak func
Temporary breakpoint 1 at 0x8048470
(gdb) r
Starting program: /home/buckxu/work/3/4/xuzhina_dump_c3_s4 

Temporary breakpoint 1, 0x08048470 in func(int) ()
 (gdb) bt          
#0  0x08048470 in func(int) ()
#1  0x080484ac in test(int, int) ()
#2  0x080484ec in main ()
(gdb) x /32x $esp
0xbffff448:     0x080484ac      0x00000000      0x437c43c4      0x43647f36
0xbffff458:     0x080497b8      0x08048552      0x00000001      0xbffff524
0xbffff468:     0xbffff52c      0x43647f4d      0x437c43c4      0x43cb75ec
0xbffff478:     0x0804850b      0x00000000      0x080484ec      0x00000005
0xbffff488:     0x00000064      0x4362f635      0x00000001      0xbffff524
0xbffff498:     0xbffff52c      0xb7ffef18      0x00000001      0x00000001
0xbffff4a8:     0x00000000      0x00000008      0x00000002      0x437c3ff4
0xbffff4b8:     0x00000000      0x00000000      0x00000000      0xdd2af16c

从上面可以看到,0x080484ac的地址是0xbffff448,而0x080484ec的地址是0xbffff480。0xbffff480-0xbffff448=0x38 = 0x34 + 4(返回地址大小)。和上面推断一致。

在-fomit-frame-pointer编译选项生成的程序里,栈布局有这样的规律:

两个相邻的返回地址ret1,ret2,其中ret1属于函数func1,ret2属于函数func2,且func1调用func2。当func2调用func3时,ret2被压入栈。其中func2的局部变量空间大小为var_size,func3压入栈中的参数大小为par_size,那么它们会满足下面的条件:

1. addr(ret1)-addr(ret2)= var_size + par_size + 4

2. info symbol ret1, info symbol ret2都能够显示出func1, func2

PS:这个规律是更加通用的,不止在x86平台下,在sparc,mips,arm都是这样。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2013年01月21日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档