老码识途1之函数调用和局部变量
无论在编程中,还是在面试中,都会遇见调用函数这个东东,但是,要是让你说函数是怎么调用的,你能回答上来吗,接下来就让我们一起探索函数如何在汇编层次上实现调用的
在接下来,我们将有几个问题要去解决
函数调用如何传递参数的
函数调用如何查找调用函数的地址的
函数内部调用过程是怎么样的
函数调用如何返回结果
如果返回值是结构体又如何返回
函数调用结束后,如何返回调用之前的状态
#include "stdafx.h"int func(int a,int b){int sum ;sum = a+b;return sum;
}int _tmain(int argc, _TCHAR* argv[])
{int a ;a = func(1,2);return 0;}
(1) 函数调用如何传递参数的
下面是上面调用的反汇编代码
int a ;a = func(1,2);
0007142E push 2
00071430 push 1
00071432 call func (071028h)
00071437 add esp,8
0007143A mov dword ptr [a],eax
从上面的push 2;push1
可以看出,函数调用参数的传递是从右向左进行压栈。然后调用函数,
函数调用如何查找调用函数的地址的
int func(int a,int b){000713D0 push ebp
000713D1 mov ebp,esp
000713D3 sub esp,0CCh
000713D9 push ebx
000713DA push esi
000713DB push edi
000713DC lea edi,[ebp-0CCh]
000713E2 mov ecx,33h
000713E7 mov eax,0CCCCCCCCh
000713EC rep stos dword ptr es:[edi] int sum ;sum = a+b;
000713EE mov eax,dword ptr [a]
000713F1 add eax,dword ptr [b]
000713F4 mov dword ptr [sum],eax return sum;
000713F7 mov eax,dword ptr [sum]
}
000713FA pop edi
000713FB pop esi
000713FC pop ebx
000713FD mov esp,ebp
000713FF pop ebp
00071400 ret
上面是函数定义的汇编代码。当函数参数压栈之后,汇编代码执行了call func (071028h)
,下面是执行call命令之前的各个寄存器内容
执行了call指令后,汇编指令直接跳转到了下面这张图的状态。EIP和ESP寄存器发生了变化。下面这个图估计就是函数调用表,用来匹对函数和函数的入口,类似中断向量表了
这里,发现栈中又压入了一个数据0x00071437h,有没有发现这里为什么会突然压入这个数据呢,带着这个疑问继续向下看吧
再通过jmp 000713D0
进入了函数调用
函数内部调用过程
首先是调用前准备工作
首先push ebp
是将ebp压栈,ebp寄存器是上个调用单元的栈低寄存器,压栈是用于保护现场,然后mov ebp,esp
将当前的栈顶作为栈底。sub esp 0CCh
这步是给当前函数调用分配空间,所以将push ebp这部分内存还是属于上个函数调用的空间,执行完sub esp,0CCh
后,接下来栈使用的空间就是属于当前的函数调用栈了,push ebx,push esi,push edi
,继续保护上一个函数调用的现场。而下部分指令是对栈的保护,实际也是一种填充
000713DC lea edi,[ebp-0CCh]
000713E2 mov ecx,33h
000713E7 mov eax,0CCCCCCCCh
000713EC rep stos dword ptr es:[edi]
rep指令的目的是重复其上面的指令.ECX的值是重复的次数.
STOS指令的作用是将eax中的值拷贝到ES:EDI指向的地址
如果设置了direction flag, 那么edi会在该指令执行后减小,
如果没有设置direction flag, 那么edi的值会增加.
REP可以是任何字符传指令(CMPS, LODS, MOVS, SCAS, STOS)的前缀.
REP能够引发其后的字符串指令被重复, 只要ecx的值不为0, 重复就会继续.
每一次字符串指令执行后, ecx的值都会减小.
stos((store into String),意思是把eax的内容拷贝到目的地址。
用法:stos dst,dst是一个目的地址,例如:stos dword ptr es:[edi]。dword ptr前缀告诉stos,一次拷贝双字(4个字节)的数据到目的地址。为什么一次非要拷贝双字呢?这和eax寄存器有关,到底神马关系,慢慢道来。。
执行stos之前必须往eax(32为寄存器)放入要拷贝的数据。上图中,eax的内容是cccccccc,不用说都明白int3中断。
这段代码是初始化堆栈和分配局部变量用的,往分配好的局部变量空间放入int3中断的原因是:防止该空间里的东东被意外执行。
由于ecx寄存器是033h=51,一共循环51次,实际这里51x4 = 204,正好把空间填满
int sum ;sum = a+b;
000713EE mov eax,dword ptr [a]
000713F1 add eax,dword ptr [b]
000713F4 mov dword ptr [sum],eax
000713F7 mov eax,dword ptr [sum]
}
这部分就是取得a和b的值,然后进行运算,将运算结果存储再栈中。最后将结果放入eax的寄存器中,
000713FA pop edi
000713FB pop esi
000713FC pop ebx
000713FD mov esp,ebp
000713FF pop ebp
00071400 ret
上面的也就是函数调用的环境恢复过程,这里主要说一下ret指令过程,还记得我们刚刚在上面说的执行call指令时,会将当前的eip寄存器的值压入栈中,现在ret指令就是将压入栈中的值重新恢复到eip中
pop eip
add esp,4
如果返回值是结构体又如何返回
我们现在都知道返回int类型可以使用eax寄存器,但是,如果返回结构体呢,寄存器能够装的下吗,这是我们就要重新探索函数的返回类型了
#include "stdafx.h"
struct MyStruct
{int a;int b;int c;int d;
};
MyStruct func(){MyStruct a;a.a = 1;a.b = 2;a.c = 3;a.d = 4;return a;
}int _tmain(int argc, _TCHAR* argv[])
{MyStruct a ;a = func();return 0;}
上面的汇编代码如下
MyStruct a ;a = func();
0064149E lea eax,[ebp-104h]
006414A4 push eax
006414A5 call func (0641195h)
006414AA add esp,4
006414AD mov ecx,dword ptr [eax]
006414AF mov dword ptr [ebp-0ECh],ecx
006414B5 mov edx,dword ptr [eax+4]
006414B8 mov dword ptr [ebp-0E8h],edx
006414BE mov ecx,dword ptr [eax+8]
006414C1 mov dword ptr [ebp-0E4h],ecx
006414C7 mov edx,dword ptr [eax+0Ch]
006414CA mov dword ptr [ebp-0E0h],edx
006414D0 mov eax,dword ptr [ebp-0ECh]
006414D6 mov dword ptr [a],eax
006414D9 mov ecx,dword ptr [ebp-0E8h]
006414DF mov dword ptr [ebp-10h],ecx
006414E2 mov edx,dword ptr [ebp-0E4h]
006414E8 mov dword ptr [ebp-0Ch],edx
006414EB mov eax,dword ptr [ebp-0E0h]
006414F1 mov dword ptr [ebp-8],eax return 0;
006414F4 xor eax,eax }
上面可以看到,编译器将[ebp-104h]值压入栈中,这个值到底是什么呢。通过编译发现EAX = 010FFD54,但是&a = 0x010FFE44,估计和a的地址没关系了,我们继续向下查找,
老码识途1之函数调用和局部变量相关推荐
- 老码识途之对象函数调用
上一期,我们讨论了普通函数的调用过程,如果没弄明白,看这里 今天所要讲的将是对象调用函数. class C{public:int a;int b;int c;void f(int t){a = t;} ...
- 老码识途:从机器码到框架的系统观逆向修炼之路 pdf电子书
重要提示尊敬的用户您好,由于老码识途:从机器码到框架的系统观逆向修炼之路pdf书受百度网盘影响无法做公共分享,只能私密分享,有不到之处请多多谅解! 百度网盘链接: http://pan.baidu.c ...
- 《老码识途》读书笔记:第一章(上)
<老码识途>读书笔记:第一章--欲向码途问大道,锵锵bit是吾刀(上) 1.赋值语句 对于全局变量赋值语句,例如下面这句: 1 int gi; 2 void main(int argc ...
- 老码识途学习笔记(一)
第一章 全局变量引发的故事 1 程序存储区 程序存储区一般有下列几段: 程序代码区(SECTION.txt ): 用来存放可执行文件的操作指令(二进制),也就是说是它是可执行程序在内存中的镜像.代码段 ...
- 《老码识途:从机器码到框架的系统观逆向修炼之路》- 第1章 - 总结
本章学到了什么 调试技巧:在VS中断点调试,查看反汇编代码,step into进行步进调试,运行过程中查看寄存器.内存地址.变量值变化等. 机器码构造能力:使用C/C++中的直接在C代码里写汇编语言的 ...
- 老码识途——在堆中构建mov和jmp指令
// asmjmp.cpp : 定义控制台应用程序的入口点. // #include <stdio.h> #include <malloc.h>int gi; void * a ...
- 老码识途读书笔记 1
知识点记录: 1.int 或指针类型的全局变量默认初始化为0,局部变量则为0xcccccccc.(win7 + vs2008 ) 2.内存溢出攻击即使用6个字节空间改变程序执行流程达到某种目的.话说当 ...
- 老码识途之构造函数和析构函数
对象初始化过程就是先父类构造函数,再子类构造函数.,那么我们从汇编角度去探索这个过程是怎么样的 class P{public:int a ;P(){a = 1;}~P(){a = 4;} };clas ...
- 读书 --- 老码识途
上周在图书馆借了这本书,这个周末细看了下,是本好书.作者应该是个大学教授叫韩宏.书中讲的很底层,一开始就告诉大家如何debug一段程序,在VS2008里面查看内存.寄存器.反汇编.通过这些来认识汇编. ...
最新文章
- selenium+python自动化81-html报告优化(饼图+失败重跑+兼容python23)
- HTML5 学习笔记(一)- video
- [CF843D]Dynamic Shortest Path
- Django 3.2.5博客开发教程:用Admin管理后台管理数据
- python3.7安装包多大_python3.7 pip 安装第三方包
- int** 赋值_关于Java语言复合赋值运算符的两个问题,快来瞧瞧
- 这些深度学习术语,你了解多少?(上)
- 自定义用户控件显示属性分类、描述、默认值
- 类和对象编程(一):类成员函数
- cmakelists语法_cmake使用教程(六)-蛋疼的语法
- 洞穴辐射(radiation)
- 【机器学习】线性回归实战案例三:股票数据价格区间预测模型(国外+国内数据)
- 推动5G+北斗高精度定位系统更好赋能千行百业
- 小米手机android版本怎么更新,基于Android 10的MIUI稳定版正式推送,你的小米手机更新了吗?...
- python范围缩放_如何缩放到初始绘图/缩放的特定范围?
- Temporal Abstraction
- nfc卡模式与标准模式_全功能NFC是什么意思?点对点/读写卡/卡模拟三种模式介绍...
- 高通Android display分析【转】
- python3数据库框架_python3大框架简介 小收藏
- 简单的基于交换机迁移的SDN控制器负载均衡实验