Baseline for middle-end system
This commit is contained in:
parent
09ccc95814
commit
5dc819186e
@ -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
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
84
src/symbols/symbols_core.cpp
Normal file
84
src/symbols/symbols_core.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
@ -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,
|
||||||
""
|
"",
|
||||||
});
|
""
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user