【汇编 C】函数调用的底层原理解析堆栈图

17 篇文章 1 订阅

目录

前言:

1、函数定义

       返回值类型

       函数名

       参数列表

       函数返回

       了解即可

2、查看反汇编

3、环境配置

       创建excel

        vs2010窗口

4、逐步分析汇编并绘制堆栈图

       缓冲区

总结


前言:

        本文的很多地方可能会有超纲的内容,听不懂没关系,因为我们主要也不是讲这些东西的,主要讲的是堆栈图的绘制,这样以后遇到C语言代码,就可以直接转到反汇编去看看底层是如何去实现的,所以能看懂的地方就看,看不懂的就跳过,知道是这么回事就行了。当然能看懂更好,毕竟百度一大堆东西嘛。

1、函数定义

         返回类型 函数名(参数列表)

        {

                return ;                --        分号必须是英文的

        }

        例子:

        这个函数想表达什么东西呢?

       返回值类型

        首先函数的返回值类型是int,int类型占四个字节,它是用来说明返回值的数据类型是什么

        int         4个字节

        short     2个字节         

        char      1个字节

        long      8个字节               

        等等,因为这些都是整型,所以long类型的函数能返回char类型的不足为奇,但是char类型不能返回long类型的。

       函数名

        对于函数名,这个是你自己想怎么取就怎么取的,前提是要遵守函数名格式的规则,函数名的规则就是:

        1、必须字母、数字、下划线组成;

        2、数字不能开头

       参数列表

        参数列表分为形参和实参,它们的区别如下:

        形参出现在 函数定义 中,在整个函数体内都可以使用, 离开该函数则不能使用。. 实参出现在 主调函数中,进入被调函数后,实参变量也不能使用 。

        可能概念会有些模糊

        形参:

        实参:

        打印返回值:

       函数返回

        我们学汇编的时候知道,函数调用了之后,函数中是必须设有RETN做返回的,不然会导致函数运行一下就直接乱了,找不到刚刚执行的地方了,所以在C语言也是,函数最好还是做一下返回,并且这里的函数返回后面会有一个值,叫做返回值,返回值的数据类型必须和函数名前边的类型保持一致。

        long可以返回char,因为他们都是整型

        char不能返回long,因为char是一个字节,long八个字节放在一个字节中,可能会丢失数据。

        那是不是当返回值类型和返回值的数据宽度一样,就可以返回了?毕竟放得下嘛

        不是 

       了解即可

        指针类型在32位程序中占4个字节,在64位程序中占八个字节。但是我们的int(4个字节)类型是没有办法返回一个指针的。

2、查看反汇编

         待解析的函数代码如下:

int plus(int x,int y)
{                       // 括号内是函数体,可以写你想要实现的东西,不是只能写返回
	return x+y;
}

int main()				// 入口程序 程序开始执行的地方
{
	plus(1,2);			// 调用函数
	return 0;			// 执行结束
}

        首先我们需要转到反汇编模式,然后再一步一步调试

        设置断点,然后运行,然后ALT+8转到反汇编

        但是有的时候,当你转到反汇编的时候,没有黄色的箭头,这个时候我们只需要F10执行一下,然后屏幕会自动追踪到黄色箭头的位置BUT,万一你的断点设置在程最关键的一步了,但是这个时候你的屏幕上没有黄色的箭头,难道你要F10执行吗?

        这里教大家一个小技巧,你可以在你想要设置断点的上面加一行"没有作用的汇编指令",然后你把断点设置在这个指令上就行了。

        如下:

        像这样,加一行没用的指令,把断点设置在这条指令上就行了,那么当屏幕上没有出现黄色的箭头的时候也可以放心的使用F10了。

        __asm是内嵌汇编的关键字,之前讲过。

3、环境配置

        我们既然要画堆栈图,那么就要准备画图的工具啊,所以我一般会使用excel表格去进行绘制,并且这里如果大家使用的是vs2010的话,应该把寄存器窗口和内存窗口调出来,既然我们是分析底层分析汇编的,那么寄存器和内存肯定不能少啊。

       创建excel

        win11:右键桌面->新建->xls工作表

        

 

        双击进入excel表,点击试图,取消网格线

        随便选中一列,右键

 

        上面这一列就是我们用来模拟堆栈的地方

        vs2010窗口

        首先我们运行调出反汇编

        

         

        内存尽量设置成四个字节四个字节的显示

 

         至此我们的环境就配置好了,下面我们来逐步分析函数代码底层是如何操作的。

