Expand cpptrace API (#37)

This commit is contained in:
Jeremy Rifkin 2023-09-18 20:33:46 -04:00 committed by GitHub
parent 43a50c734c
commit 0b32df64e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 513 additions and 126 deletions

View File

@ -1,5 +1,5 @@
---
Checks: '-*,clang-diagnostic-*,clang-analyzer-*,bugprone-*,cert-*,clang-analyzer-*,concurrency-*,cppcoreguidelines-*,misc-*,modernize-*,performance-*,portability-*,readability-*,-cppcoreguidelines-macro-usage,-modernize-use-trailing-return-type,-misc-non-private-member-variables-in-classes,-cppcoreguidelines-pro-type-reinterpret-cast,-cppcoreguidelines-pro-type-vararg,-cppcoreguidelines-avoid-c-arrays,-modernize-avoid-c-arrays,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-readability-else-after-return,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cert-dcl50-cpp,-cppcoreguidelines-init-variables,-readability-implicit-bool-conversion,-cppcoreguidelines-pro-bounds-constant-array-index,-cppcoreguidelines-owning-memory,-cppcoreguidelines-pro-type-member-init,-readability-isolate-declaration,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-cppcoreguidelines-pro-type-union-access,-cppcoreguidelines-pro-type-cstyle-cast,-readability-named-parameter,-cppcoreguidelines-avoid-goto,-readability-uppercase-literal-suffix,-performance-avoid-endl,-bugprone-easily-swappable-parameters'
Checks: '-*,clang-diagnostic-*,clang-analyzer-*,bugprone-*,cert-*,clang-analyzer-*,concurrency-*,cppcoreguidelines-*,misc-*,modernize-*,performance-*,portability-*,readability-*,-cppcoreguidelines-macro-usage,-modernize-use-trailing-return-type,-misc-non-private-member-variables-in-classes,-cppcoreguidelines-pro-type-reinterpret-cast,-cppcoreguidelines-pro-type-vararg,-cppcoreguidelines-avoid-c-arrays,-modernize-avoid-c-arrays,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-readability-else-after-return,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cert-dcl50-cpp,-cppcoreguidelines-init-variables,-readability-implicit-bool-conversion,-cppcoreguidelines-pro-bounds-constant-array-index,-cppcoreguidelines-owning-memory,-cppcoreguidelines-pro-type-member-init,-readability-isolate-declaration,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-cppcoreguidelines-pro-type-union-access,-cppcoreguidelines-pro-type-cstyle-cast,-readability-named-parameter,-cppcoreguidelines-avoid-goto,-readability-uppercase-literal-suffix,-performance-avoid-endl,-bugprone-easily-swappable-parameters,-cppcoreguidelines-non-private-member-variables-in-classes'
WarningsAsErrors: '*'
HeaderFilterRegex: ''
AnalyzeTemporaryDtors: false

120
README.md
View File

@ -46,14 +46,40 @@ Generating traces is as easy as calling `cpptrace::print_trace`:
#include <cpptrace/cpptrace.hpp>
void trace() {
cpptrace::print_trace();
cpptrace::generate_trace().print();
}
// ...
/* other stuff */
```
![Screenshot](res/screenshot.png)
Cpptrace provides access to resolved stack traces as well as lightweight raw traces (just addresses) that can be
resolved later:
```cpp
const auto raw_trace = cpptrace::generate_raw_trace();
// then later
raw_trace.resolve().print();
```
Cpptrace also provides exception types that store stack traces:
```cpp
#include <cpptrace/cpptrace.hpp>
void trace() {
throw cpptrace::exception();
}
/* other stuff */
// terminate called after throwing an instance of 'cpptrace::exception'
// what(): cpptrace::exception:
// Stack trace (most recent call first):
// #0 0x00005641c715a1b6 in trace() at demo.cpp:9
// #1 0x00005641c715a229 in foo(int) at demo.cpp:16
// #2 0x00005641c715a2ba in main at demo.cpp:34
```
## CMake FetchContent Usage
```cmake
@ -73,8 +99,9 @@ On windows and macos some extra work is required, see [below](#platform-logistic
## API
`cpptrace::print_trace()` can be used to print a stacktrace at the current call site, `cpptrace::generate_trace()` can
be used to get raw frame information for custom use.
`cpptrace::generate_trace()` can used to generate a stacktrace object at the current call site. Resolved frames can be
accessed from this object with `.frames` and also the trace can be printed with `.print()`. Cpptrace also provides a
method to get lightweight raw traces, which are just vectors of program counters, which can be resolved at a later time.
**Note:** Debug info (`-g`/`/Z7`/`/Zi`/`/DEBUG`) is generally required for good trace information.
@ -83,15 +110,96 @@ for generating these is included above.
```cpp
namespace cpptrace {
/*
* Raw trace access
*/
struct raw_trace {
std::vector<uintptr_t> frames;
explicit raw_trace(std::vector<uintptr_t>&& frames);
object_trace resolve_object_trace() const;
stacktrace resolve() const;
void clear();
/* iterators exist for this object */
};
/*
* Object trace with object file information for each frame, any accessible symbol information, and the address in
* the object file for the frame's program counter.
*/
struct object_frame {
std::string obj_path;
std::string symbol;
uintptr_t raw_address = 0;
uintptr_t obj_address = 0;
};
struct object_trace {
std::vector<object_frame> frames;
explicit object_trace(std::vector<object_frame>&& frames);
stacktrace resolve() const;
void clear();
/* iterators exist for this object */
};
/*
* Resolved stacktrace object.
*/
struct stacktrace_frame {
uintptr_t address;
std::uint_least32_t line;
std::uint_least32_t col;
std::string filename;
std::string symbol;
bool operator==(const stacktrace_frame& other) const;
bool operator!=(const stacktrace_frame& other) const;
};
std::vector<stacktrace_frame> generate_trace(std::uint32_t skip = 0);
void print_trace(std::uint32_t skip = 0);
struct stacktrace {
std::vector<stacktrace_frame> frames;
explicit stacktrace(std::vector<stacktrace_frame>&& frames);
void print() const;
void print(std::ostream& stream) const;
void print(std::ostream& stream, bool color) const;
std::string to_string() const;
void clear();
/* iterators exist for this object */
};
/*
* Trace generation
*/
raw_trace generate_raw_trace(std::uint32_t skip = 0);
object_trace generate_object_trace(std::uint32_t skip = 0);
stacktrace generate_trace(std::uint32_t skip = 0);
/*
* Utilities
*/
std::string demangle(const std::string& name);
// Traced exception class
class exception : public std::exception {
public:
explicit exception();
const char* what() const noexcept override;
};
class exception_with_message : public exception {
public:
explicit exception_with_message(std::string&& message_arg);
const char* what() const noexcept override;
};
// All stdexcept errors have analogs here. Same constructor as exception_with_message.
class logic_error : exception_with_message { ... };
class domain_error : exception_with_message { ... };
class invalid_argument : exception_with_message { ... };
class length_error : exception_with_message { ... };
class out_of_range : exception_with_message { ... };
class runtime_error : exception_with_message { ... };
class range_error : exception_with_message { ... };
class overflow_error : exception_with_message { ... };
class underflow_error : exception_with_message { ... };
}
```

View File

@ -2,6 +2,8 @@
#define CPPTRACE_HPP
#include <cstdint>
#include <exception>
#include <ostream>
#include <string>
#include <vector>
@ -12,15 +14,171 @@
#endif
namespace cpptrace {
struct object_trace;
struct stacktrace;
struct raw_trace {
std::vector<uintptr_t> frames;
explicit raw_trace(std::vector<uintptr_t>&& frames_) : frames(frames_) {}
CPPTRACE_API object_trace resolve_object_trace() const;
CPPTRACE_API stacktrace resolve() const;
CPPTRACE_API void clear();
using iterator = std::vector<uintptr_t>::iterator;
using const_iterator = std::vector<uintptr_t>::const_iterator;
inline iterator begin() noexcept { return frames.begin(); }
inline const_iterator cbegin() const noexcept { return frames.cbegin(); }
inline iterator end() noexcept { return frames.end(); }
inline const_iterator cend() const noexcept { return frames.cend(); }
};
struct object_frame {
std::string obj_path;
std::string symbol;
uintptr_t raw_address = 0;
uintptr_t obj_address = 0;
};
struct object_trace {
std::vector<object_frame> frames;
explicit object_trace(std::vector<object_frame>&& frames_) : frames(frames_) {}
CPPTRACE_API stacktrace resolve() const;
CPPTRACE_API void clear();
using iterator = std::vector<object_frame>::iterator;
using const_iterator = std::vector<object_frame>::const_iterator;
inline iterator begin() noexcept { return frames.begin(); }
inline const_iterator cbegin() const noexcept { return frames.cbegin(); }
inline iterator end() noexcept { return frames.end(); }
inline const_iterator cend() const noexcept { return frames.cend(); }
};
struct stacktrace_frame {
uintptr_t address;
std::uint_least32_t line;
std::uint_least32_t col;
std::string filename;
std::string symbol;
bool operator==(const stacktrace_frame& other) const {
return address == other.address
&& line == other.line
&& col == other.col
&& filename == other.filename
&& symbol == other.symbol;
}
bool operator!=(const stacktrace_frame& other) const {
return !operator==(other);
}
};
struct stacktrace {
std::vector<stacktrace_frame> frames;
explicit stacktrace(std::vector<stacktrace_frame>&& frames_) : frames(frames_) {}
CPPTRACE_API void print() const;
CPPTRACE_API void print(std::ostream& stream) const;
CPPTRACE_API void print(std::ostream& stream, bool color) const;
CPPTRACE_API std::string to_string() const;
CPPTRACE_API void clear();
using iterator = std::vector<stacktrace_frame>::iterator;
using const_iterator = std::vector<stacktrace_frame>::const_iterator;
inline iterator begin() noexcept { return frames.begin(); }
inline const_iterator cbegin() const noexcept { return frames.cbegin(); }
inline iterator end() noexcept { return frames.end(); }
inline const_iterator cend() const noexcept { return frames.cend(); }
};
CPPTRACE_API raw_trace generate_raw_trace(std::uint32_t skip = 0);
CPPTRACE_API object_trace generate_object_trace(std::uint32_t skip = 0);
CPPTRACE_API stacktrace generate_trace(std::uint32_t skip = 0);
// utilities:
CPPTRACE_API std::string demangle(const std::string& name);
class exception : public std::exception {
protected:
mutable raw_trace trace;
mutable std::string resolved_message;
explicit exception(uint32_t skip) : trace(generate_raw_trace(skip + 1)) {}
virtual const std::string& get_resolved_message() const {
if(resolved_message.empty()) {
resolved_message = "cpptrace::exception:\n" + trace.resolve().to_string();
trace.clear();
}
return resolved_message;
}
public:
explicit exception() : exception(1) {}
const char* what() const noexcept override {
return get_resolved_message().c_str();
}
};
class exception_with_message : public exception {
protected:
mutable std::string message;
explicit exception_with_message(std::string&& message_arg, uint32_t skip)
: exception(skip + 1), message(std::move(message_arg)) {}
const std::string& get_resolved_message() const override {
if(resolved_message.empty()) {
resolved_message = message + "\n" + trace.resolve().to_string();
trace.clear();
message.clear();
}
return resolved_message;
}
public:
explicit exception_with_message(std::string&& message_arg)
: exception_with_message(std::move(message_arg), 1) {}
const char* what() const noexcept override {
return get_resolved_message().c_str();
}
};
class logic_error : public exception_with_message {
public:
explicit logic_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {}
};
class domain_error : public exception_with_message {
public:
explicit domain_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {}
};
class invalid_argument : public exception_with_message {
public:
explicit invalid_argument(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {}
};
class length_error : public exception_with_message {
public:
explicit length_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {}
};
class out_of_range : public exception_with_message {
public:
explicit out_of_range(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {}
};
class runtime_error : public exception_with_message {
public:
explicit runtime_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {}
};
class range_error : public exception_with_message {
public:
explicit range_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {}
};
class overflow_error : public exception_with_message {
public:
explicit overflow_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {}
};
class underflow_error : public exception_with_message {
public:
explicit underflow_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {}
};
CPPTRACE_API std::vector<stacktrace_frame> generate_trace(std::uint32_t skip = 0);
CPPTRACE_API void print_trace(std::uint32_t skip = 0);
}
#endif

View File

@ -2,16 +2,18 @@
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include "symbols/symbols.hpp"
#include "unwind/unwind.hpp"
#include "demangle/demangle.hpp"
#include "platform/common.hpp"
#include "platform/utils.hpp"
#include "platform/object.hpp"
#define ESC "\033["
#define RESET ESC "0m"
@ -23,29 +25,59 @@
#define CYAN ESC "36m"
namespace cpptrace {
CPPTRACE_FORCE_NO_INLINE CPPTRACE_API
std::vector<stacktrace_frame> generate_trace(std::uint32_t skip) {
std::vector<void*> frames = detail::capture_frames(skip + 1);
CPPTRACE_API
object_trace raw_trace::resolve_object_trace() const {
return object_trace(detail::get_frames_object_info(frames));
}
CPPTRACE_API
stacktrace raw_trace::resolve() const {
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
for(auto& frame : trace) {
frame.symbol = detail::demangle(frame.symbol);
}
return trace;
return stacktrace(std::move(trace));
}
CPPTRACE_API
void print_trace(std::uint32_t skip) {
detail::enable_virtual_terminal_processing_if_needed();
std::cerr<<"Stack trace (most recent call first):"<<std::endl;
void raw_trace::clear() {
frames.clear();
}
CPPTRACE_API
stacktrace object_trace::resolve() const {
return stacktrace(detail::resolve_frames(frames));
}
CPPTRACE_API
void object_trace::clear() {
frames.clear();
}
CPPTRACE_API
void stacktrace::print() const {
print(std::cerr, true);
}
CPPTRACE_API
void stacktrace::print(std::ostream& stream) const {
print(stream, true);
}
CPPTRACE_API
void stacktrace::print(std::ostream& stream, bool color) const {
if(color) {
detail::enable_virtual_terminal_processing_if_needed();
}
stream<<"Stack trace (most recent call first):"<<std::endl;
std::size_t counter = 0;
const auto trace = generate_trace(skip + 1);
if(trace.empty()) {
std::cerr<<"<empty trace>"<<std::endl;
if(frames.empty()) {
stream<<"<empty trace>"<<std::endl;
return;
}
const auto frame_number_width = detail::n_digits(static_cast<int>(trace.size()) - 1);
for(const auto& frame : trace) {
std::cerr
const auto frame_number_width = detail::n_digits(static_cast<int>(frames.size()) - 1);
for(const auto& frame : frames) {
stream
<< '#'
<< std::setw(static_cast<int>(frame_number_width))
<< std::left
@ -53,28 +85,65 @@ namespace cpptrace {
<< std::right
<< " "
<< std::hex
<< BLUE
<< (color ? BLUE : "")
<< "0x"
<< std::setw(2 * sizeof(uintptr_t))
<< std::setfill('0')
<< frame.address
<< std::dec
<< std::setfill(' ')
<< RESET
<< (color ? RESET : "")
<< " in "
<< YELLOW
<< (color ? YELLOW : "")
<< frame.symbol
<< RESET
<< (color ? RESET : "")
<< " at "
<< GREEN
<< (color ? GREEN : "")
<< frame.filename
<< RESET
<< (color ? RESET : "")
<< ":"
<< BLUE
<< (color ? BLUE : "")
<< frame.line
<< RESET
<< (frame.col > 0 ? ":" BLUE + std::to_string(frame.col) + RESET : "")
<< (color ? RESET : "")
<< (frame.col > 0 ? (color ? ":" BLUE : ":") + std::to_string(frame.col) + (color ? RESET : "") : "")
<< std::endl;
}
}
CPPTRACE_API
std::string stacktrace::to_string() const {
std::ostringstream oss;
print(oss, false);
return std::move(oss).str();
}
CPPTRACE_API
void stacktrace::clear() {
frames.clear();
}
CPPTRACE_FORCE_NO_INLINE CPPTRACE_API
raw_trace generate_raw_trace(std::uint32_t skip) {
return raw_trace(detail::capture_frames(skip + 1));
}
CPPTRACE_FORCE_NO_INLINE CPPTRACE_API
object_trace generate_object_trace(std::uint32_t skip) {
return object_trace(detail::get_frames_object_info(detail::capture_frames(skip + 1)));
}
CPPTRACE_FORCE_NO_INLINE CPPTRACE_API
stacktrace generate_trace(std::uint32_t skip) {
std::vector<uintptr_t> frames = detail::capture_frames(skip + 1);
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
for(auto& frame : trace) {
frame.symbol = detail::demangle(frame.symbol);
}
return stacktrace(std::move(trace));
}
CPPTRACE_API
std::string demangle(const std::string& name) {
return detail::demangle(name);
}
}

View File

@ -45,6 +45,8 @@
#include <stdexcept>
#include <string>
#include <cpptrace/cpptrace.hpp>
namespace cpptrace {
namespace detail {
// Placed here instead of utils because it's used by error.hpp and utils.hpp

View File

@ -24,13 +24,6 @@
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
inline uintptr_t get_module_image_base(const std::string& obj_path) {
@ -69,19 +62,19 @@ namespace detail {
}
#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) {
inline std::vector<object_frame> get_frames_object_info(const std::vector<uintptr_t>& addrs) {
// reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c
std::vector<dlframe> frames;
std::vector<object_frame> frames;
frames.reserve(addrs.size());
for(const void* addr : addrs) {
for(const uintptr_t addr : addrs) {
Dl_info info;
dlframe frame;
frame.raw_address = reinterpret_cast<uintptr_t>(addr);
if(dladdr(addr, &info)) { // thread safe
object_frame frame;
frame.raw_address = addr;
if(dladdr(reinterpret_cast<void*>(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)
frame.obj_address = addr
- reinterpret_cast<uintptr_t>(info.dli_fbase)
+ get_module_image_base(info.dli_fname);
frame.symbol = info.dli_sname ?: "";
@ -129,22 +122,22 @@ namespace detail {
}
// 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) {
inline std::vector<object_frame> get_frames_object_info(const std::vector<uintptr_t>& addrs) {
// reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c
std::vector<dlframe> frames;
std::vector<object_frame> frames;
frames.reserve(addrs.size());
for(const void* addr : addrs) {
dlframe frame;
frame.raw_address = reinterpret_cast<uintptr_t>(addr);
for(const uintptr_t addr : addrs) {
object_frame frame;
frame.raw_address = 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),
reinterpret_cast<const char*>(addr),
&handle
)) {
frame.obj_path = get_module_name(handle);
frame.obj_address = reinterpret_cast<uintptr_t>(addr)
frame.obj_address = addr
- reinterpret_cast<uintptr_t>(handle)
+ get_module_image_base(frame.obj_path);
} else {

View File

@ -31,7 +31,6 @@ namespace detail {
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);

View File

@ -11,46 +11,48 @@
namespace cpptrace {
namespace detail {
using collated_vec = std::vector<
std::pair<std::reference_wrapper<const dlframe>, std::reference_wrapper<stacktrace_frame>>
std::pair<std::reference_wrapper<const object_frame>, std::reference_wrapper<stacktrace_frame>>
>;
std::unordered_map<std::string, collated_vec> collate_frames(
const std::vector<dlframe>& frames,
const std::vector<object_frame>& frames,
std::vector<stacktrace_frame>& trace
);
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE
namespace libbacktrace {
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames);
std::vector<stacktrace_frame> resolve_frames(const std::vector<uintptr_t>& frames);
}
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
namespace libdwarf {
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames);
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames);
}
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDL
namespace libdl {
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames);
std::vector<stacktrace_frame> resolve_frames(const std::vector<uintptr_t>& frames);
}
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE
namespace addr2line {
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames);
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames);
}
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP
namespace dbghelp {
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames);
std::vector<stacktrace_frame> resolve_frames(const std::vector<uintptr_t>& frames);
}
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_NOTHING
namespace nothing {
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames);
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames);
std::vector<stacktrace_frame> resolve_frames(const std::vector<uintptr_t>& frames);
}
#endif
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames);
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames);
std::vector<stacktrace_frame> resolve_frames(const std::vector<uintptr_t>& frames);
}
}

View File

@ -3,12 +3,13 @@
#include <vector>
#include <unordered_map>
#include "../platform/common.hpp"
#include "../platform/object.hpp"
namespace cpptrace {
namespace detail {
std::unordered_map<std::string, collated_vec> collate_frames(
const std::vector<dlframe>& frames,
const std::vector<object_frame>& frames,
std::vector<stacktrace_frame>& trace
) {
std::unordered_map<std::string, collated_vec> entries;
@ -49,25 +50,61 @@ namespace detail {
}
}
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames) {
std::vector<stacktrace_frame> trace(frames.size());
#if defined(CPPTRACE_GET_SYMBOLS_WITH_LIBDL) \
|| defined(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP) \
|| defined(CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE)
// actually need to go backwards to a void*
std::vector<uintptr_t> raw_frames(frames.size());
for(std::size_t i = 0; i < frames.size(); i++) {
raw_frames[i] = frames[i].raw_address;
}
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDL
apply_trace(trace, libdl::resolve_frames(frames));
apply_trace(trace, libdl::resolve_frames(raw_frames));
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
apply_trace(trace, libdwarf::resolve_frames(frames));
apply_trace(trace, libdwarf::resolve_frames(frames));
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP
apply_trace(trace, dbghelp::resolve_frames(frames));
apply_trace(trace, dbghelp::resolve_frames(raw_frames));
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE
apply_trace(trace, addr2line::resolve_frames(frames));
apply_trace(trace, addr2line::resolve_frames(frames));
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE
apply_trace(trace, libbacktrace::resolve_frames(frames));
apply_trace(trace, libbacktrace::resolve_frames(raw_frames));
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_NOTHING
apply_trace(trace, nothing::resolve_frames(frames));
apply_trace(trace, nothing::resolve_frames(frames));
#endif
return trace;
}
std::vector<stacktrace_frame> resolve_frames(const std::vector<uintptr_t>& frames) {
#if defined(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) \
|| defined(CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE)
auto dlframes = get_frames_object_info(frames);
#endif
std::vector<stacktrace_frame> trace(frames.size());
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDL
apply_trace(trace, libdl::resolve_frames(frames));
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
apply_trace(trace, libdwarf::resolve_frames(dlframes));
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP
apply_trace(trace, dbghelp::resolve_frames(frames));
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE
apply_trace(trace, addr2line::resolve_frames(dlframes));
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE
apply_trace(trace, libbacktrace::resolve_frames(frames));
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_NOTHING
apply_trace(trace, nothing::resolve_frames(frames));
#endif
return trace;
}

View File

@ -271,18 +271,17 @@ namespace addr2line {
}
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames) {
// TODO: Refactor better
std::vector<stacktrace_frame> trace(frames.size(), stacktrace_frame { 0, 0, 0, "", "" });
const std::vector<dlframe> dlframes = get_frames_object_info(frames);
for(size_t i = 0; i < dlframes.size(); i++) {
trace[i].address = dlframes[i].raw_address;
for(size_t i = 0; i < frames.size(); i++) {
trace[i].address = frames[i].raw_address;
// Set what is known for now, and resolutions from addr2line should overwrite
trace[i].filename = dlframes[i].obj_path;
trace[i].symbol = dlframes[i].symbol;
trace[i].filename = frames[i].obj_path;
trace[i].symbol = frames[i].symbol;
}
if(has_addr2line()) {
const auto entries = collate_frames(dlframes, trace);
const auto entries = collate_frames(frames, trace);
for(const auto& entry : entries) {
const auto& object_name = entry.first;
const auto& entries_vec = entry.second;

View File

@ -324,7 +324,7 @@ namespace dbghelp {
std::mutex dbghelp_lock;
// TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions
stacktrace_frame resolve_frame(HANDLE proc, void* addr) {
stacktrace_frame resolve_frame(HANDLE proc, uintptr_t addr) {
const std::lock_guard<std::mutex> lock(dbghelp_lock); // all dbghelp functions are not thread safe
alignas(SYMBOL_INFO) char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
SYMBOL_INFO* symbol = (SYMBOL_INFO*)buffer;
@ -332,8 +332,8 @@ namespace dbghelp {
symbol->MaxNameLen = MAX_SYM_NAME;
union { DWORD64 a; DWORD b; } displacement;
IMAGEHLP_LINE64 line;
bool got_line = SymGetLineFromAddr64(proc, (DWORD64)addr, &displacement.b, &line);
if(SymFromAddr(proc, (DWORD64)addr, &displacement.a, symbol)) {
bool got_line = SymGetLineFromAddr64(proc, addr, &displacement.b, &line);
if(SymFromAddr(proc, addr, &displacement.a, symbol)) {
if(got_line) {
IMAGEHLP_STACK_FRAME frame;
frame.InstructionOffset = symbol->Address;
@ -344,7 +344,7 @@ namespace dbghelp {
if(SymSetContext(proc, &frame, nullptr) == FALSE && GetLastError() != ERROR_SUCCESS) {
fprintf(stderr, "Stack trace: Internal error while calling SymSetContext\n");
return {
reinterpret_cast<uintptr_t>(addr),
addr,
static_cast<std::uint_least32_t>(line.LineNumber),
0,
line.FileName,
@ -375,7 +375,7 @@ namespace dbghelp {
static std::regex comma_re(R"(,(?=\S))");
signature = std::regex_replace(signature, comma_re, ", ");
return {
reinterpret_cast<uintptr_t>(addr),
addr,
static_cast<std::uint_least32_t>(line.LineNumber),
0,
line.FileName,
@ -383,7 +383,7 @@ namespace dbghelp {
};
} else {
return {
reinterpret_cast<uintptr_t>(addr),
addr,
0,
0,
"",
@ -392,7 +392,7 @@ namespace dbghelp {
}
} else {
return {
reinterpret_cast<uintptr_t>(addr),
addr,
0,
0,
"",
@ -401,7 +401,7 @@ namespace dbghelp {
}
}
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
std::vector<stacktrace_frame> resolve_frames(const std::vector<uintptr_t>& frames) {
std::vector<stacktrace_frame> trace;
trace.reserve(frames.size());

View File

@ -12,11 +12,11 @@
namespace cpptrace {
namespace detail {
namespace libdl {
stacktrace_frame resolve_frame(const void* addr) {
stacktrace_frame resolve_frame(const uintptr_t addr) {
Dl_info info;
if(dladdr(addr, &info)) { // thread-safe
if(dladdr(reinterpret_cast<void*>(addr), &info)) { // thread-safe
return {
reinterpret_cast<uintptr_t>(addr),
addr,
0,
0,
info.dli_fname ? info.dli_fname : "",
@ -24,7 +24,7 @@ namespace libdl {
};
} else {
return {
reinterpret_cast<uintptr_t>(addr),
addr,
0,
0,
"",
@ -33,10 +33,10 @@ namespace libdl {
}
}
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
std::vector<stacktrace_frame> resolve_frames(const std::vector<uintptr_t>& frames) {
std::vector<stacktrace_frame> trace;
trace.reserve(frames.size());
for(const void* frame : frames) {
for(const auto frame : frames) {
trace.push_back(resolve_frame(frame));
}
return trace;

View File

@ -58,12 +58,12 @@ namespace libbacktrace {
}
// TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions
stacktrace_frame resolve_frame(const void* addr) {
stacktrace_frame resolve_frame(const uintptr_t addr) {
stacktrace_frame frame;
frame.col = 0;
backtrace_pcinfo(
get_backtrace_state(),
reinterpret_cast<uintptr_t>(addr),
addr,
full_callback,
error_callback,
&frame
@ -72,7 +72,7 @@ namespace libbacktrace {
// fallback, try to at least recover the symbol name with backtrace_syminfo
backtrace_syminfo(
get_backtrace_state(),
reinterpret_cast<uintptr_t>(addr),
addr,
syminfo_callback,
error_callback,
&frame
@ -81,10 +81,10 @@ namespace libbacktrace {
return frame;
}
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
std::vector<stacktrace_frame> resolve_frames(const std::vector<uintptr_t>& frames) {
std::vector<stacktrace_frame> trace;
trace.reserve(frames.size());
for(const void* frame : frames) {
for(const auto frame : frames) {
trace.push_back(resolve_frame(frame));
}
return trace;

View File

@ -1018,7 +1018,7 @@ namespace libdwarf {
}
CPPTRACE_FORCE_NO_INLINE
stacktrace_frame resolve_frame(const dlframe& frame_info) {
stacktrace_frame resolve_frame(const object_frame& frame_info) {
stacktrace_frame frame{};
frame.filename = frame_info.obj_path;
frame.symbol = frame_info.symbol;
@ -1041,10 +1041,9 @@ namespace libdwarf {
};
CPPTRACE_FORCE_NO_INLINE
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames) {
std::vector<stacktrace_frame> trace(frames.size(), stacktrace_frame { 0, 0, 0, "", "" });
const auto dlframes = get_frames_object_info(frames);
for(const auto& obj_entry : collate_frames(dlframes, trace)) {
for(const auto& obj_entry : collate_frames(frames, trace)) {
const auto& obj_name = obj_entry.first;
dwarf_resolver resolver(obj_name);
for(const auto& entry : obj_entry.second) {

View File

@ -8,7 +8,17 @@
namespace cpptrace {
namespace detail {
namespace nothing {
std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
std::vector<stacktrace_frame> resolve_frames(const std::vector<uintptr_t>& frames) {
return std::vector<stacktrace_frame>(frames.size(), {
0,
0,
0,
"",
""
});
}
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames) {
return std::vector<stacktrace_frame>(frames.size(), {
0,
0,

View File

@ -15,7 +15,7 @@ namespace detail {
constexpr size_t hard_max_frames = 100;
#endif
CPPTRACE_FORCE_NO_INLINE
std::vector<void*> capture_frames(size_t skip);
std::vector<uintptr_t> capture_frames(size_t skip);
}
}

View File

@ -13,12 +13,16 @@
namespace cpptrace {
namespace detail {
CPPTRACE_FORCE_NO_INLINE
std::vector<void*> capture_frames(size_t skip) {
std::vector<void*> frames(hard_max_frames + skip, nullptr);
const int n_frames = backtrace(frames.data(), int(hard_max_frames + skip)); // thread safe
frames.resize(n_frames);
frames.erase(frames.begin(), frames.begin() + ptrdiff_t(std::min(skip + 1, frames.size())));
frames.shrink_to_fit();
std::vector<uintptr_t> capture_frames(size_t skip) {
std::vector<void*> addrs(hard_max_frames + skip, nullptr);
const int n_frames = backtrace(addrs.data(), int(hard_max_frames + skip)); // thread safe
addrs.resize(n_frames);
addrs.erase(addrs.begin(), addrs.begin() + ptrdiff_t(std::min(skip + 1, addrs.size())));
addrs.shrink_to_fit();
std::vector<uintptr_t> frames(addrs.size(), 0);
for(std::size_t i = 0; i < addrs.size(); i++) {
frames[i] = reinterpret_cast<uintptr_t>(addrs[i]);
}
return frames;
}
}

View File

@ -7,7 +7,7 @@
namespace cpptrace {
namespace detail {
std::vector<void*> capture_frames(size_t) {
std::vector<uintptr_t> capture_frames(size_t) {
return {};
}
}

View File

@ -17,7 +17,7 @@ namespace detail {
struct unwind_state {
std::size_t skip;
std::size_t count;
std::vector<void*>& vec;
std::vector<uintptr_t>& vec;
};
_Unwind_Reason_Code unwind_callback(_Unwind_Context* context, void* arg) {
@ -44,14 +44,14 @@ namespace detail {
return _URC_END_OF_STACK;
} else {
// TODO: push_back?...
state.vec[state.count++] = (void*)ip;
state.vec[state.count++] = ip;
return _URC_NO_REASON;
}
}
CPPTRACE_FORCE_NO_INLINE
std::vector<void*> capture_frames(size_t skip) {
std::vector<void*> frames(hard_max_frames, nullptr);
std::vector<uintptr_t> capture_frames(size_t skip) {
std::vector<uintptr_t> frames(hard_max_frames, 0);
unwind_state state{skip + 1, 0, frames};
_Unwind_Backtrace(unwind_callback, &state); // presumably thread-safe
frames.resize(state.count);

View File

@ -5,6 +5,7 @@
#include "../platform/common.hpp"
#include "../platform/utils.hpp"
#include <cstdint>
#include <vector>
#include <windows.h>
@ -12,12 +13,15 @@
namespace cpptrace {
namespace detail {
CPPTRACE_FORCE_NO_INLINE
std::vector<void*> capture_frames(size_t skip) {
std::vector<PVOID> addrs(hard_max_frames, nullptr);
int frames = CaptureStackBackTrace(static_cast<DWORD>(skip + 1), hard_max_frames, addrs.data(), NULL);
addrs.resize(frames);
addrs.shrink_to_fit();
return addrs;
std::vector<uintptr_t> capture_frames(size_t skip) {
std::vector<void*> addrs(hard_max_frames, nullptr);
int n_frames = CaptureStackBackTrace(static_cast<DWORD>(skip + 1), hard_max_frames, addrs.data(), NULL);
addrs.resize(n_frames);
std::vector<uintptr_t> frames(addrs.size(), 0);
for(std::size_t i = 0; i < addrs.size(); i++) {
frames[i] = reinterpret_cast<uintptr_t>(addrs[i]);
}
return frames;
}
}
}

View File

@ -6,6 +6,7 @@ add_executable(main main.cpp)
add_subdirectory(cpptrace)
target_link_libraries(main cpptrace)
target_compile_features(main PRIVATE cxx_std_11)
if(WIN32)
add_custom_command(

View File

@ -1,7 +1,7 @@
#include <cpptrace/cpptrace.hpp>
void trace() {
cpptrace::print_trace();
cpptrace::generate_trace().print();
}
void foo(int) {

View File

@ -6,7 +6,7 @@
#include <string>
void trace() {
cpptrace::print_trace();
cpptrace::generate_trace().print();
}
void foo(int n) {

View File

@ -14,6 +14,7 @@ FetchContent_Declare(
)
FetchContent_MakeAvailable(cpptrace)
target_link_libraries(main cpptrace)
target_compile_features(main PRIVATE cxx_std_11)
if(WIN32)
add_custom_command(

View File

@ -1,7 +1,7 @@
#include <cpptrace/cpptrace.hpp>
void trace() {
cpptrace::print_trace();
cpptrace::generate_trace().print();
}
void foo(int) {

View File

@ -6,3 +6,4 @@ add_executable(main main.cpp)
find_package(cpptrace REQUIRED)
target_link_libraries(main cpptrace::cpptrace)
target_compile_features(main PRIVATE cxx_std_11)

View File

@ -1,7 +1,7 @@
#include <cpptrace/cpptrace.hpp>
void trace() {
cpptrace::print_trace();
cpptrace::generate_trace().print();
}
void foo(int) {

View File

@ -7,5 +7,5 @@
#include <exception>
TEST(TraceTest, trace_test) {
ASSERT_THROW((cpptrace::print_trace(), false), std::logic_error);
ASSERT_THROW((cpptrace::generate_trace().print(), false), std::logic_error);
}