Files

113 lines
3.9 KiB
Python

"""One loaded NSO module as the generator and its analyses see it: image at a guest base, .text as words,
function seeds, and bounded-cache instruction lifting.
The function seeds and the .eh_frame_hdr reader are adapted from LAS Recomp tools/analyze.py and tools/gen.py
@ da35b83 (MIT, Copyright (c) 2026 Peiikai).
"""
import struct
from collections import OrderedDict
import numpy as np
from lifter import Insn, Lifter, Unsupported, hx
from nso import Nso, R_AARCH64_RELATIVE
LIFT_CACHE = 1 << 16 # lifted instructions kept per module; analyses revisit nearby addresses only
def _read_encoded(img, off, enc, pc_base):
fmt, app = enc & 0x0F, enc & 0x70
if fmt == 0x0B: # sdata4
v, size = struct.unpack_from('<i', img, off)[0], 4
elif fmt == 0x03: # udata4
v, size = struct.unpack_from('<I', img, off)[0], 4
elif fmt in (0x0C, 0x04): # sdata8, udata8
v, size = struct.unpack_from('<q' if fmt == 0x0C else '<Q', img, off)[0], 8
else:
raise ValueError(f'unsupported pointer encoding {enc:#x}')
if app == 0x10: # pcrel
v += off
elif app == 0x30: # datarel
v += pc_base
return v, size
class Module:
def __init__(self, name, path, base, simd='c'):
self.name = name
self.nso = Nso(str(path))
self.base = base
n = self.nso
self.image = bytes(n.image)
self.lo = base + n.text[0]
self.hi = self.lo + n.text[1]
self.words = np.frombuffer(self.image, dtype='<u4', count=n.text[1] // 4, offset=n.text[0])
self.lifter = Lifter(base, simd)
self._cache = OrderedDict()
# ---- memory
def in_text(self, a):
return self.lo <= a < self.hi and a % 4 == 0
def in_image(self, a, n=1):
return self.base <= a and a + n <= self.base + len(self.image)
def word(self, a):
return int(self.words[(a - self.lo) >> 2])
def read(self, a, n, signed=False):
"""Little-endian integer of n bytes at guest address a (unrelocated image)."""
o = a - self.base
return int.from_bytes(self.image[o:o + n], 'little', signed=signed)
# ---- lifting
def lift(self, a):
"""Lifted instruction at a; Unsupported becomes an rt_udf trap."""
ins = self._cache.get(a)
if ins is not None:
self._cache.move_to_end(a)
return ins
try:
ins = self.lifter.lift(self.word(a), a)
except Unsupported as e:
ins = Insn('trap', f'rt_udf(c, {hx(a)}); /* {e} */')
self._cache[a] = ins
if len(self._cache) > LIFT_CACHE:
self._cache.popitem(last=False)
return ins
# ---- function seeds
def eh_frame_functions(self):
"""Function starts from .eh_frame_hdr's binary search table (absolute addresses)."""
img, hdr = self.image, self.nso.eh_start
if not hdr or hdr >= len(img):
return []
_, ptr_enc, cnt_enc, tbl_enc = img[hdr:hdr + 4]
off = hdr + 4
_, sz = _read_encoded(img, off, ptr_enc, hdr)
off += sz
count, sz = _read_encoded(img, off, cnt_enc, hdr)
off += sz
starts = []
for _ in range(count):
loc, sz = _read_encoded(img, off, tbl_enc, hdr)
off += sz
_, sz = _read_encoded(img, off, tbl_enc, hdr)
off += sz
starts.append(self.base + loc)
return starts
def seeds(self):
"""Known function starts: .eh_frame_hdr, exported FUNC symbols, R_AARCH64_RELATIVE targets in .text."""
starts = set(self.eh_frame_functions())
for _, info, shndx, value, _ in self.nso.syms:
if shndx and (info & 0xF) == 2:
starts.add(self.base + value)
for _, typ, _, add in self.nso.relocs:
if typ == R_AARCH64_RELATIVE:
starts.add(self.base + add)
return sorted(a for a in starts if self.in_text(a))