From 11e55c2cf8fe86a27115cead7dfb426a5595c7f7 Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 26 Mar 2024 22:23:35 -0700 Subject: [PATCH] ObjectExplorer: Add "Called Methods" to method displays --- src/mods/tools/ObjectExplorer.cpp | 78 +++++++++++++++++++++++++++++++ src/mods/tools/ObjectExplorer.hpp | 9 ++++ 2 files changed, 87 insertions(+) diff --git a/src/mods/tools/ObjectExplorer.cpp b/src/mods/tools/ObjectExplorer.cpp index 42e56e372..caa485007 100644 --- a/src/mods/tools/ObjectExplorer.cpp +++ b/src/mods/tools/ObjectExplorer.cpp @@ -3041,7 +3041,70 @@ void ObjectExplorer::display_native_fields(REManagedObject* obj, sdk::RETypeDefi //#endif } +void ObjectExplorer::populate_method_meta_info(sdk::REMethodDefinition& m) { + const auto method_ptr = m.get_function(); + + if (method_ptr == nullptr) { + return; + } + + if (m_method_meta_infos.contains((uintptr_t)method_ptr)) { + return; + } + + m_method_meta_infos[(uintptr_t)method_ptr] = std::make_unique(); + + if (IsBadReadPtr(method_ptr, sizeof(void*))) { + return; + } + + std::unordered_set called_functions{}; + + // Disassemble the function's instructions looking for calls to other functions, and add them to the list of methods this function calls + utility::exhaustive_decode((uint8_t*)method_ptr, 5000, [&](utility::ExhaustionContext& ctx) -> utility::ExhaustionResult { + if (ctx.addr != (uintptr_t)method_ptr) { + if (auto it = m_method_map.find(ctx.addr); it != m_method_map.end()) { + called_functions.insert(it->second); + } + } + + if (std::string_view{ctx.instrux.Mnemonic}.starts_with("CALL")) { + const auto resolved_addr = utility::resolve_displacement(ctx.addr); + + if (resolved_addr) { + if (auto it = m_method_map.find(*resolved_addr); it != m_method_map.end()) { + called_functions.insert(it->second); + } + } + + // Step over all calls, we don't want to go down a rabbit hole that freezes the program + return utility::ExhaustionResult::STEP_OVER; + } + + return utility::ExhaustionResult::CONTINUE; + }); + + for (auto& called_function : called_functions) { + m_method_meta_infos[(uintptr_t)method_ptr]->called_functions.push_back(called_function); + } + + std::sort( + m_method_meta_infos[(uintptr_t)method_ptr]->called_functions.begin(), + m_method_meta_infos[(uintptr_t)method_ptr]->called_functions.end(), + [](sdk::REMethodDefinition* a, sdk::REMethodDefinition* b) + { + const auto decltype_a = a->get_declaring_type(); + const auto decltype_b = b->get_declaring_type(); + const auto fullname_a = decltype_a != nullptr ? decltype_a->get_full_name() + "." + a->get_name() : a->get_name(); + const auto fullname_b = decltype_b != nullptr ? decltype_b->get_full_name() + "." + b->get_name() : b->get_name(); + + return fullname_a < fullname_b; + }); +} + void ObjectExplorer::attempt_display_method(REManagedObject* obj, sdk::REMethodDefinition& m, bool use_full_name) { + populate_method_meta_info(m); + const auto declaring_type = m.get_declaring_type(); const auto method_name = (use_full_name && declaring_type != nullptr) ? declaring_type->get_full_name() + "." + m.get_name() : m.get_name(); const auto method_return_type = m.get_return_type(); @@ -3181,6 +3244,21 @@ void ObjectExplorer::attempt_display_method(REManagedObject* obj, sdk::REMethodD } } + if (ImGui::BeginTable("##calls", 1, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable)) { + ImGui::TableNextColumn(); + ImGui::Text("Called Method"); + + // Show a list of methods this method calls + if (m_method_meta_infos.contains((uintptr_t)method_ptr)) { + for (const auto& called_m : m_method_meta_infos[(uintptr_t)method_ptr]->called_functions) { + ImGui::TableNextColumn(); + attempt_display_method(nullptr, *called_m, true); + } + } + + ImGui::EndTable(); + } + if (ImGui::BeginTable("##disassembly", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable)) { ImGui::TableNextColumn(); ImGui::Text("Address"); diff --git a/src/mods/tools/ObjectExplorer.hpp b/src/mods/tools/ObjectExplorer.hpp index feb959ccc..c510a8706 100644 --- a/src/mods/tools/ObjectExplorer.hpp +++ b/src/mods/tools/ObjectExplorer.hpp @@ -161,6 +161,7 @@ class ObjectExplorer : public Tool { void display_reflection_properties(REManagedObject* obj, REType* type_info); void display_native_methods(REManagedObject* obj, sdk::RETypeDefinition* tdef); void display_native_fields(REManagedObject* obj, sdk::RETypeDefinition* tdef); + void populate_method_meta_info(sdk::REMethodDefinition& m); void attempt_display_method(REManagedObject* obj, sdk::REMethodDefinition& m, bool use_full_name = false); void attempt_display_field(REManagedObject* obj, VariableDescriptor* desc, REType* type_info); void display_data(void* data, void* real_data, std::string type_name, bool is_enum = false, bool managed_str = false, const sdk::RETypeDefinition* override_def = nullptr); @@ -270,6 +271,14 @@ class ObjectExplorer : public Tool { std::chrono::high_resolution_clock::time_point last_call_time{}; }; + // Contains extra information about the method + struct MethodMetaInfo { + std::vector called_functions{}; + }; + + // Function address -> meta info (not REMethodDefinition, since method defs can share function addresses) + std::unordered_map> m_method_meta_infos{}; + asmjit::JitRuntime m_jit_runtime; std::vector m_pinned_objects{};