kit

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

strip.c (21028B)


      1 #include <kit/archive.h>
      2 #include <kit/core.h>
      3 #include <kit/object.h>
      4 #include <stdint.h>
      5 #include <string.h>
      6 
      7 #include "driver.h"
      8 #include "objedit.h"
      9 
     10 /* `kit strip` — drop debug sections and / or unwanted symbols from an object,
     11  * linked image, or static archive, then write the result back.
     12  *
     13  * Operations (the last one wins; default is --strip-all):
     14  *   --strip-debug        drop sections whose kind is KIT_SEC_DEBUG
     15  *   --strip-unneeded     drop debug + symbols not referenced by any reloc
     16  *   --strip-all          drop debug + every non-essential symbol (default)
     17  *
     18  * Filters applied on top of the operation:
     19  *   --keep-symbol=NAME, -K NAME    keep NAME even if the operation would drop
     20  * it
     21  *   --strip-symbol=NAME, -N NAME   always drop NAME
     22  *
     23  * I/O:
     24  *   -o PATH              write output to PATH (else rewrite the input in place)
     25  */
     26 
     27 #define STRIP_TOOL "strip"
     28 
     29 void driver_help_strip(void) {
     30   driver_printf(
     31       "%.*s",
     32       KIT_SLICE_ARG(KIT_SLICE_LIT(
     33           "kit strip — drop debug sections and/or symbols\n"
     34           "\n"
     35           "USAGE\n"
     36           "  kit strip [OPTIONS] FILE\n"
     37           "\n"
     38           "DESCRIPTION\n"
     39           "  Copies an object, linked executable/shared library, or static\n"
     40           "  archive while removing selected debug sections and symbols. By\n"
     41           "  default FILE is replaced in place; use -o PATH to preserve it.\n"
     42           "\n"
     43           "OPTIONS\n"
     44           "  -h, --help            Show this help and exit\n"
     45           "\n"
     46           "OPERATIONS (last one wins; default is --strip-all)\n"
     47           "  --strip-debug         remove debug-info sections\n"
     48           "  --strip-unneeded      remove debug + symbols not needed by "
     49           "relocs\n"
     50           "  --strip-all           remove debug + all non-essential symbols\n"
     51           "  --remove-signature    allow a signed linked image to be changed;\n"
     52           "                        the result must be signed again\n"
     53           "\n"
     54           "SYMBOL FILTERS (may repeat)\n"
     55           "  --keep-symbol=NAME, -K NAME   keep NAME even when the operation\n"
     56           "                                would otherwise drop it\n"
     57           "  --strip-symbol=NAME, -N NAME  always drop NAME\n"
     58           "\n"
     59           "OUTPUT\n"
     60           "  -o PATH               write to PATH (default: rewrite FILE in "
     61           "place)\n"
     62           "\n"
     63           "LINKED IMAGES\n"
     64           "  ELF, Mach-O, and PE executables/shared libraries are rewritten in\n"
     65           "  place at the format layer without relinking. Loader-required\n"
     66           "  metadata and executable permissions are preserved. Signed images\n"
     67           "  fail unchanged unless --remove-signature is explicit. Symbol\n"
     68           "  keep/drop filters apply only to relocatable inputs.\n"
     69           "\n"
     70           "EXAMPLES\n"
     71           "  kit strip --strip-debug -o foo.stripped.o foo.o\n"
     72           "  kit strip --strip-debug -o app.stripped app\n"
     73           "  kit objdump -h foo.stripped.o\n"
     74           "  kit strip --strip-unneeded -o libfoo.stripped.a libfoo.a\n"
     75           "  kit nm -g libfoo.stripped.a\n"
     76           "\n"
     77           "EXIT CODES\n"
     78           "  0   success           1   I/O or strip error           2   bad "
     79           "usage\n")));
     80 }
     81 
     82 typedef enum StripOp {
     83   STRIP_OP_DEBUG,
     84   STRIP_OP_UNNEEDED,
     85   STRIP_OP_ALL,
     86 } StripOp;
     87 
     88 typedef struct StripOpts {
     89   StripOp op;
     90   const char** keep;
     91   uint32_t nkeep;
     92   uint32_t cap_keep;
     93   const char** strip;
     94   uint32_t nstrip;
     95   uint32_t cap_strip;
     96   const char* output;
     97   const char* input;
     98   int remove_signature;
     99 } StripOpts;
    100 
    101 static int push_name(DriverEnv* env, const char*** arr, uint32_t* n,
    102                      uint32_t* cap, const char* name) {
    103   if (*n >= *cap) {
    104     uint32_t newcap = *cap ? *cap * 2u : 8u;
    105     const char** nb =
    106         (const char**)driver_alloc_zeroed(env, (size_t)newcap * sizeof(*nb));
    107     if (!nb) {
    108       driver_errf(STRIP_TOOL, "out of memory");
    109       return -1;
    110     }
    111     if (*arr) {
    112       memcpy(nb, *arr, (size_t)(*n) * sizeof(*nb));
    113       driver_free(env, (void*)*arr, (size_t)(*cap) * sizeof(*nb));
    114     }
    115     *arr = nb;
    116     *cap = newcap;
    117   }
    118   (*arr)[(*n)++] = name;
    119   return 0;
    120 }
    121 
    122 static int parse_name_arg(int* i, int argc, char** argv, const char* flag,
    123                           const char* short_flag, const char** out) {
    124   const char* a = argv[*i];
    125   size_t flen = kit_slice_cstr(flag).len;
    126   /* --flag=NAME ; prefix match */
    127   if (driver_strneq(a, flag, flen) && a[flen] == '=') {
    128     *out = a + flen + 1;
    129     return 1;
    130   }
    131   /* --flag NAME (no '='): treat as "next argv" form, used when --flag is
    132    * passed without value. Not standard; skip. */
    133   if (driver_streq(a, flag)) {
    134     if (*i + 1 >= argc) return -1;
    135     *out = argv[++(*i)];
    136     return 1;
    137   }
    138   /* -K NAME / -N NAME */
    139   if (short_flag && driver_streq(a, short_flag)) {
    140     if (*i + 1 >= argc) return -1;
    141     *out = argv[++(*i)];
    142     return 1;
    143   }
    144   return 0;
    145 }
    146 
    147 /* The core strip pass: drop debug sections + prune symbols per the level and
    148  * keep-list (kit_obj_builder_strip in libkit), then layer the driver-only
    149  * --strip-symbol explicit drops on top. Running the explicit drops last is
    150  * what makes --strip-symbol win over --keep-symbol when a name is on both
    151  * lists. Mutations are issued against the builder; the emit-time sweep cleans
    152  * up cascades (orphan relocs against removed sections, etc.). */
    153 static int strip_one_builder(DriverEnv* env, KitObjFile* of, KitObjBuilder* b,
    154                              const StripOpts* opts, const char* input_name) {
    155   KitSlice* keep = NULL;
    156   int level = opts->op == STRIP_OP_DEBUG      ? KIT_STRIP_DEBUG
    157               : opts->op == STRIP_OP_UNNEEDED ? KIT_STRIP_UNNEEDED
    158                                               : KIT_STRIP_ALL;
    159   uint32_t i;
    160   int rc = 1;
    161 
    162   /* Adapt the --keep-symbol C-string list to the KitSlice keep_names shape. */
    163   if (opts->nkeep) {
    164     keep =
    165         (KitSlice*)driver_alloc_zeroed(env, (size_t)opts->nkeep * sizeof(*keep));
    166     if (!keep) {
    167       driver_errf(STRIP_TOOL, "%s: out of memory", input_name);
    168       return 1;
    169     }
    170     for (i = 0; i < opts->nkeep; ++i) keep[i] = kit_slice_cstr(opts->keep[i]);
    171   }
    172 
    173   if (kit_obj_builder_strip(b, level, keep, opts->nkeep) != KIT_OK) {
    174     driver_errf(STRIP_TOOL, "%s: strip failed", input_name);
    175     goto done;
    176   }
    177 
    178   /* --strip-symbol: always drop the named symbols (overrides keep + policy). */
    179   if (opts->nstrip) {
    180     KitObjSymIter* sit = NULL;
    181     if (kit_obj_symiter_new(of, &sit) != KIT_OK) {
    182       driver_errf(STRIP_TOOL, "%s: out of memory", input_name);
    183       goto done;
    184     }
    185     for (;;) {
    186       KitObjSymInfo si;
    187       KitIterResult ir = kit_obj_symiter_next(sit, &si);
    188       if (ir != KIT_ITER_ITEM) break;
    189       if (driver_name_in_list(si.name, opts->strip, opts->nstrip))
    190         kit_obj_builder_remove_symbol(b, si.id);
    191     }
    192     kit_obj_symiter_free(sit);
    193   }
    194   rc = 0;
    195 
    196 done:
    197   if (keep) driver_free(env, keep, (size_t)opts->nkeep * sizeof(*keep));
    198   return rc;
    199 }
    200 
    201 static int strip_object_bytes(DriverEnv* env, const KitContext* ctx,
    202                               const KitSlice* input, const StripOpts* opts,
    203                               const char* input_name, uint8_t** out_data,
    204                               size_t* out_size) {
    205   KitObjFile* of = NULL;
    206   KitObjBuilder* b;
    207   KitWriter* w = NULL;
    208   KitStatus st;
    209   size_t n = 0;
    210   const uint8_t* data;
    211   uint8_t* copy;
    212   int rc = 1;
    213 
    214   *out_data = NULL;
    215   *out_size = 0;
    216 
    217   if (kit_obj_open(ctx, kit_slice_cstr(input_name), input, &of) != KIT_OK) {
    218     driver_errf(STRIP_TOOL, "%s: not a recognized object", input_name);
    219     return 1;
    220   }
    221   if (kit_writer_mem(env->heap, &w) != KIT_OK || !w) {
    222     driver_errf(STRIP_TOOL, "%s: out of memory", input_name);
    223     kit_obj_free(of);
    224     return 1;
    225   }
    226   if (kit_obj_kind(of) != KIT_OBJ_KIND_REL) {
    227     KitLinkedRewriteOptions rewrite_opts = {0};
    228     if (opts->nkeep || opts->nstrip) {
    229       driver_errf(STRIP_TOOL,
    230                   "%s: symbol keep/drop filters are unsupported for linked "
    231                   "images",
    232                   input_name);
    233       kit_writer_close(w);
    234       kit_obj_free(of);
    235       return 1;
    236     }
    237     rewrite_opts.strip_level = opts->op == STRIP_OP_DEBUG
    238                                    ? KIT_LINKED_STRIP_DEBUG
    239                                    : KIT_LINKED_STRIP_ALL;
    240     rewrite_opts.remove_signature = opts->remove_signature != 0;
    241     st = kit_obj_rewrite_linked(ctx, kit_slice_cstr(input_name), input,
    242                                 &rewrite_opts, w, NULL);
    243     if (st != KIT_OK) {
    244       if (st == KIT_NOMEM)
    245         driver_errf(STRIP_TOOL, "%s: out of memory", input_name);
    246       else if (st == KIT_IO)
    247         driver_errf(STRIP_TOOL, "%s: rewrite output failed", input_name);
    248       kit_writer_close(w);
    249       kit_obj_free(of);
    250       return 1;
    251     }
    252   } else {
    253     b = kit_obj_file_builder(of);
    254     if (!b) {
    255       driver_errf(STRIP_TOOL, "%s: no builder for object", input_name);
    256       kit_writer_close(w);
    257       kit_obj_free(of);
    258       return 1;
    259     }
    260     if (strip_one_builder(env, of, b, opts, input_name) != 0) {
    261       kit_writer_close(w);
    262       kit_obj_free(of);
    263       return 1;
    264     }
    265     if (kit_obj_builder_emit(b, w) != KIT_OK) {
    266       driver_errf(STRIP_TOOL, "%s: emit failed", input_name);
    267       kit_writer_close(w);
    268       kit_obj_free(of);
    269       return 1;
    270     }
    271   }
    272   data = kit_writer_mem_bytes(w, &n);
    273   copy = (uint8_t*)driver_alloc(env, n ? n : 1u);
    274   if (!copy) {
    275     driver_errf(STRIP_TOOL, "%s: out of memory", input_name);
    276     kit_writer_close(w);
    277     kit_obj_free(of);
    278     return 1;
    279   }
    280   if (n) memcpy(copy, data, n);
    281   kit_writer_close(w);
    282   kit_obj_free(of);
    283 
    284   *out_data = copy;
    285   *out_size = n;
    286   rc = 0;
    287   return rc;
    288 }
    289 
    290 /* Strip every object member of an archive, write a fresh archive with
    291  * a refreshed System-V symbol index. Non-object members pass through
    292  * unchanged. */
    293 static int strip_archive(DriverEnv* env, const KitContext* ctx,
    294                          const KitSlice* input, const StripOpts* opts,
    295                          const char* output_path) {
    296   KitArIter* it = NULL;
    297   KitArMember m;
    298   KitArInput* members = NULL;
    299   uint8_t** owned_data = NULL;
    300   size_t* owned_size = NULL;
    301   char* name_storage = NULL;
    302   size_t name_bytes_total = 0;
    303   uint32_t nmembers = 0, k;
    304   KitArMemberSymbols* msyms = NULL;
    305   void** sym_allocs = NULL;
    306   KitWriter* out = NULL;
    307   KitArWriteOptions opts_ar = {0};
    308   int rc = 1;
    309 
    310   /* Pass 1: count members + total name bytes. */
    311   if (kit_ar_iter_new(ctx, input, &it) != KIT_OK) {
    312     driver_errf(STRIP_TOOL, "%s: not a recognized archive", opts->input);
    313     return 1;
    314   }
    315   for (;;) {
    316     KitIterResult r = kit_ar_iter_next(it, &m);
    317     if (r != KIT_ITER_ITEM) break;
    318     nmembers++;
    319     name_bytes_total += m.name.len + 1;
    320   }
    321   kit_ar_iter_free(it);
    322   it = NULL;
    323 
    324   if (nmembers) {
    325     members = (KitArInput*)driver_alloc_zeroed(
    326         env, (size_t)nmembers * sizeof(*members));
    327     owned_data = (uint8_t**)driver_alloc_zeroed(
    328         env, (size_t)nmembers * sizeof(*owned_data));
    329     owned_size = (size_t*)driver_alloc_zeroed(
    330         env, (size_t)nmembers * sizeof(*owned_size));
    331     if (!members || !owned_data || !owned_size) {
    332       driver_errf(STRIP_TOOL, "out of memory");
    333       goto done;
    334     }
    335   }
    336   if (name_bytes_total) {
    337     name_storage = (char*)driver_alloc_zeroed(env, name_bytes_total);
    338     if (!name_storage) {
    339       driver_errf(STRIP_TOOL, "out of memory");
    340       goto done;
    341     }
    342   }
    343 
    344   /* Pass 2: walk members; strip object members, pass others through. */
    345   if (kit_ar_iter_new(ctx, input, &it) != KIT_OK) {
    346     driver_errf(STRIP_TOOL, "iter re-open failed");
    347     goto done;
    348   }
    349   {
    350     size_t cursor = 0;
    351     k = 0;
    352     while (k < nmembers) {
    353       KitIterResult r = kit_ar_iter_next(it, &m);
    354       size_t j;
    355       char* dst;
    356       KitBinFmt fmt;
    357       KitSlice mbytes;
    358       char* member_name = NULL;
    359       size_t member_name_size = 0;
    360       if (r != KIT_ITER_ITEM) break;
    361       dst = name_storage + cursor;
    362       for (j = 0; j < m.name.len; ++j) *dst++ = m.name.s[j];
    363       *dst++ = '\0';
    364       members[k].name.s = name_storage + cursor;
    365       members[k].name.len = m.name.len;
    366       cursor = (size_t)(dst - name_storage);
    367 
    368       mbytes.data = m.data;
    369       mbytes.len = m.size;
    370       fmt = kit_detect_fmt(m.data, m.size);
    371       if (fmt == KIT_BIN_ELF || fmt == KIT_BIN_COFF || fmt == KIT_BIN_MACHO ||
    372           fmt == KIT_BIN_WASM) {
    373         uint8_t* sd = NULL;
    374         size_t ss = 0;
    375         size_t archive_len = strlen(opts->input);
    376         member_name_size = archive_len + m.name.len + 3u;
    377         member_name = (char*)driver_alloc(env, member_name_size);
    378         if (!member_name) {
    379           driver_errf(STRIP_TOOL, "%s(%.*s): out of memory", opts->input,
    380                       (int)m.name.len, m.name.s);
    381           kit_ar_iter_free(it);
    382           it = NULL;
    383           goto done;
    384         }
    385         memcpy(member_name, opts->input, archive_len);
    386         member_name[archive_len] = '(';
    387         memcpy(member_name + archive_len + 1u, m.name.s, m.name.len);
    388         member_name[archive_len + 1u + m.name.len] = ')';
    389         member_name[member_name_size - 1u] = '\0';
    390         if (strip_object_bytes(env, ctx, &mbytes, opts, member_name, &sd, &ss) !=
    391             0) {
    392           driver_free(env, member_name, member_name_size);
    393           kit_ar_iter_free(it);
    394           it = NULL;
    395           goto done;
    396         }
    397         driver_free(env, member_name, member_name_size);
    398         owned_data[k] = sd;
    399         owned_size[k] = ss;
    400         members[k].bytes.data = sd;
    401         members[k].bytes.len = ss;
    402       } else {
    403         members[k].bytes.data = m.data;
    404         members[k].bytes.len = m.size;
    405       }
    406       k++;
    407     }
    408   }
    409   kit_ar_iter_free(it);
    410   it = NULL;
    411 
    412   /* Pass 3: rebuild the System-V symbol index from the new bytes. */
    413   if (nmembers) {
    414     msyms = (KitArMemberSymbols*)driver_alloc_zeroed(
    415         env, (size_t)nmembers * sizeof(*msyms));
    416     sym_allocs = (void**)driver_alloc_zeroed(
    417         env, (size_t)nmembers * sizeof(*sym_allocs));
    418     if (!msyms || !sym_allocs) {
    419       driver_errf(STRIP_TOOL, "out of memory");
    420       goto done;
    421     }
    422     for (k = 0; k < nmembers; ++k) {
    423       if (kit_obj_global_syms(ctx, &members[k].bytes, &msyms[k],
    424                               &sym_allocs[k]) != KIT_OK) {
    425         driver_errf(STRIP_TOOL, "out of memory");
    426         goto done;
    427       }
    428     }
    429   }
    430 
    431   if (ctx->file_io->open_writer(ctx->file_io->user, output_path, &out) !=
    432       KIT_OK) {
    433     driver_errf(STRIP_TOOL, "failed to open: %.*s",
    434                 KIT_SLICE_ARG(kit_slice_cstr(output_path)));
    435     goto done;
    436   }
    437   opts_ar.epoch = driver_epoch_from_env();
    438   opts_ar.long_names = 1;
    439   opts_ar.symbol_index = 1;
    440   opts_ar.member_symbols = msyms;
    441   rc = kit_ar_write(out, members, nmembers, &opts_ar) == KIT_OK ? 0 : 1;
    442   if (rc == 0 && kit_writer_status(out) != KIT_OK) rc = 1;
    443 
    444 done:
    445   if (out) {
    446     if (rc != 0) driver_writer_abort(out);
    447     kit_writer_close(out);
    448   }
    449   if (it) kit_ar_iter_free(it);
    450   if (sym_allocs) {
    451     for (k = 0; k < nmembers; ++k) {
    452       if (sym_allocs[k]) kit_obj_global_syms_free(ctx, sym_allocs[k]);
    453     }
    454     driver_free(env, sym_allocs, (size_t)nmembers * sizeof(*sym_allocs));
    455   }
    456   if (msyms) driver_free(env, msyms, (size_t)nmembers * sizeof(*msyms));
    457   if (owned_data) {
    458     for (k = 0; k < nmembers; ++k) {
    459       if (owned_data[k]) driver_free(env, owned_data[k], owned_size[k]);
    460     }
    461     driver_free(env, owned_data, (size_t)nmembers * sizeof(*owned_data));
    462   }
    463   if (owned_size)
    464     driver_free(env, owned_size, (size_t)nmembers * sizeof(*owned_size));
    465   if (members) driver_free(env, members, (size_t)nmembers * sizeof(*members));
    466   if (name_storage) driver_free(env, name_storage, name_bytes_total);
    467   return rc;
    468 }
    469 
    470 int driver_strip(int argc, char** argv) {
    471   DriverEnv env;
    472   KitContext ctx;
    473   StripOpts opts;
    474   KitFileData input_fd = {0};
    475   KitSlice input;
    476   KitWriter* w = NULL;
    477   uint8_t* out_data = NULL;
    478   size_t out_size = 0;
    479   int have_input = 0;
    480   int have_input_mode = 0;
    481   int options_done = 0;
    482   int rc = 1;
    483   int i;
    484   uint32_t input_mode = 0;
    485   const char* final_output = NULL;
    486 
    487   if (argc < 2) {
    488     driver_help_strip();
    489     return 0;
    490   }
    491 
    492   memset(&opts, 0, sizeof opts);
    493   opts.op = STRIP_OP_ALL;
    494   driver_env_init(&env);
    495   ctx = driver_env_to_context(&env);
    496 
    497   for (i = 1; i < argc; ++i) {
    498     const char* a = argv[i];
    499     const char* val = NULL;
    500     int matched;
    501     if (!options_done && driver_streq(a, "--")) {
    502       options_done = 1;
    503       continue;
    504     }
    505     if (!options_done &&
    506         (driver_streq(a, "-h") || driver_streq(a, "--help"))) {
    507       driver_help_strip();
    508       rc = 0;
    509       goto done;
    510     }
    511     if (!options_done && driver_streq(a, "--strip-debug")) {
    512       opts.op = STRIP_OP_DEBUG;
    513       continue;
    514     }
    515     if (!options_done && driver_streq(a, "--strip-unneeded")) {
    516       opts.op = STRIP_OP_UNNEEDED;
    517       continue;
    518     }
    519     if (!options_done &&
    520         (driver_streq(a, "--strip-all") || driver_streq(a, "-s"))) {
    521       opts.op = STRIP_OP_ALL;
    522       continue;
    523     }
    524     if (!options_done && driver_streq(a, "--remove-signature")) {
    525       opts.remove_signature = 1;
    526       continue;
    527     }
    528     if (!options_done && driver_streq(a, "-o")) {
    529       if (i + 1 >= argc) {
    530         driver_errf(STRIP_TOOL, "-o requires a path");
    531         rc = 2;
    532         goto done;
    533       }
    534       opts.output = argv[++i];
    535       continue;
    536     }
    537     matched = options_done
    538                   ? 0
    539                   : parse_name_arg(&i, argc, argv, "--keep-symbol", "-K",
    540                                    &val);
    541     if (matched < 0) {
    542       driver_errf(STRIP_TOOL, "%.*s requires a symbol name",
    543                   KIT_SLICE_ARG(kit_slice_cstr(a)));
    544       rc = 2;
    545       goto done;
    546     }
    547     if (matched) {
    548       if (push_name(&env, &opts.keep, &opts.nkeep, &opts.cap_keep, val) != 0) {
    549         rc = 1;
    550         goto done;
    551       }
    552       continue;
    553     }
    554     matched = options_done
    555                   ? 0
    556                   : parse_name_arg(&i, argc, argv, "--strip-symbol", "-N",
    557                                    &val);
    558     if (matched < 0) {
    559       driver_errf(STRIP_TOOL, "%.*s requires a symbol name",
    560                   KIT_SLICE_ARG(kit_slice_cstr(a)));
    561       rc = 2;
    562       goto done;
    563     }
    564     if (matched) {
    565       if (push_name(&env, &opts.strip, &opts.nstrip, &opts.cap_strip, val) !=
    566           0) {
    567         rc = 1;
    568         goto done;
    569       }
    570       continue;
    571     }
    572     if (!options_done && a[0] == '-' && a[1] != '\0') {
    573       driver_errf(STRIP_TOOL, "unknown option: %.*s",
    574                   KIT_SLICE_ARG(kit_slice_cstr(a)));
    575       rc = 2;
    576       goto done;
    577     }
    578     if (opts.input) {
    579       driver_errf(STRIP_TOOL, "only one input file is supported");
    580       rc = 2;
    581       goto done;
    582     }
    583     opts.input = a;
    584   }
    585 
    586   if (!opts.input) {
    587     driver_errf(STRIP_TOOL, "missing input file");
    588     rc = 2;
    589     goto done;
    590   }
    591 
    592   if (ctx.file_io->read_all(ctx.file_io->user, opts.input, &input_fd) !=
    593       KIT_OK) {
    594     driver_errf(STRIP_TOOL, "failed to read: %.*s",
    595                 KIT_SLICE_ARG(kit_slice_cstr(opts.input)));
    596     goto done;
    597   }
    598   have_input = 1;
    599   if (driver_path_mode_get(opts.input, &input_mode) != 0) {
    600     driver_errf(STRIP_TOOL, "failed to read input mode: %.*s",
    601                 KIT_SLICE_ARG(kit_slice_cstr(opts.input)));
    602     goto done;
    603   }
    604   have_input_mode = 1;
    605   input.data = input_fd.data;
    606   input.len = input_fd.size;
    607 
    608   {
    609     KitBinFmt fmt = kit_detect_fmt(input.data, input.len);
    610     const char* out_path = opts.output ? opts.output : opts.input;
    611     final_output = out_path;
    612     if (fmt == KIT_BIN_AR) {
    613       rc = strip_archive(&env, &ctx, &input, &opts, out_path);
    614       goto done;
    615     }
    616     if (strip_object_bytes(&env, &ctx, &input, &opts, opts.input, &out_data,
    617                            &out_size) != 0) {
    618       goto done;
    619     }
    620     if (ctx.file_io->open_writer(ctx.file_io->user, out_path, &w) != KIT_OK) {
    621       driver_errf(STRIP_TOOL, "failed to open: %.*s",
    622                   KIT_SLICE_ARG(kit_slice_cstr(out_path)));
    623       goto done;
    624     }
    625     kit_writer_write(w, out_data, out_size);
    626     if (kit_writer_status(w) != KIT_OK) {
    627       driver_errf(STRIP_TOOL, "write failed: %.*s",
    628                   KIT_SLICE_ARG(kit_slice_cstr(out_path)));
    629       goto done;
    630     }
    631     rc = 0;
    632   }
    633 
    634 done:
    635   if (w) {
    636     if (rc != 0) driver_writer_abort(w);
    637     kit_writer_close(w);
    638     w = NULL;
    639   }
    640   if (rc == 0 && final_output && have_input_mode &&
    641       driver_path_mode_set(final_output, input_mode) != 0) {
    642     driver_errf(STRIP_TOOL, "failed to preserve mode: %.*s",
    643                 KIT_SLICE_ARG(kit_slice_cstr(final_output)));
    644     rc = 1;
    645   }
    646   if (out_data) driver_free(&env, out_data, out_size);
    647   if (have_input) ctx.file_io->release(ctx.file_io->user, &input_fd);
    648   if (opts.keep)
    649     driver_free(&env, (void*)opts.keep,
    650                 (size_t)opts.cap_keep * sizeof(*opts.keep));
    651   if (opts.strip)
    652     driver_free(&env, (void*)opts.strip,
    653                 (size_t)opts.cap_strip * sizeof(*opts.strip));
    654   driver_env_fini(&env);
    655   return rc;
    656 }