Skip to content

Add support for modules loaded at runtime #247

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
May 29, 2025
Merged
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
- [Headers](#headers)
- [Libdwarf Tuning](#libdwarf-tuning)
- [JIT Support](#jit-support)
- [Loading Libraries at Runtime](#loading-libraries-at-runtime)
- [ABI Versioning](#abi-versioning)
- [Supported Debug Formats](#supported-debug-formats)
- [How to Include The Library](#how-to-include-the-library)
Expand Down Expand Up @@ -1195,6 +1196,25 @@ registered with cpptrace.

[jitci]: https://sourceware.org/gdb/current/onlinedocs/gdb.html/JIT-Interface.html

## Loading Libraries at Runtime

This section only applies to the dbghelp backend (`CPPTRACE_GET_SYMBOLS_WITH_DBGHELP`) on Windows.

When loading a DLL at runtime with `LoadLibrary` after a stacktrace has already been generated,
symbols from that library may not be resolved correctly for subsequent stacktraces. To fix this,
call `cpptrace::experimental::load_symbols_for_file` with the same filename that was passed to
`LoadLibrary`.

```cpp
HMODULE hModule = LoadLibrary("mydll.dll");
if (hModule) {
cpptrace::experimental::load_symbols_for_file("mydll.dll");
}
```

For backends other than dbghelp, `load_symbols_for_file` does nothing. For platforms other than
Windows, it is not declared.

# ABI Versioning

Since cpptrace vX, the library uses an inline ABI versioning namespace and all symbols part of the public interface are
Expand Down
5 changes: 5 additions & 0 deletions include/cpptrace/utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ CPPTRACE_BEGIN_NAMESPACE
CPPTRACE_EXPORT void set_dwarf_resolver_line_table_cache_size(nullable<std::size_t> max_entries);
CPPTRACE_EXPORT void set_dwarf_resolver_disable_aranges(bool disable);
}

// dbghelp
namespace experimental {
CPPTRACE_EXPORT void load_symbols_for_file(const std::string& filename);
}
CPPTRACE_END_NAMESPACE

#endif
1 change: 1 addition & 0 deletions src/cpptrace.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -98,5 +98,6 @@ CPPTRACE_BEGIN_NAMESPACE
export using cpptrace::experimental::set_cache_mode;
export using cpptrace::experimental::set_dwarf_resolver_line_table_cache_size;
export using cpptrace::experimental::set_dwarf_resolver_disable_aranges;
export using cpptrace::experimental::load_symbols_for_file;
}
CPPTRACE_END_NAMESPACE
17 changes: 17 additions & 0 deletions src/symbols/symbols_core.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include <cpptrace/basic.hpp>
#include <cpptrace/utils.hpp>

#include "cpptrace/forward.hpp"
#include "symbols/symbols.hpp"

#include <vector>
Expand Down Expand Up @@ -152,3 +154,18 @@ namespace internal {
}
}
}


/*
Fallback definition for cpptrace::experimental::load_symbols_for_file. If
CPPTRACE_GET_SYMBOLS_WITH_DBGHELP is defined, this function is defined in symbols_with_dbghelp.cpp.
*/
#ifndef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP
CPPTRACE_BEGIN_NAMESPACE
namespace experimental {
void load_symbols_for_file(const std::string& filename) {
(void)filename;
}
}
CPPTRACE_END_NAMESPACE
#endif
63 changes: 63 additions & 0 deletions src/symbols/symbols_with_dbghelp.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP

#include <cpptrace/basic.hpp>
#include <cpptrace/utils.hpp>
#include "symbols/symbols.hpp"
#include "platform/dbghelp_utils.hpp"
#include "binary/object.hpp"
Expand All @@ -19,6 +20,7 @@
#endif
#include <windows.h>
#include <dbghelp.h>
#include <psapi.h>

namespace cpptrace {
namespace internal {
Expand Down Expand Up @@ -450,4 +452,65 @@ namespace dbghelp {
}
}

CPPTRACE_BEGIN_NAMESPACE
namespace experimental {
/*
When a module was loaded at runtime with LoadLibrary after SymInitialize was already called,
it is necessary to manually load the symbols from that module with SymLoadModuleEx.

See "Symbol Handler Initialization" in Microsoft documentation at
https://learn.microsoft.com/en-us/windows/win32/debug/symbol-handler-initialization
*/
void load_symbols_for_file(const std::string& filename) {
HMODULE hModule = GetModuleHandleA(filename.c_str());
if (hModule == NULL) {
throw internal::internal_error(
"Unable to get module handle for file '{}' : {}",
filename,
std::system_error(GetLastError(), std::system_category()).what()
);
}

// SymLoadModuleEx needs the module's base address and size, so get these with GetModuleInformation.
MODULEINFO module_info;
if (
!GetModuleInformation(
GetCurrentProcess(),
hModule,
&module_info,
sizeof(module_info)
)
) {
throw internal::internal_error(
"Unable to get module information for file '{}' : {}",
filename,
std::system_error(GetLastError(), std::system_category()).what()
);
}

auto lock = internal::get_dbghelp_lock();
HANDLE syminit_handle = internal::ensure_syminit().get_process_handle();
if (
!SymLoadModuleEx(
syminit_handle,
NULL,
filename.c_str(),
NULL,
(DWORD64)module_info.lpBaseOfDll,
// The documentation says this is optional, but if omitted (0), symbol loading fails
module_info.SizeOfImage,
NULL,
0
)
) {
throw internal::internal_error(
"Unable to load symbols for file '{}' : {}",
filename,
std::system_error(GetLastError(), std::system_category()).what()
);
}
}
}
CPPTRACE_END_NAMESPACE

#endif
Loading