From 3e0689a5e6db12c6e4802f999cb7dd92267f67d4 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sat, 9 Mar 2024 22:15:41 -0600 Subject: [PATCH 01/18] Resolve object address for dbghelp frames by default, handles #100 --- src/symbols/symbols_with_dbghelp.cpp | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/symbols/symbols_with_dbghelp.cpp b/src/symbols/symbols_with_dbghelp.cpp index 71e9203..31c46db 100644 --- a/src/symbols/symbols_with_dbghelp.cpp +++ b/src/symbols/symbols_with_dbghelp.cpp @@ -325,6 +325,13 @@ namespace dbghelp { // TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions stacktrace_frame resolve_frame(HANDLE proc, frame_ptr addr) { + // The get_frame_object_info() ends up being inexpensive, at on my machine + // debug release + // uncached trace resolution (29 frames) 1.9-2.1 ms 1.4-1.8 ms + // cached trace resolution (29 frames) 1.1-1.2 ms 0.2-0.4 ms + // get_frame_object_info() 0.001-0.002 ms 0.0003-0.0006 ms + // At some point it might make sense to make an option to control this. + auto object_frame = get_frame_object_info(addr); const std::lock_guard lock(dbghelp_lock); // all dbghelp functions are not thread safe alignas(SYMBOL_INFO) char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; SYMBOL_INFO* symbol = (SYMBOL_INFO*)buffer; @@ -345,7 +352,7 @@ namespace dbghelp { std::fprintf(stderr, "Stack trace: Internal error while calling SymSetContext\n"); return { addr, - 0, + object_frame.object_address, { static_cast(line.LineNumber) }, nullable::null(), line.FileName, @@ -378,7 +385,7 @@ namespace dbghelp { signature = std::regex_replace(signature, comma_re, ", "); return { addr, - 0, + object_frame.object_address, { static_cast(line.LineNumber) }, nullable::null(), line.FileName, @@ -388,7 +395,7 @@ namespace dbghelp { } else { return { addr, - 0, + object_frame.object_address, nullable::null(), nullable::null(), "", @@ -397,7 +404,15 @@ namespace dbghelp { }; } } else { - return { addr, 0, nullable::null(), nullable::null(), "", "", false }; + return { + addr, + object_frame.object_address, + nullable::null(), + nullable::null(), + "", + "", + false + }; } } From 754c5884646e2391b9a8d0d7f853d1afa3801163 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sat, 9 Mar 2024 22:25:04 -0600 Subject: [PATCH 02/18] Disable CE button temporarily --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 04d462d..40adc0a 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=jeremy-rifkin_cpptrace&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=jeremy-rifkin_cpptrace)
[![Community Discord Link](https://img.shields.io/badge/Chat%20on%20the%20(very%20small)-Community%20Discord-blue?labelColor=2C3239&color=7289DA&style=flat&logo=discord&logoColor=959DA5)](https://discord.gg/frjaAZvqUZ) -
-[![Try on Compiler Explorer](https://img.shields.io/badge/-Compiler%20Explorer-brightgreen?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAYAAAAmlE46AAAACXBIWXMAAACwAAAAsAEUaqtpAAABSElEQVQokYVTsU7DMBB9QMTCEJbOMLB5oF0tRfUPIPIJZctYJkZYu3WMxNL+ARUfQKpImcPgDYnsXWBgYQl61TkYyxI3Wef37j3fnQ/6vkcsikY9AbiWq0mpbevDBmLRqDEAA4CEHMADgFRwrwDmch6X2i73RCFVHvC/WCeCMAFpC2AFoPPu5x4md4rnAN4luS61nYWSgauNU8ydkr0bLTMYAoIYtWqxM4LtEumeERDtfUjlMDrp7L67iddyyJtOvUIu2rquVn4iiVSOKXYhiMSJWLwUJZLuQ2CWmVldV4MT11UmXgB8fr0dX3WP6VHMiVrscim6Da2mJxffzwSU2v6xWzSKmzQ4cUTOaCBTvWgU14xkzjhckKm/q3wnrRAcAhksxMZNAdxEf0fRKI6E8zqT1C0X28ccRpqAUltW5pu4sxv5Mb8B4AciE3bHMxz/+gAAAABJRU5ErkJggg==&labelColor=2C3239&style=flat&label=Try+it+on&color=30C452)](https://godbolt.org/z/5sEszzEPE) + Cpptrace is a simple, portable, and self-contained C++ stacktrace library supporting C++11 and greater on Linux, macOS, and Windows including MinGW and Cygwin environments. The goal: Make stack traces simple for once. From c35392d20bbd6fc8faaa0d4b0b8b8576a5c76f77 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sat, 9 Mar 2024 22:25:51 -0600 Subject: [PATCH 03/18] Fix comment --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 40adc0a..e51006b 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,10 @@ [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=jeremy-rifkin_cpptrace&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=jeremy-rifkin_cpptrace)
[![Community Discord Link](https://img.shields.io/badge/Chat%20on%20the%20(very%20small)-Community%20Discord-blue?labelColor=2C3239&color=7289DA&style=flat&logo=discord&logoColor=959DA5)](https://discord.gg/frjaAZvqUZ) - + Cpptrace is a simple, portable, and self-contained C++ stacktrace library supporting C++11 and greater on Linux, macOS, and Windows including MinGW and Cygwin environments. The goal: Make stack traces simple for once. From 92be6c23f1e8eebfacb84e860b838f3f6f15116a Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Fri, 15 Mar 2024 17:39:23 -0500 Subject: [PATCH 04/18] Update note about debug symbols being needed --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cb4bfa9..84e5809 100644 --- a/README.md +++ b/README.md @@ -141,14 +141,18 @@ thrown, and providing an API for safe tracing from signal handlers. # In-Depth Documentation +## Prerequisites + +> [!IMPORTANT] +> Debug info (`-g`/`/Z7`/`/Zi`/`/DEBUG`/`-DBUILD_TYPE=Debug`/`-DBUILD_TYPE=RelWithDebInfo`) is required for complete +> trace information. + ## `namespace cpptrace` `cpptrace::generate_trace()` can be used to generate a stacktrace object at the current call site. Resolved frames can be accessed from this object with `.frames` and also the trace can be printed with `.print()`. Cpptrace also provides a method to get lightweight raw traces, which are just vectors of program counters, which can be resolved at a later time. -**Note:** Debug info (`-g`/`/Z7`/`/Zi`/`/DEBUG`) is generally required for good trace information. - All functions are thread-safe unless otherwise noted. ### Stack Traces From a528aa8e0b6fbc8bb7ba2b81889509dd605b4d09 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sat, 16 Mar 2024 16:17:27 -0500 Subject: [PATCH 05/18] Fix object address resolution with _dl_find_object, #104 --- src/binary/object.hpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/binary/object.hpp b/src/binary/object.hpp index bbc59f3..bc41737 100644 --- a/src/binary/object.hpp +++ b/src/binary/object.hpp @@ -44,9 +44,7 @@ namespace detail { frame.object_path = buffer; } } - frame.object_address = address - - to_frame_ptr(result.dlfo_link_map->l_addr) - + get_module_image_base(frame.object_path); + frame.object_address = address - to_frame_ptr(result.dlfo_link_map->l_addr); } return frame; } From 8407adf6d3dde7f205129e37826da67a470740b1 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sat, 16 Mar 2024 17:52:38 -0500 Subject: [PATCH 06/18] Add print_with_snippets to docs --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 84e5809..390512e 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,9 @@ The core resolved stack trace object. Generate a trace with `cpptrace::generate_ `cpptrace::stacktrace::current()`. On top of a set of helper functions `struct stacktrace` allows direct access to frames as well as iterators. +`cpptrace::stacktrace::print` can be used to print a stacktrace. `cpptrace::stacktrace::print_with_snippets` can be used +to print a stack trace with source code snippets. + ```cpp namespace cpptrace { // Some type sufficient for an instruction pointer, currently always an alias to std::uintptr_t @@ -190,6 +193,9 @@ namespace cpptrace { void print() const; void print(std::ostream& stream) const; void print(std::ostream& stream, bool color) const; + void print_with_snippets() const; + void print_with_snippets(std::ostream& stream) const; + void print_with_snippets(std::ostream& stream, bool color) const; std::string to_string(bool color = false) const; void clear(); bool empty() const noexcept; From 9b69d200c1ce4a101a4551e8acf7440414ba497a Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sat, 16 Mar 2024 18:03:34 -0500 Subject: [PATCH 07/18] Bump to v0.5.0 --- CHANGELOG.md | 18 ++++++++++++++++++ CMakeLists.txt | 2 +- README.md | 14 +++++++------- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d12247d..b48df93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog - [Changelog](#changelog) +- [v0.5.0](#v050) - [v0.4.1](#v041) - [v0.4.0](#v040) - [v0.3.1](#v031) @@ -10,6 +11,23 @@ - [v0.1.1](#v011) - [v0.1](#v01) +# v0.5.0 + +New: +- Traces with source code snippets with `cpptrace::stacktrace::print_with_snippets` +- Added `cpptrace::get_snippet` utility +- Added `cpptrace::can_signal_safe_unwind` utility +- Added `stacktrace_frame::get_object_info` + +Changes: +- The library is now compiled with position-independent code by default + +Fixes: +- Fixed issue with `_dl_find_object` implementation + +Misc: +- Various refactoring, cleanup, and improvements + # v0.4.1 Changes: diff --git a/CMakeLists.txt b/CMakeLists.txt index 851c1e9..e27743f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,7 @@ set(package_name "cpptrace") project( cpptrace - VERSION 0.4.1 + VERSION 0.5.0 DESCRIPTION "Simple, portable, and self-contained stacktrace library for C++11 and newer " HOMEPAGE_URL "https://github.com/jeremy-rifkin/cpptrace" LANGUAGES C CXX diff --git a/README.md b/README.md index 390512e..7242589 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ include(FetchContent) FetchContent_Declare( cpptrace GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git - GIT_TAG v0.4.1 # + GIT_TAG v0.5.0 # ) FetchContent_MakeAvailable(cpptrace) target_link_libraries(your_target cpptrace::cpptrace) @@ -566,7 +566,7 @@ include(FetchContent) FetchContent_Declare( cpptrace GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git - GIT_TAG v0.4.1 # + GIT_TAG v0.5.0 # ) FetchContent_MakeAvailable(cpptrace) target_link_libraries(your_target cpptrace::cpptrace) @@ -582,7 +582,7 @@ information. ```sh git clone https://github.com/jeremy-rifkin/cpptrace.git -git checkout v0.4.1 +git checkout v0.5.0 mkdir cpptrace/build cd cpptrace/build cmake .. -DCMAKE_BUILD_TYPE=Release @@ -618,7 +618,7 @@ you when installing new libraries. ```ps1 git clone https://github.com/jeremy-rifkin/cpptrace.git -git checkout v0.4.1 +git checkout v0.5.0 mkdir cpptrace/build cd cpptrace/build cmake .. -DCMAKE_BUILD_TYPE=Release @@ -636,7 +636,7 @@ To install just for the local user (or any custom prefix): ```sh git clone https://github.com/jeremy-rifkin/cpptrace.git -git checkout v0.4.1 +git checkout v0.5.0 mkdir cpptrace/build cd cpptrace/build cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$HOME/wherever @@ -715,7 +715,7 @@ make install cd ~/scratch/cpptrace-test git clone https://github.com/jeremy-rifkin/cpptrace.git cd cpptrace -git checkout v0.4.1 +git checkout v0.5.0 mkdir build cd build cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=On -DCPPTRACE_USE_EXTERNAL_LIBDWARF=On -DCMAKE_PREFIX_PATH=~/scratch/cpptrace-test/resources -DCMAKE_INSTALL_PREFIX=~/scratch/cpptrace-test/resources @@ -735,7 +735,7 @@ cpptrace and its dependencies. Cpptrace is available through conan at https://conan.io/center/recipes/cpptrace. ``` [requires] -cpptrace/0.4.1 +cpptrace/0.5.0 [generators] CMakeDeps CMakeToolchain From a841a1b74a74a023c43643ddefa3039b29dc12e6 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sat, 16 Mar 2024 19:23:03 -0500 Subject: [PATCH 08/18] Fix msvc warning treated as error --- src/snippets/snippet.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/snippets/snippet.cpp b/src/snippets/snippet.cpp index d9ec4dc..660d375 100644 --- a/src/snippets/snippet.cpp +++ b/src/snippets/snippet.cpp @@ -9,6 +9,7 @@ #include #include "../utils/common.hpp" +#include "../utils/utils.hpp" namespace cpptrace { namespace detail { @@ -36,7 +37,7 @@ namespace detail { } // else load file file.seekg(0, std::ios::beg); - contents.resize(size); + contents.resize(to(size)); if(!file.read(&contents[0], size)) { // error ... } From c5138e2f40a4d8fc74f5d69ed36de0ff5f4b2978 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sat, 16 Mar 2024 19:34:16 -0500 Subject: [PATCH 09/18] Add nominmax for windows.h --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 851c1e9..23bd55f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -288,6 +288,8 @@ target_compile_features( PRIVATE cxx_std_11 ) +target_compile_definitions(${target_name} PRIVATE NOMINMAX) + if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") SET(CMAKE_C_ARCHIVE_FINISH " -no_warning_for_no_symbols -c ") SET(CMAKE_CXX_ARCHIVE_FINISH " -no_warning_for_no_symbols -c ") From fdf44992598ffb0e889a2aba2197d59c8f95a905 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sat, 16 Mar 2024 23:31:07 -0500 Subject: [PATCH 10/18] Update docs to showcase source code snippets --- README.md | 3 +++ res/snippets.png | Bin 0 -> 23753 bytes 2 files changed, 3 insertions(+) create mode 100644 res/snippets.png diff --git a/README.md b/README.md index 390512e..83473c6 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,9 @@ Additional notable features: - Utilities for demangling - Utilities for catching `std::exception`s and wrapping them in traced exceptions - Signal-safe stack tracing +- Source code snippets in traces + +![Snippets](res/snippets.png) ## CMake FetchContent Usage diff --git a/res/snippets.png b/res/snippets.png new file mode 100644 index 0000000000000000000000000000000000000000..04f6b00b3939a6e220df87507e17b82d11839fc9 GIT binary patch literal 23753 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 cuKbkwsDb=U{fxX4h#$YArlh4 Date: Mon, 18 Mar 2024 21:03:26 -0700 Subject: [PATCH 11/18] Defend against min and max macros from windows.h (#105) Alternative to https://github.com/microsoft/vcpkg/pull/37512/files#diff-9f533b43a5faabaa6b5a0e046f0ae425cd85736808604dd61dc9a955db3d060aR9 I left the examples in mach-o.hpp as they are guarded by #if IS_APPLE --- include/cpptrace/cpptrace.hpp | 6 +++--- src/utils/dwarf.hpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/cpptrace/cpptrace.hpp b/include/cpptrace/cpptrace.hpp index 83cf3e4..e47e785 100644 --- a/include/cpptrace/cpptrace.hpp +++ b/include/cpptrace/cpptrace.hpp @@ -118,7 +118,7 @@ namespace cpptrace { return *this; } bool has_value() const noexcept { - return raw_value != std::numeric_limits::max(); + return raw_value != (std::numeric_limits::max)(); } T& value() noexcept { return raw_value; @@ -133,7 +133,7 @@ namespace cpptrace { std::swap(raw_value, other.raw_value); } void reset() noexcept { - raw_value = std::numeric_limits::max(); + raw_value = (std::numeric_limits::max)(); } bool operator==(const nullable& other) const noexcept { return raw_value == other.raw_value; @@ -142,7 +142,7 @@ namespace cpptrace { return raw_value != other.raw_value; } constexpr static nullable null() noexcept { - return { std::numeric_limits::max() }; + return { (std::numeric_limits::max)() }; } }; diff --git a/src/utils/dwarf.hpp b/src/utils/dwarf.hpp index 39feb34..468e318 100644 --- a/src/utils/dwarf.hpp +++ b/src/utils/dwarf.hpp @@ -343,7 +343,7 @@ namespace libdwarf { return; } Dwarf_Addr baseaddr = 0; - if(lowpc != std::numeric_limits::max()) { + if(lowpc != (std::numeric_limits::max)()) { baseaddr = lowpc; } Dwarf_Ranges* ranges = nullptr; @@ -381,7 +381,7 @@ namespace libdwarf { template // callback should return true to keep going void dwarf_ranges(int version, optional pc, F callback) const { - Dwarf_Addr lowpc = std::numeric_limits::max(); + Dwarf_Addr lowpc = (std::numeric_limits::max)(); if(wrap(dwarf_lowpc, die, &lowpc) == DW_DLV_OK) { if(pc.has_value() && pc.unwrap() == lowpc) { callback(lowpc, lowpc + 1); From 8f8e1e34e2d25f5f3bf95040b743385160fe7071 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Tue, 19 Mar 2024 22:59:35 -0500 Subject: [PATCH 12/18] Re-add CE button on readme --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index e195e99..355cad2 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,8 @@ [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=jeremy-rifkin_cpptrace&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=jeremy-rifkin_cpptrace)
[![Community Discord Link](https://img.shields.io/badge/Chat%20on%20the%20(very%20small)-Community%20Discord-blue?labelColor=2C3239&color=7289DA&style=flat&logo=discord&logoColor=959DA5)](https://discord.gg/frjaAZvqUZ) - +[![Try on Compiler Explorer](https://img.shields.io/badge/-Compiler%20Explorer-brightgreen?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAYAAAAmlE46AAAACXBIWXMAAACwAAAAsAEUaqtpAAABSElEQVQokYVTsU7DMBB9QMTCEJbOMLB5oF0tRfUPIPIJZctYJkZYu3WMxNL+ARUfQKpImcPgDYnsXWBgYQl61TkYyxI3Wef37j3fnQ/6vkcsikY9AbiWq0mpbevDBmLRqDEAA4CEHMADgFRwrwDmch6X2i73RCFVHvC/WCeCMAFpC2AFoPPu5x4md4rnAN4luS61nYWSgauNU8ydkr0bLTMYAoIYtWqxM4LtEumeERDtfUjlMDrp7L67iddyyJtOvUIu2rquVn4iiVSOKXYhiMSJWLwUJZLuQ2CWmVldV4MT11UmXgB8fr0dX3WP6VHMiVrscim6Da2mJxffzwSU2v6xWzSKmzQ4cUTOaCBTvWgU14xkzjhckKm/q3wnrRAcAhksxMZNAdxEf0fRKI6E8zqT1C0X28ccRpqAUltW5pu4sxv5Mb8B4AciE3bHMxz/+gAAAABJRU5ErkJggg==&labelColor=2C3239&style=flat&label=Try+it+on&color=30C452)](https://godbolt.org/z/c6TqTzqcf) Cpptrace is a simple, portable, and self-contained C++ stacktrace library supporting C++11 and greater on Linux, macOS, and Windows including MinGW and Cygwin environments. The goal: Make stack traces simple for once. From d1199dc325b6c23536811f0e8d81672f1d5b5093 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Wed, 20 Mar 2024 22:08:21 -0500 Subject: [PATCH 13/18] Fix potential null dereference issue, thanks @eyalgolan1337. Resolves #106. --- src/utils/utils.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils/utils.hpp b/src/utils/utils.hpp index c61db30..fdef76c 100644 --- a/src/utils/utils.hpp +++ b/src/utils/utils.hpp @@ -455,7 +455,9 @@ namespace detail { } inline void file_deleter(std::FILE* ptr) { - fclose(ptr); + if(ptr) { + fclose(ptr); + } } } } From b72164b39b484a2c6f9dc16e8743de67446ebf7e Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Wed, 20 Mar 2024 22:12:26 -0500 Subject: [PATCH 14/18] Bump to v0.5.1 --- CHANGELOG.md | 7 +++++++ CMakeLists.txt | 2 +- README.md | 14 +++++++------- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b48df93..79fcfde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,13 @@ - [v0.1.1](#v011) - [v0.1](#v01) +# v0.5.1 + +Fixes: +- Fix MSVC warning treated as error for 32-bit windows +- Fix MSVC issue with min/max macros +- Fix potential null dereference issue identified by eyalgolan1337 + # v0.5.0 New: diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f42a4d..e251dc9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,7 @@ set(package_name "cpptrace") project( cpptrace - VERSION 0.5.0 + VERSION 0.5.1 DESCRIPTION "Simple, portable, and self-contained stacktrace library for C++11 and newer " HOMEPAGE_URL "https://github.com/jeremy-rifkin/cpptrace" LANGUAGES C CXX diff --git a/README.md b/README.md index 355cad2..123168c 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ include(FetchContent) FetchContent_Declare( cpptrace GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git - GIT_TAG v0.5.0 # + GIT_TAG v0.5.1 # ) FetchContent_MakeAvailable(cpptrace) target_link_libraries(your_target cpptrace::cpptrace) @@ -567,7 +567,7 @@ include(FetchContent) FetchContent_Declare( cpptrace GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git - GIT_TAG v0.5.0 # + GIT_TAG v0.5.1 # ) FetchContent_MakeAvailable(cpptrace) target_link_libraries(your_target cpptrace::cpptrace) @@ -583,7 +583,7 @@ information. ```sh git clone https://github.com/jeremy-rifkin/cpptrace.git -git checkout v0.5.0 +git checkout v0.5.1 mkdir cpptrace/build cd cpptrace/build cmake .. -DCMAKE_BUILD_TYPE=Release @@ -619,7 +619,7 @@ you when installing new libraries. ```ps1 git clone https://github.com/jeremy-rifkin/cpptrace.git -git checkout v0.5.0 +git checkout v0.5.1 mkdir cpptrace/build cd cpptrace/build cmake .. -DCMAKE_BUILD_TYPE=Release @@ -637,7 +637,7 @@ To install just for the local user (or any custom prefix): ```sh git clone https://github.com/jeremy-rifkin/cpptrace.git -git checkout v0.5.0 +git checkout v0.5.1 mkdir cpptrace/build cd cpptrace/build cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$HOME/wherever @@ -716,7 +716,7 @@ make install cd ~/scratch/cpptrace-test git clone https://github.com/jeremy-rifkin/cpptrace.git cd cpptrace -git checkout v0.5.0 +git checkout v0.5.1 mkdir build cd build cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=On -DCPPTRACE_USE_EXTERNAL_LIBDWARF=On -DCMAKE_PREFIX_PATH=~/scratch/cpptrace-test/resources -DCMAKE_INSTALL_PREFIX=~/scratch/cpptrace-test/resources @@ -736,7 +736,7 @@ cpptrace and its dependencies. Cpptrace is available through conan at https://conan.io/center/recipes/cpptrace. ``` [requires] -cpptrace/0.5.0 +cpptrace/0.5.1 [generators] CMakeDeps CMakeToolchain From e65edcf91adb4a88b9fb62cb7e8fa549dbd84e8e Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Thu, 28 Mar 2024 19:51:53 -0500 Subject: [PATCH 15/18] Make note about -DCPPTRACE_STATIC_DEFINE more prominent --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 123168c..2efa46b 100644 --- a/README.md +++ b/README.md @@ -674,7 +674,8 @@ The typical dependencies for cpptrace are: Note: Newer libdwarf requires `-lzstd`, older libdwarf does not. -If you are linking statically, you will additionally need to specify `-DCPPTRACE_STATIC_DEFINE`. +> [!IMPORTANT] +> If you are linking statically, you will additionally need to specify `-DCPPTRACE_STATIC_DEFINE`. Dependencies may differ if different back-ends are manually selected. From a3899781dc268877107caae61d1b16ce1ded1e9b Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Fri, 29 Mar 2024 22:50:30 -0500 Subject: [PATCH 16/18] Try to fix ci --- .github/workflows/build.yml | 10 ++++++---- .github/workflows/test.yml | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 45e9356..49811f9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,14 +34,15 @@ jobs: - uses: actions/checkout@v4 - name: dependencies run: | - pip3 install colorama + python3 -m venv env + env/bin/pip install colorama - name: libdwarf run: | cd .. cpptrace/ci/setup-prerequisites.sh - name: build run: | - python3 ci/build-in-all-configs.py --${{matrix.compiler}} --default-config + env/bin/python ci/build-in-all-configs.py --${{matrix.compiler}} --default-config build-windows: runs-on: windows-2022 strategy: @@ -95,14 +96,15 @@ jobs: - uses: actions/checkout@v4 - name: dependencies run: | - pip3 install colorama + python3 -m venv env + env/bin/pip install colorama - name: libdwarf run: | cd .. cpptrace/ci/setup-prerequisites.sh - name: build run: | - python3 ci/build-in-all-configs.py --${{matrix.compiler}} + env/bin/python ci/build-in-all-configs.py --${{matrix.compiler}} build-windows-all-configurations: runs-on: windows-2022 needs: build-windows diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0f81803..cc8da81 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,10 +42,11 @@ jobs: cpptrace/ci/setup-prerequisites.sh - name: dependencies run: | - pip3 install colorama + python3 -m venv env + env/bin/pip install colorama - name: build and test run: | - python3 ci/test-all-configs.py --${{matrix.compiler}} --default-config + env/bin/python ci/test-all-configs.py --${{matrix.compiler}} --default-config test-windows: runs-on: windows-2022 strategy: @@ -106,10 +107,11 @@ jobs: cpptrace/ci/setup-prerequisites.sh - name: dependencies run: | - pip3 install colorama + python3 -m venv env + env/bin/pip install colorama - name: build and test run: | - python3 ci/test-all-configs.py --${{matrix.compiler}} + env/bin/python ci/test-all-configs.py --${{matrix.compiler}} test-windows-all-configurations: runs-on: windows-2022 strategy: From fa6155ff470ccc5e2631ebbc18b38a2dbb82d805 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Fri, 29 Mar 2024 23:08:18 -0500 Subject: [PATCH 17/18] Remove awful stringf system --- src/symbols/symbols_with_libbacktrace.cpp | 2 +- src/utils/common.hpp | 12 - src/utils/dbghelp_syminit_manager.hpp | 4 +- src/utils/dwarf.hpp | 4 +- src/utils/error.hpp | 17 +- src/utils/microfmt.hpp | 316 ++++++++++++++++++++++ src/utils/utils.hpp | 1 + 7 files changed, 331 insertions(+), 25 deletions(-) create mode 100644 src/utils/microfmt.hpp diff --git a/src/symbols/symbols_with_libbacktrace.cpp b/src/symbols/symbols_with_libbacktrace.cpp index 2d7937b..c719bba 100644 --- a/src/symbols/symbols_with_libbacktrace.cpp +++ b/src/symbols/symbols_with_libbacktrace.cpp @@ -38,7 +38,7 @@ namespace libbacktrace { } void error_callback(void*, const char* msg, int errnum) { - throw std::runtime_error(stringf("Libbacktrace error: %s, code %d\n", msg, errnum)); + throw std::runtime_error(microfmt::format("Libbacktrace error: {}, code {}\n", msg, errnum)); } backtrace_state* get_backtrace_state() { diff --git a/src/utils/common.hpp b/src/utils/common.hpp index d091afa..b6426fe 100644 --- a/src/utils/common.hpp +++ b/src/utils/common.hpp @@ -52,18 +52,6 @@ namespace cpptrace { namespace detail { - // Placed here instead of utils because it's used by error.hpp and utils.hpp - template std::string stringf(T... args) { - int length = std::snprintf(nullptr, 0, args...); - if(length < 0) { - throw std::logic_error("invalid arguments to stringf"); - } - std::string str(length, 0); - // .data is const char* in c++11, but &str[0] should be legal - std::snprintf(&str[0], length + 1, args...); - return str; - } - static const stacktrace_frame null_frame { 0, 0, diff --git a/src/utils/dbghelp_syminit_manager.hpp b/src/utils/dbghelp_syminit_manager.hpp index 6f7ffd7..688b349 100644 --- a/src/utils/dbghelp_syminit_manager.hpp +++ b/src/utils/dbghelp_syminit_manager.hpp @@ -17,7 +17,7 @@ namespace detail { ~dbghelp_syminit_manager() { for(auto handle : set) { if(!SymCleanup(handle)) { - ASSERT(false, stringf("Cpptrace SymCleanup failed with code %llu\n", to_ull(GetLastError()))); + ASSERT(false, microfmt::format("Cpptrace SymCleanup failed with code {}\n", GetLastError())); } } } @@ -25,7 +25,7 @@ namespace detail { void init(HANDLE proc) { if(set.count(proc) == 0) { if(!SymInitialize(proc, NULL, TRUE)) { - throw std::logic_error(stringf("SymInitialize failed %llu", to_ull(GetLastError()))); + throw std::logic_error(microfmt::format("SymInitialize failed {}", GetLastError())); } set.insert(proc); } diff --git a/src/utils/dwarf.hpp b/src/utils/dwarf.hpp index 468e318..3a282de 100644 --- a/src/utils/dwarf.hpp +++ b/src/utils/dwarf.hpp @@ -28,7 +28,7 @@ namespace libdwarf { char* msg = dwarf_errmsg(error); (void)dbg; // dwarf_dealloc_error(dbg, error); - throw std::runtime_error(stringf("Cpptrace dwarf error %u %s\n", ev, msg)); + throw std::runtime_error(microfmt::format("Cpptrace dwarf error {} {}\n", ev, msg)); } struct die_object { @@ -235,7 +235,7 @@ namespace libdwarf { return die_object(dbg, target); } default: - PANIC(stringf("unknown form for attribute %d %d\n", attr_num, form)); + PANIC(microfmt::format("unknown form for attribute {} {}\n", attr_num, form)); } } diff --git a/src/utils/error.hpp b/src/utils/error.hpp index d0533ab..962844f 100644 --- a/src/utils/error.hpp +++ b/src/utils/error.hpp @@ -7,6 +7,7 @@ #include #include "common.hpp" +#include "microfmt.hpp" #if IS_MSVC #define CPPTRACE_PFUNC __FUNCSIG__ @@ -57,8 +58,8 @@ namespace detail { const char* name = assert_names[static_cast::type>(type)]; if(message == "") { throw std::logic_error( - stringf( - "Cpptrace %s failed at %s:%d: %s\n" + microfmt::format( + "Cpptrace {} failed at {}:{}: {}\n" " %s(%s);\n", action, location.file, location.line, signature, name, expression @@ -66,8 +67,8 @@ namespace detail { ); } else { throw std::logic_error( - stringf( - "Cpptrace %s failed at %s:%d: %s: %s\n" + microfmt::format( + "Cpptrace {} failed at {}:{}: {}: {}\n" " %s(%s);\n", action, location.file, location.line, signature, message.c_str(), name, expression @@ -83,15 +84,15 @@ namespace detail { ) { if(message == "") { throw std::logic_error( - stringf( - "Cpptrace panic %s:%d: %s\n", + microfmt::format( + "Cpptrace panic {}:{}: {}\n", location.file, location.line, signature ) ); } else { throw std::logic_error( - stringf( - "Cpptrace panic %s:%d: %s: %s\n", + microfmt::format( + "Cpptrace panic {}:{}: {}: {}\n", location.file, location.line, signature, message.c_str() ) ); diff --git a/src/utils/microfmt.hpp b/src/utils/microfmt.hpp new file mode 100644 index 0000000..4386b03 --- /dev/null +++ b/src/utils/microfmt.hpp @@ -0,0 +1,316 @@ +#ifndef MICROFMT_HPP +#define MICROFMT_HPP + +// Copyright (c) 2024 Jeremy Rifkin; MIT License + +#include +#include +#include +#include +#include +#include +#if defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L +#include +#endif +#ifdef _MSC_VER +#include +#endif + +// {[[align][width]:[fill][base]]} +// width: number or {} + +namespace microfmt { + namespace detail { + #define STR2(x) #x + #define STR(x) STR2(x) + #define MICROFMT_ASSERT(expr) if(!(expr)) { \ + throw std::runtime_error("Microfmt check failed" __FILE__ ":" STR(__LINE__) ": " #expr); \ + } + + #ifdef _MSC_VER + inline std::uint64_t clz(std::uint64_t value) { + unsigned long out = 0; + #ifdef _WIN64 + _BitScanForward64(&out, value); + #else + if(_BitScanForward(&out, std::uint32_t(value >> 32))) { + return 63 ^ int(out + 32); + } + _BitScanForward(&out, std::uint32_t(value)); + #endif + return out; + } + #else + inline std::uint64_t clz(std::uint64_t value) { + return __builtin_clzll(value); + } + #endif + + enum class alignment { left, right }; + + struct format_options { + alignment align = alignment::left; + char fill = ' '; + size_t width = 0; + char base = 'd'; + }; + + template void do_write(std::string& out, It begin, It end, const format_options& options) { + auto size = end - begin; + MICROFMT_ASSERT(size >= 0); + if(static_cast(size) >= options.width) { + out.append(begin, end); + } else { + auto out_size = out.size(); + out.resize(out_size + options.width); + if(options.align == alignment::left) { + std::copy(begin, end, out.begin() + out_size); + std::fill(out.begin() + out_size + size, out.end(), options.fill); + } else { + std::fill(out.begin() + out_size, out.begin() + out_size + (options.width - size), options.fill); + std::copy(begin, end, out.begin() + out_size + (options.width - size)); + } + } + } + + template + std::string to_string(std::uint64_t value, const char* digits = "0123456789abcdef") { + if(value == 0) { + return "0"; + } else { + // digits = floor(1 + log_base(x)) + // log_base(x) = log_2(x) / log_2(base) + // log_2(x) == 63 - clz(x) + // 1 + (63 - clz(value)) / (63 - clz(1 << shift)) + // 63 - clz(1 << shift) is the same as shift + auto n_digits = 1 + (63 - clz(value)) / shift; + std::string number; + number.resize(n_digits); + std::size_t i = n_digits - 1; + while(value > 0) { + number[i--] = digits[value & mask]; + value >>= shift; + } + return number; + } + } + + inline std::string to_string(std::uint64_t value, const format_options& options) { + switch(options.base) { + case 'd': return std::to_string(value); + case 'H': return to_string<4, 0xf>(value, "0123456789ABCDEF"); + case 'h': return to_string<4, 0xf>(value); + case 'o': return to_string<3, 0x7>(value); + case 'b': return to_string<1, 0x1>(value); + default: + MICROFMT_ASSERT(false); + } + } + + class format_value { + enum class value_type { + char_value, + int64_value, + uint64_value, + string_value, + #if defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L + string_view_value, + #endif + c_string_value, + }; + union { + char char_value; + std::int64_t int64_value; + std::uint64_t uint64_value; + const std::string* string_value; + #if defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L + std::string_view string_view_value; + #endif + const char* c_string_value; + }; + value_type value; + + public: + format_value(char c) : char_value(c), value(value_type::char_value) {} + format_value(short int_val) : int64_value(int_val), value(value_type::int64_value) {} + format_value(int int_val) : int64_value(int_val), value(value_type::int64_value) {} + format_value(long int_val) : int64_value(int_val), value(value_type::int64_value) {} + format_value(long long int_val) : int64_value(int_val), value(value_type::int64_value) {} + format_value(unsigned char int_val) : uint64_value(int_val), value(value_type::uint64_value) {} + format_value(unsigned short int_val) : uint64_value(int_val), value(value_type::uint64_value) {} + format_value(unsigned int int_val) : uint64_value(int_val), value(value_type::uint64_value) {} + format_value(unsigned long int_val) : uint64_value(int_val), value(value_type::uint64_value) {} + format_value(unsigned long long int_val) : uint64_value(int_val), value(value_type::uint64_value) {} + format_value(const std::string& string) : string_value(&string), value(value_type::string_value) {} + #if defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L + format_value(std::string_view sv) : string_view_value(sv), value(value_type::string_view_value) {} + #endif + format_value(const char* c_string) : c_string_value(c_string), value(value_type::c_string_value) {} + + int unwrap_int() const { + switch(value) { + case value_type::int64_value: return static_cast(int64_value); + case value_type::uint64_value: return static_cast(uint64_value); + default: MICROFMT_ASSERT(false); + } + } + + public: + void write(std::string& out, const format_options& options) const { + switch(value) { + case value_type::char_value: + do_write(out, &char_value, &char_value + 1, options); + break; + case value_type::int64_value: + { + std::string str; + std::int64_t val = int64_value; + if(val < 0) { + str += '-'; + val *= -1; + } + str += to_string(static_cast(val), options); + do_write(out, str.begin(), str.end(), options); + } + break; + case value_type::uint64_value: + { + std::string str = to_string(uint64_value, options); + do_write(out, str.begin(), str.end(), options); + } + break; + case value_type::string_value: + do_write(out, string_value->begin(), string_value->end(), options); + break; + #if defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L + case value_type::string_view_value: + do_write(out, string_view_value.begin(), string_view_value.end(), options); + break; + #endif + case value_type::c_string_value: + do_write(out, c_string_value, c_string_value + std::strlen(c_string_value), options); + break; + default: + MICROFMT_ASSERT(false); + } + } + }; + + inline int parse_int(std::string::const_iterator begin, std::string::const_iterator end) { + int x = 0; + for(auto it = begin; it != end; it++) { + MICROFMT_ASSERT(isdigit(*it)); + x *= 10; + x += *it - '0'; + } + return x; + } + + template + std::string format(const std::string& format_string, std::array args) { + std::string str; + std::size_t arg_i = 0; + auto it = format_string.begin(); + auto peek = [&] (std::size_t dist = 1) -> char { // 0 on failure + if(it != format_string.end()) { + return *(it + dist); + } else { + return 0; + } + }; + auto read_number = [&] () -> int { // -1 on failure + auto scan = it; + while(scan != format_string.end() && isdigit(*scan)) { + scan++; + } + if(scan != it) { + int val = parse_int(it, scan); + it = scan; + return val; + } else { + return -1; + } + }; + while(it != format_string.end()) { + if(*it == '{') { + if(peek() == '{') { + // try to handle escape + str += '{'; + it++; + } else { + // parse format string + it++; + MICROFMT_ASSERT(it != format_string.end()); + format_options options; + // try to parse alignment + if(*it == '<' || *it == '>') { + options.align = *it == '<' ? alignment::left : alignment::right; + it++; + } + // try to parse width + auto width = read_number(); + if(width != -1) { + options.width = width; + } else if(*it == '{') { // try to parse variable width + MICROFMT_ASSERT(peek() == '}'); + it += 2; + MICROFMT_ASSERT(arg_i < N); + options.width = args[arg_i++].unwrap_int(); + } + // try to parse fill/base + if(*it == ':') { + it++; + // try to parse fill + if(*it != '}' && peek(1) != '}') { + // two chars before the }, treat as fill+base + options.fill = *it++; + options.base = *it++; + } else if(*it != '}') { + // one char before the }, treat as base if possible + if(*it == 'd' || *it == 'h' || *it == 'H' || *it == 'o' || *it == 'b') { + options.base = *it++; + } else { + options.fill = *it++; + } + } else { + MICROFMT_ASSERT(false); + } + } + MICROFMT_ASSERT(*it == '}'); + MICROFMT_ASSERT(arg_i < N); + args[arg_i++].write(str, options); + } + } else if(*it == '}') { + // parse }} escape + if(peek() == '}') { + str += '}'; + it++; + } else { + MICROFMT_ASSERT(false); + } + } else { + str += *it; + } + it++; + } + return str; + } + } + + template + std::string format(const std::string& format_string, Args... args) { + return detail::format(format_string, {detail::format_value(args)...}); + } + + template + void print(const std::string& format_string, Args... args) { + std::cout< + void print(std::ostream& ostream, const std::string& format_string, Args... args) { + ostream< From 47e7ee79eba25ac2ea06a35629cb2374980e0e9b Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sat, 30 Mar 2024 11:09:28 -0500 Subject: [PATCH 18/18] Formatting update --- src/utils/microfmt.hpp | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/utils/microfmt.hpp b/src/utils/microfmt.hpp index 4386b03..b9d7f4c 100644 --- a/src/utils/microfmt.hpp +++ b/src/utils/microfmt.hpp @@ -196,7 +196,7 @@ namespace microfmt { } }; - inline int parse_int(std::string::const_iterator begin, std::string::const_iterator end) { + inline int parse_int(const char* begin, const char* end) { int x = 0; for(auto it = begin; it != end; it++) { MICROFMT_ASSERT(isdigit(*it)); @@ -207,12 +207,12 @@ namespace microfmt { } template - std::string format(const std::string& format_string, std::array args) { + std::string format(const char* fmt_begin, const char* fmt_end, std::array args) { std::string str; std::size_t arg_i = 0; - auto it = format_string.begin(); + auto it = fmt_begin; auto peek = [&] (std::size_t dist = 1) -> char { // 0 on failure - if(it != format_string.end()) { + if(it != fmt_end) { return *(it + dist); } else { return 0; @@ -220,7 +220,7 @@ namespace microfmt { }; auto read_number = [&] () -> int { // -1 on failure auto scan = it; - while(scan != format_string.end() && isdigit(*scan)) { + while(scan != fmt_end && isdigit(*scan)) { scan++; } if(scan != it) { @@ -231,7 +231,7 @@ namespace microfmt { return -1; } }; - while(it != format_string.end()) { + while(it != fmt_end) { if(*it == '{') { if(peek() == '{') { // try to handle escape @@ -240,7 +240,7 @@ namespace microfmt { } else { // parse format string it++; - MICROFMT_ASSERT(it != format_string.end()); + MICROFMT_ASSERT(it != fmt_end); format_options options; // try to parse alignment if(*it == '<' || *it == '>') { @@ -298,18 +298,27 @@ namespace microfmt { } template - std::string format(const std::string& format_string, Args... args) { - return detail::format(format_string, {detail::format_value(args)...}); + #if defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L + std::string format(std::string_view fmt, Args&&... args) { + #else + std::string format(const std::string& fmt, Args&&... args) { + #endif + return detail::format(fmt.begin(), fmt.end(), {detail::format_value(args)...}); } template - void print(const std::string& format_string, Args... args) { - std::cout<(fmt, fmt + std::strlen(fmt), {detail::format_value(args)...}); } - template - void print(std::ostream& ostream, const std::string& format_string, Args... args) { - ostream< + void print(const S& fmt, Args&&... args) { + std::cout< + void print(std::ostream& ostream, const S& fmt, Args&&... args) { + ostream<