kit

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

commit a2df3a5a2105cd5314df6542dfb42ade10c623d8
parent 2e3847ef14119de226f9941f6de96ca204877e2a
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue, 16 Jun 2026 14:22:45 -0700

image: --format rom/sections, --metadata, selection/require/no-dynamic flags

Replace the bin-only gate in obj_emit_image with a real format dispatch:

- --format rom: fixed-size flat binary. Requires --pad-to as the explicit
  size; deterministic fill of unused bytes; fails when payload exceeds the
  requested size. Draws from load segments by default, or from a --section
  list when one is given.
- --format sections: concatenate --section NAME in declared order via a new
  collect_sections() path over the section-reading API. Rejects a missing
  named section; report->sections_concat marks the base as not a load address.
- --metadata FILE: deterministic JSON sidecar (stable key order, no host
  paths/timestamps) with target/object-format/entry/build-id, format, source,
  selection, base/bias/fill policy, ranges, output size, and hole warnings.
  Build-id is parsed from .note.gnu.build-id; target from the object's spec.
- Selection/validation flags wired into KitImageOptions and enforced before
  any bytes are written: --only-section/--remove-section filter the segment
  selection; --section orders the sections format; --require-entry/
  --require-symbol/--require-section/--no-dynamic validate the input.
- --format elf is deferred with a diagnostic pointing at objcopy/strip.

objcopy -O binary is unchanged (still bin/segments) and byte-identical.

Diffstat:
Mdriver/cmd/image.c | 512+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Minclude/kit/image.h | 26++++++++++++++++++++++++++
Msrc/obj/image.c | 326++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
3 files changed, 815 insertions(+), 49 deletions(-)

