百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

CPU眼里的:函数调用 | 返回(cpu执行程序,程序返回前,data)

liebian365 2025-02-03 13:50 16 浏览 0 评论

为什么有人说C/C++语言的函数返回,是最高效、脆弱的设计,让我们用CPU的视角一探究竟


01

提出问题

请问当函数执行完毕后,函数怎么知道:自己应该返回到哪里?它是否有走错路的可能?为什么有人说函数返回机制:是C/C++最脆弱的设计呢?

今天,让我们用 64位CPU 的视角,回答这个问题。一起揭示:如此精妙的设计背后,隐藏的致命缺陷。


02

代码分析

打开 Compiler Explorer,写一个简单的函数func;定义一个临时数组,作一下赋值;然后,作一下函数调用,如图所示。

左上角是:C语言的源码;左下角是:对应的汇编指令,其中左下角的黑色数字代表:每条汇编指令所在的内存地址(由于是64位的CPU,每个内存地址占用8个字节);

右边的内存块,是当前线程的“堆栈”,为了方便展示:“堆栈”的堆叠结构,下面是高端地址,上面是低端地址;每个内存块的字节长度为:8 个字节。(如果习惯:高端地址在上的“堆栈”结构,可以把书籍旋转180度观看,二者只有视角的差异,没有本质的区别)

初始“栈帧”,是 main 函数的“栈帧”,位于 红、蓝 两条线之间,如图所示。

红色水位线,是CPU寄存器 rsp 的值,用来标识:“栈顶”的内存地址;蓝色基准线,是CPU寄存器rbp的值,用来标识:main 函数的“栈帧”基地址。不用关心 main 函数的“栈帧”,一切从调用函数func开始(对应的指令地址是:4011059),如图所示。

call 指令,它包含了 2 个操作:

操作 1:把下一条指令的地址,也就是函数 func 的返回地址(0x401105e)压入堆栈,红色的“栈顶”水位线,也随之升高8个字节。

操作 2:CPU 跳转到函数 func 的首地址。

至此,函数 func 的调用过程就完成了。接着,开始执行函数 func的第 1 条push指令,如图所示。

先把 rbp 寄存器的值(0x80000030),压入“栈顶”;“栈顶”水位线,也随之升高。至此,main 函数的“栈帧”保护工作,完成!

随后的 mov 指令,更新一下“栈帧”基准线,让它与“栈顶”水位线齐平,如图所示。

至此,函数 func 的“栈帧”设置,完成!

关于“栈帧”的详细分析,请参看“CPU眼里的{函数括号}”,这里只点到为止。

随后两条mov指令,对数组赋值,如图所示。

以蓝色基准线为基准,分别在偏移为 8 和 16 的地方,写入:2 和 1。至此,函数功能完成,可以返回了。

pop 指令,把事先压入“栈顶”的 rbp 值(0x80000030),返还给寄存器 rbp,如图所示。


这样蓝色基准线,就恢复到了最开始的位置;同时,“栈顶”的红色水位线,也随之下降。

最后的 ret 指令,跟 pop 指令类似,如图所示。

把“栈顶”处的返回值(0x401105e),弹给 CPU 寄存器 rip,这样,CPU 就可以跳转到:主调函数 main 被打断的地方:0x401105e,继续执行了。

同时,随着“栈顶”的下降,红色水位线也随之下降。这样,红、蓝两条线,都恢复到了最开始的位置。堆栈内存完璧归赵,一点没多,一点没少。一切恢复如初,就跟没有发生过函数调用一样。

至此,整个函数的调用、返回过程,完成!必须称赞这种巧妙的设计,高效,简洁,还节省空间;但优点即缺点,这种就地存放返回地址的方法,即方便了函数返回,也方便了黑客入侵。

让时间倒退到:给数组赋值的阶段,如图所示。

如果以此类推的话,数组的第 3 号元素,就对应着函数的返回地址。如果我们让数组越界,强行给不存在的:第 3 号元素赋值,不就可以改变函数 func 的返回地址了吗?

说干就干!我们将返回地址改为:一个恶意函数malfunc的内存首地址,也就是:0x401165,让我们看看运行结果,如图所示。

不出所料,输出了:4 个骷髅头!恶意函数被执行了。


03

总结

1. 主调函数,在调用函数时,会把返回地址,偷偷存放在:“堆栈”中。

2. 被调函数返回时,会从“堆栈”中取出返回地址,引导 CPU 跳回到:主调函数。

3. 不同编译器,在实现函数返回上,会略有不同;但殊途同归,一通百通。

最后,函数返回的设计方法,简洁、高效;但缺点是:返回地址这种关键数据,离临时变量太近。容易被越界访问,导致程序意外崩溃;也为黑客攻击,留下了难以弥补的窟窿。

所以,用C/C++编写代码,对程序员的要求很高。即便语法规则,滚瓜烂熟,也难以百毒不侵;需要:眼中有代码,心中有指令;强大的内功,才是避坑的关键。


04

热点问题

Q1:你怎么知道malfunc的内存地址?

A1:可以选择打印malfunc的内存地址;也可以在Compiler Explorer里面,打开设置,勾选“Compile to binary”,直接显示malfunc的内存地址。

或者可以通过使用malfunc这个函数名称,获得它的内存地址,代码如下:

