aboutsummaryrefslogtreecommitdiffstats
path: root/psp_out/mkelf_xtensa.py
blob: 590b6314434cd14aeec45e21c7d9d6ce3ce64bec (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/usr/bin/env python3
"""Wrap a raw Tensilica Xtensa firmware image in a minimal ELF32 (EM_XTENSA=94,
little-endian). For the Navi31 SMU/MP1 (smu_13_0_0.bin), which is Xtensa
windowed-ABI, NOT RS64/RISC-V.

Ghidra recognizes EM_XTENSA and offers the Xtensa language (Xtensa:LE:32).
Stock IDA has no Xtensa module -> use a community Xtensa processor plugin or Ghidra.

usage: mkelf_xtensa.py <raw.bin> <base_hex> <out.elf> [entry_hex]
"""
import struct, sys, os

def mkelf_xtensa(raw: bytes, base: int, entry: int|None=None) -> bytes:
    if entry is None: entry = base
    EHSIZE, PHENTSIZE = 52, 32
    DATA_OFF = 0x1000
    e = b'\x7fELF' + bytes([1, 1, 1, 0]) + b'\x00'*8   # ELFCLASS32, LSB
    e += struct.pack('<HHI', 2, 94, 1)                 # ET_EXEC, EM_XTENSA(94), version
    e += struct.pack('<III', entry, EHSIZE, 0)         # entry, phoff, shoff
    e += struct.pack('<I', 0)                           # e_flags
    e += struct.pack('<HHHHHH', EHSIZE, PHENTSIZE, 1, 0, 0, 0)
    ph = struct.pack('<IIIIIIII', 1, DATA_OFF, base, base, len(raw), len(raw), 7, 0x1000)
    out = bytearray(e + ph)
    out += b'\x00' * (DATA_OFF - len(out))
    out += raw
    return bytes(out)

if __name__ == '__main__':
    raw = open(sys.argv[1], 'rb').read()
    base = int(sys.argv[2], 16)
    entry = int(sys.argv[4], 16) if len(sys.argv) > 4 else base
    open(sys.argv[3], 'wb').write(mkelf_xtensa(raw, base, entry))
    print(f"{os.path.basename(sys.argv[1])} -> {os.path.basename(sys.argv[3])} "
          f"EM_XTENSA base={base:#x} entry={entry:#x} size={len(raw):,}")