symbolize.c (7144B)
1 #include <kit/core.h> 2 #include <stddef.h> 3 #include <stdint.h> 4 #include <string.h> 5 6 #include "driver.h" 7 #include "dwarfsym.h" 8 9 /* kit symbolize — annotate a __kit_print_backtrace stream in place. 10 * 11 * Where `addr2line` is a faithful clone of the GNU/LLVM "addresses in, 12 * file:line out" contract, `symbolize` matches kit's actual backtrace 13 * artifact: it reads the raw "#N 0x<hex>" lines that __kit_print_backtrace 14 * writes (rt/lib/stack/print_backtrace.c), finds the address on each line, 15 * resolves it through the same DWARF reader (kit_dwarf_func_at + 16 * kit_dwarf_addr_to_line, via driver/lib/dwarfsym), and rewrites the line as 17 * 18 * #0 0x401136 bt_leaf at addr2line_prog.c:51:3 19 * 20 * preserving the original "#N" framing that addr2line structurally can't keep. 21 * Lines with no recognizable 0x<hex> token pass through verbatim, so a mixed 22 * log (banner + frames) survives unharmed. See doc/plan/BACKTRACE.md (WS5). */ 23 24 #define SYM_TOOL "symbolize" 25 26 typedef struct SymOpts { 27 const char* exe_path; 28 int basenames; /* --basenames */ 29 } SymOpts; 30 31 /* Find the first "0x"/"0X"-prefixed hex run in [s, s+len) and decode it. 32 * Returns 1 and stores the value in *out when a token with at least one hex 33 * digit is found, else 0. Works on a non-NUL-terminated span (the line is a 34 * slice of the stdin buffer), so it never reads past `len`. */ 35 static int sym_line_addr(const char* s, size_t len, uint64_t* out) { 36 size_t i; 37 for (i = 0; i + 1 < len; ++i) { 38 if (s[i] == '0' && (s[i + 1] == 'x' || s[i + 1] == 'X')) { 39 uint64_t v = 0; 40 size_t j = i + 2; 41 int any = 0; 42 for (; j < len; ++j) { 43 char c = s[j]; 44 int d; 45 if (c >= '0' && c <= '9') 46 d = c - '0'; 47 else if (c >= 'a' && c <= 'f') 48 d = c - 'a' + 10; 49 else if (c >= 'A' && c <= 'F') 50 d = c - 'A' + 10; 51 else 52 break; 53 v = (v << 4) | (uint64_t)d; 54 any = 1; 55 } 56 if (any) { 57 *out = v; 58 return 1; 59 } 60 } 61 } 62 return 0; 63 } 64 65 /* Print " <func> at <file>:<line>[:<col>]" for a resolved address, using the 66 * "??" / "??:0" placeholders addr2line's pretty mode uses for the missing 67 * halves. Mirrors a2l_translate's pretty path so the two tools render an 68 * unresolved frame identically. */ 69 static void sym_emit_annotation(const KitDwarfResolve* loc, 70 const SymOpts* opts) { 71 driver_printf(" "); 72 if (loc->have_func) 73 driver_printf("%.*s at ", (int)loc->func.len, loc->func.s); 74 else 75 driver_printf("?? at "); 76 77 if (loc->have_line) { 78 const char* f = loc->file.s; 79 if (opts->basenames) f = driver_basename(f); 80 driver_printf("%s:%u", f, loc->line); 81 if (loc->col) driver_printf(":%u", loc->col); 82 } else { 83 driver_printf("??:0"); 84 } 85 } 86 87 void driver_help_symbolize(void) { 88 driver_printf( 89 "%.*s", 90 KIT_SLICE_ARG(KIT_SLICE_LIT( 91 "kit symbolize — annotate a kit backtrace stream with func at " 92 "file:line\n" 93 "\n" 94 "USAGE\n" 95 " <prog that prints a backtrace> | kit symbolize -e FILE\n" 96 " kit symbolize -e FILE < backtrace.txt\n" 97 "\n" 98 "DESCRIPTION\n" 99 " Reads raw `#N 0x<hex>` backtrace lines as text on stdin, resolves\n" 100 " each address via FILE's debug info, and writes annotated text to\n" 101 " stdout in the form\n" 102 " #0 0x401136 bt_leaf at file.c:51:3\n" 103 " keeping the original \"#N\" framing. Lines with no 0x<hex> " 104 "address " 105 "pass\n" 106 " through unchanged. FILE is required for normal operation; with " 107 "no\n" 108 "-e this release prints help and returns 0.\n" 109 "\n" 110 "OPTIONS\n" 111 " -e FILE object file with debug info (required)\n" 112 " --basenames strip directory from file paths\n" 113 " -h, --help show this help\n" 114 "\n" 115 "ADDRESS RESTRICTION\n" 116 " Input addresses must be link-time addresses. Static/non-PIE\n" 117 " runtime addresses already match. For PIE/ASLR, subtract the load\n" 118 " bias before producing the input; no load-bias option is exposed.\n" 119 "\n" 120 "EXAMPLES\n" 121 " # debug-app was linked with -g -no-pie.\n" 122 " kit nm -n debug-app\n" 123 " ADDR=$(kit nm -n debug-app | awk '$3 == \"main\" || " 124 "$3 == \"_main\" { print $1; exit }')\n" 125 " printf '#0 0x%s\\nplain line\\n' \"$ADDR\" | \\\n" 126 " kit symbolize --basenames -e debug-app\n" 127 "\n" 128 "EXIT CODES\n" 129 " 0 success (also no -e help exception)\n" 130 " 1 debug/object or I/O error 2 bad usage\n"))); 131 } 132 133 int driver_symbolize(int argc, char** argv) { 134 DriverEnv env; 135 SymOpts opts; 136 DriverDwarfSym sym; 137 uint8_t* data = NULL; 138 size_t size = 0; 139 int i, rc = 1, opened = 0; 140 size_t pos; 141 142 if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) { 143 driver_help_symbolize(); 144 return 0; 145 } 146 147 memset(&opts, 0, sizeof opts); 148 driver_env_init(&env); 149 150 for (i = 1; i < argc; ++i) { 151 const char* a = argv[i]; 152 if (driver_streq(a, "-e")) { 153 if (i + 1 >= argc) { 154 driver_errf(SYM_TOOL, "-e requires a path"); 155 rc = 2; 156 goto done; 157 } 158 opts.exe_path = argv[++i]; 159 continue; 160 } 161 if (driver_streq(a, "--basenames")) { 162 opts.basenames = 1; 163 continue; 164 } 165 if (a[0] == '-' && a[1] != '\0') { 166 driver_errf(SYM_TOOL, "unknown option: %s", a); 167 rc = 2; 168 goto done; 169 } 170 driver_errf(SYM_TOOL, "unexpected argument: %s", a); 171 rc = 2; 172 goto done; 173 } 174 175 if (!opts.exe_path) { 176 driver_errf(SYM_TOOL, "no object file specified (-e FILE)"); 177 rc = 2; 178 goto done; 179 } 180 181 /* open() memsets `sym` before any fallible step, so once we are past this 182 * point driver_dwarfsym_close is always safe — even on a partial failure. */ 183 opened = 1; 184 if (driver_dwarfsym_open(&sym, &env, SYM_TOOL, opts.exe_path) != 0) goto done; 185 186 if (!driver_read_stdin(&env, &data, &size)) { 187 driver_errf(SYM_TOOL, "failed to read backtrace stream from stdin"); 188 goto done; 189 } 190 191 /* Emit each line verbatim, appending an annotation when it carries an 192 * address. Split on '\n'; a final line without a trailing newline is still 193 * processed (and printed without one). */ 194 pos = 0; 195 while (pos < size) { 196 size_t start = pos; 197 size_t end = start; 198 uint64_t addr = 0; 199 while (end < size && data[end] != '\n') ++end; 200 201 driver_printf("%.*s", (int)(end - start), (const char*)(data + start)); 202 if (sym_line_addr((const char*)(data + start), end - start, &addr)) { 203 KitDwarfResolve loc; 204 kit_dwarf_resolve(sym.dwarf, addr, 1, &loc); 205 sym_emit_annotation(&loc, &opts); 206 } 207 if (end < size) { 208 driver_printf("\n"); 209 pos = end + 1; 210 } else { 211 pos = end; /* last line had no terminator */ 212 } 213 } 214 215 rc = 0; 216 217 done: 218 if (data) driver_free(&env, data, size); 219 if (opened) driver_dwarfsym_close(&sym); 220 driver_env_fini(&env); 221 return rc; 222 }