反汇编角度理解函数调用机制

发布于:2022-11-29 ⋅ 阅读:(696) ⋅ 点赞:(0)

作为一个程序员,对自己写的程序,不仅要知其然,还要知其所以然。
微信公众号关注: 凌云的碎碎念

涉及到每一个细节,这样才能了如指掌,很好的掌握自己写的程序。

下面抛出问题:

  1. 函数是如何调用的?

  2. 函数调用后如何返回到原来的位置?

  3. 关于局部变量与参数到底是怎么回事?

  4. 不同的调用约定下参数是如何传递进去的、栈是如何平衡的?

  5. 返回值是如何传递给调用函数的?

我不怎么喜欢说理论,比较喜欢实践,话不多说,最好的方式就是用代码讲话。

这样讲起来比较有参考性,而不是让人感觉就是自己夸夸其谈!

先上C源码,调用3个不同的调用约定的函数,来看看它们都是如何处理的。
在这里插入图片描述
第一点,函数是如何调用的。

在上面的C源码中可以看到我这里用了3个不同的调用约定,下面我们来看看生成的反汇编:
在这里插入图片描述
首先回到第一个问题,函数是如何调用的

从反汇编层面来看,抛开其它的来看,调用函数都可以看到一个call指令

那么,CALL指令到底做了什么呢,当然,call指令后边跟不同的东西对应的处理也是不同的

我们以当前的反汇编为例来讲

CALL指令会将下一行指令的地址push到栈中,然后jmp到你调用的函数地址

我们跟进去看一看究竟:
在这里插入图片描述
注意此时画绿色框的,是CALL指令的下一行地址,我们跟进CALL看看~~
在这里插入图片描述
注意绿色框的,正是我们之前CALL指令的下一行地址。

下面我们来看第二个问题,在执行完被调用函数后,如何返回到调用者的函数
在这里插入图片描述
在这个函数执行完后可以看到ret指令,ret所做的操作就是将当前esp里地址的值放到EIP寄存器里,然后EBP-4
在这里插入图片描述
用简图来表示一下:
在这里插入图片描述
就是这么简单能理解的,可能上面我把细节都贴出来大家看起来有点晕~~~

下面的3个问题一起来解决。

来看看参数是如何传进去的,以及如何寻找的
在这里插入图片描述
首先对于这3个调用约定

第一个函数,用的调用约定为 __stdcall

可以看出来是从右至左依次push压栈然后call调用对应的函数,由被调用函数自行平衡栈

第二个函数,用的调用约定是 __cdecl

也是从右至左依次push将参数压栈然后call调用函数,不过后边多了一个add的指令

由调用函数平衡栈

第三个函数, 用的调用约定是 __fastcall

可以看出前2个参数是用ecx和edx来传递,然后后边的从右至左依次push压栈

由被调用函数平衡栈

来看看第一个函数是如何把参数传进去的:
在这里插入图片描述
注意这个函数的调用约定是 __stdcall,3个push,此时ESP为0x18FEF0
在这里插入图片描述
调用进去后,画框的一系列操作我称之为保存现场与初始化栈

保存现场是指保存上个函数执行时它的环境,初始化栈是指这个函数执行需要的栈空间

可以看到sub esp, 0x40 这就是初始化,然后全部填充为0xCCCCCCCC

也就是传说中的“烫烫烫~~~”
在这里插入图片描述
可以看到此时是通过EBP + ** 来实现找参数的

+8 是第一个参数,依此类推

EBP本身的值是上个函数用的EBP,由函数开头push ebp保存的

EBP+4 是返回调用者函数的地址

来看看局部变量是怎么回事
在这里插入图片描述
可以非常清晰的看到局部变量是通过EBP-**的方式来实现的

-4是第一个局部变量d,依此类推。
在这里插入图片描述
可以看到我们返回的三个数相加,传进去的参数值分别是1 2 3 加起来等于6,放在EAX里

一般情况下,返回值都是放在EAX里的,但也有不一般的情况,对于不一般的情况,随着以后的文章退出会涉及与讲到!

这个函数的功能执行完后,最后就是恢复上个函数执行时所需要的环境!
在这里插入图片描述
这个ret 0xC 执行的操作,将此时栈顶ESP里的值给EIP,再ESP+4,然后再ESP + 0xC

等于说这里ESP总共加了 0x10

__cdcall 和这个也差不多,只不过平衡栈是调用者自己来负责的。

来看看__fastcall
在这里插入图片描述
__fastcall的话是将edx与ecx的值给保存到了ebp-8 与 ebp-4的位置

第3个参数是通过push来实现的,所以找这个参数用的ebp+8

好了,关于函数的调用机制就写到这了,不知道大家看起来轻不轻松,作者倒是觉得看汇编是最容易理解的,有任何问题可以留言给我哦。


网站公告

今日签到

点亮在社区的每一天
去签到