Baseline for middle-end system

This commit is contained in:
Jeremy 2023-09-11 11:57:01 -04:00
parent 09ccc95814
commit 5dc819186e
No known key found for this signature in database
GPG Key ID: 3E11861CB34E158C
9 changed files with 1548 additions and 1366 deletions

View File

@ -224,6 +224,7 @@ set(
src/symbols/symbols_with_libbacktrace.cpp src/symbols/symbols_with_libbacktrace.cpp
src/symbols/symbols_with_libdwarf.cpp src/symbols/symbols_with_libdwarf.cpp
src/symbols/symbols_with_nothing.cpp src/symbols/symbols_with_nothing.cpp
src/symbols/symbols_core.cpp
src/unwind/unwind_with_execinfo.cpp src/unwind/unwind_with_execinfo.cpp
src/unwind/unwind_with_nothing.cpp src/unwind/unwind_with_nothing.cpp
src/unwind/unwind_with_unwind.cpp src/unwind/unwind_with_unwind.cpp

View File

@ -8,6 +8,36 @@
namespace cpptrace { namespace cpptrace {
namespace detail { 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); std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames);
} }
} }

View File

@ -0,0 +1,84 @@
#include "symbols.hpp"
#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
void apply_trace(
std::vector<stacktrace_frame>& result,
const 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_LIBBACKTRACE
apply_trace(trace, libbacktrace::resolve_frames(frames));
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
apply_trace(trace, libdwarf::resolve_frames(frames));
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDL
apply_trace(trace, libdl::resolve_frames(frames));
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE
apply_trace(trace, addr2line::resolve_frames(frames));
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP
apply_trace(trace, dbghelp::resolve_frames(frames));
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_NOTHING
apply_trace(trace, nothing::resolve_frames(frames));
#endif
return trace;
}
}
}

View File

