102 lines
3.4 KiB
Python
102 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
|||
|
|
"""Wrap the segments of an ESP8266 (v1) app image in a minimal ELF so binutils can read it.
|
||
|
|
|
||
|
|
Reads only the app image (the 0x1000 slot of a full flash dump), never SPIFFS.
|
||
|
|
|
||
|
|
python tools/esp8266_image_to_elf.py <full_dump.bin> <out.elf> [--offset 0x1000]
|
||
|
|
|
||
|
|
Each image segment becomes one section (.irom0, .iram0_N, .dram0_N) at its load address,
|
||
|
|
so `xtensa-lx106-elf-objdump -d out.elf` disassembles with real addresses.
|
||
|
|
"""
|
||
|
|
import argparse
|
||
|
|
import struct
|
||
|
|
|
||
|
|
APP_SLOT_MAX = 0x100000 # never read past the 1 MB app slot (SPIFFS starts at 0x200000)
|
||
|
|
|
||
|
|
|
||
|
|
def read_segments(image):
|
||
|
|
magic, nseg, _mode, _sizefreq, entry = struct.unpack_from("<BBBBI", image, 0)
|
||
|
|
if magic != 0xE9:
|
||
|
|
raise SystemExit(f"bad image magic 0x{magic:02x}")
|
||
|
|
off = 8
|
||
|
|
segs = []
|
||
|
|
for _ in range(nseg):
|
||
|
|
addr, size = struct.unpack_from("<II", image, off)
|
||
|
|
off += 8
|
||
|
|
segs.append((addr, image[off:off + size]))
|
||
|
|
off += size
|
||
|
|
return entry, segs
|
||
|
|
|
||
|
|
|
||
|
|
def section_name(addr, counters):
|
||
|
|
if 0x40200000 <= addr < 0x40300000:
|
||
|
|
kind = "irom0"
|
||
|
|
elif 0x40100000 <= addr < 0x40108000:
|
||
|
|
kind = "iram0"
|
||
|
|
else:
|
||
|
|
kind = "dram0"
|
||
|
|
counters[kind] = counters.get(kind, 0) + 1
|
||
|
|
return f".{kind}_{counters[kind] - 1}"
|
||
|
|
|
||
|
|
|
||
|
|
def build_elf(entry, segs):
|
||
|
|
counters = {}
|
||
|
|
names = [section_name(a, counters) for a, _ in segs]
|
||
|
|
shstrtab = b"\0"
|
||
|
|
name_off = []
|
||
|
|
for n in names + [".shstrtab"]:
|
||
|
|
name_off.append(len(shstrtab))
|
||
|
|
shstrtab += n.encode() + b"\0"
|
||
|
|
|
||
|
|
ehsize, phentsize, shentsize = 52, 32, 40
|
||
|
|
nseg = len(segs)
|
||
|
|
data_off = ehsize + phentsize * nseg
|
||
|
|
body = b""
|
||
|
|
offsets = []
|
||
|
|
for _, data in segs:
|
||
|
|
while (data_off + len(body)) % 4:
|
||
|
|
body += b"\0"
|
||
|
|
offsets.append(data_off + len(body))
|
||
|
|
body += data
|
||
|
|
shstr_off = data_off + len(body)
|
||
|
|
body += shstrtab
|
||
|
|
while (data_off + len(body)) % 4:
|
||
|
|
body += b"\0"
|
||
|
|
shoff = data_off + len(body)
|
||
|
|
shnum = nseg + 2
|
||
|
|
|
||
|
|
out = bytearray()
|
||
|
|
out += b"\x7fELF" + bytes([1, 1, 1]) + bytes(9)
|
||
|
|
out += struct.pack("<HHIIIIIHHHHHH", 2, 94, 1, entry, ehsize, shoff, 0x300,
|
||
|
|
ehsize, phentsize, nseg, shentsize, shnum, shnum - 1)
|
||
|
|
for (addr, data), o in zip(segs, offsets):
|
||
|
|
flags = 5 if addr >= 0x40000000 else 6 # R+X for code, R+W for data
|
||
|
|
out += struct.pack("<IIIIIIII", 1, o, addr, addr, len(data), len(data), flags, 4)
|
||
|
|
out += body
|
||
|
|
out += bytes(shentsize) # null section
|
||
|
|
for i, ((addr, data), o) in enumerate(zip(segs, offsets)):
|
||
|
|
code = addr >= 0x40000000
|
||
|
|
sh_flags = 0x2 | (0x4 if code else 0x1) # ALLOC + EXECINSTR / WRITE
|
||
|
|
out += struct.pack("<IIIIIIIIII", name_off[i], 1, sh_flags, addr, o, len(data), 0, 0, 4, 0)
|
||
|
|
out += struct.pack("<IIIIIIIIII", name_off[-1], 3, 0, 0, shstr_off, len(shstrtab), 0, 0, 1, 0)
|
||
|
|
return bytes(out)
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
||
|
|
ap.add_argument("dump")
|
||
|
|
ap.add_argument("out")
|
||
|
|
ap.add_argument("--offset", type=lambda s: int(s, 0), default=0x1000)
|
||
|
|
args = ap.parse_args()
|
||
|
|
with open(args.dump, "rb") as f:
|
||
|
|
f.seek(args.offset)
|
||
|
|
image = f.read(APP_SLOT_MAX - args.offset)
|
||
|
|
entry, segs = read_segments(image)
|
||
|
|
with open(args.out, "wb") as f:
|
||
|
|
f.write(build_elf(entry, segs))
|
||
|
|
for addr, data in segs:
|
||
|
|
print(f"0x{addr:08x} 0x{len(data):06x}")
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|