commit b3474b50c323bb2164de52664fca502296cd9dac Author: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sat Jul 1 16:44:57 2023 -0400 Initial commit diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..e4666b7 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,41 @@ +name: build c++20 + +on: + push: + pull_request: + +jobs: + build-gcc: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v2 + - name: dependencies + run: sudo apt install gcc-10 g++-10 + - name: build + run: | + mkdir -p build + cd build + cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=g++-10 + make + build-clang: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v2 + - name: build + run: | + mkdir -p build + cd build + cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=clang++-14 + make + build-msvc: + runs-on: windows-2019 + steps: + - uses: actions/checkout@v2 + - name: Enable Developer Command Prompt + uses: ilammy/msvc-dev-cmd@v1.10.0 + - name: build + run: | + mkdir -p build + cd build + cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=cl + make diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ecd87b2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.vscode +build +a.out +test/build diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..42b0edd --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,261 @@ +cmake_minimum_required(VERSION 3.8...3.23) + +if(${CMAKE_VERSION} VERSION_LESS 3.12) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +endif() + +project( + libcpptrace + VERSION 1.0.0 + LANGUAGES CXX +) + +set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) + +#set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +include(GNUInstallDirs) +include(CheckCXXSourceCompiles) + +file(GLOB_RECURSE sources src/*.cpp) +#message(STATUS "Sources" ${sources}) +add_library(cpptrace ${sources} include/cpptrace/cpptrace.hpp) + +target_include_directories( + cpptrace + PUBLIC + $ + $ +) + +target_compile_features( + cpptrace + PUBLIC + cxx_std_11 +) + +set_target_properties( + cpptrace + PROPERTIES + CXX_STANDARD_REQUIRED TRUE + CXX_EXTENSIONS OFF +) + +target_compile_options( + cpptrace + PRIVATE + $<$>:-Wall -Wextra -Werror=return-type -Wshadow> + $<$:/W4 /WX /permissive-> +) + +function(check_support var source includes libraries definitions) + set(CMAKE_REQUIRED_INCLUDES "${includes}") + list(APPEND CMAKE_REQUIRED_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + set(CMAKE_REQUIRED_LIBRARIES "${libraries}") + set(CMAKE_REQUIRED_DEFINITIONS "${definitions}") + check_cxx_source_compiles("#include \"${source}\"" ${var}) + set(${var} ${${var}} PARENT_SCOPE) +endfunction() + +option(LIBCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE "" OFF) + +option(LIBCPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE "" OFF) +option(LIBCPPTRACE_GET_SYMBOLS_WITH_LIBDL "" OFF) +option(LIBCPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE "" OFF) +option(LIBCPPTRACE_GET_SYMBOLS_WITH_DBGHELP "" OFF) +option(LIBCPPTRACE_GET_SYMBOLS_WITH_NOTHING "" OFF) + +option(LIBCPPTRACE_UNWIND_WITH_EXECINFO "" OFF) +option(LIBCPPTRACE_UNWIND_WITH_WINAPI "" OFF) +option(LIBCPPTRACE_UNWIND_WITH_NOTHING "" OFF) + +option(LIBCPPTRACE_DEMANGLE_WITH_CXXABI "" OFF) +option(LIBCPPTRACE_DEMANGLE_WITH_NOTHING "" OFF) + +# =============================================== Autoconfig full dump =============================================== +# If nothing is specified, attempt to use libbacktrace's full dump +if( + NOT ( + LIBCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE OR + LIBCPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE OR + LIBCPPTRACE_GET_SYMBOLS_WITH_NOTHING OR + LIBCPPTRACE_UNWIND_WITH_EXECINFO OR + LIBCPPTRACE_UNWIND_WITH_WINAPI OR + LIBCPPTRACE_UNWIND_WITH_NOTHING + ) +) + # Attempt to auto-config + check_support(HAS_BACKTRACE has_backtrace.cpp "" "backtrace" "") + + if(HAS_BACKTRACE) + set(LIBCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE On) + message(STATUS "Cpptrace auto config: Using libbacktrace for the full trace") + endif() +endif() + +# =============================================== Autoconfig unwinding =============================================== +# Unwind back-ends (If not doing LIBCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE) +if( + NOT ( + LIBCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE OR + LIBCPPTRACE_UNWIND_WITH_EXECINFO OR + LIBCPPTRACE_UNWIND_WITH_WINAPI OR + LIBCPPTRACE_UNWIND_WITH_NOTHING + ) +) + # Attempt to auto-config + if(UNIX) + check_support(HAS_EXECINFO has_execinfo.cpp "" "" "") + + if(HAS_EXECINFO) + set(LIBCPPTRACE_UNWIND_WITH_EXECINFO On) + message(STATUS "Cpptrace auto config: Using execinfo.h for unwinding") + else() + set(LIBCPPTRACE_UNWIND_WITH_NOTHING On) + message(WARNING "Cpptrace auto config: doesn't seem to be supported, stack tracing will not work.") + endif() + elseif(WIN32) + set(LIBCPPTRACE_UNWIND_WITH_WINAPI On) + message(STATUS "Cpptrace auto config: Using winapi for unwinding") + endif() +else() + #message(STATUS "MANUAL CONFIG SPECIFIED") +endif() + +# =============================================== Autoconfig symbols =============================================== +# Symbol back-ends (If not doing LIBCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE) +if( + NOT ( + LIBCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE OR + LIBCPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE OR + LIBCPPTRACE_GET_SYMBOLS_WITH_NOTHING + ) +) + # Attempt to auto-config + if(UNIX) + check_support(HAS_EXECINFO has_execinfo.cpp "" "" "") + + if(HAS_EXECINFO) + set(LIBCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE On) + message(STATUS "Cpptrace auto config: Using libbacktrace for symbols") + else() + message(WARNING "Cpptrace auto config: No symbol back end could be automatically configured") + endif() + elseif(WIN32) + #set(LIBCPPTRACE_UNWIND_WITH_WINAPI On) + #message(STATUS "Cpptrace auto config: Using winapi for unwinding") + endif() +else() + #message(STATUS "MANUAL CONFIG SPECIFIED") +endif() + +# =============================================== Autoconfig demangling =============================================== +# Handle demangle configuration +if( + NOT ( + LIBCPPTRACE_DEMANGLE_WITH_CXXABI OR + LIBCPPTRACE_DEMANGLE_WITH_NOTHING + ) +) + check_support(HAS_CXXABI has_cxxabi.cpp "" "" "") + if(HAS_CXXABI) + set(LIBCPPTRACE_DEMANGLE_WITH_CXXABI On) + else() + set(LIBCPPTRACE_DEMANGLE_WITH_NOTHING On) + endif() +else() + #message(STATUS "Manual demangling back-end specified") +endif() + +# =============================================== Apply options to build =============================================== + +# Full +if(LIBCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE) + target_compile_definitions(cpptrace PUBLIC LIBCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE) + target_link_libraries(cpptrace PRIVATE backtrace) +endif() + +# Symbols +if(LIBCPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE) + target_compile_definitions(cpptrace PUBLIC LIBCPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE) +endif() + +# Unwinding +if(LIBCPPTRACE_UNWIND_WITH_EXECINFO) + target_compile_definitions(cpptrace PUBLIC LIBCPPTRACE_UNWIND_WITH_EXECINFO) +endif() + +if(LIBCPPTRACE_UNWIND_WITH_WINAPI) + target_compile_definitions(cpptrace PUBLIC LIBCPPTRACE_UNWIND_WITH_WINAPI) +endif() + +# Demangling +if(LIBCPPTRACE_DEMANGLE_WITH_CXXABI) + target_compile_definitions(cpptrace PUBLIC LIBCPPTRACE_DEMANGLE_WITH_CXXABI) +endif() + +if(LIBCPPTRACE_DEMANGLE_WITH_NOTHING) + target_compile_definitions(cpptrace PUBLIC LIBCPPTRACE_DEMANGLE_WITH_NOTHING) +endif() + +target_link_libraries( + cpptrace + PRIVATE + $<$:dbghelp> + ${CMAKE_DL_LIBS} +) + +if(CMAKE_BUILD_TYPE STREQUAL "") + message(FATAL_ERROR "Setting CMAKE_BUILD_TYPE is required") +endif() + +if(NOT CMAKE_SKIP_INSTALL_RULES) + include(CMakePackageConfigHelpers) + + install( + TARGETS cpptrace + EXPORT cpptrace_targets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR} + ) + + install( + FILES + include/cpptrace.hpp + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cpptrace/cpptrace + ) + + export( + EXPORT cpptrace_targets + FILE ${CMAKE_CURRENT_BINARY_DIR}/cpptrace/cpptrace_targets.cmake + NAMESPACE cpptrace:: + ) + + configure_package_config_file( + cmake/cpptrace-config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/cpptrace/cpptrace-config.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cpptrace + ) + + write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/cpptrace/cpptrace-config-version.cmake + VERSION ${PACKAGE_VERSION} + COMPATIBILITY SameMajorVersion + ) + + install( + EXPORT cpptrace_targets + FILE cpptrace_targets.cmake + NAMESPACE cpptrace:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cpptrace + ) + + install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/cpptrace/cpptrace-config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/cpptrace/cpptrace-config-version.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cpptrace + ) +endif() diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..152a555 --- /dev/null +++ b/LICENSE @@ -0,0 +1,18 @@ +The MIT License (MIT) + +Copyright (c) 2023 Jeremy Rifkin + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, +sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a5f41e2 --- /dev/null +++ b/README.md @@ -0,0 +1,74 @@ +# libcpptrace + +Libcpptrace is a lightweight C++ stacktrace library supporting C++11 and greater on Linux, Unix, MacOS, and Windows +(including cygwin / mingw environments). The goal: Make traces simple for once. + +*Some day C++23's `` will be ubiquitous* + +## Table of contents + +- [libcpptrace](#libcpptrace) + - [Table of contents](#table-of-contents) + - [Docs](#docs) + - [Backends](#backends) + - [License](#license) + +## Docs + +`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. + +**Note:** Debug info (`-g`) is generally required for good trace information. Some back-ends read symbols from dynamic +export information which requires `-rdynamic` or manually marking symbols for exporting. + +```cpp +namespace cpptrace { + struct stacktrace_frame { + size_t line; + size_t col; + std::string filename; + std::string symbol; + }; + std::vector generate_trace(); + void print_trace(); +} +``` + +## Back-ends + +Back-end libraries are required for unwinding the stack and resolving symbol information (name and source location) in +order to generate a stacktrace. + +The CMake script attempts to automatically choose a good back-end based on what is available on your system. You can +also manually set which back-end you want used. + +**Unwinding** + +| Library | CMake config | Windows | Linux | Info | +|---------|--------------|---------|-------|------| +| execinfo.h | `LIBCPPTRACE_UNWIND_WITH_EXECINFO` | | ✔️ | Frames are captured with `execinfo.h`'s `backtrace`, part of libc. | +| winapi | `LIBCPPTRACE_UNWIND_WITH_WINAPI` | ✔️ | | Frames are captured with `CaptureStackBackTrace`. | +| N/A | `LIBCPPTRACE_UNWIND_WITH_NOTHING` | ✔️ | ✔️ | Unwinding is not done, stack traces will be empty. | + +**Symbol resolution** + +| Library | CMake config | Windows | Linux | Info | +|---------|--------------|---------|-------|------| +| libbacktrace | `LIBCPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE` | ❌ | ✔️ | Libbacktrace is already installed on most systems, or available through the compiler directly. | +| libdl | `LIBCPPTRACE_GET_SYMBOLS_WITH_LIBDL` | | ✔️ | Libdl uses dynamic export information. Compiling with `-rdynamic` is often needed. | +| addr2line | `LIBCPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE` | | ✔️ | Symbols are resolved by invoking `addr2line` via `fork()`. | +| dbghelp.h | `LIBCPPTRACE_GET_SYMBOLS_WITH_DBGHELP` | ✔️ | ❌ | Dbghelp.h allows access to symbols via debug info. | +| N/A | `LIBCPPTRACE_GET_SYMBOLS_WITH_NOTHING` | ✔️ | ✔️ | Don't attempt to resolve symbols. | + +Lastly, C++ symbol demangling is done with ``. Under dbghelp.h this is not needed, the symbols aren't mangled +when they are first extracted. + +Libbacktrace can generate a full stack trace itself, both unwinding and resolving symbols, and this can be chosen with +`LIBCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE`. + +There are plenty more libraries that can be used for unwinding, parsing debug information, and demangling. In the future +more options may be added. + +## License + +The library is under the MIT license. diff --git a/cmake/cpptrace-config.cmake.in b/cmake/cpptrace-config.cmake.in new file mode 100644 index 0000000..c84bed9 --- /dev/null +++ b/cmake/cpptrace-config.cmake.in @@ -0,0 +1,3 @@ +@PACKAGE_INIT@ + +include(${CMAKE_CURRENT_LIST_DIR}/cpptrace_targets.cmake) diff --git a/cmake/has_backtrace.cpp b/cmake/has_backtrace.cpp new file mode 100644 index 0000000..69c599a --- /dev/null +++ b/cmake/has_backtrace.cpp @@ -0,0 +1,5 @@ +#include + +int main() { + backtrace_state* state = backtrace_create_state(nullptr, true, nullptr, nullptr); +} diff --git a/cmake/has_cxxabi.cpp b/cmake/has_cxxabi.cpp new file mode 100644 index 0000000..01cafc6 --- /dev/null +++ b/cmake/has_cxxabi.cpp @@ -0,0 +1,6 @@ +#include + +int main() { + void* frames[10]; + int size = backtrace(frames, 10); +} diff --git a/cmake/has_execinfo.cpp b/cmake/has_execinfo.cpp new file mode 100644 index 0000000..69c599a --- /dev/null +++ b/cmake/has_execinfo.cpp @@ -0,0 +1,5 @@ +#include + +int main() { + backtrace_state* state = backtrace_create_state(nullptr, true, nullptr, nullptr); +} diff --git a/include/cpptrace/cpptrace.hpp b/include/cpptrace/cpptrace.hpp new file mode 100644 index 0000000..95fc57c --- /dev/null +++ b/include/cpptrace/cpptrace.hpp @@ -0,0 +1,20 @@ +#ifndef CPPTRACE_HPP +#define CPPTRACE_HPP + +#include +#include +#include + +namespace cpptrace { + struct stacktrace_frame { + uintptr_t address; + int line; + int col; + std::string filename; + std::string symbol; + }; + std::vector generate_trace(); + void print_trace(); +} + +#endif diff --git a/src/cpptrace.cpp b/src/cpptrace.cpp new file mode 100644 index 0000000..52cab88 --- /dev/null +++ b/src/cpptrace.cpp @@ -0,0 +1,64 @@ +#include + +#include +#include +#include +#include + +#ifndef LIBCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE + +#include "symbols/libcpp_symbols.hpp" +#include "unwind/libcpp_unwind.hpp" +#include "demangle/libcpp_demangle.hpp" + +namespace cpptrace { + std::vector generate_trace() { + std::vector frames = detail::capture_frames(); + std::vector trace; + detail::symbolizer symbolizer; + for(const auto frame : frames) { + auto entry = symbolizer.resolve_frame(frame); + entry.symbol = detail::demangle(entry.symbol); + trace.push_back(entry); + } + return trace; + } +} + +#else + +// full trace + +#include "full/libcpp_full_trace.hpp" +#include "demangle/libcpp_demangle.hpp" + +namespace cpptrace { + std::vector generate_trace() { + auto trace = detail::generate_trace(); + for(auto& entry : trace) { + entry.symbol = detail::demangle(entry.symbol); + } + return trace; + } +} + +#endif + +namespace cpptrace { + void print_trace() { + std::cerr<<"Stack trace (most recent call first):"< 0 ? ":" + std::to_string(frame.col) : "") + << " " + << frame.symbol + << std::endl; + } + } +} diff --git a/src/demangle/demangle_with_cxxabi.cpp b/src/demangle/demangle_with_cxxabi.cpp new file mode 100644 index 0000000..216eef3 --- /dev/null +++ b/src/demangle/demangle_with_cxxabi.cpp @@ -0,0 +1,27 @@ +#ifdef LIBCPPTRACE_DEMANGLE_WITH_CXXABI + +#include +#include "libcpp_demangle.hpp" + +#include + +#include +#include + +namespace cpptrace { + namespace detail { + std::string demangle(const std::string& name) { + int status; + char* demangled = abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status); + if(demangled) { + std::string s = demangled; + free(demangled); + return s; + } else { + return name; + } + } + } +} + +#endif diff --git a/src/demangle/demangle_with_nothing.cpp b/src/demangle/demangle_with_nothing.cpp new file mode 100644 index 0000000..53beda7 --- /dev/null +++ b/src/demangle/demangle_with_nothing.cpp @@ -0,0 +1,19 @@ +#ifdef LIBCPPTRACE_DEMANGLE_WITH_NOTHING + +#include +#include "libcpp_demangle.hpp" + +#include + +#include +#include + +namespace cpptrace { + namespace detail { + std::string demangle(const std::string& name) { + return name; + } + } +} + +#endif diff --git a/src/demangle/libcpp_demangle.hpp b/src/demangle/libcpp_demangle.hpp new file mode 100644 index 0000000..efb90cc --- /dev/null +++ b/src/demangle/libcpp_demangle.hpp @@ -0,0 +1,14 @@ +#ifndef LIBCPP_DEMANGLE_HPP +#define LIBCPP_DEMANGLE_HPP + +#include + +#include + +namespace cpptrace { + namespace detail { + std::string demangle(const std::string&); + } +} + +#endif diff --git a/src/full/full_trace_with_libbacktrace.cpp b/src/full/full_trace_with_libbacktrace.cpp new file mode 100644 index 0000000..91c507c --- /dev/null +++ b/src/full/full_trace_with_libbacktrace.cpp @@ -0,0 +1,47 @@ +#ifdef LIBCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE + +#include +#include "libcpp_full_trace.hpp" +#include "../platform/libcpp_program_name.hpp" + +#include + +#include + +namespace cpptrace { + namespace detail { + int full_callback(void* data, uintptr_t address, const char* file, int line, const char* symbol) { + reinterpret_cast*>(data)->push_back({ + address, + line, + -1, + file ? file : "", + symbol ? symbol : "" + }); + return 0; + } + + void error_callback(void*, const char*, int) { + // nothing for now + } + + backtrace_state* get_backtrace_state() { + // backtrace_create_state must be called only one time per program + static backtrace_state* state = nullptr; + static bool called = false; + if(!called) { + state = backtrace_create_state(program_name().c_str(), true, error_callback, nullptr); + called = true; + } + return state; + } + + std::vector generate_trace() { + std::vector frames; + backtrace_full(get_backtrace_state(), 0, full_callback, error_callback, &frames); + return frames; + } + } +} + +#endif diff --git a/src/full/libcpp_full_trace.hpp b/src/full/libcpp_full_trace.hpp new file mode 100644 index 0000000..3b17f38 --- /dev/null +++ b/src/full/libcpp_full_trace.hpp @@ -0,0 +1,14 @@ +#ifndef LIBCPP_FULL_TRACE_HPP +#define LIBCPP_FULL_TRACE_HPP + +#include + +#include + +namespace cpptrace { + namespace detail { + std::vector generate_trace(); + } +} + +#endif diff --git a/src/platform/libcpp_program_name.hpp b/src/platform/libcpp_program_name.hpp new file mode 100644 index 0000000..90019df --- /dev/null +++ b/src/platform/libcpp_program_name.hpp @@ -0,0 +1,35 @@ +#ifndef LIBCPP_PROGRAM_NAME_HPP +#define LIBCPP_PROGRAM_NAME_HPP + +#include + +#ifdef _WIN32 +#include + +namespace cpptrace { + namespace detail { + inline std::string program_name() { + char buffer[MAX_PATH + 1]; + int res = GetModuleFileNameA(nullptr, buffer, MAX_PATH); + if(res) { + return buffer; + } else { + return ""; + } + } + } +} + +#else + +namespace cpptrace { + namespace detail { + inline std::string program_name() { + return ""; + } + } +} + +#endif + +#endif diff --git a/src/symbols/libcpp_symbols.hpp b/src/symbols/libcpp_symbols.hpp new file mode 100644 index 0000000..cc9b102 --- /dev/null +++ b/src/symbols/libcpp_symbols.hpp @@ -0,0 +1,22 @@ +#ifndef LIBCPP_SYMBOLIZE_HPP +#define LIBCPP_SYMBOLIZE_HPP + +#include + +#include +#include + +namespace cpptrace { + namespace detail { + class symbolizer { + struct impl; + std::unique_ptr impl; + public: + symbolizer(); + ~symbolizer(); + stacktrace_frame resolve_frame(void* addr); + }; + } +} + +#endif diff --git a/src/symbols/symbols_with_addr2line.cpp b/src/symbols/symbols_with_addr2line.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/symbols/symbols_with_dbghelp.cpp b/src/symbols/symbols_with_dbghelp.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/symbols/symbols_with_dl.cpp b/src/symbols/symbols_with_dl.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/symbols/symbols_with_libbacktrace.cpp b/src/symbols/symbols_with_libbacktrace.cpp new file mode 100644 index 0000000..d97629c --- /dev/null +++ b/src/symbols/symbols_with_libbacktrace.cpp @@ -0,0 +1,63 @@ +#ifdef LIBCPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE + +#include +#include "libcpp_symbolize.hpp" +#include "../platform/libcpp_program_name.hpp" + +#include +#include + +#include + +namespace cpptrace { + namespace detail { + backtrace_state* get_backtrace_state() { + // backtrace_create_state must be called only one time per program + static backtrace_state* state = nullptr; + static bool called = false; + if(!called) { + state = backtrace_create_state(program_name().c_str(), true, error_callback, nullptr); + called = true; + } + return state; + } + + int full_callback(void* data, uintptr_t address, const char* file, int line, const char* symbol) { + stacktrace_frame& frame = *static_cast(data); + data.address = address; + data.line = line; + data.filename = file ? file : ""; + data.symbol = symbol ? symbol : ""; + return 0; + } + + void error_callback(void*, const char*, int) { + // nothing at the moment + } + + symbolizer::symbolizer() : impl(std::make_unique()) {} + symbolizer::~symbolizer() = default; + + stacktrace_frame symbolizer::resolve_frame(void* addr) { + impl->resolve_frame(addr); + } + + // TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions + struct symbolizer::impl { + stacktrace_frame resolve_frame(void* addr) { + stacktrace_frame frame; + frame.col = -1; + backtrace_pcinfo( + get_backtrace_state(), + reinterpret_cast(addr), + full_callback, + error_callback, + &frame + ); + return frame; + } + }; + } +} + +#endif diff --git a/src/symbols/symbols_with_nothing.cpp b/src/symbols/symbols_with_nothing.cpp new file mode 100644 index 0000000..d54e713 --- /dev/null +++ b/src/symbols/symbols_with_nothing.cpp @@ -0,0 +1,28 @@ +#ifdef LIBCPPTRACE_GET_SYMBOLS_WITH_NOTHING + +#include +#include "libcpp_symbolize.hpp" +#include "../platform/libcpp_program_name.hpp" + +#include + +namespace cpptrace { + namespace detail { + symbolizer::symbolizer() = default; + symbolizer::~symbolizer() = default; + + stacktrace_frame symbolizer::resolve_frame(void*) { + return { + 0, + -1, + -1, + "", + "", + }; + } + + struct symbolizer::impl {}; + } +} + +#endif diff --git a/src/unwind/libcpp_unwind.hpp b/src/unwind/libcpp_unwind.hpp new file mode 100644 index 0000000..d1b41a9 --- /dev/null +++ b/src/unwind/libcpp_unwind.hpp @@ -0,0 +1,19 @@ +#ifndef LIBCPP_UNWIND_HPP +#define LIBCPP_UNWIND_HPP + +#include + +#include + +namespace cpptrace { + namespace detail { + #ifdef LIBCPPTRACE_HARD_MAX_FRAMES + constexpr size_t hard_max_frames = LIBCPPTRACE_HARD_MAX_FRAMES; + #else + constexpr size_t hard_max_frames = 100; + #endif + std::vector capture_frames(); + } +} + +#endif diff --git a/src/unwind/unwind_with_execinfo.cpp b/src/unwind/unwind_with_execinfo.cpp new file mode 100644 index 0000000..6c50dee --- /dev/null +++ b/src/unwind/unwind_with_execinfo.cpp @@ -0,0 +1,22 @@ +#ifdef LIBCPPTRACE_UNWIND_WITH_EXECINFO + +#include +#include "libcpp_unwind.hpp" + +#include + +#include + +namespace cpptrace { + namespace detail { + std::vector capture_frames() { + std::vector frames(hard_max_frames, nullptr); + int n_frames = backtrace(bt.data(), hard_max_frames); + frames.resize(n_frames); + frames.shrink_to_fit(); + return frames; + } + } +} + +#endif diff --git a/src/unwind/unwind_with_nothing.cpp b/src/unwind/unwind_with_nothing.cpp new file mode 100644 index 0000000..b9f905b --- /dev/null +++ b/src/unwind/unwind_with_nothing.cpp @@ -0,0 +1,16 @@ +#ifdef LIBCPPTRACE_UNWIND_WITH_NOTHING + +#include +#include "libcpp_unwind.hpp" + +#include + +namespace cpptrace { + namespace detail { + std::vector capture_frames() { + return {}; + } + } +} + +#endif diff --git a/src/unwind/unwind_with_winapi.cpp b/src/unwind/unwind_with_winapi.cpp new file mode 100644 index 0000000..73ff4b7 --- /dev/null +++ b/src/unwind/unwind_with_winapi.cpp @@ -0,0 +1,26 @@ +#ifdef LIBCPPTRACE_UNWIND_WITH_WINAPI + +#include +#include "libcpp_unwind.hpp" + +#include + +#include + +namespace cpptrace { + namespace detail { + std::vector capture_frames() { + // TODO: When does this need to be called? Can it be moved to the symbolizer? + SymSetOptions(SYMOPT_ALLOW_ABSOLUTE_SYMBOLS); + HANDLE proc = GetCurrentProcess(); + if(!SymInitialize(proc, NULL, TRUE)) return {}; + std::vector addrs(hard_max_frames, nullptr); + int frames = CaptureStackBackTrace(n_skip, hard_max_frames, addrs.data(), NULL); + addrs.resize(frames); + addrs.shrink_to_fit(); + return addrs; + } + } +} + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..34fac2f --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.8...3.23) + +if(${CMAKE_VERSION} VERSION_LESS 3.12) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +endif() + +project( + test-libcpptrace + VERSION 1.0.0 + LANGUAGES CXX +) + +add_executable(test test.cpp) +target_link_libraries(test PRIVATE cpptrace) +target_include_directories(test PRIVATE ../include) +target_link_directories(test PRIVATE ../build) diff --git a/test/test.cpp b/test/test.cpp new file mode 100644 index 0000000..fc55abc --- /dev/null +++ b/test/test.cpp @@ -0,0 +1,30 @@ +#include + +void trace() { + cpptrace::print_trace(); +} + +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); +} + +void function_one(int) { + function_two(0, 0); +} + +int main() { + function_one(0); +}