diff --git a/driver/cmd/image.c b/driver/cmd/image.c @@ -1,5 +1,8 @@ #include <kit/image.h> +#include <kit/object.h> +#include <stdarg.h> #include <stdint.h> +#include <stdio.h> #include <string.h> #include "driver.h" @@ -8,8 +11,25 @@ typedef struct ImageCliOpts { KitImageOptions image; + /* Backing storage for the repeatable name lists referenced by `image`. Each + * list owns its own array; the KitImageOptions pointers alias into them. */ KitSlice* segments; uint32_t cap_segments; + KitSlice* only_sections; + uint32_t cap_only; + KitSlice* remove_sections; + uint32_t cap_remove; + KitSlice* section_order; + uint32_t cap_section_order; + KitSlice* require_symbols; + uint32_t cap_require_symbols; + KitSlice* require_sections; + uint32_t cap_require_sections; + /* ELF/debug knobs (only meaningful for --format elf, which is deferred). */ + bool strip_debug; + bool keep_symbols; + const char* split_debug; + const char* metadata; const char* input; const char* output; } ImageCliOpts; @@ -24,10 +44,22 @@ void driver_help_image(void) { " kit image [OPTIONS] INPUT [-o OUTPUT]\n" "\n" "FORMAT\n" - " --format bin emit a flat binary image (default)\n" + " --format bin flat binary from loadable segments " + "(default)\n" + " --format rom fixed-size flat binary (requires " + "--pad-to)\n" + " --format sections concatenate --section bytes in order\n" " --from segments derive bytes from loadable segments " "(default)\n" + " --from sections derive bytes from named sections\n" + "\n" + "SELECTION\n" " --segment PT_LOAD select a segment kind/name (may repeat)\n" + " --only-section NAME restrict selection to NAME (may repeat)\n" + " --remove-section NAME drop NAME from the selection (may " + "repeat)\n" + " --section NAME ordered section for --format sections " + "(may repeat)\n" "\n" "ADDRESSING\n" " --addr vaddr|paddr|lma address field used for layout " @@ -43,6 +75,18 @@ void driver_help_image(void) { " --pad-to SIZE pad final size to SIZE\n" " --max-size SIZE reject output larger than SIZE\n" "\n" + "VALIDATION\n" + " --require-entry fail unless the image declares an " + "entry\n" + " --require-symbol NAME fail unless NAME is defined (may " + "repeat)\n" + " --require-section NAME fail unless NAME is present (may " + "repeat)\n" + " --no-dynamic reject dynamic-linking artifacts\n" + "\n" + "REPORTING\n" + " --metadata FILE write a deterministic JSON sidecar\n" + "\n" "OUTPUT\n" " -o, --output FILE write output to FILE (default: stdout)\n" "\n" @@ -195,35 +239,342 @@ static int parse_addr(const char* s, uint32_t* out) { return -1; } -static int push_segment(DriverEnv* env, ImageCliOpts* o, const char* name) { - if (o->image.nsegments >= o->cap_segments) { - uint32_t newcap = o->cap_segments ? o->cap_segments * 2u : 4u; +/* Append cstr `name` to a growable KitSlice list, re-aliasing the matching + * KitImageOptions pointer at *aliased_ptr to the (possibly reallocated) + * storage and bumping *count. */ +static int push_name(DriverEnv* env, KitSlice** arr, uint32_t* count, + uint32_t* cap, const KitSlice** aliased_ptr, + const char* name) { + if (*count >= *cap) { + uint32_t newcap = *cap ? *cap * 2u : 4u; KitSlice* next; - if (newcap <= o->cap_segments) return -1; - next = - (KitSlice*)driver_alloc_zeroed(env, (size_t)newcap * sizeof(*next)); + if (newcap <= *cap) return -1; + next = (KitSlice*)driver_alloc_zeroed(env, (size_t)newcap * sizeof(*next)); if (!next) return -1; - if (o->segments) { - memcpy(next, o->segments, - (size_t)o->image.nsegments * sizeof(*next)); - driver_free(env, o->segments, - (size_t)o->cap_segments * sizeof(*o->segments)); + if (*arr) { + memcpy(next, *arr, (size_t)(*count) * sizeof(*next)); + driver_free(env, *arr, (size_t)(*cap) * sizeof(**arr)); } - o->segments = next; - o->cap_segments = newcap; - o->image.segments = o->segments; + *arr = next; + *cap = newcap; + *aliased_ptr = next; } - o->segments[o->image.nsegments++] = kit_slice_cstr(name); + (*arr)[(*count)++] = kit_slice_cstr(name); return 0; } +/* ---- metadata JSON sidecar ------------------------------------------------ + * + * Deterministic: stable key order, no host paths, no timestamps. Values that + * vary by input (target triple, entry, build id, ranges) are derived from the + * opened object and the layout report. + * + * The driver has no public string-builder, so the document is assembled into a + * small growable byte buffer with snprintf-backed appends, then written in one + * shot. */ + +typedef struct MetaBuf { + DriverEnv* env; + char* data; + size_t len; + size_t cap; + int oom; +} MetaBuf; + +static void meta_reserve(MetaBuf* mb, size_t extra) { + size_t need = mb->len + extra + 1u; /* + NUL room for snprintf */ + if (mb->oom || need <= mb->cap) return; + { + size_t newcap = mb->cap ? mb->cap : 256u; + char* next; + while (newcap < need) { + size_t doubled = newcap * 2u; + if (doubled <= newcap) { + mb->oom = 1; + return; + } + newcap = doubled; + } + next = (char*)driver_alloc_zeroed(mb->env, newcap); + if (!next) { + mb->oom = 1; + return; + } + if (mb->data) { + memcpy(next, mb->data, mb->len); + driver_free(mb->env, mb->data, mb->cap); + } + mb->data = next; + mb->cap = newcap; + } +} + +static void meta_putc(MetaBuf* mb, char c) { + meta_reserve(mb, 1); + if (mb->oom) return; + mb->data[mb->len++] = c; +} + +static void meta_putf(MetaBuf* mb, const char* fmt, ...) { + va_list ap, ap2; + int n; + va_start(ap, fmt); + va_copy(ap2, ap); + n = vsnprintf(NULL, 0, fmt, ap); + va_end(ap); + if (n < 0) { + mb->oom = 1; + va_end(ap2); + return; + } + meta_reserve(mb, (size_t)n); + if (mb->oom) { + va_end(ap2); + return; + } + vsnprintf(mb->data + mb->len, (size_t)n + 1u, fmt, ap2); + va_end(ap2); + mb->len += (size_t)n; +} + +/* JSON-escape a section/segment name (quotes + minimal escaping). */ +static void meta_put_json_string(MetaBuf* mb, KitSlice s) { + size_t i; + meta_putc(mb, '"'); + for (i = 0; i < s.len; ++i) { + char c = s.s[i]; + if (c == '"' || c == '\\') { + meta_putc(mb, '\\'); + meta_putc(mb, c); + } else if ((unsigned char)c < 0x20) { + meta_putf(mb, "\\u%04x", (unsigned)(unsigned char)c); + } else { + meta_putc(mb, c); + } + } + meta_putc(mb, '"'); +} + +/* Locate and parse a `.note.gnu.build-id` ELF note's descriptor bytes. Returns + * the descriptor slice (borrowed from the object) or an empty slice. */ +static KitSlice read_build_id(KitObjFile* of) { + KitObjSection sid = KIT_SECTION_NONE; + const uint8_t* data = NULL; + size_t len = 0; + uint32_t namesz, descsz; + size_t off; + KitSlice empty = KIT_SLICE_NULL; + + if (kit_obj_section_by_name(of, kit_slice_cstr(".note.gnu.build-id"), &sid) != + KIT_OK || + sid == KIT_SECTION_NONE) + return empty; + if (kit_obj_section_data(of, sid, &data, &len) != KIT_OK || !data || len < 12) + return empty; + /* ELF note: u32 namesz, u32 descsz, u32 type, name[namesz], desc[descsz]. + * Little-endian (the only endianness kit links). */ + namesz = (uint32_t)data[0] | ((uint32_t)data[1] << 8) | + ((uint32_t)data[2] << 16) | ((uint32_t)data[3] << 24); + descsz = (uint32_t)data[4] | ((uint32_t)data[5] << 8) | + ((uint32_t)data[6] << 16) | ((uint32_t)data[7] << 24); + off = 12u + ((namesz + 3u) & ~3u); + if (off > len || descsz > len - off) return empty; + { + KitSlice s; + s.s = (const char*)data + off; + s.len = descsz; + return s; + } +} + +/* Emit the JSON list of selected segments/sections that contributed bytes. + * For the section path this is the declared --section order; for the segment + * path it is the loadable segments the layout selected — mirroring the + * emitter's default-ELF-PT_LOAD / explicit-list policy plus only/remove + * filters, so the sidecar reflects exactly what the image contains. */ +static void meta_put_selection(MetaBuf* mb, KitObjFile* of, + const ImageCliOpts* o, int from_sections) { + uint32_t i; + int first = 1; + if (from_sections) { + for (i = 0; i < o->image.nsection_order; ++i) { + if (!first) meta_putc(mb, ','); + first = 0; + meta_put_json_string(mb, o->image.section_order[i]); + } + return; + } + { + KitObjSegIter* it = NULL; + KitObjSegInfo seg; + KitObjFmt fmt = kit_obj_fmt(of); + if (kit_obj_segiter_new(of, &it) != KIT_OK) return; + while (kit_obj_segiter_next(it, &seg) == KIT_ITER_ITEM) { + int keep = 1; + uint32_t j; + if (seg.file_size == 0) continue; + if (o->image.nremove_sections) { + for (j = 0; j < o->image.nremove_sections; ++j) { + KitSlice n = o->image.remove_sections[j]; + if (seg.name.len == n.len && + (n.len == 0 || memcmp(seg.name.s, n.s, n.len) == 0)) { + keep = 0; + break; + } + } + } + if (keep && o->image.nonly_sections) { + keep = 0; + for (j = 0; j < o->image.nonly_sections; ++j) { + KitSlice n = o->image.only_sections[j]; + if (seg.name.len == n.len && + (n.len == 0 || memcmp(seg.name.s, n.s, n.len) == 0)) { + keep = 1; + break; + } + } + } else if (keep && o->image.nsegments) { + keep = 0; + for (j = 0; j < o->image.nsegments; ++j) { + KitSlice n = o->image.segments[j]; + if (seg.name.len == n.len && + (n.len == 0 || memcmp(seg.name.s, n.s, n.len) == 0)) { + keep = 1; + break; + } + } + } else if (keep && !o->image.nonly_sections && fmt == KIT_OBJ_ELF) { + /* Default ELF selection: PT_LOAD segments. */ + keep = (seg.name.len >= 4 && + (seg.name.s[seg.name.len - 4] == 'L' || + seg.name.s[seg.name.len - 4] == 'l')); + } + if (!keep) continue; + if (!first) meta_putc(mb, ','); + first = 0; + if (seg.name.len) + meta_put_json_string(mb, seg.name); + else + meta_put_json_string(mb, kit_slice_cstr("PT_LOAD")); + } + kit_obj_segiter_free(it); + } +} + +static int write_metadata(DriverEnv* env, const KitContext* ctx, KitObjFile* of, + const ImageCliOpts* o, const KitImageReport* report, + const char* path) { + MetaBuf mb; + KitObjImageInfo info; + KitTargetSpec spec; + char triple[128]; + KitSlice build_id; + int from_sections; + int have_info; + KitWriter* w = NULL; + int rc = 0; + + memset(&mb, 0, sizeof mb); + mb.env = env; + + spec = kit_obj_target(of); + if (driver_target_to_triple(spec, triple, sizeof triple) != 0) + triple[0] = '\0'; + have_info = (kit_obj_image_info(of, &info) == KIT_OK); + build_id = read_build_id(of); + /* Mirror obj_emit_image's source selection so the sidecar names the source + * that actually produced the bytes (rom draws from sections when --section + * is given). */ + from_sections = + (o->image.format == KIT_IMAGE_FORMAT_SECTIONS) || + (o->image.from == KIT_IMAGE_FROM_SECTIONS) || + (o->image.format == KIT_IMAGE_FORMAT_ROM && o->image.nsection_order > 0); + + /* Keys are emitted in a fixed order so the sidecar is byte-stable. */ + meta_putf(&mb, "{\n"); + meta_putf(&mb, " \"format\": \"%s\",\n", + o->image.format == KIT_IMAGE_FORMAT_ROM ? "rom" + : o->image.format == KIT_IMAGE_FORMAT_SECTIONS ? "sections" + : o->image.format == KIT_IMAGE_FORMAT_ELF ? "elf" + : "bin"); + meta_putf(&mb, " \"source\": \"%s\",\n", + from_sections ? "sections" : "segments"); + meta_putf(&mb, " \"target\": \"%s\",\n", triple); + meta_putf(&mb, " \"object_format\": \"%s\",\n", + kit_obj_fmt_name(kit_obj_fmt(of))); + if (have_info && info.entry) + meta_putf(&mb, " \"entry\": \"0x%llx\",\n", + (unsigned long long)info.entry); + else + meta_putf(&mb, " \"entry\": null,\n"); + if (build_id.len) { + size_t k; + meta_putf(&mb, " \"build_id\": \""); + for (k = 0; k < build_id.len; ++k) + meta_putf(&mb, "%02x", (unsigned)(unsigned char)build_id.s[k]); + meta_putf(&mb, "\",\n"); + } else { + meta_putf(&mb, " \"build_id\": null,\n"); + } + + meta_putf(&mb, " \"selection\": ["); + meta_put_selection(&mb, of, o, from_sections); + meta_putf(&mb, "],\n"); + + meta_putf(&mb, " \"addr_kind\": \"%s\",\n", + o->image.addr == KIT_IMAGE_ADDR_PADDR ? "paddr" + : o->image.addr == KIT_IMAGE_ADDR_LMA ? "lma" + : "vaddr"); + meta_putf(&mb, " \"base\": \"0x%llx\",\n", + (unsigned long long)report->base); + meta_putf(&mb, " \"base_is_load_address\": %s,\n", + report->sections_concat ? "false" : "true"); + meta_putf(&mb, " \"bias\": %lld,\n", (long long)o->image.bias); + meta_putf(&mb, " \"fill\": \"0x%02x\",\n", (unsigned)o->image.fill); + meta_putf(&mb, " \"payload_size\": %llu,\n", + (unsigned long long)report->payload_size); + meta_putf(&mb, " \"output_size\": %llu,\n", + (unsigned long long)report->size); + meta_putf(&mb, " \"ranges\": %u,\n", (unsigned)report->nranges); + meta_putf(&mb, " \"max_hole\": %llu,\n", + (unsigned long long)report->max_hole); + meta_putf(&mb, " \"warnings\": ["); + if (report->had_holes) meta_putf(&mb, "\"holes_present\""); + meta_putf(&mb, "]\n"); + meta_putf(&mb, "}\n"); + + if (mb.oom) { + driver_errf(IMAGE_TOOL, "out of memory building metadata"); + rc = 1; + goto done; + } + if (ctx->file_io->open_writer(ctx->file_io->user, path, &w) != KIT_OK) { + driver_errf(IMAGE_TOOL, "failed to open metadata file: %s", path); + rc = 1; + goto done; + } + if (kit_writer_write(w, mb.data, mb.len) != KIT_OK || + kit_writer_status(w) != KIT_OK) { + driver_writer_abort(w); + driver_errf(IMAGE_TOOL, "failed to write metadata file: %s", path); + rc = 1; + } + +done: + if (w) kit_writer_close(w); + if (mb.data) driver_free(env, mb.data, mb.cap); + return rc; +} + int driver_image(int argc, char** argv) { DriverEnv env; KitContext ctx; ImageCliOpts o; DriverLoad load = {0}; KitSlice input = KIT_SLICE_NULL; + KitObjFile* of = NULL; KitWriter* out = NULL; + KitImageReport report; int own_writer = 0; int rc = 2; int i; @@ -234,6 +585,7 @@ int driver_image(int argc, char** argv) { } memset(&o, 0, sizeof o); + memset(&report, 0, sizeof report); o.image.format = KIT_IMAGE_FORMAT_BIN; o.image.from = KIT_IMAGE_FROM_SEGMENTS; o.image.addr = KIT_IMAGE_ADDR_VADDR; @@ -280,7 +632,78 @@ int driver_image(int argc, char** argv) { matched = take_value(&i, argc, argv, "--segment", &val); if (matched < 0) goto missing_value; if (matched) { - if (push_segment(&env, &o, val) != 0) goto oom; + if (push_name(&env, &o.segments, &o.image.nsegments, &o.cap_segments, + &o.image.segments, val) != 0) + goto oom; + continue; + } + matched = take_value(&i, argc, argv, "--only-section", &val); + if (matched < 0) goto missing_value; + if (matched) { + if (push_name(&env, &o.only_sections, &o.image.nonly_sections, + &o.cap_only, &o.image.only_sections, val) != 0) + goto oom; + continue; + } + matched = take_value(&i, argc, argv, "--remove-section", &val); + if (matched < 0) goto missing_value; + if (matched) { + if (push_name(&env, &o.remove_sections, &o.image.nremove_sections, + &o.cap_remove, &o.image.remove_sections, val) != 0) + goto oom; + continue; + } + matched = take_value(&i, argc, argv, "--section", &val); + if (matched < 0) goto missing_value; + if (matched) { + if (push_name(&env, &o.section_order, &o.image.nsection_order, + &o.cap_section_order, &o.image.section_order, val) != 0) + goto oom; + continue; + } + matched = take_value(&i, argc, argv, "--require-symbol", &val); + if (matched < 0) goto missing_value; + if (matched) { + if (push_name(&env, &o.require_symbols, &o.image.nrequire_symbols, + &o.cap_require_symbols, &o.image.require_symbols, val) != 0) + goto oom; + continue; + } + matched = take_value(&i, argc, argv, "--require-section", &val); + if (matched < 0) goto missing_value; + if (matched) { + if (push_name(&env, &o.require_sections, &o.image.nrequire_sections, + &o.cap_require_sections, &o.image.require_sections, val) != + 0) + goto oom; + continue; + } + if (driver_streq(a, "--require-entry")) { + o.image.require_entry = true; + continue; + } + if (driver_streq(a, "--no-dynamic")) { + o.image.no_dynamic = true; + continue; + } + if (driver_streq(a, "--strip-debug")) { + o.strip_debug = true; + continue; + } + if (driver_streq(a, "--keep-symbols")) { + o.keep_symbols = true; + continue; + } + matched = take_value(&i, argc, argv, "--split-debug", &val); + if (matched < 0) goto missing_value; + if (matched) { + o.split_debug = val; + continue; + } + matched = take_value(&i, argc, argv, "--metadata", &val); + if (matched < 0) goto missing_value; + if (matched) { + o.metadata = val; continue; } matched = take_value(&i, argc, argv, "--addr", &val); @@ -381,12 +804,26 @@ int driver_image(int argc, char** argv) { driver_errf(IMAGE_TOOL, "missing input file"); goto done; } + if (o.split_debug || o.keep_symbols || o.strip_debug) { + if (o.image.format != KIT_IMAGE_FORMAT_ELF) { + driver_errf(IMAGE_TOOL, + "--strip-debug/--split-debug/--keep-symbols are only " + "meaningful with --format elf"); + goto done; + } + } if (driver_load_bytes(&env.file_io, IMAGE_TOOL, o.input, &load, &input) != 0) { rc = 1; goto done; } + if (kit_obj_open(&ctx, kit_slice_cstr(o.input), &input, &of) != KIT_OK) { + driver_errf(IMAGE_TOOL, "%s is not a recognized linked object", o.input); + rc = 1; + goto done; + } + if (o.output) { if (ctx.file_io->open_writer(ctx.file_io->user, o.output, &out) != KIT_OK) { driver_errf(IMAGE_TOOL, "failed to open output: %s", o.output); @@ -399,17 +836,48 @@ int driver_image(int argc, char** argv) { } own_writer = 1; - rc = kit_image_emit_bytes(&ctx, kit_slice_cstr(o.input), &input, &o.image, - out, NULL) == KIT_OK - ? 0 - : 1; - if (rc != 0 && out) driver_writer_abort(out); + if (kit_image_emit(&ctx, of, &input, &o.image, out, &report) != KIT_OK) { + driver_writer_abort(out); + rc = 1; + goto done; + } + /* Close the image writer before writing the sidecar so the image bytes are + * flushed and the metadata reflects a complete output. */ + if (own_writer && out) { + kit_writer_close(out); + out = NULL; + own_writer = 0; + } + rc = 0; + + if (o.metadata) { + if (write_metadata(&env, &ctx, of, &o, &report, o.metadata) != 0) { + rc = 1; + goto done; + } + } done: if (own_writer && out) kit_writer_close(out); + if (of) kit_obj_free(of); driver_release_bytes(&env.file_io, &load); if (o.segments) driver_free(&env, o.segments, (size_t)o.cap_segments * sizeof(*o.segments)); + if (o.only_sections) + driver_free(&env, o.only_sections, + (size_t)o.cap_only * sizeof(*o.only_sections)); + if (o.remove_sections) + driver_free(&env, o.remove_sections, + (size_t)o.cap_remove * sizeof(*o.remove_sections)); + if (o.section_order) + driver_free(&env, o.section_order, + (size_t)o.cap_section_order * sizeof(*o.section_order)); + if (o.require_symbols) + driver_free(&env, o.require_symbols, + (size_t)o.cap_require_symbols * sizeof(*o.require_symbols)); + if (o.require_sections) + driver_free(&env, o.require_sections, + (size_t)o.cap_require_sections * sizeof(*o.require_sections)); driver_env_fini(&env); return rc; diff --git a/include/kit/image.h b/include/kit/image.h @@ -40,6 +40,20 @@ typedef struct KitImageOptions { const KitSlice* segments; uint32_t nsegments; + /* Selection filters applied to segments/sections after the base selection. + * `only_sections` (if non-empty) restricts the selection to the named + * sections/segments; `remove_sections` drops the named ones. */ + const KitSlice* only_sections; + uint32_t nonly_sections; + const KitSlice* remove_sections; + uint32_t nremove_sections; + + /* Ordered section list for KIT_IMAGE_FORMAT_SECTIONS. Sections are + * concatenated in this exact order. Required (non-empty) for the sections + * format. */ + const KitSlice* section_order; + uint32_t nsection_order; + bool have_base; uint64_t base; @@ -56,6 +70,15 @@ typedef struct KitImageOptions { uint64_t pad_to; bool have_max_size; uint64_t max_size; + + /* Validation. The emitter inspects the opened object and fails before + * writing anything if a requirement is unmet. */ + bool require_entry; /* fail unless the image declares an entry */ + const KitSlice* require_symbols; + uint32_t nrequire_symbols; + const KitSlice* require_sections; + uint32_t nrequire_sections; + bool no_dynamic; /* reject dynamic linking artifacts (interp/PLT-GOT/dynamic) */ } KitImageOptions; typedef struct KitImageReport { @@ -65,6 +88,9 @@ typedef struct KitImageReport { uint64_t max_hole; uint32_t nranges; bool had_holes; + /* True when the report describes a section concatenation rather than a + * loadable memory image; in that case `base` is not a load address. */ + bool sections_concat; } KitImageReport; KIT_API KitStatus kit_image_emit(const KitContext*, KitObjFile*, diff --git a/src/obj/image.c b/src/obj/image.c @@ -5,10 +5,20 @@ #include "core/diag.h" +/* A contiguous piece of output. Both the segment path (flat load image) and + * the section path (ordered concatenation) lower to a list of these. + * + * data/size : the bytes to emit (borrowed from object_bytes). + * addr/end : layout addresses for the segment path; for the section path + * these are synthesized so the ranges pack with no holes. + * name : diagnostic spelling. + * order : stable tiebreak for equal addresses. */ typedef struct ImageRange { - KitObjSegInfo seg; + const uint8_t* data; + uint64_t size; uint64_t addr; uint64_t end; + KitSlice name; uint32_t order; } ImageRange; @@ -57,6 +67,19 @@ static int slice_ieq(KitSlice a, KitSlice b) { return 1; } +static int slice_eq(KitSlice a, KitSlice b) { + if (a.len != b.len) return 0; + return a.len == 0 || memcmp(a.s, b.s, a.len) == 0; +} + +static int name_in_list(KitSlice name, const KitSlice* list, uint32_t n) { + uint32_t i; + for (i = 0; i < n; ++i) { + if (slice_eq(name, list[i])) return 1; + } + return 0; +} + static KitSlice drop_pt_prefix(KitSlice s) { if (s.len > 3 && ascii_lower((unsigned char)s.s[0]) == 'p' && ascii_lower((unsigned char)s.s[1]) == 't' && s.s[2] == '_') { @@ -76,17 +99,30 @@ static int is_load_segment(KitSlice name) { return segment_name_match(name, KIT_SLICE_LIT("LOAD")); } +static int segment_in_list(KitSlice have, const KitSlice* list, uint32_t n) { + uint32_t i; + for (i = 0; i < n; ++i) { + if (segment_name_match(have, list[i])) return 1; + } + return 0; +} + static int select_segment(KitObjFmt fmt, const KitObjSegInfo* seg, const KitImageOptions* opts) { - uint32_t i; + /* --remove-section drops a segment by name regardless of base selection. */ + if (opts->nremove_sections && + segment_in_list(seg->name, opts->remove_sections, + opts->nremove_sections)) + return 0; + /* --only-section, when given, is the authoritative keep-set. */ + if (opts->nonly_sections) + return segment_in_list(seg->name, opts->only_sections, + opts->nonly_sections); if (opts->nsegments == 0) { if (fmt == KIT_OBJ_ELF) return is_load_segment(seg->name); return 1; } - for (i = 0; i < opts->nsegments; ++i) { - if (segment_name_match(seg->name, opts->segments[i])) return 1; - } - return 0; + return segment_in_list(seg->name, opts->segments, opts->nsegments); } static uint64_t seg_addr(const KitObjSegInfo* seg, @@ -123,6 +159,11 @@ static KitStatus ranges_push(const KitContext* ctx, ImageRange** ranges, return KIT_OK; } +static void ranges_free(const KitContext* ctx, ImageRange* ranges, + uint32_t cap) { + if (ranges) ctx->heap->free(ctx->heap, ranges, (size_t)cap * sizeof(*ranges)); +} + static void ranges_sort(ImageRange* r, uint32_t n) { uint32_t i; for (i = 1; i < n; ++i) { @@ -181,8 +222,7 @@ static KitStatus collect_ranges(const KitContext* ctx, KitObjFile* obj, kit_ctx_diagf(ctx, "image: segment %.*s file range is out of bounds", KIT_SLICE_ARG(seg.name)); kit_obj_segiter_free(it); - if (ranges) ctx->heap->free(ctx->heap, ranges, - (size_t)cap * sizeof(*ranges)); + ranges_free(ctx, ranges, cap); return KIT_MALFORMED; } if (!u64_sub_bias(seg_addr(&seg, opts), opts->bias, &addr) || @@ -190,21 +230,21 @@ static KitStatus collect_ranges(const KitContext* ctx, KitObjFile* obj, kit_ctx_diagf(ctx, "image: segment %.*s address range overflows", KIT_SLICE_ARG(seg.name)); kit_obj_segiter_free(it); - if (ranges) ctx->heap->free(ctx->heap, ranges, - (size_t)cap * sizeof(*ranges)); + ranges_free(ctx, ranges, cap); return KIT_MALFORMED; } memset(&r, 0, sizeof r); - r.seg = seg; + r.data = bytes->data + (size_t)seg.file_off; + r.size = seg.file_size; r.addr = addr; r.end = end; + r.name = seg.name; r.order = order; st = ranges_push(ctx, &ranges, &nranges, &cap, &r); if (st != KIT_OK) { kit_obj_segiter_free(it); - if (ranges) ctx->heap->free(ctx->heap, ranges, - (size_t)cap * sizeof(*ranges)); + ranges_free(ctx, ranges, cap); return st; } ++order; @@ -213,8 +253,7 @@ static KitStatus collect_ranges(const KitContext* ctx, KitObjFile* obj, if (!nranges) { kit_ctx_diagf(ctx, "image: no selected loadable segment bytes"); - if (ranges) ctx->heap->free(ctx->heap, ranges, - (size_t)cap * sizeof(*ranges)); + ranges_free(ctx, ranges, cap); return KIT_NOT_FOUND; } ranges_sort(ranges, nranges); @@ -224,6 +263,96 @@ static KitStatus collect_ranges(const KitContext* ctx, KitObjFile* obj, return KIT_OK; } +/* Section-based collection: gather sections named by opts->section_order in + * that exact order. The result is a pure concatenation — addresses are + * synthesized so the ranges pack with no holes, and the caller must not treat + * the layout base as a load address (report->sections_concat is set). */ +static KitStatus collect_sections(const KitContext* ctx, KitObjFile* obj, + const KitImageOptions* opts, + ImageRange** ranges_out, uint32_t* nranges_out, + uint32_t* cap_out) { + ImageRange* ranges = NULL; + uint32_t nranges = 0, cap = 0; + uint64_t cursor = 0; + uint32_t i; + KitStatus st; + + *ranges_out = NULL; + *nranges_out = 0; + *cap_out = 0; + + if (opts->nsection_order == 0) { + kit_ctx_diagf(ctx, "image: --format sections requires at least one " + "--section NAME"); + return KIT_INVALID; + } + + for (i = 0; i < opts->nsection_order; ++i) { + KitSlice want = opts->section_order[i]; + KitObjSection sid = KIT_SECTION_NONE; + const uint8_t* data = NULL; + size_t len = 0; + ImageRange r; + uint64_t end; + + if (opts->nremove_sections && + name_in_list(want, opts->remove_sections, opts->nremove_sections)) { + kit_ctx_diagf(ctx, + "image: section %.*s is both selected and --remove-section", + KIT_SLICE_ARG(want)); + ranges_free(ctx, ranges, cap); + return KIT_INVALID; + } + if (kit_obj_section_by_name(obj, want, &sid) != KIT_OK || + sid == KIT_SECTION_NONE) { + kit_ctx_diagf(ctx, "image: section %.*s not found", KIT_SLICE_ARG(want)); + ranges_free(ctx, ranges, cap); + return KIT_NOT_FOUND; + } + if (kit_obj_section_data(obj, sid, &data, &len) != KIT_OK) { + kit_ctx_diagf(ctx, "image: cannot read section %.*s bytes", + KIT_SLICE_ARG(want)); + ranges_free(ctx, ranges, cap); + return KIT_MALFORMED; + } + if (len == 0 || data == NULL) { + /* A NOBITS (.bss) or empty section contributes no bytes; skip it but + * keep the position so order is preserved deterministically. */ + continue; + } + if (!u64_add(cursor, (uint64_t)len, &end)) { + kit_ctx_diagf(ctx, "image: concatenated section size overflows"); + ranges_free(ctx, ranges, cap); + return KIT_MALFORMED; + } + + memset(&r, 0, sizeof r); + r.data = data; + r.size = (uint64_t)len; + r.addr = cursor; + r.end = end; + r.name = want; + r.order = i; + st = ranges_push(ctx, &ranges, &nranges, &cap, &r); + if (st != KIT_OK) { + ranges_free(ctx, ranges, cap); + return st; + } + cursor = end; + } + + if (!nranges) { + kit_ctx_diagf(ctx, "image: selected sections contain no bytes"); + ranges_free(ctx, ranges, cap); + return KIT_NOT_FOUND; + } + /* Already in declared order with packed addresses; no sort needed. */ + *ranges_out = ranges; + *nranges_out = nranges; + *cap_out = cap; + return KIT_OK; +} + static KitStatus compute_layout(const KitContext* ctx, const ImageRange* ranges, uint32_t nranges, const KitImageOptions* opts, @@ -269,7 +398,7 @@ static KitStatus compute_layout(const KitContext* ctx, const ImageRange* ranges, return KIT_ERR; } } - if (!u64_add(payload, r->seg.file_size, &payload)) { + if (!u64_add(payload, r->size, &payload)) { kit_ctx_diagf(ctx, "image: payload size overflows"); return KIT_MALFORMED; } @@ -325,8 +454,7 @@ static KitStatus write_bytes(KitWriter* out, const uint8_t* data, size_t n) { } static KitStatus write_layout(const KitContext* ctx, const ImageRange* ranges, - uint32_t nranges, const KitSlice* bytes, - const KitImageOptions* opts, + uint32_t nranges, const KitImageOptions* opts, const KitImageReport* report, KitWriter* out) { uint64_t cur = report->base; uint32_t i; @@ -337,8 +465,7 @@ static KitStatus write_layout(const KitContext* ctx, const ImageRange* ranges, kit_ctx_diagf(ctx, "image: failed to write hole fill"); return KIT_IO; } - if (write_bytes(out, bytes->data + (size_t)r->seg.file_off, - (size_t)r->seg.file_size) != KIT_OK) { + if (write_bytes(out, r->data, (size_t)r->size) != KIT_OK) { kit_ctx_diagf(ctx, "image: failed to write segment bytes"); return KIT_IO; } @@ -354,6 +481,96 @@ static KitStatus write_layout(const KitContext* ctx, const ImageRange* ranges, return kit_writer_status(out); } +/* Validation pass: --require-entry / --require-symbol / --require-section / + * --no-dynamic. Runs against the opened object before any bytes are written. */ +static KitStatus validate_object(const KitContext* ctx, KitObjFile* obj, + const KitImageOptions* opts) { + uint32_t i; + + if (opts->require_entry) { + KitObjImageInfo info; + if (kit_obj_image_info(obj, &info) != KIT_OK || info.entry == 0) { + kit_ctx_diagf(ctx, "image: --require-entry: input declares no entry point"); + return KIT_NOT_FOUND; + } + } + + for (i = 0; i < opts->nrequire_sections; ++i) { + KitObjSection sid = KIT_SECTION_NONE; + KitSlice want = opts->require_sections[i]; + if (kit_obj_section_by_name(obj, want, &sid) != KIT_OK || + sid == KIT_SECTION_NONE) { + kit_ctx_diagf(ctx, "image: --require-section: %.*s not found", + KIT_SLICE_ARG(want)); + return KIT_NOT_FOUND; + } + } + + for (i = 0; i < opts->nrequire_symbols; ++i) { + KitObjSymInfo si; + KitSlice want = opts->require_symbols[i]; + if (kit_obj_symbol_by_name(obj, want, &si) != KIT_OK) { + /* Fall back to the dynamic symbol table for stripped linked images. */ + KitObjSymIter* dit = NULL; + int found = 0; + if (kit_obj_dynsymiter_new(obj, &dit) == KIT_OK) { + KitObjSymInfo di; + while (kit_obj_symiter_next(dit, &di) == KIT_ITER_ITEM) { + if (slice_eq(di.name, want)) { + found = 1; + break; + } + } + kit_obj_symiter_free(dit); + } + if (!found) { + kit_ctx_diagf(ctx, "image: --require-symbol: %.*s not found", + KIT_SLICE_ARG(want)); + return KIT_NOT_FOUND; + } + } + } + + if (opts->no_dynamic) { + KitObjImageInfo info; + KitObjDepIter* dep = NULL; + KitObjDepInfo di; + int has_dep = 0; + KitObjSection sid = KIT_SECTION_NONE; + static const char* dyn_secs[] = {".dynamic", ".interp", ".plt", + ".got.plt", ".got", ".dynsym"}; + size_t k; + + if (kit_obj_image_info(obj, &info) == KIT_OK && info.interp.len) { + kit_ctx_diagf(ctx, + "image: --no-dynamic: input has a dynamic interpreter %.*s", + KIT_SLICE_ARG(info.interp)); + return KIT_ERR; + } + if (kit_obj_depiter_new(obj, &dep) == KIT_OK) { + if (kit_obj_depiter_next(dep, &di) == KIT_ITER_ITEM) has_dep = 1; + kit_obj_depiter_free(dep); + } + if (has_dep) { + kit_ctx_diagf(ctx, + "image: --no-dynamic: input depends on a shared library"); + return KIT_ERR; + } + for (k = 0; k < sizeof(dyn_secs) / sizeof(dyn_secs[0]); ++k) { + if (kit_obj_section_by_name(obj, kit_slice_cstr(dyn_secs[k]), &sid) == + KIT_OK && + sid != KIT_SECTION_NONE) { + kit_ctx_diagf(ctx, + "image: --no-dynamic: input has a dynamic section %s", + dyn_secs[k]); + return KIT_ERR; + } + } + } + + return KIT_OK; +} + KitStatus obj_emit_image(const KitContext* ctx, KitObjFile* obj, const KitSlice* object_bytes, const KitImageOptions* opts, KitWriter* out, @@ -362,7 +579,9 @@ KitStatus obj_emit_image(const KitContext* ctx, KitObjFile* obj, KitImageReport report; ImageRange* ranges = NULL; uint32_t nranges = 0, cap = 0; + KitImageFormat format; KitStatus st; + int from_sections; if (!ctx || !ctx->heap || !obj || !object_bytes || !out) return KIT_INVALID; if (!object_bytes->data && object_bytes->len) return KIT_INVALID; @@ -376,11 +595,23 @@ KitStatus obj_emit_image(const KitContext* ctx, KitObjFile* obj, opts = &defopts; } - if (opts->format != KIT_IMAGE_FORMAT_BIN || - opts->from != KIT_IMAGE_FROM_SEGMENTS) { - kit_ctx_diagf(ctx, "image: only --format bin --from segments is supported"); + format = (KitImageFormat)opts->format; + if (format != KIT_IMAGE_FORMAT_BIN && format != KIT_IMAGE_FORMAT_ROM && + format != KIT_IMAGE_FORMAT_SECTIONS && format != KIT_IMAGE_FORMAT_ELF) { + kit_ctx_diagf(ctx, "image: unknown output format"); + return KIT_INVALID; + } + if (format == KIT_IMAGE_FORMAT_ELF) { + kit_ctx_diagf(ctx, + "image: --format elf is not implemented; use `kit objcopy` " + "/ `kit strip` to copy, normalize, or strip a linked ELF"); return KIT_UNSUPPORTED; } + if (opts->from != KIT_IMAGE_FROM_SEGMENTS && + opts->from != KIT_IMAGE_FROM_SECTIONS) { + kit_ctx_diagf(ctx, "image: invalid --from source"); + return KIT_INVALID; + } if (opts->addr != KIT_IMAGE_ADDR_VADDR && opts->addr != KIT_IMAGE_ADDR_PADDR && opts->addr != KIT_IMAGE_ADDR_LMA) { kit_ctx_diagf(ctx, "image: invalid address kind"); @@ -395,16 +626,57 @@ KitStatus obj_emit_image(const KitContext* ctx, KitObjFile* obj, return KIT_INVALID; } - st = collect_ranges(ctx, obj, object_bytes, opts, &ranges, &nranges, &cap); + /* Source selection. Three formats, two byte sources: + * bin : load segments only. + * sections : named sections only (the format IS the concatenation). + * rom : load segments by default, or named sections when a --section + * list (or --from sections) is supplied. + * --section is the section-order list; it implies the section source for the + * formats that allow it and is rejected for bin. */ + if (format == KIT_IMAGE_FORMAT_BIN && opts->nsection_order) { + kit_ctx_diagf(ctx, + "image: --section is only valid with --format sections or " + "--format rom"); + return KIT_INVALID; + } + if (format == KIT_IMAGE_FORMAT_BIN && + opts->from == KIT_IMAGE_FROM_SECTIONS) { + kit_ctx_diagf(ctx, + "image: --from sections requires --format sections or rom"); + return KIT_INVALID; + } + from_sections = (format == KIT_IMAGE_FORMAT_SECTIONS) || + (opts->from == KIT_IMAGE_FROM_SECTIONS) || + (format == KIT_IMAGE_FORMAT_ROM && opts->nsection_order > 0); + if (from_sections && opts->nsection_order == 0) { + kit_ctx_diagf(ctx, + "image: a section source requires at least one --section " + "NAME"); + return KIT_INVALID; + } + if (format == KIT_IMAGE_FORMAT_ROM && !opts->have_pad_to) { + kit_ctx_diagf(ctx, + "image: --format rom requires an explicit size via --pad-to"); + return KIT_INVALID; + } + + st = validate_object(ctx, obj, opts); + if (st != KIT_OK) return st; + + if (from_sections) { + st = collect_sections(ctx, obj, opts, &ranges, &nranges, &cap); + } else { + st = collect_ranges(ctx, obj, object_bytes, opts, &ranges, &nranges, &cap); + } if (st == KIT_OK) { memset(&report, 0, sizeof report); + report.sections_concat = from_sections ? true : false; st = compute_layout(ctx, ranges, nranges, opts, &report); } if (st == KIT_OK) { - st = write_layout(ctx, ranges, nranges, object_bytes, opts, &report, out); + st = write_layout(ctx, ranges, nranges, opts, &report, out); } if (st == KIT_OK && report_out) *report_out = report; - if (ranges) ctx->heap->free(ctx->heap, ranges, - (size_t)cap * sizeof(*ranges)); + ranges_free(ctx, ranges, cap); return st; }