kit

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

objdump.c (51549B)


      1 #include <kit/archive.h>
      2 #include <kit/core.h>
      3 #include <kit/disasm.h>
      4 #include <kit/dwarf.h>
      5 #include <kit/object.h>
      6 #include <stdio.h>
      7 #include <string.h>
      8 
      9 #include "driver.h"
     10 
     11 /* `kit objdump` — print section/symbol info, disassembly, hex contents,
     12  * and relocations for object files and archives. Archives are auto-detected
     13  * by magic; each member is dumped in turn. All display logic lives here;
     14  * the library supplies data-access APIs. */
     15 
     16 #define OBJDUMP_TOOL "objdump"
     17 
     18 #define MAX_J_FILTERS 16
     19 
     20 typedef struct ObjdumpOpts {
     21   int f;          /* -f: file header           */
     22   int h;          /* -h: section headers       */
     23   int t;          /* -t: symbol table          */
     24   int d;          /* -d: disasm exec sections  */
     25   int D;          /* -D: disasm all sections   */
     26   int r;          /* -r: relocations           */
     27   int s;          /* -s: hex section contents  */
     28   int p;          /* -p / --private-headers: program/dynamic headers (image) */
     29   int T;          /* -T / --dynamic-syms: dynamic symbol table */
     30   int R;          /* -R / --dynamic-reloc: dynamic relocations */
     31   unsigned dwarf; /* --dwarf: bitmask of OBJDUMP_DWARF_* (0 = off) */
     32   const char* j[MAX_J_FILTERS];
     33   int nj;
     34 } ObjdumpOpts;
     35 
     36 /* --dwarf section selectors. */
     37 #define OBJDUMP_DWARF_INFO 0x1u
     38 #define OBJDUMP_DWARF_ABBREV 0x2u
     39 #define OBJDUMP_DWARF_LINE 0x4u
     40 #define OBJDUMP_DWARF_STR 0x8u
     41 #define OBJDUMP_DWARF_ALL                                           \
     42   (OBJDUMP_DWARF_INFO | OBJDUMP_DWARF_ABBREV | OBJDUMP_DWARF_LINE | \
     43    OBJDUMP_DWARF_STR)
     44 
     45 static void objdump_usage(void) {
     46   driver_errf(OBJDUMP_TOOL, "%.*s",
     47               KIT_SLICE_ARG(KIT_SLICE_LIT(
     48                   "usage: kit objdump [-h] [-t] [-d] [-D] [-r] [-s] [-p] "
     49                   "[-T] [-R] [-j NAME ...] input...\n"
     50                   "       kit objdump --help    for full option reference")));
     51 }
     52 
     53 void driver_help_objdump(void) {
     54   driver_printf(
     55       "%.*s",
     56       KIT_SLICE_ARG(KIT_SLICE_LIT(
     57           "kit objdump — print info about object files and archives\n"
     58           "\n"
     59           "USAGE\n"
     60           "  kit objdump [options] input ...\n"
     61           "\n"
     62           "DESCRIPTION\n"
     63           "  Prints section/symbol info, disassembly, hex contents, and\n"
     64           "  relocations for object files (ELF / COFF / Mach-O / Wasm) and "
     65           "`ar`\n"
     66           "  archives. Archives are auto-detected by magic; each member is\n"
     67           "  dumped in turn with an `archive(member)` label.\n"
     68           "\n"
     69           "  With no operation flags the default is `-h -t` (section headers "
     70           "+\n"
     71           "  symbol table), matching GNU objdump's default-ish behaviour.\n"
     72           "\n"
     73           "OPERATIONS (any combination)\n"
     74           "  -f                Print the file header: architecture, format,\n"
     75           "                    section / symbol counts, HAS_RELOC / HAS_SYMS\n"
     76           "                    flags, and (for PE images) image base,\n"
     77           "                    entry point, and subsystem.\n"
     78           "  -h                Print section headers (idx, name, size, align,\n"
     79           "                    flags). For COFF inputs the raw\n"
     80           "                    IMAGE_SCN_* Characteristics value is appended\n"
     81           "                    on a continuation line and COMDAT groups are\n"
     82           "                    printed after the section table. NOTE: this is\n"
     83           "                    the GNU objdump meaning of -h — it does NOT\n"
     84           "                    print this help; use --help.\n"
     85           "  -t                Print the symbol table\n"
     86           "  -d                Disassemble executable sections\n"
     87           "  -D                Disassemble all sections\n"
     88           "  -r                Print relocation records\n"
     89           "  -s                Print section contents as a hex+ASCII dump\n"
     90           "  -p, --private-headers\n"
     91           "                    Print the linked-image view: entry point, load\n"
     92           "                    segments (program headers), and dynamic\n"
     93           "                    dependencies. For PE images, the optional\n"
     94           "                    header, data directories, and import lists.\n"
     95           "  -T, --dynamic-syms\n"
     96           "                    Print the dynamic symbol table (.dynsym /\n"
     97           "                    export table). Empty for relocatable objects.\n"
     98           "  -R, --dynamic-reloc\n"
     99           "                    Print dynamic relocation records. Empty for\n"
    100           "                    relocatable objects.\n"
    101           "  -x                Aggregate: -f -h -r -t\n"
    102           "  --dwarf[=LIST]    Dump DWARF debug sections. LIST is a comma-\n"
    103           "                    separated subset of info, abbrev, line, str;\n"
    104           "                    bare --dwarf dumps all four.\n"
    105           "\n"
    106           "FILTERS\n"
    107           "  -j NAME           Restrict output to the named section. "
    108           "Repeatable;\n"
    109           "                    affects -h / -t / -d / -D / -r / -s.\n"
    110           "\n"
    111           "SUPPORTED INPUTS\n"
    112           "  *.o / *.obj       Single object file (ELF / COFF / Mach-O / "
    113           "Wasm)\n"
    114           "  *.a               POSIX `ar` archive — every object member is "
    115           "dumped\n"
    116           "                    in turn with an `<archive>(<member>)` label\n"
    117           "\n"
    118           "GETTING HELP\n"
    119           "  --help            Show this help and exit (-h is `section "
    120           "headers`)\n"
    121           "\n"
    122           "EXAMPLES\n"
    123           "  kit objdump -d a.o\n"
    124           "  kit objdump -h -j .text -j .rodata a.o\n"
    125           "  kit objdump -t libfoo.a\n"
    126           "  SDK=$(kit cc -print-sysroot)\n"
    127           "  printf 'extern int value; int add(void) { return value + 1; }\\n' "
    128           "> debug.c\n"
    129           "  kit cc -isysroot \"$SDK\" "
    130           "-g -c debug.c -o debug.o\n"
    131           "  kit objdump -t -r --dwarf=line debug.o\n"
    132           "\n"
    133           "EXIT CODES\n"
    134           "  0   success           1   parse / I/O error           2   bad "
    135           "usage\n")));
    136 }
    137 
    138 /* ---- PE/COFF display helpers ----
    139  *
    140  * PE executables / DLLs now open through kit_obj_open like every other
    141  * format, so the image data (optional-header scalars, data directories,
    142  * imports) arrive through the neutral image API — kit_obj_image_info, the
    143  * raw-fields iterator (KitObjImageRaw), and the dependency iterator. The
    144  * helpers below only map PE numeric constants to the symbolic names
    145  * objdump prints (data-directory index, subsystem). */
    146 
    147 /* Data-directory index (IMAGE_DIRECTORY_ENTRY_IMPORT). */
    148 #define PE_DIR_IMPORT 1u
    149 
    150 /* COFF-specific Characteristics bits we surface as tags. Kept in sync
    151  * with src/obj/coff.h's IMAGE_SCN_* values; objdump only needs the
    152  * diagnostic-visible subset. */
    153 #define OBJDUMP_IMAGE_SCN_LNK_INFO 0x00000200u
    154 #define OBJDUMP_IMAGE_SCN_LNK_REMOVE 0x00000800u
    155 #define OBJDUMP_IMAGE_SCN_LNK_COMDAT 0x00001000u
    156 #define OBJDUMP_IMAGE_SCN_GPREL 0x00008000u
    157 #define OBJDUMP_IMAGE_SCN_MEM_DISCARDABLE 0x02000000u
    158 #define OBJDUMP_IMAGE_SCN_MEM_SHARED 0x10000000u
    159 
    160 static int j_match(const ObjdumpOpts* o, KitSlice name);
    161 
    162 /* Names match the IMAGE_DIRECTORY_ENTRY_* index. Keep aligned with the
    163  * order in coff.h to avoid drift. */
    164 static const char* pe_dir_name(uint32_t i) {
    165   switch (i) {
    166     case 0:
    167       return "EXPORT";
    168     case 1:
    169       return "IMPORT";
    170     case 2:
    171       return "RESOURCE";
    172     case 3:
    173       return "EXCEPTION";
    174     case 4:
    175       return "SECURITY";
    176     case 5:
    177       return "BASERELOC";
    178     case 6:
    179       return "DEBUG";
    180     case 7:
    181       return "ARCHITECTURE";
    182     case 8:
    183       return "GLOBALPTR";
    184     case 9:
    185       return "TLS";
    186     case 10:
    187       return "LOAD_CONFIG";
    188     case 11:
    189       return "BOUND_IMPORT";
    190     case 12:
    191       return "IAT";
    192     case 13:
    193       return "DELAY_IMPORT";
    194     case 14:
    195       return "COM_DESCRIPTOR";
    196     case 15:
    197       return "RESERVED";
    198     default:
    199       return "?";
    200   }
    201 }
    202 
    203 static const char* pe_subsystem_name(uint16_t s) {
    204   switch (s) {
    205     case 1:
    206       return "NATIVE";
    207     case 2:
    208       return "WINDOWS_GUI";
    209     case 3:
    210       return "WINDOWS_CUI";
    211     case 5:
    212       return "OS2_CUI";
    213     case 7:
    214       return "POSIX_CUI";
    215     case 9:
    216       return "WINDOWS_CE_GUI";
    217     case 10:
    218       return "EFI_APPLICATION";
    219     case 11:
    220       return "EFI_BOOT_SERVICE_DRIVER";
    221     case 12:
    222       return "EFI_RUNTIME_DRIVER";
    223     case 13:
    224       return "EFI_ROM";
    225     case 14:
    226       return "XBOX";
    227     case 16:
    228       return "WINDOWS_BOOT_APPLICATION";
    229     default:
    230       return "UNKNOWN";
    231   }
    232 }
    233 
    234 /* Render objdump's "file format" spelling: the obj layer's canonical bare
    235  * name (elf/coff/macho/wasm) plus the bitwidth suffix this tool presents.
    236  * wasm carries no suffix; an out-of-range fmt renders "unknown". The bare
    237  * name is the single source of truth (kit_obj_fmt_name); the suffix is
    238  * legitimate tool presentation. `buf` must hold at least FMT_STR_CAP bytes. */
    239 #define FMT_STR_CAP 16
    240 static const char* fmt_str(KitObjFmt fmt, uint8_t ptr_size, char* buf,
    241                            size_t cap) {
    242   const char* base = kit_obj_fmt_name(fmt);
    243   if (!base) return "unknown";
    244   if (fmt == KIT_OBJ_WASM) return base; /* wasm has no bitwidth suffix */
    245   snprintf(buf, cap, "%s%s", base, ptr_size == 8 ? "64" : "32");
    246   return buf;
    247 }
    248 
    249 static const char* arch_str(KitArchKind arch) {
    250   switch (arch) {
    251     case KIT_ARCH_X86_64:
    252       return "x86_64";
    253     case KIT_ARCH_X86_32:
    254       return "i386";
    255     case KIT_ARCH_ARM_64:
    256       return "arm64";
    257     case KIT_ARCH_ARM_32:
    258       return "arm";
    259     case KIT_ARCH_RV64:
    260       return "riscv64";
    261     case KIT_ARCH_RV32:
    262       return "riscv32";
    263     case KIT_ARCH_WASM:
    264       return "wasm32";
    265   }
    266   return "unknown";
    267 }
    268 
    269 /* Collected PE optional-header escape-hatch view, gathered from the neutral
    270  * raw-fields iterator (KitObjImageRaw): the 16 data directories plus the
    271  * Subsystem / DllCharacteristics scalars. Returns 0 if the image carries no
    272  * raw fields (i.e. not a PE image). */
    273 typedef struct PeRaw {
    274   uint16_t subsystem;
    275   uint16_t dllchars;
    276   uint32_t dir_rva[16];
    277   uint32_t dir_size[16];
    278 } PeRaw;
    279 
    280 static int pe_collect_raw(KitObjFile* f, PeRaw* out) {
    281   KitObjImageRawIter* it = NULL;
    282   KitObjImageRaw r;
    283   memset(out, 0, sizeof *out);
    284   if (kit_obj_image_rawiter_new(f, &it) != KIT_OK) return 0;
    285   while (kit_obj_image_rawiter_next(it, &r) == KIT_ITER_ITEM) {
    286     if (r.tag < 16) {
    287       out->dir_rva[r.tag] = (uint32_t)r.value;
    288       out->dir_size[r.tag] = (uint32_t)r.extra;
    289     } else if (r.tag == KIT_OBJ_RAW_PE_SUBSYSTEM) {
    290       out->subsystem = (uint16_t)r.value;
    291     } else if (r.tag == KIT_OBJ_RAW_PE_DLLCHARS) {
    292       out->dllchars = (uint16_t)r.value;
    293     }
    294   }
    295   kit_obj_image_rawiter_free(it);
    296   return 1;
    297 }
    298 
    299 /* PE-image `-p`: the GNU objdump "PE32+ private headers" view — optional
    300  * header highlights, data directories, and the import tables — rendered
    301  * entirely from the neutral image API (kit_obj_image_info + the raw-fields
    302  * iterator + the dependency iterator). */
    303 static void dump_pe_private(KitObjFile* f, const char* label) {
    304   PeRaw raw;
    305   KitObjImageInfo info;
    306   KitTargetSpec target = kit_obj_target(f);
    307   KitObjDepIter* dit = NULL;
    308   KitObjDepInfo dep;
    309   uint32_t i;
    310   int have_imports = 0;
    311   if (!pe_collect_raw(f, &raw)) return;
    312   if (kit_obj_image_info(f, &info) != KIT_OK) return;
    313 
    314   driver_printf("\n%.*s:\tPE32+ private headers\n",
    315                 KIT_SLICE_ARG(kit_slice_cstr(label)));
    316   driver_printf("  Magic:               0x20b  (PE32+)\n");
    317   driver_printf("  Machine:             %.*s\n",
    318                 KIT_SLICE_ARG(kit_slice_cstr(arch_str(target.arch))));
    319   driver_printf("  ImageBase:           0x%llx\n",
    320                 (unsigned long long)info.image_base);
    321   driver_printf("  AddressOfEntryPoint: 0x%llx\n",
    322                 (unsigned long long)(info.entry > info.image_base
    323                                          ? info.entry - info.image_base
    324                                          : 0));
    325   driver_printf(
    326       "  Subsystem:           %u  (%.*s)\n", (unsigned)raw.subsystem,
    327       KIT_SLICE_ARG(kit_slice_cstr(pe_subsystem_name(raw.subsystem))));
    328   driver_printf("  DllCharacteristics:  0x%04x\n", (unsigned)raw.dllchars);
    329 
    330   driver_printf("\nData Directories:\n");
    331   driver_printf("  Idx Name            RVA        Size\n");
    332   for (i = 0; i < 16; ++i) {
    333     if (raw.dir_rva[i] == 0 && raw.dir_size[i] == 0) continue;
    334     driver_printf("  %2u  %-14s  0x%08x 0x%08x\n", i, pe_dir_name(i),
    335                   raw.dir_rva[i], raw.dir_size[i]);
    336   }
    337 
    338   if (kit_obj_depiter_new(f, &dit) == KIT_OK) {
    339     while (kit_obj_depiter_next(dit, &dep) == KIT_ITER_ITEM) {
    340       uint32_t k;
    341       if (!have_imports) {
    342         driver_printf("\nThe Import Tables:\n");
    343         have_imports = 1;
    344       }
    345       driver_printf("  DLL Name: %.*s\n", KIT_SLICE_ARG(dep.name));
    346       for (k = 0; k < dep.nimports; ++k)
    347         driver_printf("    Name:     %.*s\n", KIT_SLICE_ARG(dep.imports[k]));
    348     }
    349     kit_obj_depiter_free(dit);
    350   }
    351   driver_printf("\n");
    352 }
    353 
    354 static char sym_bind_char(KitSymBind b) {
    355   switch (b) {
    356     case KIT_SB_LOCAL:
    357       return 'l';
    358     case KIT_SB_GLOBAL:
    359       return 'g';
    360     case KIT_SB_WEAK:
    361       return 'w';
    362   }
    363   return ' ';
    364 }
    365 
    366 static char sym_kind_char(KitSymKind k) {
    367   switch (k) {
    368     case KIT_SK_FUNC:
    369       return 'F';
    370     case KIT_SK_OBJ:
    371       return 'O';
    372     case KIT_SK_SECTION:
    373       return 'S';
    374     case KIT_SK_FILE:
    375       return 'f';
    376     case KIT_SK_TLS:
    377       return 'T';
    378     case KIT_SK_ABS:
    379       return 'A';
    380     case KIT_SK_COMMON:
    381       return 'C';
    382     case KIT_SK_UNDEF:
    383       return 'U';
    384     case KIT_SK_NOTYPE:
    385       return 'n';
    386     case KIT_SK_IFUNC:
    387       return 'i';
    388   }
    389   return ' ';
    390 }
    391 
    392 static int j_match(const ObjdumpOpts* o, KitSlice name) {
    393   int i;
    394   if (o->nj == 0) return 1;
    395   for (i = 0; i < o->nj; ++i) {
    396     if (kit_slice_eq_cstr(name, o->j[i])) return 1;
    397   }
    398   return 0;
    399 }
    400 
    401 /* Compose the comma-separated flag tag list GNU objdump prints in -h.
    402  * For COFF inputs, `coff_chars` is the raw IMAGE_SECTION_HEADER.Characteristics
    403  * value; for other formats it should be 0. */
    404 static void render_sec_flags(const KitObjSecInfo* sec, KitObjFmt fmt,
    405                              uint32_t coff_chars, char* buf, size_t cap) {
    406   size_t n = 0;
    407   const char* tags[16];
    408   int nt = 0;
    409   int i;
    410   int is_bss = (sec->kind == KIT_SEC_BSS);
    411 
    412   if (!is_bss && sec->size > 0) tags[nt++] = "CONTENTS";
    413   if (sec->flags & KIT_SF_ALLOC) tags[nt++] = "ALLOC";
    414   if ((sec->flags & KIT_SF_ALLOC) && !is_bss) tags[nt++] = "LOAD";
    415   if ((sec->flags & KIT_SF_ALLOC) && !(sec->flags & KIT_SF_WRITE))
    416     tags[nt++] = "READONLY";
    417   if (sec->flags & KIT_SF_EXEC) tags[nt++] = "CODE";
    418   if ((sec->flags & KIT_SF_WRITE) && !(sec->flags & KIT_SF_EXEC) &&
    419       (sec->flags & KIT_SF_ALLOC))
    420     tags[nt++] = "DATA";
    421   if (sec->flags & KIT_SF_TLS) tags[nt++] = "TLS";
    422   if (sec->flags & KIT_SF_MERGE) tags[nt++] = "MERGE";
    423   if (sec->flags & KIT_SF_STRINGS) tags[nt++] = "STRINGS";
    424   if (sec->kind == KIT_SEC_DEBUG) tags[nt++] = "DEBUGGING";
    425 
    426   if (fmt == KIT_OBJ_COFF) {
    427     if (coff_chars & OBJDUMP_IMAGE_SCN_LNK_COMDAT) tags[nt++] = "LINK_ONCE";
    428     if (coff_chars & OBJDUMP_IMAGE_SCN_LNK_INFO) tags[nt++] = "LINK_INFO";
    429     if (coff_chars & OBJDUMP_IMAGE_SCN_LNK_REMOVE) tags[nt++] = "LINK_REMOVE";
    430     if (coff_chars & OBJDUMP_IMAGE_SCN_MEM_DISCARDABLE)
    431       tags[nt++] = "DISCARDABLE";
    432     if (coff_chars & OBJDUMP_IMAGE_SCN_MEM_SHARED) tags[nt++] = "SHARED";
    433     if (coff_chars & OBJDUMP_IMAGE_SCN_GPREL) tags[nt++] = "GPREL";
    434   }
    435 
    436   for (i = 0; i < nt && n + 1 < cap; ++i) {
    437     const char* t = tags[i];
    438     if (i > 0 && n + 1 < cap) buf[n++] = ',';
    439     while (*t && n + 1 < cap) buf[n++] = *t++;
    440   }
    441   if (sec->entsize && n + 1 < cap) {
    442     char tmp[32];
    443     int k = snprintf(tmp, sizeof tmp, "%.*sentsize=%u",
    444                      KIT_SLICE_ARG(kit_slice_cstr(n ? "," : "")), sec->entsize);
    445     size_t j;
    446     for (j = 0; k > 0 && j < (size_t)k && n + 1 < cap; ++j) buf[n++] = tmp[j];
    447   }
    448   buf[n] = '\0';
    449 }
    450 
    451 static void dump_sections(KitObjFile* f, const ObjdumpOpts* opts) {
    452   uint32_t nsec = kit_obj_nsections(f);
    453   KitObjFmt fmt = kit_obj_fmt(f);
    454   uint32_t i;
    455   char flagbuf[160];
    456 
    457   driver_printf("Sections:\n");
    458   driver_printf("Idx Name                 Size      Align  Flags\n");
    459   for (i = 0; i < nsec; ++i) {
    460     KitObjSecInfo sec;
    461     uint32_t raw_type = 0;
    462     if (kit_obj_section(f, i, &sec) != KIT_OK) continue;
    463     if (!j_match(opts, sec.name)) continue;
    464     kit_obj_section_format_flags(f, i, &raw_type, NULL);
    465     render_sec_flags(&sec, fmt, raw_type, flagbuf, sizeof(flagbuf));
    466     driver_printf(
    467         "%3u %-20s %08llx  2**%-2u  %.*s\n", i,
    468         sec.name.len ? sec.name.s : "(anon)", (unsigned long long)sec.size,
    469         sec.align ? (unsigned)__builtin_ctz(sec.align ? sec.align : 1) : 0,
    470         KIT_SLICE_ARG(kit_slice_cstr(flagbuf)));
    471     /* Show the raw IMAGE_SCN_* value on a continuation line for COFF
    472      * inputs — useful when diagnosing why a section ended up with the
    473      * tags it did. The hex is much shorter than printing every set bit
    474      * by name, and the tag list above already covers the bits that
    475      * change behaviour at link time. */
    476     if (fmt == KIT_OBJ_COFF && raw_type) {
    477       driver_printf(
    478           "                                              Characteristics: "
    479           "0x%08x\n",
    480           raw_type);
    481     }
    482   }
    483   driver_printf("\n");
    484 }
    485 
    486 /* GNU objdump prints COMDAT group membership immediately after the
    487  * section header table. The reader exposes groups uniformly across
    488  * formats (ELF SHT_GROUP and COFF COMDAT both arrive here) so we just
    489  * iterate.  Output is silent when the object carries no groups. */
    490 static void dump_groups(KitObjFile* f, const ObjdumpOpts* opts) {
    491   KitObjGroupIter* it = NULL;
    492   KitObjGroupInfo g;
    493   int printed_header = 0;
    494   (void)opts;
    495 
    496   if (kit_obj_groupiter_new(f, &it) != KIT_OK) return;
    497   while (kit_obj_groupiter_next(it, &g) == KIT_ITER_ITEM) {
    498     uint32_t k;
    499     if (!printed_header) {
    500       driver_printf("COMDAT groups:\n");
    501       printed_header = 1;
    502     }
    503     driver_printf("  group %.*s (signature sym #%u, %u section%.*s)\n",
    504                   KIT_SLICE_ARG(g.name.len ? g.name : KIT_SLICE_LIT("(anon)")),
    505                   (unsigned)g.signature, (unsigned)g.nsections,
    506                   KIT_SLICE_ARG(kit_slice_cstr(g.nsections == 1 ? "" : "s")));
    507     for (k = 0; k < g.nsections; ++k) {
    508       KitObjSection sid = g.sections[k];
    509       KitObjSecInfo si;
    510       if (sid == KIT_SECTION_NONE) continue;
    511       if (kit_obj_section(f, sid, &si) != KIT_OK) continue;
    512       driver_printf(
    513           "    [%3u] %.*s\n", (unsigned)sid,
    514           KIT_SLICE_ARG(si.name.len ? si.name : KIT_SLICE_LIT("(anon)")));
    515     }
    516   }
    517   kit_obj_groupiter_free(it);
    518   if (printed_header) driver_printf("\n");
    519 }
    520 
    521 /* `dynamic` selects the dynamic symbol table (.dynsym / export trie) via
    522  * kit_obj_dynsymiter_new instead of the static .symtab. Both share the
    523  * KitObjSymInfo shape and the same _next/_free, so the body is identical. */
    524 static void dump_symbols(KitObjFile* f, const ObjdumpOpts* opts, int dynamic) {
    525   KitObjSymIter* it = NULL;
    526   KitObjSymInfo sym;
    527   KitStatus st;
    528 
    529   driver_printf(dynamic ? "DYNAMIC SYMBOL TABLE:\n" : "SYMBOL TABLE:\n");
    530   st = dynamic ? kit_obj_dynsymiter_new(f, &it) : kit_obj_symiter_new(f, &it);
    531   if (st != KIT_OK) return;
    532   for (;;) {
    533     KitIterResult r = kit_obj_symiter_next(it, &sym);
    534     KitSlice secname;
    535     if (r != KIT_ITER_ITEM) break;
    536     if (sym.section == KIT_SECTION_NONE) {
    537       secname = KIT_SLICE_LIT("*UND*");
    538     } else {
    539       KitObjSecInfo sec;
    540       if (kit_obj_section(f, sym.section, &sec) != KIT_OK) continue;
    541       secname = sec.name.len ? sec.name : KIT_SLICE_LIT("(none)");
    542       if (opts->nj && !j_match(opts, secname)) continue;
    543     }
    544     driver_printf(
    545         "%016llx %c   %c %-18s %016llx %.*s\n", (unsigned long long)sym.value,
    546         sym_bind_char(sym.bind), sym_kind_char(sym.kind), secname.s,
    547         (unsigned long long)sym.size,
    548         KIT_SLICE_ARG(sym.name.len ? sym.name : KIT_SLICE_LIT("(none)")));
    549   }
    550   kit_obj_symiter_free(it);
    551   driver_printf("\n");
    552 }
    553 
    554 static void dump_hex(KitObjFile* f, const ObjdumpOpts* opts) {
    555   uint32_t nsec = kit_obj_nsections(f);
    556   uint32_t i;
    557 
    558   for (i = 0; i < nsec; ++i) {
    559     KitObjSecInfo sec;
    560     size_t len = 0;
    561     const uint8_t* data = NULL;
    562     size_t ofs;
    563 
    564     if (kit_obj_section(f, i, &sec) != KIT_OK) continue;
    565     if (!j_match(opts, sec.name)) continue;
    566     if (kit_obj_section_data(f, i, &data, &len) != KIT_OK) continue;
    567     if (!data || len == 0) continue;
    568 
    569     driver_printf(
    570         "Contents of section %.*s:\n",
    571         KIT_SLICE_ARG(sec.name.len ? sec.name : KIT_SLICE_LIT("(anon)")));
    572     for (ofs = 0; ofs < len; ofs += 16) {
    573       size_t j;
    574       char ascii[17];
    575       driver_printf(" %04llx ", (unsigned long long)ofs);
    576       for (j = 0; j < 16; ++j) {
    577         if (ofs + j < len) {
    578           driver_printf("%02x", data[ofs + j]);
    579           ascii[j] = (data[ofs + j] >= 32 && data[ofs + j] < 127)
    580                          ? (char)data[ofs + j]
    581                          : '.';
    582         } else {
    583           driver_printf("  ");
    584           ascii[j] = ' ';
    585         }
    586         if ((j & 1) == 1) driver_printf(" ");
    587       }
    588       ascii[16] = '\0';
    589       driver_printf(" %.*s\n", KIT_SLICE_ARG(kit_slice_cstr(ascii)));
    590     }
    591   }
    592   driver_printf("\n");
    593 }
    594 
    595 static void dump_relocs(KitObjFile* f, const ObjdumpOpts* opts) {
    596   KitObjRelocIter* it = NULL;
    597   KitObjReloc r;
    598   uint32_t cur_sec = (uint32_t)-1;
    599   int printed_header = 0;
    600   int emitted_any = 0;
    601 
    602   if (kit_obj_reliter_new(f, &it) != KIT_OK) return;
    603   for (;;) {
    604     KitObjSecInfo sec;
    605     KitIterResult res = kit_obj_reliter_next(it, &r);
    606     if (res != KIT_ITER_ITEM) break;
    607     if (kit_obj_section(f, r.section, &sec) != KIT_OK) continue;
    608     if (!j_match(opts, sec.name)) continue;
    609 
    610     if (r.section != cur_sec) {
    611       if (printed_header) driver_printf("\n");
    612       driver_printf(
    613           "RELOCATION RECORDS FOR [%.*s]:\n",
    614           KIT_SLICE_ARG(sec.name.len ? sec.name : KIT_SLICE_LIT("(anon)")));
    615       driver_printf("OFFSET           TYPE              VALUE\n");
    616       cur_sec = r.section;
    617       printed_header = 1;
    618     }
    619 
    620     if (r.addend) {
    621       driver_printf(
    622           "%016llx %-17s %.*s%c0x%llx\n", (unsigned long long)r.offset,
    623           r.kind_name.len ? r.kind_name.s : "?",
    624           KIT_SLICE_ARG(r.sym_name.len ? r.sym_name : KIT_SLICE_LIT("*ABS*")),
    625           r.addend < 0 ? '-' : '+',
    626           (unsigned long long)(r.addend < 0 ? -r.addend : r.addend));
    627     } else {
    628       driver_printf(
    629           "%016llx %-17s %.*s\n", (unsigned long long)r.offset,
    630           r.kind_name.len ? r.kind_name.s : "?",
    631           KIT_SLICE_ARG(r.sym_name.len ? r.sym_name : KIT_SLICE_LIT("*ABS*")));
    632     }
    633     emitted_any = 1;
    634   }
    635   kit_obj_reliter_free(it);
    636   if (emitted_any) driver_printf("\n");
    637 }
    638 
    639 /* Find a symbol whose value is exactly `value`. When `section_idx` is a real
    640  * index the match is scoped to that section; pass OBJDUMP_SEC_ANY to match by
    641  * address across the whole symbol table — used by the segment-fallback path,
    642  * where the disassembled bytes have no owning section. */
    643 #define OBJDUMP_SEC_ANY UINT32_MAX
    644 
    645 static KitSlice objdump_sym_at(KitObjFile* f, uint32_t section_idx,
    646                                uint64_t value) {
    647   KitObjSymIter* it = NULL;
    648   KitObjSymInfo sym;
    649   KitSlice best = KIT_SLICE_NULL;
    650 
    651   if (kit_obj_symiter_new(f, &it) != KIT_OK) return KIT_SLICE_NULL;
    652   for (;;) {
    653     KitIterResult r = kit_obj_symiter_next(it, &sym);
    654     if (r != KIT_ITER_ITEM) break;
    655     if (section_idx != OBJDUMP_SEC_ANY && sym.section != section_idx) continue;
    656     if (sym.value != value) continue;
    657     if (!sym.name.len) continue;
    658     if (sym.kind == KIT_SK_SECTION) continue;
    659     best = sym.name;
    660     if (sym.kind == KIT_SK_FUNC || sym.bind != KIT_SB_LOCAL) break;
    661   }
    662   kit_obj_symiter_free(it);
    663   return best;
    664 }
    665 
    666 /* Disassemble `len` bytes at `data`, treating `vaddr` as the address of the
    667  * first byte. Symbol labels are looked up via `sym_section` (a real section
    668  * index, or OBJDUMP_SEC_ANY for the segment-fallback path). */
    669 static void disasm_buffer(const KitDisasmContext* dctx, KitObjFile* f,
    670                           const uint8_t* data, size_t len, uint64_t vaddr,
    671                           uint32_t sym_section) {
    672   KitDisasmIter* dis = NULL;
    673   KitInsn insn;
    674 
    675   if (kit_disasm_iter_new(dctx, data, len, vaddr, f, &dis) != KIT_OK) return;
    676   for (;;) {
    677     KitIterResult r = kit_disasm_iter_next(dis, &insn);
    678     uint32_t b;
    679     KitSlice label;
    680     if (r != KIT_ITER_ITEM) break;
    681     label = objdump_sym_at(f, sym_section, insn.vaddr);
    682     if (label.len)
    683       driver_printf("%016llx <%.*s>:\n", (unsigned long long)insn.vaddr,
    684                     KIT_SLICE_ARG(label));
    685     driver_printf("%8llx:\t", (unsigned long long)insn.vaddr);
    686     for (b = 0; b < insn.nbytes; ++b) driver_printf("%02x ", insn.bytes[b]);
    687     for (b = insn.nbytes; b < 8; ++b) driver_printf("   ");
    688     driver_printf("\t%.*s", KIT_SLICE_ARG(insn.mnemonic));
    689     if (insn.operands.len) {
    690       driver_printf(" %.*s", KIT_SLICE_ARG(insn.operands));
    691     }
    692     if (insn.annotation.len) {
    693       driver_printf(" # %.*s", KIT_SLICE_ARG(insn.annotation));
    694     }
    695     driver_printf("\n");
    696   }
    697   kit_disasm_iter_free(dis);
    698 }
    699 
    700 /* Fallback for fully section-stripped images (objcopy --strip-sections,
    701  * packers): the section table is gone, but the code still lives in the
    702  * executable PT_LOAD segments. We disassemble each such segment's file
    703  * contents directly, using its vaddr as the base. `image` is the raw file
    704  * bytes; segment file_off/file_size index into it. Returns the number of
    705  * segments disassembled. Format-agnostic — driven entirely by the segment
    706  * iterator, no ELF/Mach-O special-casing. */
    707 static uint32_t dump_disasm_segments(const KitDisasmContext* dctx,
    708                                      KitObjFile* f, const ObjdumpOpts* opts,
    709                                      const KitSlice* image) {
    710   KitObjSegIter* sit = NULL;
    711   KitObjSegInfo seg;
    712   uint32_t emitted = 0;
    713 
    714   if (!image || kit_obj_segiter_new(f, &sit) != KIT_OK) return 0;
    715   while (kit_obj_segiter_next(sit, &seg) == KIT_ITER_ITEM) {
    716     if (!(seg.perms & KIT_SEG_X)) continue;
    717     if (!j_match(opts, seg.name)) continue;
    718     if (seg.file_size == 0) continue;
    719     if (seg.file_off > image->len || seg.file_size > image->len - seg.file_off)
    720       continue;
    721 
    722     driver_printf(
    723         "Disassembly of segment %.*s:\n\n",
    724         KIT_SLICE_ARG(seg.name.len ? seg.name : KIT_SLICE_LIT("LOAD")));
    725     disasm_buffer(dctx, f, (const uint8_t*)image->data + seg.file_off,
    726                   (size_t)seg.file_size, seg.vaddr, OBJDUMP_SEC_ANY);
    727     driver_printf("\n");
    728     emitted++;
    729   }
    730   kit_obj_segiter_free(sit);
    731   return emitted;
    732 }
    733 
    734 static void dump_disasm(const KitDisasmContext* dctx, KitObjFile* f,
    735                         const ObjdumpOpts* opts, const KitSlice* image) {
    736   uint32_t nsec = kit_obj_nsections(f);
    737   uint32_t i;
    738   uint32_t emitted = 0;
    739   KitDisasmContext file_dctx;
    740   KitTarget* file_target = NULL;
    741   KitTargetOptions topts;
    742 
    743   if (!dctx) return;
    744   file_dctx = *dctx;
    745   memset(&topts, 0, sizeof topts);
    746   topts.spec = kit_obj_target(f);
    747   if (kit_target_new(&dctx->context, &topts, &file_target) != KIT_OK) return;
    748   file_dctx.target = file_target;
    749 
    750   for (i = 0; i < nsec; ++i) {
    751     KitObjSecInfo sec;
    752     size_t len = 0;
    753     const uint8_t* data = NULL;
    754     int want;
    755 
    756     if (kit_obj_section(f, i, &sec) != KIT_OK) continue;
    757     want = opts->D ? 1 : ((sec.flags & KIT_SF_EXEC) != 0);
    758     if (!want) continue;
    759     if (!j_match(opts, sec.name)) continue;
    760 
    761     if (kit_obj_section_data(f, i, &data, &len) != KIT_OK) continue;
    762     if (!data || len == 0) continue;
    763 
    764     driver_printf(
    765         "Disassembly of section %.*s:\n\n",
    766         KIT_SLICE_ARG(sec.name.len ? sec.name : KIT_SLICE_LIT("(anon)")));
    767     /* sec.addr is the load vaddr for a linked image, 0 for a relocatable
    768      * object — so branch/call targets resolve correctly in both. */
    769     disasm_buffer(&file_dctx, f, data, len, sec.addr, i);
    770     driver_printf("\n");
    771     emitted++;
    772   }
    773 
    774   /* No disassemblable sections, but this is a linked image: the section table
    775    * was stripped. Fall back to the executable load segments. */
    776   if (emitted == 0 && kit_obj_kind(f) != KIT_OBJ_KIND_REL)
    777     dump_disasm_segments(&file_dctx, f, opts, image);
    778   kit_target_free(file_target);
    779 }
    780 
    781 /* `-f`: GNU objdump-style file header summary. Object files have no
    782  * meaningful entry point so start address is always 0. For a PE image we
    783  * also surface the Windows subsystem (via the raw-fields escape hatch). The
    784  * flags line summarizes whether the input has symbols and relocations so
    785  * it's clear at a glance whether further -t / -r work is going to be
    786  * productive. */
    787 static void dump_file_header(KitObjFile* f, const char* label) {
    788   KitTargetSpec target = kit_obj_target(f);
    789   KitObjFmt fmt = kit_obj_fmt(f);
    790   KitObjSymIter* sit = NULL;
    791   KitObjRelocIter* rit = NULL;
    792   uint32_t nsec = kit_obj_nsections(f);
    793   uint32_t nsym = 0;
    794   int has_relocs = 0;
    795   unsigned flags = 0;
    796   KitObjKind kind = kit_obj_kind(f);
    797   KitObjImageInfo info;
    798   int have_info = kit_obj_image_info(f, &info) == KIT_OK;
    799   const char* sep = "";
    800 
    801   if (kit_obj_symiter_new(f, &sit) == KIT_OK) {
    802     KitObjSymInfo s;
    803     while (kit_obj_symiter_next(sit, &s) == KIT_ITER_ITEM) nsym++;
    804     kit_obj_symiter_free(sit);
    805   }
    806   if (kit_obj_reliter_new(f, &rit) == KIT_OK) {
    807     KitObjReloc r;
    808     if (kit_obj_reliter_next(rit, &r) == KIT_ITER_ITEM) has_relocs = 1;
    809     kit_obj_reliter_free(rit);
    810   }
    811   /* GNU objdump's BFD flag bits: 0x01 HAS_RELOC, 0x02 EXEC_P, 0x10 HAS_SYMS,
    812    * 0x40 DYNAMIC, 0x100 D_PAGED. */
    813   if (has_relocs) flags |= 0x0001u;
    814   if (kind == KIT_OBJ_KIND_EXEC) flags |= 0x0002u;
    815   if (nsym) flags |= 0x0010u;
    816   if (kind == KIT_OBJ_KIND_DYN) flags |= 0x0040u;
    817   if (kind != KIT_OBJ_KIND_REL) flags |= 0x0100u;
    818 
    819   driver_printf("architecture: %.*s, flags 0x%08x:\n",
    820                 KIT_SLICE_ARG(kit_slice_cstr(arch_str(target.arch))), flags);
    821 #define OBJDUMP_FLAG(bit, name)         \
    822   do {                                  \
    823     if (flags & (bit)) {                \
    824       driver_printf("%s%s", sep, name); \
    825       sep = ", ";                       \
    826     }                                   \
    827   } while (0)
    828   OBJDUMP_FLAG(0x0001u, "HAS_RELOC");
    829   OBJDUMP_FLAG(0x0002u, "EXEC_P");
    830   OBJDUMP_FLAG(0x0010u, "HAS_SYMS");
    831   OBJDUMP_FLAG(0x0040u, "DYNAMIC");
    832   OBJDUMP_FLAG(0x0100u, "D_PAGED");
    833 #undef OBJDUMP_FLAG
    834   if (flags) driver_printf("\n");
    835   driver_printf("start address 0x%016llx\n",
    836                 have_info ? (unsigned long long)info.entry : 0ull);
    837   {
    838     char fmt_buf[FMT_STR_CAP];
    839     driver_printf("format: %.*s, sections: %u, symbols: %u\n\n",
    840                   KIT_SLICE_ARG(kit_slice_cstr(
    841                       fmt_str(fmt, target.ptr_size, fmt_buf, sizeof fmt_buf))),
    842                   nsec, nsym);
    843   }
    844   if (fmt == KIT_OBJ_COFF && kind != KIT_OBJ_KIND_REL) {
    845     PeRaw raw;
    846     if (pe_collect_raw(f, &raw))
    847       driver_printf(
    848           "subsystem: %u (%.*s)\n\n", (unsigned)raw.subsystem,
    849           KIT_SLICE_ARG(kit_slice_cstr(pe_subsystem_name(raw.subsystem))));
    850   }
    851   (void)label;
    852 }
    853 
    854 /* ---- DWARF structural dump (`--dwarf`) ----
    855  *
    856  * Pulls the raw .debug_info / .debug_abbrev / .debug_line / .debug_str
    857  * structure out via the kit_dwarf_*_iter API and formats it. The library
    858  * hands back numeric DWARF codes; kit_dwarf_{tag,attr,form}_name turn them
    859  * into symbolic spellings (the canonical table all dumpers share), and
    860  * dw_emit_code applies objdump's hex fallback for codes those don't name. */
    861 
    862 /* Print a symbolic DWARF code or, when unknown, its hex value. */
    863 static void dw_emit_code(const char* name, uint32_t val) {
    864   if (name)
    865     driver_printf("%s", name);
    866   else
    867     driver_printf("0x%x", val);
    868 }
    869 
    870 static void dw_emit_attr_value(const KitDwarfAttr* a) {
    871   switch (a->form_class) {
    872     case KIT_DWARF_FC_STRING:
    873       driver_printf("\"%.*s\"", KIT_SLICE_ARG(a->str));
    874       break;
    875     case KIT_DWARF_FC_SDATA:
    876       driver_printf("%lld", (long long)a->s);
    877       break;
    878     case KIT_DWARF_FC_FLAG:
    879       driver_printf("%s", a->u ? "true" : "false");
    880       break;
    881     case KIT_DWARF_FC_BLOCK: {
    882       uint32_t i;
    883       driver_printf("%u byte block:", a->block_len);
    884       for (i = 0; i < a->block_len; ++i) driver_printf(" %02x", a->block[i]);
    885       break;
    886     }
    887     case KIT_DWARF_FC_ADDR:
    888     case KIT_DWARF_FC_REF:
    889     case KIT_DWARF_FC_UDATA:
    890     default:
    891       driver_printf("0x%llx", (unsigned long long)a->u);
    892       break;
    893   }
    894 }
    895 
    896 static void dump_dwarf_die_attrs(KitDebugInfo* dbg, uint32_t die_offset,
    897                                  uint32_t depth) {
    898   KitDwarfAttrIter* ai = NULL;
    899   KitDwarfAttr a;
    900   uint32_t k;
    901   if (kit_dwarf_attr_iter_new(dbg, die_offset, &ai) != KIT_OK) return;
    902   while (kit_dwarf_attr_iter_next(ai, &a) == KIT_ITER_ITEM) {
    903     for (k = 0; k <= depth + 1; ++k) driver_printf("  ");
    904     dw_emit_code(kit_dwarf_attr_name(a.attr), a.attr);
    905     driver_printf(" (");
    906     dw_emit_code(kit_dwarf_form_name(a.form), a.form);
    907     driver_printf(") = ");
    908     dw_emit_attr_value(&a);
    909     driver_printf("\n");
    910   }
    911   kit_dwarf_attr_iter_free(ai);
    912 }
    913 
    914 static void dump_dwarf_info(KitDebugInfo* dbg) {
    915   KitDwarfCuIter* cui = NULL;
    916   KitDwarfDieIter* dii = NULL;
    917   KitDwarfCu cu;
    918   KitDwarfDie die;
    919   driver_printf(".debug_info contents:\n");
    920   if (kit_dwarf_cu_iter_new(dbg, &cui) == KIT_OK) {
    921     while (kit_dwarf_cu_iter_next(cui, &cu) == KIT_ITER_ITEM) {
    922       driver_printf(
    923           "  Compilation Unit @ offset 0x%x: version %u, abbrev_offset 0x%x, "
    924           "addr_size %u, length 0x%x\n",
    925           cu.offset, (unsigned)cu.version, cu.abbrev_offset,
    926           (unsigned)cu.address_size, cu.length);
    927     }
    928     kit_dwarf_cu_iter_free(cui);
    929   }
    930   if (kit_dwarf_die_iter_new(dbg, &dii) != KIT_OK) return;
    931   while (kit_dwarf_die_iter_next(dii, &die) == KIT_ITER_ITEM) {
    932     uint32_t k;
    933     for (k = 0; k <= die.depth; ++k) driver_printf("  ");
    934     driver_printf("<0x%x> ", die.offset);
    935     dw_emit_code(kit_dwarf_tag_name(die.tag), die.tag);
    936     driver_printf("\n");
    937     dump_dwarf_die_attrs(dbg, die.offset, die.depth);
    938   }
    939   kit_dwarf_die_iter_free(dii);
    940   driver_printf("\n");
    941 }
    942 
    943 static void dump_dwarf_abbrev(KitDebugInfo* dbg) {
    944   KitDwarfAbbrevIter* it = NULL;
    945   KitDwarfAbbrev ab;
    946   uint32_t cur_table = 0xffffffffu;
    947   driver_printf(".debug_abbrev contents:\n");
    948   if (kit_dwarf_abbrev_iter_new(dbg, &it) != KIT_OK) return;
    949   while (kit_dwarf_abbrev_iter_next(it, &ab) == KIT_ITER_ITEM) {
    950     KitDwarfAbbrevAttrIter* ait = NULL;
    951     KitDwarfAbbrevAttr aa;
    952     if (ab.table_offset != cur_table) {
    953       cur_table = ab.table_offset;
    954       driver_printf("  Abbrev table @ offset 0x%x:\n", cur_table);
    955     }
    956     driver_printf("    [%llu] ", (unsigned long long)ab.code);
    957     dw_emit_code(kit_dwarf_tag_name(ab.tag), ab.tag);
    958     driver_printf(" %s\n",
    959                   ab.has_children ? "[has children]" : "[no children]");
    960     if (kit_dwarf_abbrev_attr_iter_new(dbg, ab.table_offset, ab.code, &ait) !=
    961         KIT_OK)
    962       continue;
    963     while (kit_dwarf_abbrev_attr_iter_next(ait, &aa) == KIT_ITER_ITEM) {
    964       driver_printf("        ");
    965       dw_emit_code(kit_dwarf_attr_name(aa.attr), aa.attr);
    966       driver_printf("  ");
    967       dw_emit_code(kit_dwarf_form_name(aa.form), aa.form);
    968       driver_printf("\n");
    969     }
    970     kit_dwarf_abbrev_attr_iter_free(ait);
    971   }
    972   kit_dwarf_abbrev_iter_free(it);
    973   driver_printf("\n");
    974 }
    975 
    976 static void dump_dwarf_line(KitDebugInfo* dbg) {
    977   KitDwarfCuIter* cui = NULL;
    978   KitDwarfCu cu;
    979   driver_printf(".debug_line contents:\n");
    980   if (kit_dwarf_cu_iter_new(dbg, &cui) != KIT_OK) return;
    981   while (kit_dwarf_cu_iter_next(cui, &cu) == KIT_ITER_ITEM) {
    982     KitDwarfLineIter* li = NULL;
    983     KitDwarfLineRow row;
    984     if (kit_dwarf_line_iter_new(dbg, cu.offset, &li) != KIT_OK) continue;
    985     driver_printf("  CU @ offset 0x%x:\n", cu.offset);
    986     driver_printf("    %-18s %-6s %-6s %-4s %s\n", "Address", "File", "Line",
    987                   "Col", "Flags");
    988     while (kit_dwarf_line_iter_next(li, &row) == KIT_ITER_ITEM) {
    989       driver_printf("    0x%016llx %-6u %-6u %-4u %s%s\n",
    990                     (unsigned long long)row.address, row.file_index, row.line,
    991                     row.column, row.is_stmt ? "stmt " : "",
    992                     row.end_sequence ? "end_seq" : "");
    993     }
    994     kit_dwarf_line_iter_free(li);
    995   }
    996   kit_dwarf_cu_iter_free(cui);
    997   driver_printf("\n");
    998 }
    999 
   1000 static void dump_dwarf_str(KitDebugInfo* dbg) {
   1001   KitDwarfStrIter* it = NULL;
   1002   KitDwarfStr s;
   1003   driver_printf(".debug_str contents:\n");
   1004   if (kit_dwarf_str_iter_new(dbg, &it) != KIT_OK) return;
   1005   while (kit_dwarf_str_iter_next(it, &s) == KIT_ITER_ITEM) {
   1006     driver_printf("  0x%x  \"%.*s\"\n", s.offset, KIT_SLICE_ARG(s.str));
   1007   }
   1008   kit_dwarf_str_iter_free(it);
   1009   driver_printf("\n");
   1010 }
   1011 
   1012 static void dump_dwarf(const KitContext* ctx, KitObjFile* f,
   1013                        const ObjdumpOpts* opts) {
   1014   KitDebugInfo* dbg = NULL;
   1015   if (kit_dwarf_open(ctx, f, &dbg) != KIT_OK || !dbg) {
   1016     driver_errf(OBJDUMP_TOOL, "no DWARF debug info found");
   1017     return;
   1018   }
   1019   if (opts->dwarf & OBJDUMP_DWARF_INFO) dump_dwarf_info(dbg);
   1020   if (opts->dwarf & OBJDUMP_DWARF_ABBREV) dump_dwarf_abbrev(dbg);
   1021   if (opts->dwarf & OBJDUMP_DWARF_LINE) dump_dwarf_line(dbg);
   1022   if (opts->dwarf & OBJDUMP_DWARF_STR) dump_dwarf_str(dbg);
   1023   kit_dwarf_free(dbg);
   1024 }
   1025 
   1026 /* Dynamic relocations (-R). Unlike section relocations these aren't grouped
   1027  * by section, so we print one flat table in GNU `objdump -R` style. */
   1028 static void dump_dynrelocs(KitObjFile* f) {
   1029   KitObjRelocIter* it = NULL;
   1030   KitObjReloc r;
   1031   int any = 0;
   1032 
   1033   if (kit_obj_dynreliter_new(f, &it) != KIT_OK) return;
   1034   for (;;) {
   1035     KitIterResult res = kit_obj_reliter_next(it, &r);
   1036     if (res != KIT_ITER_ITEM) break;
   1037     if (!any) {
   1038       driver_printf("DYNAMIC RELOCATION RECORDS\n");
   1039       driver_printf("OFFSET           TYPE              VALUE\n");
   1040       any = 1;
   1041     }
   1042     if (r.addend) {
   1043       driver_printf(
   1044           "%016llx %-17s %.*s%c0x%llx\n", (unsigned long long)r.offset,
   1045           r.kind_name.len ? r.kind_name.s : "?",
   1046           KIT_SLICE_ARG(r.sym_name.len ? r.sym_name : KIT_SLICE_LIT("*ABS*")),
   1047           r.addend < 0 ? '-' : '+',
   1048           (unsigned long long)(r.addend < 0 ? -r.addend : r.addend));
   1049     } else {
   1050       driver_printf(
   1051           "%016llx %-17s %.*s\n", (unsigned long long)r.offset,
   1052           r.kind_name.len ? r.kind_name.s : "?",
   1053           KIT_SLICE_ARG(r.sym_name.len ? r.sym_name : KIT_SLICE_LIT("*ABS*")));
   1054     }
   1055   }
   1056   kit_obj_reliter_free(it);
   1057   driver_printf(any ? "\n" : "DYNAMIC RELOCATION RECORDS (none)\n\n");
   1058 }
   1059 
   1060 /* Format `perms` into the caller-supplied `buf[4]` and return it. The caller
   1061  * owns the storage, so there is no shared mutable state between calls. */
   1062 static const char* seg_perms_str(uint32_t perms, char buf[4]) {
   1063   buf[0] = (perms & KIT_SEG_R) ? 'r' : '-';
   1064   buf[1] = (perms & KIT_SEG_W) ? 'w' : '-';
   1065   buf[2] = (perms & KIT_SEG_X) ? 'x' : '-';
   1066   buf[3] = '\0';
   1067   return buf;
   1068 }
   1069 
   1070 /* align is a power of two; report it as 2**N like GNU objdump. */
   1071 static unsigned u32_log2(uint32_t v) {
   1072   unsigned n = 0;
   1073   while (v > 1) {
   1074     v >>= 1;
   1075     ++n;
   1076   }
   1077   return n;
   1078 }
   1079 
   1080 /* Private/program headers (-p): the linked-image view. PE images get the
   1081  * GNU objdump "PE32+ private headers" rendering (optional header + data
   1082  * directories + import tables); ELF / Mach-O get the entry/segments/dynamic
   1083  * view. Both are driven by the neutral kit_obj image API. Relocatable
   1084  * objects have no image and report so. */
   1085 static void dump_private(KitObjFile* f, const char* label) {
   1086   KitObjImageInfo info;
   1087   KitObjSegIter* sit = NULL;
   1088   KitObjDepIter* dit = NULL;
   1089   KitObjSegInfo seg;
   1090   KitObjDepInfo dep;
   1091   int have_info;
   1092 
   1093   if (kit_obj_fmt(f) == KIT_OBJ_COFF && kit_obj_kind(f) != KIT_OBJ_KIND_REL) {
   1094     dump_pe_private(f, label);
   1095     return;
   1096   }
   1097 
   1098   if (kit_obj_kind(f) == KIT_OBJ_KIND_REL) {
   1099     driver_printf(
   1100         "Private headers:\n"
   1101         "  relocatable object — no program or dynamic headers\n\n");
   1102     return;
   1103   }
   1104 
   1105   have_info = kit_obj_image_info(f, &info) == KIT_OK;
   1106   if (have_info) {
   1107     driver_printf("Image:\n");
   1108     driver_printf("  entry point  0x%016llx\n", (unsigned long long)info.entry);
   1109     driver_printf("  image base   0x%016llx\n",
   1110                   (unsigned long long)info.image_base);
   1111     if (info.interp.len)
   1112       driver_printf("  interpreter  %.*s\n", KIT_SLICE_ARG(info.interp));
   1113     driver_printf("\n");
   1114   }
   1115 
   1116   driver_printf("Program Header:\n");
   1117   if (kit_obj_segiter_new(f, &sit) == KIT_OK) {
   1118     int any = 0;
   1119     while (kit_obj_segiter_next(sit, &seg) == KIT_ITER_ITEM) {
   1120       char perms[4];
   1121       any = 1;
   1122       driver_printf(
   1123           "  %-12.*s off 0x%016llx vaddr 0x%016llx align 2**%u\n"
   1124           "               filesz 0x%016llx memsz 0x%016llx flags %s\n",
   1125           KIT_SLICE_ARG(seg.name.len ? seg.name : KIT_SLICE_LIT("LOAD")),
   1126           (unsigned long long)seg.file_off, (unsigned long long)seg.vaddr,
   1127           u32_log2(seg.align), (unsigned long long)seg.file_size,
   1128           (unsigned long long)seg.vsize, seg_perms_str(seg.perms, perms));
   1129     }
   1130     kit_obj_segiter_free(sit);
   1131     if (!any) driver_printf("  (none)\n");
   1132   }
   1133   driver_printf("\n");
   1134 
   1135   driver_printf("Dynamic Section:\n");
   1136   if (have_info && info.soname.len)
   1137     driver_printf("  SONAME       %.*s\n", KIT_SLICE_ARG(info.soname));
   1138   if (kit_obj_depiter_new(f, &dit) == KIT_OK) {
   1139     while (kit_obj_depiter_next(dit, &dep) == KIT_ITER_ITEM) {
   1140       uint32_t k;
   1141       driver_printf("  NEEDED       %.*s\n", KIT_SLICE_ARG(dep.name));
   1142       for (k = 0; k < dep.nimports; ++k)
   1143         driver_printf("                 %.*s\n", KIT_SLICE_ARG(dep.imports[k]));
   1144     }
   1145     kit_obj_depiter_free(dit);
   1146   }
   1147   {
   1148     KitObjRpathIter* rit = NULL;
   1149     KitSlice rpath;
   1150     if (kit_obj_rpathiter_new(f, &rit) == KIT_OK) {
   1151       while (kit_obj_rpathiter_next(rit, &rpath) == KIT_ITER_ITEM)
   1152         driver_printf("  RPATH        %.*s\n", KIT_SLICE_ARG(rpath));
   1153       kit_obj_rpathiter_free(rit);
   1154     }
   1155   }
   1156   driver_printf("\n");
   1157 }
   1158 
   1159 static void dump_obj(const KitContext* ctx, const KitDisasmContext* dctx,
   1160                      const char* label, KitObjFile* f, const ObjdumpOpts* opts,
   1161                      const KitSlice* image) {
   1162   KitTargetSpec target = kit_obj_target(f);
   1163   KitObjFmt fmt = kit_obj_fmt(f);
   1164   char fmt_buf[FMT_STR_CAP];
   1165 
   1166   driver_printf("%.*s:\tfile format %.*s-%.*s\n\n",
   1167                 KIT_SLICE_ARG(kit_slice_cstr(label)),
   1168                 KIT_SLICE_ARG(kit_slice_cstr(
   1169                     fmt_str(fmt, target.ptr_size, fmt_buf, sizeof fmt_buf))),
   1170                 KIT_SLICE_ARG(kit_slice_cstr(arch_str(target.arch))));
   1171 
   1172   if (opts->f) dump_file_header(f, label);
   1173   if (opts->h) dump_sections(f, opts);
   1174   if (opts->h) dump_groups(f, opts);
   1175   if (opts->t) dump_symbols(f, opts, 0);
   1176   if (opts->T) dump_symbols(f, opts, 1);
   1177   if (opts->p) dump_private(f, label);
   1178   if (opts->s) dump_hex(f, opts);
   1179   if (opts->d || opts->D) dump_disasm(dctx, f, opts, image);
   1180   if (opts->r) dump_relocs(f, opts);
   1181   if (opts->R) dump_dynrelocs(f);
   1182   if (opts->dwarf) dump_dwarf(ctx, f, opts);
   1183 }
   1184 
   1185 static int dump_archive(const char* path, const KitSlice* input,
   1186                         const KitContext* ctx, const KitDisasmContext* dctx,
   1187                         const ObjdumpOpts* opts) {
   1188   KitArIter* it = NULL;
   1189   KitArMember member;
   1190   char label[256];
   1191   int j;
   1192 
   1193   driver_printf("In archive %.*s:\n\n", KIT_SLICE_ARG(kit_slice_cstr(path)));
   1194 
   1195   if (kit_ar_iter_new(ctx, input, &it) != KIT_OK) return 1;
   1196   for (;;) {
   1197     KitIterResult r = kit_ar_iter_next(it, &member);
   1198     KitSlice min;
   1199     KitObjFile* f = NULL;
   1200     if (r != KIT_ITER_ITEM) break;
   1201 
   1202     /* Build "archive.a(member.o)" label. */
   1203     j = 0;
   1204     {
   1205       const char* p = path;
   1206       while (*p && j < 230) label[j++] = *p++;
   1207     }
   1208     label[j++] = '(';
   1209     {
   1210       size_t k = 0;
   1211       while (k < member.name.len && j < 252) label[j++] = member.name.s[k++];
   1212     }
   1213     label[j++] = ')';
   1214     label[j] = '\0';
   1215 
   1216     min.data = member.data;
   1217     min.len = member.size;
   1218 
   1219     if (kit_obj_open(ctx, member.name, &min, &f) != KIT_OK) {
   1220       driver_errf(OBJDUMP_TOOL, "failed to parse member: %.*s",
   1221                   KIT_SLICE_ARG(kit_slice_cstr(label)));
   1222       continue;
   1223     }
   1224     dump_obj(ctx, dctx, label, f, opts, &min);
   1225     kit_obj_free(f);
   1226   }
   1227   kit_ar_iter_free(it);
   1228 
   1229   return 0;
   1230 }
   1231 
   1232 static int parse_short_flags(const char* arg, ObjdumpOpts* o) {
   1233   const char* p;
   1234   for (p = arg + 1; *p; ++p) {
   1235     switch (*p) {
   1236       case 'f':
   1237         o->f = 1;
   1238         break;
   1239       case 'h':
   1240         o->h = 1;
   1241         break;
   1242       case 't':
   1243         o->t = 1;
   1244         break;
   1245       case 'd':
   1246         o->d = 1;
   1247         break;
   1248       case 'D':
   1249         o->D = 1;
   1250         break;
   1251       case 'r':
   1252         o->r = 1;
   1253         break;
   1254       case 's':
   1255         o->s = 1;
   1256         break;
   1257       case 'p':
   1258         o->p = 1;
   1259         break;
   1260       case 'T':
   1261         o->T = 1;
   1262         break;
   1263       case 'R':
   1264         o->R = 1;
   1265         break;
   1266       case 'x':
   1267         o->f = 1;
   1268         o->h = 1;
   1269         o->r = 1;
   1270         o->t = 1;
   1271         break;
   1272       default:
   1273         driver_errf(OBJDUMP_TOOL, "unknown flag: -%c", *p);
   1274         return -1;
   1275     }
   1276   }
   1277   return 0;
   1278 }
   1279 
   1280 static int parse_long_flag(const char* arg, ObjdumpOpts* o) {
   1281   if (driver_streq(arg, "--file-headers")) {
   1282     o->f = 1;
   1283     return 1;
   1284   }
   1285   if (driver_streq(arg, "--section-headers")) {
   1286     o->h = 1;
   1287     return 1;
   1288   }
   1289   if (driver_streq(arg, "--syms")) {
   1290     o->t = 1;
   1291     return 1;
   1292   }
   1293   if (driver_streq(arg, "--reloc")) {
   1294     o->r = 1;
   1295     return 1;
   1296   }
   1297   if (driver_streq(arg, "--full-contents")) {
   1298     o->s = 1;
   1299     return 1;
   1300   }
   1301   if (driver_streq(arg, "--disassemble")) {
   1302     o->d = 1;
   1303     return 1;
   1304   }
   1305   if (driver_streq(arg, "--all-headers")) {
   1306     o->f = 1;
   1307     o->h = 1;
   1308     o->r = 1;
   1309     o->t = 1;
   1310     return 1;
   1311   }
   1312   if (driver_streq(arg, "--private-headers")) {
   1313     o->p = 1;
   1314     return 1;
   1315   }
   1316   if (driver_streq(arg, "--dynamic-syms")) {
   1317     o->T = 1;
   1318     return 1;
   1319   }
   1320   if (driver_streq(arg, "--dynamic-reloc")) {
   1321     o->R = 1;
   1322     return 1;
   1323   }
   1324   return 0;
   1325 }
   1326 
   1327 /* Match one comma-separated --dwarf section selector against `tok` of
   1328  * length `n`. Returns the OBJDUMP_DWARF_* bit, or 0 if unrecognized. */
   1329 static unsigned dwarf_sel_bit(const char* tok, size_t n) {
   1330   if (n == 4 && memcmp(tok, "info", 4) == 0) return OBJDUMP_DWARF_INFO;
   1331   if (n == 6 && memcmp(tok, "abbrev", 6) == 0) return OBJDUMP_DWARF_ABBREV;
   1332   if (n == 4 && memcmp(tok, "line", 4) == 0) return OBJDUMP_DWARF_LINE;
   1333   if (n == 3 && memcmp(tok, "str", 3) == 0) return OBJDUMP_DWARF_STR;
   1334   return 0;
   1335 }
   1336 
   1337 /* Parse a `--dwarf` / `--dwarf=sec,sec` argument into o->dwarf. Returns 1
   1338  * if `arg` was a dwarf flag (handled), 0 if not, -1 on a bad selector. */
   1339 static int parse_dwarf_flag(const char* arg, ObjdumpOpts* o) {
   1340   const char *list, *p;
   1341   if (driver_streq(arg, "--dwarf")) {
   1342     o->dwarf = OBJDUMP_DWARF_ALL;
   1343     return 1;
   1344   }
   1345   if (strncmp(arg, "--dwarf=", 8) != 0) return 0;
   1346   list = arg + 8;
   1347   for (p = list; *p;) {
   1348     const char* start = p;
   1349     unsigned bit;
   1350     while (*p && *p != ',') ++p;
   1351     bit = dwarf_sel_bit(start, (size_t)(p - start));
   1352     if (!bit) return -1;
   1353     o->dwarf |= bit;
   1354     if (*p == ',') ++p;
   1355   }
   1356   if (!o->dwarf) return -1;
   1357   return 1;
   1358 }
   1359 
   1360 int driver_objdump(int argc, char** argv) {
   1361   DriverEnv env;
   1362   ObjdumpOpts opts = {0};
   1363   int i;
   1364   int rc = 0;
   1365   int saw_input = 0;
   1366   int saw_op = 0;
   1367   int options = 1;
   1368   KitContext ctx;
   1369   KitDisasmContext dctx;
   1370   KitDisasmContext* dctx_p = NULL;
   1371 
   1372   /* For objdump, -h means "section headers" (GNU objdump convention),
   1373    * so we only honour --help / -help (and bare invocation) as help. */
   1374   if (argc < 2 || driver_argv_wants_help(argc, argv, 0)) {
   1375     driver_help_objdump();
   1376     return 0;
   1377   }
   1378 
   1379   driver_env_init(&env);
   1380   ctx = driver_env_to_context(&env);
   1381 
   1382   /* First pass: parse flags. */
   1383   for (i = 1; i < argc; ++i) {
   1384     const char* a = argv[i];
   1385     if (options && driver_streq(a, "--")) {
   1386       options = 0;
   1387       continue;
   1388     }
   1389     if (!options) continue;
   1390     if (a[0] != '-' || a[1] == '\0') continue;
   1391     if (driver_streq(a, "-j")) {
   1392       if (i + 1 >= argc) {
   1393         driver_errf(OBJDUMP_TOOL, "%.*s",
   1394                     KIT_SLICE_ARG(KIT_SLICE_LIT("-j requires a section name")));
   1395         rc = 2;
   1396         goto done;
   1397       }
   1398       if (opts.nj >= MAX_J_FILTERS) {
   1399         driver_errf(OBJDUMP_TOOL, "%.*s",
   1400                     KIT_SLICE_ARG(KIT_SLICE_LIT("too many -j filters")));
   1401         rc = 2;
   1402         goto done;
   1403       }
   1404       opts.j[opts.nj++] = argv[++i];
   1405       continue;
   1406     }
   1407     {
   1408       int dr = parse_dwarf_flag(a, &opts);
   1409       if (dr < 0) {
   1410         driver_errf(OBJDUMP_TOOL, "%.*s",
   1411                     KIT_SLICE_ARG(KIT_SLICE_LIT(
   1412                         "--dwarf sections: info, abbrev, line, str")));
   1413         rc = 2;
   1414         goto done;
   1415       }
   1416       if (dr > 0) continue;
   1417     }
   1418     if (parse_long_flag(a, &opts)) continue;
   1419     if (parse_short_flags(a, &opts) != 0) {
   1420       objdump_usage();
   1421       rc = 2;
   1422       goto done;
   1423     }
   1424   }
   1425 
   1426   saw_op = opts.f || opts.h || opts.t || opts.d || opts.D || opts.r || opts.s ||
   1427            opts.p || opts.T || opts.R || opts.dwarf != 0;
   1428   if (!saw_op) { /* Default = -h -t (matches the prior behavior). */
   1429     opts.h = 1;
   1430     opts.t = 1;
   1431   }
   1432 
   1433   /* Disassembler context: a target + ctx value pair. Disasm consults
   1434    * the per-file object reader for annotation. */
   1435   if (opts.d || opts.D) {
   1436     dctx.target = NULL;
   1437     dctx.context = ctx;
   1438     dctx_p = &dctx;
   1439   }
   1440 
   1441   /* Second pass: process inputs. */
   1442   options = 1;
   1443   for (i = 1; i < argc && rc == 0; ++i) {
   1444     const char* a = argv[i];
   1445     KitFileData fd = {0};
   1446     KitSlice input;
   1447     KitBinFmt bin;
   1448 
   1449     if (options && driver_streq(a, "--")) {
   1450       options = 0;
   1451       continue;
   1452     }
   1453 
   1454     if (options && a[0] == '-' && a[1] != '\0') {
   1455       if (driver_streq(a, "-j")) ++i;
   1456       continue;
   1457     }
   1458     saw_input = 1;
   1459 
   1460     if (ctx.file_io->read_all(ctx.file_io->user, a, &fd) != KIT_OK) {
   1461       driver_errf(OBJDUMP_TOOL, "failed to read: %.*s",
   1462                   KIT_SLICE_ARG(kit_slice_cstr(a)));
   1463       rc = 1;
   1464       break;
   1465     }
   1466 
   1467     input.data = fd.data;
   1468     input.len = fd.size;
   1469 
   1470     bin = kit_detect_fmt(input.data, input.len);
   1471     switch (bin) {
   1472       case KIT_BIN_AR:
   1473         rc = dump_archive(a, &input, &ctx, dctx_p, &opts);
   1474         break;
   1475       case KIT_BIN_ELF:
   1476       case KIT_BIN_COFF:
   1477       case KIT_BIN_PE:
   1478       case KIT_BIN_MACHO:
   1479       case KIT_BIN_WASM: {
   1480         KitObjFile* f = NULL;
   1481         /* PE executables / DLLs open through kit_obj_open like every other
   1482          * format (read_coff dispatches the DOS 'MZ' magic to the image
   1483          * reader); the whole dump flows through the neutral dump_obj path. */
   1484         if (kit_obj_open(&ctx, kit_slice_cstr(a), &input, &f) != KIT_OK) {
   1485           driver_errf(OBJDUMP_TOOL, "failed to parse: %.*s",
   1486                       KIT_SLICE_ARG(kit_slice_cstr(a)));
   1487           rc = 1;
   1488         } else {
   1489           dump_obj(&ctx, dctx_p, a, f, &opts, &input);
   1490           kit_obj_free(f);
   1491         }
   1492         break;
   1493       }
   1494       case KIT_BIN_UNKNOWN:
   1495       default:
   1496         driver_errf(OBJDUMP_TOOL, "unsupported file format: %.*s",
   1497                     KIT_SLICE_ARG(kit_slice_cstr(a)));
   1498         rc = 1;
   1499         break;
   1500     }
   1501 
   1502     ctx.file_io->release(ctx.file_io->user, &fd);
   1503   }
   1504 
   1505   if (rc == 0 && !saw_input) {
   1506     objdump_usage();
   1507     rc = 2;
   1508   }
   1509 
   1510 done:
   1511   driver_env_fini(&env);
   1512   return rc;
   1513 }