1414#include < bit>
1515#include < type_traits>
1616
17- #define DYNLIB_INVALID_VMETHOD_INDEX -1
17+ #define DYNLIB_INVALID_VCALL -1
1818
1919namespace DynLibUtils {
2020
21- template <auto METHOD , class T >
22- inline std::ptrdiff_t GetVirtualMethodIndex (T *pClass ) noexcept
21+ template <auto METHOD >
22+ constexpr std::ptrdiff_t GetVirtualMethodIndex () noexcept
2323{
2424 static_assert (std::is_member_function_pointer_v<decltype (METHOD )>, " Templated method must be a pointer-to-member-function" );
2525
26- #if defined(_MSC_VER)
27- // --- MSVC ABI: runtime scan vtable (no constexpr!) ---
28- struct MSVCPMF { void * ptr; std::ptrdiff_t adj; };
29- union { decltype (METHOD ) m; MSVCPMF pmf; } u{ METHOD };
30- void * target = u.pmf .ptr ;
31-
32- void ** vtbl = *reinterpret_cast <void ***>(pClass);
33-
34- constexpr std::size_t header = 2 ; // [RTTI][offset-to-top]
35-
36- for (std::size_t n = header; ; ++n)
37- if (vtbl[n] == target)
38- return n - header;
39-
40- #elif defined(__GNUG__) || defined(__clang__)
4126 // --- Itanium C++ ABI: PMF.ptr = 1 + offset_in_bytes for virtual ones ---
4227 struct ItaniumPMF { void * ptr; std::ptrdiff_t adj; };
4328 union { decltype (METHOD ) m; ItaniumPMF pmf; } u{ METHOD };
4429
30+ #if defined(_MSC_VER)
31+ auto *pAddr = reinterpret_cast <unsigned char *>(u.pmf .ptr );
32+
33+ // Skip JMP-thunk
34+ if (*pAddr == 0xE9 )
35+ {
36+ // May or may not be!
37+ // Check where it'd jump
38+ pAddr += 5 /* size of the instruction*/ + *(unsigned long *)(pAddr + 1 );
39+ }
40+
41+ // Check whether it's a virtual function call
42+ // They look like this:
43+ // 004125A0 8B 01 mov eax,dword ptr [ecx]
44+ // 004125A2 FF 60 04 jmp dword ptr [eax+4]
45+ // ==OR==
46+ // 00411B80 8B 01 mov eax,dword ptr [ecx]
47+ // 00411B82 FF A0 18 03 00 00 jmp dword ptr [eax+318h]
48+
49+ // However, for vararg functions, they look like this:
50+ // 0048F0B0 8B 44 24 04 mov eax,dword ptr [esp+4]
51+ // 0048F0B4 8B 00 mov eax,dword ptr [eax]
52+ // 0048F0B6 FF 60 08 jmp dword ptr [eax+8]
53+ // ==OR==
54+ // 0048F0B0 8B 44 24 04 mov eax,dword ptr [esp+4]
55+ // 0048F0B4 8B 00 mov eax,dword ptr [eax]
56+ // 00411B82 FF A0 18 03 00 00 jmp dword ptr [eax+318h]
57+
58+ // With varargs, the this pointer is passed as if it was the first argument
59+
60+ bool ok = false ;
61+
62+ if (pAddr[0 ] == 0x8B && pAddr[1 ] == 0x44 && pAddr[2 ] == 0x24 && pAddr[3 ] == 0x04 &&
63+ pAddr[4 ] == 0x8B && pAddr[5 ] == 0x00 )
64+ {
65+ pAddr += 6 ; ok = true ;
66+ }
67+ else if (pAddr[0 ] == 0x8B && pAddr[1 ] == 0x01 )
68+ {
69+ pAddr += 2 ; ok = true ;
70+ }
71+ else if (pAddr[0 ] == 0x48 && pAddr[1 ] == 0x8B && pAddr[2 ] == 0x01 )
72+ {
73+ pAddr += 3 ; ok = true ;
74+ }
75+
76+ if (!ok)
77+ return DYNLIB_INVALID_VCALL ;
78+
79+ // FF /60 /A0 /20
80+ if (*pAddr++ == 0xFF )
81+ {
82+ if (*pAddr == 0x60 )
83+ {
84+ return static_cast <std::ptrdiff_t >(*++pAddr) / sizeof (void *);
85+ }
86+ else if (*pAddr == 0xA0 )
87+ {
88+ return static_cast <std::ptrdiff_t >(
89+ *reinterpret_cast <unsigned int *>(++pAddr)
90+ ) / sizeof (void *);
91+ }
92+ else if (*pAddr == 0x20 )
93+ {
94+ return 0 ;
95+ }
96+ else
97+ {
98+ return DYNLIB_INVALID_VCALL ;
99+ }
100+ }
101+
102+ #elif defined(__GNUG__) || defined(__clang__)
45103 constexpr auto raw = reinterpret_cast <std::uintptr_t >(u.pmf .ptr );
46104
47105 static_assert ((raw & 1u ) != 0 , " Not a virtual member function" );
@@ -51,7 +109,7 @@ inline std::ptrdiff_t GetVirtualMethodIndex(T *pClass) noexcept
51109 static_assert (false , " Unsupported compiler" );
52110#endif
53111
54- return DYNLIB_INVALID_VMETHOD_INDEX ;
112+ return DYNLIB_INVALID_VCALL ;
55113}
56114
57115class CVirtualTable : public CMemoryView <void *>
0 commit comments