当前位置: 首页 > news >正文

wordpress首页截断插件/徐州新站百度快照优化

wordpress首页截断插件,徐州新站百度快照优化,做竞价网站,重庆政府网渝快办从汇编角度理解C/C函数调用,能够加深我们对代码的理解,提升调试能力。本文主要讲函数调用栈的建立和销毁、call与ret指令的本质、栈变量的申请、函数参数-返回地址-ebp在栈上的相对位置。1.前言//main.c int bar(int c, int d) {int e c d;return e; }…

76cce4cee22df72f9262a07a84c7fa66.png

从汇编角度理解C/C++函数调用,能够加深我们对代码的理解,提升调试能力。本文主要讲函数调用栈的建立和销毁、call与ret指令的本质、栈变量的申请、函数参数-返回地址-ebp在栈上的相对位置。

1.前言

//main.c
int bar(int c, int d)
{int e = c + d;return e;
}
int foo(int a, int b)
{return bar(a, b);
}
int main(void)
{foo(2, 5);return 0;
}

编译这段代码,进入gdb,然后对main函数进行反汇编(disassemble main),得到如下汇编代码.

(gdb) disassemble main
Dump of assembler code for function main:0x0000000000400506 <+0>: push   %rbp0x0000000000400507 <+1>: mov    %rsp,%rbp //以上两句,建立了main函数的栈
=> 0x000000000040050a <+4>: mov    $0x5,%esi 0x000000000040050f <+9>: mov    $0x2,%edi // 将实参放入寄存器0x0000000000400514 <+14>:    callq  0x4004e7 <foo>0x0000000000400519 <+19>:    mov    $0x0,%eax0x000000000040051e <+24>:    pop    %rbp // 销毁main函数栈0x000000000040051f <+25>:    retq

此时,我们查看对应寄存器,得到如下结果. 我们可以看到,此时rbp和rsp指向同一个位置,这是在gdb中执行start命令之后,指令所在的位置。

(gdb) info registers rbp rsp
rbp            0x7fffffffe380   0x7fffffffe380
rsp            0x7fffffffe380   0x7fffffffe380

接下来我们将用这个例子来详细探索函数调用过程中的参数传递、控制转移call、新函数栈的建立、新函数栈的销毁、控制转移的恢复ret.

2.进入被调用函数的准备

40050a~40050f两条指令为函数调用传递参数的过程,我们可以看到是函数参数是倒序传入的:先传入第N个参数,再传入第N-1个参数。(注意,目前64位的机器上,函数参数的传递很少是通过栈进行的。)

接下来的三行汇编,对应的是实参的传递和对foo函数的调用。下面,我们接着执行几条汇编(si 3),进入foo函数内部,具体执行如下与解释如下.

(gdb) si 3
(gdb) disassemble foo
=> 0x00000000004004e7 <+0>: push   %rbp //0x00000000004004e8 <+1>: mov    %rsp,%rbp //新的函数栈的建立0x00000000004004eb <+4>: sub    $0x8,%rsp //申请a和b对应的栈空间0x00000000004004ef <+8>: mov    %edi,-0x4(%rbp) 0x00000000004004f2 <+11>:    mov    %esi,-0x8(%rbp) //将传入参数,存放到当前函数栈0x00000000004004f5 <+14>:    mov    -0x8(%rbp),%edx0x00000000004004f8 <+17>:    mov    -0x4(%rbp),%eax0x00000000004004fb <+20>:    mov    %edx,%esi0x00000000004004fd <+22>:    mov    %eax,%edi //将局部变量,放入寄存器0x00000000004004ff <+24>:    callq  0x4004cd <bar>0x0000000000400504 <+29>:    leaveq0x0000000000400505 <+30>:    retq

call指令

注意当指令走到0x00000000004004e7的时候(尚未执行),也就是callq执行刚刚执行完毕,对应寄存器的值如下。

(gdb) info registers rbp rsp
rbp            0x7fffffffe380   0x7fffffffe380
rsp            0x7fffffffe378   0x7fffffffe378

和上一次我们查看rsp/rbp相比,rsp向低地址移动了8位(有数据入栈),结合上下文,这一寄存器的变化只会出现在callq 0x4004e7之中。进一步,我们看一下入栈的数据是什么,也就是原来rsp被存放了什么数值。从下面的代码中我们可以看到,原来的rsp上方存放的数值是callq之后的指令的地址。

