summaryrefslogtreecommitdiffstats
path: root/tools/guest-diag/gpa.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/guest-diag/gpa.py')
-rwxr-xr-xtools/guest-diag/gpa.py82
1 files changed, 82 insertions, 0 deletions
diff --git a/tools/guest-diag/gpa.py b/tools/guest-diag/gpa.py
new file mode 100755
index 0000000..61ba27b
--- /dev/null
+++ b/tools/guest-diag/gpa.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright (c) 2026 Open-IOV contributors
+# gpa.py - translate a guest virtual address to a guest physical address via
+# the QEMU monitor's HMP `gva2gpa`, reusing the QMP client in semfix_watch.py.
+#
+# Usage: sudo python3 tools/guest-diag/gpa.py <gva_hex> [--offset]
+# <gva_hex> guest VA, e.g. 0xffffc90000000000 or 0x7f...
+# --offset also print the /dev/shm/vgpu-guest-ram file offset
+# (gpa - 0x80000000)
+#
+# The monitor's `gva2gpa` translates with the selected CPU's CR3; while a CPU
+# runs user code under PTI the kernel half is hidden, so retry across CPUs
+# exactly like semfix_watch.Qmp.gva2gpa does. On failure the raw monitor text
+# is printed so the operator can see the reason (unmapped VA, no guest, ...).
+import argparse
+import os
+import re
+import sys
+
+HERE = os.path.dirname(os.path.abspath(__file__))
+sys.path.insert(0, HERE)
+from semfix_watch import Qmp # noqa: E402
+
+QMP_SOCK = os.environ.get("QMP_SOCK", "/tmp/qemu-qmp.sock")
+RAM_BASE = 0x80000000
+
+
+def parse_va(text):
+ text = text.strip()
+ try:
+ return int(text, 0)
+ except ValueError:
+ return int(text, 16)
+
+
+def translate(q, va):
+ """Return (gpa, raw_hmp_text) or (None, last_raw_text)."""
+ last = ""
+ for cpu in q._cpu_order():
+ q._set_cpu(cpu)
+ out = q.hmp("gva2gpa 0x%x" % va)
+ last = out.strip()
+ m = re.search(r"gpa:\s*0x([0-9a-fA-F]+)", out)
+ if m:
+ return int(m.group(1), 16), last
+ return None, last
+
+
+def main():
+ ap = argparse.ArgumentParser(add_help=True)
+ ap.add_argument("gva", help="guest virtual address (hex, 0x prefix optional)")
+ ap.add_argument("--offset", action="store_true",
+ help="also print gpa - 0x80000000 (guest RAM file offset)")
+ ap.add_argument("--qmp", default=QMP_SOCK, help="QMP socket path")
+ args = ap.parse_args()
+
+ va = parse_va(args.gva)
+ if not os.path.exists(args.qmp):
+ print("gpa: QMP socket not found: %s" % args.qmp, file=sys.stderr)
+ return 2
+ try:
+ q = Qmp(args.qmp)
+ except Exception as exc: # noqa: BLE001 - diagnostic surface
+ print("gpa: cannot connect to %s: %s" % (args.qmp, exc), file=sys.stderr)
+ return 2
+
+ gpa, text = translate(q, va)
+ if gpa is None:
+ print("gpa: gva2gpa 0x%x failed" % va, file=sys.stderr)
+ if text:
+ print(text, file=sys.stderr)
+ return 1
+
+ print("GPA 0x%x" % gpa)
+ if args.offset:
+ print("OFFSET 0x%x" % (gpa - RAM_BASE))
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())