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.
84 lines
3.3 KiB
Python
84 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
|
"""The ELF extractor must follow *sections*, not program headers.
|
|
|
|
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. An extractor that follows program
|
|
headers therefore puts a phantom 180-byte region below the first real section, and the
|
|
image is refused with APP_ERR_VMA or, worse, built from the wrong base. These tests build
|
|
the ELFs by hand, so they need no toolchain.
|
|
"""
|
|
import struct
|
|
import unittest
|
|
import os
|
|
import tempfile
|
|
|
|
import elf2bin
|
|
|
|
|
|
def build_elf(body=b"\x11" * 64, with_phantom_phdr=False):
|
|
"""A minimal 32-bit little-endian ELF with one allocated section holding @body.
|
|
|
|
With @with_phantom_phdr the first program header is a LOAD covering the ELF header and
|
|
the program-header table itself -- no section content, exactly what lld emits -- which
|
|
must not become part of the extracted image.
|
|
"""
|
|
ehsize, phentsize, shentsize = 52, 32, 40
|
|
phnum = 2 if with_phantom_phdr else 1
|
|
phoff = ehsize
|
|
data_off = phoff + phnum * phentsize
|
|
shoff = data_off + len(body)
|
|
blob = bytearray(shoff + shentsize)
|
|
blob[0:4] = b"\x7fELF"
|
|
blob[4] = 1 # 32-bit
|
|
blob[5] = 1 # little endian
|
|
struct.pack_into("<I", blob, 0x1C, phoff)
|
|
struct.pack_into("<H", blob, 0x2A, phentsize)
|
|
struct.pack_into("<H", blob, 0x2C, phnum)
|
|
struct.pack_into("<I", blob, 0x20, shoff)
|
|
struct.pack_into("<H", blob, 0x2E, shentsize)
|
|
struct.pack_into("<H", blob, 0x30, 1)
|
|
blob[data_off:data_off + len(body)] = body # the section's file content
|
|
i = 0
|
|
if with_phantom_phdr:
|
|
struct.pack_into("<IIIIIIII", blob, phoff, 1, 0, 0x20000000, 0x20000000,
|
|
ehsize + phentsize, 0, 4, 4)
|
|
i = 1
|
|
struct.pack_into("<IIIIIIII", blob, phoff + i * phentsize, 1, data_off, 0x20000280,
|
|
0x20000280, len(body), len(body), 5, 4)
|
|
struct.pack_into("<IIIIIIIIII", blob, shoff, 0, 1, 0x6, 0x20000280,
|
|
data_off, len(body), 0, 0, 4, 0)
|
|
return bytes(blob)
|
|
|
|
|
|
class TestExtract(unittest.TestCase):
|
|
def _extract(self, blob, minimum=0x20000280):
|
|
path = os.path.join(tempfile.mkdtemp(), "in.elf")
|
|
out = os.path.join(os.path.dirname(path), "out.bin")
|
|
with open(path, "wb") as fh:
|
|
fh.write(blob)
|
|
elf2bin.main([path, out, "--min", hex(minimum), "--quiet"])
|
|
with open(out, "rb") as fh:
|
|
return fh.read()
|
|
|
|
def test_a_plain_single_section_elf(self):
|
|
blob = build_elf()
|
|
self.assertEqual(self._extract(blob), b"\x11" * 64)
|
|
|
|
def test_the_phantom_header_segment_is_not_part_of_the_image(self):
|
|
"""This is the case that broke the first version: 0x20000000 is not the base."""
|
|
blob = build_elf(with_phantom_phdr=True)
|
|
self.assertEqual(self._extract(bytes(blob)), b"\x11" * 64)
|
|
|
|
def test_a_wrong_base_is_refused_rather_than_silently_shifted(self):
|
|
blob = build_elf()
|
|
with self.assertRaises(SystemExit):
|
|
self._extract(bytes(blob), minimum=0x20000000)
|
|
|
|
def test_something_that_is_not_an_elf_is_refused(self):
|
|
with self.assertRaises(SystemExit):
|
|
self._extract(b"not an elf at all" + b"\x00" * 64)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|