(gdb) x/8 0x7fffffffe370 
0x7fffffffe370: 0xffffe460 0x00007fff 0x00400519 0x00000000 //注意0x00400519这个值,它是main函数中对应指令的地址
0x7fffffffe380: 0x00000000 0x00000000 0xf7a30445 0x00007fff

总结一下:call指令的实际用途:1.push IP 2.通过设置 IP实现指令之间的跳转

3.新函数栈的建立与销毁

函数栈的建立

对应4004e7~4004e8处的汇编指令:重新设置rsprbp.

函数中局部变量的申请与赋值

对应的代码位于4004eb~4004f2. 从这里我们可以看出1.同一个函数内部,栈变量的申请是同时发生的,但是赋值是逐条执行。2.栈变量的申请,仅仅涉及rsp指针的移动,不会导致segment fault,但是变量的读写,会具体访问到对应的内存,将会触发segment fault。这里我们来看一个例子,猜一猜,这段代码会crash到哪一行?答案是会crash在func()这一行。因为数组a对应的空间申请发生在func()之前,而调用func的时候会将IP入栈,而此时,栈已经溢出了。

int func(void) {int b; b = 1; return b; 
} 
int main(){ func(); int a[1024*1024*8]; a[0] = 1; a[1024*1024*8 -1] = 1; return 0; 
}

函数栈的销毁

函数栈的销毁涉及两方面:栈上空间的释放与rsp和rbp的重置。我们来看看foo函数末尾对应的汇编指令

0x0000000000400504 <+29>: leaveq

继续用si来逐条执行对应指令,同时查看这条指令前后rpb和rsp的值如下

(gdb) disassemble foo
Dump of assembler code for function foo:0x00000000004004e7 <+0>: push   %rbp0x00000000004004e8 <+1>: mov    %rsp,%rbp0x00000000004004eb <+4>: sub    $0x8,%rsp0x00000000004004ef <+8>: mov    %edi,-0x4(%rbp)0x00000000004004f2 <+11>:    mov    %esi,-0x8(%rbp)0x00000000004004f5 <+14>:    mov    -0x8(%rbp),%edx0x00000000004004f8 <+17>:    mov    -0x4(%rbp),%eax0x00000000004004fb <+20>:    mov    %edx,%esi0x00000000004004fd <+22>:    mov    %eax,%edi0x00000000004004ff <+24>:    callq  0x4004cd <bar>
=> 0x0000000000400504 <+29>:    leaveq0x0000000000400505 <+30>:    retq
End of assembler dump.
(gdb) info registers rbp rsp
rbp            0x7fffffffe370   0x7fffffffe370
rsp            0x7fffffffe368   0x7fffffffe368
(gdb) si
0x0000000000400505  9   } //对应retq语句
(gdb) info registers rbp rsp
rbp            0x7fffffffe380   0x7fffffffe380
rsp            0x7fffffffe378   0x7fffffffe378
(gdb) disassemble foo
Dump of assembler code for function foo:0x00000000004004e7 <+0>: push   %rbp0x00000000004004e8 <+1>: mov    %rsp,%rbp0x00000000004004eb <+4>: sub    $0x8,%rsp0x00000000004004ef <+8>: mov    %edi,-0x4(%rbp)0x00000000004004f2 <+11>:    mov    %esi,-0x8(%rbp)0x00000000004004f5 <+14>:    mov    -0x8(%rbp),%edx0x00000000004004f8 <+17>:    mov    -0x4(%rbp),%eax0x00000000004004fb <+20>:    mov    %edx,%esi0x00000000004004fd <+22>:    mov    %eax,%edi0x00000000004004ff <+24>:    callq  0x4004cd <bar>0x0000000000400504 <+29>:    leaveq
=> 0x0000000000400505 <+30>:    retq

从理论上来说,leaveq 应该正好是入栈的逆向过程mov %rbp %rsp; pop %rbp.

控制转移的恢复ret/retq

最后我们看看retq执行完毕之后,寄存器前后的变化。

