《coredump问题原理探究》Linux x86版3.6节栈布局之gcc内嵌关键字

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

在intel CPU平台下,可以在代码加上__attribute__((regparm(number ) ) )指定函数调用时通过寄存器eax,edx,ecx来传递参数。其中number就是指定有多少个参数是通过寄存器来传递。由于只有这3个寄存器可以传递参数,所以number最大值为3。

那么,使用这个属性会对栈布局产生什么样的影响呢?会不会影响之前探讨过的两个规律?

多说无谓,重要是写例子来实验。

先看一下例子:

int add( int a, int b, int c,
         int d  )
{
     return a + b + c + d;
}
 
__attribute__( ( regparm( 1 ) ) ) int addOne( int a, int b, int c,
         int d )
{
     return a + b + c + d;
}
 
__attribute__( ( regparm( 2 ) ) ) int addTwo( int a, int b, int c,
         int d )
{
     return a + b + c + d ;
}
 
__attribute__( ( regparm( 3 ) ) ) int addThree( int a, int b, int c,
         int d )
{
     return a + b + c + d;
}
 
int main()
{
     int sum = 0;
     sum += add( 1,2,3,4);
     sum += addOne( 1,2,3,4);
     sum += addTwo( 1,2,3,4);
     sum += addThree( 1,2,3,4);

     return sum;
}

看一下main函数的汇编:

(gdb) disassemble main
Dump of assembler code for function main:
   0x080484e7 <+0>:     push   %ebp
   0x080484e8 <+1>:     mov    %esp,%ebp
   0x080484ea <+3>:     sub    $0x20,%esp
   0x080484ed <+6>:     movl   $0x0,-0x4(%ebp)
   0x080484f4 <+13>:    movl   $0x4,0xc(%esp)
   0x080484fc <+21>:    movl   $0x3,0x8(%esp)
   0x08048504 <+29>:    movl   $0x2,0x4(%esp)
   0x0804850c <+37>:    movl   $0x1,(%esp)
   0x08048513 <+44>:    call   0x8048470 <_Z3addiiii>
   0x08048518 <+49>:    add    %eax,-0x4(%ebp)

   0x0804851b <+52>:    movl   $0x4,0x8(%esp)
   0x08048523 <+60>:    movl   $0x3,0x4(%esp)
   0x0804852b <+68>:    movl   $0x2,(%esp)
   0x08048532 <+75>:    mov    $0x1,%eax
   0x08048537 <+80>:    call   0x8048487 <_Z6addOneiiii>

   0x0804853c <+85>:    add    %eax,-0x4(%ebp)

   0x0804853f <+88>:    movl   $0x4,0x4(%esp)
   0x08048547 <+96>:    movl   $0x3,(%esp)
   0x0804854e <+103>:   mov    $0x2,%edx
   0x08048553 <+108>:   mov    $0x1,%eax
   0x08048558 <+113>:   call   0x80484a4 <_Z6addTwoiiii>

   0x0804855d <+118>:   add    %eax,-0x4(%ebp)

   0x08048560 <+121>:   movl   $0x4,(%esp)
   0x08048567 <+128>:   mov    $0x3,%ecx
   0x0804856c <+133>:   mov    $0x2,%edx
   0x08048571 <+138>:   mov    $0x1,%eax
   0x08048576 <+143>:   call   0x80484c4 <_Z8addThreeiiii>

   0x0804857b <+148>:   add    %eax,-0x4(%ebp)
   0x0804857e <+151>:   mov    -0x4(%ebp),%eax
   0x08048581 <+154>:   leave  
   0x08048582 <+155>:   ret    
End of assembler dump.

由上面汇编可以看到:

1.      main函数调用addOne时,第一个参数通过eax传递,其它依次压入栈里。

2.      main函数调用addTwo时,第一个参数通过eax传递,第二个参数通过edx传递,其它依次压入栈中。

3.      main函数调用addThree时,第一个参数通过eax传递,第二个参数通过edx传递,第三个参数通过ecx传递,其它依次压入栈中。

可见,通过寄存器传递参数,会依次使用eax,edx,ecx。

再回忆一下上一节的规律,两个相邻的返回地址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 symbolret2都能够显示出func1,func2

