首页
学习
活动
专区
圈层
工具
发布
21 篇文章
1
《coredump问题原理探究》Linux x86版6.3节有成员变量的类coredump例子
2
《coredump问题原理探究》Linux x86版6.5节虚函数的coredump例子
3
《coredump问题原理探究》Linux x86版6.8节多继承coredump例子
4
《coredump问题原理探究》Linux x86版7.2节vector coredump例子
5
《coredump问题原理探究》Linux x86版7.4节List coredump例子
6
《coredump问题原理探究》Linux x86版7.6节 Map coredump例子
7
《coredump问题原理探究》Linux x86版5.9节C风格数据结构内存布局之联合体
8
《coredump问题原理探究》Linux x86版6.1节C++风格数据结构内存布局之无成员变量的类
9
《coredump问题原理探究》Linux x86版6.2节C++风格数据结构内存布局之有成员变量的类
10
《coredump问题原理探究》Linux x86版4.5节函数的逆向之coredump例子
11
《coredump问题原理探究》Linux x86版5.1节C风格数据结构内存布局之引言
12
《coredump问题原理探究》Linux x86版5.2节C风格数据结构内存布局之基本数据类型
13
《coredump问题原理探究》Linux x86版5.3节C风格数据结构内存布局之数组
14
《coredump问题原理探究》Linux x86版5.4节C风格数据结构内存布局之数组coredump例子
15
《coredump问题原理探究》Linux x86版5.5节C风格数据结构内存布局之基本数据类型构成的结构体
16
《coredump问题原理探究》Linux x86版5.6节C风格数据结构内存布局之复合类型构成的结构体
17
《coredump问题原理探究》Linux x86版5.7节C风格数据结构内存布局之结构体数组
18
《coredump问题原理探究》Linux x86版5.8节C风格数据结构内存布局之结构体数组结构体coredump
19
《coredump问题原理探究》Linux x86版3.5节栈布局之-fomit-frame-pointer编译选项
20
《coredump问题原理探究》Linux x86版3.6节栈布局之gcc内嵌关键字
21
《coredump问题原理探究》Linux x86版3.8节栈布局之栈溢出coredump例子

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

版权声明:本文为博主原创文章,未经博主允许不得转载。 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都是这样。

下一篇
举报
领券