Cthulhu  0.2.10
Cthulhu compiler collection
backtrace.c
Go to the documentation of this file.
1 // SPDX-License-Identifier: LGPL-3.0-only
2 
3 #include "format/backtrace.h"
4 #include "common.h"
5 
6 #include "backtrace/backtrace.h"
7 
8 #include "io/io.h"
9 
10 #include "arena/arena.h"
11 
12 #include "std/str.h"
13 #include "std/typed/vector.h"
14 
15 #include "base/panic.h"
16 #include "base/util.h"
17 
18 #include "core/macros.h"
19 
20 #define COLOUR_ADDR eColourRed
21 #define COLOUR_SYMBOL eColourBlue
22 #define COLOUR_INDEX eColourCyan
23 #define COLOUR_RECURSE eColourYellow
24 #define COLOUR_LINE eColourWhite
25 
26 static const char *const kUnknownSymbol = "<unknown>";
27 
28 typedef struct bt_report_t
29 {
32 
36 } bt_report_t;
37 
39 typedef struct entry_t
40 {
43 
46  char *file;
47 
50  char *symbol;
51 
54  size_t line;
55 
58 } entry_t;
59 
60 // state for a backtrace format run
61 typedef struct backtrace_t
62 {
65 
68 
72 
76 
78  size_t index_align;
79 
81  size_t symbol_align;
82 
85 
88 } backtrace_t;
89 
92 typedef struct collapsed_t
93 {
95  unsigned first;
96 
98  unsigned last;
99 
101  unsigned repeat;
102 } collapsed_t;
103 
104 #define IS_COLLAPSED_RANGE(it) ((it).repeat != 0)
105 
106 static const entry_t *get_collapsed_entry(const backtrace_t *self, collapsed_t range)
107 {
108  CTASSERTF(!IS_COLLAPSED_RANGE(range), "range is not a single frame");
109 
110  return typevec_offset(self->entries, range.first);
111 }
112 
113 // stacktrace collapsing
114 
115 // this approach is O(n^2) but it is simple
116 // we scan along the trace and collect the current sequence
117 // we collect the current sequence into a buffer, and if we reach the end we discard it
118 // and start again with the next frame
119 // we compare the current symbol with the start of the sequence, if they match we start collecting
120 // we store the collapsed frames and unmatched frames in a new buffer
121 // the final buffer is what we print
122 
126 static unsigned match_sequence(
127  const typevec_t *frames,
128  unsigned scan_start,
129  unsigned seq_start,
130  collapsed_t *collapsed
131 )
132 {
133  size_t seq_len = scan_start - seq_start;
134  size_t ent_len = typevec_len(frames);
135 
136  // see how many times the sequence repeats
137  // if it doesnt repeat then return false
138  size_t count = 0;
139  size_t j = 0;
140  for (size_t i = scan_start; i < ent_len; i++)
141  {
142  const entry_t *a = typevec_offset(frames, i);
143  const entry_t *b = typevec_offset(frames, j + seq_start);
144 
145  if (a->address != b->address)
146  break;
147 
148  j += 1;
149 
150  if (j == seq_len)
151  {
152  count += 1;
153  j = 0;
154  }
155  }
156 
157  if (count == 0)
158  return 0; // no match
159 
160  // we have a match
161  // we need to store the sequence and the repeat count
162 
163  collapsed->first = (unsigned)(scan_start);
164  collapsed->last = (unsigned)(scan_start + seq_len - 1);
165  collapsed->repeat = (unsigned)(count);
166  return (unsigned)(count * seq_len);
167 }
168 
169 static unsigned collapse_frame(const typevec_t *frames, unsigned start, collapsed_t *collapsed)
170 {
171  size_t len = typevec_len(frames);
172  if (start <= len)
173  {
174  // push the first entry
175  const entry_t *head = typevec_offset(frames, start);
176 
177  for (unsigned i = start + 1; i < len; i++)
178  {
179  const entry_t *frame = typevec_offset(frames, i);
180  CTASSERTF(frame != NULL, "entry at %u is NULL", i);
181 
182  // we might have a match
183  if (frame->address == head->address)
184  {
185  unsigned count = match_sequence(frames, i, start, collapsed);
186 
187  // if we matched more then we're done
188  if (count != 0) return count;
189 
190  // otherwise break out and push just the single entry
191  break;
192  }
193 
194  // we dont have a match, so start again
195  }
196  }
197 
198  // if we get here then no repeating sequence was matched
199  collapsed->first = start;
200  collapsed->last = start;
201  collapsed->repeat = 0;
202  return 1;
203 }
204 
205 static typevec_t *collapse_frames(const typevec_t *frames, arena_t *arena)
206 {
207  size_t len = typevec_len(frames);
208  typevec_t *result = typevec_new(sizeof(collapsed_t), len, arena);
209 
210  for (unsigned i = 0; i < len; i++)
211  {
212  collapsed_t collapsed = {0};
213  unsigned count = collapse_frame(frames, i, &collapsed);
214 
215  CTASSERTF(count > 0, "count of 0 at %u", i);
216 
217  typevec_push(result, &collapsed);
218 
219  // skip the frames we collapsed
220  i += count - 1;
221  }
222 
223  return result;
224 }
225 
226 // text formatting
227 
228 // special case recursions with only a single frame repeated to the form of
229 //
230 // symbol x repeat (file:line)
231 //
232 // all other recursions are printed as
233 //
234 // repeats N times
235 // | [1] symbol (file:line)
236 // | [2] symbol (file:line)
237 // | [N] symbol (file:line)
238 
239 // the length of a pointer in hex, plus the 0x prefix
240 #define PTR_TEXT_LEN (2 + 2 * sizeof(void*))
241 
242 // get the longest symbol in a sequence of entries
243 static size_t get_longest_symbol(const typevec_t *frames, collapsed_t range)
244 {
245  CTASSERTF(IS_COLLAPSED_RANGE(range), "range is not a single frame");
246  size_t largest = 0;
247 
248  for (size_t i = range.first; i <= range.last; i++)
249  {
250  const entry_t *entry = typevec_offset(frames, i);
251  CTASSERTF(entry != NULL, "entry at %zu is NULL", i);
252 
253  size_t width = ctu_strlen(entry->symbol);
254  largest = CT_MAX(width, largest);
255  }
256 
257  return largest;
258 }
259 
260 typedef struct symbol_match_info_t
261 {
262  // the largest symbol in this sequence
264 
265  // how many entries in this sequence before needing to recalculate
266  // the largest symbol
267  size_t count;
269 
270 static symbol_match_info_t get_largest_collapsed_symbol(const typevec_t *frames, const collapsed_t *entries, size_t len)
271 {
272  CTASSERT(entries != NULL);
273 
274  size_t largest = ctu_strlen(kUnknownSymbol);
275 
276  for (size_t i = 0; i < len; i++)
277  {
278  const collapsed_t *range = entries + i;
279  CTASSERTF(range != NULL, "entry at %zu is NULL", i);
280 
281  if (IS_COLLAPSED_RANGE(*range))
282  {
283  symbol_match_info_t info = {
284  .largest_symbol = largest,
285  .count = i
286  };
287 
288  return info;
289  }
290 
291  const entry_t *entry = typevec_offset(frames, range->first);
292 
293  size_t width = ctu_strlen(entry->symbol);
294  largest = CT_MAX(width, largest);
295  }
296 
297  symbol_match_info_t info = {
298  .largest_symbol = largest,
299  .count = len
300  };
301  return info;
302 }
303 
304 static const char *get_file_path(backtrace_t *pass, const char *file)
305 {
306  fmt_backtrace_t config = pass->options;
307  const char *path = config.project_source_path;
308  size_t len = pass->source_root_len;
309 
310  if (path == NULL)
311  return file;
312 
313  if (str_startswith(file, path))
314  return file + len;
315 
316  return file;
317 }
318 
319 static const char *fmt_entry_location(backtrace_t *pass, const entry_t *entry)
320 {
321  fmt_backtrace_t config = pass->options;
322 
323  bt_resolve_t resolved = entry->info;
324 
325  if (resolved & eResolveFile)
326  {
327  const char *file = get_file_path(pass, entry->file);
328 
329  if (resolved & eResolveLine)
330  {
331  where_t where = {
332  .first_line = entry->line,
333  };
334 
335  source_config_t source_config = {
336  .context = pass->format_context,
337  .colour = eColourDefault,
338  .heading_style = config.header,
339  .zero_indexed_lines = config.config & eBtZeroIndexedLines,
340  };
341 
342  char *out = fmt_source_location(source_config, file, where);
343  return str_format(pass->format_context.arena, "(%s)", out);
344  }
345 
346  return file;
347  }
348 
349  return colour_format(pass->format_context, COLOUR_ADDR, "0x%" BT_PRI_ADDRESS, entry->address);
350 }
351 
352 static char *fmt_entry(backtrace_t *pass, size_t symbol_align, const entry_t *entry)
353 {
354  fmt_backtrace_t config = pass->options;
355  print_options_t options = config.options;
356 
357  bt_resolve_t resolved = entry->info;
358 
359  // we only need the @ seperator if we only have the address
360  bool needs_seperator = !((resolved & eResolveFile) && (resolved & eResolveLine));
361  const char *where = fmt_entry_location(pass, entry);
362  const char *name = (resolved & eResolveName) ? entry->symbol : kUnknownSymbol;
363 
364  char *it = fmt_right_align(options.arena, symbol_align, "%s", name);
365  char *coloured = colour_text(pass->format_context, COLOUR_SYMBOL, it);
366 
367  if (needs_seperator)
368  return str_format(options.arena, "%s @ %s", coloured, where);
369 
370  return str_format(options.arena, "%s %s", coloured, where);
371 }
372 
373 static char *fmt_index(backtrace_t *pass, size_t index)
374 {
375  fmt_backtrace_t config = pass->options;
376  print_options_t options = config.options;
377 
378  char *idx = fmt_left_align(options.arena, pass->index_align, "%zu", index);
379  return colour_format(pass->format_context, COLOUR_INDEX, "[%s]", idx);
380 }
381 
382 static void print_single_frame(backtrace_t *pass, size_t index, const entry_t *entry)
383 {
384  fmt_backtrace_t options = pass->options;
385  print_options_t base = options.options;
386  char *idx = fmt_index(pass, index);
387  io_printf(base.io, "%s %s\n", idx, fmt_entry(pass, pass->symbol_align, entry));
388 }
389 
390 static void print_frame_sequence(backtrace_t *pass, size_t index, collapsed_t collapsed)
391 {
392  fmt_backtrace_t options = pass->options;
393  print_options_t base = options.options;
394 
395  size_t largest = get_longest_symbol(pass->entries, collapsed);
396 
397  char *idx = fmt_index(pass, index);
398  char *coloured = colour_format(pass->format_context, COLOUR_RECURSE, "%u", collapsed.repeat);
399  io_printf(base.io, "%s repeats %s times\n", idx, coloured);
400 
401  for (size_t i = collapsed.first; i <= collapsed.last; i++)
402  {
403  const entry_t *entry = typevec_offset(pass->entries, i);
404  CTASSERTF(entry != NULL, "entry at %zu is NULL", i);
405 
406  char *it = fmt_entry(pass, largest, entry);
407  char *inner = colour_format(pass->format_context, COLOUR_RECURSE, "[%zu]", i);
408  io_printf(base.io, " - %s %s\n", inner, it);
409  }
410 }
411 
412 static void print_collapsed(backtrace_t *pass, size_t index, collapsed_t collapsed)
413 {
414  if (IS_COLLAPSED_RANGE(collapsed))
415  {
416  print_frame_sequence(pass, index, collapsed);
417  }
418  else
419  {
420  const entry_t *entry = get_collapsed_entry(pass, collapsed);
421  print_single_frame(pass, index, entry);
422  }
423 }
424 
425 STA_DECL
427 {
428  CTASSERT(report != NULL);
429 
430  print_options_t options = fmt.options;
431 
432  typevec_t *entries = collapse_frames(report->frames, options.arena);
433 
434  collapsed_t *collapsed = typevec_data(entries);
435  size_t frame_count = typevec_len(entries);
436 
437  symbol_match_info_t symbol_align = get_largest_collapsed_symbol(report->frames, collapsed, frame_count);
438  int align = get_num_width(frame_count);
439 
440  backtrace_t pass = {
441  .options = fmt,
442  .format_context = format_context_make(options),
443  .entries = report->frames,
444  .frames = entries,
445  .index_align = align,
446  .symbol_align = symbol_align.largest_symbol,
447  .arena = options.arena,
448  };
449 
450  const char *source_path = fmt.project_source_path;
451  if (source_path)
452  {
453  size_t len = ctu_strlen(source_path);
454  if (source_path[len - 1] != '/' && source_path[len - 1] != '\\')
455  len += 1;
456  pass.source_root_len = len;
457  }
458 
459  for (size_t i = 0; i < frame_count; i++)
460  {
461  const collapsed_t *frame = typevec_offset(entries, i);
462  CTASSERTF(frame != NULL, "collapsed at %zu is NULL", i);
463 
464  // this logic handles aligning symbol names on a per region basis
465  // a region is defined as consecutive frames that were not recursive
466  if (symbol_align.count > 0)
467  {
468  symbol_align.count -= 1;
469  }
470  else
471  {
472  if (!IS_COLLAPSED_RANGE(*frame))
473  {
474  size_t remaining = frame_count - i;
475  symbol_align = get_largest_collapsed_symbol(report->frames, frame, remaining);
476  pass.symbol_align = symbol_align.largest_symbol;
477  }
478  }
479 
480  print_collapsed(&pass, i, *frame);
481  }
482 }
483 
484 STA_DECL
486 {
487  CTASSERT(arena != NULL);
488 
489  bt_report_t *report = ARENA_MALLOC(sizeof(bt_report_t), "bt_report", NULL, arena);
490  report->arena = arena;
491  report->frames = typevec_new(sizeof(entry_t), 4, arena);
492 
493  ARENA_IDENTIFY(report->frames, "entries", report, arena);
494 
495  return report;
496 }
497 
498 static void read_stacktrace_frame(bt_address_t frame, void *user)
499 {
500  bt_report_add(user, frame);
501 }
502 
503 STA_DECL
505 {
506  bt_report_t *report = bt_report_new(arena);
507 
508  bt_read(read_stacktrace_frame, report);
509 
510  return report;
511 }
512 
513 STA_DECL
515 {
516  CTASSERT(report != NULL);
517 
518  char path[1024];
519  char name[1024];
520 
521  bt_symbol_t symbol = {
522  .name = text_make(name, sizeof(name)),
523  .path = text_make(path, sizeof(path)),
524  };
525  bt_resolve_t info = bt_resolve_symbol(frame, &symbol);
526 
527  entry_t entry = {
528  .info = info,
529  .file = arena_strndup(symbol.path.text, symbol.path.length, report->arena),
530  .symbol = arena_strndup(symbol.name.text, symbol.name.length, report->arena),
531  .line = symbol.line,
532  .address = frame,
533  };
534 
535  typevec_push(report->frames, &entry);
536 }
#define COLOUR_RECURSE
Definition: backtrace.c:23
#define COLOUR_SYMBOL
Definition: backtrace.c:21
#define COLOUR_INDEX
Definition: backtrace.c:22
#define COLOUR_ADDR
Definition: backtrace.c:20
#define IS_COLLAPSED_RANGE(it)
Definition: backtrace.c:104
STA_DECL char * colour_format(format_context_t context, colour_t idx, const char *fmt,...)
Definition: colour.c:65
#define STA_DECL
sal2 annotation on function implementations to copy annotations from the declaration
uint_least64_t bt_address_t
an address of a symbol
Definition: backtrace.h:26
#define BT_PRI_ADDRESS
format specifier for bt_address_t
Definition: backtrace.h:29
CT_BACKTRACE_API bt_resolve_t bt_resolve_symbol(bt_address_t frame, bt_symbol_t *symbol)
resolve a frame to a symbol
Definition: common.c:14
bt_resolve_t
how much of a frame was reconstructed
Definition: backtrace.h:47
CT_BACKTRACE_API void bt_read(bt_trace_t callback, void *user)
get a backtrace from the current location using a callback
Definition: common.c:35
@ eResolveLine
the line number was found
Definition: backtrace.h:53
@ eResolveName
the symbol name was found
Definition: backtrace.h:57
@ eResolveFile
the file path was found
Definition: backtrace.h:64
CT_CONSTFN CT_BASE_API text_t text_make(STA_READS(length) char *text, size_t length)
create a new owning text array text must be a valid string at least length bytes long
CT_NODISCARD CT_PUREFN CT_BASE_API size_t ctu_strlen(const char *str)
get the length of a string not including the null terminator equivalent to strlen but with safety che...
Definition: util.c:87
STA_RET_STRING colour_t idx
Definition: colour.h:98
STA_RET_STRING CT_FORMAT_API char * colour_text(format_context_t context, colour_t idx, const char *text)
add colour to a string
Definition: colour.c:56
@ eColourDefault
Definition: colour.h:32
STA_DECL bt_report_t * bt_report_collect(arena_t *arena)
collect a backtrace into a report this is equivalent to calling bt_report_new() and then bt_report_ad...
Definition: backtrace.c:504
STA_DECL bt_report_t * bt_report_new(arena_t *arena)
create a new backtrace report
Definition: backtrace.c:485
STA_DECL void bt_report_add(bt_report_t *report, bt_address_t frame)
add a frame to a backtrace report
Definition: backtrace.c:514
STA_DECL void fmt_backtrace(fmt_backtrace_t fmt, bt_report_t *report)
print a backtrace report
Definition: backtrace.c:426
CT_IO_API size_t io_printf(io_t *io, STA_FORMAT_STRING const char *fmt,...)
printf to an io object
#define CT_MAX(L, R)
Definition: macros.h:34
#define ARENA_IDENTIFY(ptr, name, parent, arena)
rename and reparent a pointer in a custom allocator
Definition: arena.h:409
CT_NODISCARD CT_ARENA_API char * arena_strndup(STA_READS(len) const char *str, size_t len, arena_t *arena)
allocate a copy of a string with a maximum length from a custom allocator
#define ARENA_MALLOC(size, name, parent, arena)
allocate memory from a custom allocator
Definition: arena.h:392
#define CTASSERT(expr)
assert a condition, prints the condition as a message
Definition: panic.h:130
#define CTASSERTF(expr,...)
assert a condition with a message and optional format arguments
Definition: panic.h:116
CT_NODISCARD STA_FORMAT_STRING const char * fmt
Definition: str.h:68
CT_NODISCARD CT_PUREFN CT_STD_API bool str_startswith(const char *str, const char *prefix)
see if a string starts with a prefix
Definition: str.c:233
CT_STD_API void * typevec_push(typevec_t *vec, const void *src)
push a value onto the vector
Definition: vector.c:156
CT_NODISCARD CT_PUREFN CT_STD_API size_t typevec_len(const typevec_t *vec)
get the length of a vector
Definition: vector.c:120
CT_NODISCARD CT_PUREFN CT_STD_API void * typevec_data(const typevec_t *vec)
get a pointer to the underlying data
Definition: vector.c:199
CT_NODISCARD CT_STD_API typevec_t * typevec_new(size_t width, size_t len, arena_t *arena)
create a new typed vector on the heap
Definition: vector.c:77
CT_NODISCARD CT_PUREFN CT_STD_API void * typevec_offset(const typevec_t *vec, size_t index)
get a pointer to the value at the given index
Definition: vector.c:191
STA_DECL char * str_format(arena_t *arena, const char *fmt,...)
Definition: str.c:97
an allocator object
Definition: arena.h:86
size_t index_align
the alignment of the frame index numbers
Definition: backtrace.c:78
typevec_t * frames
Definition: backtrace.c:75
format_context_t format_context
format context
Definition: backtrace.c:67
arena_t * arena
arena
Definition: backtrace.c:87
typevec_t * entries
Definition: backtrace.c:71
fmt_backtrace_t options
the user provided options
Definition: backtrace.c:64
size_t symbol_align
the current alignment of the symbol names
Definition: backtrace.c:81
size_t source_root_len
the length of the source root
Definition: backtrace.c:84
a backtrace report context
Definition: backtrace.c:29
typevec_t * frames
all entries
Definition: backtrace.c:35
arena_t * arena
memory pool
Definition: backtrace.c:31
a symbol
Definition: backtrace.h:33
text_t name
a buffer to hold the name
Definition: backtrace.h:39
text_t path
a buffer to hold the path to the file
Definition: backtrace.h:42
source_line_t line
the line number
Definition: backtrace.h:36
a single possibly collapsed frame this is a span covering (first, last) * repeat frames
Definition: backtrace.c:93
unsigned repeat
how many times this sequence repeats
Definition: backtrace.c:101
unsigned first
index of the first frame in this sequence
Definition: backtrace.c:95
unsigned last
index of the last frame in this sequence
Definition: backtrace.c:98
a single backtrace entry
Definition: backtrace.c:40
char * symbol
the symbol name
Definition: backtrace.c:50
bt_resolve_t info
what symbol info has been resolved by the backend?
Definition: backtrace.c:42
char * file
the file name
Definition: backtrace.c:46
size_t line
the line number
Definition: backtrace.c:54
bt_address_t address
the address of the frame
Definition: backtrace.c:57
printing options for a stacktrace
Definition: backtrace.h:36
print_options_t options
basic print options
Definition: backtrace.h:38
const char * project_source_path
path to the root of the projects source directory if this is set then paths that are under this direc...
Definition: backtrace.h:49
fmt_backtrace_options_t config
configuration flags
Definition: backtrace.h:44
generic print options
Definition: format.h:35
arena_t * arena
temporary arena
Definition: format.h:37
io_t * io
io buffer
Definition: format.h:40
a formatting context when using colours
Definition: colour.h:46
arena_t * arena
Definition: colour.h:47
format_context_t context
Definition: common.h:31
size_t length
the number of characters in the text
Definition: text.h:19
A vector with a fixed type size.
Definition: vector.h:24
a location inside a scanner locations are inclusive and 0-based
Definition: where.h:23
ctu_line_t first_line
the first line of the location
Definition: where.h:25
char * fmt_source_location(source_config_t config, const char *path, where_t where)
Definition: common.c:124
char * fmt_left_align(arena_t *arena, size_t width, const char *fmt,...)
Definition: common.c:36
char * fmt_right_align(arena_t *arena, size_t width, const char *fmt,...)
Definition: common.c:59
int get_num_width(size_t num)
get the width of a number if it were printed as base10
Definition: common.c:21
format_context_t format_context_make(print_options_t options)
Definition: common.c:114