mtk-wifi-fw/tools/mtk_fw_elf.py

98 lines
3.1 KiB
Python
Executable file

#!/usr/bin/env python3
"""Emit a spec-correct 32-bit LE ELF (e_machine=EM_NDS32) from a MediaTek
Connac2 WiFi firmware container, one PT_LOAD segment per region at true
load addresses.
Structure is validated by readelf; disassembly requires Ghidra (official
NDS32 module) or an Andes nds32le-elf toolchain — Debian binutils dropped
NDS32. Entry point is unknown pre-RE (e_entry=0); set it in your tool after
import.
NON_DL regions are included as loadable segments by default; they are
host-side data per the driver, not downloaded to the MCU.
"""
import struct
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from mtk_fw_extract import PATCH_HDR, PATCH_SEC, REGION, TRAILER # noqa: E402
EM_NDS32 = 167
EHDR = struct.Struct('<16sHHIIIIIHHHHHH') # 52 bytes
PHDR = struct.Struct('<IIIIIIII') # 32 bytes
PT_LOAD, PF_RWX = 1, 7
def ram_regions(d: bytes):
"""Yield (addr, data) for each region of a RAM container."""
t = TRAILER.unpack_from(d, len(d) - TRAILER.size)
n = t[2]
table = len(d) - TRAILER.size - n * REGION.size
off = 0
for i in range(n):
r = REGION.unpack_from(d, table + i * REGION.size)
addr, ln = r[4], r[5]
data = d[off:off + ln]
off += ln
yield addr, data
def patch_regions(d: bytes):
"""Yield (addr, data) for each section of a patch container."""
h = PATCH_HDR.unpack_from(d, 0)
n = h[9]
for i in range(n):
s = PATCH_SEC.unpack_from(d, PATCH_HDR.size + i * PATCH_SEC.size)
typ, offs, size = s[0], s[1], s[2]
addr = s[3]
yield addr, d[offs:offs + size]
def build_elf(regions) -> bytes:
regions = [(a, dat) for a, dat in regions if dat]
n = len(regions)
phoff = EHDR.size
data_off = phoff + n * PHDR.size
eh = EHDR.pack(
b'\x7fELF' + bytes([1, 1, 1, 0]) + b'\x00' * 8, # ELFCLASS32/LSB/v1
2, EM_NDS32, 1,
regions[0][0] if regions else 0, # e_entry (first region; see doc)
phoff, 0, 0,
EHDR.size, PHDR.size, n,
40, 0, 0)
out = bytearray(eh)
offset = data_off
for addr, dat in regions:
out += PHDR.pack(PT_LOAD, offset, addr, addr, len(dat), len(dat),
PF_RWX, 0x1000)
offset += len(dat)
for _, dat in regions:
out += dat
return bytes(out)
def main():
if len(sys.argv) < 2:
sys.exit(f'usage: {sys.argv[0]} <blob> [<blob>...] [-o outdir]')
args, outdir = [], Path('extracted')
it = iter(sys.argv[1:])
for a in it:
if a == '-o':
outdir = Path(next(it))
else:
args.append(a)
for path in args:
p = Path(path)
d = p.read_bytes()
regions = patch_regions(d) if 'patch' in p.name else ram_regions(d)
elf = build_elf(regions)
out = outdir / p.stem / f'{p.stem}.elf'
out.parent.mkdir(parents=True, exist_ok=True)
out.write_bytes(elf)
n_seg = struct.unpack_from('<H', elf, 44)[0] # e_phnum
print(f'{p.name} -> {out} ({len(elf)} bytes, {n_seg} PT_LOAD)')
if __name__ == '__main__':
main()