Skip to content

Commit ea61e14

Browse files
committed
Update GetVirtualMethodIndex method with AM way
1 parent 82dd241 commit ea61e14

1 file changed

Lines changed: 77 additions & 19 deletions

File tree

include/dynlibutils/virtual.hpp

Lines changed: 77 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,34 +14,92 @@
1414
#include <bit>
1515
#include <type_traits>
1616

17-
#define DYNLIB_INVALID_VMETHOD_INDEX -1
17+
#define DYNLIB_INVALID_VCALL -1
1818

1919
namespace 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

57115
class CVirtualTable : public CMemoryView<void *>

0 commit comments

Comments
 (0)