我一直在研究DynObj并决定用 vftables 做我自己的实验。我正在使用 Visual Studio 2010 并创建了一个控制台主程序,它使用一个返回 std::string 的虚拟函数来实例化一个对象。
main 中的测试代码尝试使用从对象的 vftable 获得的指针调用对象的公共虚函数。我发现对于返回原始类型的函数没有问题。但是当函数返回一个 std::string 时,编译器会插入一个有效的堆栈弹出(添加 esp,4)。这会导致随后的堆栈检查代码引发异常。
我注意到这是在全局空间中声明的函数的规范。但是类中的函数不会在调用后生成 ESP 修饰符。
这是代码的本质以及程序集......
class VClass
{
public:
VClass() {}
~VClass() {}
virtual std::string GetString() {return "vstring";}
};
std::string StrFunc()
{
return "string";
}
void main(int argc, char* argv[])
{
VClass vClass;
__int32 ** vftabletable;
// 00BE1730 lea ecx,[ebp-18h]
// 00BE1733 call VClass::VClass (0BE1181h)
// 00BE1738 mov dword ptr [ebp-4],0
vftabletable = (__int32**)&vClass;
// 00BE173F lea eax,[ebp-18h]
// 00BE1742 mov dword ptr [ebp-24h],eax
StrFunc();
// 00BE1745 lea eax,[ebp-15Ch]
// 00BE174B push eax
// 00BE174C call StrFunc (0BE11EFh)
// 00BE1751 add esp,4
// 00BE1754 lea ecx,[ebp-15Ch]
// 00BE175A call std::basic_string<char,std::char_traits<char>,std::allocator<char> >::~basic_string<char,std::char_traits<char>,std::allocator<char> > (0BE1221h)
vClass.GetString();
// 00BE175F lea eax,[ebp-134h]
// 00BE1765 push eax
// 00BE1766 lea ecx,[ebp-18h]
// 00BE1769 call VClass::GetString (0BE1195h)
// 00BE176E lea ecx,[ebp-134h]
// 00BE1774 call std::basic_string<char,std::char_traits<char>,std::allocator<char> >::~basic_string<char,std::char_traits<char>,std::allocator<char> > (0BE1221h)
((std::string (*)())vftabletable[0][0])();
// 00BE1779 mov esi,esp
// 00BE177B lea eax,[ebp-10Ch]
// 00BE1781 push eax
// 00BE1782 mov ecx,dword ptr [ebp-24h]
// 00BE1785 mov edx,dword ptr [ecx]
// 00BE1787 mov eax,dword ptr [edx]
// 00BE1789 call eax
// 00BE178B add esp,4
// 00BE178E cmp esi,esp
// 00BE1790 call @ILT+570(__RTC_CheckEsp) (0BE123Fh)
// 00BE1795 lea ecx,[ebp-10Ch]
// 00BE179B call std::basic_string<char,std::char_traits<char>,std::allocator<char> >::~basic_string<char,std::char_traits<char>,std::allocator<char> > (0BE1221h)
}
通过强制转换的指针对虚函数的最后一次调用导致:
运行时检查失败 #0 - ESP 的值未在函数调用中正确保存。这通常是调用使用一种调用约定声明的函数和使用另一种调用约定声明的函数指针的结果。
所以我的问题是:如何在返回非原始类型的虚函数上调用正确的调用约定?
顺便说一句,当我在 00BE178B 中断并将下一条语句设置为 00BE178E 时,执行完成没有问题。