kit

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

commit e65c7fb58d0ce0c8ab01662418758b92937379f9
parent 152604e1ae971b6f30497fc904a7d9857d6968bb
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue, 16 Jun 2026 15:21:12 -0700

image: metadata selection mirrors the emitter's segment policy

The `kit image --metadata` sidecar reimplemented the keep/drop policy with
exact, case-sensitive byte matching, while the real emitter
(select_segment -> segment_name_match) strips a leading PT_ and folds case.
The ELF segment iterator interns the loadable segment as "LOAD", so
`--segment PT_LOAD` (len 7 != 4) and `--segment load` (memcmp fails) both
selected bytes in the emitted image yet were written as an empty
`"selection": []` — a sidecar that contradicted the bytes.

Expose the emitter's predicate instead of duplicating it: add a public
kit_image_segment_selected (src/api/image.c over the internal
obj_image_segment_selected in src/obj/image.c, which wraps select_segment
plus the file_size==0 skip) and route the metadata writer's three hand-rolled
memcmp loops and the default-ELF "L"/"l" check through a single call. The
selection list now equals exactly the segments the emitter contributed.

Test (test/tools/run.sh): --segment PT_LOAD / --segment load / default all
report the loadable LOAD segment; --remove-section pt_load removes it from
both bytes and report. Red-confirmed against the old exact-byte logic.

Diffstat:
Mdriver/cmd/image.c | 45+++++----------------------------------------
Minclude/kit/image.h | 10++++++++++
Msrc/api/config_stubs.c | 8++++++++
Msrc/api/image.c | 5+++++
Msrc/obj/image.c | 11+++++++++++
Msrc/obj/image.h | 9+++++++++
Mtest/tools/run.sh | 23+++++++++++++++++++++++
7 files changed, 71 insertions(+), 40 deletions(-)

