Skip to content

Commit 36538e0

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

1 file changed

Lines changed: 86 additions & 25 deletions

File tree

include/dynlibutils/virtual.hpp

Lines changed: 86 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,44 +14,105 @@
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 ---
42-
struct ItaniumPMF { void* ptr; std::ptrdiff_t adj; };
43-
union { decltype(METHOD) m; ItaniumPMF pmf; } u{ METHOD };
44-
45-
constexpr auto raw = reinterpret_cast<std::uintptr_t>(u.pmf.ptr);
27+
struct ItaniumPMF
28+
{
29+
union
30+
{
31+
std::ptrdiff_t addr;
32+
void* ptr;
33+
};
34+
35+
std::ptrdiff_t adj;
36+
};
37+
constexpr union { decltype(METHOD) m; ItaniumPMF pmf; } u{ METHOD };
4638

47-
static_assert((raw & 1u) != 0, "Not a virtual member function");
39+
#if defined(_MSC_VER)
40+
auto *pAddr = reinterpret_cast<unsigned char *>(u.pmf.ptr);
41+
42+
// Skip JMP-thunk
43+
if (*pAddr == 0xE9)
44+
{
45+
// May or may not be!
46+
// Check where it'd jump
47+
pAddr += 5 /*size of the instruction*/ + *(unsigned long*)(pAddr + 1);
48+
}
49+
50+
// Check whether it's a virtual function call
51+
// They look like this:
52+
// 004125A0 8B 01 mov eax,dword ptr [ecx]
53+
// 004125A2 FF 60 04 jmp dword ptr [eax+4]
54+
// ==OR==
55+
// 00411B80 8B 01 mov eax,dword ptr [ecx]
56+
// 00411B82 FF A0 18 03 00 00 jmp dword ptr [eax+318h]
57+
58+
// However, for vararg functions, they look like this:
59+
// 0048F0B0 8B 44 24 04 mov eax,dword ptr [esp+4]
60+
// 0048F0B4 8B 00 mov eax,dword ptr [eax]
61+
// 0048F0B6 FF 60 08 jmp dword ptr [eax+8]
62+
// ==OR==
63+
// 0048F0B0 8B 44 24 04 mov eax,dword ptr [esp+4]
64+
// 0048F0B4 8B 00 mov eax,dword ptr [eax]
65+
// 00411B82 FF A0 18 03 00 00 jmp dword ptr [eax+318h]
66+
67+
// With varargs, the this pointer is passed as if it was the first argument
68+
69+
bool ok = false;
70+
71+
if (pAddr[0] == 0x8B && pAddr[1] == 0x44 && pAddr[2] == 0x24 && pAddr[3] == 0x04 &&
72+
pAddr[4] == 0x8B && pAddr[5] == 0x00)
73+
{
74+
pAddr += 6; ok = true;
75+
}
76+
else if (pAddr[0] == 0x8B && pAddr[1] == 0x01)
77+
{
78+
pAddr += 2; ok = true;
79+
}
80+
else if (pAddr[0] == 0x48 && pAddr[1] == 0x8B && pAddr[2] == 0x01)
81+
{
82+
pAddr += 3; ok = true;
83+
}
84+
85+
if (!ok)
86+
return DYNLIB_INVALID_VCALL;
87+
88+
// FF /60 /A0 /20
89+
if (*pAddr++ == 0xFF)
90+
{
91+
if (*pAddr == 0x60)
92+
{
93+
return static_cast<std::ptrdiff_t>(*++pAddr) / sizeof(void *);
94+
}
95+
else if (*pAddr == 0xA0)
96+
{
97+
return static_cast<std::ptrdiff_t>(*reinterpret_cast<unsigned int*>(++pAddr)) / sizeof(void *);
98+
}
99+
else if (*pAddr == 0x20)
100+
{
101+
return 0;
102+
}
103+
else
104+
{
105+
return DYNLIB_INVALID_VCALL;
106+
}
107+
}
48108

49-
return (raw - 1u) / sizeof(void*);
109+
#elif defined(__GNUG__) || defined(__clang__)
110+
return (u.pmf.addr - 1u) / sizeof(void*);
50111
#else
51112
static_assert(false, "Unsupported compiler");
52113
#endif
53114

54-
return DYNLIB_INVALID_VMETHOD_INDEX;
115+
return DYNLIB_INVALID_VCALL;
55116
}
56117

57118
class CVirtualTable : public CMemoryView<void *>

0 commit comments

Comments
 (0)