@ -24,310 +24,320 @@
namespace cpptrace { namespace cpptrace {
namespace detail { namespace detail {
#if IS_LINUX || IS_APPLE namespace addr2line {
bool has_addr2line() { #if IS_LINUX || IS_APPLE
static std::mutex mutex; bool has_addr2line() {
static bool has_addr2line = false; static std::mutex mutex;
static bool checked = false; static bool has_addr2line = false;
std::lock_guard<std::mutex> lock(mutex); static bool checked = false;
if(!checked) { std::lock_guard<std::mutex> lock(mutex);
checked = true; if(!checked) {
// Detects if addr2line exists by trying to invoke addr2line --help checked = true;
constexpr int magic = 42; // 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;
internal_verify(pipe(output_pipe.data) == 0);
internal_verify(pipe(input_pipe.data) == 0);
// 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 ""; } // error? TODO: Diagnostic
if(pid == 0) { // child if(pid == 0) { // child
close(STDOUT_FILENO); dup2(output_pipe.write_end, STDOUT_FILENO);
close(STDERR_FILENO); // atos --help writes to stderr 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 #ifdef CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH
#if !IS_APPLE #if !IS_APPLE
execlp("addr2line", "addr2line", "--help", nullptr); execlp("addr2line", "addr2line", "-e", executable.c_str(), "-f", "-C", "-p", nullptr);
#else
execlp("atos", "atos", "--help", nullptr);
#endif
#else #else
#ifndef CPPTRACE_ADDR2LINE_PATH execlp("atos", "atos", "-o", executable.c_str(), nullptr);
#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 #endif
_exit(magic); #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(), nullptr);
#endif
#endif
_exit(1); // TODO: Diagnostic?
} }
int status; internal_verify(write(input_pipe.write_end, addresses.data(), addresses.size()) != -1);
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;
internal_verify(pipe(output_pipe.data) == 0);
internal_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.read_end);
close(input_pipe.write_end); close(input_pipe.write_end);
close(STDERR_FILENO); // TODO: Might be worth conditionally enabling or piping close(output_pipe.write_end);
#ifdef CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH std::string output;
#if !IS_APPLE constexpr int buffer_size = 4096;
execlp("addr2line", "addr2line", "-e", executable.c_str(), "-f", "-C", "-p", nullptr); char buffer[buffer_size];
#else size_t count = 0;
execlp("atos", "atos", "-o", executable.c_str(), nullptr); while((count = read(output_pipe.read_end, buffer, buffer_size)) > 0) {
#endif output.insert(output.end(), buffer, buffer + count);
#else }
#ifndef CPPTRACE_ADDR2LINE_PATH // TODO: check status from addr2line?
#error "CPPTRACE_ADDR2LINE_PATH must be defined if CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH is not" waitpid(pid, nullptr, 0);
#endif return output;
#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(), nullptr);
#endif
#endif
_exit(1); // TODO: Diagnostic?
} }
internal_verify(write(input_pipe.write_end, addresses.data(), addresses.size()) != -1); #elif IS_WINDOWS
close(input_pipe.read_end); bool has_addr2line() {
close(input_pipe.write_end); static std::mutex mutex;
close(output_pipe.write_end); static bool has_addr2line = false;
std::string output; static bool checked = false;
constexpr int buffer_size = 4096; std::lock_guard<std::mutex> lock(mutex);
char buffer[buffer_size]; if(!checked) {
size_t count = 0; // TODO: Popen is a hack. Implement properly with CreateProcess and pipes later.
while((count = read(output_pipe.read_end, buffer, buffer_size)) > 0) { checked = true;
output.insert(output.end(), buffer, buffer + count); #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;
} }
// TODO: check status from addr2line?
waitpid(pid, nullptr, 0); std::string resolve_addresses(const std::string& addresses, const std::string& executable) {
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. // TODO: Popen is a hack. Implement properly with CreateProcess and pipes later.
checked = true; ///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 --version", "r"); FILE* p = popen(("addr2line -e \"" + executable + "\" -fCp " + addresses).c_str(), "r");
#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
FILE* p = popen(CPPTRACE_ADDR2LINE_PATH " --version", "r");
#endif #endif
if(p) { FILE* p = popen(
has_addr2line = pclose(p) == 0; (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;
} }
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 #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>>>; using target_vec = std::vector<std::pair<std::string, std::reference_wrapper<stacktrace_frame>>>;
// NOLINTNEXTLINE(readability-convert-member-functions-to-static) // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
std::unordered_map<std::string, target_vec> get_addr2line_targets( std::unordered_map<std::string, target_vec> get_addr2line_targets(
const std::vector<dlframe>& dlframes, const std::vector<dlframe>& dlframes,
std::vector<stacktrace_frame>& trace std::vector<stacktrace_frame>& trace
) { ) {
std::unordered_map<std::string, target_vec> entries; std::unordered_map<std::string, target_vec> entries;
for(std::size_t i = 0; i < dlframes.size(); i++) { for(std::size_t i = 0; i < dlframes.size(); i++) {
const auto& entry = dlframes[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 // 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`. // on macos when looking up the shared object containing `start`.
if(!entry.obj_path.empty()) { 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()); ///fprintf(
try { /// stderr,
entries[entry.obj_path].emplace_back( /// "%s %s\n",
to_hex(entry.obj_address), /// to_hex(entry.raw_address).c_str(),
trace[i] /// to_hex(entry.raw_address - entry.obj_base + base).c_str()
); ///);
} catch(file_error&) { try {
// entries[entry.obj_path].emplace_back(
} catch(...) { to_hex(entry.obj_address),
throw; trace[i]
} );
// Set what is known for now, and resolutions from addr2line should overwrite } catch(file_error&) {
trace[i].filename = entry.obj_path; //
trace[i].symbol = entry.symbol; } catch(...) {
} throw;
} }
return entries; // Set what is known for now, and resolutions from addr2line should overwrite
} trace[i].filename = entry.obj_path;
trace[i].symbol = entry.symbol;
// 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 {
internal_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(':');
internal_verify(colon != std::string::npos);
internal_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);
internal_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);
internal_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);
internal_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");
internal_verify(output.size() == entries_vec.size());
for(size_t i = 0; i < output.size(); i++) {
update_trace(output[i], i, entries_vec);
} }
} }
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 {
internal_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(':');
internal_verify(colon != std::string::npos);
internal_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);
internal_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);
internal_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);
internal_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");
internal_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 trace;
} }
} }
} }

View File

