linux main函数汇编分析,函数栈帧(用汇编来剖析)

这次讲解一下C++函数调用,学了这么久C语言,肯定听说过栈(数据结构啊,地址空间的栈啊之类的),函数调用就和栈密切相关。

因为地址空间内的栈是从高地址向低地址生长的,也就是说压栈顺序靠后的反而地址比较低,栈底的地址高于栈顶的地址,下面贴上一段测试代码

#include

#include

void bug()

{

printf("haha I ma a bug!!");

exit(100);

}

int func(int x, int y)

{

int *p = &x;

p--;

*p = (int)bug;

printf("x:%d,y:%d\n", x, y);

int c = 0xcccc;

return c;

}

int main()

{

printf("I am main\n");

int a = 0xaaaa;

int b = 0xbbbb;

func(a, b);

printf("I should run here\n");

return 0;

}

这段代码的运行结果,并没有执行main函数的第二个printf,而是跑到了bug函数中执行,这是因为我修改了函数栈帧中的返回地址部分

b67f1cd76c088bf658c847a7fc5f459d.png

本来是打算通过linux系统来看的,但是CentOS7的栈帧实现似乎有些不同,同样的代码在centos7上面跑不通。

下面是反汇编

int main()

{

00A118E0  push        ebp

00A118E1  mov        ebp,esp

00A118E3  sub        esp,0D8h

00A118E9  push        ebx

00A118EA  push        esi

00A118EB  push        edi

00A118EC  lea        edi,[ebp-0D8h]

00A118F2  mov        ecx,36h

00A118F7  mov        eax,0CCCCCCCCh

00A118FC  rep stos    dword ptr es:[edi]

printf("I am main\n");

00A118FE  push        offset string "I am main\n" (0A16CF0h)

00A11903  call        _printf (0A1132Ah)

00A11908  add        esp,4

int a = 0xaaaa;

00A1190B  mov        dword ptr [a],0AAAAh

int b = 0xbbbb;

00A11912  mov        dword ptr [b],0BBBBh

func(a, b);

00A11919  mov        eax,dword ptr [b]

00A1191C  push        eax

00A1191D  mov        ecx,dword ptr [a]

00A11920  push        ecx

00A11921  call        func (0A11366h)

00A11926  add        esp,8

printf("I should run here\n");

00A11929  push        offset string "I should run here\n" (0A16CFCh)

00A1192E  call        _printf (0A1132Ah)

00A11933  add        esp,4

return 0;

00A11936  xor        eax,eax

}

因为main函数本身真的是个函数!所以在执行我们编写的程序之前操作系统需要保存当前它运行的状态,就跟函数调用很类似

1 00A118E0 push ebp    这句话就是把操作系统的状态压栈

2 00A118E1 mov ebp,esp    然后把栈底指针挪到新的位置

3 00A118E3 sub esp,0D8h   扩展新的栈帧,你总不能让新的栈底和栈顶挨在一起吧?

过程图我会在讲到func函数的时候给出来,更容易理解,之后的push之类的就是为了保存现场和执行前准备

1 printf("I am main\n");2 00A118FE push offset string "I am main\n"(0A16CF0h)3 00A11903 call _printf (0A1132Ah)4 00A11908 add esp,4

这部分就是调用printf的系统调用,因为库函数更多是对操作系统调用的再一次调用(封装?的说法也可以),因为我不是很懂这部分,也就不详细解释其中_printf的系统调用究竟怎么工作了

int a = 0xaaaa;

00A1190B mov dword ptr [a],0AAAAhint b = 0xbbbb;

00A11912 mov dword ptr [b],0BBBBh

赋值阶段,这里给了双字,所以是dword 通过指针赋值~,ptr就是指针,mov dst src就是把后面的给前面的,就是dst=src这样的

8ddf7f6ab39c35a5f38fdaf8b8c34002.gif

1 func(a, b);2 00A11919 mov eax,dword ptr [b]3 00A1191C push eax 联合上一句的赋值语句构成参数压栈 y=b4 00A1191D mov ecx,dword ptr [a]5 00A11920 push ecx 联合上一句的赋值语句构成参数压栈 x=a6 00A11921 call func (0A11366h) call函数调用,把fun函数的地址call一下7 00A11926 add esp,8 push了这么多不得把栈顶指针挪一挪?

8ddf7f6ab39c35a5f38fdaf8b8c34002.gif

重头戏来了,这就是这次要讲述的主要部分,函数调用时候的栈帧!令人惊讶的是传的实参是放在main函数栈帧中的。我们来结合func的汇编看一下

8ddf7f6ab39c35a5f38fdaf8b8c34002.gif

