Skip to content

Commit a3dce5b

Browse files
bkloster-smaBenjamin Klosterjeremy-rifkin
authored
Add support for modules loaded at runtime (#247)
I noticed that cpptrace could not resolve symbols from DLLs that were loaded with `LoadLibrary` after already generating at least one stacktrace. According to the Microsoft documentation for [Symbol Handler Initialization](https://learn.microsoft.com/en-us/windows/win32/debug/symbol-handler-initialization), one should call SymLoadModuleEx for this case. I've implemented a new function `cpptrace::experimental::load_symbols_for_file(const std::string& name)` that does this. --------- Co-authored-by: Benjamin Kloster <kloster@smaract.com> Co-authored-by: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com>
1 parent 2a217d8 commit a3dce5b

File tree

5 files changed

+106
-0
lines changed

5 files changed

+106
-0
lines changed

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
4343
- [Headers](#headers)
4444
- [Libdwarf Tuning](#libdwarf-tuning)
4545
- [JIT Support](#jit-support)
46+
- [Loading Libraries at Runtime](#loading-libraries-at-runtime)
4647
- [ABI Versioning](#abi-versioning)
4748
- [Supported Debug Formats](#supported-debug-formats)
4849
- [How to Include The Library](#how-to-include-the-library)
@@ -1195,6 +1196,25 @@ registered with cpptrace.
11951196
11961197
[jitci]: https://sourceware.org/gdb/current/onlinedocs/gdb.html/JIT-Interface.html
11971198
1199+
## Loading Libraries at Runtime
1200+
1201+
This section only applies to the dbghelp backend (`CPPTRACE_GET_SYMBOLS_WITH_DBGHELP`) on Windows.
1202+
1203+
When loading a DLL at runtime with `LoadLibrary` after a stacktrace has already been generated,
1204+
symbols from that library may not be resolved correctly for subsequent stacktraces. To fix this,
1205+
call `cpptrace::experimental::load_symbols_for_file` with the same filename that was passed to
1206+
`LoadLibrary`.
1207+
1208+
```cpp
1209+
HMODULE hModule = LoadLibrary("mydll.dll");
1210+
if (hModule) {
1211+
cpptrace::experimental::load_symbols_for_file("mydll.dll");
1212+
}
1213+
```
1214+
1215+
For backends other than dbghelp, `load_symbols_for_file` does nothing. For platforms other than
1216+
Windows, it is not declared.
1217+
11981218
# ABI Versioning
11991219

12001220
Since cpptrace vX, the library uses an inline ABI versioning namespace and all symbols part of the public interface are

include/cpptrace/utils.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ CPPTRACE_BEGIN_NAMESPACE
5757
CPPTRACE_EXPORT void set_dwarf_resolver_line_table_cache_size(nullable<std::size_t> max_entries);
5858
CPPTRACE_EXPORT void set_dwarf_resolver_disable_aranges(bool disable);
5959
}
60+
61+
// dbghelp
62+
namespace experimental {
63+
CPPTRACE_EXPORT void load_symbols_for_file(const std::string& filename);
64+
}
6065
CPPTRACE_END_NAMESPACE
6166

6267
#endif

src/cpptrace.cppm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,5 +99,6 @@ CPPTRACE_BEGIN_NAMESPACE
9999
export using cpptrace::experimental::set_cache_mode;
100100
export using cpptrace::experimental::set_dwarf_resolver_line_table_cache_size;
101101
export using cpptrace::experimental::set_dwarf_resolver_disable_aranges;
102+
export using cpptrace::experimental::load_symbols_for_file;
102103
}
103104
CPPTRACE_END_NAMESPACE

src/symbols/symbols_core.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#include <cpptrace/basic.hpp>
2+
#include <cpptrace/utils.hpp>
23

4+
#include "cpptrace/forward.hpp"
35
#include "symbols/symbols.hpp"
46

57
#include <vector>
@@ -152,3 +154,18 @@ namespace internal {
152154
}
153155
}
154156
}
157+
158+
159+
/*
160+
Fallback definition for cpptrace::experimental::load_symbols_for_file. If
161+
CPPTRACE_GET_SYMBOLS_WITH_DBGHELP is defined, this function is defined in symbols_with_dbghelp.cpp.
162+
*/
163+
#ifndef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP
164+
CPPTRACE_BEGIN_NAMESPACE
165+
namespace experimental {
166+
void load_symbols_for_file(const std::string& filename) {
167+
(void)filename;
168+
}
169+
}
170+
CPPTRACE_END_NAMESPACE
171+
#endif

src/symbols/symbols_with_dbghelp.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP
22

33
#include <cpptrace/basic.hpp>
4+
#include <cpptrace/utils.hpp>
45
#include "symbols/symbols.hpp"
56
#include "platform/dbghelp_utils.hpp"
67
#include "binary/object.hpp"
@@ -19,6 +20,7 @@
1920
#endif
2021
#include <windows.h>
2122
#include <dbghelp.h>
23+
#include <psapi.h>
2224

2325
namespace cpptrace {
2426
namespace internal {
@@ -450,4 +452,65 @@ namespace dbghelp {
450452
}
451453
}
452454

455+
CPPTRACE_BEGIN_NAMESPACE
456+
namespace experimental {
457+
/*
458+
When a module was loaded at runtime with LoadLibrary after SymInitialize was already called,
459+
it is necessary to manually load the symbols from that module with SymLoadModuleEx.
460+
461+
See "Symbol Handler Initialization" in Microsoft documentation at
462+
https://learn.microsoft.com/en-us/windows/win32/debug/symbol-handler-initialization
463+
*/
464+
void load_symbols_for_file(const std::string& filename) {
465+
HMODULE hModule = GetModuleHandleA(filename.c_str());
466+
if (hModule == NULL) {
467+
throw internal::internal_error(
468+
"Unable to get module handle for file '{}' : {}",
469+
filename,
470+
std::system_error(GetLastError(), std::system_category()).what()
471+
);
472+
}
473+
474+
// SymLoadModuleEx needs the module's base address and size, so get these with GetModuleInformation.
475+
MODULEINFO module_info;
476+
if (
477+
!GetModuleInformation(
478+
GetCurrentProcess(),
479+
hModule,
480+
&module_info,
481+
sizeof(module_info)
482+
)
483+
) {
484+
throw internal::internal_error(
485+
"Unable to get module information for file '{}' : {}",
486+
filename,
487+
std::system_error(GetLastError(), std::system_category()).what()
488+
);
489+
}
490+
491+
auto lock = internal::get_dbghelp_lock();
492+
HANDLE syminit_handle = internal::ensure_syminit().get_process_handle();
493+
if (
494+
!SymLoadModuleEx(
495+
syminit_handle,
496+
NULL,
497+
filename.c_str(),
498+
NULL,
499+
(DWORD64)module_info.lpBaseOfDll,
500+
// The documentation says this is optional, but if omitted (0), symbol loading fails
501+
module_info.SizeOfImage,
502+
NULL,
503+
0
504+
)
505+
) {
506+
throw internal::internal_error(
507+
"Unable to load symbols for file '{}' : {}",
508+
filename,
509+
std::system_error(GetLastError(), std::system_category()).what()
510+
);
511+
}
512+
}
513+
}
514+
CPPTRACE_END_NAMESPACE
515+
453516
#endif

0 commit comments

Comments
 (0)