Device: boot-section detection probes SPMCR beside SPMCSR, the link picks any hardware USART through the instance-aware lookups (URSEL chips included), WDRF reads MCUSR-or-MCUCSR, and the >64 KiB shape lands — word-addressed wire flash (info flag bit 1, page byte 0 means 256, base as a word address), far reads through flash_load_far, a single 32-bit byte-cursor page walk (the 256-byte page wraps its low byte exactly), and slot arithmetic in words (the return address already is one). Host: addresses stay bytes internally and scale at the wire, the boot-fuse decode becomes a per-signature table (byte index + BOOTSZ ladder — the m168A's lives in EXTENDED), and the planner tests pin every chip's ladder plus the word-addressed info decode. Tests: the device runner serves every mega over the USART pty, pbapp banners over the right link, the update rehearsal synthesizes its assumed fuses from the tool's own table, and the PI lint tracks the renamed info symbol. All six classic-mega/168A targets pass the full suite (size, PI, planner, protocol, reloc, self-update) at 466–504 B; the 1284P builds await a libavr far-path slimming to make its 512. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
54 lines
1.9 KiB
Python
54 lines
1.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Position-independence lint for the pureboot image.
|
|
|
|
The self-staging design lets the identical binary run from any 512-byte
|
|
slot, which holds only if nothing in the image addresses itself absolutely.
|
|
Two link-time facts guarantee it, both asserted here from the built ELF:
|
|
|
|
1. No absolute jmp/call opcodes — all control flow is PC-relative
|
|
(rjmp/rcall/ijmp/icall). -mrelax normally guarantees this; a code
|
|
change that grows a branch out of relaxation range would break it
|
|
silently.
|
|
2. The info block sits within the image's first 256 bytes: the 'b'
|
|
command rebuilds its address as (running slot high byte : low byte of
|
|
the link address), which needs the offset to fit that low byte.
|
|
|
|
Usage: check_pi.py <objdump> <nm> <elf> <text_start_hex>
|
|
"""
|
|
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
|
|
def main():
|
|
objdump, nm, elf, text_start = sys.argv[1:]
|
|
text_start = int(text_start, 0)
|
|
|
|
listing = subprocess.run([objdump, "-d", elf], capture_output=True, text=True, check=True).stdout
|
|
absolute = [
|
|
line
|
|
for line in listing.splitlines()
|
|
if re.search(r"\t(jmp|call)\t", line)
|
|
]
|
|
if absolute:
|
|
print("FAIL: absolute control flow in the image:")
|
|
print("\n".join(absolute))
|
|
sys.exit(1)
|
|
|
|
symbols = subprocess.run([nm, "-C", elf], capture_output=True, text=True, check=True).stdout
|
|
info = [line for line in symbols.splitlines() if "info_data" in line]
|
|
if len(info) != 1:
|
|
print(f"FAIL: expected one info-block storage symbol, found {len(info)}")
|
|
sys.exit(1)
|
|
offset = int(info[0].split()[0], 16) - text_start
|
|
if not 0 <= offset < 256:
|
|
print(f"FAIL: info block at image offset {offset:#x}, must sit in the first 256 bytes")
|
|
sys.exit(1)
|
|
|
|
print(f"PI lint: control flow PC-relative, info block at offset {offset:#x}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|