kit

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

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:
Mdriver/cmd/image.c | 58+++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mdriver/cmd/objcopy.c | 18+++++++++++++++++-
Minclude/kit/image.h | 13+++++++++++++
Msrc/obj/image.c | 220+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
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;