ReXGlue SDK
Loading...
Searching...
No Matches
hook.h
Go to the documentation of this file.
1
11
12#pragma once
13
14#include <atomic>
15#include <cstring>
16#include <format>
17#include <tuple>
18#include <type_traits>
19#include <cstddef>
20#include <string>
21#include <utility>
22
24#include <rex/platform.h>
26#include <rex/ppc/context.h>
28#include <rex/ppc/call.h>
29#include <rex/types.h>
30#include <rex/assert.h>
31#include <rex/ppc/image.h>
32#include <rex/utility.h>
33
44#ifdef REXGLUE_ENABLE_PROFILING
45#include <tracy/Tracy.hpp>
46#define REX_HOOK(subroutine, function) \
47 extern "C" REX_DETAIL_ENTRY_EXPORT REX_FUNC(subroutine) { \
48 ZoneNamedN(___tracy_hook_zone, #subroutine, TracyIsStarted); \
49 rex::ppc::GuestToHostThunk<function>(ctx, base); \
50 }
51#else
52#define REX_HOOK(subroutine, function) \
53 extern "C" REX_DETAIL_ENTRY_EXPORT REX_FUNC(subroutine) { \
54 rex::ppc::GuestToHostThunk<function>(ctx, base); \
55 }
56#endif
57
69#define REX_HOOK_RAW(name) extern "C" REX_DETAIL_ENTRY_EXPORT REX_FUNC(name)
70
80#define REX_STUB(subroutine) \
81 extern "C" REX_DETAIL_ENTRY_EXPORT REX_FUNC(subroutine) { \
82 static constinit ::std::atomic_flag rex_stub_seen_; \
83 ::rex::log::detail::WriteStub(::rex::log::cpu, rex_stub_seen_, #subroutine, {}); \
84 }
85
96#define REX_STUB_LOG(subroutine, msg) \
97 extern "C" REX_DETAIL_ENTRY_EXPORT REX_FUNC(subroutine) { \
98 static constinit ::std::atomic_flag rex_stub_seen_; \
99 ::rex::log::detail::WriteStub(::rex::log::cpu, rex_stub_seen_, #subroutine, msg); \
100 }
101
112#define REX_STUB_RETURN(subroutine, value) \
113 extern "C" REX_DETAIL_ENTRY_EXPORT REX_FUNC(subroutine) { \
114 static constinit ::std::atomic_flag rex_stub_seen_; \
115 const auto rex_stub_value_ = (value); \
116 ::rex::log::detail::WriteStub( \
117 ::rex::log::cpu, rex_stub_seen_, #subroutine, \
118 ::std::format("returning {:#x}", static_cast<::rex::u32>(rex_stub_value_))); \
119 ctx.r3.u64 = rex_stub_value_; \
120 }
121
132#define REX_EXPORT(name, function) REX_HOOK(name, function)
133
143#define REX_EXPORT_STUB(name) REX_STUB(name)
144
155#define REX_EXPORT_STUB_RETURN(name, value) REX_STUB_RETURN(name, value)
156
169// NOLINTBEGIN(bugprone-macro-parentheses): callable is a declared name
170#define REX_IMPORT(symbol, callable, sig) \
171 REX_EXTERN(symbol); \
172 [[maybe_unused]] static rex::ppc::ImportFunction<sig> callable { \
173 symbol \
174 }
175// NOLINTEND(bugprone-macro-parentheses)
176
177namespace rex::ppc {
178
179namespace detail {
180
182struct DetourFrame {
183 PPCContext* ctx;
184 u8* base;
185 u32 entry_r1;
186 u64 entry_lr;
187 bool ran_original;
188 DetourFrame* previous;
189};
190
195REX_PPC_API DetourFrame*& GetInnermostDetourFrame();
196
207REX_PPC_API std::string CheckDetourLocalWrites(const char* name, const PPCCodegenFlags& flags,
208 const PPCContext& at_entry,
209 const PPCContext& at_exit, std::atomic_flag& warned);
210
219REX_PPC_API void CheckBoundDetourLocalWrites(const char* name, const PPCContext& at_entry,
220 const PPCContext& at_exit, std::atomic_flag& warned);
221
230REX_PPC_API bool CheckDetourArguments(const char* name, const PPCCallUsage& usage, u8 declared_gpr,
231 u16 declared_fpr);
232
241REX_PPC_API void CheckBoundDetourArguments(const char* name, PPCFunc* host, u8 declared_gpr,
242 u16 declared_fpr);
243
245struct DeclaredArguments {
246 u8 gpr_mask;
247 u16 fpr_mask;
248};
249
254template <typename Guest>
255constexpr DeclaredArguments GetDeclaredArguments() {
256 DeclaredArguments declared{};
257 for (const ArgPlace& place : PlaceSignatureArgs(static_cast<Guest*>(nullptr))) {
258 for (u32 slot = place.slot;
259 slot < place.slot + place.slots && slot < ArgTranslator::kIntegerRegisters; ++slot) {
260 declared.gpr_mask = static_cast<u8>(declared.gpr_mask | (1u << slot));
261 }
262 if (place.fpr >= 0) {
263 declared.fpr_mask = static_cast<u16>(declared.fpr_mask | (1u << place.fpr));
264 }
265 }
266 return declared;
267}
268
270template <typename F>
271struct DetourBody;
272
274template <typename R, typename... A>
275struct DetourBody<R (*)(A...)> {
276 static constexpr bool kContext = false;
277 using Result = R;
278 using Guest = R(A...);
279 using Args = std::tuple<A...>;
280};
281
283template <typename R, typename... A>
284struct DetourBody<R (*)(PPCContext&, A...)> {
285 static constexpr bool kContext = true;
286 using Result = R;
287 using Guest = R(A...);
288 using Args = std::tuple<A...>;
289};
290
296template <typename R, typename... Args>
297R CallOriginal(DetourFrame& frame, PPCFunc& fn, Args... args) {
298 static_assert(!HiddenReturnType<R>, "A detour cannot return a structure by value");
299 PPCContext& ctx = *frame.ctx;
300 u8* base = frame.base;
301 frame.ran_original = true;
302 auto arguments = std::make_tuple(args...);
303 const u32 caller_r1 = ctx.r1.u32;
304 const u64 caller_lr = ctx.lr;
305 const bool opened = caller_r1 != frame.entry_r1;
306 if (opened) {
307 OpenCallFrame(ctx, base, (GetStackArgumentBytes<Args...>() + 15) & ~15u);
308 }
309 TranslateArgsToGuest(ctx, base, arguments);
310 fn(ctx, base);
311 if (opened) {
312 ctx.r1.u32 = caller_r1;
313 ctx.lr = caller_lr;
314 }
315 return LoadGuestResult<R>(base, ctx.r3, ctx.f1, ctx.v1, 0);
316}
317
319template <typename Guest, typename Args, size_t... I>
320void ReadDetourArgs(PPCContext& ctx, u8* base, Args& args, std::index_sequence<I...>) noexcept {
321 constexpr auto kPlaces = PlaceSignatureArgs(static_cast<Guest*>(nullptr));
322 ((std::get<I>(args) =
324 ...);
325}
326
328class DetourScope : public rex::NonCopyable {
329 public:
336 DetourScope(PPCContext& ctx, u8* base, bool chained)
337 : frame_{&ctx, base, ctx.r1.u32,
338 ctx.lr, false, chained ? GetInnermostDetourFrame() : nullptr},
339 context_previous_(ctx.detour_frame),
340 chained_(chained) {
341 if (chained_) {
342 GetInnermostDetourFrame() = &frame_;
343 }
344 ctx.detour_frame = &frame_;
345 }
346
348 ~DetourScope() {
349 frame_.ctx->r1.u32 = frame_.entry_r1;
350 frame_.ctx->lr = frame_.entry_lr;
351 frame_.ctx->detour_frame = context_previous_;
352 if (chained_) {
353 GetInnermostDetourFrame() = frame_.previous;
354 }
355 }
356
357 const DetourFrame& GetFrame() const { return frame_; }
358
359 private:
360 DetourFrame frame_;
361 DetourFrame* context_previous_;
362 bool chained_;
363};
364
365} // namespace detail
366
368template <typename Sig>
370
372template <typename R, typename... Args>
373struct DetourOriginal<R(Args...)> {
374 PPCFunc& fn;
375
376 constexpr explicit DetourOriginal(PPCFunc& original) noexcept : fn(original) {}
377
382 R operator()(Args... args) const {
383 detail::DetourFrame* frame = detail::GetInnermostDetourFrame();
384 REX_ASSERT(frame != nullptr, "A detour original was called outside a detour body");
385 return detail::CallOriginal<R>(*frame, fn, args...);
386 }
387};
388
390template <typename R, typename... Args>
391struct DetourOriginal<R(PPCContext&, Args...)> {
392 PPCFunc& fn;
393
394 constexpr explicit DetourOriginal(PPCFunc& original) noexcept : fn(original) {}
395
397 R operator()(PPCContext& ctx, Args... args) const {
398 detail::DetourFrame* frame = ctx.detour_frame;
399 REX_ASSERT(frame != nullptr, "A detour original was called outside a detour body");
400 return detail::CallOriginal<R>(*frame, this->fn, args...);
401 }
402};
403
410template <auto Body>
411__attribute__((noinline)) void RunDetour(PPCContext& ctx, u8* base, const char* name,
412 [[maybe_unused]] PPCFunc* host) {
413 using Traits = detail::DetourBody<decltype(Body)>;
414 using R = Traits::Result;
415 static_assert(!HiddenReturnType<R>, "A detour cannot return a structure by value");
416
417 detail::CheckHookContext(ctx, name);
418 if (ctx.stop_requested && ctx.stop_requested->load(std::memory_order_acquire)) {
419 throw rex::ThreadStop{};
420 }
421#if !REX_PLATFORM_WIN32
422 const rex::platform::HostCallDepth host_call_depth;
423#endif
424 typename Traits::Args args;
425 detail::ReadDetourArgs<typename Traits::Guest>(
426 ctx, base, args, std::make_index_sequence<std::tuple_size_v<typename Traits::Args>>());
427
428#ifndef NDEBUG
429 static constinit std::atomic_flag warned;
430 static constinit std::atomic_flag arguments_checked;
431 const PPCContext at_entry = ctx;
432 if (!arguments_checked.test_and_set()) {
433 constexpr detail::DeclaredArguments kDeclared =
434 detail::GetDeclaredArguments<typename Traits::Guest>();
435 detail::CheckBoundDetourArguments(name, host, kDeclared.gpr_mask, kDeclared.fpr_mask);
436 }
437#endif
438 detail::DetourScope scope(ctx, base, !Traits::kContext);
439
440 auto invoke = [&]() -> R {
441 if constexpr (Traits::kContext) {
442 return std::apply([&](auto&... values) -> R { return Body(ctx, values...); }, args);
443 } else {
444 return std::apply(Body, args);
445 }
446 };
447 auto check_exit = [&] {
448#ifndef NDEBUG
449 const detail::DetourFrame& frame = scope.GetFrame();
450 if (!frame.ran_original) {
451 REXCPU_ERROR("REX_DETOUR {} returned without running its original", name);
452 }
453 REX_ASSERT(frame.ran_original, "REX_DETOUR returned without running its original");
454 if (ctx.r1.u32 != frame.entry_r1) {
455 REXCPU_ERROR("REX_DETOUR {} left a StackFrame open, r1 is {:#x} and was {:#x}", name,
456 ctx.r1.u32, frame.entry_r1);
457 }
458 REX_ASSERT(ctx.r1.u32 == frame.entry_r1, "REX_DETOUR left a StackFrame open");
459 if (ctx.r2.u64 != at_entry.r2.u64) {
460 REXCPU_ERROR("REX_DETOUR {} changed r2", name);
461 }
462 REX_ASSERT(ctx.r2.u64 == at_entry.r2.u64, "REX_DETOUR changed r2");
463 if (ctx.r13.u64 != at_entry.r13.u64) {
464 REXCPU_ERROR("REX_DETOUR {} changed r13", name);
465 }
466 REX_ASSERT(ctx.r13.u64 == at_entry.r13.u64, "REX_DETOUR changed r13");
467 detail::CheckBoundDetourLocalWrites(name, at_entry, ctx, warned);
468#else
469 (void)name;
470#endif
471 };
472 if constexpr (std::is_void_v<R>) {
473 invoke();
474 asm volatile("" ::: "memory");
475 check_exit();
476 } else {
477 auto result = invoke();
478 asm volatile("" ::: "memory");
479 check_exit();
480 StoreHostResult(ctx, base, 0, result);
481 }
482 if (ctx.stop_requested && ctx.stop_requested->load(std::memory_order_acquire)) {
483 throw rex::ThreadStop{};
484 }
485}
486
487} // namespace rex::ppc
488
498#ifdef REXGLUE_ENABLE_PROFILING
499#define REX_DETAIL_DETOUR_ZONE(name) ZoneNamedN(___tracy_hook_zone, #name, TracyIsStarted);
500#else
501#define REX_DETAIL_DETOUR_ZONE(name)
502#endif
503
519// NOLINTBEGIN(bugprone-macro-parentheses): Ret and params declare a function
520#define REX_DETOUR(name, Ret, params) \
521 REX_EXTERN(__imp__##name); \
522 struct name##_rex_detour { \
523 static inline const rex::ppc::DetourOriginal<Ret params> original{__imp__##name}; \
524 static Ret Body params; \
525 }; \
526 REX_HOOK_RAW(name) { \
527 REX_DETAIL_DETOUR_ZONE(name) \
528 rex::ppc::RunDetour<&name##_rex_detour::Body>(ctx, base, #name, name); \
529 } \
530 Ret name##_rex_detour::Body params
531// NOLINTEND(bugprone-macro-parentheses)
#define REX_ASSERT(cond,...)
Definition assert.h:84
Definition utility.h:37
Definition seh.h:100
Definition call.h:91
PPC image layout, embedded metadata assets and pdata decoding.
Definition title_runtime.h:33
u32 OpenCallFrame(PPCContext &ctx, u8 *base, u32 stack_bytes)
Definition call.h:782
void StoreHostResult(PPCContext &ctx, u8 *base, u32 hidden_pointer, const RetT &value)
Definition call.h:672
R LoadGuestResult(u8 *base, const PPCRegister &r3, const PPCRegister &f1, const PPCVRegister &v1, u32 result_storage)
Definition call.h:822
constexpr u32 GetStackArgumentBytes()
Definition call.h:795
void RunDetour(PPCContext &ctx, u8 *base, const char *name, PPCFunc *host)
Definition hook.h:411
void TranslateArgsToGuest(PPCContext &ctx, u8 *base, std::tuple< TArgs... > &) noexcept
Definition call.h:595
constexpr auto PlaceSignatureArgs(R(*)(Args...))
Definition call.h:552
std::expected< T, Error > Result
Definition result.h:98
Platform flags, packing macros and export markers.
#define REX_PPC_API
Definition platform.h:194
Definition context.h:387
const std::atomic< bool > * stop_requested
Definition context.h:634
PPCRegister f1
Definition context.h:461
u64 lr
Definition context.h:428
PPCRegister r2
Definition context.h:397
PPCRegister r3
Definition context.h:389
rex::ppc::detail::DetourFrame * detour_frame
Definition context.h:453
PPCVRegister v1
Definition context.h:499
PPCRegister r13
Definition context.h:407
PPCRegister r1
Definition context.h:396
Definition thread.h:356
static T GetValue(PPCContext &ctx, u8 *base, const ArgPlace &arg) noexcept
Definition call.h:339
static constexpr size_t kIntegerRegisters
Definition call.h:130
R operator()(Args... args) const
Definition hook.h:382
R operator()(PPCContext &ctx, Args... args) const
Definition hook.h:397
Definition hook.h:369
Integer types, byte order wrappers and size literals.
Move only function, const propagating pointer and scope guard.