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);
+}