diff --git a/.gitignore b/.gitignore index 252257b..fbd07ba 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ build*/ repro*/ __pycache__ scratch +.vscode diff --git a/CMakeLists.txt b/CMakeLists.txt index 09158a1..c42206f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 "$<$:-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 $ + ) + 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 "$<$:-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 $ - ) - 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 "$<$:-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 $ - ) - 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) diff --git a/README.md b/README.md index e4dd4c1..17d81bd 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/include/cpptrace/cpptrace.hpp b/include/cpptrace/cpptrace.hpp index 156e348..10f7af3 100644 --- a/include/cpptrace/cpptrace.hpp +++ b/include/cpptrace/cpptrace.hpp @@ -9,7 +9,7 @@ #include #include #include - +// Generated by the build system. #include #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 { diff --git a/include/ctrace/ctrace.h b/include/ctrace/ctrace.h new file mode 100644 index 0000000..3aa1467 --- /dev/null +++ b/include/ctrace/ctrace.h @@ -0,0 +1,141 @@ +#ifndef CTRACE_H +#define CTRACE_H + +#include +#include +#include + +#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 diff --git a/src/ctrace.cpp b/src/ctrace.cpp new file mode 100644 index 0000000..7cc3a49 --- /dev/null +++ b/src/ctrace.cpp @@ -0,0 +1,398 @@ +#include +#include +#include + +#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 + 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::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::length(raw_string); + char* new_string = new char[count + 1]; + std::char_traits::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& 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& 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 new_frames; + new_frames.reserve(ptrace->count); + for(std::size_t i = 0; i < ptrace->count; ++i) { + using nullable_type = cpptrace::nullable; + 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 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 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 frames = cpptrace::detail::capture_frames(skip + 1, max_depth); + std::vector 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 frames(trace->count, 0); + std::copy(trace->frames, trace->frames + trace->count, frames.begin()); + std::vector 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 frames(trace->count, 0); + std::copy(trace->frames, trace->frames + trace->count, frames.begin()); + std::vector 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 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 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(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(""); + 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, "\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(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(cache_mode); + } +} diff --git a/src/symbols/symbols_with_dbghelp.cpp b/src/symbols/symbols_with_dbghelp.cpp index 82d2fd5..929419e 100644 --- a/src/symbols/symbols_with_dbghelp.cpp +++ b/src/symbols/symbols_with_dbghelp.cpp @@ -345,7 +345,7 @@ namespace dbghelp { std::fprintf(stderr, "Stack trace: Internal error while calling SymSetContext\n"); return { addr, - static_cast(line.LineNumber), + { static_cast(line.LineNumber) }, nullable::null(), line.FileName, symbol->Name, @@ -377,7 +377,7 @@ namespace dbghelp { signature = std::regex_replace(signature, comma_re, ", "); return { addr, - static_cast(line.LineNumber), + { static_cast(line.LineNumber) }, nullable::null(), line.FileName, signature, diff --git a/test/ctrace_demo.cpp b/test/ctrace_demo.cpp new file mode 100644 index 0000000..955de0a --- /dev/null +++ b/test/ctrace_demo.cpp @@ -0,0 +1,58 @@ +#include +#include +#include +#include + +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 +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); +}