Refactor nested namespaces
This commit is contained in:
parent
cc43a23987
commit
fdbc69e18e
@ -4,9 +4,9 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
std::string demangle(const std::string&);
|
std::string demangle(const std::string&);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -8,25 +8,25 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
std::string demangle(const std::string& name) {
|
std::string demangle(const std::string& name) {
|
||||||
int status;
|
int status;
|
||||||
// presumably thread-safe
|
// presumably thread-safe
|
||||||
// it appears safe to pass nullptr for status however the docs don't explicitly say it's safe so I don't
|
// it appears safe to pass nullptr for status however the docs don't explicitly say it's safe so I don't
|
||||||
// want to rely on it
|
// want to rely on it
|
||||||
char* const demangled = abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status);
|
char* const demangled = abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status);
|
||||||
// demangled will always be nullptr on non-zero status, and if __cxa_demangle ever fails for any reason
|
// demangled will always be nullptr on non-zero status, and if __cxa_demangle ever fails for any reason
|
||||||
// we'll just quietly return the mangled name
|
// we'll just quietly return the mangled name
|
||||||
if(demangled) {
|
if(demangled) {
|
||||||
std::string str = demangled;
|
std::string str = demangled;
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
|
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
|
||||||
std::free(demangled);
|
std::free(demangled);
|
||||||
return str;
|
return str;
|
||||||
} else {
|
} else {
|
||||||
return name;
|
return name;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -5,11 +5,11 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
std::string demangle(const std::string& name) {
|
std::string demangle(const std::string& name) {
|
||||||
return name;
|
return name;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -9,10 +9,10 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
CPPTRACE_FORCE_NO_INLINE
|
CPPTRACE_FORCE_NO_INLINE
|
||||||
std::vector<stacktrace_frame> generate_trace(size_t skip);
|
std::vector<stacktrace_frame> generate_trace(size_t skip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -17,77 +17,77 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
struct trace_data {
|
struct trace_data {
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
|
||||||
std::vector<stacktrace_frame>& frames;
|
std::vector<stacktrace_frame>& frames;
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
|
||||||
size_t& skip;
|
size_t& skip;
|
||||||
};
|
};
|
||||||
|
|
||||||
int full_callback(void* data_pointer, uintptr_t address, const char* file, int line, const char* symbol) {
|
int full_callback(void* data_pointer, uintptr_t address, const char* file, int line, const char* symbol) {
|
||||||
trace_data& data = *reinterpret_cast<trace_data*>(data_pointer);
|
trace_data& data = *reinterpret_cast<trace_data*>(data_pointer);
|
||||||
if(data.skip > 0) {
|
if(data.skip > 0) {
|
||||||
data.skip--;
|
data.skip--;
|
||||||
} else if(address == uintptr_t(-1)) {
|
} else if(address == uintptr_t(-1)) {
|
||||||
// sentinel for libbacktrace, stop tracing
|
// sentinel for libbacktrace, stop tracing
|
||||||
return 1;
|
return 1;
|
||||||
} else {
|
} else {
|
||||||
data.frames.push_back({
|
data.frames.push_back({
|
||||||
address,
|
address,
|
||||||
static_cast<std::uint_least32_t>(line),
|
static_cast<std::uint_least32_t>(line),
|
||||||
0,
|
0,
|
||||||
file ? file : "",
|
file ? file : "",
|
||||||
symbol ? symbol : ""
|
symbol ? symbol : ""
|
||||||
});
|
});
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void syminfo_callback(void* data, uintptr_t, const char* symbol, uintptr_t, uintptr_t) {
|
|
||||||
stacktrace_frame& frame = *static_cast<stacktrace_frame*>(data);
|
|
||||||
frame.symbol = symbol ? symbol : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
void error_callback(void*, const char* msg, int errnum) {
|
|
||||||
nonfatal_error(stringf("libbacktrace error: %s, code %d", msg, errnum));
|
|
||||||
}
|
|
||||||
|
|
||||||
backtrace_state* get_backtrace_state() {
|
|
||||||
static std::mutex mutex;
|
|
||||||
const std::lock_guard<std::mutex> lock(mutex);
|
|
||||||
// backtrace_create_state must be called only one time per program
|
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
|
||||||
static backtrace_state* state = nullptr;
|
|
||||||
static bool called = false;
|
|
||||||
if(!called) {
|
|
||||||
state = backtrace_create_state(nullptr, true, error_callback, nullptr);
|
|
||||||
called = true;
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
CPPTRACE_FORCE_NO_INLINE
|
|
||||||
std::vector<stacktrace_frame> generate_trace(size_t skip) {
|
|
||||||
std::vector<stacktrace_frame> frames;
|
|
||||||
skip++; // add one for this call
|
|
||||||
trace_data data { frames, skip };
|
|
||||||
backtrace_full(get_backtrace_state(), 0, full_callback, error_callback, &data);
|
|
||||||
for(auto& frame : frames) {
|
|
||||||
if(frame.symbol.empty()) {
|
|
||||||
// fallback, try to at least recover the symbol name with backtrace_syminfo
|
|
||||||
backtrace_syminfo(
|
|
||||||
get_backtrace_state(),
|
|
||||||
frame.address,
|
|
||||||
syminfo_callback,
|
|
||||||
error_callback,
|
|
||||||
&frame
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return frames;
|
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void syminfo_callback(void* data, uintptr_t, const char* symbol, uintptr_t, uintptr_t) {
|
||||||
|
stacktrace_frame& frame = *static_cast<stacktrace_frame*>(data);
|
||||||
|
frame.symbol = symbol ? symbol : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
void error_callback(void*, const char* msg, int errnum) {
|
||||||
|
nonfatal_error(stringf("libbacktrace error: %s, code %d", msg, errnum));
|
||||||
|
}
|
||||||
|
|
||||||
|
backtrace_state* get_backtrace_state() {
|
||||||
|
static std::mutex mutex;
|
||||||
|
const std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
// backtrace_create_state must be called only one time per program
|
||||||
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
static backtrace_state* state = nullptr;
|
||||||
|
static bool called = false;
|
||||||
|
if(!called) {
|
||||||
|
state = backtrace_create_state(nullptr, true, error_callback, nullptr);
|
||||||
|
called = true;
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
CPPTRACE_FORCE_NO_INLINE
|
||||||
|
std::vector<stacktrace_frame> generate_trace(size_t skip) {
|
||||||
|
std::vector<stacktrace_frame> frames;
|
||||||
|
skip++; // add one for this call
|
||||||
|
trace_data data { frames, skip };
|
||||||
|
backtrace_full(get_backtrace_state(), 0, full_callback, error_callback, &data);
|
||||||
|
for(auto& frame : frames) {
|
||||||
|
if(frame.symbol.empty()) {
|
||||||
|
// fallback, try to at least recover the symbol name with backtrace_syminfo
|
||||||
|
backtrace_syminfo(
|
||||||
|
get_backtrace_state(),
|
||||||
|
frame.address,
|
||||||
|
syminfo_callback,
|
||||||
|
error_callback,
|
||||||
|
&frame
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return frames;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -3,28 +3,29 @@
|
|||||||
#include <cpptrace/cpptrace.hpp>
|
#include <cpptrace/cpptrace.hpp>
|
||||||
#include "full_trace.hpp"
|
#include "full_trace.hpp"
|
||||||
#include "../platform/common.hpp"
|
#include "../platform/common.hpp"
|
||||||
|
#include "../platform/utils.hpp"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <stacktrace>
|
#include <stacktrace>
|
||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
CPPTRACE_FORCE_NO_INLINE
|
CPPTRACE_FORCE_NO_INLINE
|
||||||
std::vector<stacktrace_frame> generate_trace(size_t skip) {
|
std::vector<stacktrace_frame> generate_trace(size_t skip) {
|
||||||
std::vector<stacktrace_frame> frames;
|
std::vector<stacktrace_frame> frames;
|
||||||
std::stacktrace trace = std::stacktrace::current(skip + 1);
|
std::stacktrace trace = std::stacktrace::current(skip + 1);
|
||||||
for(const auto entry : trace) {
|
for(const auto entry : trace) {
|
||||||
frames.push_back({
|
frames.push_back({
|
||||||
entry.native_handle(),
|
entry.native_handle(),
|
||||||
entry.source_line(),
|
entry.source_line(),
|
||||||
0,
|
0,
|
||||||
entry.source_file(),
|
entry.source_file(),
|
||||||
entry.description()
|
entry.description()
|
||||||
});
|
});
|
||||||
}
|
|
||||||
return frames;
|
|
||||||
}
|
}
|
||||||
|
return frames;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -7,39 +7,39 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE
|
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE
|
||||||
namespace libbacktrace {
|
namespace libbacktrace {
|
||||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
|
|
||||||
namespace libdwarf {
|
|
||||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDL
|
|
||||||
namespace libdl {
|
|
||||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE
|
|
||||||
namespace addr2line {
|
|
||||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP
|
|
||||||
namespace dbghelp {
|
|
||||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_NOTHING
|
|
||||||
namespace nothing {
|
|
||||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames);
|
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
|
||||||
|
namespace libdwarf {
|
||||||
|
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDL
|
||||||
|
namespace libdl {
|
||||||
|
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE
|
||||||
|
namespace addr2line {
|
||||||
|
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP
|
||||||
|
namespace dbghelp {
|
||||||
|
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef CPPTRACE_GET_SYMBOLS_WITH_NOTHING
|
||||||
|
namespace nothing {
|
||||||
|
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -1,55 +1,56 @@
|
|||||||
#include "symbols.hpp"
|
#include "symbols.hpp"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "../platform/object.hpp"
|
#include "../platform/object.hpp"
|
||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
void apply_trace(
|
void apply_trace(
|
||||||
std::vector<stacktrace_frame>& result,
|
std::vector<stacktrace_frame>& result,
|
||||||
std::vector<stacktrace_frame>&& trace
|
std::vector<stacktrace_frame>&& trace
|
||||||
) {
|
) {
|
||||||
for(std::size_t i = 0; i < result.size(); i++) {
|
for(std::size_t i = 0; i < result.size(); i++) {
|
||||||
if(result[i].address == 0) {
|
if(result[i].address == 0) {
|
||||||
result[i].address = trace[i].address;
|
result[i].address = trace[i].address;
|
||||||
}
|
}
|
||||||
if(result[i].line == 0) {
|
if(result[i].line == 0) {
|
||||||
result[i].line = trace[i].line;
|
result[i].line = trace[i].line;
|
||||||
}
|
}
|
||||||
if(result[i].col == 0) {
|
if(result[i].col == 0) {
|
||||||
result[i].col = trace[i].col;
|
result[i].col = trace[i].col;
|
||||||
}
|
}
|
||||||
if(result[i].filename.empty()) {
|
if(result[i].filename.empty()) {
|
||||||
result[i].filename = std::move(trace[i].filename);
|
result[i].filename = std::move(trace[i].filename);
|
||||||
}
|
}
|
||||||
if(result[i].symbol.empty()) {
|
if(result[i].symbol.empty()) {
|
||||||
result[i].symbol = std::move(trace[i].symbol);
|
result[i].symbol = std::move(trace[i].symbol);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
|
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
|
||||||
std::vector<stacktrace_frame> trace(frames.size());
|
std::vector<stacktrace_frame> trace(frames.size());
|
||||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDL
|
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDL
|
||||||
apply_trace(trace, libdl::resolve_frames(frames));
|
apply_trace(trace, libdl::resolve_frames(frames));
|
||||||
#endif
|
#endif
|
||||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
|
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
|
||||||
apply_trace(trace, libdwarf::resolve_frames(frames));
|
apply_trace(trace, libdwarf::resolve_frames(frames));
|
||||||
#endif
|
#endif
|
||||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP
|
#ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP
|
||||||
apply_trace(trace, dbghelp::resolve_frames(frames));
|
apply_trace(trace, dbghelp::resolve_frames(frames));
|
||||||
#endif
|
#endif
|
||||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE
|
#ifdef CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE
|
||||||
apply_trace(trace, addr2line::resolve_frames(frames));
|
apply_trace(trace, addr2line::resolve_frames(frames));
|
||||||
#endif
|
#endif
|
||||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE
|
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE
|
||||||
apply_trace(trace, libbacktrace::resolve_frames(frames));
|
apply_trace(trace, libbacktrace::resolve_frames(frames));
|
||||||
#endif
|
#endif
|
||||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_NOTHING
|
#ifdef CPPTRACE_GET_SYMBOLS_WITH_NOTHING
|
||||||
apply_trace(trace, nothing::resolve_frames(frames));
|
apply_trace(trace, nothing::resolve_frames(frames));
|
||||||
#endif
|
#endif
|
||||||
return trace;
|
return trace;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -24,329 +24,329 @@
|
|||||||
#include "../platform/object.hpp"
|
#include "../platform/object.hpp"
|
||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
namespace addr2line {
|
namespace addr2line {
|
||||||
#if IS_LINUX || IS_APPLE
|
#if IS_LINUX || IS_APPLE
|
||||||
bool has_addr2line() {
|
bool has_addr2line() {
|
||||||
static std::mutex mutex;
|
static std::mutex mutex;
|
||||||
static bool has_addr2line = false;
|
static bool has_addr2line = false;
|
||||||
static bool checked = false;
|
static bool checked = false;
|
||||||
std::lock_guard<std::mutex> lock(mutex);
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
if(!checked) {
|
if(!checked) {
|
||||||
checked = true;
|
checked = true;
|
||||||
// Detects if addr2line exists by trying to invoke addr2line --help
|
// Detects if addr2line exists by trying to invoke addr2line --help
|
||||||
constexpr int magic = 42;
|
constexpr int magic = 42;
|
||||||
// NOLINTNEXTLINE(misc-include-cleaner)
|
// NOLINTNEXTLINE(misc-include-cleaner)
|
||||||
const pid_t pid = fork();
|
const pid_t pid = fork();
|
||||||
if(pid == -1) { return false; }
|
if(pid == -1) { return false; }
|
||||||
if(pid == 0) { // child
|
if(pid == 0) { // child
|
||||||
close(STDOUT_FILENO);
|
close(STDOUT_FILENO);
|
||||||
close(STDERR_FILENO); // atos --help writes to stderr
|
close(STDERR_FILENO); // atos --help writes to stderr
|
||||||
#ifdef CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH
|
|
||||||
#if !IS_APPLE
|
|
||||||
execlp("addr2line", "addr2line", "--help", nullptr);
|
|
||||||
#else
|
|
||||||
execlp("atos", "atos", "--help", nullptr);
|
|
||||||
#endif
|
|
||||||
#else
|
|
||||||
#ifndef CPPTRACE_ADDR2LINE_PATH
|
|
||||||
#error "CPPTRACE_ADDR2LINE_PATH must be defined if CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH is not"
|
|
||||||
#endif
|
|
||||||
execl(CPPTRACE_ADDR2LINE_PATH, CPPTRACE_ADDR2LINE_PATH, "--help", nullptr);
|
|
||||||
#endif
|
|
||||||
_exit(magic);
|
|
||||||
}
|
|
||||||
int status;
|
|
||||||
waitpid(pid, &status, 0);
|
|
||||||
// NOLINTNEXTLINE(misc-include-cleaner)
|
|
||||||
has_addr2line = WEXITSTATUS(status) == 0;
|
|
||||||
}
|
|
||||||
return has_addr2line;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct pipe_t {
|
|
||||||
union {
|
|
||||||
struct {
|
|
||||||
int read_end;
|
|
||||||
int write_end;
|
|
||||||
};
|
|
||||||
int data[2];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
static_assert(sizeof(pipe_t) == 2 * sizeof(int), "Unexpected struct packing");
|
|
||||||
|
|
||||||
std::string resolve_addresses(const std::string& addresses, const std::string& executable) {
|
|
||||||
pipe_t output_pipe;
|
|
||||||
pipe_t input_pipe;
|
|
||||||
CPPTRACE_VERIFY(pipe(output_pipe.data) == 0);
|
|
||||||
CPPTRACE_VERIFY(pipe(input_pipe.data) == 0);
|
|
||||||
// NOLINTNEXTLINE(misc-include-cleaner)
|
|
||||||
const pid_t pid = fork();
|
|
||||||
if(pid == -1) { return ""; } // error? TODO: Diagnostic
|
|
||||||
if(pid == 0) { // child
|
|
||||||
dup2(output_pipe.write_end, STDOUT_FILENO);
|
|
||||||
dup2(input_pipe.read_end, STDIN_FILENO);
|
|
||||||
close(output_pipe.read_end);
|
|
||||||
close(output_pipe.write_end);
|
|
||||||
close(input_pipe.read_end);
|
|
||||||
close(input_pipe.write_end);
|
|
||||||
close(STDERR_FILENO); // TODO: Might be worth conditionally enabling or piping
|
|
||||||
#ifdef CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH
|
|
||||||
#if !IS_APPLE
|
|
||||||
execlp("addr2line", "addr2line", "-e", executable.c_str(), "-f", "-C", "-p", nullptr);
|
|
||||||
#else
|
|
||||||
execlp("atos", "atos", "-o", executable.c_str(), "-fullPath", nullptr);
|
|
||||||
#endif
|
|
||||||
#else
|
|
||||||
#ifndef CPPTRACE_ADDR2LINE_PATH
|
|
||||||
#error "CPPTRACE_ADDR2LINE_PATH must be defined if CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH is not"
|
|
||||||
#endif
|
|
||||||
#if !IS_APPLE
|
|
||||||
execl(
|
|
||||||
CPPTRACE_ADDR2LINE_PATH,
|
|
||||||
CPPTRACE_ADDR2LINE_PATH,
|
|
||||||
"-e",
|
|
||||||
executable.c_str(),
|
|
||||||
"-f",
|
|
||||||
"-C",
|
|
||||||
"-p",
|
|
||||||
nullptr
|
|
||||||
);
|
|
||||||
#else
|
|
||||||
execl(
|
|
||||||
CPPTRACE_ADDR2LINE_PATH,
|
|
||||||
CPPTRACE_ADDR2LINE_PATH,
|
|
||||||
"-o", executable.c_str(),
|
|
||||||
"-fullPath",
|
|
||||||
nullptr
|
|
||||||
);
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
_exit(1); // TODO: Diagnostic?
|
|
||||||
}
|
|
||||||
CPPTRACE_VERIFY(write(input_pipe.write_end, addresses.data(), addresses.size()) != -1);
|
|
||||||
close(input_pipe.read_end);
|
|
||||||
close(input_pipe.write_end);
|
|
||||||
close(output_pipe.write_end);
|
|
||||||
std::string output;
|
|
||||||
constexpr int buffer_size = 4096;
|
|
||||||
char buffer[buffer_size];
|
|
||||||
size_t count = 0;
|
|
||||||
while((count = read(output_pipe.read_end, buffer, buffer_size)) > 0) {
|
|
||||||
output.insert(output.end(), buffer, buffer + count);
|
|
||||||
}
|
|
||||||
// TODO: check status from addr2line?
|
|
||||||
waitpid(pid, nullptr, 0);
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
#elif IS_WINDOWS
|
|
||||||
bool has_addr2line() {
|
|
||||||
static std::mutex mutex;
|
|
||||||
static bool has_addr2line = false;
|
|
||||||
static bool checked = false;
|
|
||||||
std::lock_guard<std::mutex> lock(mutex);
|
|
||||||
if(!checked) {
|
|
||||||
// TODO: Popen is a hack. Implement properly with CreateProcess and pipes later.
|
|
||||||
checked = true;
|
|
||||||
#ifdef CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH
|
|
||||||
FILE* p = popen("addr2line --version", "r");
|
|
||||||
#else
|
|
||||||
#ifndef CPPTRACE_ADDR2LINE_PATH
|
|
||||||
#error "CPPTRACE_ADDR2LINE_PATH must be defined if CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH is not"
|
|
||||||
#endif
|
|
||||||
FILE* p = popen(CPPTRACE_ADDR2LINE_PATH " --version", "r");
|
|
||||||
#endif
|
|
||||||
if(p) {
|
|
||||||
has_addr2line = pclose(p) == 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return has_addr2line;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string resolve_addresses(const std::string& addresses, const std::string& executable) {
|
|
||||||
// TODO: Popen is a hack. Implement properly with CreateProcess and pipes later.
|
|
||||||
///fprintf(stderr, ("addr2line -e " + executable + " -fCp " + addresses + "\n").c_str());
|
|
||||||
#ifdef CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH
|
#ifdef CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH
|
||||||
FILE* p = popen(("addr2line -e \"" + executable + "\" -fCp " + addresses).c_str(), "r");
|
#if !IS_APPLE
|
||||||
|
execlp("addr2line", "addr2line", "--help", nullptr);
|
||||||
|
#else
|
||||||
|
execlp("atos", "atos", "--help", nullptr);
|
||||||
|
#endif
|
||||||
#else
|
#else
|
||||||
#ifndef CPPTRACE_ADDR2LINE_PATH
|
#ifndef CPPTRACE_ADDR2LINE_PATH
|
||||||
#error "CPPTRACE_ADDR2LINE_PATH must be defined if CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH is not"
|
#error "CPPTRACE_ADDR2LINE_PATH must be defined if CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH is not"
|
||||||
#endif
|
#endif
|
||||||
FILE* p = popen(
|
execl(CPPTRACE_ADDR2LINE_PATH, CPPTRACE_ADDR2LINE_PATH, "--help", nullptr);
|
||||||
(CPPTRACE_ADDR2LINE_PATH " -e \"" + executable + "\" -fCp " + addresses).c_str(),
|
|
||||||
"r"
|
|
||||||
);
|
|
||||||
#endif
|
#endif
|
||||||
std::string output;
|
_exit(magic);
|
||||||
constexpr int buffer_size = 4096;
|
|
||||||
char buffer[buffer_size];
|
|
||||||
size_t count = 0;
|
|
||||||
while((count = fread(buffer, 1, buffer_size, p)) > 0) {
|
|
||||||
output.insert(output.end(), buffer, buffer + count);
|
|
||||||
}
|
|
||||||
pclose(p);
|
|
||||||
///fprintf(stderr, "%s\n", output.c_str());
|
|
||||||
return output;
|
|
||||||
}
|
}
|
||||||
|
int status;
|
||||||
|
waitpid(pid, &status, 0);
|
||||||
|
// NOLINTNEXTLINE(misc-include-cleaner)
|
||||||
|
has_addr2line = WEXITSTATUS(status) == 0;
|
||||||
|
}
|
||||||
|
return has_addr2line;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct pipe_t {
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
int read_end;
|
||||||
|
int write_end;
|
||||||
|
};
|
||||||
|
int data[2];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(pipe_t) == 2 * sizeof(int), "Unexpected struct packing");
|
||||||
|
|
||||||
|
std::string resolve_addresses(const std::string& addresses, const std::string& executable) {
|
||||||
|
pipe_t output_pipe;
|
||||||
|
pipe_t input_pipe;
|
||||||
|
CPPTRACE_VERIFY(pipe(output_pipe.data) == 0);
|
||||||
|
CPPTRACE_VERIFY(pipe(input_pipe.data) == 0);
|
||||||
|
// NOLINTNEXTLINE(misc-include-cleaner)
|
||||||
|
const pid_t pid = fork();
|
||||||
|
if(pid == -1) { return ""; } // error? TODO: Diagnostic
|
||||||
|
if(pid == 0) { // child
|
||||||
|
dup2(output_pipe.write_end, STDOUT_FILENO);
|
||||||
|
dup2(input_pipe.read_end, STDIN_FILENO);
|
||||||
|
close(output_pipe.read_end);
|
||||||
|
close(output_pipe.write_end);
|
||||||
|
close(input_pipe.read_end);
|
||||||
|
close(input_pipe.write_end);
|
||||||
|
close(STDERR_FILENO); // TODO: Might be worth conditionally enabling or piping
|
||||||
|
#ifdef CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH
|
||||||
|
#if !IS_APPLE
|
||||||
|
execlp("addr2line", "addr2line", "-e", executable.c_str(), "-f", "-C", "-p", nullptr);
|
||||||
|
#else
|
||||||
|
execlp("atos", "atos", "-o", executable.c_str(), "-fullPath", nullptr);
|
||||||
#endif
|
#endif
|
||||||
|
#else
|
||||||
using target_vec = std::vector<std::pair<std::string, std::reference_wrapper<stacktrace_frame>>>;
|
#ifndef CPPTRACE_ADDR2LINE_PATH
|
||||||
|
#error "CPPTRACE_ADDR2LINE_PATH must be defined if CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH is not"
|
||||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
#endif
|
||||||
std::unordered_map<std::string, target_vec> get_addr2line_targets(
|
#if !IS_APPLE
|
||||||
const std::vector<dlframe>& dlframes,
|
execl(
|
||||||
std::vector<stacktrace_frame>& trace
|
CPPTRACE_ADDR2LINE_PATH,
|
||||||
) {
|
CPPTRACE_ADDR2LINE_PATH,
|
||||||
std::unordered_map<std::string, target_vec> entries;
|
"-e",
|
||||||
for(std::size_t i = 0; i < dlframes.size(); i++) {
|
executable.c_str(),
|
||||||
const auto& entry = dlframes[i];
|
"-f",
|
||||||
// If libdl fails to find the shared object for a frame, the path will be empty. I've observed this
|
"-C",
|
||||||
// on macos when looking up the shared object containing `start`.
|
"-p",
|
||||||
if(!entry.obj_path.empty()) {
|
nullptr
|
||||||
///fprintf(
|
);
|
||||||
/// stderr,
|
#else
|
||||||
/// "%s %s\n",
|
execl(
|
||||||
/// to_hex(entry.raw_address).c_str(),
|
CPPTRACE_ADDR2LINE_PATH,
|
||||||
/// to_hex(entry.raw_address - entry.obj_base + base).c_str()
|
CPPTRACE_ADDR2LINE_PATH,
|
||||||
///);
|
"-o", executable.c_str(),
|
||||||
try {
|
"-fullPath",
|
||||||
entries[entry.obj_path].emplace_back(
|
nullptr
|
||||||
to_hex(entry.obj_address),
|
);
|
||||||
trace[i]
|
#endif
|
||||||
);
|
#endif
|
||||||
} catch(file_error&) {
|
_exit(1); // TODO: Diagnostic?
|
||||||
//
|
}
|
||||||
} catch(...) {
|
CPPTRACE_VERIFY(write(input_pipe.write_end, addresses.data(), addresses.size()) != -1);
|
||||||
throw;
|
close(input_pipe.read_end);
|
||||||
}
|
close(input_pipe.write_end);
|
||||||
// Set what is known for now, and resolutions from addr2line should overwrite
|
close(output_pipe.write_end);
|
||||||
trace[i].filename = entry.obj_path;
|
std::string output;
|
||||||
trace[i].symbol = entry.symbol;
|
constexpr int buffer_size = 4096;
|
||||||
}
|
char buffer[buffer_size];
|
||||||
}
|
size_t count = 0;
|
||||||
return entries;
|
while((count = read(output_pipe.read_end, buffer, buffer_size)) > 0) {
|
||||||
}
|
output.insert(output.end(), buffer, buffer + count);
|
||||||
|
}
|
||||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
// TODO: check status from addr2line?
|
||||||
void update_trace(const std::string& line, size_t entry_index, const target_vec& entries_vec) {
|
waitpid(pid, nullptr, 0);
|
||||||
#if !IS_APPLE
|
return output;
|
||||||
// Result will be of the form "<symbol> at path:line"
|
}
|
||||||
// The path may be ?? if addr2line cannot resolve, line may be ?
|
#elif IS_WINDOWS
|
||||||
// Edge cases:
|
bool has_addr2line() {
|
||||||
// ?? ??:0
|
static std::mutex mutex;
|
||||||
// symbol :?
|
static bool has_addr2line = false;
|
||||||
const std::size_t at_location = line.find(" at ");
|
static bool checked = false;
|
||||||
std::size_t symbol_end;
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
std::size_t filename_start;
|
if(!checked) {
|
||||||
if(at_location != std::string::npos) {
|
// TODO: Popen is a hack. Implement properly with CreateProcess and pipes later.
|
||||||
symbol_end = at_location;
|
checked = true;
|
||||||
filename_start = at_location + 4;
|
#ifdef CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH
|
||||||
} else {
|
FILE* p = popen("addr2line --version", "r");
|
||||||
CPPTRACE_VERIFY(line.find("?? ") == 0, "Unexpected edge case while processing addr2line output");
|
#else
|
||||||
symbol_end = 2;
|
#ifndef CPPTRACE_ADDR2LINE_PATH
|
||||||
filename_start = 3;
|
#error "CPPTRACE_ADDR2LINE_PATH must be defined if CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH is not"
|
||||||
}
|
#endif
|
||||||
auto symbol = line.substr(0, symbol_end);
|
FILE* p = popen(CPPTRACE_ADDR2LINE_PATH " --version", "r");
|
||||||
auto colon = line.rfind(':');
|
#endif
|
||||||
CPPTRACE_VERIFY(colon != std::string::npos);
|
if(p) {
|
||||||
CPPTRACE_VERIFY(colon >= filename_start); // :? to deal with "symbol :?" edge case
|
has_addr2line = pclose(p) == 0;
|
||||||
auto filename = line.substr(filename_start, colon - filename_start);
|
|
||||||
auto line_number = line.substr(colon + 1);
|
|
||||||
if(line_number != "?") {
|
|
||||||
entries_vec[entry_index].second.get().line = std::stoi(line_number);
|
|
||||||
}
|
|
||||||
if(!filename.empty() && filename != "??") {
|
|
||||||
entries_vec[entry_index].second.get().filename = filename;
|
|
||||||
}
|
|
||||||
if(!symbol.empty()) {
|
|
||||||
entries_vec[entry_index].second.get().symbol = symbol;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
// Result will be of the form "<symbol> (in <object name>) (file:line)"
|
|
||||||
// The symbol may just be the given address if atos can't resolve it
|
|
||||||
// Examples:
|
|
||||||
// trace() (in demo) (demo.cpp:8)
|
|
||||||
// 0x100003b70 (in demo)
|
|
||||||
// 0xffffffffffffffff
|
|
||||||
// foo (in bar) + 14
|
|
||||||
// I'm making some assumptions here. Support may need to be improved later. This is tricky output to
|
|
||||||
// parse.
|
|
||||||
const std::size_t in_location = line.find(" (in ");
|
|
||||||
if(in_location == std::string::npos) {
|
|
||||||
// presumably the 0xffffffffffffffff case
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const std::size_t symbol_end = in_location;
|
|
||||||
entries_vec[entry_index].second.get().symbol = line.substr(0, symbol_end);
|
|
||||||
const std::size_t obj_end = line.find(")", in_location);
|
|
||||||
CPPTRACE_VERIFY(
|
|
||||||
obj_end != std::string::npos,
|
|
||||||
"Unexpected edge case while processing addr2line/atos output"
|
|
||||||
);
|
|
||||||
const std::size_t filename_start = line.find(") (", obj_end);
|
|
||||||
if(filename_start == std::string::npos) {
|
|
||||||
// presumably something like 0x100003b70 (in demo) or foo (in bar) + 14
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const std::size_t filename_end = line.find(":", filename_start);
|
|
||||||
CPPTRACE_VERIFY(
|
|
||||||
filename_end != std::string::npos,
|
|
||||||
"Unexpected edge case while processing addr2line/atos output"
|
|
||||||
);
|
|
||||||
entries_vec[entry_index].second.get().filename = line.substr(
|
|
||||||
filename_start + 3,
|
|
||||||
filename_end - filename_start - 3
|
|
||||||
);
|
|
||||||
const std::size_t line_start = filename_end + 1;
|
|
||||||
const std::size_t line_end = line.find(")", filename_end);
|
|
||||||
CPPTRACE_VERIFY(
|
|
||||||
line_end == line.size() - 1,
|
|
||||||
"Unexpected edge case while processing addr2line/atos output"
|
|
||||||
);
|
|
||||||
entries_vec[entry_index].second.get().line = std::stoi(line.substr(line_start, line_end - line_start));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
|
||||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
|
|
||||||
// TODO: Refactor better
|
|
||||||
std::vector<stacktrace_frame> trace(frames.size(), stacktrace_frame { 0, 0, 0, "", "" });
|
|
||||||
for(size_t i = 0; i < frames.size(); i++) {
|
|
||||||
trace[i].address = reinterpret_cast<uintptr_t>(frames[i]);
|
|
||||||
}
|
|
||||||
if(has_addr2line()) {
|
|
||||||
const std::vector<dlframe> dlframes = get_frames_object_info(frames);
|
|
||||||
const auto entries = get_addr2line_targets(dlframes, trace);
|
|
||||||
for(const auto& entry : entries) {
|
|
||||||
const auto& object_name = entry.first;
|
|
||||||
const auto& entries_vec = entry.second;
|
|
||||||
// You may ask why it'd ever happen that there could be an empty entries_vec array, if there're
|
|
||||||
// no addresses why would get_addr2line_targets do anything? The reason is because if things in
|
|
||||||
// get_addr2line_targets fail it will silently skip. This is partly an optimization but also an
|
|
||||||
// assertion below will fail if addr2line is given an empty input.
|
|
||||||
if(entries_vec.empty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
std::string address_input;
|
|
||||||
for(const auto& pair : entries_vec) {
|
|
||||||
address_input += pair.first;
|
|
||||||
#if !IS_WINDOWS
|
|
||||||
address_input += '\n';
|
|
||||||
#else
|
|
||||||
address_input += ' ';
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
auto output = split(trim(resolve_addresses(address_input, object_name)), "\n");
|
|
||||||
CPPTRACE_VERIFY(output.size() == entries_vec.size());
|
|
||||||
for(size_t i = 0; i < output.size(); i++) {
|
|
||||||
update_trace(output[i], i, entries_vec);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return trace;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return has_addr2line;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string resolve_addresses(const std::string& addresses, const std::string& executable) {
|
||||||
|
// TODO: Popen is a hack. Implement properly with CreateProcess and pipes later.
|
||||||
|
///fprintf(stderr, ("addr2line -e " + executable + " -fCp " + addresses + "\n").c_str());
|
||||||
|
#ifdef CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH
|
||||||
|
FILE* p = popen(("addr2line -e \"" + executable + "\" -fCp " + addresses).c_str(), "r");
|
||||||
|
#else
|
||||||
|
#ifndef CPPTRACE_ADDR2LINE_PATH
|
||||||
|
#error "CPPTRACE_ADDR2LINE_PATH must be defined if CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH is not"
|
||||||
|
#endif
|
||||||
|
FILE* p = popen(
|
||||||
|
(CPPTRACE_ADDR2LINE_PATH " -e \"" + executable + "\" -fCp " + addresses).c_str(),
|
||||||
|
"r"
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
std::string output;
|
||||||
|
constexpr int buffer_size = 4096;
|
||||||
|
char buffer[buffer_size];
|
||||||
|
size_t count = 0;
|
||||||
|
while((count = fread(buffer, 1, buffer_size, p)) > 0) {
|
||||||
|
output.insert(output.end(), buffer, buffer + count);
|
||||||
|
}
|
||||||
|
pclose(p);
|
||||||
|
///fprintf(stderr, "%s\n", output.c_str());
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using target_vec = std::vector<std::pair<std::string, std::reference_wrapper<stacktrace_frame>>>;
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||||
|
std::unordered_map<std::string, target_vec> get_addr2line_targets(
|
||||||
|
const std::vector<dlframe>& dlframes,
|
||||||
|
std::vector<stacktrace_frame>& trace
|
||||||
|
) {
|
||||||
|
std::unordered_map<std::string, target_vec> entries;
|
||||||
|
for(std::size_t i = 0; i < dlframes.size(); i++) {
|
||||||
|
const auto& entry = dlframes[i];
|
||||||
|
// If libdl fails to find the shared object for a frame, the path will be empty. I've observed this
|
||||||
|
// on macos when looking up the shared object containing `start`.
|
||||||
|
if(!entry.obj_path.empty()) {
|
||||||
|
///fprintf(
|
||||||
|
/// stderr,
|
||||||
|
/// "%s %s\n",
|
||||||
|
/// to_hex(entry.raw_address).c_str(),
|
||||||
|
/// to_hex(entry.raw_address - entry.obj_base + base).c_str()
|
||||||
|
///);
|
||||||
|
try {
|
||||||
|
entries[entry.obj_path].emplace_back(
|
||||||
|
to_hex(entry.obj_address),
|
||||||
|
trace[i]
|
||||||
|
);
|
||||||
|
} catch(file_error&) {
|
||||||
|
//
|
||||||
|
} catch(...) {
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
// Set what is known for now, and resolutions from addr2line should overwrite
|
||||||
|
trace[i].filename = entry.obj_path;
|
||||||
|
trace[i].symbol = entry.symbol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||||
|
void update_trace(const std::string& line, size_t entry_index, const target_vec& entries_vec) {
|
||||||
|
#if !IS_APPLE
|
||||||
|
// Result will be of the form "<symbol> at path:line"
|
||||||
|
// The path may be ?? if addr2line cannot resolve, line may be ?
|
||||||
|
// Edge cases:
|
||||||
|
// ?? ??:0
|
||||||
|
// symbol :?
|
||||||
|
const std::size_t at_location = line.find(" at ");
|
||||||
|
std::size_t symbol_end;
|
||||||
|
std::size_t filename_start;
|
||||||
|
if(at_location != std::string::npos) {
|
||||||
|
symbol_end = at_location;
|
||||||
|
filename_start = at_location + 4;
|
||||||
|
} else {
|
||||||
|
CPPTRACE_VERIFY(line.find("?? ") == 0, "Unexpected edge case while processing addr2line output");
|
||||||
|
symbol_end = 2;
|
||||||
|
filename_start = 3;
|
||||||
|
}
|
||||||
|
auto symbol = line.substr(0, symbol_end);
|
||||||
|
auto colon = line.rfind(':');
|
||||||
|
CPPTRACE_VERIFY(colon != std::string::npos);
|
||||||
|
CPPTRACE_VERIFY(colon >= filename_start); // :? to deal with "symbol :?" edge case
|
||||||
|
auto filename = line.substr(filename_start, colon - filename_start);
|
||||||
|
auto line_number = line.substr(colon + 1);
|
||||||
|
if(line_number != "?") {
|
||||||
|
entries_vec[entry_index].second.get().line = std::stoi(line_number);
|
||||||
|
}
|
||||||
|
if(!filename.empty() && filename != "??") {
|
||||||
|
entries_vec[entry_index].second.get().filename = filename;
|
||||||
|
}
|
||||||
|
if(!symbol.empty()) {
|
||||||
|
entries_vec[entry_index].second.get().symbol = symbol;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// Result will be of the form "<symbol> (in <object name>) (file:line)"
|
||||||
|
// The symbol may just be the given address if atos can't resolve it
|
||||||
|
// Examples:
|
||||||
|
// trace() (in demo) (demo.cpp:8)
|
||||||
|
// 0x100003b70 (in demo)
|
||||||
|
// 0xffffffffffffffff
|
||||||
|
// foo (in bar) + 14
|
||||||
|
// I'm making some assumptions here. Support may need to be improved later. This is tricky output to
|
||||||
|
// parse.
|
||||||
|
const std::size_t in_location = line.find(" (in ");
|
||||||
|
if(in_location == std::string::npos) {
|
||||||
|
// presumably the 0xffffffffffffffff case
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::size_t symbol_end = in_location;
|
||||||
|
entries_vec[entry_index].second.get().symbol = line.substr(0, symbol_end);
|
||||||
|
const std::size_t obj_end = line.find(")", in_location);
|
||||||
|
CPPTRACE_VERIFY(
|
||||||
|
obj_end != std::string::npos,
|
||||||
|
"Unexpected edge case while processing addr2line/atos output"
|
||||||
|
);
|
||||||
|
const std::size_t filename_start = line.find(") (", obj_end);
|
||||||
|
if(filename_start == std::string::npos) {
|
||||||
|
// presumably something like 0x100003b70 (in demo) or foo (in bar) + 14
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::size_t filename_end = line.find(":", filename_start);
|
||||||
|
CPPTRACE_VERIFY(
|
||||||
|
filename_end != std::string::npos,
|
||||||
|
"Unexpected edge case while processing addr2line/atos output"
|
||||||
|
);
|
||||||
|
entries_vec[entry_index].second.get().filename = line.substr(
|
||||||
|
filename_start + 3,
|
||||||
|
filename_end - filename_start - 3
|
||||||
|
);
|
||||||
|
const std::size_t line_start = filename_end + 1;
|
||||||
|
const std::size_t line_end = line.find(")", filename_end);
|
||||||
|
CPPTRACE_VERIFY(
|
||||||
|
line_end == line.size() - 1,
|
||||||
|
"Unexpected edge case while processing addr2line/atos output"
|
||||||
|
);
|
||||||
|
entries_vec[entry_index].second.get().line = std::stoi(line.substr(line_start, line_end - line_start));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||||
|
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
|
||||||
|
// TODO: Refactor better
|
||||||
|
std::vector<stacktrace_frame> trace(frames.size(), stacktrace_frame { 0, 0, 0, "", "" });
|
||||||
|
for(size_t i = 0; i < frames.size(); i++) {
|
||||||
|
trace[i].address = reinterpret_cast<uintptr_t>(frames[i]);
|
||||||
|
}
|
||||||
|
if(has_addr2line()) {
|
||||||
|
const std::vector<dlframe> dlframes = get_frames_object_info(frames);
|
||||||
|
const auto entries = get_addr2line_targets(dlframes, trace);
|
||||||
|
for(const auto& entry : entries) {
|
||||||
|
const auto& object_name = entry.first;
|
||||||
|
const auto& entries_vec = entry.second;
|
||||||
|
// You may ask why it'd ever happen that there could be an empty entries_vec array, if there're
|
||||||
|
// no addresses why would get_addr2line_targets do anything? The reason is because if things in
|
||||||
|
// get_addr2line_targets fail it will silently skip. This is partly an optimization but also an
|
||||||
|
// assertion below will fail if addr2line is given an empty input.
|
||||||
|
if(entries_vec.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::string address_input;
|
||||||
|
for(const auto& pair : entries_vec) {
|
||||||
|
address_input += pair.first;
|
||||||
|
#if !IS_WINDOWS
|
||||||
|
address_input += '\n';
|
||||||
|
#else
|
||||||
|
address_input += ' ';
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
auto output = split(trim(resolve_addresses(address_input, object_name)), "\n");
|
||||||
|
CPPTRACE_VERIFY(output.size() == entries_vec.size());
|
||||||
|
for(size_t i = 0; i < output.size(); i++) {
|
||||||
|
update_trace(output[i], i, entries_vec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return trace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -14,416 +14,416 @@
|
|||||||
#include <dbghelp.h>
|
#include <dbghelp.h>
|
||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
namespace dbghelp {
|
namespace dbghelp {
|
||||||
|
|
||||||
// SymFromAddr only returns the function's name. In order to get information about parameters,
|
// SymFromAddr only returns the function's name. In order to get information about parameters,
|
||||||
// important for C++ stack traces where functions may be overloaded, we have to manually use
|
// important for C++ stack traces where functions may be overloaded, we have to manually use
|
||||||
// Windows DIA to walk debug info structures. Resources:
|
// Windows DIA to walk debug info structures. Resources:
|
||||||
// https://web.archive.org/web/20201027025750/http://www.debuginfo.com/articles/dbghelptypeinfo.html
|
// https://web.archive.org/web/20201027025750/http://www.debuginfo.com/articles/dbghelptypeinfo.html
|
||||||
// https://web.archive.org/web/20201203160805/http://www.debuginfo.com/articles/dbghelptypeinfofigures.html
|
// https://web.archive.org/web/20201203160805/http://www.debuginfo.com/articles/dbghelptypeinfofigures.html
|
||||||
// https://github.com/DynamoRIO/dynamorio/blob/master/ext/drsyms/drsyms_windows.c#L1370-L1439
|
// https://github.com/DynamoRIO/dynamorio/blob/master/ext/drsyms/drsyms_windows.c#L1370-L1439
|
||||||
// TODO: Currently unable to detect rvalue references
|
// TODO: Currently unable to detect rvalue references
|
||||||
// TODO: Currently unable to detect const
|
// TODO: Currently unable to detect const
|
||||||
enum class SymTagEnum {
|
enum class SymTagEnum {
|
||||||
SymTagNull, SymTagExe, SymTagCompiland, SymTagCompilandDetails, SymTagCompilandEnv,
|
SymTagNull, SymTagExe, SymTagCompiland, SymTagCompilandDetails, SymTagCompilandEnv,
|
||||||
SymTagFunction, SymTagBlock, SymTagData, SymTagAnnotation, SymTagLabel, SymTagPublicSymbol,
|
SymTagFunction, SymTagBlock, SymTagData, SymTagAnnotation, SymTagLabel, SymTagPublicSymbol,
|
||||||
SymTagUDT, SymTagEnum, SymTagFunctionType, SymTagPointerType, SymTagArrayType,
|
SymTagUDT, SymTagEnum, SymTagFunctionType, SymTagPointerType, SymTagArrayType,
|
||||||
SymTagBaseType, SymTagTypedef, SymTagBaseClass, SymTagFriend, SymTagFunctionArgType,
|
SymTagBaseType, SymTagTypedef, SymTagBaseClass, SymTagFriend, SymTagFunctionArgType,
|
||||||
SymTagFuncDebugStart, SymTagFuncDebugEnd, SymTagUsingNamespace, SymTagVTableShape,
|
SymTagFuncDebugStart, SymTagFuncDebugEnd, SymTagUsingNamespace, SymTagVTableShape,
|
||||||
SymTagVTable, SymTagCustom, SymTagThunk, SymTagCustomType, SymTagManagedType,
|
SymTagVTable, SymTagCustom, SymTagThunk, SymTagCustomType, SymTagManagedType,
|
||||||
SymTagDimension, SymTagCallSite, SymTagInlineSite, SymTagBaseInterface, SymTagVectorType,
|
SymTagDimension, SymTagCallSite, SymTagInlineSite, SymTagBaseInterface, SymTagVectorType,
|
||||||
SymTagMatrixType, SymTagHLSLType, SymTagCaller, SymTagCallee, SymTagExport,
|
SymTagMatrixType, SymTagHLSLType, SymTagCaller, SymTagCallee, SymTagExport,
|
||||||
SymTagHeapAllocationSite, SymTagCoffGroup, SymTagMax
|
SymTagHeapAllocationSite, SymTagCoffGroup, SymTagMax
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class IMAGEHLP_SYMBOL_TYPE_INFO {
|
enum class IMAGEHLP_SYMBOL_TYPE_INFO {
|
||||||
TI_GET_SYMTAG, TI_GET_SYMNAME, TI_GET_LENGTH, TI_GET_TYPE, TI_GET_TYPEID, TI_GET_BASETYPE,
|
TI_GET_SYMTAG, TI_GET_SYMNAME, TI_GET_LENGTH, TI_GET_TYPE, TI_GET_TYPEID, TI_GET_BASETYPE,
|
||||||
TI_GET_ARRAYINDEXTYPEID, TI_FINDCHILDREN, TI_GET_DATAKIND, TI_GET_ADDRESSOFFSET,
|
TI_GET_ARRAYINDEXTYPEID, TI_FINDCHILDREN, TI_GET_DATAKIND, TI_GET_ADDRESSOFFSET,
|
||||||
TI_GET_OFFSET, TI_GET_VALUE, TI_GET_COUNT, TI_GET_CHILDRENCOUNT, TI_GET_BITPOSITION,
|
TI_GET_OFFSET, TI_GET_VALUE, TI_GET_COUNT, TI_GET_CHILDRENCOUNT, TI_GET_BITPOSITION,
|
||||||
TI_GET_VIRTUALBASECLASS, TI_GET_VIRTUALTABLESHAPEID, TI_GET_VIRTUALBASEPOINTEROFFSET,
|
TI_GET_VIRTUALBASECLASS, TI_GET_VIRTUALTABLESHAPEID, TI_GET_VIRTUALBASEPOINTEROFFSET,
|
||||||
TI_GET_CLASSPARENTID, TI_GET_NESTED, TI_GET_SYMINDEX, TI_GET_LEXICALPARENT, TI_GET_ADDRESS,
|
TI_GET_CLASSPARENTID, TI_GET_NESTED, TI_GET_SYMINDEX, TI_GET_LEXICALPARENT, TI_GET_ADDRESS,
|
||||||
TI_GET_THISADJUST, TI_GET_UDTKIND, TI_IS_EQUIV_TO, TI_GET_CALLING_CONVENTION,
|
TI_GET_THISADJUST, TI_GET_UDTKIND, TI_IS_EQUIV_TO, TI_GET_CALLING_CONVENTION,
|
||||||
TI_IS_CLOSE_EQUIV_TO, TI_GTIEX_REQS_VALID, TI_GET_VIRTUALBASEOFFSET,
|
TI_IS_CLOSE_EQUIV_TO, TI_GTIEX_REQS_VALID, TI_GET_VIRTUALBASEOFFSET,
|
||||||
TI_GET_VIRTUALBASEDISPINDEX, TI_GET_IS_REFERENCE, TI_GET_INDIRECTVIRTUALBASECLASS,
|
TI_GET_VIRTUALBASEDISPINDEX, TI_GET_IS_REFERENCE, TI_GET_INDIRECTVIRTUALBASECLASS,
|
||||||
TI_GET_VIRTUALBASETABLETYPE, TI_GET_OBJECTPOINTERTYPE, IMAGEHLP_SYMBOL_TYPE_INFO_MAX
|
TI_GET_VIRTUALBASETABLETYPE, TI_GET_OBJECTPOINTERTYPE, IMAGEHLP_SYMBOL_TYPE_INFO_MAX
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class BasicType {
|
enum class BasicType {
|
||||||
btNoType = 0, btVoid = 1, btChar = 2, btWChar = 3, btInt = 6, btUInt = 7, btFloat = 8,
|
btNoType = 0, btVoid = 1, btChar = 2, btWChar = 3, btInt = 6, btUInt = 7, btFloat = 8,
|
||||||
btBCD = 9, btBool = 10, btLong = 13, btULong = 14, btCurrency = 25, btDate = 26,
|
btBCD = 9, btBool = 10, btLong = 13, btULong = 14, btCurrency = 25, btDate = 26,
|
||||||
btVariant = 27, btComplex = 28, btBit = 29, btBSTR = 30, btHresult = 31
|
btVariant = 27, btComplex = 28, btBit = 29, btBSTR = 30, btHresult = 31
|
||||||
};
|
};
|
||||||
|
|
||||||
// SymGetTypeInfo utility
|
// SymGetTypeInfo utility
|
||||||
template<typename T, IMAGEHLP_SYMBOL_TYPE_INFO SymType, bool FAILABLE = false>
|
template<typename T, IMAGEHLP_SYMBOL_TYPE_INFO SymType, bool FAILABLE = false>
|
||||||
T get_info(ULONG type_index, HANDLE proc, ULONG64 modbase) {
|
T get_info(ULONG type_index, HANDLE proc, ULONG64 modbase) {
|
||||||
T info;
|
T info;
|
||||||
if(
|
if(
|
||||||
!SymGetTypeInfo(
|
!SymGetTypeInfo(
|
||||||
proc,
|
proc,
|
||||||
modbase,
|
modbase,
|
||||||
type_index,
|
type_index,
|
||||||
static_cast<::IMAGEHLP_SYMBOL_TYPE_INFO>(SymType),
|
static_cast<::IMAGEHLP_SYMBOL_TYPE_INFO>(SymType),
|
||||||
&info
|
&info
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
if(FAILABLE) {
|
if(FAILABLE) {
|
||||||
return (T)-1;
|
return (T)-1;
|
||||||
} else {
|
} else {
|
||||||
throw std::logic_error(
|
throw std::logic_error(
|
||||||
std::string("SymGetTypeInfo failed: ")
|
std::string("SymGetTypeInfo failed: ")
|
||||||
+ std::system_error(GetLastError(), std::system_category()).what()
|
+ std::system_error(GetLastError(), std::system_category()).what()
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<IMAGEHLP_SYMBOL_TYPE_INFO SymType, bool FAILABLE = false>
|
|
||||||
std::string get_info_wchar(ULONG type_index, HANDLE proc, ULONG64 modbase) {
|
|
||||||
WCHAR* info;
|
|
||||||
if(
|
|
||||||
!SymGetTypeInfo(proc, modbase, type_index, static_cast<::IMAGEHLP_SYMBOL_TYPE_INFO>(SymType), &info)
|
|
||||||
) {
|
|
||||||
throw std::logic_error(
|
|
||||||
std::string("SymGetTypeInfo failed: ")
|
|
||||||
+ std::system_error(GetLastError(), std::system_category()).what()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// special case to properly free a buffer and convert string to narrow chars, only used for
|
|
||||||
// TI_GET_SYMNAME
|
|
||||||
static_assert(
|
|
||||||
SymType == IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_SYMNAME,
|
|
||||||
"get_info_wchar called with unexpected IMAGEHLP_SYMBOL_TYPE_INFO"
|
|
||||||
);
|
);
|
||||||
std::wstring wstr(info);
|
|
||||||
std::string str;
|
|
||||||
str.reserve(wstr.size());
|
|
||||||
for(const auto c : wstr) {
|
|
||||||
str.push_back(static_cast<char>(c));
|
|
||||||
}
|
|
||||||
LocalFree(info);
|
|
||||||
return str;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
// Translate basic types to string
|
template<IMAGEHLP_SYMBOL_TYPE_INFO SymType, bool FAILABLE = false>
|
||||||
static std::string get_basic_type(ULONG type_index, HANDLE proc, ULONG64 modbase) {
|
std::string get_info_wchar(ULONG type_index, HANDLE proc, ULONG64 modbase) {
|
||||||
auto basic_type = get_info<BasicType, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_BASETYPE>(
|
WCHAR* info;
|
||||||
|
if(
|
||||||
|
!SymGetTypeInfo(proc, modbase, type_index, static_cast<::IMAGEHLP_SYMBOL_TYPE_INFO>(SymType), &info)
|
||||||
|
) {
|
||||||
|
throw std::logic_error(
|
||||||
|
std::string("SymGetTypeInfo failed: ")
|
||||||
|
+ std::system_error(GetLastError(), std::system_category()).what()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// special case to properly free a buffer and convert string to narrow chars, only used for
|
||||||
|
// TI_GET_SYMNAME
|
||||||
|
static_assert(
|
||||||
|
SymType == IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_SYMNAME,
|
||||||
|
"get_info_wchar called with unexpected IMAGEHLP_SYMBOL_TYPE_INFO"
|
||||||
|
);
|
||||||
|
std::wstring wstr(info);
|
||||||
|
std::string str;
|
||||||
|
str.reserve(wstr.size());
|
||||||
|
for(const auto c : wstr) {
|
||||||
|
str.push_back(static_cast<char>(c));
|
||||||
|
}
|
||||||
|
LocalFree(info);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate basic types to string
|
||||||
|
static std::string get_basic_type(ULONG type_index, HANDLE proc, ULONG64 modbase) {
|
||||||
|
auto basic_type = get_info<BasicType, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_BASETYPE>(
|
||||||
|
type_index,
|
||||||
|
proc,
|
||||||
|
modbase
|
||||||
|
);
|
||||||
|
//auto length = get_info<ULONG64, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_LENGTH>(type_index, proc, modbase);
|
||||||
|
switch(basic_type) {
|
||||||
|
case BasicType::btNoType:
|
||||||
|
return "<no basic type>";
|
||||||
|
case BasicType::btVoid:
|
||||||
|
return "void";
|
||||||
|
case BasicType::btChar:
|
||||||
|
return "char";
|
||||||
|
case BasicType::btWChar:
|
||||||
|
return "wchar_t";
|
||||||
|
case BasicType::btInt:
|
||||||
|
return "int";
|
||||||
|
case BasicType::btUInt:
|
||||||
|
return "unsigned int";
|
||||||
|
case BasicType::btFloat:
|
||||||
|
return "float";
|
||||||
|
case BasicType::btBool:
|
||||||
|
return "bool";
|
||||||
|
case BasicType::btLong:
|
||||||
|
return "long";
|
||||||
|
case BasicType::btULong:
|
||||||
|
return "unsigned long";
|
||||||
|
default:
|
||||||
|
return "<unknown basic type>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string resolve_type(ULONG type_index, HANDLE proc, ULONG64 modbase);
|
||||||
|
|
||||||
|
struct class_name_result {
|
||||||
|
bool has_class_name;
|
||||||
|
std::string name;
|
||||||
|
};
|
||||||
|
// Helper for member pointers
|
||||||
|
static class_name_result lookup_class_name(ULONG type_index, HANDLE proc, ULONG64 modbase) {
|
||||||
|
DWORD class_parent_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_CLASSPARENTID, true>(
|
||||||
|
type_index,
|
||||||
|
proc,
|
||||||
|
modbase
|
||||||
|
);
|
||||||
|
if(class_parent_id == (DWORD)-1) {
|
||||||
|
return {false, ""};
|
||||||
|
} else {
|
||||||
|
return {true, resolve_type(class_parent_id, proc, modbase)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct type_result {
|
||||||
|
std::string base;
|
||||||
|
std::string extent;
|
||||||
|
};
|
||||||
|
// Resolve more complex types
|
||||||
|
// returns [base, extent]
|
||||||
|
static type_result lookup_type(ULONG type_index, HANDLE proc, ULONG64 modbase) {
|
||||||
|
auto tag = get_info<SymTagEnum, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_SYMTAG>(type_index, proc, modbase);
|
||||||
|
switch(tag) {
|
||||||
|
case SymTagEnum::SymTagBaseType:
|
||||||
|
return {get_basic_type(type_index, proc, modbase), ""};
|
||||||
|
case SymTagEnum::SymTagPointerType: {
|
||||||
|
DWORD underlying_type_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_TYPEID>(
|
||||||
type_index,
|
type_index,
|
||||||
proc,
|
proc,
|
||||||
modbase
|
modbase
|
||||||
);
|
);
|
||||||
//auto length = get_info<ULONG64, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_LENGTH>(type_index, proc, modbase);
|
bool is_ref = get_info<BOOL, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_IS_REFERENCE>(
|
||||||
switch(basic_type) {
|
type_index,
|
||||||
case BasicType::btNoType:
|
proc,
|
||||||
return "<no basic type>";
|
modbase
|
||||||
case BasicType::btVoid:
|
);
|
||||||
return "void";
|
std::string pp = is_ref ? "&" : "*"; // pointer punctuator
|
||||||
case BasicType::btChar:
|
auto class_name_res = lookup_class_name(type_index, proc, modbase);
|
||||||
return "char";
|
if(class_name_res.has_class_name) {
|
||||||
case BasicType::btWChar:
|
pp = class_name_res.name + "::" + pp;
|
||||||
return "wchar_t";
|
}
|
||||||
case BasicType::btInt:
|
const auto type = lookup_type(underlying_type_id, proc, modbase);
|
||||||
return "int";
|
if(type.extent.empty()) {
|
||||||
case BasicType::btUInt:
|
return {type.base + (pp.size() > 1 ? " " : "") + pp, ""};
|
||||||
return "unsigned int";
|
} else {
|
||||||
case BasicType::btFloat:
|
return {type.base + "(" + pp, ")" + type.extent};
|
||||||
return "float";
|
|
||||||
case BasicType::btBool:
|
|
||||||
return "bool";
|
|
||||||
case BasicType::btLong:
|
|
||||||
return "long";
|
|
||||||
case BasicType::btULong:
|
|
||||||
return "unsigned long";
|
|
||||||
default:
|
|
||||||
return "<unknown basic type>";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case SymTagEnum::SymTagArrayType: {
|
||||||
static std::string resolve_type(ULONG type_index, HANDLE proc, ULONG64 modbase);
|
DWORD underlying_type_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_TYPEID>(
|
||||||
|
type_index,
|
||||||
struct class_name_result {
|
proc,
|
||||||
bool has_class_name;
|
modbase
|
||||||
std::string name;
|
);
|
||||||
};
|
DWORD length = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_COUNT>(
|
||||||
// Helper for member pointers
|
type_index,
|
||||||
static class_name_result lookup_class_name(ULONG type_index, HANDLE proc, ULONG64 modbase) {
|
proc,
|
||||||
|
modbase
|
||||||
|
);
|
||||||
|
const auto type = lookup_type(underlying_type_id, proc, modbase);
|
||||||
|
return {type.base, "[" + std::to_string(length) + "]" + type.extent};
|
||||||
|
}
|
||||||
|
case SymTagEnum::SymTagFunctionType: {
|
||||||
|
DWORD return_type_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_TYPEID>(
|
||||||
|
type_index,
|
||||||
|
proc,
|
||||||
|
modbase
|
||||||
|
);
|
||||||
|
DWORD n_children = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_COUNT, true>(
|
||||||
|
type_index,
|
||||||
|
proc,
|
||||||
|
modbase
|
||||||
|
);
|
||||||
DWORD class_parent_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_CLASSPARENTID, true>(
|
DWORD class_parent_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_CLASSPARENTID, true>(
|
||||||
type_index,
|
type_index,
|
||||||
proc,
|
proc,
|
||||||
modbase
|
modbase
|
||||||
);
|
);
|
||||||
if(class_parent_id == (DWORD)-1) {
|
int n_ignore = class_parent_id != (DWORD)-1; // ignore this param
|
||||||
return {false, ""};
|
// this must be ignored before TI_FINDCHILDREN_PARAMS::Count is set, else error
|
||||||
|
n_children -= n_ignore;
|
||||||
|
// return type
|
||||||
|
const auto return_type = lookup_type(return_type_id, proc, modbase);
|
||||||
|
if(n_children == 0) {
|
||||||
|
return {return_type.base, "()" + return_type.extent};
|
||||||
} else {
|
} else {
|
||||||
return {true, resolve_type(class_parent_id, proc, modbase)};
|
// alignment should be fine
|
||||||
|
size_t sz = sizeof(TI_FINDCHILDREN_PARAMS) +
|
||||||
|
(n_children) * sizeof(TI_FINDCHILDREN_PARAMS::ChildId[0]);
|
||||||
|
TI_FINDCHILDREN_PARAMS* children = (TI_FINDCHILDREN_PARAMS*) new char[sz];
|
||||||
|
children->Start = 0;
|
||||||
|
children->Count = n_children;
|
||||||
|
if(
|
||||||
|
!SymGetTypeInfo(
|
||||||
|
proc, modbase, type_index,
|
||||||
|
static_cast<::IMAGEHLP_SYMBOL_TYPE_INFO>(
|
||||||
|
IMAGEHLP_SYMBOL_TYPE_INFO::TI_FINDCHILDREN
|
||||||
|
),
|
||||||
|
children
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw std::logic_error(
|
||||||
|
std::string("SymGetTypeInfo failed: ")
|
||||||
|
+ std::system_error(GetLastError(), std::system_category()).what()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// get children type
|
||||||
|
std::string extent = "(";
|
||||||
|
if(children->Start != 0) {
|
||||||
|
throw std::logic_error("Error: children->Start == 0");
|
||||||
|
}
|
||||||
|
for(std::size_t i = 0; i < n_children; i++) {
|
||||||
|
extent += (i == 0 ? "" : ", ") + resolve_type(children->ChildId[i], proc, modbase);
|
||||||
|
}
|
||||||
|
extent += ")";
|
||||||
|
delete[] (char*) children;
|
||||||
|
return {return_type.base, extent + return_type.extent};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case SymTagEnum::SymTagFunctionArgType: {
|
||||||
struct type_result {
|
DWORD underlying_type_id =
|
||||||
std::string base;
|
get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_TYPEID>(type_index, proc, modbase);
|
||||||
std::string extent;
|
return {resolve_type(underlying_type_id, proc, modbase), ""};
|
||||||
};
|
}
|
||||||
// Resolve more complex types
|
case SymTagEnum::SymTagTypedef:
|
||||||
// returns [base, extent]
|
case SymTagEnum::SymTagEnum:
|
||||||
static type_result lookup_type(ULONG type_index, HANDLE proc, ULONG64 modbase) {
|
case SymTagEnum::SymTagUDT:
|
||||||
auto tag = get_info<SymTagEnum, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_SYMTAG>(type_index, proc, modbase);
|
case SymTagEnum::SymTagBaseClass:
|
||||||
switch(tag) {
|
return {
|
||||||
case SymTagEnum::SymTagBaseType:
|
get_info_wchar<IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_SYMNAME>(type_index, proc, modbase), ""
|
||||||
return {get_basic_type(type_index, proc, modbase), ""};
|
|
||||||
case SymTagEnum::SymTagPointerType: {
|
|
||||||
DWORD underlying_type_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_TYPEID>(
|
|
||||||
type_index,
|
|
||||||
proc,
|
|
||||||
modbase
|
|
||||||
);
|
|
||||||
bool is_ref = get_info<BOOL, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_IS_REFERENCE>(
|
|
||||||
type_index,
|
|
||||||
proc,
|
|
||||||
modbase
|
|
||||||
);
|
|
||||||
std::string pp = is_ref ? "&" : "*"; // pointer punctuator
|
|
||||||
auto class_name_res = lookup_class_name(type_index, proc, modbase);
|
|
||||||
if(class_name_res.has_class_name) {
|
|
||||||
pp = class_name_res.name + "::" + pp;
|
|
||||||
}
|
|
||||||
const auto type = lookup_type(underlying_type_id, proc, modbase);
|
|
||||||
if(type.extent.empty()) {
|
|
||||||
return {type.base + (pp.size() > 1 ? " " : "") + pp, ""};
|
|
||||||
} else {
|
|
||||||
return {type.base + "(" + pp, ")" + type.extent};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case SymTagEnum::SymTagArrayType: {
|
|
||||||
DWORD underlying_type_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_TYPEID>(
|
|
||||||
type_index,
|
|
||||||
proc,
|
|
||||||
modbase
|
|
||||||
);
|
|
||||||
DWORD length = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_COUNT>(
|
|
||||||
type_index,
|
|
||||||
proc,
|
|
||||||
modbase
|
|
||||||
);
|
|
||||||
const auto type = lookup_type(underlying_type_id, proc, modbase);
|
|
||||||
return {type.base, "[" + std::to_string(length) + "]" + type.extent};
|
|
||||||
}
|
|
||||||
case SymTagEnum::SymTagFunctionType: {
|
|
||||||
DWORD return_type_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_TYPEID>(
|
|
||||||
type_index,
|
|
||||||
proc,
|
|
||||||
modbase
|
|
||||||
);
|
|
||||||
DWORD n_children = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_COUNT, true>(
|
|
||||||
type_index,
|
|
||||||
proc,
|
|
||||||
modbase
|
|
||||||
);
|
|
||||||
DWORD class_parent_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_CLASSPARENTID, true>(
|
|
||||||
type_index,
|
|
||||||
proc,
|
|
||||||
modbase
|
|
||||||
);
|
|
||||||
int n_ignore = class_parent_id != (DWORD)-1; // ignore this param
|
|
||||||
// this must be ignored before TI_FINDCHILDREN_PARAMS::Count is set, else error
|
|
||||||
n_children -= n_ignore;
|
|
||||||
// return type
|
|
||||||
const auto return_type = lookup_type(return_type_id, proc, modbase);
|
|
||||||
if(n_children == 0) {
|
|
||||||
return {return_type.base, "()" + return_type.extent};
|
|
||||||
} else {
|
|
||||||
// alignment should be fine
|
|
||||||
size_t sz = sizeof(TI_FINDCHILDREN_PARAMS) +
|
|
||||||
(n_children) * sizeof(TI_FINDCHILDREN_PARAMS::ChildId[0]);
|
|
||||||
TI_FINDCHILDREN_PARAMS* children = (TI_FINDCHILDREN_PARAMS*) new char[sz];
|
|
||||||
children->Start = 0;
|
|
||||||
children->Count = n_children;
|
|
||||||
if(
|
|
||||||
!SymGetTypeInfo(
|
|
||||||
proc, modbase, type_index,
|
|
||||||
static_cast<::IMAGEHLP_SYMBOL_TYPE_INFO>(
|
|
||||||
IMAGEHLP_SYMBOL_TYPE_INFO::TI_FINDCHILDREN
|
|
||||||
),
|
|
||||||
children
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
throw std::logic_error(
|
|
||||||
std::string("SymGetTypeInfo failed: ")
|
|
||||||
+ std::system_error(GetLastError(), std::system_category()).what()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// get children type
|
|
||||||
std::string extent = "(";
|
|
||||||
if(children->Start != 0) {
|
|
||||||
throw std::logic_error("Error: children->Start == 0");
|
|
||||||
}
|
|
||||||
for(std::size_t i = 0; i < n_children; i++) {
|
|
||||||
extent += (i == 0 ? "" : ", ") + resolve_type(children->ChildId[i], proc, modbase);
|
|
||||||
}
|
|
||||||
extent += ")";
|
|
||||||
delete[] (char*) children;
|
|
||||||
return {return_type.base, extent + return_type.extent};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case SymTagEnum::SymTagFunctionArgType: {
|
|
||||||
DWORD underlying_type_id =
|
|
||||||
get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_TYPEID>(type_index, proc, modbase);
|
|
||||||
return {resolve_type(underlying_type_id, proc, modbase), ""};
|
|
||||||
}
|
|
||||||
case SymTagEnum::SymTagTypedef:
|
|
||||||
case SymTagEnum::SymTagEnum:
|
|
||||||
case SymTagEnum::SymTagUDT:
|
|
||||||
case SymTagEnum::SymTagBaseClass:
|
|
||||||
return {
|
|
||||||
get_info_wchar<IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_SYMNAME>(type_index, proc, modbase), ""
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return {
|
|
||||||
"<unknown type " +
|
|
||||||
std::to_string(static_cast<std::underlying_type<SymTagEnum>::type>(tag)) +
|
|
||||||
">",
|
|
||||||
""
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
default:
|
||||||
|
return {
|
||||||
|
"<unknown type " +
|
||||||
|
std::to_string(static_cast<std::underlying_type<SymTagEnum>::type>(tag)) +
|
||||||
|
">",
|
||||||
|
""
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
static std::string resolve_type(ULONG type_index, HANDLE proc, ULONG64 modbase) {
|
static std::string resolve_type(ULONG type_index, HANDLE proc, ULONG64 modbase) {
|
||||||
const auto type = lookup_type(type_index, proc, modbase);
|
const auto type = lookup_type(type_index, proc, modbase);
|
||||||
return type.base + type.extent;
|
return type.base + type.extent;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct function_info {
|
struct function_info {
|
||||||
HANDLE proc;
|
HANDLE proc;
|
||||||
ULONG64 modbase;
|
ULONG64 modbase;
|
||||||
int counter;
|
int counter;
|
||||||
int n_children;
|
int n_children;
|
||||||
int n_ignore;
|
int n_ignore;
|
||||||
std::string str;
|
std::string str;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Enumerates function parameters
|
// Enumerates function parameters
|
||||||
static BOOL __stdcall enumerator_callback(
|
static BOOL __stdcall enumerator_callback(
|
||||||
PSYMBOL_INFO symbol_info,
|
PSYMBOL_INFO symbol_info,
|
||||||
ULONG,
|
ULONG,
|
||||||
PVOID data
|
PVOID data
|
||||||
) {
|
) {
|
||||||
function_info* ctx = (function_info*)data;
|
function_info* ctx = (function_info*)data;
|
||||||
if(ctx->counter++ >= ctx->n_children) {
|
if(ctx->counter++ >= ctx->n_children) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if(ctx->n_ignore-- > 0) {
|
if(ctx->n_ignore-- > 0) {
|
||||||
return true; // just skip
|
return true; // just skip
|
||||||
}
|
}
|
||||||
ctx->str += resolve_type(symbol_info->TypeIndex, ctx->proc, ctx->modbase);
|
ctx->str += resolve_type(symbol_info->TypeIndex, ctx->proc, ctx->modbase);
|
||||||
if(ctx->counter < ctx->n_children) {
|
if(ctx->counter < ctx->n_children) {
|
||||||
ctx->str += ", ";
|
ctx->str += ", ";
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::mutex dbghelp_lock;
|
std::mutex dbghelp_lock;
|
||||||
|
|
||||||
// TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions
|
// TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions
|
||||||
stacktrace_frame resolve_frame(HANDLE proc, void* addr) {
|
stacktrace_frame resolve_frame(HANDLE proc, void* addr) {
|
||||||
const std::lock_guard<std::mutex> lock(dbghelp_lock); // all dbghelp functions are not thread safe
|
const std::lock_guard<std::mutex> lock(dbghelp_lock); // all dbghelp functions are not thread safe
|
||||||
alignas(SYMBOL_INFO) char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
|
alignas(SYMBOL_INFO) char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
|
||||||
SYMBOL_INFO* symbol = (SYMBOL_INFO*)buffer;
|
SYMBOL_INFO* symbol = (SYMBOL_INFO*)buffer;
|
||||||
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
||||||
symbol->MaxNameLen = MAX_SYM_NAME;
|
symbol->MaxNameLen = MAX_SYM_NAME;
|
||||||
union { DWORD64 a; DWORD b; } displacement;
|
union { DWORD64 a; DWORD b; } displacement;
|
||||||
IMAGEHLP_LINE64 line;
|
IMAGEHLP_LINE64 line;
|
||||||
bool got_line = SymGetLineFromAddr64(proc, (DWORD64)addr, &displacement.b, &line);
|
bool got_line = SymGetLineFromAddr64(proc, (DWORD64)addr, &displacement.b, &line);
|
||||||
if(SymFromAddr(proc, (DWORD64)addr, &displacement.a, symbol)) {
|
if(SymFromAddr(proc, (DWORD64)addr, &displacement.a, symbol)) {
|
||||||
if(got_line) {
|
if(got_line) {
|
||||||
IMAGEHLP_STACK_FRAME frame;
|
IMAGEHLP_STACK_FRAME frame;
|
||||||
frame.InstructionOffset = symbol->Address;
|
frame.InstructionOffset = symbol->Address;
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symsetcontext
|
// https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symsetcontext
|
||||||
// "If you call SymSetContext to set the context to its current value, the
|
// "If you call SymSetContext to set the context to its current value, the
|
||||||
// function fails but GetLastError returns ERROR_SUCCESS."
|
// function fails but GetLastError returns ERROR_SUCCESS."
|
||||||
// This is the stupidest fucking api I've ever worked with.
|
// This is the stupidest fucking api I've ever worked with.
|
||||||
if(SymSetContext(proc, &frame, nullptr) == FALSE && GetLastError() != ERROR_SUCCESS) {
|
if(SymSetContext(proc, &frame, nullptr) == FALSE && GetLastError() != ERROR_SUCCESS) {
|
||||||
fprintf(stderr, "Stack trace: Internal error while calling SymSetContext\n");
|
fprintf(stderr, "Stack trace: Internal error while calling SymSetContext\n");
|
||||||
return {
|
|
||||||
reinterpret_cast<uintptr_t>(addr),
|
|
||||||
static_cast<std::uint_least32_t>(line.LineNumber),
|
|
||||||
0,
|
|
||||||
line.FileName,
|
|
||||||
symbol->Name
|
|
||||||
};
|
|
||||||
}
|
|
||||||
DWORD n_children = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_COUNT, true>(
|
|
||||||
symbol->TypeIndex,
|
|
||||||
proc,
|
|
||||||
symbol->ModBase
|
|
||||||
);
|
|
||||||
DWORD class_parent_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_CLASSPARENTID, true>(
|
|
||||||
symbol->TypeIndex,
|
|
||||||
proc,
|
|
||||||
symbol->ModBase
|
|
||||||
);
|
|
||||||
function_info fi {
|
|
||||||
proc,
|
|
||||||
symbol->ModBase,
|
|
||||||
0,
|
|
||||||
int(n_children),
|
|
||||||
class_parent_id != (DWORD)-1,
|
|
||||||
""
|
|
||||||
};
|
|
||||||
SymEnumSymbols(proc, 0, nullptr, enumerator_callback, &fi);
|
|
||||||
std::string signature = symbol->Name + std::string("(") + fi.str + ")";
|
|
||||||
// There's a phenomina with DIA not inserting commas after template parameters. Fix them here.
|
|
||||||
static std::regex comma_re(R"(,(?=\S))");
|
|
||||||
signature = std::regex_replace(signature, comma_re, ", ");
|
|
||||||
return {
|
|
||||||
reinterpret_cast<uintptr_t>(addr),
|
|
||||||
static_cast<std::uint_least32_t>(line.LineNumber),
|
|
||||||
0,
|
|
||||||
line.FileName,
|
|
||||||
signature
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
reinterpret_cast<uintptr_t>(addr),
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
"",
|
|
||||||
symbol->Name
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
return {
|
||||||
reinterpret_cast<uintptr_t>(addr),
|
reinterpret_cast<uintptr_t>(addr),
|
||||||
|
static_cast<std::uint_least32_t>(line.LineNumber),
|
||||||
0,
|
0,
|
||||||
0,
|
line.FileName,
|
||||||
"",
|
symbol->Name
|
||||||
""
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
DWORD n_children = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_COUNT, true>(
|
||||||
|
symbol->TypeIndex,
|
||||||
|
proc,
|
||||||
|
symbol->ModBase
|
||||||
|
);
|
||||||
|
DWORD class_parent_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_CLASSPARENTID, true>(
|
||||||
|
symbol->TypeIndex,
|
||||||
|
proc,
|
||||||
|
symbol->ModBase
|
||||||
|
);
|
||||||
|
function_info fi {
|
||||||
|
proc,
|
||||||
|
symbol->ModBase,
|
||||||
|
0,
|
||||||
|
int(n_children),
|
||||||
|
class_parent_id != (DWORD)-1,
|
||||||
|
""
|
||||||
|
};
|
||||||
|
SymEnumSymbols(proc, 0, nullptr, enumerator_callback, &fi);
|
||||||
|
std::string signature = symbol->Name + std::string("(") + fi.str + ")";
|
||||||
|
// There's a phenomina with DIA not inserting commas after template parameters. Fix them here.
|
||||||
|
static std::regex comma_re(R"(,(?=\S))");
|
||||||
|
signature = std::regex_replace(signature, comma_re, ", ");
|
||||||
|
return {
|
||||||
|
reinterpret_cast<uintptr_t>(addr),
|
||||||
|
static_cast<std::uint_least32_t>(line.LineNumber),
|
||||||
|
0,
|
||||||
|
line.FileName,
|
||||||
|
signature
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
reinterpret_cast<uintptr_t>(addr),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
"",
|
||||||
|
symbol->Name
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
|
return {
|
||||||
std::vector<stacktrace_frame> trace;
|
reinterpret_cast<uintptr_t>(addr),
|
||||||
trace.reserve(frames.size());
|
0,
|
||||||
|
0,
|
||||||
// TODO: When does this need to be called? Can it be moved to the symbolizer?
|
"",
|
||||||
SymSetOptions(SYMOPT_ALLOW_ABSOLUTE_SYMBOLS);
|
""
|
||||||
HANDLE proc = GetCurrentProcess();
|
};
|
||||||
if(!SymInitialize(proc, NULL, TRUE)) {
|
|
||||||
//TODO?
|
|
||||||
throw std::logic_error("SymInitialize failed");
|
|
||||||
}
|
|
||||||
for(const auto frame : frames) {
|
|
||||||
trace.push_back(resolve_frame(proc, frame));
|
|
||||||
}
|
|
||||||
if(!SymCleanup(proc)) {
|
|
||||||
//throw std::logic_error("SymCleanup failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
return trace;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
|
||||||
|
std::vector<stacktrace_frame> trace;
|
||||||
|
trace.reserve(frames.size());
|
||||||
|
|
||||||
|
// TODO: When does this need to be called? Can it be moved to the symbolizer?
|
||||||
|
SymSetOptions(SYMOPT_ALLOW_ABSOLUTE_SYMBOLS);
|
||||||
|
HANDLE proc = GetCurrentProcess();
|
||||||
|
if(!SymInitialize(proc, NULL, TRUE)) {
|
||||||
|
//TODO?
|
||||||
|
throw std::logic_error("SymInitialize failed");
|
||||||
|
}
|
||||||
|
for(const auto frame : frames) {
|
||||||
|
trace.push_back(resolve_frame(proc, frame));
|
||||||
|
}
|
||||||
|
if(!SymCleanup(proc)) {
|
||||||
|
//throw std::logic_error("SymCleanup failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
return trace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -10,39 +10,39 @@
|
|||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
namespace libdl {
|
namespace libdl {
|
||||||
stacktrace_frame resolve_frame(const void* addr) {
|
stacktrace_frame resolve_frame(const void* addr) {
|
||||||
Dl_info info;
|
Dl_info info;
|
||||||
if(dladdr(addr, &info)) { // thread-safe
|
if(dladdr(addr, &info)) { // thread-safe
|
||||||
return {
|
return {
|
||||||
reinterpret_cast<uintptr_t>(addr),
|
reinterpret_cast<uintptr_t>(addr),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
info.dli_fname ? info.dli_fname : "",
|
info.dli_fname ? info.dli_fname : "",
|
||||||
info.dli_sname ? info.dli_sname : ""
|
info.dli_sname ? info.dli_sname : ""
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
reinterpret_cast<uintptr_t>(addr),
|
reinterpret_cast<uintptr_t>(addr),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
"",
|
"",
|
||||||
""
|
""
|
||||||
};
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
|
|
||||||
std::vector<stacktrace_frame> trace;
|
|
||||||
trace.reserve(frames.size());
|
|
||||||
for(const void* frame : frames) {
|
|
||||||
trace.push_back(resolve_frame(frame));
|
|
||||||
}
|
|
||||||
return trace;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
|
||||||
|
std::vector<stacktrace_frame> trace;
|
||||||
|
trace.reserve(frames.size());
|
||||||
|
for(const void* frame : frames) {
|
||||||
|
trace.push_back(resolve_frame(frame));
|
||||||
|
}
|
||||||
|
return trace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -17,80 +17,80 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
namespace libbacktrace {
|
namespace libbacktrace {
|
||||||
int full_callback(void* data, uintptr_t address, const char* file, int line, const char* symbol) {
|
int full_callback(void* data, uintptr_t address, const char* file, int line, const char* symbol) {
|
||||||
stacktrace_frame& frame = *static_cast<stacktrace_frame*>(data);
|
stacktrace_frame& frame = *static_cast<stacktrace_frame*>(data);
|
||||||
if(line == 0) {
|
if(line == 0) {
|
||||||
///fprintf(stderr, "Getting bad data for some reason\n"); // TODO: Eliminate
|
///fprintf(stderr, "Getting bad data for some reason\n"); // TODO: Eliminate
|
||||||
}
|
|
||||||
frame.address = address;
|
|
||||||
frame.line = line;
|
|
||||||
frame.filename = file ? file : "";
|
|
||||||
frame.symbol = symbol ? symbol : "";
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void syminfo_callback(void* data, uintptr_t address, const char* symbol, uintptr_t, uintptr_t) {
|
|
||||||
stacktrace_frame& frame = *static_cast<stacktrace_frame*>(data);
|
|
||||||
frame.address = address;
|
|
||||||
frame.line = 0;
|
|
||||||
frame.filename = "";
|
|
||||||
frame.symbol = symbol ? symbol : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
void error_callback(void*, const char* msg, int errnum) {
|
|
||||||
fprintf(stderr, "Libbacktrace error: %s, code %d\n", msg, errnum);
|
|
||||||
}
|
|
||||||
|
|
||||||
backtrace_state* get_backtrace_state() {
|
|
||||||
static std::mutex mutex;
|
|
||||||
const std::lock_guard<std::mutex> lock(mutex);
|
|
||||||
// backtrace_create_state must be called only one time per program
|
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
|
||||||
static backtrace_state* state = nullptr;
|
|
||||||
static bool called = false;
|
|
||||||
if(!called) {
|
|
||||||
state = backtrace_create_state(program_name(), true, error_callback, nullptr);
|
|
||||||
called = true;
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions
|
|
||||||
stacktrace_frame resolve_frame(const void* addr) {
|
|
||||||
stacktrace_frame frame;
|
|
||||||
frame.col = 0;
|
|
||||||
backtrace_pcinfo(
|
|
||||||
get_backtrace_state(),
|
|
||||||
reinterpret_cast<uintptr_t>(addr),
|
|
||||||
full_callback,
|
|
||||||
error_callback,
|
|
||||||
&frame
|
|
||||||
);
|
|
||||||
if(frame.symbol.empty()) {
|
|
||||||
// fallback, try to at least recover the symbol name with backtrace_syminfo
|
|
||||||
backtrace_syminfo(
|
|
||||||
get_backtrace_state(),
|
|
||||||
reinterpret_cast<uintptr_t>(addr),
|
|
||||||
syminfo_callback,
|
|
||||||
error_callback,
|
|
||||||
&frame
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
|
|
||||||
std::vector<stacktrace_frame> trace;
|
|
||||||
trace.reserve(frames.size());
|
|
||||||
for(const void* frame : frames) {
|
|
||||||
trace.push_back(resolve_frame(frame));
|
|
||||||
}
|
|
||||||
return trace;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
frame.address = address;
|
||||||
|
frame.line = line;
|
||||||
|
frame.filename = file ? file : "";
|
||||||
|
frame.symbol = symbol ? symbol : "";
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void syminfo_callback(void* data, uintptr_t address, const char* symbol, uintptr_t, uintptr_t) {
|
||||||
|
stacktrace_frame& frame = *static_cast<stacktrace_frame*>(data);
|
||||||
|
frame.address = address;
|
||||||
|
frame.line = 0;
|
||||||
|
frame.filename = "";
|
||||||
|
frame.symbol = symbol ? symbol : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
void error_callback(void*, const char* msg, int errnum) {
|
||||||
|
fprintf(stderr, "Libbacktrace error: %s, code %d\n", msg, errnum);
|
||||||
|
}
|
||||||
|
|
||||||
|
backtrace_state* get_backtrace_state() {
|
||||||
|
static std::mutex mutex;
|
||||||
|
const std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
// backtrace_create_state must be called only one time per program
|
||||||
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
static backtrace_state* state = nullptr;
|
||||||
|
static bool called = false;
|
||||||
|
if(!called) {
|
||||||
|
state = backtrace_create_state(program_name(), true, error_callback, nullptr);
|
||||||
|
called = true;
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions
|
||||||
|
stacktrace_frame resolve_frame(const void* addr) {
|
||||||
|
stacktrace_frame frame;
|
||||||
|
frame.col = 0;
|
||||||
|
backtrace_pcinfo(
|
||||||
|
get_backtrace_state(),
|
||||||
|
reinterpret_cast<uintptr_t>(addr),
|
||||||
|
full_callback,
|
||||||
|
error_callback,
|
||||||
|
&frame
|
||||||
|
);
|
||||||
|
if(frame.symbol.empty()) {
|
||||||
|
// fallback, try to at least recover the symbol name with backtrace_syminfo
|
||||||
|
backtrace_syminfo(
|
||||||
|
get_backtrace_state(),
|
||||||
|
reinterpret_cast<uintptr_t>(addr),
|
||||||
|
syminfo_callback,
|
||||||
|
error_callback,
|
||||||
|
&frame
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
|
||||||
|
std::vector<stacktrace_frame> trace;
|
||||||
|
trace.reserve(frames.size());
|
||||||
|
for(const void* frame : frames) {
|
||||||
|
trace.push_back(resolve_frame(frame));
|
||||||
|
}
|
||||||
|
return trace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -6,19 +6,19 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
namespace nothing {
|
namespace nothing {
|
||||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
|
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
|
||||||
return std::vector<stacktrace_frame>(frames.size(), {
|
return std::vector<stacktrace_frame>(frames.size(), {
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
"",
|
"",
|
||||||
""
|
""
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -8,15 +8,15 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
#ifdef CPPTRACE_HARD_MAX_FRAMES
|
#ifdef CPPTRACE_HARD_MAX_FRAMES
|
||||||
constexpr size_t hard_max_frames = CPPTRACE_HARD_MAX_FRAMES;
|
constexpr size_t hard_max_frames = CPPTRACE_HARD_MAX_FRAMES;
|
||||||
#else
|
#else
|
||||||
constexpr size_t hard_max_frames = 100;
|
constexpr size_t hard_max_frames = 100;
|
||||||
#endif
|
#endif
|
||||||
CPPTRACE_FORCE_NO_INLINE
|
CPPTRACE_FORCE_NO_INLINE
|
||||||
std::vector<void*> capture_frames(size_t skip);
|
std::vector<void*> capture_frames(size_t skip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -11,17 +11,17 @@
|
|||||||
#include <execinfo.h>
|
#include <execinfo.h>
|
||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
CPPTRACE_FORCE_NO_INLINE
|
CPPTRACE_FORCE_NO_INLINE
|
||||||
std::vector<void*> capture_frames(size_t skip) {
|
std::vector<void*> capture_frames(size_t skip) {
|
||||||
std::vector<void*> frames(hard_max_frames + skip, nullptr);
|
std::vector<void*> frames(hard_max_frames + skip, nullptr);
|
||||||
const int n_frames = backtrace(frames.data(), int(hard_max_frames + skip)); // thread safe
|
const int n_frames = backtrace(frames.data(), int(hard_max_frames + skip)); // thread safe
|
||||||
frames.resize(n_frames);
|
frames.resize(n_frames);
|
||||||
frames.erase(frames.begin(), frames.begin() + ptrdiff_t(std::min(skip + 1, frames.size())));
|
frames.erase(frames.begin(), frames.begin() + ptrdiff_t(std::min(skip + 1, frames.size())));
|
||||||
frames.shrink_to_fit();
|
frames.shrink_to_fit();
|
||||||
return frames;
|
return frames;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -6,11 +6,11 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
std::vector<void*> capture_frames(size_t) {
|
std::vector<void*> capture_frames(size_t) {
|
||||||
return {};
|
return {};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -13,52 +13,52 @@
|
|||||||
#include <unwind.h>
|
#include <unwind.h>
|
||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
struct unwind_state {
|
struct unwind_state {
|
||||||
std::size_t skip;
|
std::size_t skip;
|
||||||
std::size_t count;
|
std::size_t count;
|
||||||
std::vector<void*>& vec;
|
std::vector<void*>& vec;
|
||||||
};
|
};
|
||||||
|
|
||||||
_Unwind_Reason_Code unwind_callback(_Unwind_Context* context, void* arg) {
|
_Unwind_Reason_Code unwind_callback(_Unwind_Context* context, void* arg) {
|
||||||
unwind_state& state = *static_cast<unwind_state*>(arg);
|
unwind_state& state = *static_cast<unwind_state*>(arg);
|
||||||
if(state.skip) {
|
if(state.skip) {
|
||||||
state.skip--;
|
state.skip--;
|
||||||
if(_Unwind_GetIP(context) == uintptr_t(0)) {
|
if(_Unwind_GetIP(context) == uintptr_t(0)) {
|
||||||
return _URC_END_OF_STACK;
|
|
||||||
} else {
|
|
||||||
return _URC_NO_REASON;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CPPTRACE_VERIFY(
|
|
||||||
state.count < state.vec.size(),
|
|
||||||
"Somehow cpptrace::detail::unwind_callback is overflowing a vector"
|
|
||||||
);
|
|
||||||
int is_before_instruction = 0;
|
|
||||||
uintptr_t ip = _Unwind_GetIPInfo(context, &is_before_instruction);
|
|
||||||
if(!is_before_instruction && ip != uintptr_t(0)) {
|
|
||||||
ip--;
|
|
||||||
}
|
|
||||||
if (ip == uintptr_t(0) || state.count == state.vec.size()) {
|
|
||||||
return _URC_END_OF_STACK;
|
return _URC_END_OF_STACK;
|
||||||
} else {
|
} else {
|
||||||
// TODO: push_back?...
|
|
||||||
state.vec[state.count++] = (void*)ip;
|
|
||||||
return _URC_NO_REASON;
|
return _URC_NO_REASON;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CPPTRACE_FORCE_NO_INLINE
|
CPPTRACE_VERIFY(
|
||||||
std::vector<void*> capture_frames(size_t skip) {
|
state.count < state.vec.size(),
|
||||||
std::vector<void*> frames(hard_max_frames, nullptr);
|
"Somehow cpptrace::detail::unwind_callback is overflowing a vector"
|
||||||
unwind_state state{skip + 1, 0, frames};
|
);
|
||||||
_Unwind_Backtrace(unwind_callback, &state); // presumably thread-safe
|
int is_before_instruction = 0;
|
||||||
frames.resize(state.count);
|
uintptr_t ip = _Unwind_GetIPInfo(context, &is_before_instruction);
|
||||||
frames.shrink_to_fit();
|
if(!is_before_instruction && ip != uintptr_t(0)) {
|
||||||
return frames;
|
ip--;
|
||||||
|
}
|
||||||
|
if (ip == uintptr_t(0) || state.count == state.vec.size()) {
|
||||||
|
return _URC_END_OF_STACK;
|
||||||
|
} else {
|
||||||
|
// TODO: push_back?...
|
||||||
|
state.vec[state.count++] = (void*)ip;
|
||||||
|
return _URC_NO_REASON;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CPPTRACE_FORCE_NO_INLINE
|
||||||
|
std::vector<void*> capture_frames(size_t skip) {
|
||||||
|
std::vector<void*> frames(hard_max_frames, nullptr);
|
||||||
|
unwind_state state{skip + 1, 0, frames};
|
||||||
|
_Unwind_Backtrace(unwind_callback, &state); // presumably thread-safe
|
||||||
|
frames.resize(state.count);
|
||||||
|
frames.shrink_to_fit();
|
||||||
|
return frames;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -10,16 +10,16 @@
|
|||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
CPPTRACE_FORCE_NO_INLINE
|
CPPTRACE_FORCE_NO_INLINE
|
||||||
std::vector<void*> capture_frames(size_t skip) {
|
std::vector<void*> capture_frames(size_t skip) {
|
||||||
std::vector<PVOID> addrs(hard_max_frames, nullptr);
|
std::vector<PVOID> addrs(hard_max_frames, nullptr);
|
||||||
int frames = CaptureStackBackTrace(static_cast<DWORD>(skip + 1), hard_max_frames, addrs.data(), NULL);
|
int frames = CaptureStackBackTrace(static_cast<DWORD>(skip + 1), hard_max_frames, addrs.data(), NULL);
|
||||||
addrs.resize(frames);
|
addrs.resize(frames);
|
||||||
addrs.shrink_to_fit();
|
addrs.shrink_to_fit();
|
||||||
return addrs;
|
return addrs;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user