From 278ee3fcee89608a6fb50eb02b70e8aacbdca711 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sat, 16 Sep 2023 20:46:30 -0400 Subject: [PATCH] Work on improving error handling and some general refactoring. Also trying to bring everything into cpptrace::detail. --- src/cpptrace.cpp | 5 +- src/demangle/demangle_with_cxxabi.cpp | 4 + src/full/full_trace.hpp | 1 + src/full/full_trace_with_libbacktrace.cpp | 4 +- src/platform/common.hpp | 416 +--------------------- src/platform/elf.hpp | 143 ++++---- src/platform/error.hpp | 98 +++++ src/platform/mach-o.hpp | 225 ++++++------ src/platform/object.hpp | 249 ++++++------- src/platform/pe.hpp | 95 ++--- src/platform/program_name.hpp | 100 +++--- src/platform/utils.hpp | 342 ++++++++++++++++++ src/symbols/symbols_core.cpp | 2 + src/symbols/symbols_with_addr2line.cpp | 21 +- src/symbols/symbols_with_libdwarf.cpp | 35 +- src/unwind/unwind.hpp | 1 + src/unwind/unwind_with_execinfo.cpp | 1 + src/unwind/unwind_with_unwind.cpp | 7 +- src/unwind/unwind_with_winapi.cpp | 1 + 19 files changed, 926 insertions(+), 824 deletions(-) create mode 100644 src/platform/error.hpp create mode 100644 src/platform/utils.hpp diff --git a/src/cpptrace.cpp b/src/cpptrace.cpp index be13292..657b494 100644 --- a/src/cpptrace.cpp +++ b/src/cpptrace.cpp @@ -13,6 +13,7 @@ #include "unwind/unwind.hpp" #include "demangle/demangle.hpp" #include "platform/common.hpp" +#include "platform/utils.hpp" namespace cpptrace { CPPTRACE_FORCE_NO_INLINE CPPTRACE_API @@ -58,7 +59,7 @@ namespace cpptrace { namespace cpptrace { CPPTRACE_API void print_trace(std::uint32_t skip) { - enable_virtual_terminal_processing_if_needed(); + detail::enable_virtual_terminal_processing_if_needed(); std::cerr<<"Stack trace (most recent call first):"<"<(trace.size()) - 1); + const auto frame_number_width = detail::n_digits(static_cast(trace.size()) - 1); for(const auto& frame : trace) { std::cerr << '#' diff --git a/src/demangle/demangle_with_cxxabi.cpp b/src/demangle/demangle_with_cxxabi.cpp index a591ad0..2e523c6 100644 --- a/src/demangle/demangle_with_cxxabi.cpp +++ b/src/demangle/demangle_with_cxxabi.cpp @@ -12,7 +12,11 @@ namespace cpptrace { std::string demangle(const std::string& name) { int status; // presumably thread-safe + // it appears safe to pass nullptr for status however the docs don't explicitly say it's safe so I don't + // want to rely on it char* const demangled = abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status); + // demangled will always be nullptr on non-zero status, and if __cxa_demangle ever fails for any reason + // we'll just quietly return the mangled name if(demangled) { const std::string str = demangled; // NOLINTNEXTLINE(cppcoreguidelines-no-malloc) diff --git a/src/full/full_trace.hpp b/src/full/full_trace.hpp index 8d27cbe..f3da9da 100644 --- a/src/full/full_trace.hpp +++ b/src/full/full_trace.hpp @@ -3,6 +3,7 @@ #include #include "../platform/common.hpp" +#include "../platform/utils.hpp" #include #include diff --git a/src/full/full_trace_with_libbacktrace.cpp b/src/full/full_trace_with_libbacktrace.cpp index 16c6e5f..4067571 100644 --- a/src/full/full_trace_with_libbacktrace.cpp +++ b/src/full/full_trace_with_libbacktrace.cpp @@ -3,10 +3,10 @@ #include #include "../platform/program_name.hpp" #include "../platform/common.hpp" +#include "../platform/utils.hpp" #include #include -#include #include #include @@ -50,7 +50,7 @@ namespace cpptrace { } void error_callback(void*, const char* msg, int errnum) { - fprintf(stderr, "Libbacktrace error: %s, code %d\n", msg, errnum); + nonfatal_error(stringf("libbacktrace error: %s, code %d", msg, errnum)); } backtrace_state* get_backtrace_state() { diff --git a/src/platform/common.hpp b/src/platform/common.hpp index 095ba91..e732156 100644 --- a/src/platform/common.hpp +++ b/src/platform/common.hpp @@ -2,30 +2,11 @@ #define COMMON_HPP #ifdef _MSC_VER -#define CPPTRACE_FORCE_NO_INLINE __declspec(noinline) -#define CPPTRACE_PFUNC __FUNCSIG__ -#define CPPTRACE_MAYBE_UNUSED -#pragma warning(push) -#pragma warning(disable: 4505) // Unused local function + #define CPPTRACE_FORCE_NO_INLINE __declspec(noinline) #else -#define CPPTRACE_FORCE_NO_INLINE __attribute__((noinline)) -#define CPPTRACE_PFUNC __extension__ __PRETTY_FUNCTION__ -#define CPPTRACE_MAYBE_UNUSED __attribute__((unused)) + #define CPPTRACE_FORCE_NO_INLINE __attribute__((noinline)) #endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - #define IS_WINDOWS 0 #define IS_LINUX 0 #define IS_APPLE 0 @@ -60,389 +41,24 @@ #error "Unsupported compiler" #endif -#if IS_WINDOWS - #include -#else - #include -#endif +#include +#include +#include -// Lightweight std::source_location. -struct source_location { - // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members) - const char* const file; - //const char* const function; // disabled for now due to static constexpr restrictions - // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members) - const int line; - constexpr source_location( - //const char* _function /*= __builtin_FUNCTION()*/, - const char* _file = __builtin_FILE(), - int _line = __builtin_LINE() - ) : file(_file), /*function(_function),*/ line(_line) {} -}; - -CPPTRACE_MAYBE_UNUSED -static void primitive_assert_impl( - bool condition, - bool verify, - const char* expression, - const char* signature, - source_location location, - const char* message = nullptr -) { - if(!condition) { - const char* action = verify ? "verification" : "assertion"; - const char* name = verify ? "verify" : "assert"; - if(message == nullptr) { - (void) fprintf( - stderr, - "Cpptrace %s failed at %s:%d: %s\n", - action, location.file, location.line, signature - ); - } else { - (void) fprintf( - stderr, - "Cpptrace %s failed at %s:%d: %s: %s\n", - action, location.file, location.line, signature, message - ); +namespace cpptrace { +namespace detail { + // Placed here instead of utils because it's used by error.hpp and utils.hpp + template std::string stringf(T... args) { + int length = snprintf(0, 0, args...); + if(length < 0) { + throw std::logic_error("invalid arguments to stringf"); } - (void) fprintf(stderr, " primitive_%s(%s);\n", name, expression); - std::abort(); + std::string str(length, 0); + // .data is const char* in c++11, but &str[0] should be legal + snprintf(&str[0], length + 1, args...); + return str; } } - -template -void nothing() {} -#define PHONY_USE(E) (nothing()) - -// Still present in release mode, nonfatal -#define internal_verify(c, ...) primitive_assert_impl(c, true, #c, CPPTRACE_PFUNC, {}, ##__VA_ARGS__) - -#ifndef NDEBUG - #define CPPTRACE_PRIMITIVE_ASSERT(c, ...) \ - primitive_assert_impl(c, false, #c, CPPTRACE_PFUNC, {}, ##__VA_ARGS__) -#else - #define CPPTRACE_PRIMITIVE_ASSERT(c, ...) PHONY_USE(c) -#endif - -CPPTRACE_MAYBE_UNUSED -static std::vector split(const std::string& str, const std::string& delims) { - std::vector vec; - size_t old_pos = 0; - size_t pos = 0; - while((pos = str.find_first_of(delims, old_pos)) != std::string::npos) { - vec.emplace_back(str.substr(old_pos, pos - old_pos)); - old_pos = pos + 1; - } - vec.emplace_back(str.substr(old_pos)); - return vec; } -template -CPPTRACE_MAYBE_UNUSED -static std::string join(const C& container, const std::string& delim) { - auto iter = std::begin(container); - auto end = std::end(container); - std::string str; - if(std::distance(iter, end) > 0) { - str += *iter; - while(++iter != end) { - str += delim; - str += *iter; - } - } - return str; -} - -constexpr const char* const whitespace = " \t\n\r\f\v"; - -CPPTRACE_MAYBE_UNUSED -static std::string trim(const std::string& str) { - if(str.empty()) { - return ""; - } - const size_t left = str.find_first_not_of(whitespace); - const size_t right = str.find_last_not_of(whitespace) + 1; - return str.substr(left, right - left); -} - -CPPTRACE_MAYBE_UNUSED -static std::string to_hex(uintptr_t addr) { - std::stringstream sstream; - sstream< -struct byte_swapper; - -template -struct byte_swapper { - T operator()(T val) { - return val; - } -}; - -template -struct byte_swapper { - T operator()(T val) { - return ((((val) >> 8) & 0xff) | (((val) & 0xff) << 8)); - } -}; - -template -struct byte_swapper { - T operator()(T val) { - return ((((val) & 0xff000000) >> 24) | - (((val) & 0x00ff0000) >> 8) | - (((val) & 0x0000ff00) << 8) | - (((val) & 0x000000ff) << 24)); - } -}; - -template -struct byte_swapper { - T operator()(T val) { - return ((((val) & 0xff00000000000000ull) >> 56) | - (((val) & 0x00ff000000000000ull) >> 40) | - (((val) & 0x0000ff0000000000ull) >> 24) | - (((val) & 0x000000ff00000000ull) >> 8 ) | - (((val) & 0x00000000ff000000ull) << 8 ) | - (((val) & 0x0000000000ff0000ull) << 24) | - (((val) & 0x000000000000ff00ull) << 40) | - (((val) & 0x00000000000000ffull) << 56)); - } -}; - -template::value, int>::type = 0> -T byteswap(T value) { - return byte_swapper{}(value); -} - -CPPTRACE_MAYBE_UNUSED -inline void enable_virtual_terminal_processing_if_needed() { - // enable colors / ansi processing if necessary - #if IS_WINDOWS - // https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing - #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING - constexpr DWORD ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4; - #endif - HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); - DWORD dwMode = 0; - if(hOut == INVALID_HANDLE_VALUE) return; - if(!GetConsoleMode(hOut, &dwMode)) return; - if(dwMode != (dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) - if(!SetConsoleMode(hOut, dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) return; - #endif -} - -CPPTRACE_MAYBE_UNUSED -// NOLINTNEXTLINE(misc-no-recursion) -inline constexpr unsigned n_digits(unsigned value) { - return value < 10 ? 1 : 1 + n_digits(value / 10); -} -static_assert(n_digits(1) == 1, "n_digits utility producing the wrong result"); -static_assert(n_digits(9) == 1, "n_digits utility producing the wrong result"); -static_assert(n_digits(10) == 2, "n_digits utility producing the wrong result"); -static_assert(n_digits(11) == 2, "n_digits utility producing the wrong result"); -static_assert(n_digits(1024) == 4, "n_digits utility producing the wrong result"); - -// TODO: Re-evaluate use of off_t -template::value, int>::type = 0> -T load_bytes(FILE* obj_file, off_t offset) { - T object; - internal_verify(fseek(obj_file, offset, SEEK_SET) == 0, "fseek error"); - internal_verify(fread(&object, sizeof(T), 1, obj_file) == 1, "fread error"); - return object; -} - -class file_error : std::exception { - const char* what() const noexcept override { - return "Unable to read file"; - } -}; - -struct nullopt_t {}; - -static constexpr nullopt_t nullopt; - -template::type, void>::value, int>::type = 0> -class optional { - bool holds_value = false; - - union { - T uvalue; - }; - -public: - // clang-tidy false positive - // NOLINTNEXTLINE(modernize-use-equals-default) - optional() noexcept {} - - optional(nullopt_t) noexcept {} - - ~optional() { - reset(); - } - - optional(const optional& other) : holds_value(other.holds_value) { - if(holds_value) { - new (static_cast(std::addressof(uvalue))) T(other.uvalue); - } - } - - optional(optional&& other) noexcept(std::is_nothrow_move_constructible::value) : holds_value(other.holds_value) { - if(holds_value) { - new (static_cast(std::addressof(uvalue))) T(std::move(other.uvalue)); - } - } - - optional& operator=(const optional& other) { - optional copy(other); - swap(*this, copy); - return *this; - } - - optional& operator=(optional&& other) noexcept( - std::is_nothrow_move_assignable::value && std::is_nothrow_move_constructible::value - ) { - reset(); - if(other.holds_value) { - new (static_cast(std::addressof(uvalue))) T(std::move(other.uvalue)); - holds_value = true; - } - return *this; - } - - template< - typename U = T, - typename std::enable_if::type, optional>::value, int>::type = 0 - > - // clang-tidy false positive - // NOLINTNEXTLINE(bugprone-forwarding-reference-overload) - optional(U&& value) : holds_value(true) { - new (static_cast(std::addressof(uvalue))) T(std::forward(value)); - } - - template< - typename U = T, - typename std::enable_if::type, optional>::value, int>::type = 0 - > - optional& operator=(U&& value) { - if(holds_value) { - uvalue = std::forward(value); - } else { - new (static_cast(std::addressof(uvalue))) T(std::forward(value)); - holds_value = true; - } - return *this; - } - - optional& operator=(nullopt_t) noexcept { - reset(); - return *this; - } - - void swap(optional& other) { - if(holds_value && other.holds_value) { - std::swap(uvalue, other.uvalue); - } else if(holds_value && !other.holds_value) { - new (&other.uvalue) T(std::move(uvalue)); - uvalue.~T(); - } else if(!holds_value && other.holds_value) { - new (static_cast(std::addressof(uvalue))) T(std::move(other.uvalue)); - other.uvalue.~T(); - } - std::swap(holds_value, other.holds_value); - } - - bool has_value() const { - return holds_value; - } - - operator bool() const { - return holds_value; - } - - void reset() { - if(holds_value) { - uvalue.~T(); - } - holds_value = false; - } - - T& unwrap() & { - if(!holds_value) { - throw std::runtime_error{"Optional does not contain a value"}; - } - return uvalue; - } - - const T& unwrap() const & { - if(!holds_value) { - throw std::runtime_error{"Optional does not contain a value"}; - } - return uvalue; - } - - T&& unwrap() && { - if(!holds_value) { - throw std::runtime_error{"Optional does not contain a value"}; - } - return std::move(uvalue); - } - - const T&& unwrap() const && { - if(!holds_value) { - throw std::runtime_error{"Optional does not contain a value"}; - } - return std::move(uvalue); - } - - template - T value_or(U&& default_value) const & { - return holds_value ? uvalue : static_cast(std::forward(default_value)); - } - - template - T value_or(U&& default_value) && { - return holds_value ? std::move(uvalue) : static_cast(std::forward(default_value)); - } -}; - -// shamelessly stolen from stackoverflow -CPPTRACE_MAYBE_UNUSED -static bool directory_exists(const std::string& path) { - #if IS_WINDOWS - DWORD dwAttrib = GetFileAttributesA(path.c_str()); - return dwAttrib != INVALID_FILE_ATTRIBUTES && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY); - #else - struct stat sb; - return stat(path.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode); - #endif -} - -CPPTRACE_MAYBE_UNUSED -static std::string basename(const std::string& path) { - // Assumes no trailing /'s - auto pos = path.rfind('/'); - if(pos == std::string::npos) { - return path; - } else { - return path.substr(pos + 1); - } -} - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - #endif diff --git a/src/platform/elf.hpp b/src/platform/elf.hpp index 22e9979..39c1696 100644 --- a/src/platform/elf.hpp +++ b/src/platform/elf.hpp @@ -2,6 +2,7 @@ #define ELF_HPP #include "common.hpp" +#include "utils.hpp" #if IS_LINUX #include @@ -11,82 +12,86 @@ #include -template::value, int>::type = 0> -T elf_byteswap_if_needed(T value, bool elf_is_little) { - if(is_little_endian() == elf_is_little) { - return value; - } else { - return byteswap(value); - } -} - -// TODO: Address code duplication here. Do we actually have to care about 32-bit if the library is compiled as 64-bit? -// I think probably not... - -// TODO: Re-evaluate use of off_t -// I think we can rely on PT_PHDR https://stackoverflow.com/q/61568612/15675011... -static uintptr_t elf_get_module_image_base_from_program_table( - FILE* file, - bool is_64, - bool is_little_endian, - off_t e_phoff, - off_t e_phentsize, - int e_phnum -) { - for(int i = 0; i < e_phnum; i++) { - if(is_64) { - Elf64_Phdr program_header = load_bytes(file, e_phoff + e_phentsize * i); - if(elf_byteswap_if_needed(program_header.p_type, is_little_endian) == PT_PHDR) { - return elf_byteswap_if_needed(program_header.p_vaddr, is_little_endian) - - elf_byteswap_if_needed(program_header.p_offset, is_little_endian); - } +namespace cpptrace { +namespace detail { + template::value, int>::type = 0> + T elf_byteswap_if_needed(T value, bool elf_is_little) { + if(is_little_endian() == elf_is_little) { + return value; } else { - Elf32_Phdr program_header = load_bytes(file, e_phoff + e_phentsize * i); - if(elf_byteswap_if_needed(program_header.p_type, is_little_endian) == PT_PHDR) { - return elf_byteswap_if_needed(program_header.p_vaddr, is_little_endian) - - elf_byteswap_if_needed(program_header.p_offset, is_little_endian); - } + return byteswap(value); } } - return 0; -} -static uintptr_t elf_get_module_image_base(const std::string& obj_path) { - FILE* file = fopen(obj_path.c_str(), "rb"); - if(file == nullptr) { - throw file_error(); + // TODO: Address code duplication here. Do we actually have to care about 32-bit if the library is compiled as + // 64-bit? I think probably not... + + // TODO: Re-evaluate use of off_t + // I think we can rely on PT_PHDR https://stackoverflow.com/q/61568612/15675011... + static uintptr_t elf_get_module_image_base_from_program_table( + FILE* file, + bool is_64, + bool is_little_endian, + off_t e_phoff, + off_t e_phentsize, + int e_phnum + ) { + for(int i = 0; i < e_phnum; i++) { + if(is_64) { + Elf64_Phdr program_header = load_bytes(file, e_phoff + e_phentsize * i); + if(elf_byteswap_if_needed(program_header.p_type, is_little_endian) == PT_PHDR) { + return elf_byteswap_if_needed(program_header.p_vaddr, is_little_endian) + - elf_byteswap_if_needed(program_header.p_offset, is_little_endian); + } + } else { + Elf32_Phdr program_header = load_bytes(file, e_phoff + e_phentsize * i); + if(elf_byteswap_if_needed(program_header.p_type, is_little_endian) == PT_PHDR) { + return elf_byteswap_if_needed(program_header.p_vaddr, is_little_endian) + - elf_byteswap_if_needed(program_header.p_offset, is_little_endian); + } + } + } + return 0; } - // Initial checks/metadata - auto magic = load_bytes>(file, 0); - internal_verify(magic == (std::array{0x7F, 'E', 'L', 'F'})); - bool is_64 = load_bytes(file, 4) == 2; - bool is_little_endian = load_bytes(file, 5) == 1; - internal_verify(load_bytes(file, 6) == 1, "Unexpected ELF version"); - // - if(is_64) { - Elf64_Ehdr file_header = load_bytes(file, 0); - internal_verify(file_header.e_ehsize == sizeof(Elf64_Ehdr)); - return elf_get_module_image_base_from_program_table( - file, - is_64, - is_little_endian, - elf_byteswap_if_needed(file_header.e_phoff, is_little_endian), - elf_byteswap_if_needed(file_header.e_phentsize, is_little_endian), - elf_byteswap_if_needed(file_header.e_phnum, is_little_endian) - ); - } else { - Elf32_Ehdr file_header = load_bytes(file, 0); - internal_verify(file_header.e_ehsize == sizeof(Elf32_Ehdr)); - return elf_get_module_image_base_from_program_table( - file, - is_64, - is_little_endian, - elf_byteswap_if_needed(file_header.e_phoff, is_little_endian), - elf_byteswap_if_needed(file_header.e_phentsize, is_little_endian), - elf_byteswap_if_needed(file_header.e_phnum, is_little_endian) - ); + + static uintptr_t elf_get_module_image_base(const std::string& obj_path) { + FILE* file = fopen(obj_path.c_str(), "rb"); + if(file == nullptr) { + throw file_error(); + } + // Initial checks/metadata + auto magic = load_bytes>(file, 0); + CPPTRACE_VERIFY(magic == (std::array{0x7F, 'E', 'L', 'F'})); + bool is_64 = load_bytes(file, 4) == 2; + bool is_little_endian = load_bytes(file, 5) == 1; + CPPTRACE_VERIFY(load_bytes(file, 6) == 1, "Unexpected ELF version"); + // + if(is_64) { + Elf64_Ehdr file_header = load_bytes(file, 0); + CPPTRACE_VERIFY(file_header.e_ehsize == sizeof(Elf64_Ehdr)); + return elf_get_module_image_base_from_program_table( + file, + is_64, + is_little_endian, + elf_byteswap_if_needed(file_header.e_phoff, is_little_endian), + elf_byteswap_if_needed(file_header.e_phentsize, is_little_endian), + elf_byteswap_if_needed(file_header.e_phnum, is_little_endian) + ); + } else { + Elf32_Ehdr file_header = load_bytes(file, 0); + CPPTRACE_VERIFY(file_header.e_ehsize == sizeof(Elf32_Ehdr)); + return elf_get_module_image_base_from_program_table( + file, + is_64, + is_little_endian, + elf_byteswap_if_needed(file_header.e_phoff, is_little_endian), + elf_byteswap_if_needed(file_header.e_phentsize, is_little_endian), + elf_byteswap_if_needed(file_header.e_phnum, is_little_endian) + ); + } } } +} #endif diff --git a/src/platform/error.hpp b/src/platform/error.hpp new file mode 100644 index 0000000..a62020a --- /dev/null +++ b/src/platform/error.hpp @@ -0,0 +1,98 @@ +#ifndef ERROR_HPP +#define ERROR_HPP + +#include +#include +#include + +#include "common.hpp" + +#if IS_MSVC + #define CPPTRACE_PFUNC __FUNCSIG__ +#else + #define CPPTRACE_PFUNC __extension__ __PRETTY_FUNCTION__ +#endif + +namespace cpptrace { +namespace detail { + class file_error : public std::exception { + const char* what() const noexcept override { + return "Unable to read file"; + } + }; + + // Lightweight std::source_location. + struct source_location { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members) + const char* const file; + //const char* const function; // disabled for now due to static constexpr restrictions + // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members) + const int line; + constexpr source_location( + //const char* _function /*= __builtin_FUNCTION()*/, + const char* _file = __builtin_FILE(), + int _line = __builtin_LINE() + ) : file(_file), /*function(_function),*/ line(_line) {} + }; + + inline void primitive_assert_impl( + bool condition, + bool verify, + const char* expression, + const char* signature, + source_location location, + const char* message = nullptr + ) { + if(!condition) { + const char* action = verify ? "verification" : "assertion"; + const char* name = verify ? "VERIFY" : "ASSERT"; + if(message == nullptr) { + throw std::runtime_error( + stringf( + "Cpptrace %s failed at %s:%d: %s\n" + " CPPTRACE_%s(%s);\n", + action, location.file, location.line, signature, + name, expression + ) + ); + } else { + throw std::runtime_error( + stringf( + "Cpptrace %s failed at %s:%d: %s: %s\n" + " CPPTRACE_%s(%s);\n", + action, location.file, location.line, signature, message, + name, expression + ) + ); + } + } + } + + template + void nothing() {} + + #define PHONY_USE(E) (nothing()) + + // Check condition in both debug and release. std::runtime_error on failure. + #define CPPTRACE_VERIFY(c, ...) ( \ + ::cpptrace::detail::primitive_assert_impl(c, true, #c, CPPTRACE_PFUNC, {}, ##__VA_ARGS__) \ + ) + + #ifndef NDEBUG + // Check condition in both debug. std::runtime_error on failure. + #define CPPTRACE_ASSERT(c, ...) ( \ + ::cpptrace::detail::primitive_assert_impl(c, false, #c, CPPTRACE_PFUNC, {}, ##__VA_ARGS__) \ + ) + #else + // Check condition in both debug. std::runtime_error on failure. + #define CPPTRACE_ASSERT(c, ...) PHONY_USE(c) + #endif + + // TODO: Setting to silence these or make them fatal + inline void nonfatal_error(const std::string& message) { + fprintf(stderr, "Non-fatal cpptrace error: %s\n", message.c_str()); + } +} +} + +#endif diff --git a/src/platform/mach-o.hpp b/src/platform/mach-o.hpp index e519d56..965e871 100644 --- a/src/platform/mach-o.hpp +++ b/src/platform/mach-o.hpp @@ -2,6 +2,7 @@ #define MACHO_HPP #include "common.hpp" +#include "utils.hpp" #if IS_APPLE #include @@ -12,17 +13,6 @@ #include #include -// Based on https://github.com/AlexDenisov/segment_dumper/blob/master/main.c -// and https://lowlevelbits.org/parsing-mach-o-files/ - -static bool is_magic_64(uint32_t magic) { - return magic == MH_MAGIC_64 || magic == MH_CIGAM_64; -} - -static bool should_swap_bytes(uint32_t magic) { - return magic == MH_CIGAM || magic == MH_CIGAM_64 || magic == FAT_CIGAM; -} - #if defined(__aarch64__) #define CURRENT_CPU CPU_TYPE_ARM64 #elif defined(__arm__) && defined(__thumb__) @@ -35,121 +25,140 @@ static bool should_swap_bytes(uint32_t magic) { #error "Unknown CPU architecture" #endif -static uintptr_t macho_get_text_vmaddr_from_segments(FILE* obj_file, off_t offset, bool should_swap, uint32_t ncmds) { - off_t actual_offset = offset; - for(uint32_t i = 0; i < ncmds; i++) { - load_command cmd = load_bytes(obj_file, actual_offset); - if(should_swap) { - swap_load_command(&cmd, NX_UnknownByteOrder); - } - if(cmd.cmd == LC_SEGMENT_64) { - segment_command_64 segment = load_bytes(obj_file, actual_offset); +namespace cpptrace { +namespace detail { + // Based on https://github.com/AlexDenisov/segment_dumper/blob/master/main.c + // and https://lowlevelbits.org/parsing-mach-o-files/ + static bool is_magic_64(uint32_t magic) { + return magic == MH_MAGIC_64 || magic == MH_CIGAM_64; + } + + static bool should_swap_bytes(uint32_t magic) { + return magic == MH_CIGAM || magic == MH_CIGAM_64 || magic == FAT_CIGAM; + } + + static uintptr_t macho_get_text_vmaddr_from_segments( + FILE* obj_file, + off_t offset, + bool should_swap, + uint32_t ncmds + ) { + off_t actual_offset = offset; + for(uint32_t i = 0; i < ncmds; i++) { + load_command cmd = load_bytes(obj_file, actual_offset); if(should_swap) { - swap_segment_command_64(&segment, NX_UnknownByteOrder); + swap_load_command(&cmd, NX_UnknownByteOrder); } - //printf("segname(64): %s\n", segment.segname); - //printf(" %d\n", segment.nsects); - //printf(" %p\n", segment.vmaddr); - //printf(" %p\n", segment.vmsize); - if(strcmp(segment.segname, "__TEXT") == 0) { - return segment.vmaddr; + if(cmd.cmd == LC_SEGMENT_64) { + segment_command_64 segment = load_bytes(obj_file, actual_offset); + if(should_swap) { + swap_segment_command_64(&segment, NX_UnknownByteOrder); + } + //printf("segname(64): %s\n", segment.segname); + //printf(" %d\n", segment.nsects); + //printf(" %p\n", segment.vmaddr); + //printf(" %p\n", segment.vmsize); + if(strcmp(segment.segname, "__TEXT") == 0) { + return segment.vmaddr; + } + } else if(cmd.cmd == LC_SEGMENT) { + segment_command segment = load_bytes(obj_file, actual_offset); + if(should_swap) { + swap_segment_command(&segment, NX_UnknownByteOrder); + } + //printf("segname: %s\n", segment.segname); + if(strcmp(segment.segname, "__TEXT") == 0) { + return segment.vmaddr; + } } - } else if(cmd.cmd == LC_SEGMENT) { - segment_command segment = load_bytes(obj_file, actual_offset); + actual_offset += cmd.cmdsize; + } + // somehow no __TEXT section was found... + return 0; + } + + static uintptr_t macho_get_text_vmaddr_mach(FILE* obj_file, off_t offset, bool is_64, bool should_swap) { + uint32_t ncmds; + off_t load_commands_offset = offset; + if(is_64) { + size_t header_size = sizeof(mach_header_64); + mach_header_64 header = load_bytes(obj_file, offset); + //if(offset != 0) { // if fat the offset will be non-zero, if not fat the offset will be zero + if(header.cputype != CURRENT_CPU) { + return 0; + } + //} if(should_swap) { - swap_segment_command(&segment, NX_UnknownByteOrder); + swap_mach_header_64(&header, NX_UnknownByteOrder); } - //printf("segname: %s\n", segment.segname); - if(strcmp(segment.segname, "__TEXT") == 0) { - return segment.vmaddr; + ncmds = header.ncmds; + load_commands_offset += header_size; + } else { + size_t header_size = sizeof(mach_header); + mach_header header = load_bytes(obj_file, offset); + //if(offset != 0) { // if fat the offset will be non-zero, if not fat the offset will be zero + if(header.cputype != CURRENT_CPU) { + return 0; + } + //} + if(should_swap) { + swap_mach_header(&header, NX_UnknownByteOrder); } + ncmds = header.ncmds; + load_commands_offset += header_size; } - actual_offset += cmd.cmdsize; + return macho_get_text_vmaddr_from_segments(obj_file, load_commands_offset, should_swap, ncmds); } - // somehow no __TEXT section was found... - return 0; -} -static uintptr_t macho_get_text_vmaddr_mach(FILE* obj_file, off_t offset, bool is_64, bool should_swap) { - uint32_t ncmds; - off_t load_commands_offset = offset; - if(is_64) { - size_t header_size = sizeof(mach_header_64); - mach_header_64 header = load_bytes(obj_file, offset); - //if(offset != 0) { // if fat the offset will be non-zero, if not fat the offset will be zero - if(header.cputype != CURRENT_CPU) { - return 0; + static uintptr_t macho_get_text_vmaddr_fat(FILE* obj_file, bool should_swap) { + size_t header_size = sizeof(fat_header); + size_t arch_size = sizeof(fat_arch); + fat_header header = load_bytes(obj_file, 0); + if(should_swap) { + swap_fat_header(&header, NX_UnknownByteOrder); + } + off_t arch_offset = (off_t)header_size; + uintptr_t text_vmaddr = 0; + for(uint32_t i = 0; i < header.nfat_arch; i++) { + fat_arch arch = load_bytes(obj_file, arch_offset); + if(should_swap) { + swap_fat_arch(&arch, 1, NX_UnknownByteOrder); } - //} - if(should_swap) { - swap_mach_header_64(&header, NX_UnknownByteOrder); - } - ncmds = header.ncmds; - load_commands_offset += header_size; - } else { - size_t header_size = sizeof(mach_header); - mach_header header = load_bytes(obj_file, offset); - //if(offset != 0) { // if fat the offset will be non-zero, if not fat the offset will be zero - if(header.cputype != CURRENT_CPU) { - return 0; + off_t mach_header_offset = (off_t)arch.offset; + arch_offset += arch_size; + uint32_t magic = load_bytes(obj_file, mach_header_offset); + text_vmaddr = macho_get_text_vmaddr_mach( + obj_file, + mach_header_offset, + is_magic_64(magic), + should_swap_bytes(magic) + ); + if(text_vmaddr != 0) { + return text_vmaddr; } - //} - if(should_swap) { - swap_mach_header(&header, NX_UnknownByteOrder); } - ncmds = header.ncmds; - load_commands_offset += header_size; + // If this is reached... something went wrong. The cpu we're on wasn't found. + return text_vmaddr; } - return macho_get_text_vmaddr_from_segments(obj_file, load_commands_offset, should_swap, ncmds); -} -static uintptr_t macho_get_text_vmaddr_fat(FILE* obj_file, bool should_swap) { - size_t header_size = sizeof(fat_header); - size_t arch_size = sizeof(fat_arch); - fat_header header = load_bytes(obj_file, 0); - if(should_swap) { - swap_fat_header(&header, NX_UnknownByteOrder); - } - off_t arch_offset = (off_t)header_size; - uintptr_t text_vmaddr = 0; - for(uint32_t i = 0; i < header.nfat_arch; i++) { - fat_arch arch = load_bytes(obj_file, arch_offset); - if(should_swap) { - swap_fat_arch(&arch, 1, NX_UnknownByteOrder); + static uintptr_t macho_get_text_vmaddr(const char* path) { + FILE* obj_file = fopen(path, "rb"); + if(obj_file == nullptr) { + throw file_error(); } - off_t mach_header_offset = (off_t)arch.offset; - arch_offset += arch_size; - uint32_t magic = load_bytes(obj_file, mach_header_offset); - text_vmaddr = macho_get_text_vmaddr_mach( - obj_file, - mach_header_offset, - is_magic_64(magic), - should_swap_bytes(magic) - ); - if(text_vmaddr != 0) { - return text_vmaddr; + uint32_t magic = load_bytes(obj_file, 0); + bool is_64 = is_magic_64(magic); + bool should_swap = should_swap_bytes(magic); + uintptr_t addr; + if(magic == FAT_MAGIC || magic == FAT_CIGAM) { + addr = macho_get_text_vmaddr_fat(obj_file, should_swap); + } else { + addr = macho_get_text_vmaddr_mach(obj_file, 0, is_64, should_swap); } + fclose(obj_file); + return addr; } - // If this is reached... something went wrong. The cpu we're on wasn't found. - return text_vmaddr; } - -static uintptr_t macho_get_text_vmaddr(const char* path) { - FILE* obj_file = fopen(path, "rb"); - if(obj_file == nullptr) { - throw file_error(); - } - uint32_t magic = load_bytes(obj_file, 0); - bool is_64 = is_magic_64(magic); - bool should_swap = should_swap_bytes(magic); - uintptr_t addr; - if(magic == FAT_MAGIC || magic == FAT_CIGAM) { - addr = macho_get_text_vmaddr_fat(obj_file, should_swap); - } else { - addr = macho_get_text_vmaddr_mach(obj_file, 0, is_64, should_swap); - } - fclose(obj_file); - return addr; } #endif diff --git a/src/platform/object.hpp b/src/platform/object.hpp index 0df2f24..dfc92d7 100644 --- a/src/platform/object.hpp +++ b/src/platform/object.hpp @@ -2,6 +2,7 @@ #define OBJECT_HPP #include "common.hpp" +#include "utils.hpp" #include #include @@ -21,134 +22,140 @@ #include "pe.hpp" #endif -struct dlframe { - std::string obj_path; - std::string symbol; - uintptr_t raw_address = 0; - uintptr_t obj_address = 0; -}; +namespace cpptrace { +namespace detail { + struct dlframe { + std::string obj_path; + std::string symbol; + uintptr_t raw_address = 0; + uintptr_t obj_address = 0; + }; -#if IS_LINUX || IS_APPLE -#if !IS_APPLE -static uintptr_t get_module_image_base(const std::string& obj_path) { - static std::mutex mutex; - std::lock_guard lock(mutex); - static std::unordered_map cache; - auto it = cache.find(obj_path); - if(it == cache.end()) { - // arguably it'd be better to release the lock while computing this, but also arguably it's good to not - // have two threads try to do the same computation - auto base = elf_get_module_image_base(obj_path); - cache.insert(it, {obj_path, base}); - return base; - } else { - return it->second; - } -} -#else -static uintptr_t get_module_image_base(const std::string& obj_path) { - // We have to parse the Mach-O to find the offset of the text section..... - // I don't know how addresses are handled if there is more than one __TEXT load command. I'm assuming for - // now that there is only one, and I'm using only the first section entry within that load command. - static std::mutex mutex; - std::lock_guard lock(mutex); - static std::unordered_map cache; - auto it = cache.find(obj_path); - if(it == cache.end()) { - // arguably it'd be better to release the lock while computing this, but also arguably it's good to not - // have two threads try to do the same computation - auto base = macho_get_text_vmaddr(obj_path.c_str()); - cache.insert(it, {obj_path, base}); - return base; - } else { - return it->second; - } -} -#endif -// aladdr queries are needed to get pre-ASLR addresses and targets to run addr2line on -static std::vector get_frames_object_info(const std::vector& addrs) { - // reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c - std::vector frames; - frames.reserve(addrs.size()); - for(const void* addr : addrs) { - Dl_info info; - dlframe frame; - frame.raw_address = reinterpret_cast(addr); - if(dladdr(addr, &info)) { // thread safe - // dli_sname and dli_saddr are only present with -rdynamic, sname will be included - // but we don't really need dli_saddr - frame.obj_path = info.dli_fname; - frame.obj_address = reinterpret_cast(addr) - - reinterpret_cast(info.dli_fbase) - + get_module_image_base(info.dli_fname); - frame.symbol = info.dli_sname ?: ""; - } - frames.push_back(frame); - } - return frames; -} -#else -static std::string get_module_name(HMODULE handle) { - static std::mutex mutex; - std::lock_guard lock(mutex); - static std::unordered_map cache; - auto it = cache.find(handle); - if(it == cache.end()) { - char path[MAX_PATH]; - if(GetModuleFileNameA(handle, path, sizeof(path))) { - ///fprintf(stderr, "path: %s base: %p\n", path, handle); - cache.insert(it, {handle, path}); - return path; + #if IS_LINUX || IS_APPLE + #if !IS_APPLE + inline uintptr_t get_module_image_base(const std::string& obj_path) { + static std::mutex mutex; + std::lock_guard lock(mutex); + static std::unordered_map cache; + auto it = cache.find(obj_path); + if(it == cache.end()) { + // arguably it'd be better to release the lock while computing this, but also arguably it's good to not + // have two threads try to do the same computation + auto base = elf_get_module_image_base(obj_path); + cache.insert(it, {obj_path, base}); + return base; } else { - fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what()); - cache.insert(it, {handle, ""}); - return ""; + return it->second; } - } else { - return it->second; } -} -static uintptr_t get_module_image_base(const std::string& obj_path) { - static std::mutex mutex; - std::lock_guard lock(mutex); - static std::unordered_map cache; - auto it = cache.find(obj_path); - if(it == cache.end()) { - // arguably it'd be better to release the lock while computing this, but also arguably it's good to not - // have two threads try to do the same computation - auto base = pe_get_module_image_base(obj_path); - cache.insert(it, {obj_path, base}); - return base; - } else { - return it->second; - } -} -// aladdr queries are needed to get pre-ASLR addresses and targets to run addr2line on -static std::vector get_frames_object_info(const std::vector& addrs) { - // reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c - std::vector frames; - frames.reserve(addrs.size()); - for(const void* addr : addrs) { - dlframe frame; - frame.raw_address = reinterpret_cast(addr); - HMODULE handle; - // Multithread safe as long as another thread doesn't come along and free the module - if(GetModuleHandleExA( - GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, - static_cast(addr), - &handle - )) { - frame.obj_path = get_module_name(handle); - frame.obj_address = reinterpret_cast(addr) - - reinterpret_cast(handle) - + get_module_image_base(frame.obj_path); + #else + inline uintptr_t get_module_image_base(const std::string& obj_path) { + // We have to parse the Mach-O to find the offset of the text section..... + // I don't know how addresses are handled if there is more than one __TEXT load command. I'm assuming for + // now that there is only one, and I'm using only the first section entry within that load command. + static std::mutex mutex; + std::lock_guard lock(mutex); + static std::unordered_map cache; + auto it = cache.find(obj_path); + if(it == cache.end()) { + // arguably it'd be better to release the lock while computing this, but also arguably it's good to not + // have two threads try to do the same computation + auto base = macho_get_text_vmaddr(obj_path.c_str()); + cache.insert(it, {obj_path, base}); + return base; } else { - fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what()); + return it->second; } - frames.push_back(frame); } - return frames; + #endif + // aladdr queries are needed to get pre-ASLR addresses and targets to run addr2line on + inline std::vector get_frames_object_info(const std::vector& addrs) { + // reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c + std::vector frames; + frames.reserve(addrs.size()); + for(const void* addr : addrs) { + Dl_info info; + dlframe frame; + frame.raw_address = reinterpret_cast(addr); + if(dladdr(addr, &info)) { // thread safe + // dli_sname and dli_saddr are only present with -rdynamic, sname will be included + // but we don't really need dli_saddr + frame.obj_path = info.dli_fname; + frame.obj_address = reinterpret_cast(addr) + - reinterpret_cast(info.dli_fbase) + + get_module_image_base(info.dli_fname); + frame.symbol = info.dli_sname ?: ""; + } + frames.push_back(frame); + } + return frames; + } + #else + inline std::string get_module_name(HMODULE handle) { + static std::mutex mutex; + std::lock_guard lock(mutex); + static std::unordered_map cache; + auto it = cache.find(handle); + if(it == cache.end()) { + char path[MAX_PATH]; + if(GetModuleFileNameA(handle, path, sizeof(path))) { + ///fprintf(stderr, "path: %s base: %p\n", path, handle); + cache.insert(it, {handle, path}); + return path; + } else { + fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what()); + cache.insert(it, {handle, ""}); + return ""; + } + } else { + return it->second; + } + } + + inline uintptr_t get_module_image_base(const std::string& obj_path) { + static std::mutex mutex; + std::lock_guard lock(mutex); + static std::unordered_map cache; + auto it = cache.find(obj_path); + if(it == cache.end()) { + // arguably it'd be better to release the lock while computing this, but also arguably it's good to not + // have two threads try to do the same computation + auto base = pe_get_module_image_base(obj_path); + cache.insert(it, {obj_path, base}); + return base; + } else { + return it->second; + } + } + + // aladdr queries are needed to get pre-ASLR addresses and targets to run addr2line on + inline std::vector get_frames_object_info(const std::vector& addrs) { + // reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c + std::vector frames; + frames.reserve(addrs.size()); + for(const void* addr : addrs) { + dlframe frame; + frame.raw_address = reinterpret_cast(addr); + HMODULE handle; + // Multithread safe as long as another thread doesn't come along and free the module + if(GetModuleHandleExA( + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, + static_cast(addr), + &handle + )) { + frame.obj_path = get_module_name(handle); + frame.obj_address = reinterpret_cast(addr) + - reinterpret_cast(handle) + + get_module_image_base(frame.obj_path); + } else { + fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what()); + } + frames.push_back(frame); + } + return frames; + } + #endif +} } -#endif #endif diff --git a/src/platform/pe.hpp b/src/platform/pe.hpp index ca2ec81..5c55e7e 100644 --- a/src/platform/pe.hpp +++ b/src/platform/pe.hpp @@ -2,6 +2,8 @@ #define PE_HPP #include "common.hpp" +#include "error.hpp" +#include "utils.hpp" #if IS_WINDOWS #include @@ -12,53 +14,58 @@ #include -template::value, int>::type = 0> -T pe_byteswap_if_needed(T value) { - // PE header values are little endian - if(!is_little_endian()) { - return byteswap(value); - } else { - return value; +namespace cpptrace { +namespace detail { + template::value, int>::type = 0> + T pe_byteswap_if_needed(T value) { + // PE header values are little endian + if(!is_little_endian()) { + return byteswap(value); + } else { + return value; + } } + + inline uintptr_t pe_get_module_image_base(const std::string& obj_path) { + FILE* file; + errno_t ret = fopen_s(&file, obj_path.c_str(), "rb"); + if(ret != 0 || file == nullptr) { + throw file_error(); + return 0; + } + auto magic = load_bytes>(file, 0); + CPPTRACE_VERIFY(memcmp(magic.data(), "MZ", 2) == 0); + DWORD e_lfanew = pe_byteswap_if_needed(load_bytes(file, 0x3c)); // dos header + 0x3c + long nt_header_offset = e_lfanew; + auto signature = load_bytes>(file, nt_header_offset); // nt header + 0 + CPPTRACE_VERIFY(memcmp(signature.data(), "PE\0\0", 4) == 0); + WORD size_of_optional_header = pe_byteswap_if_needed( + load_bytes(file, nt_header_offset + 4 + 0x10) // file header + 0x10 + ); + CPPTRACE_VERIFY(size_of_optional_header != 0); + WORD optional_header_magic = pe_byteswap_if_needed( + load_bytes(file, nt_header_offset + 0x18) // optional header + 0x0 + ); + CPPTRACE_VERIFY(optional_header_magic == IMAGE_NT_OPTIONAL_HDR_MAGIC); + uintptr_t image_base; + if(optional_header_magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) { + // 32 bit + image_base = pe_byteswap_if_needed( + load_bytes(file, nt_header_offset + 0x18 + 0x1c) // optional header + 0x1c + ); + } else { + // 64 bit + // I get an "error: 'QWORD' was not declared in this scope" for some reason when using QWORD + image_base = pe_byteswap_if_needed( + load_bytes(file, nt_header_offset + 0x18 + 0x18) // optional header + 0x18 + ); + } + fclose(file); + return image_base; + } +} } -static uintptr_t pe_get_module_image_base(const std::string& obj_path) { - FILE* file; - errno_t ret = fopen_s(&file, obj_path.c_str(), "rb"); - if(ret != 0 || file == nullptr) { - throw file_error(); - return 0; - } - auto magic = load_bytes>(file, 0); - internal_verify(memcmp(magic.data(), "MZ", 2) == 0); - DWORD e_lfanew = pe_byteswap_if_needed(load_bytes(file, 0x3c)); // dos header + 0x3c - long nt_header_offset = e_lfanew; - auto signature = load_bytes>(file, nt_header_offset); // nt header + 0 - internal_verify(memcmp(signature.data(), "PE\0\0", 4) == 0); - WORD size_of_optional_header = pe_byteswap_if_needed( - load_bytes(file, nt_header_offset + 4 + 0x10) // file header + 0x10 - ); - internal_verify(size_of_optional_header != 0); - WORD optional_header_magic = pe_byteswap_if_needed( - load_bytes(file, nt_header_offset + 0x18) // optional header + 0x0 - ); - internal_verify(optional_header_magic == IMAGE_NT_OPTIONAL_HDR_MAGIC); - uintptr_t image_base; - if(optional_header_magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) { - // 32 bit - image_base = pe_byteswap_if_needed( - load_bytes(file, nt_header_offset + 0x18 + 0x1c) // optional header + 0x1c - ); - } else { - // 64 bit - // I get an "error: 'QWORD' was not declared in this scope" for some reason when using QWORD - image_base = pe_byteswap_if_needed( - load_bytes(file, nt_header_offset + 0x18 + 0x18) // optional header + 0x18 - ); - } - fclose(file); - return image_base; -} #endif #endif diff --git a/src/platform/program_name.hpp b/src/platform/program_name.hpp index 46145ce..f67ef19 100644 --- a/src/platform/program_name.hpp +++ b/src/platform/program_name.hpp @@ -8,26 +8,26 @@ #include namespace cpptrace { - namespace detail { - inline std::string program_name() { - static std::mutex mutex; - const std::lock_guard lock(mutex); - static std::string name; - static bool did_init = false; - static bool valid = false; - if(!did_init) { - did_init = true; - char buffer[MAX_PATH + 1]; - int res = GetModuleFileNameA(nullptr, buffer, MAX_PATH); - if(res) { - name = buffer; - valid = true; - } +namespace detail { + inline std::string program_name() { + static std::mutex mutex; + const std::lock_guard lock(mutex); + static std::string name; + static bool did_init = false; + static bool valid = false; + if(!did_init) { + did_init = true; + char buffer[MAX_PATH + 1]; + int res = GetModuleFileNameA(nullptr, buffer, MAX_PATH); + if(res) { + name = buffer; + valid = true; } - return valid && !name.empty() ? name.c_str() : nullptr; } + return valid && !name.empty() ? name.c_str() : nullptr; } } +} #elif defined(__APPLE__) @@ -36,26 +36,26 @@ namespace cpptrace { #include namespace cpptrace { - namespace detail { - inline const char* program_name() { - static std::mutex mutex; - const std::lock_guard lock(mutex); - static std::string name; - static bool did_init = false; - static bool valid = false; - if(!did_init) { - did_init = true; - std::uint32_t bufferSize = PATH_MAX + 1; - char buffer[bufferSize]; - if(_NSGetExecutablePath(buffer, &bufferSize) == 0) { - name.assign(buffer, bufferSize); - valid = true; - } +namespace detail { + inline const char* program_name() { + static std::mutex mutex; + const std::lock_guard lock(mutex); + static std::string name; + static bool did_init = false; + static bool valid = false; + if(!did_init) { + did_init = true; + std::uint32_t bufferSize = PATH_MAX + 1; + char buffer[bufferSize]; + if(_NSGetExecutablePath(buffer, &bufferSize) == 0) { + name.assign(buffer, bufferSize); + valid = true; } - return valid && !name.empty() ? name.c_str() : nullptr; } + return valid && !name.empty() ? name.c_str() : nullptr; } } +} #elif defined(__linux__) @@ -64,28 +64,28 @@ namespace cpptrace { #include namespace cpptrace { - namespace detail { - inline const char* program_name() { - static std::mutex mutex; - const std::lock_guard lock(mutex); - static std::string name; - static bool did_init = false; - static bool valid = false; - if(!did_init) { - did_init = true; - char buffer[PATH_MAX + 1]; - const ssize_t size = readlink("/proc/self/exe", buffer, PATH_MAX); - if(size == -1) { - return nullptr; - } - buffer[size] = 0; - name = buffer; - valid = true; +namespace detail { + inline const char* program_name() { + static std::mutex mutex; + const std::lock_guard lock(mutex); + static std::string name; + static bool did_init = false; + static bool valid = false; + if(!did_init) { + did_init = true; + char buffer[PATH_MAX + 1]; + const ssize_t size = readlink("/proc/self/exe", buffer, PATH_MAX); + if(size == -1) { + return nullptr; } - return valid && !name.empty() ? name.c_str() : nullptr; + buffer[size] = 0; + name = buffer; + valid = true; } + return valid && !name.empty() ? name.c_str() : nullptr; } } +} #endif diff --git a/src/platform/utils.hpp b/src/platform/utils.hpp new file mode 100644 index 0000000..b1e599f --- /dev/null +++ b/src/platform/utils.hpp @@ -0,0 +1,342 @@ +#ifndef UTILS_HPP +#define UTILS_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.hpp" +#include "error.hpp" +#include "utils.hpp" + +#if IS_WINDOWS + #include +#else + #include +#endif + + +namespace cpptrace { +namespace detail { + inline std::vector split(const std::string& str, const std::string& delims) { + std::vector vec; + size_t old_pos = 0; + size_t pos = 0; + while((pos = str.find_first_of(delims, old_pos)) != std::string::npos) { + vec.emplace_back(str.substr(old_pos, pos - old_pos)); + old_pos = pos + 1; + } + vec.emplace_back(str.substr(old_pos)); + return vec; + } + + template + inline std::string join(const C& container, const std::string& delim) { + auto iter = std::begin(container); + auto end = std::end(container); + std::string str; + if(std::distance(iter, end) > 0) { + str += *iter; + while(++iter != end) { + str += delim; + str += *iter; + } + } + return str; + } + + constexpr const char* const whitespace = " \t\n\r\f\v"; + + inline std::string trim(const std::string& str) { + if(str.empty()) { + return ""; + } + const size_t left = str.find_first_not_of(whitespace); + const size_t right = str.find_last_not_of(whitespace) + 1; + return str.substr(left, right - left); + } + + inline std::string to_hex(uintptr_t addr) { + std::stringstream sstream; + sstream< + struct byte_swapper; + + template + struct byte_swapper { + T operator()(T val) { + return val; + } + }; + + template + struct byte_swapper { + T operator()(T val) { + return ((((val) >> 8) & 0xff) | (((val) & 0xff) << 8)); + } + }; + + template + struct byte_swapper { + T operator()(T val) { + return ((((val) & 0xff000000) >> 24) | + (((val) & 0x00ff0000) >> 8) | + (((val) & 0x0000ff00) << 8) | + (((val) & 0x000000ff) << 24)); + } + }; + + template + struct byte_swapper { + T operator()(T val) { + return ((((val) & 0xff00000000000000ull) >> 56) | + (((val) & 0x00ff000000000000ull) >> 40) | + (((val) & 0x0000ff0000000000ull) >> 24) | + (((val) & 0x000000ff00000000ull) >> 8 ) | + (((val) & 0x00000000ff000000ull) << 8 ) | + (((val) & 0x0000000000ff0000ull) << 24) | + (((val) & 0x000000000000ff00ull) << 40) | + (((val) & 0x00000000000000ffull) << 56)); + } + }; + + template::value, int>::type = 0> + T byteswap(T value) { + return byte_swapper{}(value); + } + + inline void enable_virtual_terminal_processing_if_needed() { + // enable colors / ansi processing if necessary + #if IS_WINDOWS + // https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing + #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING + constexpr DWORD ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4; + #endif + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + DWORD dwMode = 0; + if(hOut == INVALID_HANDLE_VALUE) return; + if(!GetConsoleMode(hOut, &dwMode)) return; + if(dwMode != (dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) + if(!SetConsoleMode(hOut, dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) return; + #endif + } + + // NOLINTNEXTLINE(misc-no-recursion) + inline constexpr unsigned n_digits(unsigned value) { + return value < 10 ? 1 : 1 + n_digits(value / 10); + } + static_assert(n_digits(1) == 1, "n_digits utility producing the wrong result"); + static_assert(n_digits(9) == 1, "n_digits utility producing the wrong result"); + static_assert(n_digits(10) == 2, "n_digits utility producing the wrong result"); + static_assert(n_digits(11) == 2, "n_digits utility producing the wrong result"); + static_assert(n_digits(1024) == 4, "n_digits utility producing the wrong result"); + + // TODO: Re-evaluate use of off_t + template::value, int>::type = 0> + T load_bytes(FILE* obj_file, off_t offset) { + T object; + CPPTRACE_VERIFY(fseek(obj_file, offset, SEEK_SET) == 0, "fseek error"); + CPPTRACE_VERIFY(fread(&object, sizeof(T), 1, obj_file) == 1, "fread error"); + return object; + } + + struct nullopt_t {}; + + static constexpr nullopt_t nullopt; + + template< + typename T, + typename std::enable_if::type, void>::value, int>::type = 0 + > + class optional { + bool holds_value = false; + + union { + T uvalue; + }; + + public: + // clang-tidy false positive + // NOLINTNEXTLINE(modernize-use-equals-default) + optional() noexcept {} + + optional(nullopt_t) noexcept {} + + ~optional() { + reset(); + } + + optional(const optional& other) : holds_value(other.holds_value) { + if(holds_value) { + new (static_cast(std::addressof(uvalue))) T(other.uvalue); + } + } + + optional(optional&& other) + noexcept(std::is_nothrow_move_constructible::value) + : holds_value(other.holds_value) + { + if(holds_value) { + new (static_cast(std::addressof(uvalue))) T(std::move(other.uvalue)); + } + } + + optional& operator=(const optional& other) { + optional copy(other); + swap(*this, copy); + return *this; + } + + optional& operator=(optional&& other) + noexcept(std::is_nothrow_move_assignable::value && std::is_nothrow_move_constructible::value) + { + reset(); + if(other.holds_value) { + new (static_cast(std::addressof(uvalue))) T(std::move(other.uvalue)); + holds_value = true; + } + return *this; + } + + template< + typename U = T, + typename std::enable_if::type, optional>::value, int>::type = 0 + > + // clang-tidy false positive + // NOLINTNEXTLINE(bugprone-forwarding-reference-overload) + optional(U&& value) : holds_value(true) { + new (static_cast(std::addressof(uvalue))) T(std::forward(value)); + } + + template< + typename U = T, + typename std::enable_if::type, optional>::value, int>::type = 0 + > + optional& operator=(U&& value) { + if(holds_value) { + uvalue = std::forward(value); + } else { + new (static_cast(std::addressof(uvalue))) T(std::forward(value)); + holds_value = true; + } + return *this; + } + + optional& operator=(nullopt_t) noexcept { + reset(); + return *this; + } + + void swap(optional& other) { + if(holds_value && other.holds_value) { + std::swap(uvalue, other.uvalue); + } else if(holds_value && !other.holds_value) { + new (&other.uvalue) T(std::move(uvalue)); + uvalue.~T(); + } else if(!holds_value && other.holds_value) { + new (static_cast(std::addressof(uvalue))) T(std::move(other.uvalue)); + other.uvalue.~T(); + } + std::swap(holds_value, other.holds_value); + } + + bool has_value() const { + return holds_value; + } + + operator bool() const { + return holds_value; + } + + void reset() { + if(holds_value) { + uvalue.~T(); + } + holds_value = false; + } + + T& unwrap() & { + if(!holds_value) { + throw std::runtime_error{"Optional does not contain a value"}; + } + return uvalue; + } + + const T& unwrap() const & { + if(!holds_value) { + throw std::runtime_error{"Optional does not contain a value"}; + } + return uvalue; + } + + T&& unwrap() && { + if(!holds_value) { + throw std::runtime_error{"Optional does not contain a value"}; + } + return std::move(uvalue); + } + + const T&& unwrap() const && { + if(!holds_value) { + throw std::runtime_error{"Optional does not contain a value"}; + } + return std::move(uvalue); + } + + template + T value_or(U&& default_value) const & { + return holds_value ? uvalue : static_cast(std::forward(default_value)); + } + + template + T value_or(U&& default_value) && { + return holds_value ? std::move(uvalue) : static_cast(std::forward(default_value)); + } + }; + + // shamelessly stolen from stackoverflow + inline bool directory_exists(const std::string& path) { + #if IS_WINDOWS + DWORD dwAttrib = GetFileAttributesA(path.c_str()); + return dwAttrib != INVALID_FILE_ATTRIBUTES && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY); + #else + struct stat sb; + return stat(path.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode); + #endif + } + + inline std::string basename(const std::string& path) { + // Assumes no trailing /'s + auto pos = path.rfind('/'); + if(pos == std::string::npos) { + return path; + } else { + return path.substr(pos + 1); + } + } +} +} + +#endif diff --git a/src/symbols/symbols_core.cpp b/src/symbols/symbols_core.cpp index 839137f..c6e8684 100644 --- a/src/symbols/symbols_core.cpp +++ b/src/symbols/symbols_core.cpp @@ -2,6 +2,8 @@ #include +#include "../platform/object.hpp" + namespace cpptrace { namespace detail { void apply_trace( diff --git a/src/symbols/symbols_with_addr2line.cpp b/src/symbols/symbols_with_addr2line.cpp index 79fcc7f..2b0a5d4 100644 --- a/src/symbols/symbols_with_addr2line.cpp +++ b/src/symbols/symbols_with_addr2line.cpp @@ -3,6 +3,7 @@ #include #include "symbols.hpp" #include "../platform/common.hpp" +#include "../platform/utils.hpp" #include #include @@ -77,8 +78,8 @@ namespace cpptrace { 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); + CPPTRACE_VERIFY(pipe(output_pipe.data) == 0); + CPPTRACE_VERIFY(pipe(input_pipe.data) == 0); // NOLINTNEXTLINE(misc-include-cleaner) const pid_t pid = fork(); if(pid == -1) { return ""; } // error? TODO: Diagnostic @@ -123,7 +124,7 @@ namespace cpptrace { #endif _exit(1); // TODO: Diagnostic? } - internal_verify(write(input_pipe.write_end, addresses.data(), addresses.size()) != -1); + CPPTRACE_VERIFY(write(input_pipe.write_end, addresses.data(), addresses.size()) != -1); close(input_pipe.read_end); close(input_pipe.write_end); close(output_pipe.write_end); @@ -241,14 +242,14 @@ namespace cpptrace { symbol_end = at_location; filename_start = at_location + 4; } else { - internal_verify(line.find("?? ") == 0, "Unexpected edge case while processing addr2line output"); + CPPTRACE_VERIFY(line.find("?? ") == 0, "Unexpected edge case while processing addr2line output"); symbol_end = 2; filename_start = 3; } auto symbol = line.substr(0, symbol_end); auto colon = line.rfind(':'); - internal_verify(colon != std::string::npos); - internal_verify(colon >= filename_start); // :? to deal with "symbol :?" edge case + CPPTRACE_VERIFY(colon != std::string::npos); + CPPTRACE_VERIFY(colon >= filename_start); // :? to deal with "symbol :?" edge case auto filename = line.substr(filename_start, colon - filename_start); auto line_number = line.substr(colon + 1); if(line_number != "?") { @@ -278,7 +279,7 @@ namespace cpptrace { 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( + CPPTRACE_VERIFY( obj_end != std::string::npos, "Unexpected edge case while processing addr2line/atos output" ); @@ -288,7 +289,7 @@ namespace cpptrace { return; } const std::size_t filename_end = line.find(":", filename_start); - internal_verify( + CPPTRACE_VERIFY( filename_end != std::string::npos, "Unexpected edge case while processing addr2line/atos output" ); @@ -298,7 +299,7 @@ namespace cpptrace { ); const std::size_t line_start = filename_end + 1; const std::size_t line_end = line.find(")", filename_end); - internal_verify( + CPPTRACE_VERIFY( line_end == line.size() - 1, "Unexpected edge case while processing addr2line/atos output" ); @@ -336,7 +337,7 @@ namespace cpptrace { #endif } auto output = split(trim(resolve_addresses(address_input, object_name)), "\n"); - internal_verify(output.size() == entries_vec.size()); + CPPTRACE_VERIFY(output.size() == entries_vec.size()); for(size_t i = 0; i < output.size(); i++) { update_trace(output[i], i, entries_vec); } diff --git a/src/symbols/symbols_with_libdwarf.cpp b/src/symbols/symbols_with_libdwarf.cpp index ebbbc4d..e067a04 100644 --- a/src/symbols/symbols_with_libdwarf.cpp +++ b/src/symbols/symbols_with_libdwarf.cpp @@ -5,8 +5,9 @@ #include "../platform/common.hpp" #include "../platform/program_name.hpp" #include "../platform/object.hpp" +#include "../platform/error.hpp" +#include "../platform/utils.hpp" -#include #include #include #include @@ -40,10 +41,10 @@ Dwarf_Unsigned get_ranges_offset(Dwarf_Attribute attr) { dwarf_whatform(attr, &attrform, nullptr); if (attrform == DW_FORM_rnglistx) { int fres = dwarf_formudata(attr, &off, nullptr); - assert(fres == DW_DLV_OK); + CPPTRACE_VERIFY(fres == DW_DLV_OK); } else { int fres = dwarf_global_formref(attr, &off, nullptr); - assert(fres == DW_DLV_OK); + CPPTRACE_VERIFY(fres == DW_DLV_OK); } return off; } @@ -76,7 +77,7 @@ static int dwarf5_ranges(Dwarf_Die cu_die, Dwarf_Addr *lowest, Dwarf_Addr *highe &rlesetoffset, nullptr ); - assert(res == DW_DLV_OK); + CPPTRACE_VERIFY(res == DW_DLV_OK); if(res != DW_DLV_OK) { /* ASSERT: is DW_DLV_NO_ENTRY */ dwarf_dealloc_attribute(attr); @@ -129,7 +130,7 @@ static int dwarf5_ranges(Dwarf_Die cu_die, Dwarf_Addr *lowest, Dwarf_Addr *highe } break; default: - assert(false); + CPPTRACE_VERIFY(false); /* Something is wrong. */ break; } @@ -358,7 +359,7 @@ namespace cpptrace { char* raw_str; std::string str; ret = dwarf_formstring(attr, &raw_str, nullptr); - assert(ret == DW_DLV_OK); + CPPTRACE_VERIFY(ret == DW_DLV_OK); str = raw_str; dwarf_dealloc(dbg, raw_str, DW_DLA_STRING); dwarf_dealloc_attribute(attr); @@ -398,7 +399,7 @@ namespace cpptrace { Dwarf_Off get_global_offset() const { Dwarf_Off off; int ret = dwarf_dieoffset(die, &off, nullptr); - assert(ret == DW_DLV_OK); + CPPTRACE_VERIFY(ret == DW_DLV_OK); return off; } @@ -417,13 +418,13 @@ namespace cpptrace { Dwarf_Off off = 0; Dwarf_Bool is_info = dwarf_get_die_infotypes_flag(die); ret = dwarf_formref(attr, &off, &is_info, nullptr); - assert(ret == DW_DLV_OK); + CPPTRACE_VERIFY(ret == DW_DLV_OK); Dwarf_Off goff = 0; ret = dwarf_convert_to_global_offset(attr, off, &goff, nullptr); - assert(ret == DW_DLV_OK); + CPPTRACE_VERIFY(ret == DW_DLV_OK); Dwarf_Die targ_die_a = 0; ret = dwarf_offdie_b(dbg, goff, is_info, &targ_die_a, nullptr); - assert(ret == DW_DLV_OK); + CPPTRACE_VERIFY(ret == DW_DLV_OK); dwarf_dealloc_attribute(attr); return die_object(dbg, targ_die_a); } @@ -434,7 +435,7 @@ namespace cpptrace { int is_info_a = dwarf_get_die_infotypes_flag(die); Dwarf_Die targ_die_a = 0; ret = dwarf_offdie_b(dbg, off, is_info_a, &targ_die_a, nullptr); - assert(ret == DW_DLV_OK); + CPPTRACE_VERIFY(ret == DW_DLV_OK); dwarf_dealloc_attribute(attr); return die_object(dbg, targ_die_a); } @@ -442,11 +443,11 @@ namespace cpptrace { { Dwarf_Sig8 signature; ret = dwarf_formsig8(attr, &signature, nullptr); - assert(ret == DW_DLV_OK); + CPPTRACE_VERIFY(ret == DW_DLV_OK); Dwarf_Die targdie = 0; Dwarf_Bool targ_is_info = false; ret = dwarf_find_die_given_sig8(dbg, &signature, &targdie, &targ_is_info, nullptr); - assert(ret == DW_DLV_OK); + CPPTRACE_VERIFY(ret == DW_DLV_OK); dwarf_dealloc_attribute(attr); return die_object(dbg, targdie); } @@ -598,7 +599,7 @@ namespace cpptrace { std::string resolve_type(Dwarf_Debug dbg, const die_object& die, std::string build = ""); std::string get_array_extents(Dwarf_Debug dbg, const die_object& die) { - assert(die.get_tag() == DW_TAG_array_type); + CPPTRACE_VERIFY(die.get_tag() == DW_TAG_array_type); std::string extents = ""; walk_die_list(dbg, die.get_child(), [&extents](Dwarf_Debug dbg, const die_object& subrange) { if(subrange.get_tag() == DW_TAG_subrange_type) { @@ -632,7 +633,7 @@ namespace cpptrace { } std::string get_parameters(Dwarf_Debug dbg, const die_object& die) { - assert(die.get_tag() == DW_TAG_subroutine_type); + CPPTRACE_VERIFY(die.get_tag() == DW_TAG_subroutine_type); std::vector params; walk_die_list(dbg, die.get_child(), [¶ms](Dwarf_Debug dbg, const die_object& die) { if(die.get_tag() == DW_TAG_formal_parameter) { @@ -757,7 +758,7 @@ namespace cpptrace { Dwarf_Half dwversion, stacktrace_frame& frame ) { - assert(die.get_tag() == DW_TAG_subprogram); + CPPTRACE_VERIFY(die.get_tag() == DW_TAG_subprogram); optional name; if(auto linkage_name = die.get_string_attribute(DW_AT_linkage_name)) { name = std::move(linkage_name); @@ -955,7 +956,7 @@ namespace cpptrace { //if(dwversion >= 5) { // Dwarf_Attribute attr; // int ret = dwarf_attr(cu_die.get(), DW_AT_rnglists_base, &attr, nullptr); - // assert(ret == DW_DLV_OK); + // CPPTRACE_VERIFY(ret == DW_DLV_OK); // Dwarf_Unsigned uval = 0; // ret = dwarf_global_formref(attr, &uval, nullptr); // offset = uval; diff --git a/src/unwind/unwind.hpp b/src/unwind/unwind.hpp index 38ca151..5f7d60a 100644 --- a/src/unwind/unwind.hpp +++ b/src/unwind/unwind.hpp @@ -2,6 +2,7 @@ #define UNWIND_HPP #include "../platform/common.hpp" +#include "../platform/utils.hpp" #include #include diff --git a/src/unwind/unwind_with_execinfo.cpp b/src/unwind/unwind_with_execinfo.cpp index eea4d7b..4260471 100644 --- a/src/unwind/unwind_with_execinfo.cpp +++ b/src/unwind/unwind_with_execinfo.cpp @@ -2,6 +2,7 @@ #include "unwind.hpp" #include "../platform/common.hpp" +#include "../platform/utils.hpp" #include #include diff --git a/src/unwind/unwind_with_unwind.cpp b/src/unwind/unwind_with_unwind.cpp index 919d3dc..f3dbc41 100644 --- a/src/unwind/unwind_with_unwind.cpp +++ b/src/unwind/unwind_with_unwind.cpp @@ -2,6 +2,8 @@ #include "unwind.hpp" #include "../platform/common.hpp" +#include "../platform/error.hpp" +#include "../platform/utils.hpp" #include #include @@ -29,7 +31,10 @@ namespace cpptrace { } } - assert(state.count < state.vec.size()); + CPPTRACE_VERIFY( + state.count < state.vec.size(), + "Somehow cpptrace::detail::unwind_callback is overflowing a vector" + ); int is_before_instruction = 0; uintptr_t ip = _Unwind_GetIPInfo(context, &is_before_instruction); if(!is_before_instruction && ip != uintptr_t(0)) { diff --git a/src/unwind/unwind_with_winapi.cpp b/src/unwind/unwind_with_winapi.cpp index 548c622..242c502 100644 --- a/src/unwind/unwind_with_winapi.cpp +++ b/src/unwind/unwind_with_winapi.cpp @@ -3,6 +3,7 @@ #include #include "unwind.hpp" #include "../platform/common.hpp" +#include "../platform/utils.hpp" #include