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