从栈不平衡问题 理解 calling convention
最近在开发的过程中遇到了几个很诡异的问题,造成了栈不平衡从而导致程序崩溃。
经过几经排查发现是和调用规约(calling convention)相关的问题,特此分享出来。
首先,讲一下什么是调用规约。
函数调用规约,是指当一个函数被调用时,函数的参数会被传递给被调用的函数和返回值会被返回给调用函数。函数的调用规约就是描述参数是怎么传递和由谁平衡堆栈的,当然还有返回值。
名称 | 谁负责参数出栈 | 参数压栈顺序 |
Cdecl | Caller(调用者) | 从右往左 |
Pascal | Callee(被调用者) | 从左往右 |
Stdcall | Callee(被调用者) | 从右往左 |
Fastcall | Callee(被调用者) | 从右往左 |
Thiscall | Callee(被调用者) | 从右往左 |
下面,本文给出一个简短的代码示例:
#include<iostream> typedef void(*funcPointer)(int); void __stdcall testFunc(int i, int j)
{
std::cout << "i is:" << i << std::endl;
std::cout << "j is:" << j << std::endl;
return ;
} void callFunc(void(*func)(int))
{
func();
} void main()
{
callFunc((funcPointer)testFunc);
getchar();
}
在第12行代码定义的callFunc函数,它的参数是一个“返回值为void,参数为一个int型的函数指针”,并在内部调用这个函数指针传实参为1。
在地5行代码定义了函数testFunc,它的参数为两个int,同时为它定义了__stdcall的调用约定。
在main函数的19行中进行调用的时候,对testFunc使用了(funcPointer)进行强行类型转换,并将它传入callFunc作为实参进行调用。
在x86平台Debug版本运行这段程序的结果如下:
程序因为异常停在第19行了。
在X86平台Release版本运行这段程序的结果如下:
程序虽然也执行到结尾了,但是由于传参不正确,所以结果不对,而且也无法正常停机。
在X64平台Debug版本和Release版本运行这段程序的结果类似如下:
程序没有发生异常,顺利执行完毕,只是运行结果不正确。
为什么是这样一个结果呢?下面,本文就来细细讲解。
在计算机中有两个寄存器称为ebp和esp,它们分别称为基址指针寄存器和堆栈指针寄存器。
esp和ebp分别指向当前运行函数的栈顶和栈底。
由于callFunc是以cdecl的方式进行声明的,而testFunc是以stdcall的方式进行声明的。因此在callFunc调用testFunc时,调用者(caller)负责将参数push进栈。因为此时testFunc已经进行了强行类型转换,因此编译器认为它的输入参数即为1个int,所以在入栈时callFunc将1个int压入堆栈中,接着调用testFunc。当testFunc执行完毕之后,由于它是stdcall所以由被调用者(callee)即testFunc自身负责参数的pop退栈。而此时,由于testFunc函数本身只有2个int型参数,所以在出栈时即pop两个int,导致了栈不平衡问题的产生!(而且在执行完testFunc之后,由于callFunc是cdecl类型的所以它仍然会再进行退栈的操作)如下图所示。
此处,截取了实际程序的反汇编代码进行分析:
上图是testFunc的反汇编,为了使反汇编看起来没那么冗长作者将其中一些代码注释掉了。可以看到,最后在结束时进行了ret 8的操作,即向上退8(两个int的大小)。(此处可以看到stdcall声明的函数进行自行参数退栈的实现)
上图是callFunc的反汇编,可以看到在调用子函数结束之后它进行了esp+4的操作,即退栈1个int(因为栈的地址空间是从大向小增长的所以是加操作)。
而且最后在它ret时是没有跟参数的,代表cdecl的函数不进行自我参数退栈操作。
关于Debug和Release,X86和X64结果不一样的原因
①在Debug版本下,Visual Studio的编译器会自动在编译参数中加入/RTC,即Runtime Check。启用运行时错误检查。其中包括了:堆栈指针验证,该操作检测堆栈指针损坏。 调用约定不匹配可能导致堆栈指针损坏。 例如,使用函数指针调用 DLL 中作为 __stdcall 导出的函数,但将指向该函数的指针声明为 __cdecl。此时编译器会在每个函数的开始和结束处加入针对esp指针的检查。详见:MSDN_CL_编译参数_RTC。
因此在Debug版本下会报出上文所提到的异常。而在Release版本下,因为默认不进行太多检查即RTC被关闭,因此并不会出现弹出异常提示的情况。
Debug版反汇编代码如下:
可以从反汇编的代码中看到,在进入子函数之前先将esp的值保存在esi中,当执行完毕之后对比esi和现在的esp的值,即RTC。
②在X86版本下,在退栈时是以esp中的值为基址进行加减操作来进行的。而RTC又是对esp指针进行检查,因此此时会报出异常。
而在X64版本下,在退栈时是以ebp中的值为基址进行加减操作来进行的,RTC检查的是esp,毫不相关,所以不会抱任何异常。
诚然,这只是一个小“缺陷”,很多人认为不必在意。但是小小的问题也会在某一刻产生巨大的隐患,造成整个软件的崩溃。
从栈不平衡问题 理解 calling convention的更多相关文章
- X86调用约定 calling convention
http://zh.wikipedia.org/wiki/X86%E8%B0%83%E7%94%A8%E7%BA%A6%E5%AE%9A 这里描述了在x86芯片架构上的调用约定(calling con ...
- Calling Convention的总结
因为经常需要和不同的Calling Convention打交道,前段时间整理了一下它们之间的区别,如下: 清理堆栈 参数压栈顺序 命名规则 (MSVC++) 备注 Cdecl 调用者 (Caller) ...
- C&C++ Calling Convention
tkorays(tkorays@hotmail.com) 调用约定(Calling Convention) 是计算机编程中一个比较底层的设计,它主要涉及: 函数参数通过寄存器传递还是栈? 函数参数从左 ...
- C/C++:函数的调用约定(Calling Convention)和名称修饰(Decorated Name)以及两者不匹配引起的问题
转自:http://blog.csdn.net/zskof/article/details/3475182 注:C++有着与C不同的名称修饰,主要是为了解决重载(overload):调用约定则影响函数 ...
- Javascript的堆和栈的简单理解
<!doctype html> <html> <head> <meta charset="UTF-8"> <title> ...
- function calling convention
这是2013年写的一篇旧文,放在gegahost.net上面 http://raison.gegahost.net/?p=31 February 19, 2013 function calling c ...
- C#堆和栈的入门理解
声明:以下内容从网络整理,非原创,适当待入个人理解. 解释1.栈是编译期间就分配好的内存空间,因此你的代码中必须就栈的大小有明确的定义:堆是程序运行期间动态分配的内存空间,你可以根据程序的运行情况确定 ...
- 汇编中call printf参数压栈时错误理解
EAX, ECX,EDX,EBX均可以32bit,16bit,8bit访问,如下所示: <-------------------EAX------------------------>|& ...
- iOS:堆(heap)和栈(stack)的理解
Objective-C的对象在内存中是以堆的方式分配空间的,并且堆内存是由你释放的,即release 栈由编译器管理自动释放的,在方法中(函数体)定义的变量通常是在栈内,因此如果你的变量要跨函数的话就 ...
随机推荐
- php高性能开发阅读笔记
1.http请求与响应的简单流程 上图简单的描述了一个http请求与响应的过程,首先是用户请求过程,这是该生命周期的第一部分,用户发起请求,经过路由器与ips网关和dns服务器(域名服务器),通过we ...
- VS2013各版本激活密钥
Visual Studio Ultimate 2013 KEY(密钥):BWG7X-J98B3-W34RT-33B3R-JVYW9 Visual Studio Premium 2013 KEY(密钥) ...
- Neo4j 第二篇:图形数据库
在深入学习图形数据库之前,首先理解属性图的基本概念.一个属性图是由顶点(Vertex),边(Edge),标签(Lable),关系类型和属性(Property)组成的有向图.顶点也称作节点(Node), ...
- Swoole笔记(一)
简介 Swoole是一个PHP扩展,提供了PHP语言的异步多线程服务器,异步TCP/UDP网络客户端,异步MySQL,异步Redis,数据库连接池,AsyncTask,消息队列,毫秒定时器,异步文件读 ...
- VR全景智慧城市,完美的将虚拟与现实结合
很多人都粗浅的认为,VR虚拟智慧城市只是简单的将智慧城市和虚拟现实相结合的产物,这样的VR虚拟智慧城市看起来更像是个VR内容产品,而非城市建设成果.但是我们换个角度来思考的话,现在很多VR虚拟智慧城市 ...
- 在linux中导入sql文件的方法分享(使用命令行转移mysql数据库)
因导出sql文件 在你原来的网站服务商处利用phpmyadmin导出数据库为sql文件,这个步骤大家都会,不赘述. 上传sql文件 前面说过了,我们没有在云主机上安装ftp,怎么上传呢? 打开ftp客 ...
- 关于 vue-cli v2.8.2
我在撰写<Vue2实践揭秘>时采用的 vue-cli 版本是 v2.5.1,由于实体书的出版周期比电子书的要长,所以到全书出版vue-cli已经更新到 v2.8.2 了,我在书中曾经对 v ...
- POI使用:用poi接口不区分xls/xlsx格式解析Excel文档(41种日期格式解析方法,5种公式结果类型解析方法,3种常用数值类型精度控制办法)
一.使用poi解析excel文档 注:全部采用poi接口进行解析,不需要区分xls.xlsx格式,不需要判断文档类型. poi中的日期格式判断仅支持欧美日期习惯,对国内的日期格式并不支持判断,怎么办? ...
- 每天一个JS 小demo之商品筛选。主要知识点:DOM方法综合运用
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"& ...
- PHPStorm中的快捷键
1.Ctrl+Alt+L 格式化代码 2.windows下按下快捷`Ctrl`+`Shift`+`-`,这样就能折叠所有代码了. 3.windows下按下快捷`Ctrl`+`Shift`+`+`,这样 ...