kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

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 }