commit 57dce832a3603adb73f196313e0859e19946bac2
parent ec0883756690e6e9c76dad2e5d3e36d77d5a44b2
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Thu, 16 Jul 2026 10:12:27 -0700
image: convert relocatables and apply checked rebasing
Diffstat:
4 files changed, 296 insertions(+), 13 deletions(-)
diff --git a/driver/cmd/image.c b/driver/cmd/image.c
@@ -277,6 +277,19 @@ static int parse_from(const char* s, uint32_t* out) {
return -1;
}
+static void image_unknown_value(const char* category, const char* value,
+ const char* const* candidates,
+ size_t candidate_count) {
+ DriverSuggestion suggestions[3];
+ size_t n = driver_suggest_values(value, candidates, candidate_count,
+ suggestions, 3);
+ if (n)
+ driver_errf(IMAGE_TOOL, "unknown %s: %s; did you mean '%s'?", category,
+ value, suggestions[0].value);
+ else
+ driver_errf(IMAGE_TOOL, "unknown %s: %s", category, value);
+}
+
static int parse_addr(const char* s, uint32_t* out) {
if (driver_streq(s, "vaddr")) {
*out = KIT_IMAGE_ADDR_VADDR;
@@ -638,6 +651,7 @@ int driver_image(int argc, char** argv) {
int own_writer = 0;
int rc = 2;
int i;
+ int options = 1;
if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) {
driver_help_image();
@@ -657,6 +671,18 @@ int driver_image(int argc, char** argv) {
const char* a = argv[i];
const char* val = NULL;
int matched;
+ if (options && driver_streq(a, "--")) {
+ options = 0;
+ continue;
+ }
+ if (!options) {
+ if (o.input) {
+ driver_errf(IMAGE_TOOL, "only one input may be given");
+ goto done;
+ }
+ o.input = a;
+ continue;
+ }
if (driver_streq(a, "-o")) {
if (i + 1 >= argc) {
driver_errf(IMAGE_TOOL, "%s requires a path", a);
@@ -675,7 +701,11 @@ int driver_image(int argc, char** argv) {
if (matched < 0) goto missing_value;
if (matched) {
if (parse_format(val, &o.image.format) != 0) {
- driver_errf(IMAGE_TOOL, "unknown format: %s", val);
+ const char* const formats[] = {"bin", "binary", "rom", "sections",
+ "ihex", "ihex8", "srec", "s19",
+ "elf"};
+ image_unknown_value("format", val, formats,
+ sizeof formats / sizeof formats[0]);
goto done;
}
continue;
@@ -684,7 +714,9 @@ int driver_image(int argc, char** argv) {
if (matched < 0) goto missing_value;
if (matched) {
if (parse_from(val, &o.image.from) != 0) {
- driver_errf(IMAGE_TOOL, "unknown source: %s", val);
+ const char* const sources[] = {"segments", "sections"};
+ image_unknown_value("source", val, sources,
+ sizeof sources / sizeof sources[0]);
goto done;
}
continue;
@@ -907,7 +939,27 @@ int driver_image(int argc, char** argv) {
continue;
}
if (a[0] == '-' && a[1] != '\0') {
- driver_errf(IMAGE_TOOL, "unknown option: %s", a);
+ const char* const options[] = {
+ "-o", "--output", "--format",
+ "--from", "--segment", "--only-section",
+ "--section", "--remove-section", "--addr",
+ "--base", "--bias", "--fill",
+ "--pad-to", "--max-size", "--metadata",
+ "--require-entry", "--require-symbol", "--require-section",
+ "--no-dynamic", "--image-header", "--image-load-offset",
+ "--image-page-size", "--split-debug", "--keep-symbols",
+ "--strip-debug", "-h", "--help",
+ "--version",
+ };
+ DriverSuggestion suggestions[3];
+ size_t n = driver_suggest_values(a, options,
+ sizeof options / sizeof options[0],
+ suggestions, 3);
+ if (n)
+ driver_errf(IMAGE_TOOL, "unknown option: %s; did you mean '%s'?", a,
+ suggestions[0].value);
+ else
+ driver_errf(IMAGE_TOOL, "unknown option: %s", a);
goto done;
}
if (o.input) {
diff --git a/driver/cmd/objcopy.c b/driver/cmd/objcopy.c
@@ -525,7 +525,7 @@ int driver_objcopy(int argc, char** argv) {
KitSlice input;
int have_in = 0;
int rc = 1;
- int i;
+ int i, options = 1;
const char* out_path;
if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) {
@@ -542,6 +542,22 @@ int driver_objcopy(int argc, char** argv) {
const char* a = argv[i];
const char* val = NULL;
int matched;
+ if (options && driver_streq(a, "--")) {
+ options = 0;
+ continue;
+ }
+ if (!options) {
+ if (!opts.input)
+ opts.input = a;
+ else if (!opts.output)
+ opts.output = a;
+ else {
+ driver_errf(OBJCOPY_TOOL, "unexpected argument: %s", a);
+ rc = 2;
+ goto done;
+ }
+ continue;
+ }
if (driver_streq(a, "--strip-debug")) {
opts.op = COPY_OP_STRIP_DEBUG;
continue;
diff --git a/include/kit/image.h b/include/kit/image.h
@@ -105,6 +105,13 @@ typedef struct KitImageOptions {
} KitImageOptions;
typedef struct KitImageReport {
+ /* Address provenance. `base` remains the emitted layout base for source
+ * compatibility; the explicit fields make rebasing auditable. A
+ * relocatable object has no entry point (`has_entry == false`). */
+ uint64_t original_base;
+ uint64_t emitted_base;
+ uint64_t original_entry;
+ uint64_t emitted_entry;
uint64_t base;
uint64_t size;
uint64_t payload_size;
@@ -118,6 +125,12 @@ typedef struct KitImageReport {
uint32_t image_header;
uint32_t nranges;
bool had_holes;
+ bool has_entry;
+ /* True when allocated sections, rather than linked load segments, supplied
+ * the address-bearing ranges. This is the normal relocatable-object path
+ * and is also used when only/remove-section filters are requested. */
+ bool section_ranges;
+ bool base_is_load_address;
/* 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;
diff --git a/src/obj/image.c b/src/obj/image.c
@@ -16,6 +16,7 @@
typedef struct ImageRange {
const uint8_t* data;
uint64_t size;
+ uint64_t original_addr;
uint64_t addr;
uint64_t end; /* addr + on-disk size (file end) */
KitSlice name;
@@ -28,7 +29,7 @@ static int u64_add(uint64_t a, uint64_t b, uint64_t* out) {
return 1;
}
-static int u64_sub_bias(uint64_t a, int64_t bias, uint64_t* out) {
+static int u64_add_bias(uint64_t a, int64_t bias, uint64_t* out) {
if (bias >= 0) return u64_add(a, (uint64_t)bias, out);
{
uint64_t mag = (uint64_t)(-(bias + 1)) + 1u;
@@ -165,6 +166,18 @@ static int select_segment(KitObjFmt fmt, const KitObjSegInfo* seg,
return segment_in_list(seg->name, opts->segments, opts->nsegments);
}
+static int select_alloc_section(const KitObjSecInfo* sec,
+ const KitImageOptions* opts) {
+ if (!(sec->flags & KIT_SF_ALLOC)) return 0;
+ if (opts->nonly_sections &&
+ !name_in_list(sec->name, opts->only_sections, opts->nonly_sections))
+ return 0;
+ if (opts->nremove_sections &&
+ name_in_list(sec->name, opts->remove_sections, opts->nremove_sections))
+ return 0;
+ return 1;
+}
+
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
@@ -231,6 +244,81 @@ static void ranges_sort(ImageRange* r, uint32_t n) {
}
}
+/* Convert possibly-overlapping input ranges into disjoint fragments. Ranges
+ * are applied in selection order; a later range replaces bytes from every
+ * earlier range it covers. This is deterministic for relocatable formats
+ * whose allocated sections commonly all report address zero, and avoids
+ * allocating a potentially enormous flat staging buffer. */
+static KitStatus normalize_overlaps(const KitContext* ctx,
+ const ImageRange* source,
+ uint32_t nsource, ImageRange** ranges_out,
+ uint32_t* nranges_out, uint32_t* cap_out) {
+ ImageRange* ranges = NULL;
+ uint32_t nranges = 0, cap = 0, i;
+ KitStatus st;
+
+ *ranges_out = NULL;
+ *nranges_out = 0;
+ *cap_out = 0;
+ for (i = 0; i < nsource; ++i) {
+ ImageRange incoming = source[i];
+ uint32_t j = 0;
+ while (j < nranges) {
+ ImageRange old = ranges[j];
+ if (old.end <= incoming.addr || old.addr >= incoming.end) {
+ ++j;
+ continue;
+ }
+ if (old.addr < incoming.addr) {
+ /* Preserve the old prefix. If it also has a suffix, append that as a
+ * second borrowed fragment before installing the incoming range. */
+ ranges[j].size = incoming.addr - old.addr;
+ ranges[j].end = incoming.addr;
+ if (old.end > incoming.end) {
+ ImageRange suffix = old;
+ uint64_t delta = incoming.end - old.addr;
+ suffix.data += (size_t)delta;
+ suffix.size = old.end - incoming.end;
+ suffix.original_addr += delta;
+ suffix.addr = incoming.end;
+ st = ranges_push(ctx, &ranges, &nranges, &cap, &suffix);
+ if (st != KIT_OK) {
+ ranges_free(ctx, ranges, cap);
+ return st;
+ }
+ }
+ ++j;
+ continue;
+ }
+ if (old.end > incoming.end) {
+ uint64_t delta = incoming.end - old.addr;
+ ranges[j].data += (size_t)delta;
+ ranges[j].size = old.end - incoming.end;
+ ranges[j].original_addr += delta;
+ ranges[j].addr = incoming.end;
+ ++j;
+ continue;
+ }
+ /* Fully covered old fragment. Preserve no ordering significance here;
+ * the final address sort establishes the emission order. */
+ if (j + 1u < nranges)
+ memmove(&ranges[j], &ranges[j + 1u],
+ (size_t)(nranges - j - 1u) * sizeof(*ranges));
+ --nranges;
+ }
+ st = ranges_push(ctx, &ranges, &nranges, &cap, &incoming);
+ if (st != KIT_OK) {
+ ranges_free(ctx, ranges, cap);
+ return st;
+ }
+ }
+ ranges_sort(ranges, nranges);
+ *ranges_out = ranges;
+ *nranges_out = nranges;
+ *cap_out = cap;
+ return KIT_OK;
+}
+
static KitStatus collect_ranges(const KitContext* ctx, KitObjFile* obj,
const KitSlice* bytes,
const KitImageOptions* opts,
@@ -249,11 +337,6 @@ static KitStatus collect_ranges(const KitContext* ctx, KitObjFile* obj,
*cap_out = 0;
*mem_end_out = 0;
- if (kit_obj_kind(obj) == KIT_OBJ_KIND_REL) {
- kit_ctx_diagf(ctx, "image: input is relocatable; linked image required");
- return KIT_NOT_FOUND;
- }
-
fmt = kit_obj_fmt(obj);
st = kit_obj_segiter_new(obj, &it);
if (st != KIT_OK) return st;
@@ -274,7 +357,7 @@ static KitStatus collect_ranges(const KitContext* ctx, KitObjFile* obj,
{
uint64_t maddr, mend;
uint64_t msize = seg.vsize >= seg.file_size ? seg.vsize : seg.file_size;
- if (u64_sub_bias(seg_addr(&seg, opts), opts->bias, &maddr) &&
+ if (u64_add_bias(seg_addr(&seg, opts), opts->bias, &maddr) &&
u64_add(maddr, msize, &mend) && mend > mem_end_max)
mem_end_max = mend;
}
@@ -289,7 +372,7 @@ static KitStatus collect_ranges(const KitContext* ctx, KitObjFile* obj,
ranges_free(ctx, ranges, cap);
return KIT_MALFORMED;
}
- if (!u64_sub_bias(seg_addr(&seg, opts), opts->bias, &addr) ||
+ if (!u64_add_bias(seg_addr(&seg, opts), opts->bias, &addr) ||
!u64_add(addr, seg.file_size, &end)) {
kit_ctx_diagf(ctx, "image: segment %.*s address range overflows",
KIT_SLICE_ARG(seg.name));
@@ -301,6 +384,7 @@ static KitStatus collect_ranges(const KitContext* ctx, KitObjFile* obj,
memset(&r, 0, sizeof r);
r.data = bytes->data + (size_t)seg.file_off;
r.size = seg.file_size;
+ r.original_addr = seg_addr(&seg, opts);
r.addr = addr;
r.end = end;
r.name = seg.name;
@@ -320,7 +404,125 @@ static KitStatus collect_ranges(const KitContext* ctx, KitObjFile* obj,
ranges_free(ctx, ranges, cap);
return KIT_NOT_FOUND;
}
- ranges_sort(ranges, nranges);
+ *ranges_out = ranges;
+ *nranges_out = nranges;
+ *cap_out = cap;
+ *mem_end_out = mem_end_max;
+ return KIT_OK;
+}
+
+static KitStatus reject_selected_relocations(const KitContext* ctx,
+ KitObjFile* obj,
+ const KitImageOptions* opts) {
+ KitObjRelocIter* it = NULL;
+ KitObjReloc reloc;
+ KitStatus st = kit_obj_reliter_new(obj, &it);
+ if (st != KIT_OK) return st;
+ while (kit_obj_reliter_next(it, &reloc) == KIT_ITER_ITEM) {
+ KitObjSecInfo sec;
+ if (kit_obj_section(obj, reloc.section, &sec) != KIT_OK) {
+ kit_obj_reliter_free(it);
+ kit_ctx_diagf(ctx, "image: relocation refers to an invalid section");
+ return KIT_MALFORMED;
+ }
+ if (!select_alloc_section(&sec, opts)) continue;
+ kit_ctx_diagf(ctx,
+ "image: unapplied relocation %.*s in selected section %.*s"
+ "%s%.*s",
+ KIT_SLICE_ARG(reloc.kind_name), KIT_SLICE_ARG(sec.name),
+ reloc.sym_name.len ? " against " : "",
+ KIT_SLICE_ARG(reloc.sym_name));
+ kit_obj_reliter_free(it);
+ return KIT_UNSUPPORTED;
+ }
+ kit_obj_reliter_free(it);
+ return KIT_OK;
+}
+
+/* Address-bearing allocated-section collection. Relocatable objects have no
+ * load segments, so this is their canonical raw-image source. Linked inputs
+ * use it only when section keep/drop filters are explicit. */
+static KitStatus collect_alloc_sections(const KitContext* ctx,
+ KitObjFile* obj,
+ const KitImageOptions* opts,
+ ImageRange** ranges_out,
+ uint32_t* nranges_out,
+ uint32_t* cap_out,
+ uint64_t* mem_end_out) {
+ ImageRange* ranges = NULL;
+ uint32_t nranges = 0, cap = 0, i, nsections;
+ uint64_t mem_end_max = 0;
+ KitStatus st;
+
+ *ranges_out = NULL;
+ *nranges_out = 0;
+ *cap_out = 0;
+ *mem_end_out = 0;
+
+ if (kit_obj_kind(obj) == KIT_OBJ_KIND_REL) {
+ if (opts->nsegments) {
+ kit_ctx_diagf(ctx,
+ "image: --segment is not valid for a relocatable object; "
+ "use --only-section");
+ return KIT_INVALID;
+ }
+ st = reject_selected_relocations(ctx, obj, opts);
+ if (st != KIT_OK) return st;
+ }
+
+ nsections = kit_obj_nsections(obj);
+ for (i = 0; i < nsections; ++i) {
+ KitObjSecInfo sec;
+ const uint8_t* data = NULL;
+ size_t len = 0;
+ uint64_t addr, end, mend;
+ ImageRange r;
+ if (kit_obj_section(obj, i, &sec) != KIT_OK) {
+ ranges_free(ctx, ranges, cap);
+ return KIT_MALFORMED;
+ }
+ if (!select_alloc_section(&sec, opts)) continue;
+ if (!u64_add_bias(sec.addr, opts->bias, &addr) ||
+ !u64_add(addr, sec.size, &mend)) {
+ kit_ctx_diagf(ctx, "image: section %.*s address range overflows",
+ KIT_SLICE_ARG(sec.name));
+ ranges_free(ctx, ranges, cap);
+ return KIT_MALFORMED;
+ }
+ if (mend > mem_end_max) mem_end_max = mend;
+ if (sec.kind == KIT_SEC_BSS || sec.size == 0) continue;
+ if (kit_obj_section_data(obj, i, &data, &len) != KIT_OK) {
+ kit_ctx_diagf(ctx, "image: cannot read selected section %.*s",
+ KIT_SLICE_ARG(sec.name));
+ ranges_free(ctx, ranges, cap);
+ return KIT_MALFORMED;
+ }
+ if (!data || len == 0) continue;
+ if (!u64_add(addr, (uint64_t)len, &end)) {
+ kit_ctx_diagf(ctx, "image: section %.*s file range overflows",
+ KIT_SLICE_ARG(sec.name));
+ ranges_free(ctx, ranges, cap);
+ return KIT_MALFORMED;
+ }
+ memset(&r, 0, sizeof r);
+ r.data = data;
+ r.size = (uint64_t)len;
+ r.original_addr = sec.addr;
+ r.addr = addr;
+ r.end = end;
+ r.name = sec.name;
+ r.order = i;
+ st = ranges_push(ctx, &ranges, &nranges, &cap, &r);
+ if (st != KIT_OK) {
+ ranges_free(ctx, ranges, cap);
+ return st;
+ }
+ }
+ if (!nranges) {
+ kit_ctx_diagf(ctx, "image: no selected allocated section bytes");
+ ranges_free(ctx, ranges, cap);
+ return KIT_NOT_FOUND;
+ }
*ranges_out = ranges;
*nranges_out = nranges;
*cap_out = cap;