commit 522bdeee2d1c349d329a6d6ed5f5ad7a1f649b1f
parent f46d3c17738e810ae2964e5691842c90c19ce200
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Thu, 16 Jul 2026 10:32:23 -0700
image: finish relocatable conversion and rebasing
Diffstat:
6 files changed, 383 insertions(+), 67 deletions(-)
diff --git a/doc/RELEASE_AUDIT_2026_6_0.md b/doc/RELEASE_AUDIT_2026_6_0.md
@@ -501,8 +501,8 @@ Implementation checkpoint (updated 2026-07-16):
| KIT-P1-015 | **Complete** | 21 multi-source dependency assertions pass in the driver suite |
| KIT-P2-003 | **Complete** | `test-driver-diagnostics`: 15 pass, 0 fail |
| KIT-P1-007 | **Complete** | `test-driver-strip`: object API 26/0, relocatable/archive 5/0, linked ELF/Mach-O/PE matrix 91/0 |
-| KIT-P1-017 | In progress | implementation under focused CLI audit |
-| KIT-P2-002 | In progress | implementation under focused CLI audit |
+| KIT-P1-017 | **Complete** | relocatable/linked ELF, Mach-O, COFF, and Wasm image cases included in `test-driver-tools`: 251 pass, 0 fail |
+| KIT-P2-002 | **Complete** | 32-bit boundary/overflow, negative bias, entry records, metadata, and transactional cases included in `test-driver-tools`: 251 pass, 0 fail |
| KIT-P3-003 | **Complete** | `test-link-macho-symbols` passes aarch64 and x86-64 Kit/platform oracles |
| KIT-P1-016 | In progress | implementation under focused CLI audit |
| KIT-P2-008 | In progress | implementation under focused CLI audit |
diff --git a/driver/cmd/image.c b/driver/cmd/image.c
@@ -38,23 +38,21 @@ void driver_help_image(void) {
driver_printf(
"%.*s",
KIT_SLICE_ARG(KIT_SLICE_LIT(
- "kit image — emit flat load images from linked objects\n"
+ "kit image — emit flat load images from objects and linked images\n"
"\n"
"USAGE\n"
" kit image [OPTIONS] INPUT [-o OUTPUT]\n"
"\n"
"FORMAT\n"
- " --format bin flat binary from loadable segments "
+ " --format bin flat binary from loadable ranges "
"(default)\n"
" --format rom fixed-size flat binary (requires "
"--pad-to)\n"
" --format sections concatenate --section bytes in order\n"
- " --format ihex Intel HEX records from loadable "
- "segments\n"
- " --format srec Motorola S-records from loadable "
- "segments\n"
- " --from segments derive bytes from loadable segments "
- "(default)\n"
+ " --format ihex Intel HEX records from loadable ranges\n"
+ " --format srec Motorola S-records from loadable ranges\n"
+ " --from segments linked segments; relocatables use "
+ "allocated sections (default)\n"
" --from sections derive bytes from named sections\n"
"\n"
"SELECTION\n"
@@ -109,10 +107,9 @@ void driver_help_image(void) {
"TEXT-FORMAT ADDRESS LIMITS\n"
" IHEX/SREC records require adjusted addresses that fit their\n"
" 32-bit range. Native linked images can start above that range.\n"
- " --base changes flat-output placement; it does not rebase IHEX or\n"
- " SREC record addresses. Use an explicit signed --bias only when\n"
- " every adjusted range is representable. A bias that makes IHEX\n"
- " work may still be rejected for SREC in this release.\n"
+ " --base changes flat-output placement and is rejected for IHEX/\n"
+ " SREC. Use an explicit signed --bias to adjust every record and\n"
+ " entry address; every adjusted address must fit in 32 bits.\n"
"\n"
"GETTING HELP\n"
" -h, --help Show this help and exit\n"
@@ -478,11 +475,38 @@ static KitSlice read_build_id(KitObjFile* of) {
* 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 int image_name_in_list(KitSlice name, const KitSlice* list, uint32_t n) {
+ uint32_t i;
+ for (i = 0; i < n; ++i) {
+ if (name.len == list[i].len &&
+ (name.len == 0 || memcmp(name.s, list[i].s, name.len) == 0))
+ return 1;
+ }
+ return 0;
+}
+
+static int meta_section_selected(const KitObjSecInfo* sec,
+ const KitImageOptions* opts) {
+ if (!(sec->flags & KIT_SF_ALLOC) || sec->kind == KIT_SEC_BSS ||
+ sec->size == 0)
+ return 0;
+ if (opts->nonly_sections &&
+ !image_name_in_list(sec->name, opts->only_sections,
+ opts->nonly_sections))
+ return 0;
+ if (opts->nremove_sections &&
+ image_name_in_list(sec->name, opts->remove_sections,
+ opts->nremove_sections))
+ return 0;
+ return 1;
+}
+
static void meta_put_selection(MetaBuf* mb, KitObjFile* of,
- const ImageCliOpts* o, int from_sections) {
+ const ImageCliOpts* o,
+ const KitImageReport* report) {
uint32_t i;
int first = 1;
- if (from_sections) {
+ if (report->sections_concat) {
for (i = 0; i < o->image.nsection_order; ++i) {
if (!first) meta_putc(mb, ',');
first = 0;
@@ -490,6 +514,19 @@ static void meta_put_selection(MetaBuf* mb, KitObjFile* of,
}
return;
}
+ if (report->section_ranges) {
+ uint32_t nsections = kit_obj_nsections(of);
+ for (i = 0; i < nsections; ++i) {
+ KitObjSecInfo sec;
+ if (kit_obj_section(of, i, &sec) != KIT_OK ||
+ !meta_section_selected(&sec, &o->image))
+ continue;
+ if (!first) meta_putc(mb, ',');
+ first = 0;
+ meta_put_json_string(mb, sec.name);
+ }
+ return;
+ }
{
KitObjSegIter* it = NULL;
KitObjSegInfo seg;
@@ -534,12 +571,10 @@ 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;
@@ -549,15 +584,8 @@ static int write_metadata(DriverEnv* env, const KitContext* ctx, KitObjFile* of,
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);
+ from_sections = report->sections_concat || report->section_ranges;
/* Keys are emitted in a fixed order so the sidecar is byte-stable. */
meta_putf(&mb, "{\n");
@@ -568,9 +596,9 @@ static int write_metadata(DriverEnv* env, const KitContext* ctx, KitObjFile* of,
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)
+ if (report->has_entry)
meta_putf(&mb, " \"entry\": \"0x%llx\",\n",
- (unsigned long long)info.entry);
+ (unsigned long long)report->emitted_entry);
else
meta_putf(&mb, " \"entry\": null,\n");
if (build_id.len) {
@@ -584,7 +612,7 @@ static int write_metadata(DriverEnv* env, const KitContext* ctx, KitObjFile* of,
}
meta_putf(&mb, " \"selection\": [");
- meta_put_selection(&mb, of, o, from_sections);
+ meta_put_selection(&mb, of, o, report);
meta_putf(&mb, "],\n");
meta_putf(&mb, " \"addr_kind\": \"%s\",\n",
@@ -593,8 +621,21 @@ static int write_metadata(DriverEnv* env, const KitContext* ctx, KitObjFile* of,
: "vaddr");
meta_putf(&mb, " \"base\": \"0x%llx\",\n",
(unsigned long long)report->base);
+ meta_putf(&mb, " \"original_base\": \"0x%llx\",\n",
+ (unsigned long long)report->original_base);
+ meta_putf(&mb, " \"emitted_base\": \"0x%llx\",\n",
+ (unsigned long long)report->emitted_base);
+ if (report->has_entry) {
+ meta_putf(&mb, " \"original_entry\": \"0x%llx\",\n",
+ (unsigned long long)report->original_entry);
+ meta_putf(&mb, " \"emitted_entry\": \"0x%llx\",\n",
+ (unsigned long long)report->emitted_entry);
+ } else {
+ meta_putf(&mb, " \"original_entry\": null,\n");
+ meta_putf(&mb, " \"emitted_entry\": null,\n");
+ }
meta_putf(&mb, " \"base_is_load_address\": %s,\n",
- report->sections_concat ? "false" : "true");
+ report->base_is_load_address ? "true" : "false");
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",
@@ -989,6 +1030,14 @@ int driver_image(int argc, char** argv) {
"--image-header");
goto done;
}
+ if (o.image.have_base &&
+ (o.image.format == KIT_IMAGE_FORMAT_IHEX ||
+ o.image.format == KIT_IMAGE_FORMAT_SREC)) {
+ driver_errf(IMAGE_TOOL,
+ "--base is only valid for flat output; use --bias to rebase "
+ "IHEX/SREC addresses");
+ goto done;
+ }
if (driver_load_bytes(&env.file_io, IMAGE_TOOL, o.input, &load, &input) != 0) {
rc = 1;
@@ -996,7 +1045,8 @@ int driver_image(int argc, char** argv) {
}
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);
+ driver_errf(IMAGE_TOOL, "%s is not a recognized object or linked image",
+ o.input);
rc = 1;
goto done;
}
diff --git a/driver/cmd/objcopy.c b/driver/cmd/objcopy.c
@@ -79,10 +79,10 @@ void driver_help_objcopy(void) {
" names: elf*, mach-o / macho*, coff*, "
"wasm*, binary, ihex, srec\n"
"\n"
- " The binary, ihex, and srec outputs currently require a linked\n"
- " image; they reject relocatable .o inputs. For a flat image from a\n"
- " linked executable, `kit image --format bin` provides explicit\n"
- " segment/section selection and layout validation.\n"
+ " Binary, IHEX, and SREC use load segments from linked images and\n"
+ " allocated section contents from relocatable objects. Unapplied\n"
+ " relocations in selected sections are rejected. --only-section\n"
+ " and --remove-section select the ranges to emit.\n"
"\n"
"EXAMPLES\n"
" kit objcopy --strip-debug input.o output.o\n"
@@ -263,10 +263,10 @@ static int is_srec_fmt_name(const char* name) {
return driver_streq(name, "srec") || driver_streq(name, "s19");
}
-static int has_object_transforms(const CopyOpts* opts) {
- return opts->op != COPY_OP_NONE || opts->nremove || opts->nonly ||
- opts->nrename_sec || opts->nadd || opts->nupdate || opts->nredef ||
- opts->nglob || opts->nloc || opts->nweak;
+static int has_non_image_transforms(const CopyOpts* opts) {
+ return opts->op != COPY_OP_NONE || opts->nrename_sec || opts->nadd ||
+ opts->nupdate || opts->nredef || opts->nglob || opts->nloc ||
+ opts->nweak;
}
/* Lookup a symbol by name; KIT_OBJ_SYMBOL_NONE if not found. */
@@ -487,34 +487,76 @@ static int copy_one_object(DriverEnv* env, const KitContext* ctx,
return rc;
}
+static KitSlice* copy_image_names(const KitContext* ctx,
+ const char* const* names, uint32_t n) {
+ KitSlice* slices;
+ uint32_t i;
+ size_t bytes;
+ if (n == 0) return NULL;
+ if ((size_t)n > SIZE_MAX / sizeof(*slices)) return NULL;
+ bytes = (size_t)n * sizeof(*slices);
+ slices = (KitSlice*)ctx->heap->alloc(ctx->heap, bytes, _Alignof(KitSlice));
+ if (!slices) return NULL;
+ for (i = 0; i < n; ++i) slices[i] = kit_slice_cstr(names[i]);
+ return slices;
+}
+
+static void free_image_names(const KitContext* ctx, KitSlice* names,
+ uint32_t n) {
+ if (names)
+ ctx->heap->free(ctx->heap, names, (size_t)n * sizeof(*names));
+}
+
static int copy_one_image(const KitContext* ctx, const CopyOpts* opts,
const char* input_name, const KitSlice* input,
const char* output_path) {
KitImageOptions iopts;
+ KitSlice* only_sections = NULL;
+ KitSlice* remove_sections = NULL;
KitWriter* w = NULL;
KitStatus st;
+ int rc = 1;
memset(&iopts, 0, sizeof iopts);
iopts.format = opts->output_image_format;
iopts.from = KIT_IMAGE_FROM_SEGMENTS;
iopts.addr = KIT_IMAGE_ADDR_VADDR;
iopts.fill = 0;
+ only_sections = copy_image_names(ctx, opts->only_sections, opts->nonly);
+ remove_sections =
+ copy_image_names(ctx, opts->remove_sections, opts->nremove);
+ if ((opts->nonly && !only_sections) || (opts->nremove && !remove_sections)) {
+ driver_errf(OBJCOPY_TOOL, "out of memory preparing section selection");
+ goto done;
+ }
+ iopts.only_sections = only_sections;
+ iopts.nonly_sections = opts->nonly;
+ iopts.remove_sections = remove_sections;
+ iopts.nremove_sections = opts->nremove;
if (ctx->file_io->open_writer(ctx->file_io->user, output_path, &w) !=
KIT_OK) {
driver_errf(OBJCOPY_TOOL, "cannot open %.*s",
KIT_SLICE_ARG(kit_slice_cstr(output_path)));
- return 1;
+ goto done;
}
st = kit_image_emit_bytes(ctx, kit_slice_cstr(input_name), input, &iopts, w,
NULL);
if (st != KIT_OK || kit_writer_status(w) != KIT_OK) {
driver_writer_abort(w);
kit_writer_close(w);
- return 1;
+ w = NULL;
+ goto done;
}
kit_writer_close(w);
- return 0;
+ w = NULL;
+ rc = 0;
+
+done:
+ if (w) kit_writer_close(w);
+ free_image_names(ctx, only_sections, opts->nonly);
+ free_image_names(ctx, remove_sections, opts->nremove);
+ return rc;
}
int driver_objcopy(int argc, char** argv) {
@@ -729,7 +771,7 @@ int driver_objcopy(int argc, char** argv) {
}
out_path = opts.output ? opts.output : opts.input;
- if (opts.output_image && has_object_transforms(&opts)) {
+ if (opts.output_image && has_non_image_transforms(&opts)) {
driver_errf(OBJCOPY_TOOL,
"image output format cannot be combined with object transform "
"options");
diff --git a/src/api/image.c b/src/api/image.c
@@ -20,7 +20,7 @@ KitStatus kit_image_emit_bytes(const KitContext* ctx, KitSlice name,
if (!ctx || !object_bytes || !out) return KIT_INVALID;
st = kit_obj_open(ctx, name, object_bytes, &obj);
if (st != KIT_OK) {
- kit_ctx_diagf(ctx, "image: %.*s is not a recognized linked object",
+ kit_ctx_diagf(ctx, "image: %.*s is not a recognized object or linked image",
KIT_SLICE_ARG(name));
return st;
}
diff --git a/src/obj/image.c b/src/obj/image.c
@@ -477,7 +477,9 @@ static KitStatus collect_alloc_sections(const KitContext* ctx,
size_t len = 0;
uint64_t addr, end, mend;
ImageRange r;
- if (kit_obj_section(obj, i, &sec) != KIT_OK) {
+ st = kit_obj_section(obj, i, &sec);
+ if (st == KIT_NOT_FOUND) continue;
+ if (st != KIT_OK) {
ranges_free(ctx, ranges, cap);
return KIT_MALFORMED;
}
@@ -931,6 +933,32 @@ static KitStatus emit_ihex_eof_record(KitWriter* out) {
return write_line(out, line, n);
}
+static KitStatus emit_ihex_start_record(KitWriter* out, uint32_t entry) {
+ uint8_t payload[4];
+ uint32_t sum;
+ size_t i;
+ char line[32];
+ size_t n = 0;
+
+ payload[0] = (uint8_t)(entry >> 24);
+ payload[1] = (uint8_t)(entry >> 16);
+ payload[2] = (uint8_t)(entry >> 8);
+ payload[3] = (uint8_t)entry;
+ sum = 4u + 5u;
+
+ line[n++] = ':';
+ line_append_hex_u8(line, &n, 4u);
+ line_append_hex_u16(line, &n, 0u);
+ line_append_hex_u8(line, &n, 5u);
+ for (i = 0; i < sizeof payload; ++i) {
+ line_append_hex_u8(line, &n, payload[i]);
+ sum += payload[i];
+ }
+ line_append_hex_u8(line, &n, (uint8_t)(~sum + 1u));
+ line[n++] = '\n';
+ return write_line(out, line, n);
+}
+
static KitStatus emit_ihex_blob(const KitContext* ctx, KitWriter* out,
uint64_t addr, const uint8_t* src,
uint64_t len, uint8_t fill, int as_fill,
@@ -999,6 +1027,10 @@ static KitStatus write_ihex_records(const KitContext* ctx, const ImageRange* ran
¤t_upper);
if (st != KIT_OK) return st;
}
+ if (report->has_entry) {
+ st = emit_ihex_start_record(out, (uint32_t)report->emitted_entry);
+ if (st != KIT_OK) return st;
+ }
return emit_ihex_eof_record(out);
}
@@ -1108,7 +1140,7 @@ static uint32_t srec_start_addr_bytes(uint64_t addr) {
static KitStatus write_srec_records(const KitContext* ctx, const ImageRange* ranges,
uint32_t nranges, const KitImageOptions* opts,
- const KitImageReport* report, uint64_t entry,
+ const KitImageReport* report,
KitWriter* out) {
const uint8_t header_name[] = "KIT";
uint64_t cur;
@@ -1139,16 +1171,13 @@ static KitStatus write_srec_records(const KitContext* ctx, const ImageRange* ran
st = emit_srec_blob(ctx, out, cur, NULL, pad, opts->fill, 1);
if (st != KIT_OK) return st;
}
- if (entry > UINT32_MAX) {
- kit_ctx_diagf(ctx,
- "image: srec cannot emit above 32-bit addresses (got 0x%llx)",
- (unsigned long long)entry);
- return KIT_UNSUPPORTED;
- }
- if (emit_srec_data_record(out, srec_start_type(entry),
- srec_start_addr_bytes(entry), entry, NULL, 0u) !=
- KIT_OK) {
- return KIT_IO;
+ if (report->has_entry) {
+ uint64_t entry = report->emitted_entry;
+ if (emit_srec_data_record(out, srec_start_type(entry),
+ srec_start_addr_bytes(entry), entry, NULL, 0u) !=
+ KIT_OK) {
+ return KIT_IO;
+ }
}
return KIT_OK;
}
@@ -1254,8 +1283,8 @@ KitStatus obj_emit_image(const KitContext* ctx, KitObjFile* obj,
KitImageFormat format;
KitStatus st;
int from_sections;
+ int address_sections;
uint64_t seg_mem_end = 0;
- uint64_t srec_entry = 0;
uint8_t image_hdr[64];
const uint8_t* image_hdr_p = NULL;
@@ -1284,6 +1313,13 @@ KitStatus obj_emit_image(const KitContext* ctx, KitObjFile* obj,
"/ `kit strip` to copy, normalize, or strip a linked ELF");
return KIT_UNSUPPORTED;
}
+ if (opts->have_base &&
+ (format == KIT_IMAGE_FORMAT_IHEX || format == KIT_IMAGE_FORMAT_SREC)) {
+ kit_ctx_diagf(ctx,
+ "image: --base is only valid for flat output; use --bias "
+ "to rebase ihex/srec addresses");
+ return KIT_INVALID;
+ }
if (opts->from != KIT_IMAGE_FROM_SEGMENTS &&
opts->from != KIT_IMAGE_FROM_SECTIONS) {
kit_ctx_diagf(ctx, "image: invalid --from source");
@@ -1304,7 +1340,8 @@ KitStatus obj_emit_image(const KitContext* ctx, KitObjFile* obj,
}
/* Source selection. Five formats, two byte sources:
- * bin/srec/ihex : load segments only.
+ * bin/srec/ihex : linked load segments, or allocated sections for a
+ * relocatable input / explicit section filters.
* rom : load segments by default, or named sections when a
* --section list (or --from sections) is supplied.
* sections : named sections only (the format IS the concatenation).
@@ -1354,16 +1391,51 @@ KitStatus obj_emit_image(const KitContext* ctx, KitObjFile* obj,
st = validate_object(ctx, obj, opts);
if (st != KIT_OK) return st;
+ address_sections = !from_sections &&
+ (kit_obj_kind(obj) == KIT_OBJ_KIND_REL ||
+ opts->nonly_sections || opts->nremove_sections);
if (from_sections) {
st = collect_sections(ctx, obj, opts, &ranges, &nranges, &cap, &seg_mem_end);
+ } else if (address_sections) {
+ st = collect_alloc_sections(ctx, obj, opts, &ranges, &nranges, &cap,
+ &seg_mem_end);
} else {
st = collect_ranges(ctx, obj, object_bytes, opts, &ranges, &nranges, &cap,
&seg_mem_end);
}
+ if (st == KIT_OK && !from_sections) {
+ ImageRange* normalized = NULL;
+ uint32_t nnormalized = 0, normalized_cap = 0;
+ st = normalize_overlaps(ctx, ranges, nranges, &normalized, &nnormalized,
+ &normalized_cap);
+ if (st == KIT_OK) {
+ ranges_free(ctx, ranges, cap);
+ ranges = normalized;
+ nranges = nnormalized;
+ cap = normalized_cap;
+ }
+ }
if (st == KIT_OK) {
+ KitObjImageInfo info;
memset(&report, 0, sizeof report);
report.sections_concat = from_sections ? true : false;
+ report.section_ranges = address_sections ? true : false;
+ report.base_is_load_address =
+ (!from_sections && !address_sections) ? true : false;
+ if (!from_sections) report.original_base = ranges[0].original_addr;
st = compute_layout(ctx, ranges, nranges, opts, seg_mem_end, &report);
+ if (st == KIT_OK) {
+ report.emitted_base = report.base;
+ if (kit_obj_kind(obj) != KIT_OBJ_KIND_REL &&
+ kit_obj_image_info(obj, &info) == KIT_OK && info.entry != 0) {
+ report.has_entry = true;
+ report.original_entry = info.entry;
+ if (!u64_add_bias(info.entry, opts->bias, &report.emitted_entry)) {
+ kit_ctx_diagf(ctx, "image: entry address overflows after --bias");
+ st = KIT_INVALID;
+ }
+ }
+ }
}
if (st == KIT_OK && opts->image_header != KIT_IMAGE_HEADER_NONE) {
KitImageHeader kind;
@@ -1378,20 +1450,24 @@ KitStatus obj_emit_image(const KitContext* ctx, KitObjFile* obj,
if (st == KIT_OK) {
if (format == KIT_IMAGE_FORMAT_IHEX) {
st = check_32bit_text_image_range(ctx, &report, "ihex");
+ if (st == KIT_OK && report.has_entry &&
+ report.emitted_entry > UINT32_MAX) {
+ kit_ctx_diagf(ctx,
+ "image: ihex entry cannot emit above 32-bit addresses");
+ st = KIT_UNSUPPORTED;
+ }
if (st == KIT_OK)
st = write_ihex_records(ctx, ranges, nranges, opts, &report, out);
} else if (format == KIT_IMAGE_FORMAT_SREC) {
- KitObjImageInfo info;
- if (kit_obj_image_info(obj, &info) == KIT_OK) srec_entry = info.entry;
st = check_32bit_text_image_range(ctx, &report, "srec");
- if (st == KIT_OK && srec_entry > UINT32_MAX) {
+ if (st == KIT_OK && report.has_entry &&
+ report.emitted_entry > UINT32_MAX) {
kit_ctx_diagf(ctx,
- "image: srec cannot emit above 32-bit addresses");
+ "image: srec entry cannot emit above 32-bit addresses");
st = KIT_UNSUPPORTED;
}
if (st == KIT_OK)
- st = write_srec_records(ctx, ranges, nranges, opts, &report, srec_entry,
- out);
+ st = write_srec_records(ctx, ranges, nranges, opts, &report, out);
} else {
st = write_layout(ctx, ranges, nranges, opts, &report, image_hdr_p, out);
}
diff --git a/test/tools/run.sh b/test/tools/run.sh
@@ -494,6 +494,154 @@ run_fail image-ihex-over32 "$KIT" image --format ihex "$work/over32.elf" \
run_fail image-srec-over32 "$KIT" image --format srec "$work/over32.elf" \
-o "$work/over32.srec"
+# A signed bias applies to records and the declared entry alike. Rebasing this
+# image down by exactly 4 GiB makes both text formats representable; pin their
+# entry records as well as the data-address path.
+run_ok image-ihex-negative-bias "$KIT" image --format ihex \
+ --bias -4294967296 --metadata "$work/biased.json" "$work/over32.elf" \
+ -o "$work/biased.hex"
+contains image-ihex-biased-ela "$work/biased.hex" ":020000040001F9"
+contains image-ihex-biased-entry "$work/biased.hex" ":0400000500010000F6"
+contains image-meta-original-base "$work/biased.json" \
+ '"original_base": "0x100010000"'
+contains image-meta-emitted-base "$work/biased.json" \
+ '"emitted_base": "0x10000"'
+contains image-meta-original-entry "$work/biased.json" \
+ '"original_entry": "0x100010000"'
+contains image-meta-emitted-entry "$work/biased.json" \
+ '"emitted_entry": "0x10000"'
+run_ok image-srec-negative-bias "$KIT" image --format srec \
+ --bias -4294967296 "$work/over32.elf" -o "$work/biased.srec"
+contains image-srec-biased-entry "$work/biased.srec" "S804010000FA"
+expect_status 2 image-ihex-base-rejected "$KIT" image --format ihex \
+ --base 0 "$work/highaddr.elf" -o "$work/base.hex"
+contains image-ihex-base-hint "$work/image-ihex-base-rejected.err" \
+ "rebase IHEX/SREC addresses"
+run_fail image-negative-bias-underflow "$KIT" image --format ihex \
+ --bias -9223372036854775808 "$work/kernel.elf" -o "$work/underflow.hex"
+if [ ! -e "$work/underflow.hex" ]; then ok image-underflow-transaction;
+else not_ok image-underflow-transaction "$work/underflow.hex"; fi
+
+# The top 32-bit byte is representable; a two-byte range at the same address
+# crosses the boundary and must fail before an output transaction commits.
+cat > "$work/boundary.s" <<'EOF'
+.globl _start
+.section .text
+_start:
+ ret
+EOF
+cat > "$work/boundary.lds" <<'EOF'
+ENTRY(_start)
+SECTIONS {
+ . = 0xffffffff;
+ .text : ALIGN(1) { *(.text .text.*) }
+ /DISCARD/ : { *(.note.*) *(.comment) *(.eh_frame) }
+}
+EOF
+run_ok image-text-boundary-build "$KIT" build-exe -target x86_64-none-elf \
+ -nostdlib -static -no-pie -e _start -T "$work/boundary.lds" \
+ "$work/boundary.s" -o "$work/boundary.elf"
+run_ok image-ihex-boundary "$KIT" image --format ihex "$work/boundary.elf" \
+ -o "$work/boundary.hex"
+contains image-ihex-boundary-entry "$work/boundary.hex" \
+ ":04000005FFFFFFFFFB"
+run_ok image-srec-boundary "$KIT" image --format srec "$work/boundary.elf" \
+ -o "$work/boundary.srec"
+contains image-srec-boundary-entry "$work/boundary.srec" "S705FFFFFFFFFE"
+cat > "$work/boundary-cross.s" <<'EOF'
+.globl _start
+.section .text
+_start:
+ ret
+ nop
+EOF
+run_ok image-text-boundary-cross-build "$KIT" build-exe \
+ -target x86_64-none-elf -nostdlib -static -no-pie -e _start \
+ -T "$work/boundary.lds" "$work/boundary-cross.s" \
+ -o "$work/boundary-cross.elf"
+run_fail image-ihex-boundary-cross "$KIT" image --format ihex \
+ "$work/boundary-cross.elf" -o "$work/boundary-cross.hex"
+run_fail image-srec-boundary-cross "$KIT" image --format srec \
+ "$work/boundary-cross.elf" -o "$work/boundary-cross.srec"
+if [ ! -e "$work/boundary-cross.hex" ] && \
+ [ ! -e "$work/boundary-cross.srec" ]; then
+ ok image-boundary-cross-transaction
+else
+ not_ok image-boundary-cross-transaction
+fi
+
+# Relocatable objects use allocated section contents. Sections retain their
+# addresses, later selected overlaps win, filters apply before layout, and no
+# synthetic entry record is invented. The same library path backs objcopy.
+cat > "$work/raw-rel.s" <<'EOF'
+.globl raw_entry
+.section .text
+raw_entry:
+ ret
+.section .data
+ .byte 0x42
+EOF
+run_ok image-rel-build "$KIT" as -target x86_64-none-elf \
+ "$work/raw-rel.s" -o "$work/raw-rel.o"
+run_ok objcopy-rel-binary "$KIT" objcopy -O binary "$work/raw-rel.o" \
+ "$work/raw-rel.bin"
+"$KIT" xxd -p "$work/raw-rel.bin" > "$work/raw-rel.hexbytes"
+contains image-rel-overlap-later-wins "$work/raw-rel.hexbytes" "42"
+run_ok objcopy-rel-only-text "$KIT" objcopy --only-section .text -O binary \
+ "$work/raw-rel.o" "$work/raw-rel.text.bin"
+"$KIT" xxd -p "$work/raw-rel.text.bin" > "$work/raw-rel.text.hexbytes"
+contains image-rel-only-text-byte "$work/raw-rel.text.hexbytes" "c3"
+run_ok objcopy-rel-remove-data "$KIT" objcopy --remove-section .data \
+ -O binary "$work/raw-rel.o" "$work/raw-rel.nodata.bin"
+same_file image-rel-remove-matches-only "$work/raw-rel.text.bin" \
+ "$work/raw-rel.nodata.bin"
+run_ok objcopy-rel-ihex "$KIT" objcopy -O ihex "$work/raw-rel.o" \
+ "$work/raw-rel.ihex"
+if ! grep -q '^:04000005' "$work/raw-rel.ihex"; then
+ ok image-rel-ihex-no-entry
+else
+ not_ok image-rel-ihex-no-entry "$work/raw-rel.ihex"
+fi
+run_ok objcopy-rel-srec "$KIT" objcopy -O srec "$work/raw-rel.o" \
+ "$work/raw-rel.srec"
+if ! grep -q '^S[789]' "$work/raw-rel.srec"; then
+ ok image-rel-srec-no-entry
+else
+ not_ok image-rel-srec-no-entry "$work/raw-rel.srec"
+fi
+
+# Reject an unapplied relocation in a selected allocated section.
+cat > "$work/raw-reloc.s" <<'EOF'
+.globl raw_reloc
+.section .text
+raw_reloc:
+ call missing_target
+EOF
+run_ok image-reloc-build "$KIT" as -target x86_64-none-elf \
+ "$work/raw-reloc.s" -o "$work/raw-reloc.o"
+run_fail objcopy-reloc-rejected "$KIT" objcopy -O binary \
+ "$work/raw-reloc.o" "$work/raw-reloc.bin"
+if [ ! -e "$work/raw-reloc.bin" ]; then ok image-reloc-transaction;
+else not_ok image-reloc-transaction "$work/raw-reloc.bin"; fi
+
+# Object-format conversions remain format-neutral at the image API boundary.
+run_ok image-rel-macho-build "$KIT" objcopy -O mach-o "$work/raw-rel.o" \
+ "$work/raw-rel.macho"
+run_ok image-rel-macho "$KIT" objcopy --only-section __TEXT,__text -O binary \
+ "$work/raw-rel.macho" "$work/raw-rel.macho.bin"
+same_file image-rel-macho-bytes "$work/raw-rel.text.bin" \
+ "$work/raw-rel.macho.bin"
+run_ok image-rel-coff-build "$KIT" objcopy -O coff "$work/raw-rel.o" \
+ "$work/raw-rel.coff"
+run_ok image-rel-coff "$KIT" objcopy --only-section .text -O binary \
+ "$work/raw-rel.coff" "$work/raw-rel.coff.bin"
+same_file image-rel-coff-bytes "$work/raw-rel.text.bin" \
+ "$work/raw-rel.coff.bin"
+run_ok image-rel-wasm "$KIT" objcopy --only-section code -O binary \
+ "$repo_root/test/objdump/wasm/cases/add.wasm" "$work/raw-rel.wasm.bin"
+if [ -s "$work/raw-rel.wasm.bin" ]; then ok image-rel-wasm-nonempty;
+else not_ok image-rel-wasm-nonempty "$work/raw-rel.wasm.bin"; fi
+
# ---- image: --format sections ---------------------------------------------
# Concatenate named sections in DECLARED order. The fixture's .text is a lone
# `ret` (0xc3) and .rodata is 0x11223344, so the concatenation and its reverse
@@ -599,10 +747,10 @@ contains image-meta-seg-lowerload-selected "$work/meta.load.json" '"selection":
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.
+# Explicit section filters switch to allocated-section ranges; an empty
+# keep-set is a hard failure and cannot leave misleading metadata/output.
run_fail image-meta-remove-all "$KIT" image --format bin \
- --remove-section pt_load --metadata "$work/meta.rm.json" "$work/kernel.elf" \
+ --only-section .nope --metadata "$work/meta.rm.json" "$work/kernel.elf" \
-o "$work/sel.rm.bin"
# ==== END kernel-FZ Bug 10 ===================================================