aboutsummaryrefslogtreecommitdiff
path: root/tools/nx2/nso_syms.py
blob: 1d20c4f2ecbdb9d1e63d191563f10502f064fa90 (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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#!/usr/bin/env python3
# Symbol lookup for an NSO (read only).
#   nso_syms.py <nso> addr <hex offset>...   nearest exported symbol for module offsets
#   nso_syms.py <nso> got <hex offset>...    import bound to a GOT slot (e.g. from a PLT stub)
#   nso_syms.py <nso> find <substring>       exported symbols whose name contains substring
import struct
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))
from a64_coverage import lz4_block_decompress  # noqa: E402

DT_NULL, DT_STRTAB, DT_SYMTAB, DT_RELA, DT_RELASZ, DT_JMPREL, DT_PLTRELSZ = 0, 5, 6, 7, 8, 23, 2


def load_image(path):
    d = open(path, "rb").read()
    assert d[:4] == b"NSO0"
    flags = struct.unpack_from("<I", d, 0xC)[0]
    segs = [struct.unpack_from("<III", d, 0x10 + i * 0x10) for i in range(3)]
    csizes = struct.unpack_from("<III", d, 0x60)
    bss = struct.unpack_from("<I", d, 0x3C)[0]
    end = segs[2][1] + segs[2][2] + bss
    img = bytearray(end)
    for i, (off, loc, size) in enumerate(segs):
        raw = d[off:off + (csizes[i] if flags >> i & 1 else size)]
        img[loc:loc + size] = lz4_block_decompress(raw, size) if flags >> i & 1 else raw
    return bytes(img)


def parse(img):
    mod0 = struct.unpack_from("<I", img, 4)[0]
    assert img[mod0:mod0 + 4] == b"MOD0"
    dyn = mod0 + struct.unpack_from("<i", img, mod0 + 4)[0]
    tags = {}
    i = dyn
    while True:
        tag, val = struct.unpack_from("<qQ", img, i)
        if tag == DT_NULL:
            break
        tags.setdefault(tag, val)
        i += 16
    strtab, symtab = tags[DT_STRTAB], tags[DT_SYMTAB]

    def name(idx):
        st_name = struct.unpack_from("<I", img, symtab + idx * 24)[0]
        e = img.index(b"\0", strtab + st_name)
        return img[strtab + st_name:e].decode(errors="replace")

    syms = []
    idx = 1
    # The symbol table directly precedes the string table in these modules.
    while symtab + (idx + 1) * 24 <= strtab:
        st_name, st_info, st_other, st_shndx, st_value, st_size = struct.unpack_from("<IBBHQQ", img, symtab + idx * 24)
        if st_value and st_shndx:
            syms.append((st_value, st_size, name(idx)))
        idx += 1
    syms.sort()
    got = {}
    for start, size in ((tags.get(DT_JMPREL), tags.get(DT_PLTRELSZ)), (tags.get(DT_RELA), tags.get(DT_RELASZ))):
        if not start:
            continue
        for j in range(start, start + size, 24):
            r_off, r_info, r_add = struct.unpack_from("<QQq", img, j)
            sym = r_info >> 32
            if sym:
                got[r_off] = name(sym)
    return syms, got


def main():
    img = load_image(sys.argv[1])
    syms, got = parse(img)
    mode, args = sys.argv[2], sys.argv[3:]
    if mode == "addr":
        for a in args:
            a = int(a, 16)
            best = None
            for v, s, n in syms:
                if v <= a:
                    best = (v, s, n)
                else:
                    break
            print(f"{a:#x}: " + (f"{best[2]}+{a - best[0]:#x}" if best else "?"))
    elif mode == "got":
        for a in args:
            a = int(a, 16)
            print(f"{a:#x}: {got.get(a, '?')}")
    elif mode == "find":
        for v, s, n in syms:
            if args[0] in n:
                print(f"{v:#x} {s:#x} {n}")


if __name__ == "__main__":
    main()