commit ec0883756690e6e9c76dad2e5d3e36d77d5a44b2
parent 880e14bb778689d371ccb98f0846db43469a6c2b
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Thu, 16 Jul 2026 10:12:19 -0700
strip: rewrite linked ELF Mach-O and PE images
Diffstat:
11 files changed, 1480 insertions(+), 52 deletions(-)
diff --git a/driver/cmd/strip.c b/driver/cmd/strip.c
@@ -7,10 +7,8 @@
#include "driver.h"
#include "objedit.h"
-/* `kit strip` — drop debug sections and / or unwanted symbols from a
- * relocatable object or static archive, then write the result back. Scope
- * for the first cut matches the CTOOLCHAIN.md plan: relocatable .o and
- * .a inputs only — linked ELF (ET_EXEC / ET_DYN) is rejected.
+/* `kit strip` — drop debug sections and / or unwanted symbols from an object,
+ * linked image, or static archive, then write the result back.
*
* Operations (the last one wins; default is --strip-all):
* --strip-debug drop sections whose kind is KIT_SEC_DEBUG
@@ -38,9 +36,9 @@ void driver_help_strip(void) {
" kit strip [OPTIONS] FILE\n"
"\n"
"DESCRIPTION\n"
- " Copies a relocatable object or static archive while removing\n"
- " selected debug sections and symbols. By default FILE is replaced\n"
- " in place; use -o PATH to preserve the original.\n"
+ " Copies an object, linked executable/shared library, or static\n"
+ " archive while removing selected debug sections and symbols. By\n"
+ " default FILE is replaced in place; use -o PATH to preserve it.\n"
"\n"
"OPTIONS\n"
" -h, --help Show this help and exit\n"
@@ -50,6 +48,8 @@ void driver_help_strip(void) {
" --strip-unneeded remove debug + symbols not needed by "
"relocs\n"
" --strip-all remove debug + all non-essential symbols\n"
+ " --remove-signature allow a signed linked image to be changed;\n"
+ " the result must be signed again\n"
"\n"
"SYMBOL FILTERS (may repeat)\n"
" --keep-symbol=NAME, -K NAME keep NAME even when the operation\n"
@@ -60,14 +60,16 @@ void driver_help_strip(void) {
" -o PATH write to PATH (default: rewrite FILE in "
"place)\n"
"\n"
- "SUPPORTED INPUTS — RELOCATABLE .o OR STATIC .a ONLY\n"
- " Do not pass an executable or shared library in this release. The\n"
- " open linked-image case can return 0 while silently producing a\n"
- " small, non-executable, unusable output. Use -o for safe inputs so\n"
- " the original object/archive remains available for comparison.\n"
+ "LINKED IMAGES\n"
+ " ELF, Mach-O, and PE executables/shared libraries are rewritten in\n"
+ " place at the format layer without relinking. Loader-required\n"
+ " metadata and executable permissions are preserved. Signed images\n"
+ " fail unchanged unless --remove-signature is explicit. Symbol\n"
+ " keep/drop filters apply only to relocatable inputs.\n"
"\n"
"EXAMPLES\n"
" kit strip --strip-debug -o foo.stripped.o foo.o\n"
+ " kit strip --strip-debug -o app.stripped app\n"
" kit objdump -h foo.stripped.o\n"
" kit strip --strip-unneeded -o libfoo.stripped.a libfoo.a\n"
" kit nm -g libfoo.stripped.a\n"
@@ -93,6 +95,7 @@ typedef struct StripOpts {
uint32_t cap_strip;
const char* output;
const char* input;
+ int remove_signature;
} StripOpts;
static int push_name(DriverEnv* env, const char*** arr, uint32_t* n,
@@ -148,7 +151,7 @@ static int parse_name_arg(int* i, int argc, char** argv, const char* flag,
* lists. Mutations are issued against the builder; the emit-time sweep cleans
* up cascades (orphan relocs against removed sections, etc.). */
static int strip_one_builder(DriverEnv* env, KitObjFile* of, KitObjBuilder* b,
- const StripOpts* opts) {
+ const StripOpts* opts, const char* input_name) {
KitSlice* keep = NULL;
int level = opts->op == STRIP_OP_DEBUG ? KIT_STRIP_DEBUG
: opts->op == STRIP_OP_UNNEEDED ? KIT_STRIP_UNNEEDED
@@ -161,14 +164,14 @@ static int strip_one_builder(DriverEnv* env, KitObjFile* of, KitObjBuilder* b,
keep =
(KitSlice*)driver_alloc_zeroed(env, (size_t)opts->nkeep * sizeof(*keep));
if (!keep) {
- driver_errf(STRIP_TOOL, "out of memory");
+ driver_errf(STRIP_TOOL, "%s: out of memory", input_name);
return 1;
}
for (i = 0; i < opts->nkeep; ++i) keep[i] = kit_slice_cstr(opts->keep[i]);
}
if (kit_obj_builder_strip(b, level, keep, opts->nkeep) != KIT_OK) {
- driver_errf(STRIP_TOOL, "strip failed");
+ driver_errf(STRIP_TOOL, "%s: strip failed", input_name);
goto done;
}
@@ -176,7 +179,7 @@ static int strip_one_builder(DriverEnv* env, KitObjFile* of, KitObjBuilder* b,
if (opts->nstrip) {
KitObjSymIter* sit = NULL;
if (kit_obj_symiter_new(of, &sit) != KIT_OK) {
- driver_errf(STRIP_TOOL, "out of memory");
+ driver_errf(STRIP_TOOL, "%s: out of memory", input_name);
goto done;
}
for (;;) {
@@ -197,10 +200,12 @@ done:
static int strip_object_bytes(DriverEnv* env, const KitContext* ctx,
const KitSlice* input, const StripOpts* opts,
- uint8_t** out_data, size_t* out_size) {
+ const char* input_name, uint8_t** out_data,
+ size_t* out_size) {
KitObjFile* of = NULL;
KitObjBuilder* b;
KitWriter* w = NULL;
+ KitStatus st;
size_t n = 0;
const uint8_t* data;
uint8_t* copy;
@@ -209,37 +214,65 @@ static int strip_object_bytes(DriverEnv* env, const KitContext* ctx,
*out_data = NULL;
*out_size = 0;
- if (kit_obj_open(ctx, KIT_SLICE_NULL, input, &of) != KIT_OK) {
- driver_errf(STRIP_TOOL, "not a recognized object");
- return 1;
- }
- b = kit_obj_file_builder(of);
- if (!b) {
- driver_errf(STRIP_TOOL, "no builder for object");
- kit_obj_free(of);
- return 1;
- }
-
- if (strip_one_builder(env, of, b, opts) != 0) {
- kit_obj_free(of);
+ if (kit_obj_open(ctx, kit_slice_cstr(input_name), input, &of) != KIT_OK) {
+ driver_errf(STRIP_TOOL, "%s: not a recognized object", input_name);
return 1;
}
-
if (kit_writer_mem(env->heap, &w) != KIT_OK || !w) {
- driver_errf(STRIP_TOOL, "out of memory");
+ driver_errf(STRIP_TOOL, "%s: out of memory", input_name);
kit_obj_free(of);
return 1;
}
- if (kit_obj_builder_emit(b, w) != KIT_OK) {
- driver_errf(STRIP_TOOL, "emit failed");
- kit_writer_close(w);
- kit_obj_free(of);
- return 1;
+ if (kit_obj_kind(of) != KIT_OBJ_KIND_REL) {
+ KitLinkedRewriteOptions rewrite_opts = {0};
+ if (opts->nkeep || opts->nstrip) {
+ driver_errf(STRIP_TOOL,
+ "%s: symbol keep/drop filters are unsupported for linked "
+ "images",
+ input_name);
+ kit_writer_close(w);
+ kit_obj_free(of);
+ return 1;
+ }
+ rewrite_opts.strip_level = opts->op == STRIP_OP_DEBUG
+ ? KIT_LINKED_STRIP_DEBUG
+ : KIT_LINKED_STRIP_ALL;
+ rewrite_opts.remove_signature = opts->remove_signature != 0;
+ st = kit_obj_rewrite_linked(ctx, kit_slice_cstr(input_name), input,
+ &rewrite_opts, w, NULL);
+ if (st != KIT_OK) {
+ if (st == KIT_NOMEM)
+ driver_errf(STRIP_TOOL, "%s: out of memory", input_name);
+ else if (st == KIT_IO)
+ driver_errf(STRIP_TOOL, "%s: rewrite output failed", input_name);
+ kit_writer_close(w);
+ kit_obj_free(of);
+ return 1;
+ }
+ } else {
+ b = kit_obj_file_builder(of);
+ if (!b) {
+ driver_errf(STRIP_TOOL, "%s: no builder for object", input_name);
+ kit_writer_close(w);
+ kit_obj_free(of);
+ return 1;
+ }
+ if (strip_one_builder(env, of, b, opts, input_name) != 0) {
+ kit_writer_close(w);
+ kit_obj_free(of);
+ return 1;
+ }
+ if (kit_obj_builder_emit(b, w) != KIT_OK) {
+ driver_errf(STRIP_TOOL, "%s: emit failed", input_name);
+ kit_writer_close(w);
+ kit_obj_free(of);
+ return 1;
+ }
}
data = kit_writer_mem_bytes(w, &n);
copy = (uint8_t*)driver_alloc(env, n ? n : 1u);
if (!copy) {
- driver_errf(STRIP_TOOL, "out of memory");
+ driver_errf(STRIP_TOOL, "%s: out of memory", input_name);
kit_writer_close(w);
kit_obj_free(of);
return 1;
@@ -276,7 +309,7 @@ static int strip_archive(DriverEnv* env, const KitContext* ctx,
/* Pass 1: count members + total name bytes. */
if (kit_ar_iter_new(ctx, input, &it) != KIT_OK) {
- driver_errf(STRIP_TOOL, "not an archive");
+ driver_errf(STRIP_TOOL, "%s: not a recognized archive", opts->input);
return 1;
}
for (;;) {
@@ -322,6 +355,8 @@ static int strip_archive(DriverEnv* env, const KitContext* ctx,
char* dst;
KitBinFmt fmt;
KitSlice mbytes;
+ char* member_name = NULL;
+ size_t member_name_size = 0;
if (r != KIT_ITER_ITEM) break;
dst = name_storage + cursor;
for (j = 0; j < m.name.len; ++j) *dst++ = m.name.s[j];
@@ -337,11 +372,29 @@ static int strip_archive(DriverEnv* env, const KitContext* ctx,
fmt == KIT_BIN_WASM) {
uint8_t* sd = NULL;
size_t ss = 0;
- if (strip_object_bytes(env, ctx, &mbytes, opts, &sd, &ss) != 0) {
+ size_t archive_len = strlen(opts->input);
+ member_name_size = archive_len + m.name.len + 3u;
+ member_name = (char*)driver_alloc(env, member_name_size);
+ if (!member_name) {
+ driver_errf(STRIP_TOOL, "%s(%.*s): out of memory", opts->input,
+ (int)m.name.len, m.name.s);
+ kit_ar_iter_free(it);
+ it = NULL;
+ goto done;
+ }
+ memcpy(member_name, opts->input, archive_len);
+ member_name[archive_len] = '(';
+ memcpy(member_name + archive_len + 1u, m.name.s, m.name.len);
+ member_name[archive_len + 1u + m.name.len] = ')';
+ member_name[member_name_size - 1u] = '\0';
+ if (strip_object_bytes(env, ctx, &mbytes, opts, member_name, &sd, &ss) !=
+ 0) {
+ driver_free(env, member_name, member_name_size);
kit_ar_iter_free(it);
it = NULL;
goto done;
}
+ driver_free(env, member_name, member_name_size);
owned_data[k] = sd;
owned_size[k] = ss;
members[k].bytes.data = sd;
@@ -389,7 +442,10 @@ static int strip_archive(DriverEnv* env, const KitContext* ctx,
if (rc == 0 && kit_writer_status(out) != KIT_OK) rc = 1;
done:
- if (out) kit_writer_close(out);
+ if (out) {
+ if (rc != 0) driver_writer_abort(out);
+ kit_writer_close(out);
+ }
if (it) kit_ar_iter_free(it);
if (sym_allocs) {
for (k = 0; k < nmembers; ++k) {
@@ -421,10 +477,14 @@ int driver_strip(int argc, char** argv) {
uint8_t* out_data = NULL;
size_t out_size = 0;
int have_input = 0;
+ int have_input_mode = 0;
+ int options_done = 0;
int rc = 1;
int i;
+ uint32_t input_mode = 0;
+ const char* final_output = NULL;
- if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) {
+ if (argc < 2) {
driver_help_strip();
return 0;
}
@@ -438,19 +498,34 @@ int driver_strip(int argc, char** argv) {
const char* a = argv[i];
const char* val = NULL;
int matched;
- if (driver_streq(a, "--strip-debug")) {
+ if (!options_done && driver_streq(a, "--")) {
+ options_done = 1;
+ continue;
+ }
+ if (!options_done &&
+ (driver_streq(a, "-h") || driver_streq(a, "--help"))) {
+ driver_help_strip();
+ rc = 0;
+ goto done;
+ }
+ if (!options_done && driver_streq(a, "--strip-debug")) {
opts.op = STRIP_OP_DEBUG;
continue;
}
- if (driver_streq(a, "--strip-unneeded")) {
+ if (!options_done && driver_streq(a, "--strip-unneeded")) {
opts.op = STRIP_OP_UNNEEDED;
continue;
}
- if (driver_streq(a, "--strip-all") || driver_streq(a, "-s")) {
+ if (!options_done &&
+ (driver_streq(a, "--strip-all") || driver_streq(a, "-s"))) {
opts.op = STRIP_OP_ALL;
continue;
}
- if (driver_streq(a, "-o")) {
+ if (!options_done && driver_streq(a, "--remove-signature")) {
+ opts.remove_signature = 1;
+ continue;
+ }
+ if (!options_done && driver_streq(a, "-o")) {
if (i + 1 >= argc) {
driver_errf(STRIP_TOOL, "-o requires a path");
rc = 2;
@@ -459,7 +534,10 @@ int driver_strip(int argc, char** argv) {
opts.output = argv[++i];
continue;
}
- matched = parse_name_arg(&i, argc, argv, "--keep-symbol", "-K", &val);
+ matched = options_done
+ ? 0
+ : parse_name_arg(&i, argc, argv, "--keep-symbol", "-K",
+ &val);
if (matched < 0) {
driver_errf(STRIP_TOOL, "%.*s requires a symbol name",
KIT_SLICE_ARG(kit_slice_cstr(a)));
@@ -473,7 +551,10 @@ int driver_strip(int argc, char** argv) {
}
continue;
}
- matched = parse_name_arg(&i, argc, argv, "--strip-symbol", "-N", &val);
+ matched = options_done
+ ? 0
+ : parse_name_arg(&i, argc, argv, "--strip-symbol", "-N",
+ &val);
if (matched < 0) {
driver_errf(STRIP_TOOL, "%.*s requires a symbol name",
KIT_SLICE_ARG(kit_slice_cstr(a)));
@@ -488,7 +569,7 @@ int driver_strip(int argc, char** argv) {
}
continue;
}
- if (a[0] == '-' && a[1] != '\0') {
+ if (!options_done && a[0] == '-' && a[1] != '\0') {
driver_errf(STRIP_TOOL, "unknown option: %.*s",
KIT_SLICE_ARG(kit_slice_cstr(a)));
rc = 2;
@@ -515,18 +596,25 @@ int driver_strip(int argc, char** argv) {
goto done;
}
have_input = 1;
+ if (driver_path_mode_get(opts.input, &input_mode) != 0) {
+ driver_errf(STRIP_TOOL, "failed to read input mode: %.*s",
+ KIT_SLICE_ARG(kit_slice_cstr(opts.input)));
+ goto done;
+ }
+ have_input_mode = 1;
input.data = input_fd.data;
input.len = input_fd.size;
{
KitBinFmt fmt = kit_detect_fmt(input.data, input.len);
const char* out_path = opts.output ? opts.output : opts.input;
+ final_output = out_path;
if (fmt == KIT_BIN_AR) {
rc = strip_archive(&env, &ctx, &input, &opts, out_path);
goto done;
}
- if (strip_object_bytes(&env, &ctx, &input, &opts, &out_data, &out_size) !=
- 0) {
+ if (strip_object_bytes(&env, &ctx, &input, &opts, opts.input, &out_data,
+ &out_size) != 0) {
goto done;
}
if (ctx.file_io->open_writer(ctx.file_io->user, out_path, &w) != KIT_OK) {
@@ -544,7 +632,17 @@ int driver_strip(int argc, char** argv) {
}
done:
- if (w) kit_writer_close(w);
+ if (w) {
+ if (rc != 0) driver_writer_abort(w);
+ kit_writer_close(w);
+ w = NULL;
+ }
+ if (rc == 0 && final_output && have_input_mode &&
+ driver_path_mode_set(final_output, input_mode) != 0) {
+ driver_errf(STRIP_TOOL, "failed to preserve mode: %.*s",
+ KIT_SLICE_ARG(kit_slice_cstr(final_output)));
+ rc = 1;
+ }
if (out_data) driver_free(&env, out_data, out_size);
if (have_input) ctx.file_io->release(ctx.file_io->user, &input_fd);
if (opts.keep)
diff --git a/include/kit/object.h b/include/kit/object.h
@@ -501,6 +501,38 @@ KIT_API KitIterResult kit_obj_image_rawiter_next(KitObjImageRawIter*,
KitObjImageRaw* out);
KIT_API void kit_obj_image_rawiter_free(KitObjImageRawIter*);
+/* Format-layer rewrite of an already-linked executable or shared library.
+ * Unlike kit_obj_file_builder(), this path preserves the original loader
+ * image byte-for-byte except for file-only debug/symbol/signature metadata.
+ * It therefore retains program headers/load commands, mapped addresses and
+ * permissions, dynamic imports/exports/relocations, TLS, unwind tables, and
+ * init/fini state without relinking.
+ *
+ * Signed images are rejected before OUT is touched unless remove_signature is
+ * set. Removing a signature only drops its on-disk metadata; the result must
+ * be signed again before a platform that requires signing will execute it. */
+typedef enum KitLinkedStripLevel {
+ KIT_LINKED_STRIP_DEBUG = 1,
+ KIT_LINKED_STRIP_ALL = 2,
+} KitLinkedStripLevel;
+
+typedef struct KitLinkedRewriteOptions {
+ uint32_t strip_level; /* KitLinkedStripLevel */
+ bool remove_signature;
+} KitLinkedRewriteOptions;
+
+typedef struct KitLinkedRewriteReport {
+ uint32_t removed_debug_sections;
+ uint32_t removed_symbols;
+ bool had_signature;
+ bool removed_signature;
+} KitLinkedRewriteReport;
+
+KIT_API KitStatus kit_obj_rewrite_linked(
+ const KitContext*, KitSlice name, const KitSlice* input,
+ const KitLinkedRewriteOptions*, KitWriter* out,
+ KitLinkedRewriteReport* report_out);
+
/* Roundtrip: open an object via kit_obj_open, then hand its underlying
* builder back. The builder is the same one the reader populated; it is
* already finalized, so callers may inspect it (e.g. iterate sections via
diff --git a/src/api/object_rewrite.c b/src/api/object_rewrite.c
@@ -0,0 +1,27 @@
+#include <kit/object.h>
+
+#include "core/diag.h"
+#include "obj/format.h"
+
+KitStatus kit_obj_rewrite_linked(const KitContext* ctx, KitSlice name,
+ const KitSlice* input,
+ const KitLinkedRewriteOptions* opts,
+ KitWriter* out,
+ KitLinkedRewriteReport* report_out) {
+ KitBinFmt bin;
+ const ObjFormatImpl* fmt;
+ if (!ctx || !input || !opts || !out ||
+ (opts->strip_level != KIT_LINKED_STRIP_DEBUG &&
+ opts->strip_level != KIT_LINKED_STRIP_ALL))
+ return KIT_INVALID;
+ bin = kit_detect_fmt(input->data, input->len);
+ fmt = (bin == KIT_BIN_PE) ? obj_format_lookup(KIT_OBJ_COFF)
+ : obj_format_lookup_bin(bin);
+ if (!fmt || !fmt->rewrite_linked) {
+ kit_ctx_diagf(ctx, "%.*s: linked-image rewrite is unsupported for this "
+ "format",
+ KIT_SLICE_ARG(name));
+ return KIT_UNSUPPORTED;
+ }
+ return fmt->rewrite_linked(ctx, name, input, opts, out, report_out);
+}
diff --git a/src/obj/coff/rewrite.c b/src/obj/coff/rewrite.c
@@ -0,0 +1,253 @@
+#include "obj/rewrite.h"
+
+#include <limits.h>
+#include <string.h>
+
+#include "obj/coff/coff.h"
+
+#define PE_DEBUG_DIRECTORY_SIZE 28u
+
+typedef struct CoffRewriteView {
+ ObjRewrite rw;
+ u8* file_header;
+ u8* optional_header;
+ u8* section_headers;
+ u16 section_count;
+ u16 optional_size;
+ u32 directory_count;
+ u32 size_of_headers;
+} CoffRewriteView;
+
+static KitStatus coff_bad(CoffRewriteView* v, const char* detail) {
+ kit_ctx_diagf(v->rw.ctx, "%.*s: malformed linked PE image (%s)",
+ KIT_SLICE_ARG(v->rw.name), detail);
+ return KIT_MALFORMED;
+}
+
+static u8* coff_directory(CoffRewriteView* v, u32 index) {
+ u64 off = 112u + (u64)index * COFF_DATA_DIRECTORY_SIZE;
+ if (index >= v->directory_count || off + COFF_DATA_DIRECTORY_SIZE >
+ v->optional_size)
+ return NULL;
+ return v->optional_header + (size_t)off;
+}
+
+static u8* coff_section(CoffRewriteView* v, u32 index) {
+ if (index >= v->section_count) return NULL;
+ return v->section_headers + (size_t)index * COFF_SECTION_HEADER_SIZE;
+}
+
+/* Translate a PE RVA range to its complete on-disk representation. Header
+ * RVAs are file offsets; section RVAs are bounded by SizeOfRawData because
+ * zero-fill tails have no bytes to rewrite. */
+static int coff_rva_file_range(CoffRewriteView* v, u32 rva, u32 size,
+ u64* off_out) {
+ u32 i;
+ if ((u64)rva + size > UINT32_MAX + 1ull) return 0;
+ if (rva < v->size_of_headers) {
+ if ((u64)rva + size > v->size_of_headers ||
+ !obj_rw_range(v->rw.len, rva, size))
+ return 0;
+ *off_out = rva;
+ return 1;
+ }
+ for (i = 0; i < v->section_count; ++i) {
+ const u8* sec = coff_section(v, i);
+ u32 va = obj_rw_u32(sec + 12, 0);
+ u32 raw_size = obj_rw_u32(sec + 16, 0);
+ u32 raw_off = obj_rw_u32(sec + 20, 0);
+ u64 delta;
+ if (rva < va) continue;
+ delta = (u64)rva - va;
+ if (delta > raw_size || size > (u64)raw_size - delta) continue;
+ if (!obj_rw_range(v->rw.len, (u64)raw_off + delta, size)) return 0;
+ *off_out = (u64)raw_off + delta;
+ return 1;
+ }
+ return 0;
+}
+
+static int coff_fixed_prefix(const u8* name, const char* prefix) {
+ size_t n = strlen(prefix);
+ return n <= 8u && memcmp(name, prefix, n) == 0;
+}
+
+static int coff_debug_section(const u8* sec) {
+ const u8* name = sec;
+ return coff_fixed_prefix(name, ".debug") ||
+ coff_fixed_prefix(name, ".zdebug") ||
+ coff_fixed_prefix(name, ".stab");
+}
+
+static KitStatus coff_validate(CoffRewriteView* v, const KitContext* ctx,
+ KitSlice name, const KitSlice* input) {
+ KitStatus st;
+ u32 peoff, i;
+ u64 sections_size;
+ memset(v, 0, sizeof *v);
+ st = obj_rewrite_init(&v->rw, ctx, name, input);
+ if (st != KIT_OK) return st;
+ if (v->rw.len < COFF_DOS_HEADER_SIZE ||
+ obj_rw_u16(v->rw.bytes, 0) != IMAGE_DOS_SIGNATURE)
+ return coff_bad(v, "DOS header");
+ peoff = obj_rw_u32(v->rw.bytes + 0x3cu, 0);
+ if (!obj_rw_range(v->rw.len, peoff,
+ 4u + COFF_FILE_HEADER_SIZE) ||
+ obj_rw_u32(v->rw.bytes + peoff, 0) != IMAGE_NT_SIGNATURE)
+ return coff_bad(v, "PE header");
+ v->file_header = v->rw.bytes + peoff + 4u;
+ v->section_count = obj_rw_u16(v->file_header + 2, 0);
+ v->optional_size = obj_rw_u16(v->file_header + 16, 0);
+ if (v->optional_size == 0) {
+ kit_ctx_diagf(ctx, "%.*s: relocatable object is not a linked image",
+ KIT_SLICE_ARG(name));
+ return KIT_INVALID;
+ }
+ if (v->optional_size < 112u ||
+ !obj_rw_range(v->rw.len, (u64)peoff + 24u, v->optional_size))
+ return coff_bad(v, "optional header");
+ v->optional_header = v->file_header + COFF_FILE_HEADER_SIZE;
+ if (obj_rw_u16(v->optional_header, 0) !=
+ IMAGE_NT_OPTIONAL_HDR64_MAGIC)
+ return coff_bad(v, "PE32+ optional header");
+ v->size_of_headers = obj_rw_u32(v->optional_header + 60, 0);
+ v->directory_count = obj_rw_u32(v->optional_header + 108, 0);
+ if (v->directory_count > COFF_NUM_DATA_DIRECTORIES)
+ v->directory_count = COFF_NUM_DATA_DIRECTORIES;
+ if ((u64)112u + (u64)v->directory_count * COFF_DATA_DIRECTORY_SIZE >
+ v->optional_size)
+ return coff_bad(v, "data directories");
+ v->section_headers = v->optional_header + v->optional_size;
+ sections_size = (u64)v->section_count * COFF_SECTION_HEADER_SIZE;
+ if (!obj_rw_range(v->rw.len,
+ (u64)(v->section_headers - v->rw.bytes), sections_size))
+ return coff_bad(v, "section table");
+ for (i = 0; i < v->section_count; ++i) {
+ const u8* sec = coff_section(v, i);
+ u32 raw_size = obj_rw_u32(sec + 16, 0);
+ u32 raw_off = obj_rw_u32(sec + 20, 0);
+ if (raw_size && !obj_rw_range(v->rw.len, raw_off, raw_size))
+ return coff_bad(v, "section contents");
+ }
+ return KIT_OK;
+}
+
+static KitStatus coff_check_signature(CoffRewriteView* v,
+ const KitLinkedRewriteOptions* opts) {
+ u8* dir = coff_directory(v, IMAGE_DIRECTORY_ENTRY_SECURITY);
+ u32 off, size;
+ if (!dir) return KIT_OK;
+ off = obj_rw_u32(dir, 0);
+ size = obj_rw_u32(dir + 4, 0);
+ if (!off && !size) return KIT_OK;
+ if (!off || !size || !obj_rw_range(v->rw.len, off, size))
+ return coff_bad(v, "certificate table");
+ v->rw.report.had_signature = true;
+ if (!opts->remove_signature) {
+ kit_ctx_diagf(v->rw.ctx,
+ "%.*s: signed PE image is unchanged; pass "
+ "--remove-signature and sign the result again",
+ KIT_SLICE_ARG(v->rw.name));
+ return KIT_UNSUPPORTED;
+ }
+ memset(v->rw.bytes + off, 0, size);
+ memset(dir, 0, COFF_DATA_DIRECTORY_SIZE);
+ v->rw.report.removed_signature = true;
+ return KIT_OK;
+}
+
+static KitStatus coff_strip_debug_directory(CoffRewriteView* v) {
+ u8* dir = coff_directory(v, IMAGE_DIRECTORY_ENTRY_DEBUG);
+ u32 rva, size, i;
+ u64 off;
+ if (!dir) return KIT_OK;
+ rva = obj_rw_u32(dir, 0);
+ size = obj_rw_u32(dir + 4, 0);
+ if (!rva && !size) return KIT_OK;
+ if (!rva || !size || size % PE_DEBUG_DIRECTORY_SIZE != 0 ||
+ !coff_rva_file_range(v, rva, size, &off))
+ return coff_bad(v, "debug directory");
+ for (i = 0; i < size / PE_DEBUG_DIRECTORY_SIZE; ++i) {
+ const u8* entry = v->rw.input + (size_t)off +
+ (size_t)i * PE_DEBUG_DIRECTORY_SIZE;
+ u32 data_size = obj_rw_u32(entry + 16, 0);
+ u32 data_off = obj_rw_u32(entry + 24, 0);
+ if (data_size) {
+ if (!data_off || !obj_rw_range(v->rw.len, data_off, data_size))
+ return coff_bad(v, "debug payload");
+ memset(v->rw.bytes + data_off, 0, data_size);
+ }
+ }
+ memset(v->rw.bytes + (size_t)off, 0, size);
+ memset(dir, 0, COFF_DATA_DIRECTORY_SIZE);
+ ++v->rw.report.removed_debug_sections;
+ return KIT_OK;
+}
+
+static KitStatus coff_strip_debug_sections(CoffRewriteView* v) {
+ u32 i;
+ for (i = 0; i < v->section_count; ++i) {
+ u8* sec = coff_section(v, i);
+ u32 raw_size, raw_off;
+ if (!coff_debug_section(sec)) continue;
+ raw_size = obj_rw_u32(sec + 16, 0);
+ raw_off = obj_rw_u32(sec + 20, 0);
+ if (raw_size) memset(v->rw.bytes + raw_off, 0, raw_size);
+ /* Leave the ordinal in place for any retained COFF symbol records, but
+ * make the section non-mapped and contentless. RVA-based loader metadata
+ * and every non-debug section retain their original layout. */
+ memset(sec, 0, 8u);
+ obj_rw_put_u32(sec + 8, 0, 0);
+ obj_rw_put_u32(sec + 16, 0, 0);
+ obj_rw_put_u32(sec + 20, 0, 0);
+ obj_rw_put_u32(sec + 36, IMAGE_SCN_MEM_DISCARDABLE, 0);
+ ++v->rw.report.removed_debug_sections;
+ }
+ return KIT_OK;
+}
+
+static KitStatus coff_strip_symbol_table(CoffRewriteView* v) {
+ u32 off = obj_rw_u32(v->file_header + 8, 0);
+ u32 count = obj_rw_u32(v->file_header + 12, 0);
+ u64 symbols_size, end, total;
+ u32 strings_size;
+ if (!off && !count) return KIT_OK;
+ if (!off || count > UINT32_MAX / COFF_SYMBOL_SIZE)
+ return coff_bad(v, "COFF symbol table");
+ symbols_size = (u64)count * COFF_SYMBOL_SIZE;
+ end = (u64)off + symbols_size;
+ if (!obj_rw_range(v->rw.len, off, symbols_size) ||
+ !obj_rw_range(v->rw.len, end, 4u))
+ return coff_bad(v, "COFF symbol table");
+ strings_size = obj_rw_u32(v->rw.bytes + (size_t)end, 0);
+ if (strings_size < 4u || !obj_rw_range(v->rw.len, end, strings_size))
+ return coff_bad(v, "COFF string table");
+ total = symbols_size + strings_size;
+ memset(v->rw.bytes + off, 0, (size_t)total);
+ obj_rw_put_u32(v->file_header + 8, 0, 0);
+ obj_rw_put_u32(v->file_header + 12, 0, 0);
+ v->rw.report.removed_symbols += count;
+ return KIT_OK;
+}
+
+KitStatus obj_rewrite_coff(const KitContext* ctx, KitSlice name,
+ const KitSlice* input,
+ const KitLinkedRewriteOptions* opts,
+ KitWriter* out,
+ KitLinkedRewriteReport* report_out) {
+ CoffRewriteView v;
+ KitStatus st = coff_validate(&v, ctx, name, input);
+ if (st == KIT_OK) st = coff_check_signature(&v, opts);
+ if (st == KIT_OK) st = coff_strip_debug_directory(&v);
+ if (st == KIT_OK) st = coff_strip_debug_sections(&v);
+ if (st == KIT_OK && opts->strip_level == KIT_LINKED_STRIP_ALL)
+ st = coff_strip_symbol_table(&v);
+ if (st == KIT_OK) {
+ u16 characteristics = obj_rw_u16(v.file_header + 18, 0);
+ obj_rw_put_u16(v.file_header + 18,
+ (u16)(characteristics | IMAGE_FILE_DEBUG_STRIPPED), 0);
+ st = obj_rewrite_commit(&v.rw, out, report_out);
+ }
+ if (st != KIT_OK) obj_rewrite_discard(&v.rw);
+ return st;
+}
diff --git a/src/obj/elf/rewrite.c b/src/obj/elf/rewrite.c
@@ -0,0 +1,300 @@
+#include "obj/rewrite.h"
+
+#include <limits.h>
+#include <string.h>
+
+#include "obj/elf/elf.h"
+
+#define SHT_SYMTAB_SHNDX 18u
+#define SHN_XINDEX 0xffffu
+
+typedef struct ElfRewriteView {
+ ObjRewrite rw;
+ int is32;
+ int be;
+ u64 shoff;
+ u32 shentsize;
+ u32 shnum;
+ u32 shstrndx;
+ u64 shstr_off;
+ u64 shstr_size;
+} ElfRewriteView;
+
+typedef struct ElfRewriteSec {
+ u32 name;
+ u32 type;
+ u64 flags;
+ u64 off;
+ u64 size;
+ u32 link;
+ u32 info;
+ u64 entsize;
+ u8* hdr;
+} ElfRewriteSec;
+
+static KitStatus elf_bad(ElfRewriteView* v, const char* detail) {
+ kit_ctx_diagf(v->rw.ctx, "%.*s: malformed linked ELF image (%s)",
+ KIT_SLICE_ARG(v->rw.name), detail);
+ return KIT_MALFORMED;
+}
+
+static int elf_sec(ElfRewriteView* v, u32 index, ElfRewriteSec* out) {
+ u64 pos;
+ u8* p;
+ if (index >= v->shnum ||
+ (u64)index > (UINT64_MAX - v->shoff) / v->shentsize)
+ return 0;
+ pos = v->shoff + (u64)index * v->shentsize;
+ if (!obj_rw_range(v->rw.len, pos, v->shentsize)) return 0;
+ p = v->rw.bytes + (size_t)pos;
+ memset(out, 0, sizeof *out);
+ out->name = obj_rw_u32(p, v->be);
+ out->type = obj_rw_u32(p + 4, v->be);
+ if (v->is32) {
+ out->flags = obj_rw_u32(p + 8, v->be);
+ out->off = obj_rw_u32(p + 16, v->be);
+ out->size = obj_rw_u32(p + 20, v->be);
+ out->link = obj_rw_u32(p + 24, v->be);
+ out->info = obj_rw_u32(p + 28, v->be);
+ out->entsize = obj_rw_u32(p + 36, v->be);
+ } else {
+ out->flags = obj_rw_u64(p + 8, v->be);
+ out->off = obj_rw_u64(p + 24, v->be);
+ out->size = obj_rw_u64(p + 32, v->be);
+ out->link = obj_rw_u32(p + 40, v->be);
+ out->info = obj_rw_u32(p + 44, v->be);
+ out->entsize = obj_rw_u64(p + 56, v->be);
+ }
+ out->hdr = p;
+ return 1;
+}
+
+static int elf_name(ElfRewriteView* v, const ElfRewriteSec* s,
+ const char** name, size_t* len) {
+ u64 remain;
+ const char* p;
+ size_t n = 0;
+ if ((u64)s->name >= v->shstr_size) return 0;
+ remain = v->shstr_size - s->name;
+ p = (const char*)v->rw.bytes + (size_t)(v->shstr_off + s->name);
+ while ((u64)n < remain && p[n]) ++n;
+ if ((u64)n == remain) return 0;
+ *name = p;
+ *len = n;
+ return 1;
+}
+
+static int name_eq(const char* name, size_t len, const char* want) {
+ size_t n = strlen(want);
+ return len == n && memcmp(name, want, n) == 0;
+}
+
+static int name_prefix(const char* name, size_t len, const char* prefix) {
+ size_t n = strlen(prefix);
+ return len >= n && memcmp(name, prefix, n) == 0;
+}
+
+static int elf_debug_name(const char* name, size_t len) {
+ return name_prefix(name, len, ".debug_") ||
+ name_prefix(name, len, ".zdebug_") ||
+ name_prefix(name, len, ".stab") ||
+ name_eq(name, len, ".gdb_index") ||
+ name_eq(name, len, ".gnu_debuglink") ||
+ name_eq(name, len, ".gnu_debugaltlink") ||
+ name_eq(name, len, ".ctf") || name_prefix(name, len, ".BTF");
+}
+
+static int elf_signature_name(const char* name, size_t len) {
+ return name_eq(name, len, ".signature") || name_eq(name, len, ".sig") ||
+ name_eq(name, len, ".ima") ||
+ name_eq(name, len, ".note.signature") ||
+ name_eq(name, len, ".note.sigstore") ||
+ name_eq(name, len, ".module_sig");
+}
+
+static KitStatus elf_view_init(ElfRewriteView* v, const KitContext* ctx,
+ KitSlice name, const KitSlice* input) {
+ KitStatus st;
+ u8* e;
+ u16 type, raw_shnum, raw_shstr;
+ ElfRewriteSec zero, str;
+ memset(v, 0, sizeof *v);
+ st = obj_rewrite_init(&v->rw, ctx, name, input);
+ if (st != KIT_OK) return st;
+ e = v->rw.bytes;
+ if (v->rw.len < ELF32_EHDR_SIZE || e[0] != 0x7f || e[1] != 'E' ||
+ e[2] != 'L' || e[3] != 'F')
+ return elf_bad(v, "header");
+ if (e[EI_CLASS] == ELFCLASS32)
+ v->is32 = 1;
+ else if (e[EI_CLASS] != ELFCLASS64)
+ return elf_bad(v, "class");
+ if (e[EI_DATA] == ELFDATA2LSB)
+ v->be = 0;
+ else if (e[EI_DATA] == 2)
+ v->be = 1;
+ else
+ return elf_bad(v, "byte order");
+ if ((!v->is32 && v->rw.len < ELF64_EHDR_SIZE))
+ return elf_bad(v, "truncated header");
+ type = obj_rw_u16(e + 16, v->be);
+ if (type == ET_REL) {
+ kit_ctx_diagf(ctx, "%.*s: relocatable object is not a linked image",
+ KIT_SLICE_ARG(name));
+ return KIT_INVALID;
+ }
+ if (type != ET_EXEC && type != ET_DYN)
+ return elf_bad(v, "unsupported e_type");
+ if (v->is32) {
+ v->shoff = obj_rw_u32(e + 32, v->be);
+ v->shentsize = obj_rw_u16(e + 46, v->be);
+ raw_shnum = obj_rw_u16(e + 48, v->be);
+ raw_shstr = obj_rw_u16(e + 50, v->be);
+ if (v->shentsize < ELF32_SHDR_SIZE) return elf_bad(v, "section size");
+ } else {
+ v->shoff = obj_rw_u64(e + 40, v->be);
+ v->shentsize = obj_rw_u16(e + 58, v->be);
+ raw_shnum = obj_rw_u16(e + 60, v->be);
+ raw_shstr = obj_rw_u16(e + 62, v->be);
+ if (v->shentsize < ELF64_SHDR_SIZE) return elf_bad(v, "section size");
+ }
+ v->shnum = raw_shnum;
+ v->shstrndx = raw_shstr;
+ if (v->shoff == 0 || !obj_rw_range(v->rw.len, v->shoff, v->shentsize))
+ return elf_bad(v, "section table");
+ /* Read section zero with a temporary one-entry bound to recover the ELF
+ * extended section-count/string-index encodings. */
+ if (v->shnum == 0 || v->shstrndx == SHN_XINDEX) {
+ v->shnum = 1;
+ if (!elf_sec(v, 0, &zero)) return elf_bad(v, "section zero");
+ if (raw_shnum == 0) {
+ if (zero.size == 0 || zero.size > UINT32_MAX)
+ return elf_bad(v, "extended section count");
+ v->shnum = (u32)zero.size;
+ } else
+ v->shnum = raw_shnum;
+ if (raw_shstr == SHN_XINDEX) v->shstrndx = zero.link;
+ }
+ if (v->shnum == 0 || v->shstrndx >= v->shnum ||
+ !obj_rw_range(v->rw.len, v->shoff,
+ (u64)v->shnum * v->shentsize))
+ return elf_bad(v, "section bounds");
+ if (!elf_sec(v, v->shstrndx, &str) || str.type != SHT_STRTAB ||
+ !obj_rw_range(v->rw.len, str.off, str.size))
+ return elf_bad(v, "section-name table");
+ v->shstr_off = str.off;
+ v->shstr_size = str.size;
+ return KIT_OK;
+}
+
+KitStatus obj_rewrite_elf(const KitContext* ctx, KitSlice name,
+ const KitSlice* input,
+ const KitLinkedRewriteOptions* opts, KitWriter* out,
+ KitLinkedRewriteReport* report_out) {
+ ElfRewriteView v;
+ u8* remove = NULL;
+ size_t remove_size;
+ u32 i;
+ int changed;
+ KitStatus st = elf_view_init(&v, ctx, name, input);
+ if (st != KIT_OK) {
+ obj_rewrite_discard(&v.rw);
+ return st;
+ }
+ remove_size = v.shnum ? (size_t)v.shnum : 1u;
+ remove = (u8*)ctx->heap->alloc(ctx->heap, remove_size, 1u);
+ if (!remove) {
+ obj_rewrite_discard(&v.rw);
+ return KIT_NOMEM;
+ }
+ memset(remove, 0, remove_size);
+
+ for (i = 1; i < v.shnum; ++i) {
+ ElfRewriteSec s;
+ const char* secname = "<unnamed>";
+ size_t secname_len = sizeof("<unnamed>") - 1u;
+ if (!elf_sec(&v, i, &s) || !elf_name(&v, &s, &secname, &secname_len)) {
+ st = elf_bad(&v, "section record");
+ goto done;
+ }
+ if (elf_signature_name(secname, secname_len)) {
+ v.rw.report.had_signature = true;
+ if (opts->remove_signature) remove[i] = 1;
+ }
+ if (elf_debug_name(secname, secname_len)) remove[i] = 1;
+ if (opts->strip_level == KIT_LINKED_STRIP_ALL && s.type == SHT_SYMTAB) {
+ remove[i] = 1;
+ if (s.link < v.shnum && s.link != v.shstrndx) remove[s.link] = 1;
+ if (s.entsize)
+ v.rw.report.removed_symbols += (u32)(s.size / s.entsize);
+ }
+ }
+ if (v.rw.report.had_signature && !opts->remove_signature) {
+ kit_ctx_diagf(ctx,
+ "%.*s: signed ELF image is unchanged; pass "
+ "--remove-signature and sign the result again",
+ KIT_SLICE_ARG(name));
+ st = KIT_UNSUPPORTED;
+ goto done;
+ }
+ v.rw.report.removed_signature =
+ v.rw.report.had_signature && opts->remove_signature;
+
+ /* Remove relocation/index companions whose target or symbol table is being
+ * removed. Iterate to a fixed point because a companion can precede its
+ * target in the section table. */
+ do {
+ changed = 0;
+ for (i = 1; i < v.shnum; ++i) {
+ ElfRewriteSec s;
+ if (remove[i] || !elf_sec(&v, i, &s)) continue;
+ if ((s.type == SHT_RELA || s.type == SHT_REL) &&
+ ((s.info < v.shnum && remove[s.info]) ||
+ (s.link < v.shnum && remove[s.link]))) {
+ remove[i] = 1;
+ changed = 1;
+ } else if (s.type == SHT_SYMTAB_SHNDX && s.link < v.shnum &&
+ remove[s.link]) {
+ remove[i] = 1;
+ changed = 1;
+ }
+ }
+ } while (changed);
+
+ for (i = 1; i < v.shnum; ++i) {
+ ElfRewriteSec s;
+ const char* secname = "<unnamed>";
+ size_t secname_len = sizeof("<unnamed>") - 1u;
+ int was_debug = 0;
+ if (!remove[i]) continue;
+ if (!elf_sec(&v, i, &s)) {
+ st = elf_bad(&v, "removed section");
+ goto done;
+ }
+ if (elf_name(&v, &s, &secname, &secname_len))
+ was_debug = elf_debug_name(secname, secname_len) ||
+ s.type == SHT_RELA || s.type == SHT_REL;
+ if (s.flags & SHF_ALLOC) {
+ kit_ctx_diagf(ctx,
+ "%.*s: refusing to remove mapped ELF section %.*s",
+ KIT_SLICE_ARG(name), (int)secname_len, secname);
+ st = KIT_UNSUPPORTED;
+ goto done;
+ }
+ if (s.type != SHT_NOBITS && s.size) {
+ if (!obj_rw_range(v.rw.len, s.off, s.size)) {
+ st = elf_bad(&v, "section contents");
+ goto done;
+ }
+ memset(v.rw.bytes + (size_t)s.off, 0, (size_t)s.size);
+ }
+ memset(s.hdr, 0, v.shentsize);
+ if (was_debug) ++v.rw.report.removed_debug_sections;
+ }
+
+ st = obj_rewrite_commit(&v.rw, out, report_out);
+done:
+ ctx->heap->free(ctx->heap, remove, remove_size);
+ if (st != KIT_OK) obj_rewrite_discard(&v.rw);
+ return st;
+}
diff --git a/src/obj/format.h b/src/obj/format.h
@@ -41,6 +41,10 @@ typedef void (*ObjFormatCoffStubFn)(u8* dst, u64 stub_vaddr,
* additionally handles PE images (the dispatcher routes KIT_BIN_PE to COFF). */
typedef KitStatus (*ObjFormatDetectTargetFn)(const u8* data, size_t len,
KitTargetSpec* out);
+typedef KitStatus (*ObjFormatRewriteLinkedFn)(
+ const KitContext*, KitSlice name, const KitSlice* input,
+ const KitLinkedRewriteOptions*, KitWriter* out,
+ KitLinkedRewriteReport* report_out);
/* Synthetic-input hook: invoked before symbol resolution to inject a
* synthetic input object (e.g. the COFF __CTOR_LIST__/__DTOR_LIST__
@@ -311,6 +315,10 @@ typedef struct ObjFormatImpl {
/* Header-bytes -> KitTargetSpec detector (see ObjFormatDetectTargetFn). */
ObjFormatDetectTargetFn detect_target;
+ /* Raw linked-image metadata rewrite. This deliberately consumes the
+ * original bytes instead of an ObjBuilder so loader state is preserved. */
+ ObjFormatRewriteLinkedFn rewrite_linked;
+
/* Optional format-specific linker ingestion policy. */
int (*classify_obj_input)(Compiler*, ObjBuilder*, Sym* soname_out);
Sym (*archive_hint)(Compiler*, const char* archive_name);
diff --git a/src/obj/macho/rewrite.c b/src/obj/macho/rewrite.c
@@ -0,0 +1,355 @@
+#include "obj/rewrite.h"
+
+#include <limits.h>
+#include <string.h>
+
+#include "obj/macho/macho.h"
+
+#define LC_CODE_SIGNATURE 0x1du
+#define LC_DYLD_INFO 0x22u
+#define LC_DYLD_INFO_ONLY (0x22u | LC_REQ_DYLD)
+#define N_STAB 0xe0u
+#define N_TYPE 0x0eu
+#define N_EXT 0x01u
+#define INDIRECT_SYMBOL_LOCAL 0x80000000u
+#define INDIRECT_SYMBOL_ABS 0x40000000u
+
+typedef struct MachoRewriteView {
+ ObjRewrite rw;
+ u32 filetype;
+ u32 ncmds;
+ u32 sizeofcmds;
+ int has_export_trie;
+ u8 section_map[256];
+} MachoRewriteView;
+
+static KitStatus macho_bad(MachoRewriteView* v, const char* detail) {
+ kit_ctx_diagf(v->rw.ctx, "%.*s: malformed linked Mach-O image (%s)",
+ KIT_SLICE_ARG(v->rw.name), detail);
+ return KIT_MALFORMED;
+}
+
+static int fixed_name_eq(const u8* name, size_t cap, const char* want) {
+ size_t n = strlen(want);
+ return n <= cap && memcmp(name, want, n) == 0 &&
+ (n == cap || name[n] == 0);
+}
+
+static int fixed_name_prefix(const u8* name, size_t cap, const char* prefix) {
+ size_t n = strlen(prefix);
+ return n <= cap && memcmp(name, prefix, n) == 0;
+}
+
+static int macho_debug_section(const u8* sec) {
+ return fixed_name_eq(sec + 16, 16, "__DWARF") ||
+ fixed_name_prefix(sec, 16, "__debug_") ||
+ fixed_name_prefix(sec, 16, "__zdebug_") ||
+ fixed_name_eq(sec, 16, "__apple_names") ||
+ fixed_name_eq(sec, 16, "__apple_types") ||
+ fixed_name_eq(sec, 16, "__apple_namespac") ||
+ fixed_name_eq(sec, 16, "__apple_objc");
+}
+
+static KitStatus macho_validate(MachoRewriteView* v, const KitContext* ctx,
+ KitSlice name, const KitSlice* input) {
+ KitStatus st;
+ u64 pos, end;
+ u32 i;
+ memset(v, 0, sizeof *v);
+ st = obj_rewrite_init(&v->rw, ctx, name, input);
+ if (st != KIT_OK) return st;
+ if (v->rw.len < MACHO_HDR64_SIZE ||
+ obj_rw_u32(v->rw.bytes, 0) != MH_MAGIC_64)
+ return macho_bad(v, "64-bit little-endian header");
+ v->filetype = obj_rw_u32(v->rw.bytes + 12, 0);
+ if (v->filetype == MH_OBJECT) {
+ kit_ctx_diagf(ctx, "%.*s: relocatable object is not a linked image",
+ KIT_SLICE_ARG(name));
+ return KIT_INVALID;
+ }
+ if (v->filetype != MH_EXECUTE && v->filetype != MH_DYLIB &&
+ v->filetype != MH_DYLINKER && v->filetype != MH_BUNDLE)
+ return macho_bad(v, "unsupported file type");
+ v->ncmds = obj_rw_u32(v->rw.bytes + 16, 0);
+ v->sizeofcmds = obj_rw_u32(v->rw.bytes + 20, 0);
+ pos = MACHO_HDR64_SIZE;
+ end = pos + v->sizeofcmds;
+ if (end < pos || !obj_rw_range(v->rw.len, pos, v->sizeofcmds))
+ return macho_bad(v, "load-command table");
+ for (i = 0; i < v->ncmds; ++i) {
+ const u8* cmd;
+ u32 kind, size;
+ if (!obj_rw_range(v->rw.len, pos, 8))
+ return macho_bad(v, "load command");
+ cmd = v->rw.input + (size_t)pos;
+ kind = obj_rw_u32(cmd, 0);
+ size = obj_rw_u32(cmd + 4, 0);
+ if (size < 8 || pos + size > end)
+ return macho_bad(v, "load-command size");
+ if (kind == LC_SEGMENT_64) {
+ u32 nsects;
+ if (size < MACHO_SEGCMD64_SIZE)
+ return macho_bad(v, "segment command");
+ nsects = obj_rw_u32(cmd + 64, 0);
+ if ((u64)MACHO_SEGCMD64_SIZE + (u64)nsects * MACHO_SECT64_SIZE > size)
+ return macho_bad(v, "segment sections");
+ } else if (kind == LC_SYMTAB) {
+ u32 symoff, nsyms, stroff, strsize;
+ if (size < MACHO_SYMTAB_CMD_SIZE)
+ return macho_bad(v, "symbol command");
+ symoff = obj_rw_u32(cmd + 8, 0);
+ nsyms = obj_rw_u32(cmd + 12, 0);
+ stroff = obj_rw_u32(cmd + 16, 0);
+ strsize = obj_rw_u32(cmd + 20, 0);
+ if (!obj_rw_range(v->rw.len, symoff,
+ (u64)nsyms * MACHO_NLIST64_SIZE) ||
+ !obj_rw_range(v->rw.len, stroff, strsize))
+ return macho_bad(v, "symbol table");
+ } else if (kind == LC_DYSYMTAB) {
+ u32 indirectoff, nindirect;
+ if (size < MACHO_DYSYMTAB_CMD_SIZE)
+ return macho_bad(v, "dynamic-symbol command");
+ indirectoff = obj_rw_u32(cmd + 56, 0);
+ nindirect = obj_rw_u32(cmd + 60, 0);
+ if (nindirect &&
+ !obj_rw_range(v->rw.len, indirectoff, (u64)nindirect * 4u))
+ return macho_bad(v, "indirect symbols");
+ } else if (kind == LC_CODE_SIGNATURE) {
+ u32 off, size_data;
+ if (size < 16) return macho_bad(v, "code signature command");
+ off = obj_rw_u32(cmd + 8, 0);
+ size_data = obj_rw_u32(cmd + 12, 0);
+ if (!obj_rw_range(v->rw.len, off, size_data))
+ return macho_bad(v, "code signature data");
+ v->rw.report.had_signature = true;
+ } else if (kind == LC_DYLD_EXPORTS_TRIE || kind == LC_DYLD_INFO ||
+ kind == LC_DYLD_INFO_ONLY) {
+ v->has_export_trie = 1;
+ }
+ pos += size;
+ }
+ if (pos != end) return macho_bad(v, "load-command count");
+ return KIT_OK;
+}
+
+static KitStatus macho_compact_commands(MachoRewriteView* v,
+ const KitLinkedRewriteOptions* opts) {
+ u64 src_pos = MACHO_HDR64_SIZE;
+ u64 dst_pos = MACHO_HDR64_SIZE;
+ u32 old_section = 1, new_section = 1;
+ u32 kept_cmds = 0;
+ u32 i;
+ memset(v->section_map, 0, sizeof v->section_map);
+
+ for (i = 0; i < v->ncmds; ++i) {
+ const u8* src = v->rw.input + (size_t)src_pos;
+ u32 kind = obj_rw_u32(src, 0);
+ u32 size = obj_rw_u32(src + 4, 0);
+ if (kind == LC_CODE_SIGNATURE && opts->remove_signature) {
+ u32 off = obj_rw_u32(src + 8, 0);
+ u32 n = obj_rw_u32(src + 12, 0);
+ if (n) memset(v->rw.bytes + off, 0, n);
+ v->rw.report.removed_signature = true;
+ src_pos += size;
+ continue;
+ }
+ if (kind == LC_SEGMENT_64) {
+ u32 nsects = obj_rw_u32(src + 64, 0);
+ u32 kept = 0, j;
+ u8* dst = v->rw.bytes + (size_t)dst_pos;
+ int dwarf_segment = fixed_name_eq(src + 8, 16, "__DWARF");
+ memmove(dst, src, MACHO_SEGCMD64_SIZE);
+ for (j = 0; j < nsects; ++j, ++old_section) {
+ const u8* sec = src + MACHO_SEGCMD64_SIZE +
+ (size_t)j * MACHO_SECT64_SIZE;
+ int debug = dwarf_segment || macho_debug_section(sec);
+ if (old_section >= sizeof v->section_map) {
+ return macho_bad(v, "too many sections");
+ }
+ if (debug) {
+ u64 sec_size = obj_rw_u64(sec + 40, 0);
+ u32 sec_off = obj_rw_u32(sec + 48, 0);
+ if (sec_size && sec_off &&
+ obj_rw_range(v->rw.len, sec_off, sec_size))
+ memset(v->rw.bytes + sec_off, 0, (size_t)sec_size);
+ ++v->rw.report.removed_debug_sections;
+ continue;
+ }
+ v->section_map[old_section] = (u8)new_section++;
+ memmove(dst + MACHO_SEGCMD64_SIZE +
+ (size_t)kept * MACHO_SECT64_SIZE,
+ sec, MACHO_SECT64_SIZE);
+ ++kept;
+ }
+ if (dwarf_segment && kept == 0) {
+ src_pos += size;
+ continue;
+ }
+ size = MACHO_SEGCMD64_SIZE + kept * MACHO_SECT64_SIZE;
+ obj_rw_put_u32(dst + 4, size, 0);
+ obj_rw_put_u32(dst + 64, kept, 0);
+ dst_pos += size;
+ ++kept_cmds;
+ src_pos += obj_rw_u32(src + 4, 0);
+ continue;
+ }
+ memmove(v->rw.bytes + (size_t)dst_pos, src, size);
+ dst_pos += size;
+ src_pos += size;
+ ++kept_cmds;
+ }
+ if (dst_pos > MACHO_HDR64_SIZE + v->sizeofcmds)
+ return macho_bad(v, "compacted commands");
+ memset(v->rw.bytes + (size_t)dst_pos, 0,
+ (size_t)(MACHO_HDR64_SIZE + v->sizeofcmds - dst_pos));
+ obj_rw_put_u32(v->rw.bytes + 16, kept_cmds, 0);
+ obj_rw_put_u32(v->rw.bytes + 20, (u32)(dst_pos - MACHO_HDR64_SIZE), 0);
+ v->ncmds = kept_cmds;
+ v->sizeofcmds = (u32)(dst_pos - MACHO_HDR64_SIZE);
+ return KIT_OK;
+}
+
+static u8* macho_find_command(MachoRewriteView* v, u32 want) {
+ u64 pos = MACHO_HDR64_SIZE;
+ u32 i;
+ for (i = 0; i < v->ncmds; ++i) {
+ u8* cmd = v->rw.bytes + (size_t)pos;
+ u32 size = obj_rw_u32(cmd + 4, 0);
+ if (obj_rw_u32(cmd, 0) == want) return cmd;
+ pos += size;
+ }
+ return NULL;
+}
+
+static KitStatus macho_filter_symbols(MachoRewriteView* v,
+ const KitLinkedRewriteOptions* opts) {
+ u8* symcmd = macho_find_command(v, LC_SYMTAB);
+ u8* dysym = macho_find_command(v, LC_DYSYMTAB);
+ u8 *keep = NULL, *src_syms, *dst_syms;
+ u32* map = NULL;
+ u32 symoff, nsyms, newn = 0, i;
+ u32 indirectoff = 0, nindirect = 0;
+ size_t keep_size, map_size;
+ if (!symcmd) return KIT_OK;
+ symoff = obj_rw_u32(symcmd + 8, 0);
+ nsyms = obj_rw_u32(symcmd + 12, 0);
+ if (!nsyms) return KIT_OK;
+ keep_size = nsyms;
+ map_size = (size_t)nsyms * sizeof(*map);
+ keep = (u8*)v->rw.ctx->heap->alloc(v->rw.ctx->heap, keep_size, 1u);
+ map = (u32*)v->rw.ctx->heap->alloc(v->rw.ctx->heap, map_size,
+ _Alignof(u32));
+ if (!keep || !map) {
+ if (keep) v->rw.ctx->heap->free(v->rw.ctx->heap, keep, keep_size);
+ if (map) v->rw.ctx->heap->free(v->rw.ctx->heap, map, map_size);
+ return KIT_NOMEM;
+ }
+ memset(keep, 0, keep_size);
+ for (i = 0; i < nsyms; ++i) map[i] = UINT32_MAX;
+ src_syms = (u8*)v->rw.input + symoff;
+ dst_syms = v->rw.bytes + symoff;
+ for (i = 0; i < nsyms; ++i) {
+ const u8* sym = src_syms + (size_t)i * MACHO_NLIST64_SIZE;
+ u8 type = sym[4];
+ if (type & N_STAB) continue;
+ if (opts->strip_level == KIT_LINKED_STRIP_DEBUG) {
+ keep[i] = 1;
+ } else if ((type & N_TYPE) == N_UNDF ||
+ ((type & N_EXT) &&
+ (v->filetype == MH_DYLIB || !v->has_export_trie))) {
+ keep[i] = 1;
+ }
+ }
+ if (dysym) {
+ indirectoff = obj_rw_u32(dysym + 56, 0);
+ nindirect = obj_rw_u32(dysym + 60, 0);
+ for (i = 0; i < nindirect; ++i) {
+ u32 old = obj_rw_u32(v->rw.bytes + indirectoff + (size_t)i * 4u, 0);
+ if (!(old & (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) && old < nsyms)
+ keep[old] = 1;
+ }
+ }
+ for (i = 0; i < nsyms; ++i) {
+ u8* dst;
+ const u8* src;
+ u8 old_sect;
+ if (!keep[i]) continue;
+ src = src_syms + (size_t)i * MACHO_NLIST64_SIZE;
+ dst = dst_syms + (size_t)newn * MACHO_NLIST64_SIZE;
+ memmove(dst, src, MACHO_NLIST64_SIZE);
+ old_sect = dst[5];
+ if (old_sect) {
+ if (!v->section_map[old_sect]) {
+ /* A non-STAB symbol cannot survive when its defining section was debug
+ * metadata. Drop it instead of creating an invalid n_sect index. */
+ continue;
+ }
+ dst[5] = v->section_map[old_sect];
+ }
+ map[i] = newn++;
+ }
+ if (newn < nsyms)
+ memset(dst_syms + (size_t)newn * MACHO_NLIST64_SIZE, 0,
+ (size_t)(nsyms - newn) * MACHO_NLIST64_SIZE);
+ obj_rw_put_u32(symcmd + 12, newn, 0);
+ v->rw.report.removed_symbols += nsyms - newn;
+
+ if (dysym) {
+ u32 nlocal = 0, nextdef = 0, nundef = 0;
+ for (i = 0; i < newn; ++i) {
+ u8 type = dst_syms[(size_t)i * MACHO_NLIST64_SIZE + 4];
+ if (!(type & N_EXT))
+ ++nlocal;
+ else if ((type & N_TYPE) == N_UNDF)
+ ++nundef;
+ else
+ ++nextdef;
+ }
+ obj_rw_put_u32(dysym + 8, 0, 0);
+ obj_rw_put_u32(dysym + 12, nlocal, 0);
+ obj_rw_put_u32(dysym + 16, nlocal, 0);
+ obj_rw_put_u32(dysym + 20, nextdef, 0);
+ obj_rw_put_u32(dysym + 24, nlocal + nextdef, 0);
+ obj_rw_put_u32(dysym + 28, nundef, 0);
+ for (i = 0; i < nindirect; ++i) {
+ u8* entry = v->rw.bytes + indirectoff + (size_t)i * 4u;
+ u32 old = obj_rw_u32(entry, 0);
+ if (old & (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) continue;
+ if (old >= nsyms || map[old] == UINT32_MAX) {
+ v->rw.ctx->heap->free(v->rw.ctx->heap, map, map_size);
+ v->rw.ctx->heap->free(v->rw.ctx->heap, keep, keep_size);
+ return macho_bad(v, "indirect-symbol remap");
+ }
+ obj_rw_put_u32(entry, map[old], 0);
+ }
+ }
+ v->rw.ctx->heap->free(v->rw.ctx->heap, map, map_size);
+ v->rw.ctx->heap->free(v->rw.ctx->heap, keep, keep_size);
+ return KIT_OK;
+}
+
+KitStatus obj_rewrite_macho(const KitContext* ctx, KitSlice name,
+ const KitSlice* input,
+ const KitLinkedRewriteOptions* opts,
+ KitWriter* out,
+ KitLinkedRewriteReport* report_out) {
+ MachoRewriteView v;
+ KitStatus st = macho_validate(&v, ctx, name, input);
+ if (st != KIT_OK) {
+ obj_rewrite_discard(&v.rw);
+ return st;
+ }
+ if (v.rw.report.had_signature && !opts->remove_signature) {
+ kit_ctx_diagf(ctx,
+ "%.*s: signed Mach-O image is unchanged; pass "
+ "--remove-signature and sign the result again",
+ KIT_SLICE_ARG(name));
+ obj_rewrite_discard(&v.rw);
+ return KIT_UNSUPPORTED;
+ }
+ st = macho_compact_commands(&v, opts);
+ if (st == KIT_OK) st = macho_filter_symbols(&v, opts);
+ if (st == KIT_OK) st = obj_rewrite_commit(&v.rw, out, report_out);
+ if (st != KIT_OK) obj_rewrite_discard(&v.rw);
+ return st;
+}
diff --git a/src/obj/registry.c b/src/obj/registry.c
@@ -8,6 +8,7 @@
#include "obj/format.h"
#include "obj/macho/macho.h"
#include "obj/obj.h"
+#include "obj/rewrite.h"
#include "obj/wasm/wasm.h"
#if KIT_LINK_ENABLED
@@ -657,6 +658,7 @@ static const ObjFormatImpl obj_format_impl_elf = {
.elf_arch = obj_elf_arch,
.elf_machine = obj_elf_machine,
.detect_target = detect_elf,
+ .rewrite_linked = obj_rewrite_elf,
};
#endif
@@ -691,6 +693,7 @@ static const ObjFormatImpl obj_format_impl_macho = {
.macho_arch = obj_macho_arch,
.macho_cputype = obj_macho_cputype,
.detect_target = detect_macho,
+ .rewrite_linked = obj_rewrite_macho,
};
#endif
@@ -733,6 +736,7 @@ static const ObjFormatImpl obj_format_impl_coff = {
.coff_arch = obj_coff_arch,
.coff_machine = obj_coff_machine,
.detect_target = detect_coff,
+ .rewrite_linked = obj_rewrite_coff,
.classify_obj_input = coff_classify_obj_input,
.archive_hint = coff_archive_hint,
.archive_member = coff_archive_member,
diff --git a/src/obj/rewrite.c b/src/obj/rewrite.c
@@ -0,0 +1,92 @@
+#include "obj/rewrite.h"
+
+#include <string.h>
+
+KitStatus obj_rewrite_init(ObjRewrite* r, const KitContext* ctx, KitSlice name,
+ const KitSlice* input) {
+ if (!r || !ctx || !ctx->heap || !input || (!input->data && input->len))
+ return KIT_INVALID;
+ memset(r, 0, sizeof *r);
+ r->ctx = ctx;
+ r->name = name;
+ r->input = input->data;
+ r->len = input->len;
+ r->bytes = (u8*)ctx->heap->alloc(ctx->heap, r->len ? r->len : 1u, 1u);
+ if (!r->bytes) return KIT_NOMEM;
+ if (r->len) memcpy(r->bytes, r->input, r->len);
+ return KIT_OK;
+}
+
+void obj_rewrite_discard(ObjRewrite* r) {
+ if (!r || !r->bytes) return;
+ r->ctx->heap->free(r->ctx->heap, r->bytes, r->len ? r->len : 1u);
+ r->bytes = NULL;
+}
+
+KitStatus obj_rewrite_commit(ObjRewrite* r, KitWriter* out,
+ KitLinkedRewriteReport* report_out) {
+ KitStatus st;
+ if (!r || !r->bytes || !out) return KIT_INVALID;
+ st = r->len ? kit_writer_write(out, r->bytes, r->len) : KIT_OK;
+ if (st == KIT_OK) st = kit_writer_status(out);
+ if (st == KIT_OK && report_out) *report_out = r->report;
+ obj_rewrite_discard(r);
+ return st == KIT_OK ? KIT_OK : KIT_IO;
+}
+
+int obj_rw_range(size_t len, u64 off, u64 size) {
+ return off <= (u64)len && size <= (u64)len - off;
+}
+
+u16 obj_rw_u16(const u8* p, int be) {
+ return be ? (u16)(((u16)p[0] << 8) | p[1])
+ : (u16)((u16)p[0] | ((u16)p[1] << 8));
+}
+
+u32 obj_rw_u32(const u8* p, int be) {
+ if (be)
+ return ((u32)p[0] << 24) | ((u32)p[1] << 16) | ((u32)p[2] << 8) |
+ (u32)p[3];
+ return (u32)p[0] | ((u32)p[1] << 8) | ((u32)p[2] << 16) |
+ ((u32)p[3] << 24);
+}
+
+u64 obj_rw_u64(const u8* p, int be) {
+ u32 a = obj_rw_u32(p, be);
+ u32 b = obj_rw_u32(p + 4, be);
+ return be ? ((u64)a << 32) | b : (u64)a | ((u64)b << 32);
+}
+
+void obj_rw_put_u16(u8* p, u16 v, int be) {
+ if (be) {
+ p[0] = (u8)(v >> 8);
+ p[1] = (u8)v;
+ } else {
+ p[0] = (u8)v;
+ p[1] = (u8)(v >> 8);
+ }
+}
+
+void obj_rw_put_u32(u8* p, u32 v, int be) {
+ if (be) {
+ p[0] = (u8)(v >> 24);
+ p[1] = (u8)(v >> 16);
+ p[2] = (u8)(v >> 8);
+ p[3] = (u8)v;
+ } else {
+ p[0] = (u8)v;
+ p[1] = (u8)(v >> 8);
+ p[2] = (u8)(v >> 16);
+ p[3] = (u8)(v >> 24);
+ }
+}
+
+void obj_rw_put_u64(u8* p, u64 v, int be) {
+ if (be) {
+ obj_rw_put_u32(p, (u32)(v >> 32), 1);
+ obj_rw_put_u32(p + 4, (u32)v, 1);
+ } else {
+ obj_rw_put_u32(p, (u32)v, 0);
+ obj_rw_put_u32(p + 4, (u32)(v >> 32), 0);
+ }
+}
diff --git a/src/obj/rewrite.h b/src/obj/rewrite.h
@@ -0,0 +1,42 @@
+#ifndef KIT_OBJ_REWRITE_H
+#define KIT_OBJ_REWRITE_H
+
+#include <kit/object.h>
+
+#include "core/core.h"
+#include "core/diag.h"
+
+typedef struct ObjRewrite {
+ const KitContext* ctx;
+ KitSlice name;
+ const u8* input;
+ size_t len;
+ u8* bytes;
+ KitLinkedRewriteReport report;
+} ObjRewrite;
+
+KitStatus obj_rewrite_init(ObjRewrite*, const KitContext*, KitSlice name,
+ const KitSlice* input);
+void obj_rewrite_discard(ObjRewrite*);
+KitStatus obj_rewrite_commit(ObjRewrite*, KitWriter*,
+ KitLinkedRewriteReport* report_out);
+
+int obj_rw_range(size_t len, u64 off, u64 size);
+u16 obj_rw_u16(const u8* p, int be);
+u32 obj_rw_u32(const u8* p, int be);
+u64 obj_rw_u64(const u8* p, int be);
+void obj_rw_put_u16(u8* p, u16 v, int be);
+void obj_rw_put_u32(u8* p, u32 v, int be);
+void obj_rw_put_u64(u8* p, u64 v, int be);
+
+KitStatus obj_rewrite_elf(const KitContext*, KitSlice, const KitSlice*,
+ const KitLinkedRewriteOptions*, KitWriter*,
+ KitLinkedRewriteReport*);
+KitStatus obj_rewrite_macho(const KitContext*, KitSlice, const KitSlice*,
+ const KitLinkedRewriteOptions*, KitWriter*,
+ KitLinkedRewriteReport*);
+KitStatus obj_rewrite_coff(const KitContext*, KitSlice, const KitSlice*,
+ const KitLinkedRewriteOptions*, KitWriter*,
+ KitLinkedRewriteReport*);
+
+#endif
diff --git a/test/api/object_rewrite_test.c b/test/api/object_rewrite_test.c
@@ -0,0 +1,217 @@
+#include <kit/object.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "lib/kit_unit.h"
+
+#define PE_SIZE 0x900u
+#define PE_OFF 0x80u
+#define FILE_HDR (PE_OFF + 4u)
+#define OPT_HDR (FILE_HDR + 20u)
+#define SEC_HDR (OPT_HDR + 240u)
+#define DEBUG_RAW 0x400u
+#define RDATA_RAW 0x600u
+#define CERT_RAW 0x800u
+#define SYMTAB_RAW 0x880u
+
+static void put16(uint8_t* p, uint16_t v) {
+ p[0] = (uint8_t)v;
+ p[1] = (uint8_t)(v >> 8);
+}
+
+static void put32(uint8_t* p, uint32_t v) {
+ p[0] = (uint8_t)v;
+ p[1] = (uint8_t)(v >> 8);
+ p[2] = (uint8_t)(v >> 16);
+ p[3] = (uint8_t)(v >> 24);
+}
+
+static uint32_t get32(const uint8_t* p) {
+ return (uint32_t)p[0] | ((uint32_t)p[1] << 8) |
+ ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
+}
+
+static void put_dir(uint8_t* pe, uint32_t index, uint32_t addr,
+ uint32_t size) {
+ uint8_t* d = pe + OPT_HDR + 112u + index * 8u;
+ put32(d, addr);
+ put32(d + 4, size);
+}
+
+static void put_section(uint8_t* pe, uint32_t index, const char* name,
+ uint32_t virtual_size, uint32_t va,
+ uint32_t raw_size, uint32_t raw_off,
+ uint32_t flags) {
+ uint8_t* s = pe + SEC_HDR + index * 40u;
+ size_t n = strlen(name);
+ if (n > 8u) n = 8u;
+ memcpy(s, name, n);
+ put32(s + 8, virtual_size);
+ put32(s + 12, va);
+ put32(s + 16, raw_size);
+ put32(s + 20, raw_off);
+ put32(s + 36, flags);
+}
+
+static void build_signed_debug_pe(uint8_t pe[PE_SIZE]) {
+ uint8_t* debug_entry;
+ memset(pe, 0, PE_SIZE);
+ put16(pe, 0x5a4du);
+ put32(pe + 0x3c, PE_OFF);
+ put32(pe + PE_OFF, 0x00004550u);
+ put16(pe + FILE_HDR, 0x8664u);
+ put16(pe + FILE_HDR + 2, 3u);
+ put32(pe + FILE_HDR + 8, SYMTAB_RAW);
+ put32(pe + FILE_HDR + 12, 1u);
+ put16(pe + FILE_HDR + 16, 240u);
+ put16(pe + FILE_HDR + 18, 0x22u);
+ put16(pe + OPT_HDR, 0x020bu);
+ put32(pe + OPT_HDR + 16, 0x1000u);
+ put32(pe + OPT_HDR + 20, 0x1000u);
+ put32(pe + OPT_HDR + 60, 0x200u);
+ put32(pe + OPT_HDR + 108, 16u);
+ put_dir(pe, 1u, 0x33330000u, 0x44u); /* import metadata sentinel */
+ put_dir(pe, 4u, CERT_RAW, 0x80u);
+ put_dir(pe, 6u, 0x3000u, 28u);
+ put_dir(pe, 9u, 0x55550000u, 0x66u); /* TLS metadata sentinel */
+ put_section(pe, 0u, ".text", 0x80u, 0x1000u, 0x200u, 0x200u,
+ 0x60000020u);
+ put_section(pe, 1u, ".debug", 0x80u, 0x2000u, 0x200u, DEBUG_RAW,
+ 0x42000040u);
+ put_section(pe, 2u, ".rdata", 0x80u, 0x3000u, 0x200u, RDATA_RAW,
+ 0x40000040u);
+ memset(pe + 0x200u, 0xa5, 0x200u);
+ memset(pe + DEBUG_RAW, 0xdb, 0x200u);
+ debug_entry = pe + RDATA_RAW;
+ put32(debug_entry + 12, 2u); /* CodeView */
+ put32(debug_entry + 16, 16u);
+ put32(debug_entry + 20, 0x2020u);
+ put32(debug_entry + 24, 0x420u);
+ memset(pe + CERT_RAW, 0xce, 0x80u);
+ memset(pe + SYMTAB_RAW, 0x5a, 18u);
+ put32(pe + SYMTAB_RAW + 18u, 4u);
+}
+
+static int all_byte(const uint8_t* p, size_t n, uint8_t value) {
+ size_t i;
+ for (i = 0; i < n; ++i)
+ if (p[i] != value) return 0;
+ return 1;
+}
+
+static void check_signed_transaction(KitUnit* u, const uint8_t* pe) {
+ uint8_t snapshot[PE_SIZE];
+ KitSlice input;
+ KitLinkedRewriteOptions opts;
+ KitWriter* out = NULL;
+ size_t out_len = 99u;
+ KitStatus st;
+ memcpy(snapshot, pe, PE_SIZE);
+ input.data = pe;
+ input.len = PE_SIZE;
+ memset(&opts, 0, sizeof opts);
+ opts.strip_level = KIT_LINKED_STRIP_DEBUG;
+ CU_EXPECT(u, kit_writer_mem(&u->heap, &out) == KIT_OK && out,
+ "signed transaction memory writer");
+ st = kit_obj_rewrite_linked(&u->ctx, KIT_SLICE_LIT("signed.exe"), &input,
+ &opts, out, NULL);
+ CU_EXPECT(u, st == KIT_UNSUPPORTED,
+ "signed PE without opt-in is unsupported (%d)", (int)st);
+ (void)kit_writer_mem_bytes(out, &out_len);
+ CU_EXPECT(u, out_len == 0u, "signed failure emits no bytes");
+ CU_EXPECT(u, memcmp(pe, snapshot, PE_SIZE) == 0,
+ "signed failure leaves input unchanged");
+ CU_EXPECT(u, strstr(u->last_diag, "--remove-signature") != NULL,
+ "signed failure explains explicit opt-in");
+ kit_writer_close(out);
+}
+
+static void check_strip_debug(KitUnit* u, const uint8_t* pe) {
+ KitSlice input;
+ KitLinkedRewriteOptions opts;
+ KitLinkedRewriteReport report;
+ KitWriter* out = NULL;
+ const uint8_t* bytes;
+ size_t len = 0;
+ memset(&opts, 0, sizeof opts);
+ input.data = pe;
+ input.len = PE_SIZE;
+ memset(&report, 0, sizeof report);
+ opts.strip_level = KIT_LINKED_STRIP_DEBUG;
+ opts.remove_signature = true;
+ CU_EXPECT(u, kit_writer_mem(&u->heap, &out) == KIT_OK && out,
+ "debug rewrite memory writer");
+ CU_EXPECT(u,
+ kit_obj_rewrite_linked(&u->ctx, KIT_SLICE_LIT("debug.exe"),
+ &input, &opts, out, &report) == KIT_OK,
+ "debug rewrite succeeds");
+ bytes = kit_writer_mem_bytes(out, &len);
+ CU_EXPECT(u, len == PE_SIZE, "PE rewrite preserves file extent");
+ CU_EXPECT(u, get32(bytes + OPT_HDR + 16) == 0x1000u,
+ "entry point preserved");
+ CU_EXPECT(u, get32(bytes + OPT_HDR + 112u + 8u) == 0x33330000u &&
+ get32(bytes + OPT_HDR + 112u + 9u * 8u) == 0x55550000u,
+ "import and TLS directories preserved");
+ CU_EXPECT(u, all_byte(bytes + 0x200u, 0x200u, 0xa5),
+ "mapped text bytes preserved");
+ CU_EXPECT(u, all_byte(bytes + DEBUG_RAW, 0x200u, 0),
+ "debug section bytes removed");
+ CU_EXPECT(u, all_byte(bytes + CERT_RAW, 0x80u, 0),
+ "certificate bytes removed");
+ CU_EXPECT(u, get32(bytes + OPT_HDR + 112u + 4u * 8u) == 0u &&
+ get32(bytes + OPT_HDR + 112u + 6u * 8u) == 0u,
+ "certificate and debug directories cleared");
+ CU_EXPECT(u, get32(bytes + FILE_HDR + 8) == SYMTAB_RAW &&
+ get32(bytes + FILE_HDR + 12) == 1u,
+ "--strip-debug retains COFF symbol table");
+ CU_EXPECT(u, report.had_signature && report.removed_signature,
+ "signature removal reported");
+ CU_EXPECT(u, report.removed_debug_sections == 2u,
+ "debug directory and section reported");
+ kit_writer_close(out);
+}
+
+static void check_strip_all(KitUnit* u, const uint8_t* pe) {
+ KitSlice input;
+ KitLinkedRewriteOptions opts;
+ KitLinkedRewriteReport report;
+ KitWriter* out = NULL;
+ const uint8_t* bytes;
+ size_t len = 0;
+ memset(&opts, 0, sizeof opts);
+ input.data = pe;
+ input.len = PE_SIZE;
+ memset(&report, 0, sizeof report);
+ opts.strip_level = KIT_LINKED_STRIP_ALL;
+ opts.remove_signature = true;
+ CU_EXPECT(u, kit_writer_mem(&u->heap, &out) == KIT_OK && out,
+ "all rewrite memory writer");
+ CU_EXPECT(u,
+ kit_obj_rewrite_linked(&u->ctx, KIT_SLICE_LIT("all.exe"), &input,
+ &opts, out, &report) == KIT_OK,
+ "strip-all rewrite succeeds");
+ bytes = kit_writer_mem_bytes(out, &len);
+ CU_EXPECT(u, len == PE_SIZE, "strip-all preserves PE extent");
+ CU_EXPECT(u, get32(bytes + FILE_HDR + 8) == 0u &&
+ get32(bytes + FILE_HDR + 12) == 0u,
+ "strip-all clears COFF symbol-table header");
+ CU_EXPECT(u, all_byte(bytes + SYMTAB_RAW, 22u, 0),
+ "strip-all clears COFF symbol and string records");
+ CU_EXPECT(u, report.removed_symbols == 1u,
+ "strip-all reports removed symbols");
+ kit_writer_close(out);
+}
+
+int main(void) {
+ KitUnit u;
+ uint8_t pe[PE_SIZE];
+ kit_unit_init(&u);
+ build_signed_debug_pe(pe);
+ check_signed_transaction(&u, pe);
+ check_strip_debug(&u, pe);
+ check_strip_all(&u, pe);
+ kit_unit_summary(&u, "object_rewrite_test");
+ return kit_unit_status(&u);
+}