| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "stacktrace-common.h" |
| 4 | |
| 5 | #if defined(USE_LIBBACKTRACE) |
| 6 | #include "backtrace.h" |
| 7 | |
| 8 | static struct backtrace_state *backtrace_state = NULL; |
| 9 | |
| 10 | typedef struct { |
| 11 | BUFFER *wb; // Buffer to write to |
| 12 | size_t frame_count; // Number of frames processed |
| 13 | bool first_frame; // Is this the first frame? |
| 14 | bool found_signal_handler; // Have we found the signal handler frame? |
| 15 | } backtrace_data_t; |
| 16 | |
| 17 | // For collecting raw frames |
| 18 | typedef struct { |
| 19 | void **frames; |
| 20 | int max_frames; |
| 21 | int num_frames; |
| 22 | int skip_frames; |
| 23 | } collect_frames_data_t; |
| 24 | |
| 25 | // Simple callback for collecting PC addresses |
| 26 | static int bt_collect_frames_callback(void *data, uintptr_t pc) { |
| 27 | collect_frames_data_t *cf_data = (collect_frames_data_t *)data; |
| 28 | |
| 29 | // Skip frames at the top of the stack |
| 30 | if (cf_data->skip_frames > 0) { |
| 31 | cf_data->skip_frames--; |
| 32 | return 0; |
| 33 | } |
| 34 | |
| 35 | if (cf_data->num_frames < cf_data->max_frames) { |
| 36 | cf_data->frames[cf_data->num_frames++] = (void *)pc; |
| 37 | } |
| 38 | |
| 39 | return 0; |
| 40 | } |
| 41 | |
| 42 | // Common function to format and add a stack frame to the buffer |
| 43 | static void add_stack_frame(backtrace_data_t *bt_data, uintptr_t pc, const char *function, |
| 44 | const char *filename, int lineno) { |
| 45 | BUFFER *wb = bt_data->wb; |
| 46 | |
| 47 | if (!wb) |
| 48 | return; |
| 49 | |
| 50 | // Check if we found the signal handler frame |
| 51 | if (!bt_data->found_signal_handler && stacktrace_is_signal_handler_function(function)) { |
| 52 | // We found the signal handler, reset the buffer and clear function name |
| 53 | buffer_flush(wb); |
| 54 | bt_data->frame_count = 0; |
| 55 | bt_data->first_frame = true; |
| 56 | bt_data->found_signal_handler = true; |
| 57 | root_cause_function[0] = '\0'; |
| 58 | return; // Skip adding the signal handler itself |
| 59 | } |
| 60 | |
| 61 | // Check for logging functions, but only if we haven't found a signal handler yet |
| 62 | // This prevents double resets when crashing inside logging code |
| 63 | if (!bt_data->found_signal_handler && stacktrace_is_logging_function(function)) { |
| 64 | // Found a logging function, reset the buffer and clear function name |
| 65 | buffer_flush(wb); |
| 66 | bt_data->frame_count = 0; |
| 67 | bt_data->first_frame = true; |
| 68 | root_cause_function[0] = '\0'; |
| 69 | // continue to add the function to the stack trace |
| 70 | } |
| 71 | |
| 72 | // Check if this is a netdata source file and store the function name if it is |
| 73 | // (but only if we haven't already stored one) |
| 74 | if (!root_cause_function[0] && stacktrace_is_netdata_function(function, filename)) |
| 75 | stacktrace_keep_first_root_cause_function(function); |
| 76 | |
| 77 | // Add a newline between frames |
| 78 | if (!bt_data->first_frame) |
| 79 | buffer_putc(wb, '\n'); |
| 80 | else |
| 81 | bt_data->first_frame = false; |
| 82 | |
| 83 | // Format: #ID function (filename.c:NNN) |
| 84 | buffer_putc(wb, '#'); |
| 85 | buffer_print_uint64(wb, bt_data->frame_count); |
| 86 | buffer_putc(wb, ' '); |
| 87 | |
| 88 | if (function && *function) |
| 89 | buffer_strcat(wb, function); |
| 90 | else |
| 91 | buffer_strcat(wb, "<unknown>"); |
| 92 | |
| 93 | if(pc) { |
| 94 | buffer_strcat(wb, " ["); |
| 95 | buffer_print_uint64_hex(wb, pc); |
| 96 | buffer_putc(wb, ']'); |
| 97 | } |
| 98 | |
| 99 | if (filename && *filename) { |
| 100 | buffer_strcat(wb, " ("); |
| 101 | |
| 102 | const char *f = strstr(filename, "/src/"); |
| 103 | if (f) { |
| 104 | const char *f2 = strstr(f + 1, "/src/"); |
| 105 | if(f2) f = f2; |
| 106 | } |
| 107 | if(!f) f = filename; |
| 108 | |
| 109 | buffer_strcat(wb, f); |
| 110 | |
| 111 | if (lineno > 0) { |
| 112 | buffer_strcat(wb, ":"); |
| 113 | buffer_print_uint64(wb, (uint64_t)lineno); |
| 114 | } |
| 115 | |
| 116 | buffer_putc(wb, ')'); |
| 117 | } |
| 118 | |
| 119 | bt_data->frame_count++; |
| 120 | } |
| 121 | |
| 122 | // Error callback for libbacktrace |
| 123 | static void bt_error_handler(void *data, const char *msg, int errnum) { |
| 124 | backtrace_data_t *bt_data = (backtrace_data_t *)data; |
| 125 | |
| 126 | if (!bt_data || !bt_data->wb) |
| 127 | return; |
| 128 | |
| 129 | // Use <unknown> for function name in error cases |
| 130 | const char *function = "<unknown>"; |
| 131 | |
| 132 | // Format the error message as the filename |
| 133 | char error_buf[512] = "error: "; |
| 134 | size_t len = 7; // Length of "error: " |
| 135 | |
| 136 | // Add the error message |
| 137 | if (msg) |
| 138 | len = strcatz(error_buf, len, msg, sizeof(error_buf)); |
| 139 | |
| 140 | // Add the error number description if available |
| 141 | if (errnum > 0) { |
| 142 | if (msg) { |
| 143 | len = strcatz(error_buf, len, ": ", sizeof(error_buf)); |
| 144 | } |
| 145 | len = strcatz(error_buf, len, strerror(errnum), sizeof(error_buf)); |
| 146 | } |
| 147 | |
| 148 | add_stack_frame(bt_data, 0, function, error_buf, 0); |
| 149 | } |
| 150 | |
| 151 | // Full callback for libbacktrace |
| 152 | static int bt_full_handler(void *data, uintptr_t pc, |
| 153 | const char *filename, int lineno, |
| 154 | const char *function) { |
| 155 | backtrace_data_t *bt_data = (backtrace_data_t *)data; |
| 156 | if (!bt_data) |
| 157 | return 0; |
| 158 | |
| 159 | add_stack_frame(bt_data, pc, function, filename, lineno); |
| 160 | |
| 161 | return 0; // Continue backtrace |
| 162 | } |
| 163 | |
| 164 | const char *stacktrace_backend(void) { |
| 165 | #if BACKTRACE_SUPPORTS_DATA |
| 166 | #define BACKTRACE_DATA "data" |
| 167 | #else |
| 168 | #define BACKTRACE_DATA "no-data" |
| 169 | #endif |
| 170 | |
| 171 | #if BACKTRACE_USES_MALLOC |
| 172 | #define BACKTRACE_MEMORY "malloc" |
| 173 | #else |
| 174 | #define BACKTRACE_MEMORY "mmap" |
| 175 | #endif |
| 176 | |
| 177 | #if BACKTRACE_SUPPORTS_THREADS |
| 178 | #define BACKTRACE_THREADS "threads" |
| 179 | #else |
| 180 | #define BACKTRACE_THREADS "no-threads" |
| 181 | #endif |
| 182 | |
| 183 | return "libbacktrace (" BACKTRACE_MEMORY ", " BACKTRACE_THREADS ", " BACKTRACE_DATA ")"; |
| 184 | } |
| 185 | |
| 186 | void impl_stacktrace_init(void) { |
| 187 | if (!backtrace_state) { |
| 188 | backtrace_state = backtrace_create_state(NULL, BACKTRACE_SUPPORTS_THREADS, |
| 189 | bt_error_handler, NULL); |
| 190 | } |
| 191 | } |
| 192 | |
| 193 | void stacktrace_flush(void) { |
| 194 | // Nothing to flush with libbacktrace |
| 195 | } |
| 196 | |
| 197 | bool stacktrace_capture_is_async_signal_safe(void) { |
| 198 | // libbacktrace may use malloc depending on configuration |
| 199 | // Check the BACKTRACE_USES_MALLOC define |
| 200 | #if BACKTRACE_USES_MALLOC |
| 201 | return false; |
| 202 | #else |
| 203 | return true; |
| 204 | #endif |
| 205 | } |
| 206 | |
| 207 | bool stacktrace_available(void) { |
| 208 | return backtrace_state != NULL; |
| 209 | } |
| 210 | |
| 211 | NEVER_INLINE |
| 212 | void stacktrace_capture(BUFFER *wb) { |
| 213 | root_cause_function[0] = '\0'; |
| 214 | |
| 215 | if (!backtrace_state) { |
| 216 | buffer_strcat(wb, NO_STACK_TRACE_PREFIX "libbacktrace not initialized"); |
| 217 | return; |
| 218 | } |
| 219 | |
| 220 | backtrace_data_t bt_data = { |
| 221 | .wb = wb, |
| 222 | .frame_count = 0, |
| 223 | .first_frame = true, |
| 224 | .found_signal_handler = false |
| 225 | }; |
| 226 | |
| 227 | // Skip one frame to hide stacktrace_capture() itself |
| 228 | backtrace_full(backtrace_state, 1, bt_full_handler, |
| 229 | bt_error_handler, &bt_data); |
| 230 | |
| 231 | // If no frames were reported |
| 232 | if (bt_data.frame_count == 0) { |
| 233 | buffer_strcat(wb, NO_STACK_TRACE_PREFIX "libbacktrace reports no frames"); |
| 234 | } |
| 235 | } |
| 236 | |
| 237 | // Implementation-specific function to collect stack trace frames |
| 238 | NEVER_INLINE |
| 239 | int impl_stacktrace_get_frames(void **frames, int max_frames, int skip_frames) { |
| 240 | if (!backtrace_state || !frames || max_frames <= 0) |
| 241 | return 0; |
| 242 | |
| 243 | // Collect frames |
| 244 | collect_frames_data_t data = { |
| 245 | .frames = frames, |
| 246 | .max_frames = max_frames, |
| 247 | .num_frames = 0, |
| 248 | .skip_frames = skip_frames + 1 // +1 to also skip this function itself |
| 249 | }; |
| 250 | |
| 251 | // Pass 0 as skip_frames to backtrace_simple and let the callback handle skipping |
| 252 | backtrace_simple(backtrace_state, 0, bt_collect_frames_callback, bt_error_handler, &data); |
| 253 | |
| 254 | return data.num_frames; |
| 255 | } |
| 256 | |
| 257 | // Implementation-specific function to convert a stacktrace to a buffer |
| 258 | void impl_stacktrace_to_buffer(STACKTRACE trace, BUFFER *wb) { |
| 259 | struct stacktrace *st = (struct stacktrace *)trace; |
| 260 | |
| 261 | // Resolve each frame |
| 262 | backtrace_data_t bt_data = { |
| 263 | .wb = wb, |
| 264 | .frame_count = 0, |
| 265 | .first_frame = true, |
| 266 | .found_signal_handler = false |
| 267 | }; |
| 268 | |
| 269 | for (int i = 0; i < st->frame_count; i++) { |
| 270 | backtrace_pcinfo( |
| 271 | backtrace_state, |
| 272 | (uintptr_t)st->frames[i], |
| 273 | bt_full_handler, |
| 274 | bt_error_handler, |
| 275 | &bt_data |
| 276 | ); |
| 277 | } |
| 278 | |
| 279 | // If we couldn't resolve any frames, use addresses |
| 280 | if (bt_data.frame_count == 0) { |
| 281 | for (int i = 0; i < st->frame_count; i++) { |
| 282 | if (i > 0) |
| 283 | buffer_putc(wb, '\n'); |
| 284 | |
| 285 | buffer_putc(wb, '#'); |
| 286 | buffer_print_uint64(wb, i); |
| 287 | buffer_strcat(wb, " <unknown> ["); |
| 288 | buffer_print_uint64_hex(wb, (uint64_t)st->frames[i]); |
| 289 | buffer_putc(wb, ']'); |
| 290 | } |
| 291 | } |
| 292 | } |
| 293 | |
| 294 | #endif // USE_LIBBACKTRACE |