a[3] = malfunc;


Q2:章节的最后,是“堆栈”溢出攻击吗?如何才能让正常的程序,“堆栈”溢出呢?

A2:是的,这就是大家常说的“堆栈”溢出攻击的基本工作原理,现实操作起来,还需要考虑很多细节问题。一般来说,无论是C/C++,还是JavaScript等前端开发语言,用户输入,是一个堆栈溢出的攻击点;当输入量过大后,就很可能导致“堆栈”溢出。


Q3:递归函数,也符合本章节所描述的规律吗?

A3:是的,递归函数也是函数,虽然它看起来非常的玄幻?,如图?所示:

?但回调函数的调用、返回机制,跟普通函数完全一致。但需要注意的是:递归函数,最后一定要满足“返回”条件,否则就会无穷递归下去,也就是无穷调用下去,如图所示。

如你所见,无论多少行代码,CPU都是可以执行的,但是“堆栈”内存的数量是有限的。

不断的消耗内存,而不是释放、归还内存,“堆栈”内存迟早会被消耗殆尽(也叫:“堆栈”溢出),最终会触发操作系统、或CPU的安全机制。这也就是为什么写递归函数时,特别容易导致程序崩溃的原因。如果递归函数一直没有返回,“堆栈”溢出只是一个时间问题。


07

更多知识

如果喜欢阿布这种解读方式,希望更加系统学习这些编程知识的话,也可以考虑看看由阿布亲自编写,并由多位微软大佬联袂推荐的新书《CPU眼里的C/C++》

相关推荐

4万多吨豪华游轮遇险 竟是因为这个原因……

(观察者网讯)4.7万吨豪华游轮搁浅,竟是因为油量太低?据观察者网此前报道,挪威游轮“维京天空”号上周六(23日)在挪威近海发生引擎故障搁浅。船上载有1300多人,其中28人受伤住院。经过数天的调...

“菜鸟黑客”必用兵器之“渗透测试篇二”

"菜鸟黑客"必用兵器之"渗透测试篇二"上篇文章主要针对伙伴们对"渗透测试"应该如何学习?"渗透测试"的基本流程?本篇文章继续上次的分享,接着介绍一下黑客们常用的渗透测试工具有哪些?以及用实验环境让大家...

科幻春晚丨《震动羽翼说“Hello”》两万年星间飞行,探测器对地球的最终告白

作者|藤井太洋译者|祝力新【编者按】2021年科幻春晚的最后一篇小说,来自大家喜爱的日本科幻作家藤井太洋。小说将视角放在一颗太空探测器上,延续了他一贯的浪漫风格。...

麦子陪你做作业(二):KEGG通路数据库的正确打开姿势

作者:麦子KEGG是通路数据库中最庞大的,涵盖基因组网络信息,主要注释基因的功能和调控关系。当我们选到了合适的候选分子,单变量研究也已做完,接着研究机制的时便可使用到它。你需要了解你的分子目前已有哪些...

知存科技王绍迪:突破存储墙瓶颈,详解存算一体架构优势

智东西(公众号:zhidxcom)编辑|韦世玮智东西6月5日消息,近日,在落幕不久的GTIC2021嵌入式AI创新峰会上,知存科技CEO王绍迪博士以《存算一体AI芯片:AIoT设备的算力新选择》...

每日新闻播报(September 14)_每日新闻播报英文

AnOscarstatuestandscoveredwithplasticduringpreparationsleadinguptothe87thAcademyAward...

香港新巴城巴开放实时到站数据 供科技界研发使用

中新网3月22日电据香港《明报》报道,香港特区政府致力推动智慧城市,鼓励公私营机构开放数据,以便科技界研发使用。香港运输署21日与新巴及城巴(两巴)公司签署谅解备忘录,两巴将于2019年第3季度,开...

5款不容错过的APP: Red Bull Alert,Flipagram,WifiMapper

本周有不少非常出色的app推出,鸵鸟电台做了一个小合集。亮相本周榜单的有WifiMapper's安卓版的app,其中包含了RedBull的一款新型闹钟,还有一款可爱的怪物主题益智游戏。一起来看看我...

Qt动画效果展示_qt显示图片

今天在这篇博文中,主要实践Qt动画,做一个实例来讲解Qt动画使用,其界面如下图所示(由于没有录制为gif动画图片,所以请各位下载查看效果):该程序使用应用程序单窗口,主窗口继承于QMainWindow...

如何从0到1设计实现一门自己的脚本语言

作者:dong...

三年级语文上册 仿写句子 需要的直接下载打印吧

描写秋天的好句好段1.秋天来了,山野变成了美丽的图画。苹果露出红红的脸庞,梨树挂起金黄的灯笼,高粱举起了燃烧的火把。大雁在天空一会儿写“人”字,一会儿写“一”字。2.花园里,菊花争奇斗艳,红的似火,粉...

C++|那些一看就很简洁、优雅、经典的小代码段

目录0等概率随机洗牌:1大小写转换2字符串复制...

二年级上册语文必考句子仿写,家长打印,孩子照着练

二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...

一年级语文上 句子专项练习(可打印)

...

亲自上阵!C++ 大佬深度“剧透”:C++26 将如何在代码生成上对抗 Rust?

...

取消回复欢迎 发表评论: