summaryrefslogtreecommitdiffstatshomepage
path: root/src/lib/util/mfpresolve.cpp
diff options
context:
space:
mode:
author Vas Crabb <vas@vastheman.com>2025-02-06 02:42:26 +1100
committer Vas Crabb <vas@vastheman.com>2025-02-06 02:42:26 +1100
commitcea7758f4f5b4c26dde9646caeb0785607c817a5 (patch)
treef130536d3dd01c3d1edf146ef906b6d140920b21 /src/lib/util/mfpresolve.cpp
parent4edf86fed7aa7ca5c94ae561f7c8449121a038a5 (diff)
-cpu/drcbex64.cpp: Fixed handling of address map global mask.
* Fixed Final Furlong failing to boot. -util/mfpresolve.cpp, emu/emumem_aspace.cpp: Moved some member function pointer manipulation stuff into the library. Gives another slight reduction in the size of libemu.
Diffstat (limited to 'src/lib/util/mfpresolve.cpp')
-rw-r--r--src/lib/util/mfpresolve.cpp201
1 files changed, 187 insertions, 14 deletions
diff --git a/src/lib/util/mfpresolve.cpp b/src/lib/util/mfpresolve.cpp
index 4542a29229d..c7b9c58fde2 100644
--- a/src/lib/util/mfpresolve.cpp
+++ b/src/lib/util/mfpresolve.cpp
@@ -62,36 +62,47 @@ std::pair<std::uintptr_t, std::uintptr_t> resolve_member_function_itanium(
}
+std::tuple<std::uintptr_t, std::ptrdiff_t, bool> resolve_member_function_itanium(
+ std::uintptr_t function,
+ std::ptrdiff_t delta) noexcept
+{
+ constexpr uintptr_t funcmask = ~uintptr_t((MAME_ABI_CXX_ITANIUM_MFP_TYPE == MAME_ABI_CXX_ITANIUM_MFP_ARM) ? 0 : 1);
+ constexpr int deltashift = (MAME_ABI_CXX_ITANIUM_MFP_TYPE == MAME_ABI_CXX_ITANIUM_MFP_ARM) ? 1 : 0;
+ return std::make_tuple(
+ function & funcmask,
+ delta >> deltashift,
+ (MAME_ABI_CXX_ITANIUM_MFP_TYPE == MAME_ABI_CXX_ITANIUM_MFP_ARM) ? (delta & 1) : (function & 1));
+}
+
+
std::pair<std::uintptr_t, std::uintptr_t> resolve_member_function_msvc(
void const *funcptr,
std::size_t size,
void const *object) noexcept
{
- struct single { std::uintptr_t entrypoint; };
- struct multi { std::uintptr_t entrypoint; int this_delta; };
- struct { std::uintptr_t entrypoint; int this_delta; int vptr_offs; int vt_index; } const *unknown;
+ mfp_msvc_unknown_equiv const *unknown;
assert(sizeof(*unknown) >= size);
- unknown = reinterpret_cast<decltype(unknown)>(funcptr);
+ unknown = reinterpret_cast<mfp_msvc_unknown_equiv const *>(funcptr);
LOG("Input this=%p ", object);
- if (sizeof(single) < size)
- LOG("thisdelta=%d ", unknown->this_delta);
- if (sizeof(*unknown) == size)
- LOG("vptrdelta=%d vindex=%d ", unknown->vptr_offs, unknown->vt_index);
+ if (sizeof(mfp_msvc_single_equiv) < size)
+ LOG("thisdelta=%d ", unknown->delta);
+ if (sizeof(mfp_msvc_unknown_equiv) == size)
+ LOG("vptrdelta=%d vindex=%d ", unknown->voffset, unknown->vindex);
auto byteptr = reinterpret_cast<std::uint8_t const *>(object);
// test for pointer to member function cast across virtual inheritance relationship
- if ((sizeof(*unknown) == size) && unknown->vt_index)
+ if ((sizeof(mfp_msvc_unknown_equiv) == size) && unknown->vindex)
{
// add offset from "this" pointer to location of vptr, and add offset to virtual base from vtable
- byteptr += unknown->vptr_offs;
- std::uint8_t const *const vptr = *reinterpret_cast<std::uint8_t const *const *>(byteptr);
- byteptr += *reinterpret_cast<int const *>(vptr + unknown->vt_index);
+ byteptr += unknown->voffset;
+ auto const vptr = *reinterpret_cast<std::uint8_t const *const *>(byteptr);
+ byteptr += *reinterpret_cast<int const *>(vptr + unknown->vindex);
}
// add "this" pointer displacement if present in the pointer to member function
- if (sizeof(single) < size)
- byteptr += unknown->this_delta;
+ if (sizeof(mfp_msvc_single_equiv) < size)
+ byteptr += unknown->delta;
LOG("Calculated this=%p\n", reinterpret_cast<void const *>(byteptr));
// walk past recognisable thunks
@@ -99,6 +110,26 @@ std::pair<std::uintptr_t, std::uintptr_t> resolve_member_function_msvc(
}
+std::tuple<std::uintptr_t, std::ptrdiff_t, bool> resolve_member_function_msvc(
+ void const *funcptr,
+ std::size_t size) noexcept
+{
+ mfp_msvc_unknown_equiv const *unknown;
+ assert(sizeof(*unknown) >= size);
+ unknown = reinterpret_cast<mfp_msvc_unknown_equiv const *>(funcptr);
+
+ // no way to represent pointer to member function cast across virtual inheritance relationship
+ if ((sizeof(mfp_msvc_unknown_equiv) == size) && unknown->vindex)
+ return std::make_tuple(std::uintptr_t(static_cast<void (*)()>(nullptr)), std::ptrdiff_t(0), false);
+
+ auto const [function, is_virtual] = bypass_member_function_thunks(unknown->entrypoint);
+ return std::make_tuple(
+ function,
+ (sizeof(mfp_msvc_single_equiv) < size) ? unknown->delta : 0,
+ is_virtual);
+}
+
+
std::uintptr_t bypass_member_function_thunks(
std::uintptr_t entrypoint,
void const *object) noexcept
@@ -255,4 +286,146 @@ std::uintptr_t bypass_member_function_thunks(
#endif
}
+
+std::pair<std::uintptr_t, bool> bypass_member_function_thunks(
+ std::uintptr_t entrypoint) noexcept
+{
+#if defined(__x86_64__) || defined(_M_X64)
+ std::uint8_t const *func = reinterpret_cast<std::uint8_t const *>(entrypoint);
+ while (true)
+ {
+ // Assumes Windows calling convention, and doesn't consider that
+ // the "this" pointer could be in RDX if RCX is a pointer to
+ // space for an oversize scalar result. Since the result area
+ // is uninitialised on entry, you won't see something that looks
+ // like a vtable dispatch through RCX in this case - it won't
+ // behave badly, it just won't bypass virtual call thunks in the
+ // rare situations where the return type is an oversize scalar.
+ if (0xe9 == func[0])
+ {
+ // relative jump with 32-bit displacement (typically a resolved PLT entry)
+ LOG("Found relative jump at %p ", func);
+ func += std::ptrdiff_t(5) + *reinterpret_cast<std::int32_t const *>(func + 1);
+ LOG("redirecting to %p\n", func);
+ continue;
+ }
+ else if ((0x48 == func[0]) && (0x8b == func[1]) && (0x01 == func[2]))
+ {
+ if ((0xff == func[3]) && ((0x20 == func[4]) || (0x60 == func[4]) || (0xa0 == func[4])))
+ {
+ // MSVC virtual function call thunk - mov rax,QWORD PTR [rcx] ; jmp QWORD PTR [rax+...]
+ LOG("Found virtual member function thunk at %p\n", func);
+ if (0x20 == func[4]) // no displacement
+ return std::make_pair(std::uintptr_t(0), true);
+ else if (0x60 == func[4]) // 8-bit displacement
+ return std::make_pair(std::uintptr_t(*reinterpret_cast<std::int8_t const *>(func + 5)), true);
+ else // 32-bit displacement
+ return std::make_pair(std::uintptr_t(*reinterpret_cast<std::int32_t const *>(func + 5)), true);
+ }
+ else if ((0x48 == func[3]) && (0x8b == func[4]))
+ {
+ // clang virtual function call thunk - mov rax,QWORD PTR [rcx] ; mov rax,QWORD PTR [rax+...] ; jmp rax
+ if ((0x00 == func[5]) && (0x48 == func[6]) && (0xff == func[7]) && (0xe0 == func[8]))
+ {
+ // no displacement
+ LOG("Found virtual member function thunk at %p\n", func);
+ return std::make_pair(std::uintptr_t(0), true);
+ }
+ else if ((0x40 == func[5]) && (0x48 == func[7]) && (0xff == func[8]) && (0xe0 == func[9]))
+ {
+ // 8-bit displacement
+ LOG("Found virtual member function thunk at %p\n", func);
+ return std::make_pair(std::uintptr_t(*reinterpret_cast<std::int8_t const *>(func + 6)), true);
+ }
+ else if ((0x80 == func[5]) && (0x48 == func[10]) && (0xff == func[11]) && (0xe0 == func[12]))
+ {
+ // 32-bit displacement
+ LOG("Found virtual member function thunk at %p\n", func);
+ return std::make_pair(std::uintptr_t(*reinterpret_cast<std::int32_t const *>(func + 6)), true);
+ }
+ }
+ }
+
+ // clang uses unoptimised thunks if optimisation is disabled
+ // Without optimisation, clang produces thunks like:
+ // 50 push rax
+ // 48 89 0c 24 mov QWORD PTR [rsp],rcx
+ // 48 8b 0c 24 mov rcx,QWORD PTR [rsp]
+ // 48 8b 01 mov rax,QWORD PTR [rcx]
+ // 48 8b 80 xx xx xx xx mov rax,QWORD PTR [rax+...]
+ // 41 5a pop r10
+ // 48 ff e0 jmp rax
+ // Trying to decode these thunks likely isn't worth the effort.
+ // Chasing performance in unoptimised builds isn't very useful,
+ // and the format of these thunks may be fragile.
+
+ // not something we can easily bypass
+ break;
+ }
+ return std::make_pair(std::uintptr_t(func), false);
+#elif defined(__aarch64__) || defined(_M_ARM64)
+ std::uint32_t const *func = reinterpret_cast<std::uint32_t const *>(entrypoint);
+ auto const fetch = [&func] (auto offset) { return little_endianize_int32(func[offset]); };
+ while (true)
+ {
+ if ((0x90000010 == (fetch(0) & 0x9f00001f)) && (0x91000210 == (fetch(1) & 0xffc003ff)) && (0xd61f0200 == fetch(2)))
+ {
+ // page-relative jump with +/-4GB reach - adrp xip0,... ; add xip0,xip0,#... ; br xip0
+ LOG("Found page-relative jump at %p ", func);
+ std::int64_t const page =
+ (std::uint64_t(fetch(0) & 0x60000000) >> 17) |
+ (std::uint64_t(fetch(0) & 0x00ffffe0) << 9) |
+ ((fetch(0) & 0x00800000) ? (~std::uint64_t(0) << 33) : 0);
+ std::uint32_t const offset = (fetch(1) & 0x003ffc00) >> 10;
+ func = reinterpret_cast<std::uint32_t const *>(((std::uintptr_t(func) + page) & (~std::uintptr_t(0) << 12)) + offset);
+ LOG("redirecting to %p\n", func);
+ }
+ else if ((0xf9400010 == fetch(0)) && (0xf9400210 == (fetch(1) & 0xffc003ff)) && (0xd61f0200 == fetch(2)))
+ {
+ // virtual function call thunk - ldr xip0,[x0] ; ldr xip0,[x0,#...] ; br xip0
+ LOG("Found virtual member function thunk at %p\n", func);
+ return std::make_pair(std::uintptr_t((fetch(1) & 0x003ffc00) >> (10 - 3)), true);
+ }
+ else
+ {
+ // not something we can easily bypass
+ break;
+ }
+
+ // clang uses horribly sub-optimal thunks for AArch64
+ // Without optimisation, clang produces thunks like:
+ // d10143ff sub sp,sp,#80
+ // f90027e7 str x7,[sp,#72]
+ // f90023e6 str x6,[sp,#64]
+ // f9001fe5 str x5,[sp,#56]
+ // f9001be4 str x4,[sp,#48]
+ // f90017e3 str x3,[sp,#40]
+ // f90013e2 str x2,[sp,#32]
+ // f9000fe1 str x1,[sp,#24]
+ // f90007e0 str x0,[sp,#8]
+ // f94007e0 ldr x0,[sp,#8]
+ // f9400009 ldr x9,[x0]
+ // f9400129 ldr x9,[x9,#...]
+ // 910143ff add sp,sp,#80
+ // d61f0120 br x9
+ // With optimisation, clang produces thunks like:
+ // d10103ff sub sp,sp,#64
+ // a9008be1 stp x1,x2,[sp,#8]
+ // a90193e3 stp x3,x4,[sp,#24]
+ // a9029be5 stp x5,x6,[sp,#40]
+ // f9001fe7 str x7,[sp,#56]
+ // f9400009 ldr x9,[x0]
+ // f9400129 ldr x9,[x9,#...]
+ // 910103ff add sp,sp,#64
+ // d61f0120 br x9
+ // It's more effort than it's worth to try decoding these
+ // thunks.
+
+ }
+ return std::make_pair(std::uintptr_t(func), false);
+#else
+ return std::make_pair(entrypoint, false);
+#endif
+}
+
} // namespace util::detail