New C api (#80)
Updated C API following the requested scheme. May implement "exceptions" later... Closes #38 --------- Co-authored-by: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com>
This commit is contained in:
parent
cfdd311e15
commit
218957dfb0
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,3 +4,4 @@ build*/
|
||||
repro*/
|
||||
__pycache__
|
||||
scratch
|
||||
.vscode
|
||||
|
||||
@ -189,6 +189,7 @@ add_library(cpptrace::cpptrace ALIAS ${target_name})
|
||||
target_sources(
|
||||
${target_name} PRIVATE
|
||||
include/cpptrace/cpptrace.hpp
|
||||
include/ctrace/ctrace.h
|
||||
)
|
||||
|
||||
# add /src files to target
|
||||
@ -196,6 +197,7 @@ target_sources(
|
||||
${target_name} PRIVATE
|
||||
# src
|
||||
src/cpptrace.cpp
|
||||
src/ctrace.cpp
|
||||
src/demangle/demangle_with_cxxabi.cpp
|
||||
src/demangle/demangle_with_winapi.cpp
|
||||
src/demangle/demangle_with_nothing.cpp
|
||||
@ -519,44 +521,35 @@ endif()
|
||||
|
||||
# =============================================== Demo/test ===============================================
|
||||
|
||||
macro(add_test_dependencies exec_name)
|
||||
target_compile_features(${exec_name} PRIVATE cxx_std_11)
|
||||
target_link_libraries(${exec_name} PRIVATE ${target_name})
|
||||
# Clang has been fast to adopt dwarf 5, other tools (e.g. addr2line from binutils) have not
|
||||
check_cxx_compiler_flag("-gdwarf-4" HAS_DWARF4)
|
||||
if(HAS_DWARF4)
|
||||
target_compile_options(${exec_name} PRIVATE "$<$<CONFIG:Debug>:-gdwarf-4>")
|
||||
endif()
|
||||
# TODO: add debug info for mingw clang?
|
||||
if(CPPTRACE_BUILD_TEST_RDYNAMIC)
|
||||
set_property(TARGET ${exec_name} PROPERTY ENABLE_EXPORTS ON)
|
||||
endif()
|
||||
if(APPLE) # TODO: Temporary
|
||||
add_custom_command(
|
||||
TARGET ${exec_name}
|
||||
POST_BUILD
|
||||
COMMAND dsymutil $<TARGET_FILE:${exec_name}>
|
||||
)
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
if(CPPTRACE_BUILD_TESTING)
|
||||
add_executable(test test/test.cpp)
|
||||
target_compile_features(test PRIVATE cxx_std_11)
|
||||
target_link_libraries(test PRIVATE ${target_name})
|
||||
# Clang has been fast to adopt dwarf 5, other tools (e.g. addr2line from binutils) have not
|
||||
check_cxx_compiler_flag("-gdwarf-4" HAS_DWARF4)
|
||||
if(HAS_DWARF4)
|
||||
target_compile_options(test PRIVATE "$<$<CONFIG:Debug>:-gdwarf-4>")
|
||||
endif()
|
||||
if(CPPTRACE_BUILD_TEST_RDYNAMIC)
|
||||
set_property(TARGET test PROPERTY ENABLE_EXPORTS ON)
|
||||
endif()
|
||||
if(APPLE) # TODO: Temporary
|
||||
add_custom_command(
|
||||
TARGET test
|
||||
POST_BUILD
|
||||
COMMAND dsymutil $<TARGET_FILE:test>
|
||||
)
|
||||
endif()
|
||||
|
||||
add_executable(demo test/demo.cpp)
|
||||
target_compile_features(demo PRIVATE cxx_std_11)
|
||||
target_link_libraries(demo PRIVATE ${target_name})
|
||||
# Clang has been fast to adopt dwarf 5, other tools (e.g. addr2line from binutils) have not
|
||||
check_cxx_compiler_flag("-gdwarf-4" HAS_DWARF4)
|
||||
if(HAS_DWARF4)
|
||||
target_compile_options(demo PRIVATE "$<$<CONFIG:Debug>:-gdwarf-4>")
|
||||
endif()
|
||||
if(CPPTRACE_BUILD_TEST_RDYNAMIC)
|
||||
set_property(TARGET demo PROPERTY ENABLE_EXPORTS ON)
|
||||
endif()
|
||||
if(APPLE) # TODO: Temporary
|
||||
add_custom_command(
|
||||
TARGET demo
|
||||
POST_BUILD
|
||||
COMMAND dsymutil $<TARGET_FILE:demo>
|
||||
)
|
||||
endif()
|
||||
add_executable(c_demo test/ctrace_demo.cpp)
|
||||
|
||||
add_test_dependencies(test)
|
||||
add_test_dependencies(demo)
|
||||
add_test_dependencies(c_demo)
|
||||
|
||||
if(UNIX)
|
||||
add_executable(signal_demo test/signal_demo.cpp)
|
||||
|
||||
@ -431,8 +431,8 @@ namespace cpptrace {
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** Not all back-ends and platforms support these interfaces. If signal-safe unwinding isn't supported
|
||||
`safe_generate_raw_trace` will just produce an empty trace and if object information can't be resolved in a signal-safe
|
||||
**Note:** Not all back-ends and platforms support these interfaces. If signal-safe unwinding isn't supported,
|
||||
`safe_generate_raw_trace` will just produce an empty trace, and if object information can't be resolved in a signal-safe
|
||||
way then `get_safe_object_frame` will not populate fields beyond the `raw_address`.
|
||||
|
||||
**Another big note:** Calls to shared objects can be lazy-loaded where the first call to the shared object invokes
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// Generated by the build system.
|
||||
#include <cpptrace/cpptrace_export.hpp>
|
||||
|
||||
#if __cplusplus >= 202002L
|
||||
@ -224,11 +224,11 @@ namespace cpptrace {
|
||||
|
||||
enum class cache_mode {
|
||||
// Only minimal lookup tables
|
||||
prioritize_memory,
|
||||
prioritize_memory = 0,
|
||||
// Build lookup tables but don't keep them around between trace calls
|
||||
hybrid,
|
||||
hybrid = 1,
|
||||
// Build lookup tables as needed
|
||||
prioritize_speed
|
||||
prioritize_speed = 2
|
||||
};
|
||||
|
||||
namespace experimental {
|
||||
|
||||
141
include/ctrace/ctrace.h
Normal file
141
include/ctrace/ctrace.h
Normal file
@ -0,0 +1,141 @@
|
||||
#ifndef CTRACE_H
|
||||
#define CTRACE_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#if defined(__cplusplus)
|
||||
#define CTRACE_BEGIN_DEFINITIONS extern "C" {
|
||||
#define CTRACE_END_DEFINITIONS }
|
||||
#else
|
||||
#define CTRACE_BEGIN_DEFINITIONS
|
||||
#define CTRACE_END_DEFINITIONS
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define CTRACE_FORCE_NO_INLINE __declspec(noinline)
|
||||
#else
|
||||
#define CTRACE_FORCE_NO_INLINE __attribute__((noinline))
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define CTRACE_FORCE_INLINE __forceinline
|
||||
#elif defined(__clang__) || defined(__GNUC__)
|
||||
#define CTRACE_FORCE_INLINE __attribute__((always_inline)) inline
|
||||
#else
|
||||
#define CTRACE_FORCE_INLINE inline
|
||||
#endif
|
||||
|
||||
// See `CPPTRACE_PATH_MAX` for more info.
|
||||
#define CTRACE_PATH_MAX 4096
|
||||
|
||||
// TODO: Add exports
|
||||
|
||||
CTRACE_BEGIN_DEFINITIONS
|
||||
typedef struct raw_trace ctrace_raw_trace;
|
||||
typedef struct object_trace ctrace_object_trace;
|
||||
typedef struct stacktrace ctrace_stacktrace;
|
||||
|
||||
// Represents a boolean value, ensures a consistent ABI.
|
||||
typedef int8_t ctrace_bool;
|
||||
// A type that can represent a pointer, alias for `uintptr_t`.
|
||||
typedef uintptr_t ctrace_frame_ptr;
|
||||
typedef struct object_frame ctrace_object_frame;
|
||||
typedef struct stacktrace_frame ctrace_stacktrace_frame;
|
||||
typedef struct safe_object_frame ctrace_safe_object_frame;
|
||||
|
||||
// Type-safe null-terminated string wrapper
|
||||
typedef struct {
|
||||
const char* data;
|
||||
} ctrace_owning_string;
|
||||
|
||||
struct object_frame {
|
||||
ctrace_frame_ptr raw_address;
|
||||
ctrace_frame_ptr obj_address;
|
||||
const char* obj_path;
|
||||
// const char* symbol;
|
||||
};
|
||||
|
||||
struct stacktrace_frame {
|
||||
ctrace_frame_ptr address;
|
||||
uint32_t line;
|
||||
uint32_t column;
|
||||
const char* filename;
|
||||
const char* symbol;
|
||||
ctrace_bool is_inline;
|
||||
};
|
||||
|
||||
struct safe_object_frame {
|
||||
ctrace_frame_ptr raw_address;
|
||||
ctrace_frame_ptr relative_obj_address;
|
||||
char object_path[CTRACE_PATH_MAX + 1];
|
||||
};
|
||||
|
||||
struct raw_trace {
|
||||
ctrace_frame_ptr* frames;
|
||||
size_t count;
|
||||
};
|
||||
|
||||
struct object_trace {
|
||||
ctrace_object_frame* frames;
|
||||
size_t count;
|
||||
};
|
||||
|
||||
struct stacktrace {
|
||||
ctrace_stacktrace_frame* frames;
|
||||
size_t count;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
// Only minimal lookup tables
|
||||
ctrace_prioritize_memory = 0,
|
||||
// Build lookup tables but don't keep them around between trace calls
|
||||
ctrace_hybrid = 1,
|
||||
// Build lookup tables as needed
|
||||
ctrace_prioritize_speed = 2
|
||||
} ctrace_cache_mode;
|
||||
|
||||
// ctrace::string:
|
||||
ctrace_owning_string ctrace_generate_owning_string(const char* raw_string);
|
||||
void ctrace_free_owning_string(ctrace_owning_string* string);
|
||||
|
||||
// ctrace::generation:
|
||||
ctrace_raw_trace ctrace_generate_raw_trace(size_t skip, size_t max_depth);
|
||||
ctrace_object_trace ctrace_generate_object_trace(size_t skip, size_t max_depth);
|
||||
ctrace_stacktrace ctrace_generate_trace(size_t skip, size_t max_depth);
|
||||
|
||||
// ctrace::freeing:
|
||||
void ctrace_free_raw_trace(ctrace_raw_trace* trace);
|
||||
void ctrace_free_object_trace(ctrace_object_trace* trace);
|
||||
void ctrace_free_stacktrace(ctrace_stacktrace* trace);
|
||||
|
||||
// ctrace::resolve:
|
||||
ctrace_stacktrace ctrace_raw_trace_resolve(const ctrace_raw_trace* trace);
|
||||
ctrace_object_trace ctrace_raw_trace_resolve_object_trace(const ctrace_raw_trace* trace);
|
||||
ctrace_stacktrace ctrace_object_trace_resolve(const ctrace_object_trace* trace);
|
||||
|
||||
// ctrace::safe:
|
||||
size_t ctrace_safe_generate_raw_trace(ctrace_frame_ptr* buffer, size_t size, size_t skip, size_t max_depth);
|
||||
void ctrace_get_safe_object_frame(ctrace_frame_ptr address, ctrace_safe_object_frame* out);
|
||||
|
||||
// ctrace::io:
|
||||
ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color);
|
||||
void ctrace_stacktrace_print(const ctrace_stacktrace* trace, FILE* to, ctrace_bool use_color);
|
||||
|
||||
// utility::demangle:
|
||||
ctrace_owning_string ctrace_demangle(const char* mangled);
|
||||
|
||||
// utility::io:
|
||||
int ctrace_stdin_fileno(void);
|
||||
int ctrace_stderr_fileno(void);
|
||||
int ctrace_stdout_fileno(void);
|
||||
ctrace_bool ctrace_isatty(int fd);
|
||||
|
||||
// utility::cache:
|
||||
void ctrace_set_cache_mode(ctrace_cache_mode mode);
|
||||
ctrace_cache_mode ctrace_get_cache_mode(void);
|
||||
|
||||
CTRACE_END_DEFINITIONS
|
||||
|
||||
#endif
|
||||
398
src/ctrace.cpp
Normal file
398
src/ctrace.cpp
Normal file
@ -0,0 +1,398 @@
|
||||
#include <ctrace/ctrace.h>
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include <algorithm>
|
||||
|
||||
#include "symbols/symbols.hpp"
|
||||
#include "unwind/unwind.hpp"
|
||||
#include "demangle/demangle.hpp"
|
||||
#include "utils/exception_type.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "binary/object.hpp"
|
||||
#include "binary/safe_dl.hpp"
|
||||
|
||||
#define ESC "\033["
|
||||
#define RESET ESC "0m"
|
||||
#define RED ESC "31m"
|
||||
#define GREEN ESC "32m"
|
||||
#define YELLOW ESC "33m"
|
||||
#define BLUE ESC "34m"
|
||||
#define MAGENTA ESC "35m"
|
||||
#define CYAN ESC "36m"
|
||||
|
||||
#if defined(__GNUC__) && ((__GNUC__ > 2) || (__GNUC__ == 2 && __GNUC_MINOR__ >= 6))
|
||||
# define CTRACE_GNU_FORMAT(...) __attribute__((format(__VA_ARGS__)))
|
||||
#elif defined(__clang__)
|
||||
// Probably requires llvm >3.5? Not exactly sure.
|
||||
# define CTRACE_GNU_FORMAT(...) __attribute__((format(__VA_ARGS__)))
|
||||
#else
|
||||
# define CTRACE_GNU_FORMAT(...)
|
||||
#endif
|
||||
|
||||
#if defined(__clang__)
|
||||
# define CTRACE_FORMAT_PROLOGUE \
|
||||
_Pragma("clang diagnostic push") \
|
||||
_Pragma("clang diagnostic ignored \"-Wformat-security\"")
|
||||
# define CTRACE_FORMAT_EPILOGUE \
|
||||
_Pragma("clang diagnostic pop")
|
||||
#elif defined(__GNUC_MINOR__)
|
||||
# define CTRACE_FORMAT_PROLOGUE \
|
||||
_Pragma("GCC diagnostic push") \
|
||||
_Pragma("GCC diagnostic ignored \"-Wformat-security\"")
|
||||
# define CTRACE_FORMAT_EPILOGUE \
|
||||
_Pragma("GCC diagnostic pop")
|
||||
#else
|
||||
# define CTRACE_FORMAT_PROLOGUE
|
||||
# define CTRACE_FORMAT_EPILOGUE
|
||||
#endif
|
||||
|
||||
namespace ctrace {
|
||||
static constexpr std::uint32_t invalid_pos = ~0U;
|
||||
|
||||
CTRACE_FORMAT_PROLOGUE
|
||||
template <typename...Args>
|
||||
CTRACE_GNU_FORMAT(printf, 2, 0)
|
||||
static void ffprintf(std::FILE* f, const char fmt[], Args&&...args) {
|
||||
(void)std::fprintf(f, fmt, args...);
|
||||
(void)fflush(f);
|
||||
}
|
||||
CTRACE_FORMAT_EPILOGUE
|
||||
|
||||
static bool is_empty(std::uint32_t pos) noexcept {
|
||||
return pos == invalid_pos;
|
||||
}
|
||||
|
||||
static bool is_empty(const char* str) noexcept {
|
||||
return !str || std::char_traits<char>::length(str) == 0;
|
||||
}
|
||||
|
||||
static ctrace_owning_string generate_owning_string(const char* raw_string) noexcept {
|
||||
// Returns length to the null terminator.
|
||||
std::size_t count = std::char_traits<char>::length(raw_string);
|
||||
char* new_string = new char[count + 1];
|
||||
std::char_traits<char>::copy(new_string, raw_string, count);
|
||||
new_string[count] = '\0';
|
||||
return { new_string };
|
||||
}
|
||||
|
||||
static ctrace_owning_string generate_owning_string(const std::string& std_string) {
|
||||
return generate_owning_string(std_string.c_str());
|
||||
}
|
||||
|
||||
static void free_owning_string(const char* owned_string) noexcept {
|
||||
if(!owned_string) return; // Not necessary but eh
|
||||
delete[] owned_string;
|
||||
}
|
||||
|
||||
static void free_owning_string(ctrace_owning_string& owned_string) noexcept {
|
||||
free_owning_string(owned_string.data);
|
||||
}
|
||||
|
||||
static ctrace_object_trace c_convert(const std::vector<cpptrace::object_frame>& trace) {
|
||||
std::size_t count = trace.size();
|
||||
auto* frames = new ctrace_object_frame[count];
|
||||
std::transform(
|
||||
trace.begin(),
|
||||
trace.end(),
|
||||
frames,
|
||||
[] (const cpptrace::object_frame& frame) -> ctrace_object_frame {
|
||||
const char* new_path = generate_owning_string(frame.object_path).data;
|
||||
return { frame.raw_address, frame.object_address, new_path };
|
||||
}
|
||||
);
|
||||
return { frames, count };
|
||||
}
|
||||
|
||||
static ctrace_stacktrace c_convert(const std::vector<cpptrace::stacktrace_frame>& trace) {
|
||||
std::size_t count = trace.size();
|
||||
auto* frames = new ctrace_stacktrace_frame[count];
|
||||
std::transform(
|
||||
trace.begin(),
|
||||
trace.end(),
|
||||
frames,
|
||||
[] (const cpptrace::stacktrace_frame& frame) -> ctrace_stacktrace_frame {
|
||||
ctrace_stacktrace_frame new_frame;
|
||||
new_frame.address = frame.address;
|
||||
new_frame.line = frame.line.value_or(invalid_pos);
|
||||
new_frame.column = frame.column.value_or(invalid_pos);
|
||||
new_frame.filename = generate_owning_string(frame.filename).data;
|
||||
new_frame.symbol = generate_owning_string(cpptrace::detail::demangle(frame.symbol)).data;
|
||||
new_frame.is_inline = ctrace_bool(frame.is_inline);
|
||||
return new_frame;
|
||||
}
|
||||
);
|
||||
return { frames, count };
|
||||
}
|
||||
|
||||
static cpptrace::stacktrace cpp_convert(const ctrace_stacktrace* ptrace) {
|
||||
if(!ptrace || !ptrace->frames) return { };
|
||||
std::vector<cpptrace::stacktrace_frame> new_frames;
|
||||
new_frames.reserve(ptrace->count);
|
||||
for(std::size_t i = 0; i < ptrace->count; ++i) {
|
||||
using nullable_type = cpptrace::nullable<std::uint32_t>;
|
||||
static constexpr auto null_v = nullable_type::null().raw_value;
|
||||
const ctrace_stacktrace_frame& old_frame = ptrace->frames[i];
|
||||
cpptrace::stacktrace_frame new_frame;
|
||||
new_frame.address = old_frame.address;
|
||||
new_frame.line = nullable_type{is_empty(old_frame.line) ? null_v : old_frame.line};
|
||||
new_frame.column = nullable_type{is_empty(old_frame.column) ? null_v : old_frame.column};
|
||||
new_frame.filename = old_frame.filename;
|
||||
new_frame.symbol = old_frame.symbol;
|
||||
new_frame.is_inline = bool(old_frame.is_inline);
|
||||
new_frames.push_back(std::move(new_frame));
|
||||
}
|
||||
return cpptrace::stacktrace{std::move(new_frames)};
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
// ctrace::string
|
||||
ctrace_owning_string ctrace_generate_owning_string(const char* raw_string) {
|
||||
return ctrace::generate_owning_string(raw_string);
|
||||
}
|
||||
|
||||
void ctrace_free_owning_string(ctrace_owning_string* string) {
|
||||
if(!string) return;
|
||||
ctrace::free_owning_string(*string);
|
||||
string->data = nullptr;
|
||||
}
|
||||
|
||||
// ctrace::generation:
|
||||
CTRACE_FORCE_NO_INLINE
|
||||
ctrace_raw_trace ctrace_generate_raw_trace(size_t skip, size_t max_depth) {
|
||||
try {
|
||||
std::vector<cpptrace::frame_ptr> trace = cpptrace::detail::capture_frames(skip + 1, max_depth);
|
||||
std::size_t count = trace.size();
|
||||
auto* frames = new ctrace_frame_ptr[count];
|
||||
std::copy(trace.data(), trace.data() + count, frames);
|
||||
return { frames, count };
|
||||
} catch(...) {
|
||||
// Don't check rethrow condition, it's risky.
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
CTRACE_FORCE_NO_INLINE
|
||||
ctrace_object_trace ctrace_generate_object_trace(size_t skip, size_t max_depth) {
|
||||
try {
|
||||
std::vector<cpptrace::object_frame> trace = cpptrace::detail::get_frames_object_info(
|
||||
cpptrace::detail::capture_frames(skip + 1, max_depth)
|
||||
);
|
||||
return ctrace::c_convert(trace);
|
||||
} catch(...) { // NOSONAR
|
||||
// Don't check rethrow condition, it's risky.
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
CTRACE_FORCE_NO_INLINE
|
||||
ctrace_stacktrace ctrace_generate_trace(size_t skip, size_t max_depth) {
|
||||
try {
|
||||
std::vector<cpptrace::frame_ptr> frames = cpptrace::detail::capture_frames(skip + 1, max_depth);
|
||||
std::vector<cpptrace::stacktrace_frame> trace = cpptrace::detail::resolve_frames(frames);
|
||||
return ctrace::c_convert(trace);
|
||||
} catch(...) { // NOSONAR
|
||||
// Don't check rethrow condition, it's risky.
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ctrace::freeing:
|
||||
void ctrace_free_raw_trace(ctrace_raw_trace* trace) {
|
||||
if(!trace) return;
|
||||
ctrace_frame_ptr* frames = trace->frames;
|
||||
delete[] frames;
|
||||
trace->frames = nullptr;
|
||||
trace->count = 0;
|
||||
}
|
||||
|
||||
void ctrace_free_object_trace(ctrace_object_trace* trace) {
|
||||
if(!trace || !trace->frames) return;
|
||||
ctrace_object_frame* frames = trace->frames;
|
||||
for(std::size_t i = 0; i < trace->count; ++i) {
|
||||
const char* path = frames[i].obj_path;
|
||||
ctrace::free_owning_string(path);
|
||||
}
|
||||
|
||||
delete[] frames;
|
||||
trace->frames = nullptr;
|
||||
trace->count = 0;
|
||||
}
|
||||
|
||||
void ctrace_free_stacktrace(ctrace_stacktrace* trace) {
|
||||
if(!trace || !trace->frames) return;
|
||||
ctrace_stacktrace_frame* frames = trace->frames;
|
||||
for(std::size_t i = 0; i < trace->count; ++i) {
|
||||
ctrace::free_owning_string(frames[i].filename);
|
||||
ctrace::free_owning_string(frames[i].symbol);
|
||||
}
|
||||
|
||||
delete[] frames;
|
||||
trace->frames = nullptr;
|
||||
trace->count = 0;
|
||||
}
|
||||
|
||||
// ctrace::resolve:
|
||||
ctrace_stacktrace ctrace_raw_trace_resolve(const ctrace_raw_trace* trace) {
|
||||
if(!trace || !trace->frames) return { nullptr, 0 };
|
||||
try {
|
||||
std::vector<cpptrace::frame_ptr> frames(trace->count, 0);
|
||||
std::copy(trace->frames, trace->frames + trace->count, frames.begin());
|
||||
std::vector<cpptrace::stacktrace_frame> resolved = cpptrace::detail::resolve_frames(frames);
|
||||
return ctrace::c_convert(resolved);
|
||||
} catch(...) { // NOSONAR
|
||||
// Don't check rethrow condition, it's risky.
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
ctrace_object_trace ctrace_raw_trace_resolve_object_trace(const ctrace_raw_trace* trace) {
|
||||
if(!trace || !trace->frames) return { nullptr, 0 };
|
||||
try {
|
||||
std::vector<cpptrace::frame_ptr> frames(trace->count, 0);
|
||||
std::copy(trace->frames, trace->frames + trace->count, frames.begin());
|
||||
std::vector<cpptrace::object_frame> obj = cpptrace::detail::get_frames_object_info(frames);
|
||||
return ctrace::c_convert(obj);
|
||||
} catch(...) { // NOSONAR
|
||||
// Don't check rethrow condition, it's risky.
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
ctrace_stacktrace ctrace_object_trace_resolve(const ctrace_object_trace* trace) {
|
||||
if(!trace || !trace->frames) return { nullptr, 0 };
|
||||
try {
|
||||
std::vector<cpptrace::frame_ptr> frames(trace->count, 0);
|
||||
std::transform(
|
||||
trace->frames,
|
||||
trace->frames + trace->count,
|
||||
frames.begin(),
|
||||
[] (const ctrace_object_frame& frame) -> cpptrace::frame_ptr {
|
||||
return frame.raw_address;
|
||||
}
|
||||
);
|
||||
std::vector<cpptrace::stacktrace_frame> resolved = cpptrace::detail::resolve_frames(frames);
|
||||
return ctrace::c_convert(resolved);
|
||||
} catch(...) { // NOSONAR
|
||||
// Don't check rethrow condition, it's risky.
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
// ctrace::safe:
|
||||
size_t ctrace_safe_generate_raw_trace(ctrace_frame_ptr* buffer, size_t size, size_t skip, size_t max_depth) {
|
||||
return cpptrace::safe_generate_raw_trace(buffer, size, skip, max_depth);
|
||||
}
|
||||
|
||||
void ctrace_get_safe_object_frame(ctrace_frame_ptr address, ctrace_safe_object_frame* out) {
|
||||
// TODO: change this?
|
||||
static_assert(sizeof(cpptrace::safe_object_frame) == sizeof(ctrace_safe_object_frame), "");
|
||||
cpptrace::get_safe_object_frame(address, reinterpret_cast<cpptrace::safe_object_frame*>(out));
|
||||
}
|
||||
|
||||
// ctrace::io:
|
||||
ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color) {
|
||||
if(!trace || !trace->frames) return ctrace::generate_owning_string("<empty trace>");
|
||||
auto cpp_trace = ctrace::cpp_convert(trace);
|
||||
std::string trace_string = cpp_trace.to_string(bool(use_color));
|
||||
return ctrace::generate_owning_string(trace_string);
|
||||
}
|
||||
|
||||
void ctrace_stacktrace_print(const ctrace_stacktrace* trace, FILE* to, ctrace_bool use_color) {
|
||||
if(use_color) cpptrace::detail::enable_virtual_terminal_processing_if_needed();
|
||||
ctrace::ffprintf(to, "Stack trace (most recent call first):\n");
|
||||
if(trace->count == 0 || !trace->frames) {
|
||||
ctrace::ffprintf(to, "<empty trace>\n");
|
||||
return;
|
||||
}
|
||||
const auto reset = use_color ? ESC "0m" : "";
|
||||
const auto green = use_color ? ESC "32m" : "";
|
||||
const auto yellow = use_color ? ESC "33m" : "";
|
||||
const auto blue = use_color ? ESC "34m" : "";
|
||||
const auto frame_number_width = cpptrace::detail::n_digits(unsigned(trace->count - 1));
|
||||
ctrace_stacktrace_frame* frames = trace->frames;
|
||||
for(std::size_t i = 0; i < trace->count; ++i) {
|
||||
static constexpr auto ptr_len = 2 * sizeof(cpptrace::frame_ptr);
|
||||
ctrace::ffprintf(to, "#%-*llu ", int(frame_number_width), i);
|
||||
if(frames[i].is_inline) {
|
||||
(void)std::fprintf(to, "%*s",
|
||||
int(ptr_len + 2),
|
||||
"(inlined)");
|
||||
} else {
|
||||
(void)std::fprintf(to, "%s0x%0*llx%s",
|
||||
blue,
|
||||
int(ptr_len),
|
||||
cpptrace::detail::to_ull(frames[i].address),
|
||||
reset);
|
||||
}
|
||||
if(!ctrace::is_empty(frames[i].symbol)) {
|
||||
(void)std::fprintf(to, " in %s%s%s",
|
||||
yellow,
|
||||
frames[i].symbol,
|
||||
reset);
|
||||
}
|
||||
if(!ctrace::is_empty(frames[i].filename)) {
|
||||
(void)std::fprintf(to, " at %s%s%s",
|
||||
green,
|
||||
frames[i].filename,
|
||||
reset);
|
||||
if(ctrace::is_empty(frames[i].line)) {
|
||||
ctrace::ffprintf(to, "\n");
|
||||
continue;
|
||||
}
|
||||
(void)std::fprintf(to, ":%s%llu%s",
|
||||
blue,
|
||||
cpptrace::detail::to_ull(frames[i].line),
|
||||
reset);
|
||||
if(ctrace::is_empty(frames[i].column)) {
|
||||
ctrace::ffprintf(to, "\n");
|
||||
continue;
|
||||
}
|
||||
(void)std::fprintf(to, ":%s%llu%s",
|
||||
blue,
|
||||
cpptrace::detail::to_ull(frames[i].column),
|
||||
reset);
|
||||
}
|
||||
// always print newline at end :M
|
||||
ctrace::ffprintf(to, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
// utility::demangle:
|
||||
ctrace_owning_string ctrace_demangle(const char* mangled) {
|
||||
if(!mangled) return ctrace::generate_owning_string("");
|
||||
std::string demangled = cpptrace::demangle(mangled);
|
||||
return ctrace::generate_owning_string(demangled);
|
||||
}
|
||||
|
||||
// utility::io
|
||||
int ctrace_stdin_fileno(void) {
|
||||
return cpptrace::stdin_fileno;
|
||||
}
|
||||
|
||||
int ctrace_stderr_fileno(void) {
|
||||
return cpptrace::stderr_fileno;
|
||||
}
|
||||
|
||||
int ctrace_stdout_fileno(void) {
|
||||
return cpptrace::stdout_fileno;
|
||||
}
|
||||
|
||||
ctrace_bool ctrace_isatty(int fd) {
|
||||
return cpptrace::isatty(fd);
|
||||
}
|
||||
|
||||
// utility::cache:
|
||||
void ctrace_set_cache_mode(ctrace_cache_mode mode) {
|
||||
static constexpr auto cache_max = cpptrace::cache_mode::prioritize_speed;
|
||||
if(mode > unsigned(cache_max)) return;
|
||||
auto cache_mode = static_cast<cpptrace::cache_mode>(mode);
|
||||
cpptrace::experimental::set_cache_mode(cache_mode);
|
||||
}
|
||||
|
||||
ctrace_cache_mode ctrace_get_cache_mode(void) {
|
||||
auto cache_mode = cpptrace::detail::get_cache_mode();
|
||||
return static_cast<ctrace_cache_mode>(cache_mode);
|
||||
}
|
||||
}
|
||||
@ -345,7 +345,7 @@ namespace dbghelp {
|
||||
std::fprintf(stderr, "Stack trace: Internal error while calling SymSetContext\n");
|
||||
return {
|
||||
addr,
|
||||
static_cast<std::uint32_t>(line.LineNumber),
|
||||
{ static_cast<std::uint32_t>(line.LineNumber) },
|
||||
nullable<std::uint32_t>::null(),
|
||||
line.FileName,
|
||||
symbol->Name,
|
||||
@ -377,7 +377,7 @@ namespace dbghelp {
|
||||
signature = std::regex_replace(signature, comma_re, ", ");
|
||||
return {
|
||||
addr,
|
||||
static_cast<std::uint32_t>(line.LineNumber),
|
||||
{ static_cast<std::uint32_t>(line.LineNumber) },
|
||||
nullable<std::uint32_t>::null(),
|
||||
line.FileName,
|
||||
signature,
|
||||
|
||||
58
test/ctrace_demo.cpp
Normal file
58
test/ctrace_demo.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
#include <ctrace/ctrace.h>
|
||||
#include <cassert>
|
||||
#include <climits>
|
||||
#include <cstdio>
|
||||
|
||||
void test_linker() {
|
||||
/* Owning String */ {
|
||||
auto str = ctrace_generate_owning_string("Hello C!");
|
||||
std::printf("%s\n", str.data);
|
||||
ctrace_free_owning_string(&str);
|
||||
assert(str.data == nullptr);
|
||||
} /* Trace */ {
|
||||
ctrace_stacktrace trace = ctrace_generate_trace(0, INT_MAX);
|
||||
ctrace_owning_string str = ctrace_stacktrace_to_string(&trace, 0);
|
||||
ctrace_free_stacktrace(&trace);
|
||||
assert(trace.count == 0);
|
||||
std::printf("%s\n", str.data);
|
||||
ctrace_free_owning_string(&str);
|
||||
}
|
||||
}
|
||||
|
||||
void trace() {
|
||||
ctrace_raw_trace raw_trace = ctrace_generate_raw_trace(1, INT_MAX);
|
||||
ctrace_object_trace obj_trace = ctrace_raw_trace_resolve_object_trace(&raw_trace);
|
||||
ctrace_stacktrace trace = ctrace_object_trace_resolve(&obj_trace);
|
||||
ctrace_stacktrace_print(&trace, stdout, 1);
|
||||
ctrace_free_stacktrace(&trace);
|
||||
ctrace_free_object_trace(&obj_trace);
|
||||
ctrace_free_raw_trace(&raw_trace);
|
||||
assert(raw_trace.frames == nullptr && obj_trace.count == 0);
|
||||
}
|
||||
|
||||
void foo(int n) {
|
||||
if(n == 0) {
|
||||
trace();
|
||||
} else {
|
||||
foo(n - 1);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void foo(int x, Args... args) {
|
||||
foo(args...);
|
||||
}
|
||||
|
||||
void function_two(int, float) {
|
||||
foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
|
||||
}
|
||||
|
||||
CTRACE_FORCE_INLINE
|
||||
void function_one(int) {
|
||||
function_two(0, 0);
|
||||
}
|
||||
|
||||
int main() {
|
||||
test_linker();
|
||||
function_one(0);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user