Skip to content

Commit c5c1f22

Browse files
committed
ExceptionHandler: Add richer callstack naming
1 parent da38a7b commit c5c1f22

File tree

7 files changed

+218
-1
lines changed

7 files changed

+218
-1
lines changed

CMakeLists.txt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ project(reframework
5151
CSharp
5252
)
5353

54+
include(CSharpUtilities)
55+
5456
set(CMAKE_POLICY_VERSION_MINIMUM 3.5)
5557
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP")
5658
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
@@ -2142,6 +2144,8 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework
21422144
"src/mods/LooseFileLoader.hpp"
21432145
"src/mods/ManualFlashlight.cpp"
21442146
"src/mods/ManualFlashlight.hpp"
2147+
"src/mods/MethodDatabase.cpp"
2148+
"src/mods/MethodDatabase.hpp"
21452149
"src/mods/PluginLoader.cpp"
21462150
"src/mods/PluginLoader.hpp"
21472151
"src/mods/REFrameworkConfig.cpp"
@@ -2341,6 +2345,8 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework
23412345
"src/mods/LooseFileLoader.hpp"
23422346
"src/mods/ManualFlashlight.cpp"
23432347
"src/mods/ManualFlashlight.hpp"
2348+
"src/mods/MethodDatabase.cpp"
2349+
"src/mods/MethodDatabase.hpp"
23442350
"src/mods/PluginLoader.cpp"
23452351
"src/mods/PluginLoader.hpp"
23462352
"src/mods/REFrameworkConfig.cpp"
@@ -4191,6 +4197,8 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework
41914197
"src/mods/LooseFileLoader.hpp"
41924198
"src/mods/ManualFlashlight.cpp"
41934199
"src/mods/ManualFlashlight.hpp"
4200+
"src/mods/MethodDatabase.cpp"
4201+
"src/mods/MethodDatabase.hpp"
41944202
"src/mods/PluginLoader.cpp"
41954203
"src/mods/PluginLoader.hpp"
41964204
"src/mods/REFrameworkConfig.cpp"
@@ -4390,6 +4398,8 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework
43904398
"src/mods/LooseFileLoader.hpp"
43914399
"src/mods/ManualFlashlight.cpp"
43924400
"src/mods/ManualFlashlight.hpp"
4401+
"src/mods/MethodDatabase.cpp"
4402+
"src/mods/MethodDatabase.hpp"
43934403
"src/mods/PluginLoader.cpp"
43944404
"src/mods/PluginLoader.hpp"
43954405
"src/mods/REFrameworkConfig.cpp"
@@ -4589,6 +4599,8 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework
45894599
"src/mods/LooseFileLoader.hpp"
45904600
"src/mods/ManualFlashlight.cpp"
45914601
"src/mods/ManualFlashlight.hpp"
4602+
"src/mods/MethodDatabase.cpp"
4603+
"src/mods/MethodDatabase.hpp"
45924604
"src/mods/PluginLoader.cpp"
45934605
"src/mods/PluginLoader.hpp"
45944606
"src/mods/REFrameworkConfig.cpp"
@@ -7266,6 +7278,8 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework
72667278
"src/mods/LooseFileLoader.hpp"
72677279
"src/mods/ManualFlashlight.cpp"
72687280
"src/mods/ManualFlashlight.hpp"
7281+
"src/mods/MethodDatabase.cpp"
7282+
"src/mods/MethodDatabase.hpp"
72697283
"src/mods/PluginLoader.cpp"
72707284
"src/mods/PluginLoader.hpp"
72717285
"src/mods/REFrameworkConfig.cpp"
@@ -7465,6 +7479,8 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework
74657479
"src/mods/LooseFileLoader.hpp"
74667480
"src/mods/ManualFlashlight.cpp"
74677481
"src/mods/ManualFlashlight.hpp"
7482+
"src/mods/MethodDatabase.cpp"
7483+
"src/mods/MethodDatabase.hpp"
74687484
"src/mods/PluginLoader.cpp"
74697485
"src/mods/PluginLoader.hpp"
74707486
"src/mods/REFrameworkConfig.cpp"
@@ -8491,6 +8507,8 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework
84918507
"src/mods/LooseFileLoader.hpp"
84928508
"src/mods/ManualFlashlight.cpp"
84938509
"src/mods/ManualFlashlight.hpp"
8510+
"src/mods/MethodDatabase.cpp"
8511+
"src/mods/MethodDatabase.hpp"
84948512
"src/mods/PluginLoader.cpp"
84958513
"src/mods/PluginLoader.hpp"
84968514
"src/mods/REFrameworkConfig.cpp"
@@ -9515,6 +9533,8 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework
95159533
"src/mods/LooseFileLoader.hpp"
95169534
"src/mods/ManualFlashlight.cpp"
95179535
"src/mods/ManualFlashlight.hpp"
9536+
"src/mods/MethodDatabase.cpp"
9537+
"src/mods/MethodDatabase.hpp"
95189538
"src/mods/PluginLoader.cpp"
95199539
"src/mods/PluginLoader.hpp"
95209540
"src/mods/REFrameworkConfig.cpp"
@@ -10541,6 +10561,8 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework
1054110561
"src/mods/LooseFileLoader.hpp"
1054210562
"src/mods/ManualFlashlight.cpp"
1054310563
"src/mods/ManualFlashlight.hpp"
10564+
"src/mods/MethodDatabase.cpp"
10565+
"src/mods/MethodDatabase.hpp"
1054410566
"src/mods/PluginLoader.cpp"
1054510567
"src/mods/PluginLoader.hpp"
1054610568
"src/mods/REFrameworkConfig.cpp"
@@ -11567,6 +11589,8 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework
1156711589
"src/mods/LooseFileLoader.hpp"
1156811590
"src/mods/ManualFlashlight.cpp"
1156911591
"src/mods/ManualFlashlight.hpp"
11592+
"src/mods/MethodDatabase.cpp"
11593+
"src/mods/MethodDatabase.hpp"
1157011594
"src/mods/PluginLoader.cpp"
1157111595
"src/mods/PluginLoader.hpp"
1157211596
"src/mods/REFrameworkConfig.cpp"
@@ -12593,6 +12617,8 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework
1259312617
"src/mods/LooseFileLoader.hpp"
1259412618
"src/mods/ManualFlashlight.cpp"
1259512619
"src/mods/ManualFlashlight.hpp"
12620+
"src/mods/MethodDatabase.cpp"
12621+
"src/mods/MethodDatabase.hpp"
1259612622
"src/mods/PluginLoader.cpp"
1259712623
"src/mods/PluginLoader.hpp"
1259812624
"src/mods/REFrameworkConfig.cpp"
@@ -13619,6 +13645,8 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework
1361913645
"src/mods/LooseFileLoader.hpp"
1362013646
"src/mods/ManualFlashlight.cpp"
1362113647
"src/mods/ManualFlashlight.hpp"
13648+
"src/mods/MethodDatabase.cpp"
13649+
"src/mods/MethodDatabase.hpp"
1362213650
"src/mods/PluginLoader.cpp"
1362313651
"src/mods/PluginLoader.hpp"
1362413652
"src/mods/REFrameworkConfig.cpp"
@@ -14645,6 +14673,8 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework
1464514673
"src/mods/LooseFileLoader.hpp"
1464614674
"src/mods/ManualFlashlight.cpp"
1464714675
"src/mods/ManualFlashlight.hpp"
14676+
"src/mods/MethodDatabase.cpp"
14677+
"src/mods/MethodDatabase.hpp"
1464814678
"src/mods/PluginLoader.cpp"
1464914679
"src/mods/PluginLoader.hpp"
1465014680
"src/mods/REFrameworkConfig.cpp"
@@ -15671,6 +15701,8 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework
1567115701
"src/mods/LooseFileLoader.hpp"
1567215702
"src/mods/ManualFlashlight.cpp"
1567315703
"src/mods/ManualFlashlight.hpp"
15704+
"src/mods/MethodDatabase.cpp"
15705+
"src/mods/MethodDatabase.hpp"
1567415706
"src/mods/PluginLoader.cpp"
1567515707
"src/mods/PluginLoader.hpp"
1567615708
"src/mods/REFrameworkConfig.cpp"

