From 1c4584627977c5d210a576b5c4eb36d277158a35 Mon Sep 17 00:00:00 2001 From: praydog Date: Fri, 29 Mar 2024 18:54:02 -0700 Subject: [PATCH] HookManager: Add support for recursion w/ new lock-free(-ish) method --- src/HookManager.cpp | 93 ++++++++++++++++++++++++++++++++++++++------- src/HookManager.hpp | 23 ++++++++++- 2 files changed, 102 insertions(+), 14 deletions(-) diff --git a/src/HookManager.cpp b/src/HookManager.cpp index 16186561b..f3b2c01a8 100644 --- a/src/HookManager.cpp +++ b/src/HookManager.cpp @@ -57,11 +57,33 @@ HookManager::HookedFn::~HookedFn() { } HookManager::PreHookResult HookManager::HookedFn::on_pre_hook() { - std::shared_lock _{this->access_mux}; + //std::shared_lock _{this->access_mux}; auto any_skipped = false; auto storage = get_storage(this); + + if (storage->pre_depth == 0) { + // afaik, shared locks are not reentrant, so only lock it + // if we're not already in a pre-hook. + this->access_mux.lock_shared(); + } else if (!storage->pre_warned_recursion) { + const auto tid = std::hash{}(std::this_thread::get_id()); + const auto declaring_type = fn_def->get_declaring_type(); + const auto decltype_name = declaring_type != nullptr ? declaring_type->get_full_name() : "unknownclass"; + spdlog::warn("[HookManager] (Pre) Recursive hook detected for '{}.{}' (thread ID: {:x})", decltype_name, fn_def->get_name(), tid); + storage->pre_warned_recursion = true; + } + + if (storage->overall_depth > 0 && !storage->overall_warned_recursion) { + const auto tid = std::hash{}(std::this_thread::get_id()); + const auto declaring_type = fn_def->get_declaring_type(); + const auto decltype_name = declaring_type != nullptr ? declaring_type->get_full_name() : "unknownclass"; + spdlog::warn("[HookManager] (Overall) '{}.{}' appears to be calling itself in some way (thread ID: {:x})", decltype_name, fn_def->get_name(), tid); + storage->overall_warned_recursion = true; + } + + ++storage->pre_depth; const auto ret_addr_pre = storage->ret_addr_pre; for (const auto& cb : cbs) { @@ -70,15 +92,38 @@ HookManager::PreHookResult HookManager::HookedFn::on_pre_hook() { any_skipped = true; } } - } + } + + ++storage->overall_depth; + --storage->pre_depth; + + if (storage->pre_depth == 0) { + this->access_mux.unlock_shared(); + } return any_skipped ? PreHookResult::SKIP_ORIGINAL : PreHookResult::CALL_ORIGINAL; } void HookManager::HookedFn::on_post_hook() { - std::shared_lock _{this->access_mux}; + //std::shared_lock _{this->access_mux}; auto storage = get_storage(this); + + if (storage->post_depth == 0) { + // afaik, shared locks are not reentrant, so only lock it + // if we're not already in a post-hook. + this->access_mux.lock_shared(); + } else if (!storage->post_warned_recursion) { + const auto tid = std::hash{}(std::this_thread::get_id()); + const auto declaring_type = fn_def->get_declaring_type(); + const auto decltype_name = declaring_type != nullptr ? declaring_type->get_full_name() : "unknownclass"; + spdlog::warn("[HookManager] (Post) Recursive hook detected for '{}.{}' (thread ID: {:x})", decltype_name, fn_def->get_name(), tid); + storage->post_warned_recursion = true; + } + + ++storage->post_depth; + --storage->overall_depth; + auto& ret_val = storage->ret_val; auto& ret_addr = storage->ret_addr; @@ -87,6 +132,12 @@ void HookManager::HookedFn::on_post_hook() { cb.post_fn(ret_val, ret_ty, ret_addr); } } + + --storage->post_depth; + + if (storage->post_depth == 0) { + this->access_mux.unlock_shared(); + } } void HookManager::create_jitted_facilitator(std::unique_ptr& hook, sdk::REMethodDefinition* fn, std::function hook_initialization, std::function hook_create) { @@ -116,6 +167,8 @@ void HookManager::create_jitted_facilitator(std::unique_ptr #include #include +#include #include @@ -62,14 +63,24 @@ class HookManager { bool is_virtual{false}; HookedVTable* vtable{nullptr}; + // Per-thread storage for hooked function. struct HookStorage { size_t* args{}; uintptr_t This{}; uintptr_t ret_addr_pre{}; uintptr_t ret_addr{}; uintptr_t ret_val{}; - uintptr_t rbx{}; + + uintptr_t rbx; // temp storage for rbx. + std::stack rbx_stack{}; // full storage for rbx. std::vector args_impl{}; + + uint32_t pre_depth{0}; + uint32_t overall_depth{0}; + uint32_t post_depth{0}; + bool pre_warned_recursion{false}; // for logging recursion. + bool overall_warned_recursion{false}; // for logging recursion. + bool post_warned_recursion{false}; // for logging recursion. }; // Thread->storage @@ -82,6 +93,16 @@ class HookManager { PreHookResult on_pre_hook(); void on_post_hook(); + __declspec(noinline) static void push_rbx(HookStorage* storage, uintptr_t rbx) { + storage->rbx_stack.push(rbx); + } + + __declspec(noinline) static uintptr_t pop_rbx(HookStorage* storage) { + auto rbx = storage->rbx_stack.top(); + storage->rbx_stack.pop(); + return rbx; + } + __declspec(noinline) static HookStorage* get_storage(HookedFn* fn) { auto tid = std::hash{}(std::this_thread::get_id()); {