也就是说,__attribute__((regparm(number ) ) )对于par_size是有影响,函数参数个数减去number才是真正的par_size。

运行一下程序来验证一下结果,在main,addOne,addTwo,addThree上打断点。

(gdb) tbreak main
Temporary breakpoint 1 at 0x80484ed
(gdb) tbreak addOne
Temporary breakpoint 2 at 0x804848d
(gdb) tbreak addTwo
Temporary breakpoint 3 at 0x80484aa
(gdb) tbreak addThree(int, int, int, int) 
Temporary breakpoint 4 at 0x80484ca
(gdb) r
Starting program: /home/buckxu/work/3/5/xuzhina_dump_c3_s5 

Temporary breakpoint 1, 0x080484ed in main ()
Missing separate debuginfos, use: debuginfo-install glibc-2.15-58.fc17.i686 libgcc-4.7.2-2.fc17.i686 libstdc++-4.7.2-2.fc17.i686
(gdb) i r ebp
ebp            0xbffff488       0xbffff488
(gdb) x /2x $ebp
0xbffff488:     0x00000000      0x4362f635
(gdb) info symbol 0x4362f635
__libc_start_main + 245 in section .text of /lib/libc.so.6
(gdb) c
Continuing.

Temporary breakpoint 2, 0x0804848d in addOne(int, int, int, int) ()
(gdb) x /2x $ebp
0xbffff460:     0xbffff488      0x0804853c
(gdb) info symbol 0x0804853c
main + 85 in section .text of /home/buckxu/work/3/5/xuzhina_dump_c3_s5

由于在main函数在执行时,压入了函数桢指针,分配了0x20个字节的局部变量空间,在调用addOne时,没有压入参数,使用了局部变量空间。所以,main函数返回地址的单元和__libc_start_main返回地址的单元应该是相差0x20 + 4 + 4 = 0x28个字节。和上面的结果一样。其它像addTwo,addThree也是同样的结果。

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏黑泽君的专栏

throws 与 throw

/* * 有些时候,我们是可以对异常进行处理的,但是又有些时候,我们根本就没有权限去处理某个异常。 * 或者说,我处理不了,我就不处理了。 * 为了解决出...

23520
来自专栏Python小屋

Python计算整数阶乘的几种方法比较

问题本身很简单,主要是通过这个小问题来演示Python的一些用法,例如测试代码运行时间、函数嵌套定义等等。 from time import time from...

72570
来自专栏idba

Python的map函数

一 简介 Python 内置了很多非常有用的函数 比如map() ,reduce(),filter(),还有lambda。熟练应用这些函数可以在写python...

9020
来自专栏赵俊的Java专栏

Python 函数

34240
来自专栏耕耘实录

PHP大小写是否敏感问题的汇总

(转自:http://www.cnblogs.com/Music/archive/2011/03/23/about-php-carecase-or-ignore...

11130
来自专栏黑泽君的专栏

感觉JVM的默认异常处理不够好,既然不好那我们就自己来处理异常呗!那么如何自己处理异常呢?

* 如果程序出现了问题,我们没有做任何处理,最终JVM会做出默认的处理。 * 把异常的名称、原因及出现的位置等信息输出在控制台。同时会结束程序。 * ...

9310
来自专栏用户2442861的专栏

深入理解java异常处理机制

http://blog.csdn.net/hguisu/article/details/6155636

9120
来自专栏青青天空树

C语言中把数字转换为字符串 【转】

在将各种类型的数据构造成字符串时,sprintf 的强大功能很少会让你失望。由于sprintf 跟printf 在用法上几乎一样,只是打印的目的地不同而已,前者...

4.7K60
来自专栏极客编程

ECMAScript 6教程 (二) 对象和函数

上面代码的方法一是直接用标识符作为属性名,方法二是用表达式作为属性名,这时要将表达式放在方括号之内。

11140
来自专栏数据结构与算法

P1181 数列分段Section I

题目描述 对于给定的一个长度为N的正整数数列A[i],现要将其分成连续的若干段,并且每段和不超过M(可以等于M),问最少能将其分成多少段使得满足要求。 输入输出...

35750

扫码关注云+社区

领取腾讯云代金券