Skip to content

Commit

Permalink
Use Relative Virtual Addresses to allow their decoding without knowin…
Browse files Browse the repository at this point in the history
…g the base address (#200)

Instead of printing absolute addresses use relative ones so they can be used later to decode the stack trace.

## Motivation

It’s quite common to release apps without debug symbols while keeping them internally. Currently the lib prints absolute addresses which require the base address to decode them using symbols. With this change, the base address won’t be needed as values are relative to the beginning of the loaded module/binary.

The implementation for unix is **straightforward** as the existing code is used. For Windows, I’ve implemented similar logic using Windows API.

## Manual testing of Windows implementation

Here are 2 runs of `trivial_windbg_lib.exe` with .pdb and without .pdb file:

```jsx
>./trivial_windbg_lib.exe
 0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::init at C:\Users\czarneckim\repositories\boost\boost\stacktrace\stacktrace.hpp:110
 1# main at C:\Users\czarneckim\repositories\boost\libs\stacktrace\test\test_trivial.cpp:14
 2# __scrt_common_main_seh at D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288
 3# BaseThreadInitThunk in C:\WINDOWS\System32\KERNEL32.DLL
 4# RtlUserThreadStart in C:\WINDOWS\SYSTEM32\ntdll.dll

> mv ./trivial_windbg_lib.pdb{,_} 
> ./trivial_windbg_lib.exe
 0# 0x0000000000001DE3 in C:\Users\czarneckim\repositories\boost\bin.v2\libs\stacktrace\test\trivial_windbg_lib.test\msvc-14.3\release\x86_64\asynch-exceptions-on\cxxstd-latest-iso\debug-symbols-on\threading-multi\trivial_windbg_lib.exe
 1# 0x0000000000001FEE in C:\Users\czarneckim\repositories\boost\bin.v2\libs\stacktrace\test\trivial_windbg_lib.test\msvc-14.3\release\x86_64\asynch-exceptions-on\cxxstd-latest-iso\debug-symbols-on\threading-multi\trivial_windbg_lib.exe
 2# 0x000000000000246C in C:\Users\czarneckim\repositories\boost\bin.v2\libs\stacktrace\test\trivial_windbg_lib.test\msvc-14.3\release\x86_64\asynch-exceptions-on\cxxstd-latest-iso\debug-symbols-on\threading-multi\trivial_windbg_lib.exe
 3# BaseThreadInitThunk in C:\WINDOWS\System32\KERNEL32.DLL
 4# RtlUserThreadStart in C:\WINDOWS\SYSTEM32\ntdll.dll

> mv ./trivial_windbg_lib.pdb{_,}
> winaddr2line.exe -f -e trivial_windbg_lib.pdb 0x0000000000001DE3
boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::init
C:\Users\czarneckim\repositories\boost\boost\stacktrace\stacktrace.hpp:110
```

I’ve also decoded the returned address using x64dbg running `trivial_windbg_lib.exe+0x0000000000001DE3`:

![image](https://github.com/user-attachments/assets/a36d3306-488d-4610-9f04-7888b98c2b75)

## Control

As it was suggested in #180, the new logic is enabled by default, but can be disabled with `BOOST_STACKTRACE_DISABLE_OFFSET_ADDR_BASE` (With non header-only mode, it requires rebuilding the lib)
  • Loading branch information
McCzarny authored Jan 7, 2025
1 parent 5ec4591 commit b170b28
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 1 deletion.
72 changes: 72 additions & 0 deletions include/boost/stacktrace/detail/addr_base_msvc.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright Antony Polukhin, 2016-2024.
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_STACKTRACE_DETAIL_ADDR_BASE_MSVC_HPP
#define BOOST_STACKTRACE_DETAIL_ADDR_BASE_MSVC_HPP

#include <boost/config.hpp>
#ifdef BOOST_HAS_PRAGMA_ONCE
# pragma once
#endif

#include <cstdio>
#include <memory>

#ifdef WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <psapi.h>
#else
// Prevent inclusion of extra Windows SDK headers which can cause conflict
// with other code using Windows SDK
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <psapi.h>
#undef WIN32_LEAN_AND_MEAN
#endif

namespace boost { namespace stacktrace { namespace detail {
inline std::uintptr_t get_own_proc_addr_base(const void* addr) {
// Try to avoid allocating memory for the modules array if possible.
// The stack buffer should be large enough for most processes.
HMODULE modules_stack[1024];
std::unique_ptr<HMODULE[]> modules_allocated;
HMODULE* modules = modules_stack;

DWORD needed_bytes = 0;
uintptr_t addr_base = 0;

HANDLE process_handle = GetCurrentProcess();
bool enum_process_result = EnumProcessModules(process_handle, modules, sizeof(modules), &needed_bytes);

// Check if the error is because the buffer is too small.
if (!enum_process_result && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
modules_allocated.reset(new HMODULE[needed_bytes / sizeof(HMODULE)]);
modules = modules_allocated.get();
enum_process_result = EnumProcessModules(process_handle, modules, needed_bytes, &needed_bytes);
}

if (enum_process_result) {
for (std::size_t i = 0; i < (needed_bytes / sizeof(HMODULE)); ++i) {
MODULEINFO module_info;

// Get the module name
if (GetModuleInformation(process_handle, modules[i], &module_info, sizeof(module_info))
&& module_info.lpBaseOfDll <= addr && addr < LPBYTE(module_info.lpBaseOfDll) + module_info.SizeOfImage) {
// Module contains the address
addr_base = reinterpret_cast<std::uintptr_t>(module_info.lpBaseOfDll);
break;
}
}
}

CloseHandle(process_handle);

return addr_base;
}

}}} // namespace boost::stacktrace::detail

#endif // BOOST_STACKTRACE_DETAIL_ADDR_BASE_MSVC_HPP
10 changes: 10 additions & 0 deletions include/boost/stacktrace/detail/frame_msvc.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
#include <boost/stacktrace/detail/to_dec_array.hpp>
#include <boost/stacktrace/detail/to_hex_array.hpp>

#ifndef BOOST_STACKTRACE_DISABLE_OFFSET_ADDR_BASE
#include <boost/stacktrace/detail/addr_base_msvc.hpp>
#endif

#ifdef WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
Expand Down Expand Up @@ -391,7 +395,13 @@ public:
if (!name.empty()) {
res += name;
} else {
#ifdef BOOST_STACKTRACE_DISABLE_OFFSET_ADDR_BASE
res += to_hex_array(addr).data();
#else
// Get own base address
const uintptr_t base_addr = get_own_proc_addr_base(addr);
res += to_hex_array(reinterpret_cast<uintptr_t>(addr) - base_addr).data();
#endif
}

std::pair<std::string, std::size_t> source_line = this->get_source_file_line_impl(addr);
Expand Down
6 changes: 6 additions & 0 deletions include/boost/stacktrace/detail/frame_unwind.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <boost/stacktrace/detail/to_hex_array.hpp>
#include <boost/stacktrace/detail/location_from_symbol.hpp>
#include <boost/stacktrace/detail/to_dec_array.hpp>
#include <boost/stacktrace/detail/addr_base.hpp>
#include <boost/core/demangle.hpp>

#include <cstdio>
Expand All @@ -40,7 +41,12 @@ public:
if (!Base::res.empty()) {
Base::res = boost::core::demangle(Base::res.c_str());
} else {
#ifdef BOOST_STACKTRACE_DISABLE_OFFSET_ADDR_BASE
Base::res = to_hex_array(addr).data();
#else
const auto addr_base = boost::stacktrace::detail::get_own_proc_addr_base(addr);
Base::res = to_hex_array(reinterpret_cast<uintptr_t>(addr) - addr_base).data();
#endif
}

if (Base::prepare_source_location(addr)) {
Expand Down
48 changes: 47 additions & 1 deletion test/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include <boost/stacktrace.hpp>
#include <stdexcept>
#include <fstream>
#include <iostream>
#include <sstream>
#include <cctype>
Expand Down Expand Up @@ -263,7 +264,51 @@ void test_stacktrace_limits()
BOOST_TEST_EQ(boost::stacktrace::stacktrace(1, 1).size(), 1);
}

int main() {
std::size_t get_file_size(const char* file_name) {
std::ifstream file(file_name, std::ios::binary | std::ios::ate);
const auto file_size = file.tellg();
BOOST_TEST(file_size > 0);
return static_cast<std::size_t>(file_size);
}

uintptr_t get_address_from_frame(const std::string& frame) {
std::size_t address = 0;
std::string hex_address;
std::size_t pos = frame.find("0x");

if (pos != std::string::npos) {
// Extract the hex address substring
hex_address = frame.substr(pos + 2); // Skip "0x"

// Convert hex string to std::size_t
std::stringstream ss;
ss << std::hex << hex_address;
ss >> address;
}

return address;
}

void test_relative_virtual_address(const char* file_path)
{
const auto frame = to_string(boost::stacktrace::stacktrace(0, 1).as_vector().front());

// Skip the test if the frame does not contain an address
if (frame.find("0x") == std::string::npos) {
return;
}

const auto file_size = get_file_size(file_path);
BOOST_TEST(file_size > 0);

const auto address = get_address_from_frame(frame);
BOOST_TEST(address > 0);

// Verify that the address is within the binary
BOOST_TEST(address <= file_size);
}

int main(const int, const char* argv[]) {
test_deeply_nested_namespaces();
test_frames_string_data_validity();
test_nested<15>();
Expand All @@ -283,6 +328,7 @@ int main() {
test_nested<260>(false);

test_stacktrace_limits();
test_relative_virtual_address(argv[0]);

return boost::report_errors();
}

0 comments on commit b170b28

Please sign in to comment.