Refactor nested namespaces
This commit is contained in:
parent
cc43a23987
commit
fdbc69e18e
@ -4,9 +4,9 @@
|
||||
#include <string>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
std::string demangle(const std::string&);
|
||||
}
|
||||
namespace detail {
|
||||
std::string demangle(const std::string&);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -8,25 +8,25 @@
|
||||
#include <string>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
std::string demangle(const std::string& name) {
|
||||
int status;
|
||||
// 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
|
||||
// want to rely on it
|
||||
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
|
||||
// we'll just quietly return the mangled name
|
||||
if(demangled) {
|
||||
std::string str = demangled;
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
|
||||
std::free(demangled);
|
||||
return str;
|
||||
} else {
|
||||
return name;
|
||||
}
|
||||
namespace detail {
|
||||
std::string demangle(const std::string& name) {
|
||||
int status;
|
||||
// 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
|
||||
// want to rely on it
|
||||
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
|
||||
// we'll just quietly return the mangled name
|
||||
if(demangled) {
|
||||
std::string str = demangled;
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
|
||||
std::free(demangled);
|
||||
return str;
|
||||
} else {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -5,11 +5,11 @@
|
||||
#include <string>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
std::string demangle(const std::string& name) {
|
||||
return name;
|
||||
}
|
||||
namespace detail {
|
||||
std::string demangle(const std::string& name) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -9,10 +9,10 @@
|
||||
#include <vector>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::vector<stacktrace_frame> generate_trace(size_t skip);
|
||||
}
|
||||
namespace detail {
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::vector<stacktrace_frame> generate_trace(size_t skip);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -17,77 +17,77 @@
|
||||
#endif
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
struct trace_data {
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
|
||||
std::vector<stacktrace_frame>& frames;
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
|
||||
size_t& skip;
|
||||
};
|
||||
namespace detail {
|
||||
struct trace_data {
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
|
||||
std::vector<stacktrace_frame>& frames;
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
|
||||
size_t& skip;
|
||||
};
|
||||
|
||||
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);
|
||||
if(data.skip > 0) {
|
||||
data.skip--;
|
||||
} else if(address == uintptr_t(-1)) {
|
||||
// sentinel for libbacktrace, stop tracing
|
||||
return 1;
|
||||
} else {
|
||||
data.frames.push_back({
|
||||
address,
|
||||
static_cast<std::uint_least32_t>(line),
|
||||
0,
|
||||
file ? file : "",
|
||||
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;
|
||||
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);
|
||||
if(data.skip > 0) {
|
||||
data.skip--;
|
||||
} else if(address == uintptr_t(-1)) {
|
||||
// sentinel for libbacktrace, stop tracing
|
||||
return 1;
|
||||
} else {
|
||||
data.frames.push_back({
|
||||
address,
|
||||
static_cast<std::uint_least32_t>(line),
|
||||
0,
|
||||
file ? file : "",
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -3,28 +3,29 @@
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include "full_trace.hpp"
|
||||
#include "../platform/common.hpp"
|
||||
#include "../platform/utils.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <stacktrace>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::vector<stacktrace_frame> generate_trace(size_t skip) {
|
||||
std::vector<stacktrace_frame> frames;
|
||||
std::stacktrace trace = std::stacktrace::current(skip + 1);
|
||||
for(const auto entry : trace) {
|
||||
frames.push_back({
|
||||
entry.native_handle(),
|
||||
entry.source_line(),
|
||||
0,
|
||||
entry.source_file(),
|
||||
entry.description()
|
||||
});
|
||||
}
|
||||
return frames;
|
||||
namespace detail {
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::vector<stacktrace_frame> generate_trace(size_t skip) {
|
||||
std::vector<stacktrace_frame> frames;
|
||||
std::stacktrace trace = std::stacktrace::current(skip + 1);
|
||||
for(const auto entry : trace) {
|
||||
frames.push_back({
|
||||
entry.native_handle(),
|
||||
entry.source_line(),
|
||||
0,
|
||||
entry.source_file(),
|
||||
entry.description()
|
||||
});
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -7,39 +7,39 @@
|
||||
#include <vector>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_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
|
||||
namespace detail {
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_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);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -1,55 +1,56 @@
|
||||
#include "symbols.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "../platform/object.hpp"
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
void apply_trace(
|
||||
std::vector<stacktrace_frame>& result,
|
||||
std::vector<stacktrace_frame>&& trace
|
||||
) {
|
||||
for(std::size_t i = 0; i < result.size(); i++) {
|
||||
if(result[i].address == 0) {
|
||||
result[i].address = trace[i].address;
|
||||
}
|
||||
if(result[i].line == 0) {
|
||||
result[i].line = trace[i].line;
|
||||
}
|
||||
if(result[i].col == 0) {
|
||||
result[i].col = trace[i].col;
|
||||
}
|
||||
if(result[i].filename.empty()) {
|
||||
result[i].filename = std::move(trace[i].filename);
|
||||
}
|
||||
if(result[i].symbol.empty()) {
|
||||
result[i].symbol = std::move(trace[i].symbol);
|
||||
}
|
||||
namespace detail {
|
||||
void apply_trace(
|
||||
std::vector<stacktrace_frame>& result,
|
||||
std::vector<stacktrace_frame>&& trace
|
||||
) {
|
||||
for(std::size_t i = 0; i < result.size(); i++) {
|
||||
if(result[i].address == 0) {
|
||||
result[i].address = trace[i].address;
|
||||
}
|
||||
if(result[i].line == 0) {
|
||||
result[i].line = trace[i].line;
|
||||
}
|
||||
if(result[i].col == 0) {
|
||||
result[i].col = trace[i].col;
|
||||
}
|
||||
if(result[i].filename.empty()) {
|
||||
result[i].filename = std::move(trace[i].filename);
|
||||
}
|
||||
if(result[i].symbol.empty()) {
|
||||
result[i].symbol = std::move(trace[i].symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
|
||||
std::vector<stacktrace_frame> trace(frames.size());
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDL
|
||||
apply_trace(trace, libdl::resolve_frames(frames));
|
||||
#endif
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
|
||||
apply_trace(trace, libdwarf::resolve_frames(frames));
|
||||
#endif
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP
|
||||
apply_trace(trace, dbghelp::resolve_frames(frames));
|
||||
#endif
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE
|
||||
apply_trace(trace, addr2line::resolve_frames(frames));
|
||||
#endif
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE
|
||||
apply_trace(trace, libbacktrace::resolve_frames(frames));
|
||||
#endif
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_NOTHING
|
||||
apply_trace(trace, nothing::resolve_frames(frames));
|
||||
#endif
|
||||
return trace;
|
||||
}
|
||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
|
||||
std::vector<stacktrace_frame> trace(frames.size());
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDL
|
||||
apply_trace(trace, libdl::resolve_frames(frames));
|
||||
#endif
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
|
||||
apply_trace(trace, libdwarf::resolve_frames(frames));
|
||||
#endif
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP
|
||||
apply_trace(trace, dbghelp::resolve_frames(frames));
|
||||
#endif
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE
|
||||
apply_trace(trace, addr2line::resolve_frames(frames));
|
||||
#endif
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE
|
||||
apply_trace(trace, libbacktrace::resolve_frames(frames));
|
||||
#endif
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_NOTHING
|
||||
apply_trace(trace, nothing::resolve_frames(frames));
|
||||
#endif
|
||||
return trace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,329 +24,329 @@
|
||||
#include "../platform/object.hpp"
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
namespace addr2line {
|
||||
#if IS_LINUX || IS_APPLE
|
||||
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) {
|
||||
checked = true;
|
||||
// Detects if addr2line exists by trying to invoke addr2line --help
|
||||
constexpr int magic = 42;
|
||||
// NOLINTNEXTLINE(misc-include-cleaner)
|
||||
const pid_t pid = fork();
|
||||
if(pid == -1) { return false; }
|
||||
if(pid == 0) { // child
|
||||
close(STDOUT_FILENO);
|
||||
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());
|
||||
namespace detail {
|
||||
namespace addr2line {
|
||||
#if IS_LINUX || IS_APPLE
|
||||
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) {
|
||||
checked = true;
|
||||
// Detects if addr2line exists by trying to invoke addr2line --help
|
||||
constexpr int magic = 42;
|
||||
// NOLINTNEXTLINE(misc-include-cleaner)
|
||||
const pid_t pid = fork();
|
||||
if(pid == -1) { return false; }
|
||||
if(pid == 0) { // child
|
||||
close(STDOUT_FILENO);
|
||||
close(STDERR_FILENO); // atos --help writes to stderr
|
||||
#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
|
||||
#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"
|
||||
);
|
||||
execl(CPPTRACE_ADDR2LINE_PATH, CPPTRACE_ADDR2LINE_PATH, "--help", nullptr);
|
||||
#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;
|
||||
_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
|
||||
|
||||
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;
|
||||
#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
|
||||
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
|
||||
|
||||
@ -14,416 +14,416 @@
|
||||
#include <dbghelp.h>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
namespace dbghelp {
|
||||
namespace detail {
|
||||
namespace dbghelp {
|
||||
|
||||
// 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
|
||||
// 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/20201203160805/http://www.debuginfo.com/articles/dbghelptypeinfofigures.html
|
||||
// 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 const
|
||||
enum class SymTagEnum {
|
||||
SymTagNull, SymTagExe, SymTagCompiland, SymTagCompilandDetails, SymTagCompilandEnv,
|
||||
SymTagFunction, SymTagBlock, SymTagData, SymTagAnnotation, SymTagLabel, SymTagPublicSymbol,
|
||||
SymTagUDT, SymTagEnum, SymTagFunctionType, SymTagPointerType, SymTagArrayType,
|
||||
SymTagBaseType, SymTagTypedef, SymTagBaseClass, SymTagFriend, SymTagFunctionArgType,
|
||||
SymTagFuncDebugStart, SymTagFuncDebugEnd, SymTagUsingNamespace, SymTagVTableShape,
|
||||
SymTagVTable, SymTagCustom, SymTagThunk, SymTagCustomType, SymTagManagedType,
|
||||
SymTagDimension, SymTagCallSite, SymTagInlineSite, SymTagBaseInterface, SymTagVectorType,
|
||||
SymTagMatrixType, SymTagHLSLType, SymTagCaller, SymTagCallee, SymTagExport,
|
||||
SymTagHeapAllocationSite, SymTagCoffGroup, SymTagMax
|
||||
};
|
||||
// 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
|
||||
// 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/20201203160805/http://www.debuginfo.com/articles/dbghelptypeinfofigures.html
|
||||
// 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 const
|
||||
enum class SymTagEnum {
|
||||
SymTagNull, SymTagExe, SymTagCompiland, SymTagCompilandDetails, SymTagCompilandEnv,
|
||||
SymTagFunction, SymTagBlock, SymTagData, SymTagAnnotation, SymTagLabel, SymTagPublicSymbol,
|
||||
SymTagUDT, SymTagEnum, SymTagFunctionType, SymTagPointerType, SymTagArrayType,
|
||||
SymTagBaseType, SymTagTypedef, SymTagBaseClass, SymTagFriend, SymTagFunctionArgType,
|
||||
SymTagFuncDebugStart, SymTagFuncDebugEnd, SymTagUsingNamespace, SymTagVTableShape,
|
||||
SymTagVTable, SymTagCustom, SymTagThunk, SymTagCustomType, SymTagManagedType,
|
||||
SymTagDimension, SymTagCallSite, SymTagInlineSite, SymTagBaseInterface, SymTagVectorType,
|
||||
SymTagMatrixType, SymTagHLSLType, SymTagCaller, SymTagCallee, SymTagExport,
|
||||
SymTagHeapAllocationSite, SymTagCoffGroup, SymTagMax
|
||||
};
|
||||
|
||||
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_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_VIRTUALBASECLASS, TI_GET_VIRTUALTABLESHAPEID, TI_GET_VIRTUALBASEPOINTEROFFSET,
|
||||
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_IS_CLOSE_EQUIV_TO, TI_GTIEX_REQS_VALID, TI_GET_VIRTUALBASEOFFSET,
|
||||
TI_GET_VIRTUALBASEDISPINDEX, TI_GET_IS_REFERENCE, TI_GET_INDIRECTVIRTUALBASECLASS,
|
||||
TI_GET_VIRTUALBASETABLETYPE, TI_GET_OBJECTPOINTERTYPE, IMAGEHLP_SYMBOL_TYPE_INFO_MAX
|
||||
};
|
||||
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_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_VIRTUALBASECLASS, TI_GET_VIRTUALTABLESHAPEID, TI_GET_VIRTUALBASEPOINTEROFFSET,
|
||||
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_IS_CLOSE_EQUIV_TO, TI_GTIEX_REQS_VALID, TI_GET_VIRTUALBASEOFFSET,
|
||||
TI_GET_VIRTUALBASEDISPINDEX, TI_GET_IS_REFERENCE, TI_GET_INDIRECTVIRTUALBASECLASS,
|
||||
TI_GET_VIRTUALBASETABLETYPE, TI_GET_OBJECTPOINTERTYPE, IMAGEHLP_SYMBOL_TYPE_INFO_MAX
|
||||
};
|
||||
|
||||
enum class BasicType {
|
||||
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,
|
||||
btVariant = 27, btComplex = 28, btBit = 29, btBSTR = 30, btHresult = 31
|
||||
};
|
||||
enum class BasicType {
|
||||
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,
|
||||
btVariant = 27, btComplex = 28, btBit = 29, btBSTR = 30, btHresult = 31
|
||||
};
|
||||
|
||||
// SymGetTypeInfo utility
|
||||
template<typename T, IMAGEHLP_SYMBOL_TYPE_INFO SymType, bool FAILABLE = false>
|
||||
T get_info(ULONG type_index, HANDLE proc, ULONG64 modbase) {
|
||||
T info;
|
||||
if(
|
||||
!SymGetTypeInfo(
|
||||
proc,
|
||||
modbase,
|
||||
type_index,
|
||||
static_cast<::IMAGEHLP_SYMBOL_TYPE_INFO>(SymType),
|
||||
&info
|
||||
)
|
||||
) {
|
||||
if(FAILABLE) {
|
||||
return (T)-1;
|
||||
} else {
|
||||
throw std::logic_error(
|
||||
std::string("SymGetTypeInfo failed: ")
|
||||
+ 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"
|
||||
// SymGetTypeInfo utility
|
||||
template<typename T, IMAGEHLP_SYMBOL_TYPE_INFO SymType, bool FAILABLE = false>
|
||||
T get_info(ULONG type_index, HANDLE proc, ULONG64 modbase) {
|
||||
T info;
|
||||
if(
|
||||
!SymGetTypeInfo(
|
||||
proc,
|
||||
modbase,
|
||||
type_index,
|
||||
static_cast<::IMAGEHLP_SYMBOL_TYPE_INFO>(SymType),
|
||||
&info
|
||||
)
|
||||
) {
|
||||
if(FAILABLE) {
|
||||
return (T)-1;
|
||||
} else {
|
||||
throw std::logic_error(
|
||||
std::string("SymGetTypeInfo failed: ")
|
||||
+ std::system_error(GetLastError(), std::system_category()).what()
|
||||
);
|
||||
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
|
||||
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>(
|
||||
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;
|
||||
}
|
||||
|
||||
// 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,
|
||||
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>";
|
||||
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};
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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
|
||||
);
|
||||
if(class_parent_id == (DWORD)-1) {
|
||||
return {false, ""};
|
||||
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 {
|
||||
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};
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
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)) +
|
||||
">",
|
||||
""
|
||||
};
|
||||
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)) +
|
||||
">",
|
||||
""
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
static std::string resolve_type(ULONG type_index, HANDLE proc, ULONG64 modbase) {
|
||||
const auto type = lookup_type(type_index, proc, modbase);
|
||||
return type.base + type.extent;
|
||||
}
|
||||
static std::string resolve_type(ULONG type_index, HANDLE proc, ULONG64 modbase) {
|
||||
const auto type = lookup_type(type_index, proc, modbase);
|
||||
return type.base + type.extent;
|
||||
}
|
||||
|
||||
struct function_info {
|
||||
HANDLE proc;
|
||||
ULONG64 modbase;
|
||||
int counter;
|
||||
int n_children;
|
||||
int n_ignore;
|
||||
std::string str;
|
||||
};
|
||||
struct function_info {
|
||||
HANDLE proc;
|
||||
ULONG64 modbase;
|
||||
int counter;
|
||||
int n_children;
|
||||
int n_ignore;
|
||||
std::string str;
|
||||
};
|
||||
|
||||
// Enumerates function parameters
|
||||
static BOOL __stdcall enumerator_callback(
|
||||
PSYMBOL_INFO symbol_info,
|
||||
ULONG,
|
||||
PVOID data
|
||||
) {
|
||||
function_info* ctx = (function_info*)data;
|
||||
if(ctx->counter++ >= ctx->n_children) {
|
||||
return false;
|
||||
}
|
||||
if(ctx->n_ignore-- > 0) {
|
||||
return true; // just skip
|
||||
}
|
||||
ctx->str += resolve_type(symbol_info->TypeIndex, ctx->proc, ctx->modbase);
|
||||
if(ctx->counter < ctx->n_children) {
|
||||
ctx->str += ", ";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Enumerates function parameters
|
||||
static BOOL __stdcall enumerator_callback(
|
||||
PSYMBOL_INFO symbol_info,
|
||||
ULONG,
|
||||
PVOID data
|
||||
) {
|
||||
function_info* ctx = (function_info*)data;
|
||||
if(ctx->counter++ >= ctx->n_children) {
|
||||
return false;
|
||||
}
|
||||
if(ctx->n_ignore-- > 0) {
|
||||
return true; // just skip
|
||||
}
|
||||
ctx->str += resolve_type(symbol_info->TypeIndex, ctx->proc, ctx->modbase);
|
||||
if(ctx->counter < ctx->n_children) {
|
||||
ctx->str += ", ";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::mutex dbghelp_lock;
|
||||
std::mutex dbghelp_lock;
|
||||
|
||||
// TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions
|
||||
stacktrace_frame resolve_frame(HANDLE proc, void* addr) {
|
||||
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)];
|
||||
SYMBOL_INFO* symbol = (SYMBOL_INFO*)buffer;
|
||||
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
||||
symbol->MaxNameLen = MAX_SYM_NAME;
|
||||
union { DWORD64 a; DWORD b; } displacement;
|
||||
IMAGEHLP_LINE64 line;
|
||||
bool got_line = SymGetLineFromAddr64(proc, (DWORD64)addr, &displacement.b, &line);
|
||||
if(SymFromAddr(proc, (DWORD64)addr, &displacement.a, symbol)) {
|
||||
if(got_line) {
|
||||
IMAGEHLP_STACK_FRAME frame;
|
||||
frame.InstructionOffset = symbol->Address;
|
||||
// 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
|
||||
// function fails but GetLastError returns ERROR_SUCCESS."
|
||||
// This is the stupidest fucking api I've ever worked with.
|
||||
if(SymSetContext(proc, &frame, nullptr) == FALSE && GetLastError() != ERROR_SUCCESS) {
|
||||
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 {
|
||||
// TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions
|
||||
stacktrace_frame resolve_frame(HANDLE proc, void* addr) {
|
||||
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)];
|
||||
SYMBOL_INFO* symbol = (SYMBOL_INFO*)buffer;
|
||||
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
||||
symbol->MaxNameLen = MAX_SYM_NAME;
|
||||
union { DWORD64 a; DWORD b; } displacement;
|
||||
IMAGEHLP_LINE64 line;
|
||||
bool got_line = SymGetLineFromAddr64(proc, (DWORD64)addr, &displacement.b, &line);
|
||||
if(SymFromAddr(proc, (DWORD64)addr, &displacement.a, symbol)) {
|
||||
if(got_line) {
|
||||
IMAGEHLP_STACK_FRAME frame;
|
||||
frame.InstructionOffset = symbol->Address;
|
||||
// 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
|
||||
// function fails but GetLastError returns ERROR_SUCCESS."
|
||||
// This is the stupidest fucking api I've ever worked with.
|
||||
if(SymSetContext(proc, &frame, nullptr) == FALSE && GetLastError() != ERROR_SUCCESS) {
|
||||
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,
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
reinterpret_cast<uintptr_t>(addr),
|
||||
0,
|
||||
0,
|
||||
"",
|
||||
""
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@ -10,39 +10,39 @@
|
||||
#include <dlfcn.h>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
namespace libdl {
|
||||
stacktrace_frame resolve_frame(const void* addr) {
|
||||
Dl_info info;
|
||||
if(dladdr(addr, &info)) { // thread-safe
|
||||
return {
|
||||
reinterpret_cast<uintptr_t>(addr),
|
||||
0,
|
||||
0,
|
||||
info.dli_fname ? info.dli_fname : "",
|
||||
info.dli_sname ? info.dli_sname : ""
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
reinterpret_cast<uintptr_t>(addr),
|
||||
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;
|
||||
}
|
||||
namespace detail {
|
||||
namespace libdl {
|
||||
stacktrace_frame resolve_frame(const void* addr) {
|
||||
Dl_info info;
|
||||
if(dladdr(addr, &info)) { // thread-safe
|
||||
return {
|
||||
reinterpret_cast<uintptr_t>(addr),
|
||||
0,
|
||||
0,
|
||||
info.dli_fname ? info.dli_fname : "",
|
||||
info.dli_sname ? info.dli_sname : ""
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
reinterpret_cast<uintptr_t>(addr),
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -17,80 +17,80 @@
|
||||
#endif
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
namespace libbacktrace {
|
||||
int full_callback(void* data, uintptr_t address, const char* file, int line, const char* symbol) {
|
||||
stacktrace_frame& frame = *static_cast<stacktrace_frame*>(data);
|
||||
if(line == 0) {
|
||||
///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;
|
||||
}
|
||||
namespace detail {
|
||||
namespace libbacktrace {
|
||||
int full_callback(void* data, uintptr_t address, const char* file, int line, const char* symbol) {
|
||||
stacktrace_frame& frame = *static_cast<stacktrace_frame*>(data);
|
||||
if(line == 0) {
|
||||
///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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -6,19 +6,19 @@
|
||||
#include <vector>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
namespace nothing {
|
||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
|
||||
return std::vector<stacktrace_frame>(frames.size(), {
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
"",
|
||||
""
|
||||
});
|
||||
}
|
||||
}
|
||||
namespace detail {
|
||||
namespace nothing {
|
||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
|
||||
return std::vector<stacktrace_frame>(frames.size(), {
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
"",
|
||||
""
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -8,15 +8,15 @@
|
||||
#include <vector>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
#ifdef CPPTRACE_HARD_MAX_FRAMES
|
||||
constexpr size_t hard_max_frames = CPPTRACE_HARD_MAX_FRAMES;
|
||||
#else
|
||||
constexpr size_t hard_max_frames = 100;
|
||||
#endif
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::vector<void*> capture_frames(size_t skip);
|
||||
}
|
||||
namespace detail {
|
||||
#ifdef CPPTRACE_HARD_MAX_FRAMES
|
||||
constexpr size_t hard_max_frames = CPPTRACE_HARD_MAX_FRAMES;
|
||||
#else
|
||||
constexpr size_t hard_max_frames = 100;
|
||||
#endif
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::vector<void*> capture_frames(size_t skip);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -11,17 +11,17 @@
|
||||
#include <execinfo.h>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::vector<void*> capture_frames(size_t skip) {
|
||||
std::vector<void*> frames(hard_max_frames + skip, nullptr);
|
||||
const int n_frames = backtrace(frames.data(), int(hard_max_frames + skip)); // thread safe
|
||||
frames.resize(n_frames);
|
||||
frames.erase(frames.begin(), frames.begin() + ptrdiff_t(std::min(skip + 1, frames.size())));
|
||||
frames.shrink_to_fit();
|
||||
return frames;
|
||||
}
|
||||
namespace detail {
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::vector<void*> capture_frames(size_t skip) {
|
||||
std::vector<void*> frames(hard_max_frames + skip, nullptr);
|
||||
const int n_frames = backtrace(frames.data(), int(hard_max_frames + skip)); // thread safe
|
||||
frames.resize(n_frames);
|
||||
frames.erase(frames.begin(), frames.begin() + ptrdiff_t(std::min(skip + 1, frames.size())));
|
||||
frames.shrink_to_fit();
|
||||
return frames;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -6,11 +6,11 @@
|
||||
#include <vector>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
std::vector<void*> capture_frames(size_t) {
|
||||
return {};
|
||||
}
|
||||
namespace detail {
|
||||
std::vector<void*> capture_frames(size_t) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -13,52 +13,52 @@
|
||||
#include <unwind.h>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
struct unwind_state {
|
||||
std::size_t skip;
|
||||
std::size_t count;
|
||||
std::vector<void*>& vec;
|
||||
};
|
||||
namespace detail {
|
||||
struct unwind_state {
|
||||
std::size_t skip;
|
||||
std::size_t count;
|
||||
std::vector<void*>& vec;
|
||||
};
|
||||
|
||||
_Unwind_Reason_Code unwind_callback(_Unwind_Context* context, void* arg) {
|
||||
unwind_state& state = *static_cast<unwind_state*>(arg);
|
||||
if(state.skip) {
|
||||
state.skip--;
|
||||
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()) {
|
||||
_Unwind_Reason_Code unwind_callback(_Unwind_Context* context, void* arg) {
|
||||
unwind_state& state = *static_cast<unwind_state*>(arg);
|
||||
if(state.skip) {
|
||||
state.skip--;
|
||||
if(_Unwind_GetIP(context) == uintptr_t(0)) {
|
||||
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;
|
||||
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;
|
||||
} 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
|
||||
|
||||
@ -10,16 +10,16 @@
|
||||
#include <windows.h>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::vector<void*> capture_frames(size_t skip) {
|
||||
std::vector<PVOID> addrs(hard_max_frames, nullptr);
|
||||
int frames = CaptureStackBackTrace(static_cast<DWORD>(skip + 1), hard_max_frames, addrs.data(), NULL);
|
||||
addrs.resize(frames);
|
||||
addrs.shrink_to_fit();
|
||||
return addrs;
|
||||
}
|
||||
namespace detail {
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::vector<void*> capture_frames(size_t skip) {
|
||||
std::vector<PVOID> addrs(hard_max_frames, nullptr);
|
||||
int frames = CaptureStackBackTrace(static_cast<DWORD>(skip + 1), hard_max_frames, addrs.data(), NULL);
|
||||
addrs.resize(frames);
|
||||
addrs.shrink_to_fit();
|
||||
return addrs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Loading…
Reference in New Issue
Block a user