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:
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;
}