@cryptotaxi247 / netdata-1 / commits / 568766f2d

daemon status 26d (#20047)

* filter signal handler from stack traces When capturing stack traces from crashes where signal handlers are involved, filter out the signal handler function itself and all functions after it. This provides cleaner and more focused stack traces by showing only the code path leading up to the crash. * improve stack trace processing for crashes 1. Add root cause tracking to store the first netdata source function in a stack trace - only for libbacktrace 2. Update status-file.c to use the root cause function for crash reporting * mark functions as never inline to ensure they are always in the stack traces * show the logger function in the stack traces * never skip frames from stack traces

Costa Tsaousis committed Apr 3, 2025 at 17:12 UTC 568766f2daec5b85f96de6a3b816af6cde788d39
5 files changed +205 -6
src/daemon/signal-handler.c
+6
@@ -47,6 +47,7 @@ static struct {
47 static void (*original_handlers[NSIG])(int) = {0};
48 static void (*original_sigactions[NSIG])(int, siginfo_t *, void *) = {0};
49
50 +NEVER_INLINE
51 void nd_signal_handler(int signo, siginfo_t *info, void *context __maybe_unused) {
52
53 for(size_t i = 0; i < _countof(signals_waiting) ; i++) {
@@ -168,6 +169,9 @@ void nd_cleanup_deadly_signals(void) {
169
170 void nd_initialize_signals(bool chain_existing) {
171 signals_block_all_except_deadly();
172 +
173 + // Set the signal handler name for stack trace filtering
174 + capture_stack_trace_set_signal_handler_function("nd_signal_handler");
175
176 struct sigaction act;
177 memset(&act, 0, sizeof(struct sigaction));
@@ -206,6 +210,7 @@ void nd_initialize_signals(bool chain_existing) {
210 }
211 }
212
213 +NEVER_INLINE
214 static void process_triggered_signals(void) {
215 size_t found;
216 do {
@@ -264,6 +269,7 @@ static inline bool threshold_trigger_smaller(bool *last, double threshold, doubl
269 return !triggered && *last;
270 }
271
272 +NEVER_INLINE
273 void nd_process_signals(void) {
274 posix_unmask_my_signals();
275 const usec_t save_every_ut = 15 * 60 * USEC_PER_SEC;
src/daemon/status-file.c
+10
@@ -1140,6 +1140,7 @@ void daemon_status_file_check_crash(void) {
1140 }
1141 }
1142
1143 +NEVER_INLINE
1144 static void daemon_status_file_save_twice_if_we_can_get_stack_trace(BUFFER *wb, DAEMON_STATUS_FILE *ds, bool force) {
1145 // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
1146 // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
@@ -1156,8 +1157,14 @@ static void daemon_status_file_save_twice_if_we_can_get_stack_trace(BUFFER *wb,
1157 return;
1158
1159 buffer_flush(wb);
1160 +
1161 capture_stack_trace(wb);
1162
1163 + // Store the first netdata function from the stack trace if available
1164 + const char *first_nd_fn = capture_stack_trace_root_cause_function();
1165 + if (first_nd_fn && *first_nd_fn && !ds->fatal.function[0])
1166 + strncpyz(ds->fatal.function, first_nd_fn, sizeof(ds->fatal.function) - 1);
1167 +
1168 if(buffer_strlen(wb) > 0) {
1169 strncpyz(
1170 ds->fatal.stack_trace,
@@ -1173,6 +1180,7 @@ static void daemon_status_file_save_twice_if_we_can_get_stack_trace(BUFFER *wb,
1180 // --------------------------------------------------------------------------------------------------------------------
1181 // ng_log() hook for receiving fatal message information
1182
1183 +NEVER_INLINE
1184 void daemon_status_file_register_fatal(const char *filename, const char *function, const char *message, const char *errno_str, const char *stack_trace, long line) {
1185 FUNCTION_RUN_ONCE();
1186
@@ -1251,6 +1259,7 @@ static void daemon_status_file_out_of_memory(void) {
1259 daemon_status_file_save_twice_if_we_can_get_stack_trace(static_save_buffer, &session_status, true);
1260 }
1261
1262 +NEVER_INLINE
1263 bool daemon_status_file_deadly_signal_received(EXIT_REASON reason, SIGNAL_CODE code, void *fault_address, bool chained_handler) {
1264 FUNCTION_RUN_ONCE_RET(true);
1265
@@ -1316,6 +1325,7 @@ bool daemon_status_file_deadly_signal_received(EXIT_REASON reason, SIGNAL_CODE c
1325
1326 static SPINLOCK shutdown_timeout_spinlock = SPINLOCK_INITIALIZER;
1327
1328 +NEVER_INLINE
1329 void daemon_status_file_shutdown_timeout(BUFFER *trace) {
1330 FUNCTION_RUN_ONCE();
1331
src/libnetdata/log/nd_log-stacktrace.c
+184 -6
@@ -6,6 +6,109 @@ bool nd_log_forked = false;
6
7 #define NO_STACK_TRACE_PREFIX "info: stack trace is not available, "
8
9 +// The signal handler function name to filter out in stack traces
10 +static const char *signal_handler_function = "nd_signal_handler";
11 +
12 +// List of auxiliary functions that should not be reported as root cause
13 +static const char *auxiliary_functions[] = {
14 + "nd_uuid_copy",
15 + "out_of_memory",
16 + "shutdown_timed_out",
17 + NULL // Terminator
18 +};
19 +
20 +// List of logging functions to filter out
21 +static const char *logging_functions[] = {
22 + "netdata_logger",
23 + "netdata_logger_with_limit",
24 + "netdata_logger_fatal",
25 + NULL // Terminator
26 +};
27 +
28 +// Set the signal handler function name to filter out in stack traces
29 +void capture_stack_trace_set_signal_handler_function(const char *function_name) {
30 + signal_handler_function = function_name;
31 +}
32 +
33 +// Exact match check if a function is in the auxiliary list (strcmp)
34 +static inline bool is_auxiliary_function(const char *function) {
35 + if (!function || !*function)
36 + return false;
37 +
38 + for (int i = 0; auxiliary_functions[i]; i++) {
39 + if (strcmp(function, auxiliary_functions[i]) == 0)
40 + return true;
41 + }
42 +
43 + return false;
44 +}
45 +
46 +// Exact match check if a function is a logging function (strcmp)
47 +static inline bool is_logging_function(const char *function) {
48 + if (!function || !*function)
49 + return false;
50 +
51 + for (int i = 0; logging_functions[i]; i++) {
52 + if (strcmp(function, logging_functions[i]) == 0)
53 + return true;
54 + }
55 +
56 + return false;
57 +}
58 +
59 +// Substring check if a function contains a logging function name (strstr)
60 +static inline bool contains_logging_function(const char *text) {
61 + if (!text || !*text)
62 + return false;
63 +
64 + for (int i = 0; logging_functions[i]; i++) {
65 + if (strstr(text, logging_functions[i]) != NULL)
66 + return true;
67 + }
68 +
69 + return false;
70 +}
71 +
72 +static inline bool is_netdata_function(const char *function, const char *filename) {
73 + return function && *function && filename && *filename &&
74 + strstr(filename, "/src/") &&
75 + !strstr(filename, "/vendored/");
76 +}
77 +
78 +// Exact match check if a function is the signal handler (strcmp)
79 +static inline bool is_signal_handler_function(const char *function) {
80 + return function && *function &&
81 + signal_handler_function && *signal_handler_function &&
82 + strcmp(function, signal_handler_function) == 0;
83 +}
84 +
85 +// Substring check if a function contains the signal handler name (strstr)
86 +static inline bool contains_signal_handler_function(const char *text) {
87 + return text && *text &&
88 + signal_handler_function && *signal_handler_function &&
89 + strstr(text, signal_handler_function) != NULL;
90 +}
91 +
92 +// Thread-local buffer to store the first netdata function encountered in a stack trace
93 +static __thread char root_cause_function[48];
94 +
95 +// Returns the first netdata function found in the stack trace
96 +const char *capture_stack_trace_root_cause_function(void) {
97 + return root_cause_function[0] ? root_cause_function : NULL;
98 +}
99 +
100 +// Store a function name as the first netdata function found
101 +static inline void keep_first_root_cause_function(const char *function) {
102 + if (!function || !*function || root_cause_function[0])
103 + return; // Already have a function or null input
104 +
105 + // Skip auxiliary functions and logging functions
106 + if (is_auxiliary_function(function) || is_logging_function(function))
107 + return;
108 +
109 + strncpyz(root_cause_function, function, sizeof(root_cause_function) - 1);
110 +}
111 +
112 #if defined(HAVE_LIBBACKTRACE)
113 #include "backtrace-supported.h"
114 #endif
@@ -16,9 +119,10 @@ bool nd_log_forked = false;
119 static struct backtrace_state *backtrace_state = NULL;
120
121 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?
122 + BUFFER *wb; // Buffer to write to
123 + size_t frame_count; // Number of frames processed
124 + bool first_frame; // Is this the first frame?
125 + bool found_signal_handler; // Have we found the signal handler frame?
126 } backtrace_data_t;
127
128 // Common function to format and add a stack frame to the buffer
@@ -29,6 +133,33 @@ static void add_stack_frame(backtrace_data_t *bt_data, uintptr_t pc, const char
133 if (!wb)
134 return;
135
136 + // Check if we found the signal handler frame
137 + if (!bt_data->found_signal_handler && is_signal_handler_function(function)) {
138 + // We found the signal handler, reset the buffer and clear function name
139 + buffer_flush(wb);
140 + bt_data->frame_count = 0;
141 + bt_data->first_frame = true;
142 + bt_data->found_signal_handler = true;
143 + root_cause_function[0] = '\0';
144 + return; // Skip adding the signal handler itself
145 + }
146 +
147 + // Check for logging functions, but only if we haven't found a signal handler yet
148 + // This prevents double resets when crashing inside logging code
149 + if (!bt_data->found_signal_handler && is_logging_function(function)) {
150 + // Found a logging function, reset the buffer and clear function name
151 + buffer_flush(wb);
152 + bt_data->frame_count = 0;
153 + bt_data->first_frame = true;
154 + root_cause_function[0] = '\0';
155 + // continue to add the function to the stack trace
156 + }
157 +
158 + // Check if this is a netdata source file and store the function name if it is
159 + // (but only if we haven't already stored one)
160 + if (!root_cause_function[0] && is_netdata_function(function, filename))
161 + keep_first_root_cause_function(function);
162 +
163 // Add a newline between frames
164 if (!bt_data->first_frame)
165 buffer_putc(wb, '\n');
@@ -164,6 +295,7 @@ bool capture_stack_trace_available(void) {
295 return backtrace_state != NULL && BACKTRACE_SUPPORTED;
296 }
297
298 +NEVER_INLINE
299 void capture_stack_trace(BUFFER *wb) {
300 if (!backtrace_state) {
301 buffer_strcat(wb, NO_STACK_TRACE_PREFIX "libbacktrace not initialized");
@@ -173,11 +305,12 @@ void capture_stack_trace(BUFFER *wb) {
305 backtrace_data_t bt_data = {
306 .wb = wb,
307 .frame_count = 0,
176 - .first_frame = true
308 + .first_frame = true,
309 + .found_signal_handler = false
310 };
311
312 // Skip one frame to hide capture_stack_trace() itself
180 - backtrace_full(backtrace_state, 1, bt_full_handler,
313 + backtrace_full(backtrace_state, 0, bt_full_handler,
314 bt_error_handler, &bt_data);
315
316 // If no frames were reported
@@ -216,6 +349,7 @@ bool capture_stack_trace_available(void) {
349 return true;
350 }
351
352 +NEVER_INLINE
353 void capture_stack_trace(BUFFER *wb) {
354 // this function is async-signal-safe, if the buffer has enough space to hold the stack trace
355
@@ -228,6 +362,8 @@ void capture_stack_trace(BUFFER *wb) {
362 unw_init_local(&cursor, &context);
363
364 size_t added = 0;
365 + bool found_signal_handler = false;
366 +
367 while (unw_step(&cursor) > 0) {
368 unw_word_t offset, pc;
369 char sym[256];
@@ -242,6 +378,25 @@ void capture_stack_trace(BUFFER *wb) {
378 offset = 0;
379 }
380
381 + // Check if we found the signal handler frame
382 + if (!found_signal_handler && is_signal_handler_function(name)) {
383 + // We found the signal handler, reset the buffer
384 + buffer_flush(wb);
385 + added = 0;
386 + frames = 0;
387 + found_signal_handler = true;
388 + continue; // Skip adding the signal handler itself
389 + }
390 +
391 + // Check for logging functions, but only if we haven't found a signal handler yet
392 + if (!found_signal_handler && is_logging_function(name)) {
393 + // Found a logging function, reset the buffer
394 + buffer_flush(wb);
395 + added = 0;
396 + frames = 0;
397 + // continue to add the function to the stack trace
398 + }
399 +
400 if (frames++)
401 buffer_putc(wb, '\n');
402
@@ -284,6 +439,7 @@ bool capture_stack_trace_is_async_signal_safe(void) {
439 return false;
440 }
441
442 +NEVER_INLINE
443 void capture_stack_trace(BUFFER *wb) {
444 void *array[50];
445 char **messages;
@@ -298,14 +454,33 @@ void capture_stack_trace(BUFFER *wb) {
454 }
455
456 size_t added = 0;
457 + bool found_signal_handler = false;
458 +
459 // Format the stack trace (removing the address part)
460 for (i = 0; i < size; i++) {
461 if(messages[i] && *messages[i]) {
462 + // Check if we found the signal handler frame
463 + if (!found_signal_handler && contains_signal_handler_function(messages[i])) {
464 + // We found the signal handler, reset the buffer
465 + buffer_flush(wb);
466 + added = 0;
467 + found_signal_handler = true;
468 + continue; // Skip adding the signal handler itself
469 + }
470 +
471 + // Check for logging functions, but only if we haven't found a signal handler yet
472 + if (!found_signal_handler && contains_logging_function(messages[i])) {
473 + // Found a logging function, reset the buffer
474 + buffer_flush(wb);
475 + added = 0;
476 + // continue to add the function to the stack trace
477 + }
478 +
479 if(added)
480 buffer_putc(wb, '\n');
481
482 buffer_putc(wb, '#');
308 - buffer_print_uint64(wb, i);
483 + buffer_print_uint64(wb, added);
484 buffer_putc(wb, ' ');
485 buffer_strcat(wb, messages[i]);
486 added++;
@@ -340,6 +515,7 @@ bool capture_stack_trace_is_async_signal_safe(void) {
515 return false;
516 }
517
518 +NEVER_INLINE
519 void capture_stack_trace(BUFFER *wb) {
520 buffer_strcat(wb, NO_STACK_TRACE_PREFIX "no back-end available");
521
@@ -350,6 +526,7 @@ void capture_stack_trace(BUFFER *wb) {
526
527 #endif
528
529 +NEVER_INLINE
530 bool stack_trace_formatter(BUFFER *wb, void *data __maybe_unused) {
531 static __thread bool in_stack_trace = false;
532
@@ -367,6 +544,7 @@ bool stack_trace_formatter(BUFFER *wb, void *data __maybe_unused) {
544
545 in_stack_trace = true;
546
547 + root_cause_function[0] = '\0';
548 capture_stack_trace(wb);
549
550 in_stack_trace = false; // Ensure the flag is reset
src/libnetdata/log/nd_log.c
+3
@@ -402,6 +402,7 @@ static ND_LOG_SOURCES nd_log_validate_source(ND_LOG_SOURCES source) {
402 // --------------------------------------------------------------------------------------------------------------------
403 // public API for loggers
404
405 +NEVER_INLINE
406 void netdata_logger(ND_LOG_SOURCES source, ND_LOG_FIELD_PRIORITY priority, const char *file, const char *function, unsigned long line, const char *fmt, ... )
407 {
408 int saved_errno = errno;
@@ -424,6 +425,7 @@ void netdata_logger(ND_LOG_SOURCES source, ND_LOG_FIELD_PRIORITY priority, const
425 va_end(args);
426 }
427
428 +NEVER_INLINE
429 void netdata_logger_with_limit(ERROR_LIMIT *erl, ND_LOG_SOURCES source, ND_LOG_FIELD_PRIORITY priority, const char *file __maybe_unused, const char *function __maybe_unused, const unsigned long line __maybe_unused, const char *fmt, ... ) {
430 int saved_errno = errno;
431
@@ -477,6 +479,7 @@ static void fatal_abort_internal_checks(void) {
479 _exit(1);
480 }
481
482 +NEVER_INLINE
483 void netdata_logger_fatal(const char *file, const char *function, const unsigned long line, const char *fmt, ... ) {
484 static size_t already_in_fatal = 0;
485
src/libnetdata/log/nd_log.h
+2
@@ -39,6 +39,8 @@ void capture_stack_trace_flush(void);
39 bool capture_stack_trace_available(void);
40 bool capture_stack_trace_is_async_signal_safe(void);
41 const char *capture_stack_trace_backend(void);
42 +void capture_stack_trace_set_signal_handler_function(const char *function_name);
43 +const char *capture_stack_trace_root_cause_function(void);
44
45 typedef void (*log_event_t)(const char *filename, const char *function, const char *message, const char *errno_str, const char *stack_trace, long line);
46 void nd_log_register_fatal_hook_cb(log_event_t cb);