From 5da171e8766184678be8204a2295e9b52be3bf9c Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Tue, 27 Oct 2026 12:58:04 -0400 Subject: Add NX2_NOTES.md and Switch 2 analysis helpers npdm_info.py dumps META/ACI0 and kernel caps; nso_dis.py disassembles part of an NSO with clang + llvm-objdump. --- tools/nx2/.gitignore | 1 + tools/nx2/npdm_info.py | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++ tools/nx2/nso_dis.py | 26 +++++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 tools/nx2/.gitignore create mode 100644 tools/nx2/npdm_info.py create mode 100644 tools/nx2/nso_dis.py (limited to 'tools') diff --git a/tools/nx2/.gitignore b/tools/nx2/.gitignore new file mode 100644 index 0000000000..c18dd8d83c --- /dev/null +++ b/tools/nx2/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/tools/nx2/npdm_info.py b/tools/nx2/npdm_info.py new file mode 100644 index 0000000000..20fe713163 --- /dev/null +++ b/tools/nx2/npdm_info.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# Print the META header and kernel capabilities of a main.npdm (read only). +import struct +import sys + +d = open(sys.argv[1], "rb").read() +assert d[:4] == b"META" +flags = d[0xC] +print(f"META flags=0x{flags:02x} is64={flags & 1} address_space_type={(flags >> 1) & 7} " + f"prio={d[0xE]} core={d[0xF]} sys_res=0x{struct.unpack_from('> 4 & 0x3F, cap >> 10 & 0x3F + cl, ch = cap >> 16 & 0xFF, cap >> 24 + print(f" ThreadInfo prio {lo}..{hi} cores {cl}..{ch}") + elif t == 4: + idx, mask = cap >> 29, cap >> 5 & 0xFFFFFF + for b in range(24): + if mask >> b & 1: + svcs.add(idx * 24 + b) + elif t == 6: + print(f" MapRange 0x{cap:08x}") + elif t == 7: + print(f" MapIoPage 0x{cap:08x}") + elif t == 10: + print(f" MapRegion 0x{cap:08x}") + elif t == 11: + print(f" EnableInterrupts 0x{cap:08x}") + elif t == 13: + print(f" ProgramType {cap >> 14 & 7}") + elif t == 14: + print(f" KernelVersion {cap >> 19 & 0x1FFF}.{cap >> 15 & 0xF}") + elif t == 15: + print(f" HandleTableSize {cap >> 16 & 0x3FF}") + elif t == 16: + print(f" DebugFlags 0x{cap:08x}") + else: + print(f" unknown cap type {t}: 0x{cap:08x}") +print("SVCs:", " ".join(f"{s:02x}" for s in sorted(svcs))) diff --git a/tools/nx2/nso_dis.py b/tools/nx2/nso_dis.py new file mode 100644 index 0000000000..5e899b0264 --- /dev/null +++ b/tools/nx2/nso_dis.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# Disassemble a range of an NSO's .text using clang + llvm-objdump. +# Usage: nso_dis.py +# The NSO is only read; scratch files go to . +import subprocess +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from a64_coverage import nso_text # noqa: E402 + +llvm, nso, off, count, work = sys.argv[1], sys.argv[2], int(sys.argv[3], 16), int(sys.argv[4]), Path(sys.argv[5]) +work.mkdir(parents=True, exist_ok=True) +text = nso_text(nso) +words = [int.from_bytes(text[i:i + 4], "little") for i in range(off, off + count * 4, 4)] +# .inst (not .incbin) so the bytes get a code mapping symbol and objdump decodes them. +(work / "range.s").write_text(".text\n" + "".join(f".inst 0x{w:08x}\n" for w in words)) +subprocess.run([str(Path(llvm) / "clang.exe"), "--target=aarch64-none-elf", "-c", "range.s", "-o", "range.o"], + cwd=work, check=True) +out = subprocess.run([str(Path(llvm) / "llvm-objdump.exe"), "-d", "-j", ".text", "--no-show-raw-insn", + "--mattr=+v8.5a,+mte,+lse,+rcpc,+pauth,+crc,+sha2,+dotprod,+fullfp16", "range.o"], + cwd=work, capture_output=True, text=True, check=True).stdout +for line in out.splitlines(): + parts = line.split(":", 1) + if len(parts) == 2 and parts[0].strip() and all(c in "0123456789abcdef" for c in parts[0].strip()): + print(f"{off + int(parts[0], 16):08x}:{parts[1].strip()}") -- cgit v1.2.3