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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/details/8520792

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

先看一个例子:

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

一样。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏小灰灰

SPI框架实现之旅二:整体设计

SPI框架实现之旅二:整体设计 上一篇简单的说了一下spi相关的东西, 接下来我们准备开动,本篇博文主要集中在一些术语,使用规范的约定和使用方式 设计思路 下...

36080
来自专栏一个会写诗的程序员的博客

ES6 极简教程(ES6 Tutorial) 文 / 东海陈光剑ES6 极简教程(ES6 Tutorial)Kotlin 开发者社区

JavaScript是ECMAScript的实现和扩展,由ECMA(一个类似W3C的标准组织)参与进行标准化。ECMAScript定义了:

10330
来自专栏深度学习之tensorflow实战篇

mongodb学习整理三,mongodb与MYSQL之间的联系。query与projection

mongodb学习整理三,mongodb与MYSQL之间的联系。query与projection,尤其在使用mongodb的IDE:NOSQL manager ...

40550
来自专栏开发与安全

《linux c 编程一站式学习》课后部分习题解答

1、假设变量x和n是两个正整数,我们知道x/n这个表达式的结果要取Floor,例如x是17,n是4,则结果是4。如果希望结果取Ceiling应该怎么写表达式呢?...

46060
来自专栏搜云库

BTA 常问的 Java基础40道常见面试题及详细答案

最近看到网上流传着,各种面试经验及面试题,往往都是一大堆技术题目贴上去,而没有答案。

61160
来自专栏程序猿DD

JDK 1.5 - 1.8 各版本的新特性总结

此文章意在借鉴前人经验,留作日后查看。如有侵犯,实属无意。我以后会注意,谢谢博友的提醒。也愿各大博友们能够共同学习和努力。

74460
来自专栏进击的君君的前端之路

知识点梳理

11220
来自专栏ml

Guava------------Cache使用方法

    简单从这几个方面描述一下如何使用Cache,对Cache的各种原理介绍此处不涉及.     1.使用场景     2.如何使用Cache     3.创...

81780
来自专栏pangguoming

Java编程最差实践(常见编程错误典范)

转载自  http://macrochen.iteye.com/blog/1393502

9020
来自专栏一个会写诗的程序员的博客

第9章 文件IO操作、正则表达式与多线程第9章 文件IO操作、正则表达式与多线程

我们在《第6章 扩展函数与属性》中已经介绍过Kotlin中的类扩展的特性。使用Kotlin的扩展函数功能,我们可以直接为 String 类实现一个 inc() ...

11730

扫码关注云+社区

领取腾讯云代金券