xref: /qemu/tests/tcg/multiarch/gdbstub/registers.py (revision efade66d)
1# Exercise the register functionality by exhaustively iterating
2# through all supported registers on the system.
3#
4# This is launched via tests/guest-debug/run-test.py but you can also
5# call it directly if using it for debugging/introspection:
6#
7# SPDX-License-Identifier: GPL-2.0-or-later
8
9import gdb
10import sys
11import xml.etree.ElementTree as ET
12
13initial_vlen = 0
14failcount = 0
15
16def report(cond, msg):
17    "Report success/fail of test."
18    if cond:
19        print("PASS: %s" % (msg))
20    else:
21        print("FAIL: %s" % (msg))
22        global failcount
23        failcount += 1
24
25
26def fetch_xml_regmap():
27    """
28    Iterate through the XML descriptions and validate.
29
30    We check for any duplicate registers and report them. Return a
31    reg_map hash containing the names, regnums and initial values of
32    all registers.
33    """
34
35    # First check the XML descriptions we have sent. Most arches
36    # support XML but a few of the ancient ones don't in which case we
37    # need to gracefully fail.
38
39    try:
40        xml = gdb.execute("maint print xml-tdesc", False, True)
41    except (gdb.error):
42        print("SKIP: target does not support XML")
43        return None
44
45    total_regs = 0
46    reg_map = {}
47
48    tree = ET.fromstring(xml)
49    for f in tree.findall("feature"):
50        name = f.attrib["name"]
51        regs = f.findall("reg")
52
53        total = len(regs)
54        total_regs += total
55        base = int(regs[0].attrib["regnum"])
56        top = int(regs[-1].attrib["regnum"])
57
58        print(f"feature: {name} has {total} registers from {base} to {top}")
59
60        for r in regs:
61            name = r.attrib["name"]
62            regnum = int(r.attrib["regnum"])
63
64            entry = { "name": name, "regnum": regnum }
65
66            if name in reg_map:
67                report(False, f"duplicate register {entry} vs {reg_map[name]}")
68                continue
69
70            reg_map[name] = entry
71
72    # Validate we match
73    report(total_regs == len(reg_map.keys()),
74           f"counted all {total_regs} registers in XML")
75
76    return reg_map
77
78def get_register_by_regnum(reg_map, regnum):
79    """
80    Helper to find a register from the map via its XML regnum
81    """
82    for regname, entry in reg_map.items():
83        if entry['regnum'] == regnum:
84            return entry
85    return None
86
87def crosscheck_remote_xml(reg_map):
88    """
89    Cross-check the list of remote-registers with the XML info.
90    """
91
92    remote = gdb.execute("maint print remote-registers", False, True)
93    r_regs = remote.split("\n")
94
95    total_regs = len(reg_map.keys())
96    total_r_regs = 0
97    total_r_elided_regs = 0
98
99    for r in r_regs:
100        r = r.replace("long long", "long_long")
101        r = r.replace("long double", "long_double")
102        fields = r.split()
103        # Some of the registers reported here are "pseudo" registers that
104        # gdb invents based on actual registers so we need to filter them
105        # out.
106        if len(fields) == 8:
107            r_name = fields[0]
108            r_regnum = int(fields[6])
109
110            # Some registers are "hidden" so don't have a name
111            # although they still should have a register number
112            if r_name == "''":
113                total_r_elided_regs += 1
114                x_reg = get_register_by_regnum(reg_map, r_regnum)
115                if x_reg is not None:
116                    x_reg["hidden"] = True
117                continue
118
119            # check in the XML
120            try:
121                x_reg = reg_map[r_name]
122            except KeyError:
123                report(False, f"{r_name} not in XML description")
124                continue
125
126            x_reg["seen"] = True
127            x_regnum = x_reg["regnum"]
128            if r_regnum != x_regnum:
129                report(False, f"{r_name} {r_regnum} == {x_regnum} (xml)")
130            else:
131                total_r_regs += 1
132
133    report(total_regs == total_r_regs + total_r_elided_regs,
134           "All XML Registers accounted for")
135
136    print(f"xml-tdesc has {total_regs} registers")
137    print(f"remote-registers has {total_r_regs} registers")
138    print(f"of which {total_r_elided_regs} are hidden")
139
140    for x_key in reg_map.keys():
141        x_reg = reg_map[x_key]
142        if "hidden" in x_reg:
143            print(f"{x_reg} elided by gdb")
144        elif "seen" not in x_reg:
145            print(f"{x_reg} wasn't seen in remote-registers")
146
147def initial_register_read(reg_map):
148    """
149    Do an initial read of all registers that we know gdb cares about
150    (so ignore the elided ones).
151    """
152    frame = gdb.selected_frame()
153
154    for e in reg_map.values():
155        name = e["name"]
156        regnum = e["regnum"]
157
158        try:
159            if "hidden" in e:
160                value = frame.read_register(regnum)
161                e["initial"] = value
162            elif "seen" in e:
163                value = frame.read_register(name)
164                e["initial"] = value
165
166        except ValueError:
167                report(False, f"failed to read reg: {name}")
168
169
170def complete_and_diff(reg_map):
171    """
172    Let the program run to (almost) completion and then iterate
173    through all the registers we know about and report which ones have
174    changed.
175    """
176    # Let the program get to the end and we can check what changed
177    b = gdb.Breakpoint("_exit")
178    if b.pending: # workaround Microblaze weirdness
179        b.delete()
180        gdb.Breakpoint("_Exit")
181
182    gdb.execute("continue")
183
184    frame = gdb.selected_frame()
185    changed = 0
186
187    for e in reg_map.values():
188        if "initial" in e and "hidden" not in e:
189            name = e["name"]
190            old_val = e["initial"]
191
192            try:
193                new_val = frame.read_register(name)
194            except ValueError:
195                report(False, f"failed to read {name} at end of run")
196                continue
197
198            if new_val != old_val:
199                print(f"{name} changes from {old_val} to {new_val}")
200                changed += 1
201
202    # as long as something changed we can be confident its working
203    report(changed > 0, f"{changed} registers were changed")
204
205
206def run_test():
207    "Run through the tests"
208
209    reg_map = fetch_xml_regmap()
210
211    if reg_map is not None:
212        crosscheck_remote_xml(reg_map)
213        initial_register_read(reg_map)
214        complete_and_diff(reg_map)
215
216
217#
218# This runs as the script it sourced (via -x, via run-test.py)
219#
220try:
221    inferior = gdb.selected_inferior()
222    arch = inferior.architecture()
223    print("ATTACHED: %s" % arch.name())
224except (gdb.error, AttributeError):
225    print("SKIPPING (not connected)", file=sys.stderr)
226    exit(0)
227
228if gdb.parse_and_eval('$pc') == 0:
229    print("SKIP: PC not set")
230    exit(0)
231
232try:
233    run_test()
234except (gdb.error):
235    print ("GDB Exception: %s" % (sys.exc_info()[0]))
236    failcount += 1
237    pass
238
239print("All tests complete: %d failures" % failcount)
240exit(failcount)
241