summaryrefslogtreecommitdiffstats
path: root/tools/guest-diag/gpa.py
blob: 61ba27b54a463d5e2f5091ba0c82eb67e7dbf5ac (plain)
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
#!/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())