18 {19     callFunc((funcPointer)testFunc);20     getchar();21 }

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

在第12行代码定义的callFunc函数,它的参数是一个“返回值为void,参数为一个int型的函数指针”,并在内部调用这个函数指针传实参为1。

在地5行代码定义了函数testFunc,它的参数为两个int,同时为它定义了__stdcall的调用约定。

在main函数的19行中进行调用的时候,对testFunc使用了(funcPointer)进行强行类型转换,并将它传入callFunc作为实参进行调用。

 

x86平台Debug版本运行这段程序的结果如下:

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

程序因为异常停在第19行了。

 

X86平台Release版本运行这段程序的结果如下:

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

程序虽然也执行到结尾了,但是由于传参不正确,所以结果不对,而且也无法正常停机。

 

X64平台Debug版本和Release版本运行这段程序的结果类似如下:

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

程序没有发生异常,顺利执行完毕,只是运行结果不正确。

 

为什么是这样一个结果呢?下面,本文就来细细讲解。

在计算机中有两个寄存器称为ebpesp,它们分别称为基址指针寄存器和堆栈指针寄存器。

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类型的所以它仍然会再进行退栈的操作)如下图所示。

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

 此处,截取了实际程序的反汇编代码进行分析:

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

上图是testFunc的反汇编,为了使反汇编看起来没那么冗长作者将其中一些代码注释掉了。可以看到,最后在结束时进行了ret 8的操作,即向上退8(两个int的大小)。(此处可以看到stdcall声明的函数进行自行参数退栈的实现)

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

上图是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版反汇编代码如下:

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

可以从反汇编的代码中看到,在进入子函数之前先将esp的值保存在esi中,当执行完毕之后对比esi和现在的esp的值,即RTC。

②在X86版本下,在退栈时是以esp中的值为基址进行加减操作来进行的。而RTC又是对esp指针进行检查,因此此时会报出异常。

而在X64版本下,在退栈时是以ebp中的值为基址进行加减操作来进行的,RTC检查的是esp,毫不相关,所以不会抱任何异常。

 

诚然,这只是一个小“缺陷”,很多人认为不必在意。但是小小的问题也会在某一刻产生巨大的隐患,造成整个软件的崩溃。

http://www.cnblogs.com/saintlas/p/7093561.html

网友评论