Work on improving error handling and some general refactoring. Also trying to bring everything into cpptrace::detail.
This commit is contained in:
parent
4d04352189
commit
278ee3fcee
@ -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
|
||||
<< '#'
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include "../platform/common.hpp"
|
||||
#include "../platform/utils.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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*)#
|
||||
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
|
||||
|
||||
@ -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
98
src/platform/error.hpp
Normal 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
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
342
src/platform/utils.hpp
Normal 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*)#
|
||||
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
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "../platform/object.hpp"
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
void apply_trace(
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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(), [¶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<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;
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
#define UNWIND_HPP
|
||||
|
||||
#include "../platform/common.hpp"
|
||||
#include "../platform/utils.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
#include "unwind.hpp"
|
||||
#include "../platform/common.hpp"
|
||||
#include "../platform/utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include "unwind.hpp"
|
||||
#include "../platform/common.hpp"
|
||||
#include "../platform/utils.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user