4、逐步分析汇编并绘制堆栈图

        首先我们的寄存器中的值,先写在堆栈图上

        下面是我的,栈顶esp栈底ebp,每个人的不一样

        绘制堆栈图:

 

        查看指令

        F11执行,esp发生了改变 

         堆栈图变化:

         查看内存,验证:

        指令:

 

        执行:

 

        堆栈图:

 

        验证:

 

        通过上面两行代码我们知道了函数的传参是从右往左传 

       指令,这里call指令必须F11执行,不然会直接跳过。

        执行:

        堆栈应该会存放call指令的下一个地址 

        验证:

 

        指令:

 

        执行,这里JMP会跳到后面的地址,所以EIP会存放这个地址

 

        堆栈无变化

        指令:

        执行:

 

        堆栈:

 

        验证:

 

        指令:

 

        执行:

 

        堆栈:

 

        无需验证,这部操作就是ebp寻址的第一步。

        指令:

 

        执行,esp减0C0H,H是十六进制的标志,所以就是减去C0

        C0等于十进制的192,然后一行显示四个字节,所以192/4=48行

        所以堆栈往上提上48行

 

        验证:

 

        堆栈:

 

        至此,堆栈提升完成。

        指令:

 

        执行:

 

        堆栈:

 

        验证:

        指令:

 

        执行:

 

        excel:

 

        验证:

 

        指令:

 

         执行:

        excel:

 

       缓冲区

        从31F608到31F6C8就是我们常说的缓冲区,关于缓冲区的知识以后再讲      

        验证:

 

        上面这三行指令的作用就是将需要用到的寄存器进行备份。

        指令:

        执行:将[ebp-C0]这个“地址” 存入edi寄存器

        

        excel:

 

        无需验证

        指令:

 

        执行,往ecx里存放十六进制的C0

 

        堆栈无变化,无需验证

        指令:

 

        执行:

 

        堆栈无变化

        指令:

 

        rep重复执行指令,重复的值就是ecx的值30,换成十进制就是48

        所以这里重复执行48次将eax的值(dword)存放到edi指向的位置,我们上面edi指向了31F608,所以这里也就是将我们刚刚提升堆栈中的48行全部设置为0xCCCCCCCC。

        执行:

        堆栈:

 

        无需验证。

        上面几行指令,就是将缓冲区填满CC,CC就是汇编指令INT 3,当程序运行时遇到值为CC的地址就会理解停下来,这里填充CC有很大的争议,这是vs编译器默认的做法,我们只需要知道0xCC就是断点就行了。

        指令:

        解释一下,这里中括号里边的"x"是编译器做的优化,实际应该是ebp+8,也就是第一个参数(第二次传进来的参数)。所以这行指令应该是 mov         eax,dword ptr [ebp+8]

        执行:

        无需查看堆栈。

        指令:

 

        add         eax,dword ptr [ebp+CH]

        执行:

        无需查看堆栈

        下面这几行指令就是将寄存器原来的值还给寄存器,我就不演示了

 

         对比一下原来的值一切正常

        执行ret,函数调用完毕 

        此时的堆栈大概如下:

 

        所以下面这行指令就是平衡堆栈的,这里用的是外平栈

 

        执行之后的堆栈如下:

 

        下面的指令已经没必要再跟了,xor eax,eax就是将eax寄存器置零的

        xor相同为0,不同为1,所以这条指令后的eax肯定为0

 

总结

        通过上面的逐步观察,我们可以发现,调用函数无非就是call指令进入函数,然后提升堆栈,创建缓冲区,之后将需要用到的寄存器拷贝一份,然后运算,完成后将寄存器的值还原,平衡堆栈。就是这些步骤完成了一个函数的调用。

        关于函数调用底层是如何运行的,就讲到这里,如果有错误的地方,希望大佬指正。我个人是新手,关于写文章讲解方面有什么建议的话可以私信我的博客,谢谢大家!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值