Files
uv-k5-v3-emulator/tools/elf2bin.py
T
mckero f938d0b3bd Build overlay apps without the Arm toolchain, and say how far verification got
pip install ziglang cross-compiles to thumb-freestanding-eabi, which is enough to build a .app with no arm-none-eabi-gcc and no Docker. Measured refusals: --defsym, -Ttext and --section-start come back as unsupported linker args, so the VMA is resolved into a copy of app.ld; -T is forwarded (a missing script errors); --image-base is accepted but page-aligns the segments into 0x200103C8 and 0x20020C94. tools/elf2bin.py extracts allocated sections rather than program headers, because lld maps the ELF header and phdr table as a 180-byte LOAD of its own -- following the headers starts the image at 0x20000000 and APP_ERR_VMA. One division pulled in __aeabi_uidiv, which a -nostdlib blob cannot have: Minesweeper now avoids division entirely. app_main carries the .text.entry attribute upstream's apps use, so the entry is first for the loader's jump to offset 0.

Minesweeper builds to 2408 bytes of code against a 4096-byte budget. Installed through the page, the firmware reads the slot header twice and then exactly code_size bytes from slot+0x1000 -- the only read of that size in the boot log -- so the blob shape, header, CRC, VMA and offset are all accepted. Whether control reaches the overlay is unproven: the 100 ms PC probe saw no overlay address, no APP ERROR screen appears, and the app does not draw. test_elf2bin pins the phantom-header-segment lesson; both AGENTS files record the rest.
2026-10-02 08:32:22 +08:00

72 lines
3.1 KiB
Python

#!/usr/bin/env python3
"""Extract the loadable bytes of an overlay-app ELF as a flat binary.
objcopy -O binary is the normal tool; this reads the ELF itself. Allocated *sections*
are used rather than program headers, because lld maps the ELF header and program
header table as a LOAD of its own (52 + 4x32 = 180 bytes at the image base) with no
section content in it -- following the program headers puts a phantom 180-byte region
below the overlay VMA and makes the image start at the wrong address.
"""
import argparse
import struct
import sys
SHT_SYMTAB, SHT_NOBITS = 2, 8
SHF_ALLOC, SHF_WRITE, SHF_EXECINSTR = 0x2, 0x1, 0x4
def sections(blob: bytes):
if len(blob) < 52 or blob[0] != 0x7F or blob[1:4] != b"ELF" or blob[4] != 1 or blob[5] != 1:
raise SystemExit("not a 32-bit little-endian ELF")
e_shoff, = struct.unpack_from("<I", blob, 0x20)
e_shentsize, e_shnum, _ = struct.unpack_from("<HHH", blob, 0x2E)
if not e_shoff or not e_shnum:
return None
out = []
for i in range(e_shnum):
f = struct.unpack_from("<IIIIIIIIII", blob, e_shoff + i * e_shentsize)
addr, offset, size, flags, typ = f[3], f[4], f[5], f[2], f[1]
if typ == SHT_NOBITS: # .bss has no file bytes
out.append((addr, offset, 0, size, typ))
elif flags & SHF_ALLOC and size:
out.append((addr, offset, size, size, typ))
return out or None
def main(argv=None):
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("elf")
ap.add_argument("bin")
ap.add_argument("--min", type=lambda v: int(v, 0), default=None,
help="require the image to start here (the overlay VMA)")
ap.add_argument("--quiet", action="store_true")
args = ap.parse_args(argv)
blob = open(args.elf, "rb").read()
secs = sections(blob)
if not secs:
raise SystemExit("no allocated sections; nothing to extract")
base = min(s[0] for s in secs if s[2]) # the first section with file content
if args.min is not None and base != args.min:
raise SystemExit("image starts at 0x%08X but the overlay VMA is 0x%08X -- the blob "
"would be rejected with APP_ERR_VMA" % (base, args.min))
end = max(addr + memsz for addr, _, _, memsz, _ in secs)
out = bytearray(end - base) # .bss stays zero, as the loader wants
for addr, offset, filesz, memsz, _ in secs:
if filesz:
out[addr - base:addr - base + filesz] = blob[offset:offset + filesz]
open(args.bin, "wb").write(out)
name = lambda p: p.replace("\\", "/").split("/")[-1]
if not args.quiet:
print("%s -> %s: %d bytes at 0x%08X (%d allocated section(s), %d of them with content)"
% (name(args.elf), name(args.bin), len(out), base,
len(secs), sum(1 for s in secs if s[2])))
if len(out) > 0x1000 and args.min == 0x20000280:
print("WARNING: %d bytes exceeds the 4096-byte overlay budget" % len(out), file=sys.stderr)
return 0
if __name__ == "__main__":
sys.exit(main())