私は 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 に設定すると、実行は問題なく完了します。