initial implementation of libbacktrace (#19910)
* initial implementation of libbacktrace * in buildinfo show the parameters of libbacktrace * do not disable libbacktrace if threading is not supported * Don’t install libbacktrace, only build it. * Disable libbacktrace for 32-bit ARM builds. * Make libunwind and libbacktrace mutually exclusive at configure time. Instead of relying on it being mutually exclusive at build time. This ensures we don’t waste time on libunwind when using libbacktrace. * Only use libbacktrace on Linux and Windows * Work around broken logic in openSUSE rpmbuild. * Fix handling of libbacktrace for 32-bit ARM static builds. --------- Co-authored-by: Austin S. Hemmelgarn <austin@netdata.cloud>
Costa Tsaousis committed
Mar 21, 2025 at 19:44 UTC
b75958a4ab8c57d6ec4f8f996c578e384fc280d5
9 files changed
+261
-25
CMakeLists.txt
+13
-1
@@ -213,7 +213,10 @@ mark_as_advanced(ENABLE_SENTRY)
213
option(BUILD_FOR_PACKAGING "Include component files for native packages" False)
214
mark_as_advanced(BUILD_FOR_PACKAGING)
215
216
-option(ENABLE_LIBUNWIND "Include stack traces in log output" False)
216
+cmake_dependent_option(ENABLE_LIBBACKTRACE "Use libbacktrace for stack traces in log output" False "OS_LINUX OR OS_WINDOWS" True)
217
+mark_as_advanced(ENABLE_LIBBACKTRACE)
218
+cmake_dependent_option(ENABLE_LIBUNWIND "Use libunwind for stack traces in log output" False "NOT ENABLE_LIBBACKTRACE" False)
219
+mark_as_advanced(ENABLE_LIBUNWIND)
220
221
cmake_dependent_option(FORCE_LEGACY_LIBBPF "Force usage of libbpf 0.0.9 instead of the latest version." False "ENABLE_PLUGIN_EBPF" False)
222
mark_as_advanced(FORCE_LEGACY_LIBBPF)
@@ -389,6 +392,7 @@ endif()
392
393
include(NetdataJSONC)
394
include(NetdataYAML)
395
+include(NetdataBacktrace)
396
397
if(ENABLE_LEGACY_EBPF_PROGRAMS)
398
include(NetdataEBPFLegacy)
@@ -496,6 +500,10 @@ if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
500
message(STATUS "Added compiler and linker flags for better stack trace support")
501
endif()
502
503
+if(ENABLE_LIBBACKTRACE)
504
+ netdata_bundle_libbacktrace()
505
+endif()
506
+
507
#
508
# check source compilation
509
#
@@ -2172,6 +2180,10 @@ target_link_libraries(libnetdata PUBLIC
2180
"$<$<BOOL:${LINK_LIBM}>:m>"
2181
"${SYSTEMD_LDFLAGS}")
2182
2183
+if(HAVE_LIBBACKTRACE)
2184
+ netdata_add_libbacktrace_to_target(libnetdata)
2185
+endif()
2186
+
2187
if(OS_WINDOWS)
2188
set(HAVE_ETW True)
2189
set(HAVE_WEL True)
netdata.spec.in
+9
@@ -565,6 +565,13 @@ rm -rf "${RPM_BUILD_ROOT}"
565
%attr(0770,netdata,netdata) %dir %{_localstatedir}/lib/%{name}/registry
566
%attr(0770,netdata,netdata) %dir %{_localstatedir}/lib/%{name}/cloud.d
567
568
+# For some unknown reason openSUSE decides that some files in the build
569
+# directory were ‘installed’ when they very much were not. Explicitly
570
+# ignore that path to avoid build failures.
571
+%if 0%{?suse_version}
572
+%exclude /usr/src/packages/BUILD/build
573
+%endif
574
+
575
# Dashboard belongs to a different sub-package
576
%exclude %{_datadir}/%{name}/web
577
@@ -1006,6 +1013,8 @@ fi
1013
%{_datadir}/%{name}/web
1014
1015
%changelog
1016
+* Fri Mar 21 2025 Austin Hemmelgarn <austin@netdata.cloud> 0.0.0-31
1017
+- Exclude build directory from install to make openSUSE happy
1018
* Tue Jan 28 2025 Konstantin Shalygin <k0ste@k0ste.ru> 0.0.0-30
1019
- Removed lm_sensors dependency for plugin-go package
1020
* Thu Aug 29 2024 Austin Hemmelgarn <austin@netdata.cloud> 0.0.0-29
packaging/build-package.sh
+1
-1
@@ -51,7 +51,7 @@ add_cmake_option ENABLE_BUNDLED_PROTOBUF Off
51
add_cmake_option ENABLE_BUNDLED_JSONC Off
52
add_cmake_option ENABLE_BUNDLED_YAML Off
53
54
-add_cmake_option ENABLE_LIBUNWIND On
54
+add_cmake_option ENABLE_LIBBACKTRACE On
55
56
add_cmake_option BUILD_FOR_PACKAGING On
57
packaging/cmake/Modules/NetdataBacktrace.cmake
new
+51
@@ -0,0 +1,51 @@
1
+# SPDX-License-Identifier: GPL-3.0-or-later
2
+# Functions and macros for handling of libbacktrace
3
+#
4
+# Handle bundling of libbacktrace.
5
+#
6
+# This clones and builds libbacktrace using ExternalProject functionality.
7
+
8
+include(ExternalProject)
9
+
10
+function(netdata_bundle_libbacktrace)
11
+ message(STATUS "Preparing libbacktrace")
12
+
13
+ set(libbacktrace_SOURCE_DIR "${CMAKE_BINARY_DIR}/libbacktrace-src")
14
+ set(libbacktrace_BINARY_DIR "${CMAKE_BINARY_DIR}/libbacktrace-build")
15
+ set(libbacktrace_INSTALL_DIR "${CMAKE_BINARY_DIR}/libbacktrace-install")
16
+ set(libbacktrace_LIBRARY "${libbacktrace_INSTALL_DIR}/lib/libbacktrace.a")
17
+
18
+ # Clone and build libbacktrace
19
+ ExternalProject_Add(
20
+ libbacktrace
21
+ GIT_REPOSITORY https://github.com/ianlancetaylor/libbacktrace.git
22
+ SOURCE_DIR "${libbacktrace_SOURCE_DIR}"
23
+ BINARY_DIR "${libbacktrace_BINARY_DIR}"
24
+ CONFIGURE_COMMAND "${libbacktrace_SOURCE_DIR}/configure" --prefix=${libbacktrace_INSTALL_DIR} --enable-static
25
+ BUILD_COMMAND make install
26
+ INSTALL_COMMAND ""
27
+ BUILD_BYPRODUCTS "${libbacktrace_LIBRARY}"
28
+ EXCLUDE_FROM_ALL 1
29
+ )
30
+
31
+ # Create an imported library target
32
+ add_library(libbacktrace_library STATIC IMPORTED GLOBAL)
33
+ set_property(
34
+ TARGET libbacktrace_library
35
+ PROPERTY IMPORTED_LOCATION "${libbacktrace_LIBRARY}"
36
+ )
37
+ add_dependencies(libbacktrace_library libbacktrace)
38
+
39
+ # Export variables to parent scope
40
+ set(NETDATA_LIBBACKTRACE_INCLUDE_DIRS "${libbacktrace_INSTALL_DIR}/include" PARENT_SCOPE)
41
+ set(NETDATA_LIBBACKTRACE_LIBRARIES libbacktrace_library PARENT_SCOPE)
42
+ set(HAVE_LIBBACKTRACE TRUE PARENT_SCOPE)
43
+
44
+ message(STATUS "Finished preparing libbacktrace")
45
+endfunction()
46
+
47
+function(netdata_add_libbacktrace_to_target _target)
48
+ target_include_directories(${_target} BEFORE PUBLIC "${NETDATA_LIBBACKTRACE_INCLUDE_DIRS}")
49
+ target_link_libraries(${_target} PUBLIC ${NETDATA_LIBBACKTRACE_LIBRARIES})
50
+ add_dependencies(${_target} libbacktrace)
51
+endfunction()
packaging/cmake/config.cmake.h.in
+1
@@ -83,6 +83,7 @@
83
#cmakedefine HAVE_TIMEGM
84
#cmakedefine HAVE_TM_GMTOFF
85
86
+#cmakedefine HAVE_LIBBACKTRACE
87
#cmakedefine HAVE_LIBUNWIND
88
#cmakedefine HAVE_BACKTRACE
89
#cmakedefine HAVE_CLOSE_RANGE
packaging/docker/Dockerfile
-1
@@ -27,7 +27,6 @@ WORKDIR /opt/netdata.git
27
RUN chmod +x netdata-installer.sh && \
28
cp -rp /deps/* /usr/local/ && \
29
/bin/echo -e "INSTALL_TYPE='oci'\nPREBUILT_ARCH='$(uname -m)'" > ./system/.install-type && \
30
- NETDATA_CMAKE_OPTIONS="-DENABLE_LIBUNWIND=On" \
30
CFLAGS="$(packaging/docker/gen-cflags.sh)" LDFLAGS="-Wl,--gc-sections" ./netdata-installer.sh --dont-wait --dont-start-it --use-system-protobuf \
31
${EXTRA_INSTALL_OPTS} --disable-ebpf --install-no-prefix / "$([ "$RELEASE_CHANNEL" = stable ] && echo --stable-channel)"
32
packaging/makeself/jobs/70-netdata-git.install.sh
+5
-1
@@ -26,7 +26,11 @@ export PKG_CONFIG_PATH="/libunwind-static/lib/pkgconfig:/openssl-static/lib64/pk
26
# Set correct CMake flags for building against non-System OpenSSL
27
# See: https://github.com/warmcat/libwebsockets/blob/master/READMEs/README.build.md
28
export CMAKE_FLAGS="-DOPENSSL_ROOT_DIR=/openssl-static -DOPENSSL_LIBRARIES=/openssl-static/lib64 -DCMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE=/openssl-static -DLWS_OPENSSL_INCLUDE_DIRS=/openssl-static/include -DLWS_OPENSSL_LIBRARIES=/openssl-static/lib64/libssl.a;/openssl-static/lib64/libcrypto.a"
29
-[ "${BUILDARCH}" != "ppc64le" ] && export NETDATA_CMAKE_OPTIONS="-DENABLE_LIBUNWIND=ON"
29
+
30
+case "${BUILDARCH}" in
31
+ armv7l|armv6l) export NETDATA_CMAKE_OPTIONS="-DENABLE_LIBBACKTRACE=OFF -DENABLE_LIBUNWIND=ON" ;;
32
+ *) export NETDATA_CMAKE_OPTIONS="-DENABLE_LIBBACKTRACE=On"
33
+esac
34
35
run ./netdata-installer.sh \
36
--install-prefix "${NETDATA_INSTALL_PARENT}" \
src/daemon/buildinfo.c
+7
-20
@@ -78,8 +78,7 @@ typedef enum __attribute__((packed)) {
78
BIB_LIB_LIBCRYPTO,
79
BIB_LIB_LIBYAML,
80
BIB_LIB_LIBMNL,
81
- BIB_LIB_LIBUNWIND,
82
- BIB_LIB_BACKTRACE,
81
+ BIB_LIB_STACKTRACE,
82
BIB_PLUGIN_APPS,
83
BIB_PLUGIN_LINUX_CGROUPS,
84
BIB_PLUGIN_LINUX_CGROUP_NETWORK,
@@ -729,20 +728,12 @@ static struct {
728
.json = "libmnl",
729
.value = NULL,
730
},
732
- [BIB_LIB_LIBUNWIND] = {
731
+ [BIB_LIB_STACKTRACE] = {
732
.category = BIC_LIBS,
734
- .type = BIT_BOOLEAN,
735
- .analytics = "libunwind",
736
- .print = "libunwind (library for getting stack traces)",
737
- .json = "libunwind",
738
- .value = NULL,
739
- },
740
- [BIB_LIB_BACKTRACE] = {
741
- .category = BIC_LIBS,
742
- .type = BIT_BOOLEAN,
743
- .analytics = "backtrace",
744
- .print = "backtrace (library for getting stack traces)",
745
- .json = "backtrace",
733
+ .type = BIT_STRING,
734
+ .analytics = "stacktraces",
735
+ .print = "stacktraces (library for getting stack traces)",
736
+ .json = "stacktraces",
737
.value = NULL,
738
},
739
[BIB_PLUGIN_APPS] = {
@@ -1312,11 +1303,7 @@ __attribute__((constructor)) void initialize_build_info(void) {
1303
#ifdef HAVE_LIBMNL
1304
build_info_set_status(BIB_LIB_LIBMNL, true);
1305
#endif
1315
-#ifdef HAVE_LIBUNWIND
1316
- build_info_set_status(BIB_LIB_LIBUNWIND, true);
1317
-#elif defined(HAVE_BACKTRACE)
1318
- build_info_set_status(BIB_LIB_BACKTRACE, true);
1319
-#endif
1306
+ build_info_set_value(BIB_LIB_STACKTRACE, capture_stack_trace_backend());
1307
1308
#ifdef ENABLE_PLUGIN_APPS
1309
build_info_set_status(BIB_PLUGIN_APPS, true);
src/libnetdata/log/nd_log-stacktrace.c
+174
-1
@@ -6,7 +6,180 @@ bool nd_log_forked = false;
6
7
#define NO_STACK_TRACE_PREFIX "stack trace not available: "
8
9
-#if defined(HAVE_LIBUNWIND)
9
+#if defined(HAVE_LIBBACKTRACE)
10
+#include "backtrace-supported.h"
11
+#endif
12
+
13
+#if defined(HAVE_LIBBACKTRACE) && BACKTRACE_SUPPORTED == 1 /* && BACKTRACE_SUPPORTS_THREADS == 1 */
14
+#include "backtrace.h"
15
+
16
+static struct backtrace_state *backtrace_state = NULL;
17
+
18
+typedef struct {
19
+ BUFFER *wb; // Buffer to write to
20
+ size_t frame_count; // Number of frames processed
21
+ bool first_frame; // Is this the first frame?
22
+} backtrace_data_t;
23
+
24
+// Common function to format and add a stack frame to the buffer
25
+static void add_stack_frame(backtrace_data_t *bt_data, const char *function,
26
+ const char *filename, int lineno) {
27
+ BUFFER *wb = bt_data->wb;
28
+
29
+ if (!wb)
30
+ return;
31
+
32
+ // Add a newline between frames
33
+ if (!bt_data->first_frame)
34
+ buffer_putc(wb, '\n');
35
+ else
36
+ bt_data->first_frame = false;
37
+
38
+ // Format: #ID function (filename.c:NNN)
39
+ buffer_putc(wb, '#');
40
+ buffer_print_uint64(wb, bt_data->frame_count);
41
+ buffer_putc(wb, ' ');
42
+
43
+ if (function && *function)
44
+ buffer_strcat(wb, function);
45
+ else
46
+ buffer_strcat(wb, "<unknown>");
47
+
48
+ if (filename && *filename) {
49
+ buffer_strcat(wb, " (");
50
+
51
+ // Strip path from filename - find the last slash
52
+ const char *base_filename = filename;
53
+ const char *last_slash = strrchr(filename, '/');
54
+ if (last_slash)
55
+ base_filename = last_slash + 1;
56
+
57
+ buffer_strcat(wb, base_filename);
58
+
59
+ if (lineno > 0) {
60
+ buffer_strcat(wb, ":");
61
+ buffer_print_uint64(wb, (uint64_t)lineno);
62
+ }
63
+
64
+ buffer_putc(wb, ')');
65
+ }
66
+
67
+ bt_data->frame_count++;
68
+}
69
+
70
+// Error callback for libbacktrace
71
+static void bt_error_handler(void *data, const char *msg, int errnum) {
72
+ backtrace_data_t *bt_data = (backtrace_data_t *)data;
73
+
74
+ if (!bt_data || !bt_data->wb)
75
+ return;
76
+
77
+ // Use <unknown> for function name in error cases
78
+ const char *function = "<unknown>";
79
+
80
+ // Format the error message as the filename
81
+ char error_buf[512] = "error: ";
82
+ size_t len = 7; // Length of "error: "
83
+
84
+ // Add the error message
85
+ if (msg)
86
+ len = strcatz(error_buf, len, sizeof(error_buf), msg);
87
+
88
+ // Add the error number description if available
89
+ if (errnum > 0) {
90
+ if (msg) {
91
+ len = strcatz(error_buf, len, sizeof(error_buf), ": ");
92
+ }
93
+ len = strcatz(error_buf, len, sizeof(error_buf), strerror(errnum));
94
+ }
95
+
96
+ add_stack_frame(bt_data, function, error_buf, 0);
97
+}
98
+
99
+// Full callback for libbacktrace
100
+static int bt_full_handler(void *data, uintptr_t pc,
101
+ const char *filename, int lineno,
102
+ const char *function) {
103
+ backtrace_data_t *bt_data = (backtrace_data_t *)data;
104
+ if (!bt_data)
105
+ return 0;
106
+
107
+ add_stack_frame(bt_data, function, filename, lineno);
108
+
109
+ return 0; // Continue backtrace
110
+}
111
+
112
+const char *capture_stack_trace_backend(void) {
113
+#if BACKTRACE_SUPPORTS_DATA
114
+#define BACKTRACE_DATA "data"
115
+#else
116
+#define BACKTRACE_DATA "no-data"
117
+#endif
118
+
119
+#if BACKTRACE_USES_MALLOC
120
+#define BACKTRACE_MEMORY "malloc"
121
+#else
122
+#define BACKTRACE_MEMORY "mmap"
123
+#endif
124
+
125
+#if BACKTRACE_SUPPORTS_THREADS
126
+#define BACKTRACE_THREADS "threads"
127
+#else
128
+#define BACKTRACE_THREADS "no-threads"
129
+#endif
130
+
131
+ return "libbacktrace (" BACKTRACE_MEMORY ", " BACKTRACE_THREADS ", " BACKTRACE_DATA ")";
132
+}
133
+
134
+void capture_stack_trace_init(void) {
135
+ if (!backtrace_state) {
136
+ backtrace_state = backtrace_create_state(NULL, BACKTRACE_SUPPORTS_THREADS,
137
+ NULL, // We'll handle errors in bt_full_handler
138
+ NULL);
139
+ }
140
+}
141
+
142
+void capture_stack_trace_flush(void) {
143
+ // Nothing to flush with libbacktrace
144
+}
145
+
146
+bool capture_stack_trace_is_async_signal_safe(void) {
147
+// libbacktrace may use malloc depending on configuration
148
+// Check the BACKTRACE_USES_MALLOC define
149
+#if BACKTRACE_USES_MALLOC
150
+ return false;
151
+#else
152
+ return true;
153
+#endif
154
+}
155
+
156
+bool capture_stack_trace_available(void) {
157
+ return backtrace_state != NULL && BACKTRACE_SUPPORTED;
158
+}
159
+
160
+void capture_stack_trace(BUFFER *wb) {
161
+ if (!backtrace_state) {
162
+ buffer_strcat(wb, NO_STACK_TRACE_PREFIX "libbacktrace not initialized");
163
+ return;
164
+ }
165
+
166
+ backtrace_data_t bt_data = {
167
+ .wb = wb,
168
+ .frame_count = 0,
169
+ .first_frame = true
170
+ };
171
+
172
+ // Skip one frame to hide capture_stack_trace() itself
173
+ backtrace_full(backtrace_state, 1, bt_full_handler,
174
+ bt_error_handler, &bt_data);
175
+
176
+ // If no frames were reported
177
+ if (bt_data.frame_count == 0) {
178
+ buffer_strcat(wb, NO_STACK_TRACE_PREFIX "libbacktrace reports no frames");
179
+ }
180
+}
181
+
182
+#elif defined(HAVE_LIBUNWIND)
183
#if !defined(STATIC_BUILD)
184
#define UNW_LOCAL_ONLY
185
#endif