diff --git a/driver/cmd/image.c b/driver/cmd/image.c @@ -410,46 +410,11 @@ static void meta_put_selection(MetaBuf* mb, KitObjFile* of, 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; + /* Defer to the emitter's own selection predicate so the sidecar lists + * exactly the segments that contributed bytes — matching its PT_/case + * folding and only/remove/default-PT_LOAD policy rather than a divergent + * exact-byte reimplementation. */ + if (!kit_image_segment_selected(fmt, &seg, &o->image)) continue; if (!first) meta_putc(mb, ','); first = 0; if (seg.name.len) diff --git a/include/kit/image.h b/include/kit/image.h @@ -2,6 +2,7 @@ #define KIT_IMAGE_H #include <kit/core.h> +#include <kit/object.h> /* * Kernel/load-image emission. @@ -103,4 +104,13 @@ KIT_API KitStatus kit_image_emit_bytes(const KitContext*, KitSlice name, const KitImageOptions*, KitWriter* out, KitImageReport* report_out); +/* True when `seg` (from a format `fmt` object) contributes bytes to the image + * `kit_image_emit` would produce for `opts`: the base ELF-PT_LOAD / explicit + * --segment policy plus the only/remove filters, AND a non-empty on-disk size. + * "PT_LOAD"/"LOAD" are treated equivalently and matching is case-insensitive, + * exactly as the emitter selects — so callers reporting "what the image + * contains" do not reimplement (and drift from) the selection. */ +KIT_API bool kit_image_segment_selected(KitObjFmt fmt, const KitObjSegInfo* seg, + const KitImageOptions* opts); + #endif /* KIT_IMAGE_H */ diff --git a/src/api/config_stubs.c b/src/api/config_stubs.c @@ -110,6 +110,14 @@ KitStatus kit_image_emit_bytes(const KitContext* ctx, KitSlice name, (void)report_out; return KIT_UNSUPPORTED; } + +bool kit_image_segment_selected(KitObjFmt fmt, const KitObjSegInfo* seg, + const KitImageOptions* opts) { + (void)fmt; + (void)seg; + (void)opts; + return false; +} #endif #if !KIT_DWARF_ENABLED diff --git a/src/api/image.c b/src/api/image.c @@ -28,3 +28,8 @@ KitStatus kit_image_emit_bytes(const KitContext* ctx, KitSlice name, kit_obj_free(obj); return st; } + +bool kit_image_segment_selected(KitObjFmt fmt, const KitObjSegInfo* seg, + const KitImageOptions* opts) { + return obj_image_segment_selected(fmt, seg, opts); +} diff --git a/src/obj/image.c b/src/obj/image.c @@ -125,6 +125,17 @@ static int select_segment(KitObjFmt fmt, const KitObjSegInfo* seg, return segment_in_list(seg->name, opts->segments, opts->nsegments); } +bool obj_image_segment_selected(KitObjFmt fmt, const KitObjSegInfo* seg, + const KitImageOptions* opts) { + /* A segment contributes bytes to the emitted image iff the base/only/remove + * policy keeps it AND it has on-disk contents. This is the exact predicate + * the segment collector applies (see collect_ranges): callers that need to + * report "what the image contains" must route through here rather than + * reimplementing the matching, which would drift from the emitter. */ + if (seg->file_size == 0) return false; + return select_segment(fmt, seg, opts) != 0; +} + static uint64_t seg_addr(const KitObjSegInfo* seg, const KitImageOptions* opts) { switch ((KitImageAddrKind)opts->addr) { diff --git a/src/obj/image.h b/src/obj/image.h @@ -2,10 +2,19 @@ #define KIT_OBJ_IMAGE_H #include <kit/image.h> +#include <kit/object.h> KitStatus obj_emit_image(const KitContext*, KitObjFile*, const KitSlice* object_bytes, const KitImageOptions*, KitWriter* out, KitImageReport* report_out); +/* True when `seg` contributes bytes to the image the emitter would produce for + * `opts` (base ELF-PT_LOAD / explicit-segment policy plus only/remove filters, + * AND a non-empty on-disk size). Backs the public kit_image_segment_selected so + * the `image` driver reports the exact selection without reimplementing — and + * diverging from — the policy. */ +bool obj_image_segment_selected(KitObjFmt fmt, const KitObjSegInfo* seg, + const KitImageOptions* opts); + #endif diff --git a/test/tools/run.sh b/test/tools/run.sh @@ -302,5 +302,28 @@ run_ok image-no-dynamic-ok "$KIT" image --no-dynamic --format bin \ run_fail image-format-elf-deferred "$KIT" image --format elf \ "$work/kernel.elf" -o "$work/elf.bin" +# ==== BEGIN kernel-FZ Bug 10: metadata selection mirrors the emitter ========= +# The sidecar `selection` must list exactly the segments that contributed bytes. +# The emitter strips a leading PT_ and folds case (PT_LOAD == load == LOAD), so a +# selection written with exact-byte matching would diverge: --segment PT_LOAD and +# --segment load both select the loadable segment yet must not yield an empty +# list. (Regression guard for the hand-rolled-vs-emitter policy drift.) +run_ok image-meta-seg-ptload "$KIT" image --format bin --segment PT_LOAD \ + --metadata "$work/meta.ptload.json" "$work/kernel.elf" -o "$work/sel.ptload.bin" +contains image-meta-seg-ptload-selected "$work/meta.ptload.json" '"selection": ["LOAD"' +run_ok image-meta-seg-lowerload "$KIT" image --format bin --segment load \ + --metadata "$work/meta.load.json" "$work/kernel.elf" -o "$work/sel.load.bin" +contains image-meta-seg-lowerload-selected "$work/meta.load.json" '"selection": ["LOAD"' +# The default-ELF (no --segment) selection also lists the loadable segment(s). +run_ok image-meta-default-sel "$KIT" image --format bin \ + --metadata "$work/meta.def.json" "$work/kernel.elf" -o "$work/sel.def.bin" +contains image-meta-default-selected "$work/meta.def.json" '"selection": ["LOAD"' +# --remove-section drops it from BOTH the bytes and the reported selection; the +# removed name folds case the same way the emitter does. +run_fail image-meta-remove-all "$KIT" image --format bin \ + --remove-section pt_load --metadata "$work/meta.rm.json" "$work/kernel.elf" \ + -o "$work/sel.rm.bin" +# ==== END kernel-FZ Bug 10 =================================================== + kit_summary tools-driver kit_exit