从汇编的角度分析函数调用过程(2)
#include <stdio.h>int add(int a, int b) {int c = 0;c = a + b;return c;
}int main()
{int r = add(1, 2);return 0;
}
我们使用Visual Studio 2017
编译上面代码,并在在工程配置中将函数调用约定设置为__cdecl
。
在程序调试过程中,可以在Visual Studio
的反汇编窗口
中看到C++代码对应的汇编代码,以及寄存器
窗口中看到各个寄存器的值。
main
函数的反汇编代码如下:
int main()
{
00DD1720 push ebp // 参见add函数中关于这一部分的解析
00DD1721 mov ebp,esp
00DD1723 sub esp,0CCh
00DD1729 push ebx
00DD172A push esi
00DD172B push edi
00DD172C lea edi,[ebp-0CCh]
00DD1732 mov ecx,33h
00DD1737 mov eax,0CCCCCCCCh
00DD173C rep stos dword ptr es:[edi] int r = add(1, 2);
00DD173E push 2 // 参数b入栈
00DD1740 push 1 // 参数a入栈
00DD1742 call add (0DD1276h) // 调用add函数。CALL指令相当于执行一条PUSH指令加一条JMP指令,PUSH指令用于压入该指令的下一条指令地址到栈中,用于执行完子函数之后返回来。JMP指令用于跳转到子函数所在位置开始执行子函数。
00DD1747 add esp,8 // 因为是__cdecl,所以由调用者来平衡堆栈.
00DD174A mov dword ptr [r],eax return 0;
00DD174D xor eax,eax
}
执行进入add
函数后,add
函数内的汇编代码如下:
int add(int a, int b) {
00DD16D0 push ebp // ebp入栈,相当于暂存ebp的值
00DD16D1 mov ebp,esp // 将esp赋值给ebp,在该函数之后的执行过程中不会再改变ebp的值。
00DD16D3 sub esp,0CCh // 在栈上分配0xCC大小的局部变量存储区域
00DD16D9 push ebx // 暂存ebx
00DD16DA push esi // 暂存esi
00DD16DB push edi // 暂存edi
00DD16DC lea edi,[ebp-0CCh] //下面4行代码(含该条)实现将0xCC大小的局部变量存储区域全部赋值为0xCC
00DD16E2 mov ecx,33h // ecx存储循环次数,结合rep指令使用。为什么是0x33次了?因为是按照4个字节赋值的,0x33 * 0x4 = 0xCC
00DD16E7 mov eax,0CCCCCCCCh
00DD16EC rep stos dword ptr es:[edi] // 循环赋值int c = 0;
00DD16EE mov dword ptr [c],0 // 将局部变量c赋值为0c = a + b;
00DD16F5 mov eax,dword ptr [a]
00DD16F8 add eax,dword ptr [b]
00DD16FB mov dword ptr [c],eax return c;
00DD16FE mov eax,dword ptr [c] // 将结果存储到eax中。在函数调用中返回结果都是存储在eax中的。
}
01191701 pop edi // 将edi的值还原到函数调用前
01191702 pop esi // 将esi的值还原到函数调用前
01191703 pop ebx // 将ebx的值还原到函数调用前
01191704 mov esp,ebp // 移动栈顶到ebp位置,从而跳过了局部变量存储区域
01191706 pop ebp // 将ebp的值还原到函数调用前
01191707 ret // ret指令等同于:弹出此时栈顶的值给eip,// 因为此时栈顶存储的刚好是函数返回地址,所以相当于将返回地址赋值给eip,从而实现了返回到函数调用的地方。
在上面代码的注释中已经包含了详细的解释,特别值得注意的几个地方是:
1. rep stos dword ptr es:[edi]
结合edi
, ecx
来初始化局部存储区域。
2. 函数call
指令之前的参数压栈顺序。
3. CALL
指令相当于执行一条PUSH
指令加一条JMP
指令,PUSH
指令用于压入该指令的下一条指令地址到栈中,用于执行完子函数之后返回来。JMP
指令用于跳转到子函数所在位置开始执行子函数。
4. 因为是__cdecl
,函数调用完之后,调用方使用add esp,8
来平衡堆栈。
5. ret
指令等同于:弹出此时栈顶的值给eip,巧妙之处在于此时栈顶存储的刚好是函数返回地址。
从汇编的角度分析函数调用过程(2)相关推荐
- 从汇编的角度分析函数调用过程(1)
一. 函数参数传递形式 函数的参数传递有2种方式:堆栈方式.寄存器方式. 如果是堆栈方式传递的,就需要定义函数参数在堆栈中的传递顺序,并约定函数被调用之后,由谁来平衡堆栈: 如果是寄存器方式传递的,就 ...
- C语言的函数调用过程(栈帧的创建与销毁)
从汇编的角度解析函数调用过程 看看下面这个简单函数的调用过程: 1 int Add(int x,int y)2 {3 int sum = 0;4 sum = x + y;5 return sum;6 ...
- 函数调用过程简单分析
C/C++函数调用过程分析 这里以一个简单的C语言代码为例,来分析函数调用过程 代码: 1 #include <stdio.h> 2 3 int func(int param1 ,int ...
- 函数调用过程中的栈帧结构及其变化
前言:本文旨在从汇编代码的角度出发,分析函数调用过程中栈帧的变化. 栈帧的简单介绍: 当某个函数运行时,机器需要分配一定的内存去进行函数内的各种操作,这个过程中分配的那部分栈称为栈帧.下图描述了栈帧的 ...
- 37.Linux驱动调试-根据oops的栈信息,确定函数调用过程
上章链接入口: http://www.cnblogs.com/lifexy/p/8006748.html 在上章里,我们分析了oops的PC值在哪个函数出错的 本章便通过栈信息来分析函数调用过程 1. ...
- 深入浅出根据函数调用过程谈栈回溯原理
通过分析函数调用过程的堆栈变化,可以看出在被调函数的EBP寄存器地址存放的是调用函数的EBP寄存器地址,EBP地址+4存放的是函数调用完成后的下一条指令存放地址,该指令的前一条 ...
- JVM系列之:从汇编角度分析Volatile
文章目录 简介 重排序 写的内存屏障 非lock和LazySet 读的性能 总结 简介 Volatile关键字对熟悉java多线程的朋友来说,应该很熟悉了.Volatile是JMM(Java Memo ...
- C函数调用过程原理及函数栈帧分析
在x86的计算机系统中,内存空间中的栈主要用于保存函数的参数,返回值,返回地址,本地变量等.一切的函数调用都要将不同的数据.地址压入或者弹出栈.因此,为了更好地理解函数的调用,我们需要先来看看栈是怎么 ...
- 从函数调用过程中的堆栈变化理解缓冲区溢出
一.说明 本来是想直接写一个缓冲区溢出的例子,但是一是当前编译器和操作系统有溢出的保护措施没有完全弄清怎么取消,二是strcpy等遇到00会截断需要进行编码这比较难搞,所以最终没有实现. 但已经双看了 ...
- 函数调用过程实例详解
原文标题:<函数调用过程探究> 引言 如何定义函数.调用函数,是每个程序员学习编程的入门课.调用函数(caller)向被调函数(callee)传入参数,被调函数返回结果,看似简单的过程,其 ...
最新文章
- PAGER set to stdout_Python || 学习笔记(4):dictamp;amp;set
- 二代三代转录组测序分析实战班
- 【Leetcode】79.单词搜索
- SpringMVC(SSM)框架搭建JavaWeb项目时,前端页面文件上传,后台Java下载功能实现及相关问题记录说明
- A multi-faceted language for the Java platform
- Struts2的控制器(Controller)的工作流程图
- uiautomator +python 安卓UI自动化尝试
- SQL服务器名称更改
- kakfa怎么看消息是否堆积_不停的打开微信,只为看你是否更新了消息
- Android线程池的简单使用
- RocketMQ-0.1
- ubuntu下Pure-FTPd的安装和配置
- 蚂蚁金服:开源增强版 SpringBoot 的研发框架!
- 解决idea使用jdbc连接数据库失败的方法(针对驱动导入失败)
- php酒店系统论文,基于PHP的酒店管理系统PHP1008(毕业设计+论文)
- c++中文件打开失败
- 2021年中国不间断电源(UPS)行业市场规模、产品结构及发展趋势分析:UPS电源向节能环保方向发展 [图]
- 优秀课程案例:母亲节!用Scratch编程送给母亲最好的贺卡礼物!
- arduino实现rgb灯循环亮起
- Spark(四)— Spark Streaming