summaryrefslogtreecommitdiffstats
path: root/tools/guest-diag/ram_content_pin.py
blob: d521d9197669b84207c9233efb67374f54d4c62c (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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#!/usr/bin/env python3
"""Host-side CONTENT-based resolver for demanded GR setup-push windows.

SESSION 153-CONTINUED-66. The client's GL setup pushes (and its 0x3200000 /
0x6f00000 macro-upload windows) live in allocations that have NO kernel vmap
and NO client CPU mapping on the failing boots: the guest scanner's area+idx
identity sweep and its client-mem sweep both fail (`strict unknown-family
client-mem failed`, `strict parse failed (N B, M windows tried)`), so the
armed GR setup hold never lands its pin and the MME prelude stays 0.

The bytes ARE in guest RAM - a RAM-wide pattern scan finds the MME/SA
prelude runs (`0xa0100045` = INC 0x114 x16 args, `0x20020047` = INC 0x11c x2
args). This tool therefore resolves a demanded window from CONTENT instead of
identity:

  1. read the demand set the service publishes (debugfs sema_setup_demand:
     "0xVA 0xOFF LEN" per line);
  2. locate every physical page in the guest RAM image that carries an
     MME/SA prelude run signature;
  3. for each candidate physical page P and each window page index j, read
     the EXACT window bytes the host would read - [P - j*0x1000 + OFF, +LEN)
     - straight out of the RAM image and run the host's own method-stream
     acceptance on them;
  4. inject the validated page run into the service through the
     sema_map_add module parameter (0xVA,0xPA,LEN,1), so the host read
     resolves the demanded window from real content.

Nothing is injected unless the host's parser accepts the exact bytes, so a
wrong candidate cannot mis-resolve a demand VA (the r1120 lesson).

Usage: ram_content_pin.py [ram-image] [seconds]
"""
import importlib.util
import mmap
import os
import re
import subprocess
import sys
import time

import numpy as np

RAM = sys.argv[1] if len(sys.argv) > 1 else '/dev/shm/vgpu-guest-ram'
SECS = float(sys.argv[2]) if len(sys.argv) > 2 else 120.0
DEMAND = '/sys/kernel/debug/vgsp/sema_setup_demand'
MAPADD = '/sys/module/vgsp/parameters/sema_map_add'
GPS = ('/home/rmcore/Git/integdev_gpu_drv/open-iov/tools/guest-diag/'
       'guest_pin_stream.py')
SIGS = (0xa0100045, 0x20020047)   # INC 0x114 x16, INC 0x11c x2
PAGE = 0x1000
MAXHITS = 4096


def log(msg):
    print('[%s] %s' % (time.strftime('%H:%M:%S'), msg), flush=True)


def load_accept():
    spec = importlib.util.spec_from_file_location('gps', GPS)
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod._window_accept


def read_demand():
    out = []
    try:
        with open(DEMAND) as f:
            for ln in f:
                m = re.match(r'0x([0-9a-fA-F]+)\s+0x([0-9a-fA-F]+)\s+(\d+)',
                             ln.strip())
                if not m:
                    continue
                out.append((int(m.group(1), 16), int(m.group(2), 16),
                            int(m.group(3))))
    except OSError:
        pass
    return out


def inject(va_page, pa_page, ln):
    cmd = "printf '0x%x,0x%x,0x%x,1' > %s" % (va_page, pa_page, ln, MAPADD)
    try:
        r = subprocess.run(['sudo', '-n', 'sh', '-c', cmd],
                           capture_output=True, text=True, timeout=10)
    except subprocess.TimeoutExpired:
        return False
    return r.returncode == 0


def main():
    accept = load_accept()
    f = open(RAM, 'rb')
    mm = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ)
    size = len(mm)
    a32 = np.frombuffer(mm, dtype=np.uint32)
    hits = []
    for sig in SIGS:
        idx = np.flatnonzero(a32 == sig)
        log('sig 0x%08x: %d hit(s)' % (sig, idx.size))
        for i in idx[:MAXHITS]:
            hits.append(int(i) * 4)
    del a32
    hits = sorted(set(h >> 12 for h in hits))
    log('candidate physical pages: %d (ram image %d MB)'
        % (len(hits), size >> 20))
    if not hits:
        return 1
    seen = set()
    t0 = time.time()
    while time.time() - t0 < SECS:
        dem = read_demand()
        for (va, off, ln) in dem:
            key = (va & ~(PAGE - 1), ln)
            if key in seen:
                continue
            if ln <= 0 or ln > 0x40000:
                continue
            need = (off + ln + PAGE - 1) // PAGE
            if need > 64:
                continue
            va_page = va & ~(PAGE - 1)
            found = None
            for P in hits:
                for j in range(need):
                    P0 = P - j * PAGE
                    if P0 < 0 or P0 + off + ln > size:
                        continue
                    buf = mm[P0 + off:P0 + off + ln]
                    try:
                        ok, mode = accept(buf, ln)
                    except Exception:
                        ok = False
                    if ok:
                        found = (P0, mode)
                        break
                if found:
                    break
            if not found:
                log('demand va=0x%x off=0x%x len=%d: no content candidate'
                    % (va, off, ln))
                seen.add(key)
                continue
            P0, mode = found
            if inject(va_page, P0, need * PAGE):
                log('demand va=0x%x off=0x%x len=%d -> pa=0x%x (%s parse, '
                    'pages=%d) INJECTED'
                    % (va, off, ln, P0, mode, need))
                seen.add(key)
            else:
                log('demand va=0x%x: injection refused by the module param'
                    % va)
                seen.add(key)
        time.sleep(1.0)
    log('done')
    return 0


if __name__ == '__main__':
    sys.exit(main())