1 int func(int x, inty)2 {3 00A11770 push ebp4 00A11771 mov ebp,esp5 00A11773 sub esp,0D8h6 00A11779 push ebx7 00A1177A push esi8 00A1177B push edi9 00A1177C lea edi,[ebp-0D8h]10 00A11782 mov ecx,36h11 00A11787 mov eax,0CCCCCCCCh12 00A1178C rep stos dword ptr es:[edi]13 int *p = &x;14 00A1178E lea eax,[x]15 00A11791 mov dword ptr [p],eax16 p--;17 00A11794 mov eax,dword ptr [p]18 00A11797 sub eax,4

19 00A1179A mov dword ptr [p],eax20 *p = (int)bug;21 00A1179D mov eax,dword ptr [p]22 00A117A0 mov dword ptr [eax],offset bug (0A1127Bh)23 printf("x:%d,y:%d\n", x, y);24 00A117A6 mov eax,dword ptr [y]25 00A117A9 push eax26 00A117AA mov ecx,dword ptr [x]27 00A117AD push ecx28 00A117AE push offset string "x:%d,y:%d\n"(0A16B3Ch)29 00A117B3 call _printf (0A1132Ah)30 00A117B8 add esp,0Ch31 int c = 0xcccc;32 00A117BB mov dword ptr [c],0CCCCh33 returnc;34 00A117C2 mov eax,dword ptr [c]35 }

8ddf7f6ab39c35a5f38fdaf8b8c34002.gif

8ddf7f6ab39c35a5f38fdaf8b8c34002.gif

1 int func(int x, inty)2 {3 00A11770 push ebp4 00A11771 mov ebp,esp5 00A11773 sub esp,0D8h6 00A11779 push ebx7 00A1177A push esi8 00A1177B push edi9 00A1177C lea edi,[ebp-0D8h]10 00A11782 mov ecx,36h11 00A11787 mov eax,0CCCCCCCCh12 00A1178C rep stos dword ptr es:[edi]

8ddf7f6ab39c35a5f38fdaf8b8c34002.gif

没错了这一部分就是保存main函数的状态了,至于它保存了哪些main函数的状态,通过哪些寄存器保存的这里就不详细说明了(使用push命令的一般都是保存状态用的),刚才说的在这里上图,按步骤阅读更佳

3d950377dc7104e502e4a614c14a65a2.png

8fa6b086f14d58c9a85c154015518ab4.png这是func头两步的汇编指令

1 00A11770 push ebp2 00A11771 mov ebp,esp

分别是把返回main函数的地址就是push ebp啦,压栈!,然后把栈顶指针赋值给栈底指针,就把栈底挪过来了,这就是新的栈底了!!因为main栈帧已经告一段落了

67d5723eba30ca54ff6a2ce73ad683c5.png 这就是扩展函数栈帧的方式啦,将栈顶指针往后挪动一定的位置1 00A11773 sub esp,0D8h ,这里挪动了D8(16进制),剩下的部分就是保存寄存器状态了,我就不讲了

简单来说,两个栈帧的大概情况就是这样的

5065367e89eda9391d7383f5bbef8d56.png

所以很简单,我们不必通过y=100这样的语句就可以对y进行赋值改下代码就好

1 int func(int x, inty)2 {3 int *p = &x;4 p++;5 *p = 100;6 printf("x:%d,y:%d\n", x, y);7 int c = 0xcccc;8 returnc;9 }

9cc47e2ae01b646c28cae3a2e18a431b.png

别着急!还没结束!汇编解释来了!

1 int *p = &x;2 0009178E lea eax,[x] 这就是取偏移地址,取得x对于当前ebp的偏移地址3 00091791mov dword ptr [p],eax 简单赋值4 p--;5 00091794mov eax,dword ptr [p] 看他把寄存器来回赋值的,其实就是将把地址减个46 00091797 sub eax,4

7 0009179A mov dword ptr [p],eax8 *p = (int)bug;9 0009179D mov eax,dword ptr [p] 把函数bug的地址传过来赋值10 000917A0 mov dword ptr [eax],offset bug (09127Bh) offset也是取偏移的作用还是和lea有些不同的11 printf("x:%d,y:%d\n", x, y);12 000917A6 mov eax,dword ptr [y] 这就不说了是个系统调用,因为我也不是很懂13 000917A9 push eax14 000917AA mov ecx,dword ptr [x]15 000917AD push ecx16 000917AE push offset string "x:%d,y:%d\n"(096B3Ch)17 000917B3 call _printf (09132Ah)18 000917B8 add esp,0Ch19 int c = 0xcccc;20 000917BB mov dword ptr [c],0CCCCh 创建的局部变量位置在ebp下面~看图!21 returnc;22 000917C2 mov eax,dword ptr [c]

没看到形参对不对?就两个实参,写完了不就改了么?不对哦~

x = 10;

000A178E mov dword ptr [x],0Ah

y= 10;

000A1795 mov dword ptr [y],0Ah

我把代码改成这样看会变,这里并没有更改之前保存的寄存器里的东西,是取得了新的部分哦

dword ptr [x]这个已经不是之前的eax或者是ebx了~

0b1331709591d260c1c78e86d0c51c18.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值