(gdb) info registers rbp rsp rip 
rbp 0x7fffffffe380 0x7fffffffe380 
rsp 0x7fffffffe378 0x7fffffffe378 
rip 0x400505 0x400505 <foo+30> 
(gdb) disassemble foo 
Dump of assembler code for function foo: 
0x00000000004004e7 <+0>: push %rbp 
0x00000000004004e8 <+1>: mov %rsp,%rbp 
0x00000000004004eb <+4>: sub $0x8,%rsp 
0x00000000004004ef <+8>: mov %edi,-0x4(%rbp) 
0x00000000004004f2 <+11>: mov %esi,-0x8(%rbp) 
0x00000000004004f5 <+14>: mov -0x8(%rbp),%edx 
0x00000000004004f8 <+17>: mov -0x4(%rbp),%eax 
0x00000000004004fb <+20>: mov %edx,%esi 
0x00000000004004fd <+22>: mov %eax,%edi 
0x00000000004004ff <+24>: callq 0x4004cd <bar> 0x0000000000400504 <+29>: leaveq 
=> 0x0000000000400505 <+30>: retq End of assembler dump. 
(gdb) si 
main () at main.c:13 
13 return 0; 
(gdb) info registers rbp rsp rip 
rbp 0x7fffffffe380 0x7fffffffe380 
rsp 0x7fffffffe380 0x7fffffffe380 
rip 0x400519 0x400519 <main+19>

这里我们得到几点结论: 1. retq指令的调用,导致出栈了一个8位的数,这个就是调用者的下一条指令。 2. retq调用之前,已经处在调用者的栈帧。retq的调用,仅仅是一个栈上保存的地址存放到rip寄存器。 3. 对比mainfoo函数的末尾,我们可以看到有细小的差别:main函数栈的销毁仅仅有push %rbp一句,但foo函数的结尾是leaveq。导致这一差别的原因是main函数建立调用栈之后,并没有移动rsp.

总结

函数调用过程中栈的变化如图

1434d3d98de87a3873ef7a26fa25a876.png

我们可以看到子程序调用之前和之后(1&6),函数栈是没有任何变化的,有变化的在于rip等相关寄存器的值;call指令执行之后与ret指令执行之前,函数栈也是相同的,而ret指令之所以能转交控制权,是因为ip的值被保存到栈上。 这里我们可以看到,栈不仅仅有保存的局部变量数据,也有对控制转移指令至关重要的寄存器临时存储。一旦栈被写坏,控制转移指令就无法正常执行。下一节,我们将讲解与栈相关的控制转移指令被写坏的场景。

http://www.lbrq.cn/news/1078363.html

相关文章:

  • 没有服务器做网站/做销售怎么和客户聊天
  • 代理登陆网站/个人网页
  • 做网站的公司哪些靠谱/自己做一个网站
  • 网站的转化率/下载班级优化大师app
  • 南通通州区网站制作/保定百度推广优化排名
  • 门户网站做啥/苹果aso优化
  • 网站怎么看是什么程序做的/什么叫seo
  • 设计师助理做网站吗/seo的中文含义是
  • 聊城网站设计/南宁seo外包服务商
  • 建房城乡建设部网站/设计公司取名字大全集
  • 做网站的企业/windows10优化工具
  • 奇想网站建设/百度关键词优化推广
  • 莱芜网站建设价格/成都网站建设
  • 国内建设地铁的公司网站/谷歌搜索引擎为什么国内用不了
  • 广东现在有什么病毒疫情/长沙搜索排名优化公司
  • 网站开发需求描述/网站排行榜前十名
  • 信阳建网站/百度推广营销
  • 网站开发4k分辨率/爱战网关键词
  • 无锡建设机械网站/淘宝引流推广平台
  • 怎么样提升网站权重/专注于网站营销服务
  • 绍兴手机网站制作/百度官方app免费下载
  • 用模板建商城购物网站/营销方案案例
  • wordpress 4.7.5下载/关键词排名优化公司推荐
  • 相城做网站的公司/台州专业关键词优化
  • 邯郸医疗网站建设/seo是什么意思电商
  • 网站性能容量的收集与分析怎么做/发稿平台
  • 做网站的公司 苏迪/东莞网络营销推广软件
  • 住房和城乡建设部科技网站首页/出售网站平台
  • 代充网站怎么做/美国搜索引擎浏览器
  • 自己的服务器如何给网站备案/seo的优化原理
  • Python爬虫实战:研究SimpleCV技术,构建图像获取及处理系统
  • Next.js 怎么使用 Chakra UI
  • Ubuntu 开启wifi 5G 热点
  • ubuntu 镜像克隆
  • Rockchip RK3568J +FPGA边缘智能系统及储能网关
  • 详解Python标准库之文件格式