@ -15,382 +15,413 @@
namespace cpptrace { namespace cpptrace {
namespace detail { namespace detail {
// SymFromAddr only returns the function's name. In order to get information about parameters, namespace dbghelp {
// 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 { // SymFromAddr only returns the function's name. In order to get information about parameters,
TI_GET_SYMTAG, TI_GET_SYMNAME, TI_GET_LENGTH, TI_GET_TYPE, TI_GET_TYPEID, TI_GET_BASETYPE, // important for C++ stack traces where functions may be overloaded, we have to manually use
TI_GET_ARRAYINDEXTYPEID, TI_FINDCHILDREN, TI_GET_DATAKIND, TI_GET_ADDRESSOFFSET, // Windows DIA to walk debug info structures. Resources:
TI_GET_OFFSET, TI_GET_VALUE, TI_GET_COUNT, TI_GET_CHILDRENCOUNT, TI_GET_BITPOSITION, // https://web.archive.org/web/20201027025750/http://www.debuginfo.com/articles/dbghelptypeinfo.html
TI_GET_VIRTUALBASECLASS, TI_GET_VIRTUALTABLESHAPEID, TI_GET_VIRTUALBASEPOINTEROFFSET, // https://web.archive.org/web/20201203160805/http://www.debuginfo.com/articles/dbghelptypeinfofigures.html
TI_GET_CLASSPARENTID, TI_GET_NESTED, TI_GET_SYMINDEX, TI_GET_LEXICALPARENT, TI_GET_ADDRESS, // https://github.com/DynamoRIO/dynamorio/blob/master/ext/drsyms/drsyms_windows.c#L1370-L1439
TI_GET_THISADJUST, TI_GET_UDTKIND, TI_IS_EQUIV_TO, TI_GET_CALLING_CONVENTION, // TODO: Currently unable to detect rvalue references
TI_IS_CLOSE_EQUIV_TO, TI_GTIEX_REQS_VALID, TI_GET_VIRTUALBASEOFFSET, // TODO: Currently unable to detect const
TI_GET_VIRTUALBASEDISPINDEX, TI_GET_IS_REFERENCE, TI_GET_INDIRECTVIRTUALBASECLASS, enum class SymTagEnum {
TI_GET_VIRTUALBASETABLETYPE, TI_GET_OBJECTPOINTERTYPE, IMAGEHLP_SYMBOL_TYPE_INFO_MAX 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 BasicType { enum class IMAGEHLP_SYMBOL_TYPE_INFO {
btNoType = 0, btVoid = 1, btChar = 2, btWChar = 3, btInt = 6, btUInt = 7, btFloat = 8, TI_GET_SYMTAG, TI_GET_SYMNAME, TI_GET_LENGTH, TI_GET_TYPE, TI_GET_TYPEID, TI_GET_BASETYPE,
btBCD = 9, btBool = 10, btLong = 13, btULong = 14, btCurrency = 25, btDate = 26, TI_GET_ARRAYINDEXTYPEID, TI_FINDCHILDREN, TI_GET_DATAKIND, TI_GET_ADDRESSOFFSET,
btVariant = 27, btComplex = 28, btBit = 29, btBSTR = 30, btHresult = 31 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
};
// SymGetTypeInfo utility enum class BasicType {
template<typename T, IMAGEHLP_SYMBOL_TYPE_INFO SymType, bool FAILABLE = false> btNoType = 0, btVoid = 1, btChar = 2, btWChar = 3, btInt = 6, btUInt = 7, btFloat = 8,
T get_info(ULONG type_index, HANDLE proc, ULONG64 modbase) { btBCD = 9, btBool = 10, btLong = 13, btULong = 14, btCurrency = 25, btDate = 26,
T info; btVariant = 27, btComplex = 28, btBit = 29, btBSTR = 30, btHresult = 31
if(!SymGetTypeInfo(proc, modbase, type_index, static_cast<::IMAGEHLP_SYMBOL_TYPE_INFO>(SymType), &info)) { };
if(FAILABLE) {
return (T)-1; // SymGetTypeInfo utility
} else { 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( 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()
); );
} }
} // special case to properly free a buffer and convert string to narrow chars, only used for
return info; // TI_GET_SYMNAME
} static_assert(
SymType == IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_SYMNAME,
template<IMAGEHLP_SYMBOL_TYPE_INFO SymType, bool FAILABLE = false> "get_info_wchar called with unexpected IMAGEHLP_SYMBOL_TYPE_INFO"
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()
); );
} std::wstring wstr(info);
// special case to properly free a buffer and convert string to narrow chars, only used for TI_GET_SYMNAME std::string str;
static_assert(SymType == IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_SYMNAME, "get_info_wchar called with unexpected IMAGEHLP_SYMBOL_TYPE_INFO"); str.reserve(wstr.size());
std::wstring wstr(info); for(const auto c : wstr) {
std::string str; str.push_back(static_cast<char>(c));
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
);
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: { LocalFree(info);
DWORD underlying_type_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_TYPEID>( return str;
type_index, }
proc,
modbase // Translate basic types to string
); static std::string get_basic_type(ULONG type_index, HANDLE proc, ULONG64 modbase) {
DWORD length = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_COUNT>( auto basic_type = get_info<BasicType, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_BASETYPE>(
type_index, type_index,
proc, proc,
modbase modbase
); );
const auto type = lookup_type(underlying_type_id, proc, modbase); //auto length = get_info<ULONG64, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_LENGTH>(type_index, proc, modbase);
return {type.base, "[" + std::to_string(length) + "]" + type.extent}; 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>";
} }
case SymTagEnum::SymTagFunctionType: { }
DWORD return_type_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_TYPEID>(
type_index, static std::string resolve_type(ULONG type_index, HANDLE proc, ULONG64 modbase);
proc,
modbase struct class_name_result {
); bool has_class_name;
DWORD n_children = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_COUNT, true>( std::string name;
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
n_children -= n_ignore; // this must be ignored before TI_FINDCHILDREN_PARAMS::Count is set, else error
// 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)) + ">",
""
};
}; };
} // Helper for member pointers
static class_name_result lookup_class_name(ULONG type_index, HANDLE proc, ULONG64 modbase) {
static std::string resolve_type(ULONG type_index, HANDLE proc, ULONG64 modbase) { DWORD class_parent_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_CLASSPARENTID, true>(
const auto type = lookup_type(type_index, proc, modbase); type_index,
return type.base + type.extent; proc,
} modbase
);
struct function_info { if(class_parent_id == (DWORD)-1) {
HANDLE proc; return {false, ""};
ULONG64 modbase; } else {
int counter; return {true, resolve_type(class_parent_id, proc, modbase)};
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;
}
std::mutex dbghelp_lock; 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)) +
">",
""
};
};
}
// TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions static std::string resolve_type(ULONG type_index, HANDLE proc, ULONG64 modbase) {
stacktrace_frame resolve_frame(HANDLE proc, void* addr) { const auto type = lookup_type(type_index, proc, modbase);
const std::lock_guard<std::mutex> lock(dbghelp_lock); // all dbghelp functions are not thread safe return type.base + type.extent;
alignas(SYMBOL_INFO) char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; }
SYMBOL_INFO* symbol = (SYMBOL_INFO*)buffer;
symbol->SizeOfStruct = sizeof(SYMBOL_INFO); struct function_info {
symbol->MaxNameLen = MAX_SYM_NAME; HANDLE proc;
union { DWORD64 a; DWORD b; } displacement; ULONG64 modbase;
IMAGEHLP_LINE64 line; int counter;
bool got_line = SymGetLineFromAddr64(proc, (DWORD64)addr, &displacement.b, &line); int n_children;
if(SymFromAddr(proc, (DWORD64)addr, &displacement.a, symbol)) { int n_ignore;
if(got_line) { std::string str;
IMAGEHLP_STACK_FRAME frame; };
frame.InstructionOffset = symbol->Address;
// https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symsetcontext // Enumerates function parameters
// "If you call SymSetContext to set the context to its current value, the static BOOL __stdcall enumerator_callback(
// function fails but GetLastError returns ERROR_SUCCESS." PSYMBOL_INFO symbol_info,
// This is the stupidest fucking api I've ever worked with. ULONG,
if(SymSetContext(proc, &frame, nullptr) == FALSE && GetLastError() != ERROR_SUCCESS) { PVOID data
fprintf(stderr, "Stack trace: Internal error while calling SymSetContext\n"); ) {
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;
// 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 { return {
reinterpret_cast<uintptr_t>(addr), reinterpret_cast<uintptr_t>(addr),
static_cast<std::uint_least32_t>(line.LineNumber), static_cast<std::uint_least32_t>(line.LineNumber),
0, 0,
line.FileName, line.FileName,
signature
};
} else {
return {
reinterpret_cast<uintptr_t>(addr),
0,
0,
"",
symbol->Name 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 { } else {
return { return {
reinterpret_cast<uintptr_t>(addr), reinterpret_cast<uintptr_t>(addr),
0, 0,
0, 0,
"", "",
symbol->Name ""
}; };
} }
} 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; 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;
}
} }
} }
} }

