ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
debug/backtrace.c
Go to the documentation of this file.
1
11#include <ascii-chat/log/log.h>
14#include <stdio.h>
15#include <time.h>
16#include <string.h>
17
19 if (!bt) {
20 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid backtrace");
21 return;
22 }
23 bt->tried_symbolize = false;
24 bt->count = platform_backtrace(bt->ptrs, 32);
25}
26
27#ifndef NDEBUG
28
30 if (!bt || bt->tried_symbolize) {
31 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid symbolize params");
32 return; // Already tried or invalid (don't try twice)
33 }
34 bt->tried_symbolize = true;
36}
37
39 if (!bt) {
40 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid backtrace params");
41 return;
42 }
45}
46
48 if (!bt) {
49 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid backtrace params");
50 return;
51 }
52 if (bt->symbols) {
54 bt->symbols = NULL;
55 }
56 bt->tried_symbolize = false;
57}
58
59void backtrace_print(const char *label, const backtrace_t *bt, int skip_frames, int max_frames,
61 if (!bt || !bt->symbols || bt->count <= 0) {
62 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid backtrace params");
63 return;
64 }
65
66 // Capture count and symbols once to avoid TOCTOU race with other threads
67 int count = bt->count;
68 char **symbols = bt->symbols;
69
70 // Calculate frame limits
71 int start = skip_frames;
72 int end = count;
73 if (max_frames > 0 && (start + max_frames) < end) {
74 end = start + max_frames;
75 }
76
77 // Build backtrace in two versions: colored for terminal, plain for log file
78 char colored_buffer[16384] = {0};
79 char plain_buffer[16384] = {0};
80 int colored_offset = 0;
81 int plain_offset = 0;
82
83 // Format log header using logging system's template
84 char log_header_buf[512] = {0};
85 time_t now = time(NULL);
86 struct tm tm_info = {0};
87 platform_localtime(&now, &tm_info);
88 char timestamp[32];
89 strftime(timestamp, sizeof(timestamp), "%H:%M:%S", &tm_info);
90
92 uint64_t tid_val = (uintptr_t)tid;
93
94 // Get current time in nanoseconds for template formatting
95 uint64_t time_ns = platform_get_monotonic_time_us() * 1000ULL;
96
97 // Try to format header using the logging system's template with color
99 if (format) {
100 // Color the label with WARN color for terminal output
101 const char *colored_label_ptr = colored_string(LOG_COLOR_WARN, label);
102 char colored_label_buf[256];
103 SAFE_STRNCPY(colored_label_buf, colored_label_ptr, sizeof(colored_label_buf));
104
105 int len = log_template_apply(format, log_header_buf, sizeof(log_header_buf), LOG_WARN, timestamp, __FILE__,
106 __LINE__, __func__, tid_val, colored_label_buf, true, time_ns);
107 if (len > 0) {
108 // Successfully formatted with logging template
109 colored_offset += safe_snprintf(colored_buffer + colored_offset, sizeof(colored_buffer) - (size_t)colored_offset,
110 "%s\n", log_header_buf);
111 } else {
112 // Fallback: manual formatting if template fails
113 safe_snprintf(log_header_buf, sizeof(log_header_buf), "[%s] [WARN] [tid:%llu] %s: %s", timestamp, tid_val,
114 __func__, label);
115 const char *colored_header_ptr = colored_string(LOG_COLOR_WARN, log_header_buf);
116 char colored_header_buf[512];
117 SAFE_STRNCPY(colored_header_buf, colored_header_ptr, sizeof(colored_header_buf));
118 colored_offset += safe_snprintf(colored_buffer + colored_offset, sizeof(colored_buffer) - (size_t)colored_offset,
119 "%s\n", colored_header_buf);
120 }
121 } else {
122 // Fallback: manual formatting if no template available
123 safe_snprintf(log_header_buf, sizeof(log_header_buf), "[%s] [WARN] [tid:%llu] %s: %s", timestamp, tid_val, __func__,
124 label);
125 const char *colored_header_ptr = colored_string(LOG_COLOR_WARN, log_header_buf);
126 char colored_header_buf[512];
127 SAFE_STRNCPY(colored_header_buf, colored_header_ptr, sizeof(colored_header_buf));
128 colored_offset += safe_snprintf(colored_buffer + colored_offset, sizeof(colored_buffer) - (size_t)colored_offset,
129 "%s\n", colored_header_buf);
130 }
131
132 // Add plain label header for log file
133 plain_offset +=
134 safe_snprintf(plain_buffer + plain_offset, sizeof(plain_buffer) - (size_t)plain_offset, "%s\n", label);
135
136 // Build backtrace frames with colored output for terminal, plain for log
137 int frame_num = 0;
138 for (int i = start; i < end && i < count && colored_offset < (int)sizeof(colored_buffer) - 512; i++) {
139 // Safely access symbols[i] - symbols array might be smaller than count or have NULL entries
140 const char *symbol = NULL;
141 if (symbols && symbols[i]) {
142 symbol = symbols[i];
143 } else {
144 symbol = "???";
145 }
146
147 // Skip frame if filter says to
148 if (filter && filter(symbol)) {
149 continue;
150 }
151
152 // Build frame number string
153 char frame_num_str[16];
154 safe_snprintf(frame_num_str, sizeof(frame_num_str), "%d", frame_num);
155
156 // Get colored frame number - copy to temp buffer to avoid rotating buffer issues
157 const char *colored_num_ptr = colored_string(LOG_COLOR_GREY, frame_num_str);
158 char colored_num_buf[256];
159 SAFE_STRNCPY(colored_num_buf, colored_num_ptr, sizeof(colored_num_buf));
160
161 // Parse symbol to extract parts for selective coloring
162 // Format: "[binary_name] function_name() (file:line)"
163 char colored_symbol[2048] = {0};
164 const char *s = symbol;
165 int colored_sym_offset = 0;
166
167 // Color binary name between brackets
168 if (*s == '[') {
169 colored_sym_offset +=
170 safe_snprintf(colored_symbol + colored_sym_offset, sizeof(colored_symbol) - colored_sym_offset, "[");
171 s++;
172 // Find closing bracket
173 const char *bracket_end = strchr(s, ']');
174 if (bracket_end) {
175 int bin_len = bracket_end - s;
176 char bin_name[512];
177 size_t bin_copy_len = MIN((size_t)bin_len, sizeof(bin_name) - 1);
178 SAFE_MEMCPY(bin_name, sizeof(bin_name), s, bin_copy_len);
179 bin_name[bin_copy_len] = '\0';
180 const char *colored_bin_ptr = colored_string(LOG_COLOR_DEBUG, bin_name);
181 char colored_bin_buf[512];
182 SAFE_STRNCPY(colored_bin_buf, colored_bin_ptr, sizeof(colored_bin_buf));
183 colored_sym_offset += safe_snprintf(colored_symbol + colored_sym_offset,
184 sizeof(colored_symbol) - colored_sym_offset, "%s", colored_bin_buf);
185 colored_sym_offset +=
186 safe_snprintf(colored_symbol + colored_sym_offset, sizeof(colored_symbol) - colored_sym_offset, "] ");
187 s = bracket_end + 1;
188 }
189 }
190
191 // Skip leading spaces after bracket
192 while (*s && *s == ' ')
193 s++;
194
195 // Parse: could be "function() (file:line)" or "file:line (unresolved)"
196 const char *paren_start = strchr(s, '(');
197
198 // Detect format: if there's a colon before the first paren, it's "file:line (description)"
199 const char *colon_pos = strchr(s, ':');
200 if (colon_pos && paren_start && colon_pos < paren_start) {
201 // Format: "file:line (unresolved function)" - rearrange to "(unresolved) (file:line)"
202 // Extract file:line part (trim trailing spaces)
203 int file_len = paren_start - s;
204 while (file_len > 0 && s[file_len - 1] == ' ')
205 file_len--;
206 char file_part[512];
207 size_t file_copy_len = MIN((size_t)file_len, sizeof(file_part) - 1);
208 SAFE_MEMCPY(file_part, sizeof(file_part), s, file_copy_len);
209 file_part[file_copy_len] = '\0';
210
211 // Extract description content (without parens)
212 const char *paren_end = strchr(paren_start, ')');
213 int desc_content_len = paren_end ? (int)(paren_end - paren_start - 1) : 0;
214 char desc_content[512];
215 size_t desc_copy_len = MIN((size_t)desc_content_len, sizeof(desc_content) - 1);
216 SAFE_MEMCPY(desc_content, sizeof(desc_content), paren_start + 1, desc_copy_len);
217 desc_content[desc_copy_len] = '\0';
218
219 const char *colored_desc_ptr = colored_string(LOG_COLOR_ERROR, desc_content);
220 char colored_desc_buf[512];
221 SAFE_STRNCPY(colored_desc_buf, colored_desc_ptr, sizeof(colored_desc_buf));
222 colored_sym_offset += safe_snprintf(colored_symbol + colored_sym_offset,
223 sizeof(colored_symbol) - colored_sym_offset, "(%s)", colored_desc_buf);
224
225 // Now color file:line in parens
226 // Skip leading spaces in file_part
227 const char *file_start = file_part;
228 while (*file_start && *file_start == ' ')
229 file_start++;
230
231 char *file_colon = strchr(file_start, ':');
232 if (file_colon) {
233 int filename_len = file_colon - file_start;
234 char filename[512];
235 size_t filename_copy_len = MIN((size_t)filename_len, sizeof(filename) - 1);
236 SAFE_MEMCPY(filename, sizeof(filename), file_start, filename_copy_len);
237 filename[filename_copy_len] = '\0';
238
239 const char *colored_file_ptr = colored_string(LOG_COLOR_DEBUG, filename);
240 char colored_file_buf[512];
241 SAFE_STRNCPY(colored_file_buf, colored_file_ptr, sizeof(colored_file_buf));
242
243 const char *line_num = file_colon + 1;
244 const char *colored_line_ptr = colored_string(LOG_COLOR_GREY, line_num);
245 char colored_line_buf[512];
246 SAFE_STRNCPY(colored_line_buf, colored_line_ptr, sizeof(colored_line_buf));
247
248 colored_sym_offset +=
249 safe_snprintf(colored_symbol + colored_sym_offset, sizeof(colored_symbol) - colored_sym_offset, " (%s:%s)",
250 colored_file_buf, colored_line_buf);
251 }
252 } else if (paren_start) {
253 // Format: "function() (file:line)"
254 int func_len = paren_start - s;
255 char func_name[512];
256 size_t func_copy_len = MIN((size_t)func_len, sizeof(func_name) - 1);
257 SAFE_MEMCPY(func_name, sizeof(func_name), s, func_copy_len);
258 func_name[func_copy_len] = '\0';
259
260 const char *colored_func_ptr = colored_string(LOG_COLOR_DEV, func_name);
261 char colored_func_buf[512];
262 SAFE_STRNCPY(colored_func_buf, colored_func_ptr, sizeof(colored_func_buf));
263 colored_sym_offset += safe_snprintf(colored_symbol + colored_sym_offset,
264 sizeof(colored_symbol) - colored_sym_offset, "%s()", colored_func_buf);
265
266 // Find file:line in second set of parens
267 s = paren_start + 1;
268 while (*s && *s != '(')
269 s++;
270
271 if (*s == '(') {
272 const char *file_paren_end = strchr(s, ')');
273 if (file_paren_end) {
274 colored_sym_offset +=
275 safe_snprintf(colored_symbol + colored_sym_offset, sizeof(colored_symbol) - colored_sym_offset, " (");
276 int file_len = file_paren_end - s - 1;
277 char file_part[512];
278 size_t file_copy_len = MIN((size_t)file_len, sizeof(file_part) - 1);
279 SAFE_MEMCPY(file_part, sizeof(file_part), s + 1, file_copy_len);
280 file_part[file_copy_len] = '\0';
281
282 // Skip leading spaces in file_part
283 const char *file_start = file_part;
284 while (*file_start && *file_start == ' ')
285 file_start++;
286
287 char *colon_pos = strchr(file_start, ':');
288 if (colon_pos) {
289 int filename_len = colon_pos - file_start;
290 char filename[512];
291 size_t filename_copy_len = MIN((size_t)filename_len, sizeof(filename) - 1);
292 SAFE_MEMCPY(filename, sizeof(filename), file_start, filename_copy_len);
293 filename[filename_copy_len] = '\0';
294
295 const char *colored_file_ptr = colored_string(LOG_COLOR_DEBUG, filename);
296 char colored_file_buf[512];
297 SAFE_STRNCPY(colored_file_buf, colored_file_ptr, sizeof(colored_file_buf));
298
299 const char *line_num = colon_pos + 1;
300 const char *colored_line_ptr = colored_string(LOG_COLOR_GREY, line_num);
301 char colored_line_buf[512];
302 SAFE_STRNCPY(colored_line_buf, colored_line_ptr, sizeof(colored_line_buf));
303
304 colored_sym_offset +=
305 safe_snprintf(colored_symbol + colored_sym_offset, sizeof(colored_symbol) - colored_sym_offset, "%s:%s",
306 colored_file_buf, colored_line_buf);
307 }
308 colored_sym_offset +=
309 safe_snprintf(colored_symbol + colored_sym_offset, sizeof(colored_symbol) - colored_sym_offset, ")");
310 }
311 }
312 } else {
313 // No parens, likely a hex address - color with FATAL
314 const char *colored_addr_ptr = colored_string(LOG_COLOR_FATAL, s);
315 char colored_addr_buf[512];
316 SAFE_STRNCPY(colored_addr_buf, colored_addr_ptr, sizeof(colored_addr_buf));
317 colored_sym_offset += safe_snprintf(colored_symbol + colored_sym_offset,
318 sizeof(colored_symbol) - colored_sym_offset, "%s", colored_addr_buf);
319 }
320
321 // Format colored buffer: " [colored_num] colored_symbol\n"
322 colored_offset += safe_snprintf(colored_buffer + colored_offset, sizeof(colored_buffer) - (size_t)colored_offset,
323 " [%s] %s\n", colored_num_buf, colored_symbol);
324
325 // Format plain buffer: " [num] symbol\n"
326 plain_offset += safe_snprintf(plain_buffer + plain_offset, sizeof(plain_buffer) - (size_t)plain_offset,
327 " [%d] %s\n", frame_num, symbol);
328 frame_num++;
329 }
330
331 // Output backtrace through logging system (no direct fprintf to avoid ANSI codes in captured output)
332 log_warn("%s", plain_buffer);
333 log_file_msg("%s", plain_buffer);
334}
335
336void backtrace_print_many(const char *label, const backtrace_t *bts, int count) {
337 if (!bts || count <= 0) {
338 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: bts is NULL or count <= 0");
339 return;
340 }
341 for (int i = 0; i < count; i++) {
342 backtrace_print(label, &bts[i], 0, 0, NULL);
343 }
344}
345
346int backtrace_format(char *buf, size_t buf_size, const char *label, const backtrace_t *bt, int skip_frames,
347 int max_frames, backtrace_frame_filter_t filter) {
348 if (!buf || buf_size == 0 || !bt || !bt->symbols || bt->count <= 0) {
349 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
350 return -1;
351 }
352
353 // Capture count and symbols once to avoid TOCTOU race with other threads
354 int count = bt->count;
355 char **symbols = bt->symbols;
356
357 int offset = 0;
358
359 // Add label
360 offset += safe_snprintf(buf + offset, buf_size - (size_t)offset, "%s\n", label);
361
362 // Calculate frame limits
363 int start = skip_frames;
364 int end = count;
365 if (max_frames > 0 && (start + max_frames) < end) {
366 end = start + max_frames;
367 }
368
369 // Add frames
370 int frame_num = 0;
371 for (int i = start; i < end && i < count && offset < (int)buf_size - 128; i++) {
372 // Safely access symbols[i] - symbols array might be smaller than count or have NULL entries
373 const char *symbol = NULL;
374 if (symbols && symbols[i]) {
375 symbol = symbols[i];
376 } else {
377 symbol = "???";
378 }
379
380 // Skip frame if filter says to
381 if (filter && filter(symbol)) {
382 continue;
383 }
384
385 offset += safe_snprintf(buf + offset, buf_size - (size_t)offset, " [%d] %s\n", frame_num, symbol);
386 frame_num++;
387 }
388
389 return offset;
390}
391
392void backtrace_log_frames(int num_frames, int skip_frames) {
393 backtrace_t bt;
395 backtrace_print("Backtrace", &bt, skip_frames, num_frames, NULL);
396 backtrace_t_free(&bt);
397}
398
399void backtrace_print_symbols(const char *label, char **symbols, int count, int skip_frames, int max_frames,
401 backtrace_t bt = {0};
402 bt.symbols = symbols;
403 bt.count = count;
404 backtrace_print(label, &bt, skip_frames, max_frames, filter);
405}
406
407#endif // NDEBUG
408
409// ============================================================================
410// Release Build Stubs (NDEBUG)
411// ============================================================================
412
413#ifdef NDEBUG
414
416 (void)bt;
417 // No-op in release builds
418}
419
421 (void)bt;
422 // No-op in release builds
423}
424
425void backtrace_print(const char *label, const backtrace_t *bt, int skip_frames, int max_frames,
427 (void)label;
428 (void)bt;
429 (void)skip_frames;
430 (void)max_frames;
431 (void)filter;
432 // No-op in release builds
433}
434
435void backtrace_print_many(const char *label, const backtrace_t *bts, int count) {
436 (void)label;
437 (void)bts;
438 (void)count;
439 // No-op in release builds
440}
441
442int backtrace_format(char *buf, size_t buf_size, const char *label, const backtrace_t *bt, int skip_frames,
443 int max_frames, backtrace_frame_filter_t filter) {
444 (void)buf;
445 (void)buf_size;
446 (void)label;
447 (void)bt;
448 (void)skip_frames;
449 (void)max_frames;
450 (void)filter;
451 // No-op in release builds
452 return 0;
453}
454
455void backtrace_log_frames(int num_frames, int skip_frames) {
456 (void)num_frames;
457 (void)skip_frames;
458 // No-op in release builds
459}
460
461#endif
void backtrace_t_free(backtrace_t *bt)
Free backtrace symbols.
void backtrace_capture_and_symbolize(backtrace_t *bt)
Capture and symbolize in one call.
void backtrace_capture(backtrace_t *bt)
Capture current stack into bt.
int backtrace_format(char *buf, size_t buf_size, const char *label, const backtrace_t *bt, int skip_frames, int max_frames, backtrace_frame_filter_t filter)
Format backtrace to buffer (plain ASCII, no colors)
void backtrace_print(const char *label, const backtrace_t *bt, int skip_frames, int max_frames, backtrace_frame_filter_t filter)
Print backtrace with dual output: colored stderr + plain log file.
void backtrace_print_many(const char *label, const backtrace_t *bts, int count)
Print multiple backtraces.
void backtrace_symbolize(backtrace_t *bt)
Symbolize bt->ptrs into bt->symbols.
📍 Backtrace capture, symbolization, and formatting
Error and exit codes - unified status values (0-255)
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
unsigned long long uint64_t
Definition common.h:59
#define SAFE_MEMCPY(dest, dest_size, src, count)
Definition common.h:468
void backtrace_log_frames(int num_frames, int skip_frames)
Log N backtrace frames with function name, line number, and file.
void backtrace_print_symbols(const char *label, char **symbols, int count, int skip_frames, int max_frames, backtrace_frame_filter_t filter)
Print pre-resolved backtrace symbols with consistent formatting.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
@ ERROR_INVALID_PARAM
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
int log_template_apply(const log_template_t *format, char *buf, size_t buf_size, log_level_t level, const char *timestamp, const char *file, int line, const char *func, uint64_t tid, const char *message, bool use_colors, uint64_t time_nanoseconds)
Apply format to a log entry and write result to buffer.
Definition log/format.c:491
void log_file_msg(const char *fmt,...)
Log to file only, no stderr output.
Definition log/log.c:1467
void * log_get_template(void)
Get the current log format template.
Definition log/log.c:2156
@ LOG_COLOR_GREY
Definition log/log.h:137
@ LOG_COLOR_FATAL
Definition log/log.h:136
@ LOG_COLOR_ERROR
Definition log/log.h:135
@ LOG_COLOR_DEV
Definition log/log.h:131
@ LOG_COLOR_DEBUG
Definition log/log.h:132
@ LOG_COLOR_WARN
Definition log/log.h:134
int platform_backtrace(void **buffer, int size)
Get a backtrace of the current call stack.
Definition util.c:16
void platform_backtrace_symbols_destroy(char **symbols)
Free symbol array from platform_backtrace_symbols()
Definition util.c:28
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148
asciichat_error_t platform_localtime(const time_t *timer, struct tm *result)
Platform-safe localtime wrapper.
Definition util.c:50
uint64_t platform_get_monotonic_time_us(void)
Get monotonic time in microseconds.
char ** platform_backtrace_symbols(void *const *buffer, int size)
Convert backtrace addresses to symbol names.
Definition util.c:22
const char * colored_string(log_color_t color, const char *text)
Build a colored string for terminal output.
#define asciichat_thread_self()
📝 Logging API with multiple log levels and terminal output control
#define MIN(a, b)
Definition param.h:6
Cross-platform backtrace and stack trace capture.
bool(* backtrace_frame_filter_t)(const char *frame)
void * ptrs[32]
Raw return addresses.
int count
Number of frames captured.
bool tried_symbolize
Track if we've already tried to symbolize (prevents duplicate work)
char ** symbols
Symbolized strings (NULL until backtrace_symbolize called)
Compiled log format ready for use in log_template_apply()
Definition log/format.h:66
Cross-platform system functions interface for ascii-chat.
⏱️ High-precision timing utilities using sokol_time.h and uthash
#define LOG_WARN
Definition types.h:41
🔤 String Manipulation and Shell Escaping Utilities