Work on improving error handling and some general refactoring. Also trying to bring everything into cpptrace::detail.

This commit is contained in:
Jeremy 2023-09-16 20:46:30 -04:00
parent 4d04352189
commit 278ee3fcee
No known key found for this signature in database
GPG Key ID: 19AA8270105E8EB4
19 changed files with 926 additions and 824 deletions

View File

@ -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):"<<std::endl;
std::size_t counter = 0;
const auto trace = generate_trace(skip + 1);
@ -66,7 +67,7 @@ namespace cpptrace {
std::cerr<<"<empty trace>"<<std::endl;
return;
}
const auto frame_number_width = n_digits(static_cast<int>(trace.size()) - 1);
const auto frame_number_width = detail::n_digits(static_cast<int>(trace.size()) - 1);
for(const auto& frame : trace) {
std::cerr
<< '#'

View File

@ -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)

View File

@ -3,6 +3,7 @@
#include <cpptrace/cpptrace.hpp>
#include "../platform/common.hpp"
#include "../platform/utils.hpp"
#include <cstddef>
#include <vector>

View File

@ -3,10 +3,10 @@
#include <cpptrace/cpptrace.hpp>
#include "../platform/program_name.hpp"
#include "../platform/common.hpp"
#include "../platform/utils.hpp"
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <mutex>
#include <vector>
@ -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() {

View File

@ -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 <cstdint>
#include <cstdio>
#include <cstdlib>
#include <exception>
#include <ios>
#include <sstream>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include <memory>
#include <new>
#define IS_WINDOWS 0
#define IS_LINUX 0
#define IS_APPLE 0
@ -60,389 +41,24 @@
#error "Unsupported compiler"
#endif
#if IS_WINDOWS
#include <windows.h>
#else
#include <sys/stat.h>
#endif
#include <cstdio>
#include <stdexcept>
#include <string>
// 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<typename... T> 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<typename T>
void nothing() {}
#define PHONY_USE(E) (nothing<decltype(E)>())
// 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<std::string> split(const std::string& str, const std::string& delims) {
std::vector<std::string> 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<typename C>
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<<std::hex<<addr;
return std::move(sstream).str();
}
CPPTRACE_MAYBE_UNUSED
static bool is_little_endian() {
uint16_t num = 0x1;
auto* ptr = (uint8_t*)&num;
return ptr[0] == 1;
}
// Modified from
// https://stackoverflow.com/questions/105252/how-do-i-convert-between-big-endian-and-little-endian-values-in-c
template<typename T, size_t N>
struct byte_swapper;
template<typename T>
struct byte_swapper<T, 1> {
T operator()(T val) {
return val;
}
};
template<typename T>
struct byte_swapper<T, 2> {
T operator()(T val) {
return ((((val) >> 8) & 0xff) | (((val) & 0xff) << 8));
}
};
template<typename T>
struct byte_swapper<T, 4> {
T operator()(T val) {
return ((((val) & 0xff000000) >> 24) |
(((val) & 0x00ff0000) >> 8) |
(((val) & 0x0000ff00) << 8) |
(((val) & 0x000000ff) << 24));
}
};
template<typename T>
struct byte_swapper<T, 8> {
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<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
T byteswap(T value) {
return byte_swapper<T, sizeof(T)>{}(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<typename T, typename std::enable_if<std::is_pod<T>::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<typename T, typename std::enable_if<!std::is_same<typename std::decay<T>::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<void*>(std::addressof(uvalue))) T(other.uvalue);
}
}
optional(optional&& other) noexcept(std::is_nothrow_move_constructible<T>::value) : holds_value(other.holds_value) {
if(holds_value) {
new (static_cast<void*>(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<T>::value && std::is_nothrow_move_constructible<T>::value
) {
reset();
if(other.holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::move(other.uvalue));
holds_value = true;
}
return *this;
}
template<
typename U = T,
typename std::enable_if<!std::is_same<typename std::decay<U>::type, optional<T>>::value, int>::type = 0
>
// clang-tidy false positive
// NOLINTNEXTLINE(bugprone-forwarding-reference-overload)
optional(U&& value) : holds_value(true) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::forward<U>(value));
}
template<
typename U = T,
typename std::enable_if<!std::is_same<typename std::decay<U>::type, optional<T>>::value, int>::type = 0
>
optional& operator=(U&& value) {
if(holds_value) {
uvalue = std::forward<U>(value);
} else {
new (static_cast<void*>(std::addressof(uvalue))) T(std::forward<U>(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<void*>(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<typename U>
T value_or(U&& default_value) const & {
return holds_value ? uvalue : static_cast<T>(std::forward<U>(default_value));
}
template<typename U>
T value_or(U&& default_value) && {
return holds_value ? std::move(uvalue) : static_cast<T>(std::forward<U>(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

View File

@ -2,6 +2,7 @@
#define ELF_HPP
#include "common.hpp"
#include "utils.hpp"
#if IS_LINUX
#include <array>
@ -11,82 +12,86 @@
#include <elf.h>
template<typename T, typename std::enable_if<std::is_integral<T>::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<Elf64_Phdr>(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<typename T, typename std::enable_if<std::is_integral<T>::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<Elf32_Phdr>(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<Elf64_Phdr>(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<Elf32_Phdr>(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<std::array<char, 4>>(file, 0);
internal_verify(magic == (std::array<char, 4>{0x7F, 'E', 'L', 'F'}));
bool is_64 = load_bytes<uint8_t>(file, 4) == 2;
bool is_little_endian = load_bytes<uint8_t>(file, 5) == 1;
internal_verify(load_bytes<uint8_t>(file, 6) == 1, "Unexpected ELF version");
//
if(is_64) {
Elf64_Ehdr file_header = load_bytes<Elf64_Ehdr>(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<Elf32_Ehdr>(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<std::array<char, 4>>(file, 0);
CPPTRACE_VERIFY(magic == (std::array<char, 4>{0x7F, 'E', 'L', 'F'}));
bool is_64 = load_bytes<uint8_t>(file, 4) == 2;
bool is_little_endian = load_bytes<uint8_t>(file, 5) == 1;
CPPTRACE_VERIFY(load_bytes<uint8_t>(file, 6) == 1, "Unexpected ELF version");
//
if(is_64) {
Elf64_Ehdr file_header = load_bytes<Elf64_Ehdr>(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<Elf32_Ehdr>(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

98
src/platform/error.hpp Normal file
View File

@ -0,0 +1,98 @@
#ifndef ERROR_HPP
#define ERROR_HPP
#include <cstdio>
#include <exception>
#include <stdexcept>
#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<typename T>
void nothing() {}
#define PHONY_USE(E) (nothing<decltype(E)>())
// 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

View File

@ -2,6 +2,7 @@
#define MACHO_HPP
#include "common.hpp"
#include "utils.hpp"
#if IS_APPLE
#include <cstdio>
@ -12,17 +13,6 @@
#include <mach-o/swap.h>
#include <mach-o/fat.h>
// 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<load_command>(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<segment_command_64>(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<load_command>(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<segment_command_64>(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<segment_command>(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<segment_command>(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<mach_header_64>(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<mach_header>(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<mach_header_64>(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<fat_header>(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<fat_arch>(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<mach_header>(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<uint32_t>(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<fat_header>(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<fat_arch>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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

View File

@ -2,6 +2,7 @@
#define OBJECT_HPP
#include "common.hpp"
#include "utils.hpp"
#include <string>
#include <vector>
@ -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<std::mutex> lock(mutex);
static std::unordered_map<std::string, uintptr_t> 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<std::mutex> lock(mutex);
static std::unordered_map<std::string, uintptr_t> 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<dlframe> get_frames_object_info(const std::vector<void*>& addrs) {
// reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c
std::vector<dlframe> frames;
frames.reserve(addrs.size());
for(const void* addr : addrs) {
Dl_info info;
dlframe frame;
frame.raw_address = reinterpret_cast<uintptr_t>(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<uintptr_t>(addr)
- reinterpret_cast<uintptr_t>(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<std::mutex> lock(mutex);
static std::unordered_map<HMODULE, std::string> 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<std::mutex> lock(mutex);
static std::unordered_map<std::string, uintptr_t> 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<std::mutex> lock(mutex);
static std::unordered_map<std::string, uintptr_t> 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<dlframe> get_frames_object_info(const std::vector<void*>& addrs) {
// reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c
std::vector<dlframe> frames;
frames.reserve(addrs.size());
for(const void* addr : addrs) {
dlframe frame;
frame.raw_address = reinterpret_cast<uintptr_t>(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<const char*>(addr),
&handle
)) {
frame.obj_path = get_module_name(handle);
frame.obj_address = reinterpret_cast<uintptr_t>(addr)
- reinterpret_cast<uintptr_t>(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<std::mutex> lock(mutex);
static std::unordered_map<std::string, uintptr_t> 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<dlframe> get_frames_object_info(const std::vector<void*>& addrs) {
// reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c
std::vector<dlframe> frames;
frames.reserve(addrs.size());
for(const void* addr : addrs) {
Dl_info info;
dlframe frame;
frame.raw_address = reinterpret_cast<uintptr_t>(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<uintptr_t>(addr)
- reinterpret_cast<uintptr_t>(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<std::mutex> lock(mutex);
static std::unordered_map<HMODULE, std::string> 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<std::mutex> lock(mutex);
static std::unordered_map<std::string, uintptr_t> 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<dlframe> get_frames_object_info(const std::vector<void*>& addrs) {
// reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c
std::vector<dlframe> frames;
frames.reserve(addrs.size());
for(const void* addr : addrs) {
dlframe frame;
frame.raw_address = reinterpret_cast<uintptr_t>(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<const char*>(addr),
&handle
)) {
frame.obj_path = get_module_name(handle);
frame.obj_address = reinterpret_cast<uintptr_t>(addr)
- reinterpret_cast<uintptr_t>(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

View File

@ -2,6 +2,8 @@
#define PE_HPP
#include "common.hpp"
#include "error.hpp"
#include "utils.hpp"
#if IS_WINDOWS
#include <array>
@ -12,53 +14,58 @@
#include <windows.h>
template<typename T, typename std::enable_if<std::is_integral<T>::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<typename T, typename std::enable_if<std::is_integral<T>::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<std::array<char, 2>>(file, 0);
CPPTRACE_VERIFY(memcmp(magic.data(), "MZ", 2) == 0);
DWORD e_lfanew = pe_byteswap_if_needed(load_bytes<DWORD>(file, 0x3c)); // dos header + 0x3c
long nt_header_offset = e_lfanew;
auto signature = load_bytes<std::array<char, 4>>(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<WORD>(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<WORD>(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<DWORD>(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<unsigned __int64>(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<std::array<char, 2>>(file, 0);
internal_verify(memcmp(magic.data(), "MZ", 2) == 0);
DWORD e_lfanew = pe_byteswap_if_needed(load_bytes<DWORD>(file, 0x3c)); // dos header + 0x3c
long nt_header_offset = e_lfanew;
auto signature = load_bytes<std::array<char, 4>>(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<WORD>(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<WORD>(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<DWORD>(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<unsigned __int64>(file, nt_header_offset + 0x18 + 0x18) // optional header + 0x18
);
}
fclose(file);
return image_base;
}
#endif
#endif

View File

@ -8,26 +8,26 @@
#include <windows.h>
namespace cpptrace {
namespace detail {
inline std::string program_name() {
static std::mutex mutex;
const std::lock_guard<std::mutex> 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<std::mutex> 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 <sys/syslimits.h>
namespace cpptrace {
namespace detail {
inline const char* program_name() {
static std::mutex mutex;
const std::lock_guard<std::mutex> 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<std::mutex> 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 <unistd.h>
namespace cpptrace {
namespace detail {
inline const char* program_name() {
static std::mutex mutex;
const std::lock_guard<std::mutex> 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<std::mutex> 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

342
src/platform/utils.hpp Normal file
View File

@ -0,0 +1,342 @@
#ifndef UTILS_HPP
#define UTILS_HPP
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <exception>
#include <ios>
#include <memory>
#include <new>
#include <sstream>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "common.hpp"
#include "error.hpp"
#include "utils.hpp"
#if IS_WINDOWS
#include <windows.h>
#else
#include <sys/stat.h>
#endif
namespace cpptrace {
namespace detail {
inline std::vector<std::string> split(const std::string& str, const std::string& delims) {
std::vector<std::string> 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<typename C>
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<<std::hex<<addr;
return std::move(sstream).str();
}
inline bool is_little_endian() {
uint16_t num = 0x1;
auto* ptr = (uint8_t*)&num;
return ptr[0] == 1;
}
// Modified from
// https://stackoverflow.com/questions/105252/how-do-i-convert-between-big-endian-and-little-endian-values-in-c
template<typename T, size_t N>
struct byte_swapper;
template<typename T>
struct byte_swapper<T, 1> {
T operator()(T val) {
return val;
}
};
template<typename T>
struct byte_swapper<T, 2> {
T operator()(T val) {
return ((((val) >> 8) & 0xff) | (((val) & 0xff) << 8));
}
};
template<typename T>
struct byte_swapper<T, 4> {
T operator()(T val) {
return ((((val) & 0xff000000) >> 24) |
(((val) & 0x00ff0000) >> 8) |
(((val) & 0x0000ff00) << 8) |
(((val) & 0x000000ff) << 24));
}
};
template<typename T>
struct byte_swapper<T, 8> {
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<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
T byteswap(T value) {
return byte_swapper<T, sizeof(T)>{}(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<typename T, typename std::enable_if<std::is_pod<T>::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<!std::is_same<typename std::decay<T>::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<void*>(std::addressof(uvalue))) T(other.uvalue);
}
}
optional(optional&& other)
noexcept(std::is_nothrow_move_constructible<T>::value)
: holds_value(other.holds_value)
{
if(holds_value) {
new (static_cast<void*>(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<T>::value && std::is_nothrow_move_constructible<T>::value)
{
reset();
if(other.holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::move(other.uvalue));
holds_value = true;
}
return *this;
}
template<
typename U = T,
typename std::enable_if<!std::is_same<typename std::decay<U>::type, optional<T>>::value, int>::type = 0
>
// clang-tidy false positive
// NOLINTNEXTLINE(bugprone-forwarding-reference-overload)
optional(U&& value) : holds_value(true) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::forward<U>(value));
}
template<
typename U = T,
typename std::enable_if<!std::is_same<typename std::decay<U>::type, optional<T>>::value, int>::type = 0
>
optional& operator=(U&& value) {
if(holds_value) {
uvalue = std::forward<U>(value);
} else {
new (static_cast<void*>(std::addressof(uvalue))) T(std::forward<U>(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<void*>(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<typename U>
T value_or(U&& default_value) const & {
return holds_value ? uvalue : static_cast<T>(std::forward<U>(default_value));
}
template<typename U>
T value_or(U&& default_value) && {
return holds_value ? std::move(uvalue) : static_cast<T>(std::forward<U>(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

View File

@ -2,6 +2,8 @@
#include <vector>
#include "../platform/object.hpp"
namespace cpptrace {
namespace detail {
void apply_trace(

View File

@ -3,6 +3,7 @@
#include <cpptrace/cpptrace.hpp>
#include "symbols.hpp"
#include "../platform/common.hpp"
#include "../platform/utils.hpp"
#include <cstdint>
#include <cstdio>
@ -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);
}

View File

@ -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 <cassert>
#include <cstdint>
#include <cstdio>
#include <functional>
@ -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<std::string> params;
walk_die_list(dbg, die.get_child(), [&params](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<std::string> 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;

View File

@ -2,6 +2,7 @@
#define UNWIND_HPP
#include "../platform/common.hpp"
#include "../platform/utils.hpp"
#include <cstddef>
#include <vector>

View File

@ -2,6 +2,7 @@
#include "unwind.hpp"
#include "../platform/common.hpp"
#include "../platform/utils.hpp"
#include <algorithm>
#include <cstddef>

View File

@ -2,6 +2,8 @@
#include "unwind.hpp"
#include "../platform/common.hpp"
#include "../platform/error.hpp"
#include "../platform/utils.hpp"
#include <algorithm>
#include <cassert>
@ -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)) {

View File

@ -3,6 +3,7 @@
#include <cpptrace/cpptrace.hpp>
#include "unwind.hpp"
#include "../platform/common.hpp"
#include "../platform/utils.hpp"
#include <vector>