View File

@ -11,34 +11,36 @@
namespace cpptrace { namespace cpptrace {
namespace detail { namespace detail {
stacktrace_frame resolve_frame(const void* addr) { namespace libdl {
Dl_info info; stacktrace_frame resolve_frame(const void* addr) {
if(dladdr(addr, &info)) { // thread-safe Dl_info info;
return { if(dladdr(addr, &info)) { // thread-safe
reinterpret_cast<uintptr_t>(addr), return {
0, reinterpret_cast<uintptr_t>(addr),
0, 0,
info.dli_fname ? info.dli_fname : "", 0,
info.dli_sname ? info.dli_sname : "" info.dli_fname ? info.dli_fname : "",
}; info.dli_sname ? info.dli_sname : ""
} else { };
return { } else {
reinterpret_cast<uintptr_t>(addr), return {
0, reinterpret_cast<uintptr_t>(addr),
0, 0,
"", 0,
"" "",
}; ""
};
}
} }
}
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; std::vector<stacktrace_frame> trace;
trace.reserve(frames.size()); trace.reserve(frames.size());
for(const void* frame : frames) { for(const void* frame : frames) {
trace.push_back(resolve_frame(frame)); trace.push_back(resolve_frame(frame));
}
return trace;
} }
return trace;
} }
} }
} }

