首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《coredump问题原理探究》Linux x86版3.4节栈布局之函数参数

《coredump问题原理探究》Linux x86版3.4节栈布局之函数参数

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

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

上面已经讨论当有局部变量空参数的函数时栈的布局,那么当函数具有局部变量和参数,那么栈布局又会怎样?

先看一个例子:

int func( int c, char* s, int off )
{
     int a = 0x12345678;
     int *p = &a;
     int res = c + *( s + off );

     return *p + res;
}
 
int main()
{
     int b = 0x87654321;
 
     return b + func( 0x100, "hello", 3 );
}

看一下main函数的汇编:

(gdb) disassemble main
Dump of assembler code for function main:
   0x08048405 <+0>:     push   %ebp
   0x08048406 <+1>:     mov    %esp,%ebp
   0x08048408 <+3>:     sub    $0x1c,%esp
   0x0804840b <+6>:     movl   $0x87654321,-0x4(%ebp)
   0x08048412 <+13>:    movl   $0x3,0x8(%esp)
   0x0804841a <+21>:    movl   $0x80484d4,0x4(%esp)
   0x08048422 <+29>:    movl   $0x100,(%esp)
   0x08048429 <+36>:    call   0x80483d0 <_Z4funciPci>
   0x0804842e <+41>:    mov    -0x4(%ebp),%edx
   0x08048431 <+44>:    add    %edx,%eax
   0x08048433 <+46>:    leave  
   0x08048434 <+47>:    ret    
End of assembler dump.

从0x08048412 到0x08048422 的汇编语句可以看到,esp, esp+4,esp+8这三个单元依次存放着0x100,0x80484d4,3。而0x80484d4存放的内容可以如下获得:

(gdb) x /s 0x80484d4
0x80484d4 <__dso_handle+4>:      "hello"

可见,刚好和依次传入func函数的参数0x100,”hello”, 3一样。可知正是那三个参数。

当执行

  0x08048429 <+36>:    call   0x80483d0 <_Z4funciPci>

进入func函数时,会把main函数的返回地址压入栈,这时,栈的布局应该如下:

esp:返回地址
esp+4:0x100
esp+8:”hello”的地址
esp+0xC:3

那么当func函数执行完它的开头特征指令

push   %ebp
mov    %esp,%ebp

栈的布局应该如下:

esp:main函数桢指针
esp+4:返回地址
esp+8:0x100
esp+0xC:“hello”的地址
esp+0x10:3

由于这时ebp和esp相等,而esp会用于push、pop、sub、add等操作来申请和释放局部变量空间。所以栈布局应该是ebp为基准。所以上面栈布局应该表述如下:

ebp:main函数桢指针
ebp+4:返回地址
ebp+8:0x100
ebp+0xC:“hello”的地址
ebp+0x10:3

在func打断点来看一下,是不是如此:

(gdb) tbreak func                  
Temporary breakpoint 1 at 0x80483d6
(gdb) r
Starting program: /home/buckxu/work/3/3/xuzhina_dump_c3_s3 

Temporary breakpoint 1, 0x080483d6 in func(int, char*, int) ()
(gdb) x /8x $ebp
0xbffff4b4:     0xbffff4d8      0x0804842e      0x00000100      0x080484d4
0xbffff4c4:     0x00000003      0x0804844b      0xb7fbeff4      0x08048440
(gdb) info symbol 0x0804842e
main + 41 in section .text of /home/buckxu/work/3/3/xuzhina_dump_c3_s3
(gdb) x /s 0x080484d4
0x80484d4 <__dso_handle+4>:      "hello"

果然如此。

由上面的探讨可知,函数的参数有如下规则:

1.      参数在栈上的排列和它的声明顺序是一致的。

2.      第一个参数是在ebp+8开始,第二个参数是放在ebp+0xC,其它依此类推。

到了这里,可以知道完整的栈布局应该如下图:

从图上还可以得到这样的规律:

如果两个函数桢指针fp1,fp2,fp1正好指向fp2的单元。那么它们满足这样的关系:

1.      esp < fp1 < fp2

2.      fp1的下一个单元的内容ret1肯定是返回地址,fp2的下一个单元的内容ret2也肯定是返回地址。即ret1,ret2都可以用info symbol命令来获取它们位于哪些函数里。

探讨完了函数参数的知识,在这里探究一下main函数的参数。虽然main函数在写的时候,可以是

int main()

也可以是

int main( int argc, char* argv[] )

实际上,main函数的原型是这样的:

int main( int argc, char* argv[], char* envp[ ] )

从上面的

(gdb) tbreak func                  
Temporary breakpoint 1 at 0x80483d6
(gdb) r
Starting program: /home/buckxu/work/3/3/xuzhina_dump_c3_s3 

Temporary breakpoint 1, 0x080483d6 in func(int, char*, int) ()
(gdb) x /8x $ebp
0xbffff4b4:     0xbffff4d8      0x0804842e      0x00000100      0x080484d4
0xbffff4c4:     0x00000003      0x0804844b      0xb7fbeff4      0x08048440

可知,main函数桢指针是0xbffff4d8。现在看一下0xbffff4d8 +8,0xbffff4d8+0xC, 0xbffff4d8+0x10的内容是什么?

(gdb) x /4x 0xbffff4d8+8
0xbffff4e0:     0x00000001      0xbffff574      0xbffff57c      0xb7fdd6b0

由于程序运行没有参数,所以argc为1,而argv的值为0xbffff574,envp的值为0xbffff57c。

先看一下argv的内容:

(gdb) x /4x 0xbffff574
0xbffff574:     0xbffff6b9      0x00000000      0xbffff6e2      0xbffff6f3
(gdb) x /s 0xbffff6b9
0xbffff6b9:      "/home/buckxu/work/3/3/xuzhina_dump_c3_s3"

正好和程序的路径一样。

再看一下envp的内容:

(gdb) x /4x 0xbffff57c   
0xbffff57c:     0xbffff6e2      0xbffff6f3      0xbffff704      0xbffff714
(gdb) x /s 0xbffff6e2
0xbffff6e2:      "XDG_SESSION_ID=2"
(gdb) x /s 0xbffff6f3
0xbffff6f3:      "HOSTNAME=xuzhina"
(gdb) x /s 0xbffff704
0xbffff704:      "SHELL=/bin/bash"
(gdb) x /s 0xbffff714
0xbffff714:      "TERM=linux"

正好和

(gdb) shell set
COLUMNS=80
DIRSTACK=()
EUID=1000
GROUPS=()
HISTCONTROL=ignoredups
HISTSIZE=1000
HOME=/home/buckxu
HOSTNAME=xuzhina
HOSTTYPE=i686
PWD=/home/buckxu/work/3/3
QT_GRAPHICSSYSTEM_CHECKED=1
SHELL=/bin/bash 
SHELLOPTS=braceexpand:hashall:interactive-comments
SHLVL=2
SSH_ASKPASS=/usr/libexec/openssh/gnome-ssh-askpass
SSH_AUTH_SOCK=/tmp/ssh-gORyjvO849/agent.849
SSH_TTY=/dev/pts/0
TERM=linux
UID=1000
USER=buckxu
XDG_RUNTIME_DIR=/run/user/buckxu
XDG_SESSION_ID=2
_=/bin/gdb

一样。

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

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

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

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

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