mirror of
https://github.com/pocoproject/poco.git
synced 2025-03-28 09:32:52 +01:00

* chore(Trace): add dev env settings * add(Trace): init add Poco::trace and libbacktrace files * feat(Exception): generate stack trace if enabled at compile time * chore(DNSSD): remove binaries from git * fix(Trace): build * chore(ci): exclude exception text tests for trace build; add debug test script params * chore(build): mac (dl) * chore(cmake): Changes to build Trace with CMake. * chore(cmake): Changes to build Trace on Windows * chore(cmake): Improvements to include trace sample. * chore(cmake): Fixes to properly build/link Trace on Linux * chore(cmake): add_definitions --> add_compile_definitions * chore(cmake): Build Trace as static and don't export it. * chore(make): Link Trace with built-in libbacktrace on Linux * chore(Trace): remove unnecessary sources for libbacktrace. * chore(github): enable trace on a few github checks * chore(cmake): Build Trace with clang++ on Linux. * chore(cmake): Properly set POCO_ENABLE_TRACE globally when needed. * fix(cmake): Trace: corrected include for clang on Linux --------- Co-authored-by: Matej Kenda <matejken@gmail.com>
443 lines
16 KiB
C++
443 lines
16 KiB
C++
#include <ctrace/ctrace.h>
|
|
#include <cpptrace/cpptrace.hpp>
|
|
#include <algorithm>
|
|
|
|
#include "symbols/symbols.hpp"
|
|
#include "unwind/unwind.hpp"
|
|
#include "demangle/demangle.hpp"
|
|
#include "platform/exception_type.hpp"
|
|
#include "utils/common.hpp"
|
|
#include "utils/utils.hpp"
|
|
#include "binary/object.hpp"
|
|
#include "binary/safe_dl.hpp"
|
|
|
|
#define ESC "\033["
|
|
#define RESET ESC "0m"
|
|
#define RED ESC "31m"
|
|
#define GREEN ESC "32m"
|
|
#define YELLOW ESC "33m"
|
|
#define BLUE ESC "34m"
|
|
#define MAGENTA ESC "35m"
|
|
#define CYAN ESC "36m"
|
|
|
|
#if defined(__GNUC__) && ((__GNUC__ > 2) || (__GNUC__ == 2 && __GNUC_MINOR__ >= 6))
|
|
# define CTRACE_GNU_FORMAT(...) __attribute__((format(__VA_ARGS__)))
|
|
#elif defined(__clang__)
|
|
// Probably requires llvm >3.5? Not exactly sure.
|
|
# define CTRACE_GNU_FORMAT(...) __attribute__((format(__VA_ARGS__)))
|
|
#else
|
|
# define CTRACE_GNU_FORMAT(...)
|
|
#endif
|
|
|
|
#if defined(__clang__)
|
|
# define CTRACE_FORMAT_PROLOGUE \
|
|
_Pragma("clang diagnostic push") \
|
|
_Pragma("clang diagnostic ignored \"-Wformat-security\"")
|
|
# define CTRACE_FORMAT_EPILOGUE \
|
|
_Pragma("clang diagnostic pop")
|
|
#elif defined(__GNUC_MINOR__)
|
|
# define CTRACE_FORMAT_PROLOGUE \
|
|
_Pragma("GCC diagnostic push") \
|
|
_Pragma("GCC diagnostic ignored \"-Wformat-security\"")
|
|
# define CTRACE_FORMAT_EPILOGUE \
|
|
_Pragma("GCC diagnostic pop")
|
|
#else
|
|
# define CTRACE_FORMAT_PROLOGUE
|
|
# define CTRACE_FORMAT_EPILOGUE
|
|
#endif
|
|
|
|
namespace ctrace {
|
|
static constexpr std::uint32_t invalid_pos = ~0U;
|
|
|
|
CTRACE_FORMAT_PROLOGUE
|
|
template <typename...Args>
|
|
CTRACE_GNU_FORMAT(printf, 2, 0)
|
|
static void ffprintf(std::FILE* f, const char fmt[], Args&&...args) {
|
|
(void)std::fprintf(f, fmt, args...);
|
|
(void)fflush(f);
|
|
}
|
|
CTRACE_FORMAT_EPILOGUE
|
|
|
|
static bool is_empty(std::uint32_t pos) noexcept {
|
|
return pos == invalid_pos;
|
|
}
|
|
|
|
static bool is_empty(const char* str) noexcept {
|
|
return !str || std::char_traits<char>::length(str) == 0;
|
|
}
|
|
|
|
static ctrace_owning_string generate_owning_string(const char* raw_string) noexcept {
|
|
// Returns length to the null terminator.
|
|
std::size_t count = std::char_traits<char>::length(raw_string);
|
|
char* new_string = new char[count + 1];
|
|
std::char_traits<char>::copy(new_string, raw_string, count);
|
|
new_string[count] = '\0';
|
|
return { new_string };
|
|
}
|
|
|
|
static ctrace_owning_string generate_owning_string(const std::string& std_string) {
|
|
return generate_owning_string(std_string.c_str());
|
|
}
|
|
|
|
static void free_owning_string(const char* owned_string) noexcept {
|
|
if(!owned_string) return; // Not necessary but eh
|
|
delete[] owned_string;
|
|
}
|
|
|
|
static void free_owning_string(ctrace_owning_string& owned_string) noexcept {
|
|
free_owning_string(owned_string.data);
|
|
}
|
|
|
|
static ctrace_object_frame convert_object_frame(const cpptrace::object_frame& frame) {
|
|
const char* new_path = generate_owning_string(frame.object_path).data;
|
|
return { frame.raw_address, frame.object_address, new_path };
|
|
}
|
|
|
|
static ctrace_object_trace c_convert(const std::vector<cpptrace::object_frame>& trace) {
|
|
std::size_t count = trace.size();
|
|
auto* frames = new ctrace_object_frame[count];
|
|
std::transform(trace.begin(), trace.end(), frames, convert_object_frame);
|
|
return { frames, count };
|
|
}
|
|
|
|
static ctrace_stacktrace_frame convert_stacktrace_frame(const cpptrace::stacktrace_frame& frame) {
|
|
ctrace_stacktrace_frame new_frame;
|
|
new_frame.raw_address = frame.raw_address;
|
|
new_frame.object_address = frame.object_address;
|
|
new_frame.line = frame.line.value_or(invalid_pos);
|
|
new_frame.column = frame.column.value_or(invalid_pos);
|
|
new_frame.filename = generate_owning_string(frame.filename).data;
|
|
new_frame.symbol = generate_owning_string(cpptrace::detail::demangle(frame.symbol)).data;
|
|
new_frame.is_inline = ctrace_bool(frame.is_inline);
|
|
return new_frame;
|
|
}
|
|
|
|
static cpptrace::stacktrace_frame convert_stacktrace_frame(const ctrace_stacktrace_frame& frame) {
|
|
using nullable_type = cpptrace::nullable<std::uint32_t>;
|
|
static constexpr auto null_v = nullable_type::null().raw_value;
|
|
cpptrace::stacktrace_frame new_frame;
|
|
new_frame.raw_address = frame.raw_address;
|
|
new_frame.object_address = frame.object_address;
|
|
new_frame.line = nullable_type{is_empty(frame.line) ? null_v : frame.line};
|
|
new_frame.column = nullable_type{is_empty(frame.column) ? null_v : frame.column};
|
|
new_frame.filename = frame.filename;
|
|
new_frame.symbol = frame.symbol;
|
|
new_frame.is_inline = bool(frame.is_inline);
|
|
return new_frame;
|
|
}
|
|
|
|
static ctrace_stacktrace c_convert(const std::vector<cpptrace::stacktrace_frame>& trace) {
|
|
std::size_t count = trace.size();
|
|
auto* frames = new ctrace_stacktrace_frame[count];
|
|
std::transform(
|
|
trace.begin(),
|
|
trace.end(), frames,
|
|
static_cast<ctrace_stacktrace_frame(*)(const cpptrace::stacktrace_frame&)>(convert_stacktrace_frame)
|
|
);
|
|
return { frames, count };
|
|
}
|
|
|
|
static cpptrace::stacktrace cpp_convert(const ctrace_stacktrace* ptrace) {
|
|
if(!ptrace || !ptrace->frames) {
|
|
return { };
|
|
}
|
|
std::vector<cpptrace::stacktrace_frame> new_frames;
|
|
new_frames.reserve(ptrace->count);
|
|
for(std::size_t i = 0; i < ptrace->count; ++i) {
|
|
new_frames.push_back(convert_stacktrace_frame(ptrace->frames[i]));
|
|
}
|
|
return cpptrace::stacktrace{std::move(new_frames)};
|
|
}
|
|
}
|
|
|
|
extern "C" {
|
|
// ctrace::string
|
|
ctrace_owning_string ctrace_generate_owning_string(const char* raw_string) {
|
|
return ctrace::generate_owning_string(raw_string);
|
|
}
|
|
|
|
void ctrace_free_owning_string(ctrace_owning_string* string) {
|
|
if(!string) {
|
|
return;
|
|
}
|
|
ctrace::free_owning_string(*string);
|
|
string->data = nullptr;
|
|
}
|
|
|
|
// ctrace::generation:
|
|
CTRACE_FORCE_NO_INLINE
|
|
ctrace_raw_trace ctrace_generate_raw_trace(size_t skip, size_t max_depth) {
|
|
try {
|
|
std::vector<cpptrace::frame_ptr> trace = cpptrace::detail::capture_frames(skip + 1, max_depth);
|
|
std::size_t count = trace.size();
|
|
auto* frames = new ctrace_frame_ptr[count];
|
|
std::copy(trace.data(), trace.data() + count, frames);
|
|
return { frames, count };
|
|
} catch(...) {
|
|
// Don't check rethrow condition, it's risky.
|
|
return { nullptr, 0 };
|
|
}
|
|
}
|
|
|
|
CTRACE_FORCE_NO_INLINE
|
|
ctrace_object_trace ctrace_generate_object_trace(size_t skip, size_t max_depth) {
|
|
try {
|
|
std::vector<cpptrace::object_frame> trace = cpptrace::detail::get_frames_object_info(
|
|
cpptrace::detail::capture_frames(skip + 1, max_depth)
|
|
);
|
|
return ctrace::c_convert(trace);
|
|
} catch(...) { // NOSONAR
|
|
// Don't check rethrow condition, it's risky.
|
|
return { nullptr, 0 };
|
|
}
|
|
}
|
|
|
|
CTRACE_FORCE_NO_INLINE
|
|
ctrace_stacktrace ctrace_generate_trace(size_t skip, size_t max_depth) {
|
|
try {
|
|
std::vector<cpptrace::frame_ptr> frames = cpptrace::detail::capture_frames(skip + 1, max_depth);
|
|
std::vector<cpptrace::stacktrace_frame> trace = cpptrace::detail::resolve_frames(frames);
|
|
return ctrace::c_convert(trace);
|
|
} catch(...) { // NOSONAR
|
|
// Don't check rethrow condition, it's risky.
|
|
return { nullptr, 0 };
|
|
}
|
|
}
|
|
|
|
|
|
// ctrace::freeing:
|
|
void ctrace_free_raw_trace(ctrace_raw_trace* trace) {
|
|
if(!trace) {
|
|
return;
|
|
}
|
|
ctrace_frame_ptr* frames = trace->frames;
|
|
delete[] frames;
|
|
trace->frames = nullptr;
|
|
trace->count = 0;
|
|
}
|
|
|
|
void ctrace_free_object_trace(ctrace_object_trace* trace) {
|
|
if(!trace || !trace->frames) {
|
|
return;
|
|
}
|
|
ctrace_object_frame* frames = trace->frames;
|
|
for(std::size_t i = 0; i < trace->count; ++i) {
|
|
const char* path = frames[i].obj_path;
|
|
ctrace::free_owning_string(path);
|
|
}
|
|
|
|
delete[] frames;
|
|
trace->frames = nullptr;
|
|
trace->count = 0;
|
|
}
|
|
|
|
void ctrace_free_stacktrace(ctrace_stacktrace* trace) {
|
|
if(!trace || !trace->frames) {
|
|
return;
|
|
}
|
|
ctrace_stacktrace_frame* frames = trace->frames;
|
|
for(std::size_t i = 0; i < trace->count; ++i) {
|
|
ctrace::free_owning_string(frames[i].filename);
|
|
ctrace::free_owning_string(frames[i].symbol);
|
|
}
|
|
|
|
delete[] frames;
|
|
trace->frames = nullptr;
|
|
trace->count = 0;
|
|
}
|
|
|
|
// ctrace::resolve:
|
|
ctrace_stacktrace ctrace_resolve_raw_trace(const ctrace_raw_trace* trace) {
|
|
if(!trace || !trace->frames) {
|
|
return { nullptr, 0 };
|
|
}
|
|
try {
|
|
std::vector<cpptrace::frame_ptr> frames(trace->count, 0);
|
|
std::copy(trace->frames, trace->frames + trace->count, frames.begin());
|
|
std::vector<cpptrace::stacktrace_frame> resolved = cpptrace::detail::resolve_frames(frames);
|
|
return ctrace::c_convert(resolved);
|
|
} catch(...) { // NOSONAR
|
|
// Don't check rethrow condition, it's risky.
|
|
return { nullptr, 0 };
|
|
}
|
|
}
|
|
|
|
ctrace_object_trace ctrace_resolve_raw_trace_to_object_trace(const ctrace_raw_trace* trace) {
|
|
if(!trace || !trace->frames) {
|
|
return { nullptr, 0 };
|
|
}
|
|
try {
|
|
std::vector<cpptrace::frame_ptr> frames(trace->count, 0);
|
|
std::copy(trace->frames, trace->frames + trace->count, frames.begin());
|
|
std::vector<cpptrace::object_frame> obj = cpptrace::detail::get_frames_object_info(frames);
|
|
return ctrace::c_convert(obj);
|
|
} catch(...) { // NOSONAR
|
|
// Don't check rethrow condition, it's risky.
|
|
return { nullptr, 0 };
|
|
}
|
|
}
|
|
|
|
ctrace_stacktrace ctrace_resolve_object_trace(const ctrace_object_trace* trace) {
|
|
if(!trace || !trace->frames) {
|
|
return { nullptr, 0 };
|
|
}
|
|
try {
|
|
std::vector<cpptrace::frame_ptr> frames(trace->count, 0);
|
|
std::transform(
|
|
trace->frames,
|
|
trace->frames + trace->count,
|
|
frames.begin(),
|
|
[] (const ctrace_object_frame& frame) -> cpptrace::frame_ptr {
|
|
return frame.raw_address;
|
|
}
|
|
);
|
|
std::vector<cpptrace::stacktrace_frame> resolved = cpptrace::detail::resolve_frames(frames);
|
|
return ctrace::c_convert(resolved);
|
|
} catch(...) { // NOSONAR
|
|
// Don't check rethrow condition, it's risky.
|
|
return { nullptr, 0 };
|
|
}
|
|
}
|
|
|
|
// ctrace::safe:
|
|
size_t ctrace_safe_generate_raw_trace(ctrace_frame_ptr* buffer, size_t size, size_t skip, size_t max_depth) {
|
|
return cpptrace::safe_generate_raw_trace(buffer, size, skip, max_depth);
|
|
}
|
|
|
|
void ctrace_get_safe_object_frame(ctrace_frame_ptr address, ctrace_safe_object_frame* out) {
|
|
// TODO: change this?
|
|
static_assert(sizeof(cpptrace::safe_object_frame) == sizeof(ctrace_safe_object_frame), "");
|
|
cpptrace::get_safe_object_frame(address, reinterpret_cast<cpptrace::safe_object_frame*>(out));
|
|
}
|
|
|
|
ctrace_bool can_signal_safe_unwind() {
|
|
return cpptrace::can_signal_safe_unwind();
|
|
}
|
|
|
|
// ctrace::io:
|
|
ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color) {
|
|
if(!trace || !trace->frames) {
|
|
return ctrace::generate_owning_string("<empty trace>");
|
|
}
|
|
auto cpp_trace = ctrace::cpp_convert(trace);
|
|
std::string trace_string = cpp_trace.to_string(bool(use_color));
|
|
return ctrace::generate_owning_string(trace_string);
|
|
}
|
|
|
|
void ctrace_print_stacktrace(const ctrace_stacktrace* trace, FILE* to, ctrace_bool use_color) {
|
|
if(
|
|
use_color && (
|
|
(to == stdout && cpptrace::isatty(cpptrace::stdout_fileno)) ||
|
|
(to == stderr && cpptrace::isatty(cpptrace::stderr_fileno))
|
|
)
|
|
) {
|
|
cpptrace::detail::enable_virtual_terminal_processing_if_needed();
|
|
}
|
|
ctrace::ffprintf(to, "Stack trace (most recent call first):\n");
|
|
if(trace->count == 0 || !trace->frames) {
|
|
ctrace::ffprintf(to, "<empty trace>\n");
|
|
return;
|
|
}
|
|
const auto reset = use_color ? ESC "0m" : "";
|
|
const auto green = use_color ? ESC "32m" : "";
|
|
const auto yellow = use_color ? ESC "33m" : "";
|
|
const auto blue = use_color ? ESC "34m" : "";
|
|
const auto frame_number_width = cpptrace::detail::n_digits(unsigned(trace->count - 1));
|
|
ctrace_stacktrace_frame* frames = trace->frames;
|
|
for(std::size_t i = 0; i < trace->count; ++i) {
|
|
static constexpr auto ptr_len = 2 * sizeof(cpptrace::frame_ptr);
|
|
ctrace::ffprintf(to, "#%-*llu ", int(frame_number_width), i);
|
|
if(frames[i].is_inline) {
|
|
(void)std::fprintf(to, "%*s",
|
|
int(ptr_len + 2),
|
|
"(inlined)");
|
|
} else {
|
|
(void)std::fprintf(to, "%s0x%0*llx%s",
|
|
blue,
|
|
int(ptr_len),
|
|
cpptrace::detail::to_ull(frames[i].raw_address),
|
|
reset);
|
|
}
|
|
if(!ctrace::is_empty(frames[i].symbol)) {
|
|
(void)std::fprintf(to, " in %s%s%s",
|
|
yellow,
|
|
frames[i].symbol,
|
|
reset);
|
|
}
|
|
if(!ctrace::is_empty(frames[i].filename)) {
|
|
(void)std::fprintf(to, " at %s%s%s",
|
|
green,
|
|
frames[i].filename,
|
|
reset);
|
|
if(ctrace::is_empty(frames[i].line)) {
|
|
ctrace::ffprintf(to, "\n");
|
|
continue;
|
|
}
|
|
(void)std::fprintf(to, ":%s%llu%s",
|
|
blue,
|
|
cpptrace::detail::to_ull(frames[i].line),
|
|
reset);
|
|
if(ctrace::is_empty(frames[i].column)) {
|
|
ctrace::ffprintf(to, "\n");
|
|
continue;
|
|
}
|
|
(void)std::fprintf(to, ":%s%llu%s",
|
|
blue,
|
|
cpptrace::detail::to_ull(frames[i].column),
|
|
reset);
|
|
}
|
|
// always print newline at end :M
|
|
ctrace::ffprintf(to, "\n");
|
|
}
|
|
}
|
|
|
|
// utility::demangle:
|
|
ctrace_owning_string ctrace_demangle(const char* mangled) {
|
|
if(!mangled) {
|
|
return ctrace::generate_owning_string("");
|
|
}
|
|
std::string demangled = cpptrace::demangle(mangled);
|
|
return ctrace::generate_owning_string(demangled);
|
|
}
|
|
|
|
// utility::io
|
|
int ctrace_stdin_fileno(void) {
|
|
return cpptrace::stdin_fileno;
|
|
}
|
|
|
|
int ctrace_stderr_fileno(void) {
|
|
return cpptrace::stderr_fileno;
|
|
}
|
|
|
|
int ctrace_stdout_fileno(void) {
|
|
return cpptrace::stdout_fileno;
|
|
}
|
|
|
|
ctrace_bool ctrace_isatty(int fd) {
|
|
return cpptrace::isatty(fd);
|
|
}
|
|
|
|
// utility::cache:
|
|
void ctrace_set_cache_mode(ctrace_cache_mode mode) {
|
|
static constexpr auto cache_max = cpptrace::cache_mode::prioritize_speed;
|
|
if(mode > unsigned(cache_max)) {
|
|
return;
|
|
}
|
|
auto cache_mode = static_cast<cpptrace::cache_mode>(mode);
|
|
cpptrace::experimental::set_cache_mode(cache_mode);
|
|
}
|
|
|
|
void ctrace_enable_inlined_call_resolution(ctrace_bool enable) {
|
|
cpptrace::enable_inlined_call_resolution(enable);
|
|
}
|
|
|
|
ctrace_object_frame ctrace_get_object_info(const ctrace_stacktrace_frame* frame) {
|
|
try {
|
|
cpptrace::object_frame new_frame = cpptrace::detail::get_frame_object_info(frame->raw_address);
|
|
return ctrace::convert_object_frame(new_frame);
|
|
} catch(...) {
|
|
return {0, 0, nullptr};
|
|
}
|
|
}
|
|
}
|