利用汇编查看C++函数调用
这篇文章的内容是一个老生常谈的问题----> 函数是如何被调用的。
本文用汇编代码研究函数调用的过程,参数调用的方式,函数值的返回。
1. 函数是如何实现调用的
函数的调用是用call 和 ret 指令实现的。这里首先简单说明下这call指令的作用:call指令与跳转指令相似,但是不同的是保持返回信息, 即将下一个指令入栈,当遇到ret的时候,返回到这个地址。
呵呵,有点抽象, 下面就用实例来说明,我们写下如下代码:
1 int add(int a, int b)
2 {
3 return a+b;
4 }
5
6 int _tmain(int argc, _TCHAR* argv[])
7 {
8 int a(1), b(2);
9 b = add(a,b);
10 return0;
11 }
很简单的代码,这里我们关注的是add函数是如何被调用的,下面我们看下其汇编代码的实现
1 //int a(1), b(2);
2 004113FE mov dword ptr [a],1
3 00411405 mov dword ptr [b],2
4 //b = add(a,b);
5 0041140C mov eax,dword ptr [b]
6 0041140F push eax
7 00411410 mov ecx,dword ptr [a]
8 00411413 push ecx
9 00411414 call add (41109Bh)
10 00411419 add esp,8
11 0041141C mov dword ptr [b],eax
12 //return 0;
13 0041141F xor eax,eax
如上,红色的部分为调用的add函数的代码, 当调用add函数时相关寄存器的情况是
EIP = 0041109B
ESP = 0012FE78
EBP = 0012FF68
其中EIP是下一个指令的地址,而ESP就是保存了add函数的地址了。我们看以在memory窗口看下其值:
0x0012FE78 19 14 41 00
根据小字节存储顺序 其值应该为 00411419, 刚好为下一个指令的地址。
再来看下add函数内部的调用情况:
1 int add(int a, int b)
2 {
3 004113A0 push ebp
4 004113A1 mov ebp,esp
5 //……一大堆对esp的操作
6 004113C7 mov esp,ebp
7 004113C9 pop ebp
8 004113CA ret
9 }
这里我们需要注意的是 如上列出的指令就是所谓的堆栈平衡。首先ebp入栈, 然后把esp的值赋给ebp, 中间有一大堆对esp的操作, 最后把ebp的刚才保存的值赋给esp, ebp出栈。这样做的原因是为了中间对栈的操作不影响ebp的值,当函数返回时,其值依旧是刚进入此函数的ebp的值。
2. 函数参数的调用方式
一.堆栈方式
二.寄存器方式
三.全局变量
2.1 堆栈方式
如果函数通是堆栈方式的调用就必须要做如下两个方面:
1. 对应堆栈的顺序
2. 由谁来平衡堆栈(调用者还是子程序)
所以我们必须约定函数参数调用的方式。常见的调用约定由如下几个:
|
调用约定 |
_cdecl(C/C++的默认方式) |
pascal |
Stdcall(win32 API的默认方式) |
|
参数传递顺序 |
从右到在 |
从左到右 |
从右到在 |
|
平衡堆栈着 |
调用者 |
子程序 |
子程序 |
在这里我们看下_cdecl调用时如何实现的。还是本文的第一段代码,这里我们只观察其参数的传递:
1 0041140C mov eax,dword ptr [b]
2 0041140F push eax // ESP = 0012FE84
3 00411410 mov ecx,dword ptr [a]
4 00411413 push ecx
5 00411414 call add (41109Bh) //ESP = 0012FE7C
6 00411419 add esp,8 //ESP = 0012FE84
如上, 首先push b, 然后push a, 一切执行完之后在 00411419 平衡堆栈
这里需要指出的一点是栈是向下增长的,也就是说每一次push的时候,ESP的值会减少。
2.2 寄存器方式
寄存器传递函数参数的方式没有一个统一的标准,所以不同编译器的实现各有不同。这里只选取MSVC来说明。让在MSVC中采用_fasetcall来传递参数是,最左边的不大于4字节的参数分别放在ecx和edx中,寄存器用完以后就按照从右到左的顺序人栈。
我们可以看下如下的例子:
1 int _fastcall add(int a, int b, int c)
2 {
3 return a+b;
4 }
5
6 int _tmain(int argc, _TCHAR* argv[])
7 {
8 int a(1), b(2),c(3);
9 c = add(a,b,c);
10 return0;
11 }
下面再来看下汇编代码
1 004113FE mov dword ptr [a],1
2 00411405 mov dword ptr [b],2
3 0041140C mov dword ptr [c],3
4 // c = add(a,b,c);
5 00411413 mov eax, dword ptr [c]
6 00411416 push eax //第三个参数,入栈
7 00411417 mov edx,dword ptr [b] //第二个参数,放入edx中
8 0041141A mov ecx,dword ptr [a] //第一个参数,放入ecx中
9 0041141D call add (4111D1h)
10 00411422 mov dword ptr [c],eax
如上,我们可以比较出参数调用的不同。其调用时第一个参数是放在ecx中,第二个参数是放入edx中。
3. 函数的返回值
函数的返回方式有两种:
1. Return 直接返回
2. 以引用方式返回
第一种方式,函数的返回值是放在eax中的,如果返回值的长度大于32,其高位会放在eax中。这个比较简单,这里不着重讨论。
我们这里着重看的是以引用方式返回, 我们敲下如下代码:
1 void swap(int& a, int& b)
2 {
3 int tmp = a;
4 a = b;
5 b = tmp;
6 }
7
8 int _tmain(int argc, _TCHAR* argv[])
9 {
10 int a(1), b(2);
11 swap(a,b);
12 return0;
13 }
然后我们再来看其汇编对于add函数的实现:
1 0041140C lea eax,[b] //压地址入栈
2 0041140F push eax
3 00411410 lea ecx,[a]
4 00411413 push ecx
5 00411414 call swap (4111D6h)
6 00411419 add esp,8
7
8 //int tmp = a;
9 004113BE mov eax,dword ptr [a]
10 004113C1 mov ecx,dword ptr [eax]
11 004113C3 mov dword ptr [tmp],ecx
12 //a = b;
13 004113C6 mov eax,dword ptr [a]
14 004113C9 mov ecx,dword ptr [b]
15 004113CC mov edx,dword ptr [ecx]
16 004113CE mov dword ptr [eax],edx //指针的地址改变了
17 //b = tmp;
18 004113D0 mov eax,dword ptr [b]
19 004113D3 mov ecx,dword ptr [tmp]
20 004113D6 mov dword ptr [eax],ecx
21
如上, 以16 行为例, 引用来作为函数的参数只是用地址来代替普通的值, 其他和一般值参数没有区别。
利用汇编查看C++函数调用的更多相关文章
- opencv学习_15 (利用cmake查看opencv的源码)
当我们有时想查看opencv自带的函数的源代码,比如函数cvCreateImage, 此时我们选中cvCreateImage, 点击鼠标右键->转到定义,我们会很惊讶的发现为什么只看到了cvCr ...
- c/c++ linux epoll系列2 利用epoll_wait查看是否可以送信
linux epoll系列2 利用epoll_wait查看是否可以送信 write函数本来是非阻塞函数,但是当缓存区被写满后,再往缓存区里写的时候,就必须等待缓存区再次变成可写,所以这是write就变 ...
- 利用cmake查看opencv的源码
当我们有时想查看opencv自带的函数的源代码,比如函数cvCreateImage, 此时我们选中cvCreateImage, 点击鼠标右键->转到定义,我们会很惊讶的发现为什么只看到了cvCr ...
- 利用CMD查看系统硬件信息
利用CMD查看系统硬件信息对于在windows下查看系统信息大家一定不陌生了,我现在说几个最常用的方法,对命令感兴趣的朋友看看,(给菜鸟看的,老手就不要笑话我了,大家都是从那个时候过来的,^_^).一 ...
- R语言建立回归分析,并利用VIF查看共线性问题的例子
R语言建立回归分析,并利用VIF查看共线性问题的例子 使用R对内置longley数据集进行回归分析,如果以GNP.deflator作为因变量y,问这个数据集是否存在多重共线性问题?应该选择哪些变量参与 ...
- 利用vim查看日志,快速定位问题《转载》
利用vim查看日志,快速定位问题 链接:https://www.cnblogs.com/abcwt112/p/5192944.html
- 利用adb查看手机设备ip和连接手机的两种方式
电脑安装adb(查看菜鸟adb教程) [cmd]->输入adb devices (设置了path,否则需要 ./路径/adb devices)如图: 查看ip两种方法(可能有更多,目前我还没看到 ...
- 利用vim查看日志,快速定位问题
起因 在一般的情况下,如果开发过程中测试报告了一个问题,我一般会这么做: 1.在自己的开发环境下重试一下测试的操作,看看能不能重现问题.不行转2 2.数据库连接池改成测试库的地址,在自己的开发环境下重 ...
- linux利用grep查看打印匹配的下几行或前后几行的命令
转自:http://www.itokit.com/2013/0308/74883.html linux系统中,利用grep打印匹配的上下几行 如果在只是想匹配模式的上下几行,grep可以实现. ...
随机推荐
- 图数据库之Pregel
/* 版权声明:能够随意转载,转载时请务必标明文章原始出处和作者信息 .*/ author: 张俊林 节选自<大数据日知录:架构与算法>十四章.书籍文件夹在此 Pre ...
- read op case $op in
read op case $op in
- jQuery事件绑定方法bind、 live、delegate和on的区别
我们试图绑定一些事件到DOM元素上的时候,我相信上面这4个方法是最常用的.而它们之间到底有什么不同呢?在什么场合下用什么方法是最有效的呢? 1.准备知识 当我们在开始的时候,有些知识是必须具备的: 1 ...
- jQuery.innerWidth() 函数详解
innerWidth()函数用于设置或返回当前匹配元素的内宽度. 内宽度包括元素的内边距(padding),但不包括外边距(margin).边框(border)等部分的高度.如下图: 如果你要获取 ...
- sql从某不连续的数字中将其分段并找出缺失的数字并分段
首先做准备数据 )) ') ') ') ') ') ') ') ') ') ') ') ') ') ') ') ') 将数据转换成应该处理的数据格式 ),colValue INT ) ) ,LEN(c ...
- C#中Json字符串的各种应用类
在程序中任何事物及信息都可以用对象进行描述和承载,除了比较流行的xml之外,还有一种简洁快速处理目标信息的形式那就是Json格式. 首先Json格式有自己固定的格式:例如person这个对象就可以简单 ...
- 远程控制利器TeamViewer使用教程(图)
TeamViewer是什么? 他是一款免费的可以穿透内网的远程控制软件,可以实现桌面共享,文件传送等功能,简单一点说就是和QQ远程协助一样,但是比QQ的远程协助功能更为强大. TeamViewer与木 ...
- 使用 Chrome 来调试你的 Android App
http://www.stormzhang.com/android/2015/03/05/android-debug-use-chrome/ 个人一直对Chrome情有独钟,Chrome除了更快之外, ...
- 设置高级的Logstash 管道
设置高级的Logstash 管道: 一个Logstash 管道在很多实用例子有一个或者多个输入,filter,和output 插件. 本节中 创建Logstash 配置文件来指定那些插件和讨论每个插件 ...
- 8 个优秀的 Linux 图形图像及色彩工具
8 个优秀的 Linux 图形图像及色彩工具 1. 硬件色彩分析器LPROF LPROF 是一个用于创建设备兼容,如相机.扫描仪.显示器的ICC兼容型材的颜色分析器.这些配置提供跨设备的色彩一致性.他 ...