mirror of
https://github.com/MCKero6423/uv-k5-v3-emulator.git
synced 2026-10-02 03:15:36 +00:00
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.
72 lines
3.1 KiB
Python
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())
|