Skip to content

Commit c5cc077

Browse files
committed
Add execinfo backend
1 parent 8eccab0 commit c5cc077

9 files changed

Lines changed: 416 additions & 3 deletions

‎CMakeLists.txt‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ endif()
5050
# -- Exported names for each backend ------------------------------------------
5151

5252
set(EGGS_STACKTRACE_BACKEND_EXPORT_null Null)
53+
set(EGGS_STACKTRACE_BACKEND_EXPORT_execinfo Execinfo)
5354

5455
# -- Common OBJECT library ----
5556

@@ -73,6 +74,7 @@ target_sources(
7374
FILE_SET private_headers
7475
TYPE HEADERS
7576
BASE_DIRS src
77+
FILES src/detail/asan_backtrace.hpp
7678
FILES src/detail/backend.hpp
7779
)
7880
set_target_properties(
@@ -135,10 +137,20 @@ endmacro()
135137

136138
eggs_stacktrace_add_backend(null)
137139

140+
include(CheckExecinfoBackend)
141+
eggs_stacktrace_check_execinfo_backend(EGGS_STACKTRACE_HAVE_EXECINFO)
142+
if(EGGS_STACKTRACE_HAVE_EXECINFO)
143+
eggs_stacktrace_add_backend(execinfo _eggs_stacktrace_execinfo_support)
144+
endif()
145+
138146
# -- Main alias ----------------------------------------------------------------
139147
# Note: ALIAS cannot alias another ALIAS, so we alias the internal target name.
140148

141-
set(EGGS_STACKTRACE_DEFAULT_BACKEND null)
149+
if(TARGET _eggs_stacktrace_execinfo)
150+
set(EGGS_STACKTRACE_DEFAULT_BACKEND execinfo)
151+
else()
152+
set(EGGS_STACKTRACE_DEFAULT_BACKEND null)
153+
endif()
142154

143155
add_library(
144156
Eggs::Stacktrace
@@ -180,6 +192,12 @@ if(ENABLE_INSTALL)
180192
EXPORT EggsStacktraceTargets
181193
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
182194
)
195+
if(TARGET _eggs_stacktrace_${name}_support)
196+
install(
197+
TARGETS _eggs_stacktrace_${name}_support
198+
EXPORT EggsStacktraceTargets
199+
)
200+
endif()
183201
if(MSVC)
184202
install(
185203
FILES
@@ -193,6 +211,7 @@ if(ENABLE_INSTALL)
193211
endmacro()
194212

195213
eggs_stacktrace_install_backend(null)
214+
eggs_stacktrace_install_backend(execinfo)
196215

197216
install(
198217
EXPORT EggsStacktraceTargets
@@ -201,6 +220,11 @@ if(ENABLE_INSTALL)
201220
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/eggs.stacktrace
202221
)
203222

223+
install(
224+
FILES cmake/CheckExecinfoBackend.cmake
225+
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/eggs.stacktrace
226+
)
227+
204228
set(EGGS_STACKTRACE_DEFAULT_BACKEND_EXPORT
205229
"${EGGS_STACKTRACE_BACKEND_EXPORT_${EGGS_STACKTRACE_DEFAULT_BACKEND}}"
206230
)

‎cmake/CheckExecinfoBackend.cmake‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Eggs.Stacktrace
2+
#
3+
# Copyright (c) 2026 Agustin Berge
4+
#
5+
# Distributed under the Boost Software License, Version 1.0. (See accompanying
6+
# file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7+
8+
include_guard()
9+
10+
include(CheckCXXSourceCompiles)
11+
12+
# eggs_stacktrace_check_execinfo_backend(<out-var>)
13+
#
14+
# Checks whether the execinfo backend's dependencies (backtrace(),
15+
# dladdr(), and __cxa_demangle()) are all present and link successfully.
16+
# Sets <out-var> as an internal cache boolean. On success, also defines the
17+
# INTERFACE library _eggs_stacktrace_execinfo_support.
18+
function(eggs_stacktrace_check_execinfo_backend out_var)
19+
set(CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE)
20+
set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_DL_LIBS})
21+
check_cxx_source_compiles(
22+
"
23+
#include <cxxabi.h>
24+
#include <dlfcn.h>
25+
#include <execinfo.h>
26+
int main()
27+
{
28+
void* buf[1];
29+
int const n = ::backtrace(buf, 1);
30+
::Dl_info info{};
31+
::dladdr(buf[0], &info);
32+
int status = -1;
33+
::abi::__cxa_demangle(\"x\", nullptr, nullptr, &status);
34+
return n;
35+
}
36+
"
37+
${out_var}
38+
)
39+
if(NOT ${out_var})
40+
return()
41+
endif()
42+
43+
if(NOT TARGET _eggs_stacktrace_execinfo_support)
44+
add_library(_eggs_stacktrace_execinfo_support INTERFACE)
45+
target_link_libraries(
46+
_eggs_stacktrace_execinfo_support
47+
INTERFACE ${CMAKE_DL_LIBS}
48+
)
49+
endif()
50+
endfunction()

‎cmake/eggs.stacktrace-config.cmake.in‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,20 @@ include("${CMAKE_CURRENT_LIST_DIR}/eggs.stacktrace-targets.cmake")
44

55
# Create the canonical Eggs::Stacktrace alias for the configured default backend.
66
if(NOT TARGET Eggs::Stacktrace)
7+
set(_eggs_stacktrace_default @EGGS_STACKTRACE_DEFAULT_BACKEND_EXPORT@)
8+
if(_eggs_stacktrace_default STREQUAL "Execinfo")
9+
include("${CMAKE_CURRENT_LIST_DIR}/CheckExecinfoBackend.cmake")
10+
eggs_stacktrace_check_execinfo_backend(EGGS_STACKTRACE_HAVE_EXECINFO)
11+
if(NOT EGGS_STACKTRACE_HAVE_EXECINFO)
12+
message(WARNING "eggs.stacktrace: falling back to the Null backend")
13+
set(_eggs_stacktrace_default Null)
14+
endif()
15+
endif()
716
add_library(
817
Eggs::Stacktrace
9-
ALIAS Eggs::Stacktrace::@EGGS_STACKTRACE_DEFAULT_BACKEND_EXPORT@
18+
ALIAS Eggs::Stacktrace::${_eggs_stacktrace_default}
1019
)
20+
unset(_eggs_stacktrace_default)
1121
endif()
1222

1323
check_required_components(Eggs.Stacktrace)

‎src/detail/asan_backtrace.hpp‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Eggs.Stacktrace
2+
//
3+
// Copyright (c) 2026 Agustin Berge
4+
//
5+
// Distributed under the Boost Software License, Version 1.0.
6+
// See accompanying file LICENSE.txt or copy at
7+
// http://www.boost.org/LICENSE_1_0.txt
8+
9+
#pragma once
10+
11+
#if !defined(_WIN32)
12+
# include <dlfcn.h>
13+
#endif
14+
15+
namespace eggs {
16+
namespace detail {
17+
18+
// AddressSanitizer intercepts backtrace() with a wrapper, adding one extra
19+
// frame at the top of the captured backtrace.
20+
inline bool asan_backtrace_adds_extra_frame() noexcept
21+
{
22+
#if defined(__GLIBC__)
23+
static bool const has_asan =
24+
(::dlsym(RTLD_DEFAULT, "__asan_get_current_fake_stack") != nullptr);
25+
return has_asan;
26+
#else
27+
return false;
28+
#endif
29+
}
30+
31+
} // namespace detail
32+
} // namespace eggs

‎src/stacktrace.cpp‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,44 @@
1515

1616
#include "detail/backend.hpp"
1717

18+
#if defined(__clang__)
19+
# define EGGS_STACKTRACE_NO_TAIL_CALLS __attribute__((disable_tail_calls))
20+
#elif defined(__GNUC__)
21+
# define EGGS_STACKTRACE_NO_TAIL_CALLS \
22+
__attribute__((optimize("no-optimize-sibling-calls")))
23+
#else
24+
# define EGGS_STACKTRACE_NO_TAIL_CALLS
25+
#endif
26+
1827
namespace eggs {
1928

2029
// -- stacktrace::current --------------------------------------------------------
2130

31+
EGGS_STACKTRACE_NO_TAIL_CALLS
2232
stacktrace stacktrace::current() noexcept
2333
{
2434
stacktrace st;
2535
detail::capture(st.frames_, 1, std::numeric_limits<size_type>::max());
2636
return st;
2737
}
2838

39+
EGGS_STACKTRACE_NO_TAIL_CALLS
2940
stacktrace stacktrace::current(size_type skip) noexcept
3041
{
42+
if (skip == std::numeric_limits<size_type>::max()) return {};
43+
3144
stacktrace st;
3245
detail::capture(
3346
st.frames_, skip + 1, std::numeric_limits<size_type>::max()
3447
);
3548
return st;
3649
}
3750

51+
EGGS_STACKTRACE_NO_TAIL_CALLS
3852
stacktrace stacktrace::current(size_type skip, size_type max_depth) noexcept
3953
{
54+
if (skip == std::numeric_limits<size_type>::max()) return {};
55+
4056
stacktrace st;
4157
detail::capture(st.frames_, skip + 1, max_depth);
4258
return st;

‎src/stacktrace_execinfo.cpp‎

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Eggs.Stacktrace
2+
//
3+
// Copyright (c) 2026 Agustin Berge
4+
//
5+
// Distributed under the Boost Software License, Version 1.0.
6+
// See accompanying file LICENSE.txt or copy at
7+
// http://www.boost.org/LICENSE_1_0.txt
8+
9+
// dladdr() is a GNU extension; expose it before any system headers.
10+
#ifndef _GNU_SOURCE
11+
# define _GNU_SOURCE
12+
#endif
13+
14+
#include <eggs/stacktrace.hpp>
15+
16+
#include <algorithm>
17+
#include <cassert>
18+
#include <cstddef>
19+
#include <cstdint>
20+
#include <cstdlib>
21+
#include <limits>
22+
#include <string>
23+
#include <vector>
24+
25+
#include "detail/asan_backtrace.hpp"
26+
#include "detail/backend.hpp"
27+
28+
#include <cxxabi.h>
29+
#include <dlfcn.h>
30+
#include <execinfo.h>
31+
32+
namespace eggs {
33+
namespace detail {
34+
35+
void capture(
36+
std::vector<stacktrace_entry>& frames, std::size_t skip,
37+
std::size_t max_depth
38+
) noexcept
39+
{
40+
if (max_depth == 0) return;
41+
42+
// +1 to skip this capture() frame itself in the backtrace.
43+
std::size_t const overhead =
44+
1 + (asan_backtrace_adds_extra_frame() ? 1 : 0);
45+
if (skip > std::numeric_limits<std::size_t>::max() - overhead) return;
46+
std::size_t const first = skip + overhead;
47+
48+
constexpr int kInitial = 128;
49+
constexpr int kHardLimit = 1 << 16;
50+
51+
void* stack_buf[kInitial];
52+
void** buf = stack_buf;
53+
int cap = kInitial;
54+
int got = ::backtrace(buf, cap);
55+
assert(got >= 0 && "backtrace() returned a negative count");
56+
57+
std::vector<void*> heap_buf;
58+
try {
59+
while (got == cap && cap < kHardLimit) {
60+
std::size_t const available =
61+
static_cast<std::size_t>(cap) > first
62+
? static_cast<std::size_t>(cap) - first
63+
: 0;
64+
if (available >= max_depth) break;
65+
66+
cap *= 2;
67+
heap_buf.resize(static_cast<std::size_t>(cap));
68+
buf = heap_buf.data();
69+
got = ::backtrace(buf, cap);
70+
assert(got >= 0 && "backtrace() returned a negative count");
71+
}
72+
} catch (...) {
73+
return;
74+
}
75+
76+
std::size_t const got_sz = static_cast<std::size_t>(got);
77+
if (got_sz <= first) return;
78+
79+
std::size_t const take = std::min(got_sz - first, max_depth);
80+
try {
81+
for (std::size_t i = first; i < first + take; ++i)
82+
frames.push_back(capture_helper::make(buf[i]));
83+
} catch (...) {
84+
}
85+
}
86+
87+
std::string symbolize_description(void* address)
88+
{
89+
if (!address) return {};
90+
91+
::Dl_info info{};
92+
if (::dladdr(address, &info) == 0 || !info.dli_sname) return {};
93+
94+
int status = -1;
95+
char* demangled =
96+
abi::__cxa_demangle(info.dli_sname, nullptr, nullptr, &status);
97+
std::string result;
98+
try {
99+
result = (status == 0 && demangled) ? demangled : info.dli_sname;
100+
} catch (...) {
101+
std::free(demangled);
102+
throw;
103+
}
104+
std::free(demangled);
105+
return result;
106+
}
107+
108+
std::string symbolize_source_file(void*)
109+
{
110+
return {};
111+
}
112+
113+
std::uint_least32_t symbolize_source_line(void*)
114+
{
115+
return 0;
116+
}
117+
118+
} // namespace detail
119+
} // namespace eggs

‎test/CMakeLists.txt‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,36 @@ endfunction()
6767
add_lib_test(stacktrace_entry)
6868
add_lib_test(stacktrace)
6969
add_lib_test(stacktrace_formatter)
70+
71+
# -- execinfo backend test ------------------------------------------------------
72+
73+
if(TARGET Eggs::Stacktrace::Execinfo)
74+
add_executable(
75+
eggs.stacktrace.test.stacktrace_execinfo
76+
stacktrace_execinfo.cpp
77+
)
78+
target_sources(
79+
eggs.stacktrace.test.stacktrace_execinfo
80+
PRIVATE
81+
FILE_SET private_headers
82+
TYPE HEADERS
83+
BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}
84+
FILES support.hpp
85+
)
86+
target_link_libraries(
87+
eggs.stacktrace.test.stacktrace_execinfo
88+
PRIVATE Eggs::Stacktrace::Execinfo
89+
)
90+
set_target_properties(
91+
eggs.stacktrace.test.stacktrace_execinfo
92+
PROPERTIES ENABLE_EXPORTS ON
93+
)
94+
add_test(
95+
NAME eggs.stacktrace.stacktrace_execinfo
96+
COMMAND eggs.stacktrace.test.stacktrace_execinfo
97+
)
98+
set_tests_properties(
99+
eggs.stacktrace.stacktrace_execinfo
100+
PROPERTIES LABELS "unit"
101+
)
102+
endif()

0 commit comments

Comments
 (0)