shared/sdk/RETypeDB.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ void* REMethodDefinition::get_function() const {
465465

466466
auto decl_type = this->get_declaring_type();
467467
auto name = decl_type != nullptr ? decl_type->get_full_name() : std::string{"null"};
468-
spdlog::error("[REMethodDefinition::get_function] Encoded offset is 0 (vindex {}) (method: {}.{})", this->get_virtual_index(), name, this->get_name());
468+
SPDLOG_DEBUG("[REMethodDefinition::get_function] Encoded offset is 0 (vindex {}) (method: {}.{})", this->get_virtual_index(), name, this->get_name());
469469
return nullptr;
470470
}
471471

shared/utility/Exceptions.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ namespace utility {
1010
namespace exceptions{
1111
static bool symbols_initialized = false;
1212
static HANDLE process{};
13+
static AddressNameResolver s_address_name_resolver = nullptr;
14+
15+
void set_address_name_resolver(AddressNameResolver resolver) {
16+
s_address_name_resolver = resolver;
17+
}
1318

1419
void dump_callstack(EXCEPTION_POINTERS* exception) {
1520
const auto dbghelp = LoadLibraryA("dbghelp.dll");
@@ -63,6 +68,20 @@ void dump_callstack(EXCEPTION_POINTERS* exception) {
6368

6469
std::string symbol_name = symbol_found ? symbol->Name : "Unknown symbol";
6570

71+
if (s_address_name_resolver != nullptr) {
72+
try {
73+
auto tdb_name = s_address_name_resolver(stack[i]);
74+
if (!tdb_name.empty()) {
75+
if (!symbol_found) {
76+
symbol_name = tdb_name;
77+
} else {
78+
symbol_name += " [TDB: " + tdb_name + "]";
79+
}
80+
}
81+
} catch (...) {
82+
}
83+
}
84+
6685
if (sym_get_line_from_addr64 != nullptr && symbols_initialized && symbol_found) {
6786
DWORD displacement = 0;
6887
IMAGEHLP_LINE64 line{};

shared/utility/Exceptions.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
#pragma once
22

3+
#include <string>
4+
35
namespace utility {
46
namespace exceptions {
7+
using AddressNameResolver = std::string(*)(uintptr_t);
8+
void set_address_name_resolver(AddressNameResolver resolver);
59
void dump_callstack(struct ::_EXCEPTION_POINTERS* exception);
610
}
711
}

src/Mods.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "mods/ManualFlashlight.hpp"
1313
#include "mods/PluginLoader.hpp"
1414
#include "mods/REFrameworkConfig.hpp"
15+
#include "mods/MethodDatabase.hpp"
1516
#include "mods/Scene.hpp"
1617
#include "mods/ScriptRunner.hpp"
1718
#include "mods/VR.hpp"
@@ -29,6 +30,7 @@ Mods::Mods() {
2930
#endif
3031

3132
#ifndef BAREBONES
33+
m_mods.emplace_back(MethodDatabase::get());
3234
m_mods.emplace_back(Hooks::get());
3335
m_mods.emplace_back(LooseFileLoader::get());
3436

src/mods/MethodDatabase.cpp

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
#include <chrono>
2+
#include <spdlog/spdlog.h>
3+
4+
#include <sdk/RETypeDB.hpp>
5+
6+
#include "utility/Exceptions.hpp"
7+
#include "utility/Scan.hpp"
8+
9+
#include "MethodDatabase.hpp"
10+
11+
std::shared_ptr<MethodDatabase>& MethodDatabase::get() {
12+
static auto instance = std::make_shared<MethodDatabase>();
13+
return instance;
14+
}
15+
16+
std::string MethodDatabase::resolve_address(uintptr_t addr) {
17+
return get()->find_method(addr);
18+
}
19+
20+
std::optional<std::string> MethodDatabase::on_initialize() {
21+
spdlog::info("[MethodDatabase] Building method address map...");
22+
23+
const auto start = std::chrono::steady_clock::now();
24+
25+
auto tdb = sdk::RETypeDB::get();
26+
27+
if (tdb == nullptr) {
28+
return "MethodDatabase: RETypeDB not available";
29+
}
30+
31+
const auto num_methods = tdb->get_num_methods();
32+
size_t thunks = 0;
33+
34+
{
35+
std::unique_lock lock{m_mutex};
36+
37+
for (uint32_t i = 0; i < num_methods; ++i) {
38+
auto method = tdb->get_method(i);
39+
40+
if (method == nullptr) {
41+
continue;
42+
}
43+
44+
auto func = method->get_function();
45+
46+
if (func == nullptr) {
47+
continue;
48+
}
49+
50+
auto declaring_type = method->get_declaring_type();
51+
52+
if (declaring_type == nullptr) {
53+
continue;
54+
}
55+
56+
try {
57+
auto full_name = declaring_type->get_full_name();
58+
auto method_name = method->get_name();
59+
60+
if (method_name == nullptr) {
61+
continue;
62+
}
63+
64+
full_name += ".";
65+
full_name += method_name;
66+
67+
const auto func_addr = (uintptr_t)func;
68+
m_method_map[func_addr] = full_name;
69+
70+
// If the function starts with an E9 jmp, also map the jump target
71+
if (*(uint8_t*)func == 0xE9) {
72+
const auto target = utility::calculate_absolute((uintptr_t)func + 1);
73+
m_method_map[target] = full_name;
74+
++thunks;
75+
}
76+
} catch (...) {
77+
continue;
78+
}
79+
}
80+
}
81+
82+
const auto end = std::chrono::steady_clock::now();
83+
m_build_time_ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
84+
85+
// Estimate RAM: each std::map node has ~48 bytes overhead (red-black tree node with 3 pointers + color + key + std::string object)
86+
// plus the actual string heap allocation for strings > SSO threshold
87+
constexpr size_t map_node_overhead = sizeof(uintptr_t) + sizeof(std::string) + sizeof(void*) * 3 + sizeof(int);
88+
m_estimated_ram = 0;
89+
for (const auto& [addr, name] : m_method_map) {
90+
m_estimated_ram += map_node_overhead + (name.size() > 22 ? name.size() + 1 : 0);
91+
}
92+
93+
spdlog::info("[MethodDatabase] Built map with {} methods in {}ms (~{:.1f} MB RAM)", m_method_map.size(), m_build_time_ms, m_estimated_ram / (1024.0 * 1024.0));
94+
spdlog::info("[MethodDatabase] Thunks found: {}", thunks);
95+
96+
utility::exceptions::set_address_name_resolver(&MethodDatabase::resolve_address);
97+
98+
return Mod::on_initialize();
99+
}
100+
101+
std::string MethodDatabase::find_method(uintptr_t addr) const {
102+
std::shared_lock lock{m_mutex};
103+
104+
if (m_method_map.empty()) {
105+
return {};
106+
}
107+
108+
// Try to resolve the function start from the unwind info first
109+
const auto func_start = utility::find_function_start_unwind(addr);
110+
111+
if (func_start) {
112+
auto it = m_method_map.find(*func_start);
113+
114+
if (it != m_method_map.end()) {
115+
return it->second;
116+
}
117+
}
118+
119+
// Fall back to upper_bound lookup
120+
auto it = m_method_map.upper_bound(addr);
121+
122+
if (it == m_method_map.begin()) {
123+
return {};
124+
}
125+
126+
--it;
127+
128+
constexpr uintptr_t max_method_size = 0x10000;
129+
130+
if (addr - it->first > max_method_size) {
131+
return {};
132+
}
133+
134+
return it->second;
135+
}

src/mods/MethodDatabase.hpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#pragma once
2+
3+
#include <map>
4+
#include <shared_mutex>
5+
#include <string>
6+
7+
#include "Mod.hpp"
8+
9+
class MethodDatabase : public Mod {
10+
public:
11+
static std::shared_ptr<MethodDatabase>& get();
12+
13+
std::string_view get_name() const override { return "MethodDatabase"; }
14+
std::optional<std::string> on_initialize() override;
15+
16+
std::string find_method(uintptr_t addr) const;
17+
18+
private:
19+
static std::string resolve_address(uintptr_t addr);
20+
21+
mutable std::shared_mutex m_mutex{};
22+
std::map<uintptr_t, std::string> m_method_map{};
23+
size_t m_build_time_ms{0};
24+
size_t m_estimated_ram{0};
25+
};

0 commit comments

Comments
 (0)