View File

@ -18,75 +18,77 @@
namespace cpptrace { namespace cpptrace {
namespace detail { namespace detail {
int full_callback(void* data, uintptr_t address, const char* file, int line, const char* symbol) { namespace libbacktrace {
stacktrace_frame& frame = *static_cast<stacktrace_frame*>(data); int full_callback(void* data, uintptr_t address, const char* file, int line, const char* symbol) {
if(line == 0) { stacktrace_frame& frame = *static_cast<stacktrace_frame*>(data);
///fprintf(stderr, "Getting bad data for some reason\n"); // TODO: Eliminate 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;
} }
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) { void syminfo_callback(void* data, uintptr_t address, const char* symbol, uintptr_t, uintptr_t) {
stacktrace_frame& frame = *static_cast<stacktrace_frame*>(data); stacktrace_frame& frame = *static_cast<stacktrace_frame*>(data);
frame.address = address; frame.address = address;
frame.line = 0; frame.line = 0;
frame.filename = ""; frame.filename = "";
frame.symbol = symbol ? symbol : ""; 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 void error_callback(void*, const char* msg, int errnum) {
stacktrace_frame resolve_frame(const void* addr) { fprintf(stderr, "Libbacktrace error: %s, code %d\n", msg, errnum);
stacktrace_frame frame; }
frame.col = 0;
backtrace_pcinfo( backtrace_state* get_backtrace_state() {
get_backtrace_state(), static std::mutex mutex;
reinterpret_cast<uintptr_t>(addr), const std::lock_guard<std::mutex> lock(mutex);
full_callback, // backtrace_create_state must be called only one time per program
error_callback, // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
&frame static backtrace_state* state = nullptr;
); static bool called = false;
if(frame.symbol.empty()) { if(!called) {
// fallback, try to at least recover the symbol name with backtrace_syminfo state = backtrace_create_state(program_name(), true, error_callback, nullptr);
backtrace_syminfo( 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(), get_backtrace_state(),
reinterpret_cast<uintptr_t>(addr), reinterpret_cast<uintptr_t>(addr),
syminfo_callback, full_callback,
error_callback, error_callback,
&frame &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;
} }
return frame;
}
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; std::vector<stacktrace_frame> trace;
trace.reserve(frames.size()); trace.reserve(frames.size());
for(const void* frame : frames) { for(const void* frame : frames) {
trace.push_back(resolve_frame(frame)); trace.push_back(resolve_frame(frame));
}
return trace;
} }
return trace;
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -7,14 +7,16 @@
namespace cpptrace { namespace cpptrace {
namespace detail { namespace detail {
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) { namespace nothing {
return std::vector<stacktrace_frame>(frames.size(), { std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
0, return std::vector<stacktrace_frame>(frames.size(), {
0, 0,
0, 0,
"", 0,
"" "",
}); ""
});
}
} }
} }
} }