329 lines
11 KiB
Python
329 lines
11 KiB
Python
#!/usr/bin/env pythoimport argparse
|
|
import struct
|
|
from pathlib import Path
|
|
|
|
|
|
EM_DLX = 0x5AA5
|
|
R_DLX_PCREL26 = 9
|
|
R_DLX_RELOC_32 = 3
|
|
MASK26 = 0x03FFFFFF
|
|
|
|
|
|
def p16(v):
|
|
return struct.pack(">H", v & 0xFFFF)
|
|
|
|
|
|
def p32(v):
|
|
return struct.pack(">I", v & 0xFFFFFFFF)
|
|
|
|
|
|
def strtab(strings):
|
|
blob = b"\x00"
|
|
offsets = {"": 0}
|
|
for s in strings:
|
|
if s and s not in offsets:
|
|
offsets[s] = len(blob)
|
|
blob += s.encode("ascii") + b"\x00"
|
|
return blob, offsets
|
|
|
|
|
|
def sym(name, value, size, info, shndx):
|
|
return p32(name) + p32(value) + p32(size) + bytes([info, 0]) + p16(shndx)
|
|
|
|
|
|
def build_elf(debug_size, relocs):
|
|
di = b"\x00" * debug_size
|
|
tx = b"\x00" * 4
|
|
sec_names = [
|
|
".text",
|
|
".debug_info",
|
|
".rel.debug_info",
|
|
".symtab",
|
|
".strtab",
|
|
".shstrtab",
|
|
]
|
|
shstr, shoff = strtab(sec_names)
|
|
names = [f"s{i}" for i in range(len(relocs))]
|
|
str_blob, stroff = strtab(names)
|
|
|
|
symtab = b""
|
|
symtab += sym(0, 0, 0, 0, 0)
|
|
symtab += sym(0, 0, 0, 0x03, 1)
|
|
symtab += sym(0, 0, 0, 0x03, 2)
|
|
for i, reloc in enumerate(relocs):
|
|
_offset, value = reloc[:2]
|
|
symtab += sym(stroff[f"s{i}"], value, 4, 0x12, 2)
|
|
|
|
rb = b""
|
|
for i, reloc in enumerate(relocs):
|
|
offset, _value = reloc[:2]
|
|
r_type = reloc[2] if len(reloc) > 2 else R_DLX_PCREL26
|
|
r_info = ((3 + i) << 8) | r_type
|
|
rb += p32(offset) + p32(r_info)
|
|
|
|
o = 52
|
|
text_off = o
|
|
o += len(tx)
|
|
debug_off = o
|
|
o += len(di)
|
|
rel_off = o
|
|
o += len(rb)
|
|
sym_off = o
|
|
o += len(symtab)
|
|
str_off = o
|
|
o += len(str_blob)
|
|
shstr_off = o
|
|
o += len(shstr)
|
|
shdr_off = o
|
|
|
|
def shdr(name, stype, flags, offset, size, link, info, align, entsize):
|
|
return (
|
|
p32(name)
|
|
+ p32(stype)
|
|
+ p32(flags)
|
|
+ p32(0)
|
|
+ p32(offset)
|
|
+ p32(size)
|
|
+ p32(link)
|
|
+ p32(info)
|
|
+ p32(align)
|
|
+ p32(entsize)
|
|
)
|
|
|
|
hdrs = b""
|
|
hdrs += shdr(0, 0, 0, 0, 0, 0, 0, 0, 0)
|
|
hdrs += shdr(shoff[".text"], 1, 6, text_off, len(tx), 0, 0, 4, 0)
|
|
hdrs += shdr(shoff[".debug_info"], 1, 0, debug_off, len(di), 0, 0, 1, 0)
|
|
hdrs += shdr(shoff[".rel.debug_info"], 9, 0x40, rel_off, len(rb), 4, 2, 4, 8)
|
|
hdrs += shdr(shoff[".symtab"], 2, 0, sym_off, len(symtab), 5, 3, 4, 16)
|
|
hdrs += shdr(shoff[".strtab"], 3, 0, str_off, len(str_blob), 0, 0, 1, 0)
|
|
hdrs += shdr(shoff[".shstrtab"], 3, 0, shstr_off, len(shstr), 0, 0, 1, 0)
|
|
|
|
ident = b"\x7fELF" + bytes([1, 2, 1, 0]) + b"\x00" * 8
|
|
ehdr = (
|
|
ident
|
|
+ p16(1)
|
|
+ p16(EM_DLX)
|
|
+ p32(1)
|
|
+ p32(0)
|
|
+ p32(0)
|
|
+ p32(shdr_off)
|
|
+ p32(0)
|
|
+ p16(52)
|
|
+ p16(0)
|
|
+ p16(0)
|
|
+ p16(40)
|
|
+ p16(7)
|
|
+ p16(6)
|
|
)
|
|
return ehdr + tx + di + rb + symtab + str_blob + shstr + hdrs
|
|
|
|
|
|
def decode_dlx_vallo(low26):
|
|
low26 &= MASK26
|
|
if low26 & 0x03000000:
|
|
return (~(low26 | 0xFC000000) + 1) & 0xFFFFFFFF
|
|
return low26
|
|
|
|
|
|
def low26_to_signed(low26):
|
|
low26 &= MASK26
|
|
if low26 & 0x02000000:
|
|
return low26 - 0x04000000
|
|
return low26
|
|
|
|
|
|
def word_to_low26(word):
|
|
return word & MASK26
|
|
|
|
|
|
def symbol_for_low26(current_word, final_low26):
|
|
final_low26 &= MASK26
|
|
signed_final = low26_to_signed(final_low26)
|
|
if signed_final == -0x02000000:
|
|
raise ValueError("DLX PCREL26 cannot encode final low26 0x02000000")
|
|
vallo = decode_dlx_vallo(word_to_low26(current_word))
|
|
return (vallo + signed_final) & 0xFFFFFFFF
|
|
|
|
|
|
def encodable_low26(final_low26):
|
|
return (final_low26 & MASK26) != 0x02000000
|
|
|
|
|
|
def apply_dlx_word(memory, offset, symbol_value):
|
|
cur = int.from_bytes(bytes(memory[offset : offset + 4]), "big")
|
|
vallo = decode_dlx_vallo(cur & MASK26)
|
|
val = ((symbol_value & 0xFFFFFFFF) - vallo) & 0xFFFFFFFF
|
|
if val & 0x80000000:
|
|
val_signed = val - 0x100000000
|
|
else:
|
|
val_signed = val
|
|
if abs(val_signed) > 0x01FFFFFF:
|
|
raise ValueError(f"relocation would be out of range: {val_signed:#x}")
|
|
new_word = (cur & 0xFC000000) | (val_signed & MASK26)
|
|
memory[offset : offset + 4] = new_word.to_bytes(4, "big")
|
|
|
|
|
|
class ChainBuilder:
|
|
def __init__(self, debug_size, rbase, memory_base, memory):
|
|
self.debug_size = debug_size
|
|
self.rbase = rbase
|
|
self.memory_base = memory_base
|
|
self.memory = bytearray(memory)
|
|
self.relocs = []
|
|
self.notes = []
|
|
self._initialized_addresses = set()
|
|
|
|
def _mem_index(self, target):
|
|
idx = target - self.memory_base
|
|
if idx < 0 or idx + 4 > len(self.memory):
|
|
raise ValueError(f"target {target:#x} outside modeled memory")
|
|
return idx
|
|
|
|
def _raw_reloc(self, offset, symbol_value, note):
|
|
idx = len(self.relocs)
|
|
self.relocs.append((offset & 0xFFFFFFFF, symbol_value & 0xFFFFFFFF))
|
|
self.notes.append((idx, offset, symbol_value & 0xFFFFFFFF, note))
|
|
self._set_address_field(idx, offset & 0xFFFFFFFF)
|
|
return idx
|
|
|
|
def add_pi32_reloc(self, target, delta, note):
|
|
actual_idx = len(self.relocs) + (2 if target < 0 else 0)
|
|
self._set_address_field(actual_idx, target & 0xFFFFFFFF)
|
|
if target < 0:
|
|
self._patch_negative_address_for_index(actual_idx)
|
|
idx = len(self.relocs)
|
|
self.relocs.append((target & 0xFFFFFFFF, delta & 0xFFFFFFFF, R_DLX_RELOC_32))
|
|
self.notes.append((idx, target, delta & 0xFFFFFFFF, note))
|
|
self._set_address_field(idx, target & 0xFFFFFFFF)
|
|
mem_idx = self._mem_index(target)
|
|
cur = int.from_bytes(bytes(self.memory[mem_idx : mem_idx + 4]), "big")
|
|
new = (cur + (delta & 0xFFFFFFFF)) & 0xFFFFFFFF
|
|
self.memory[mem_idx : mem_idx + 4] = new.to_bytes(4, "big")
|
|
|
|
def _set_address_field(self, reloc_idx, address):
|
|
if reloc_idx in self._initialized_addresses:
|
|
return
|
|
field = self.rbase + reloc_idx * 32 + 8
|
|
mem_idx = field - self.memory_base
|
|
if 0 <= mem_idx and mem_idx + 8 <= len(self.memory):
|
|
self.memory[mem_idx : mem_idx + 8] = (address & 0xFFFFFFFF).to_bytes(8, "little")
|
|
self._initialized_addresses.add(reloc_idx)
|
|
|
|
def _positive_write_low26(self, target, final_low26, note):
|
|
idx = self._mem_index(target)
|
|
cur = int.from_bytes(bytes(self.memory[idx : idx + 4]), "big")
|
|
symv = symbol_for_low26(cur, final_low26)
|
|
self._raw_reloc(target, symv, note)
|
|
apply_dlx_word(self.memory, idx, symv)
|
|
|
|
def _patch_negative_address_for_index(self, actual_idx):
|
|
h = self.rbase + actual_idx * 32 + 12
|
|
self._positive_write_low26(h - 1, 0x03FFFFFF, f"patch reloc{actual_idx} address high dword bytes 0..2")
|
|
self._positive_write_low26(h, 0x03FFFFFF, f"patch reloc{actual_idx} address high dword byte 3")
|
|
|
|
def write_low26(self, target, final_low26, note):
|
|
if target < 0:
|
|
actual_idx = len(self.relocs) + 2
|
|
self._set_address_field(actual_idx, target & 0xFFFFFFFF)
|
|
self._patch_negative_address_for_index(actual_idx)
|
|
self._raw_reloc(target & 0xFFFFFFFF, 0, f"{note} placeholder before simulation")
|
|
idx = self._mem_index(target)
|
|
cur = int.from_bytes(bytes(self.memory[idx : idx + 4]), "big")
|
|
symv = symbol_for_low26(cur, final_low26)
|
|
self.relocs[-1] = (target & 0xFFFFFFFF, symv)
|
|
self.notes[-1] = (actual_idx, target, symv, note)
|
|
apply_dlx_word(self.memory, idx, symv)
|
|
else:
|
|
self._positive_write_low26(target, final_low26, note)
|
|
|
|
def write_bytes4(self, target, data):
|
|
if len(data) != 4:
|
|
raise ValueError("write_bytes4 needs exactly 4 bytes")
|
|
prior_idx = self._mem_index(target - 1)
|
|
prior_low2 = self.memory[prior_idx] & 3
|
|
low_a = (
|
|
(prior_low2 << 24)
|
|
| (data[0] << 16)
|
|
| (data[1] << 8)
|
|
| data[2]
|
|
)
|
|
low_b = ((data[0] & 3) << 24) | (data[1] << 16) | (data[2] << 8) | data[3]
|
|
if encodable_low26(low_a) and encodable_low26(low_b):
|
|
self.write_low26(target - 1, low_a, f"stage write bytes at {target:#x}")
|
|
self.write_low26(target, low_b, f"finish write bytes at {target:#x}")
|
|
return
|
|
|
|
tail_idx = self._mem_index(target + 2)
|
|
tail_low2 = self.memory[tail_idx] & 3
|
|
for filler in range(0x10000):
|
|
low_tail = (tail_low2 << 24) | (data[3] << 16) | filler
|
|
if encodable_low26(low_tail) and encodable_low26(low_a):
|
|
self.write_low26(target + 2, low_tail, f"fallback tail byte for {target:#x}")
|
|
self.write_low26(target - 1, low_a, f"fallback first three bytes at {target:#x}")
|
|
return
|
|
raise ValueError(f"no DLX byte decomposition for target {target:#x}")
|
|
|
|
|
|
def parse_hex_bytes(value):
|
|
value = value.replace(" ", "").replace(":", "")
|
|
if len(value) % 2:
|
|
raise argparse.ArgumentTypeError("hex byte string must have an even length")
|
|
return bytes.fromhex(value)
|
|
|
|
|
|
def parse_write(spec):
|
|
off, data = spec.split(":", 1)
|
|
return int(off, 0), parse_hex_bytes(data)
|
|
|
|
|
|
def parse_patch(spec):
|
|
off, data = spec.split(":", 1)
|
|
return int(off, 0), parse_hex_bytes(data)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--debug-size", type=int, default=144)
|
|
parser.add_argument("--rbase", type=lambda x: int(x, 0), required=True)
|
|
parser.add_argument("--memory-base", type=lambda x: int(x, 0), required=True)
|
|
parser.add_argument("--memory-hex", type=parse_hex_bytes)
|
|
parser.add_argument("--memory-size", type=lambda x: int(x, 0))
|
|
parser.add_argument("--patch-mem", action="append", type=parse_patch, default=[])
|
|
parser.add_argument("--write4", action="append", type=parse_write, required=True)
|
|
parser.add_argument("--out", type=Path, required=True)
|
|
parser.add_argument("--notes", type=Path)
|
|
args = parser.parse_args()
|
|
|
|
if args.memory_hex is None:
|
|
if args.memory_size is None:
|
|
parser.error("either --memory-hex or --memory-size is required")
|
|
memory = bytearray(args.memory_size)
|
|
else:
|
|
memory = bytearray(args.memory_hex)
|
|
if args.memory_size is not None and args.memory_size > len(memory):
|
|
memory.extend(b"\x00" * (args.memory_size - len(memory)))
|
|
|
|
for off, data in args.patch_mem:
|
|
idx = off - args.memory_base
|
|
if idx < 0 or idx + len(data) > len(memory):
|
|
parser.error(f"--patch-mem offset {off:#x} outside modeled memory")
|
|
memory[idx : idx + len(data)] = data
|
|
|
|
builder = ChainBuilder(args.debug_size, args.rbase, args.memory_base, memory)
|
|
for target, data in args.write4:
|
|
builder.write_bytes4(target, data)
|
|
|
|
args.out.write_bytes(build_elf(args.debug_size, builder.relocs))
|
|
print(args.out.resolve())
|
|
print(f"relocations={len(builder.relocs)}")
|
|
if args.notes:
|
|
lines = []
|
|
for idx, target, symv, note in builder.notes:
|
|
lines.append(f"{idx:03d} target={target:#x} sym=0x{symv:08x} {note}")
|
|
args.notes.write_text("\n".join(lines) + "\n", encoding="ascii")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|