xref: /qemu/tests/tcg/multiarch/gdbstub/registers.py (revision ec6f3fc3)
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    frame = gdb.selected_frame()
48
49    tree = ET.fromstring(xml)
50    for f in tree.findall("feature"):
51        name = f.attrib["name"]
52        regs = f.findall("reg")
53
54        total = len(regs)
55        total_regs += total
56        base = int(regs[0].attrib["regnum"])
57        top = int(regs[-1].attrib["regnum"])
58
59        print(f"feature: {name} has {total} registers from {base} to {top}")
60
61        for r in regs:
62            name = r.attrib["name"]
63            regnum = int(r.attrib["regnum"])
64            try:
65                value = frame.read_register(name)
66            except ValueError:
67                report(False, f"failed to read reg: {name}")
68
69            entry = { "name": name, "initial": value, "regnum": regnum }
70
71            if name in reg_map:
72                report(False, f"duplicate register {entry} vs {reg_map[name]}")
73                continue
74
75            reg_map[name] = entry
76
77    # Validate we match
78    report(total_regs == len(reg_map.keys()),
79           f"counted all {total_regs} registers in XML")
80
81    return reg_map
82
83def crosscheck_remote_xml(reg_map):
84    """
85    Cross-check the list of remote-registers with the XML info.
86    """
87
88    remote = gdb.execute("maint print remote-registers", False, True)
89    r_regs = remote.split("\n")
90
91    total_regs = len(reg_map.keys())
92    total_r_regs = 0
93
94    for r in r_regs:
95        fields = r.split()
96        # Some of the registers reported here are "pseudo" registers that
97        # gdb invents based on actual registers so we need to filter them
98        # out.
99        if len(fields) == 8:
100            r_name = fields[0]
101            r_regnum = int(fields[6])
102
103            # check in the XML
104            try:
105                x_reg = reg_map[r_name]
106            except KeyError:
107                report(False, f"{r_name} not in XML description")
108                continue
109
110            x_reg["seen"] = True
111            x_regnum = x_reg["regnum"]
112            if r_regnum != x_regnum:
113                report(False, f"{r_name} {r_regnum} == {x_regnum} (xml)")
114            else:
115                total_r_regs += 1
116
117    # Just print a mismatch in totals as gdb will filter out 64 bit
118    # registers on a 32 bit machine. Also print what is missing to
119    # help with debug.
120    if total_regs != total_r_regs:
121        print(f"xml-tdesc has ({total_regs}) registers")
122        print(f"remote-registers has ({total_r_regs}) registers")
123
124        for x_key in reg_map.keys():
125            x_reg = reg_map[x_key]
126            if "seen" not in x_reg:
127                print(f"{x_reg} wasn't seen in remote-registers")
128
129def complete_and_diff(reg_map):
130    """
131    Let the program run to (almost) completion and then iterate
132    through all the registers we know about and report which ones have
133    changed.
134    """
135    # Let the program get to the end and we can check what changed
136    b = gdb.Breakpoint("_exit")
137    if b.pending: # workaround Microblaze weirdness
138        b.delete()
139        gdb.Breakpoint("_Exit")
140
141    gdb.execute("continue")
142
143    frame = gdb.selected_frame()
144    changed = 0
145
146    for e in reg_map.values():
147        name = e["name"]
148        old_val = e["initial"]
149
150        try:
151            new_val = frame.read_register(name)
152        except:
153            report(False, f"failed to read {name} at end of run")
154            continue
155
156        if new_val != old_val:
157            print(f"{name} changes from {old_val} to {new_val}")
158            changed += 1
159
160    # as long as something changed we can be confident its working
161    report(changed > 0, f"{changed} registers were changed")
162
163
164def run_test():
165    "Run through the tests"
166
167    reg_map = fetch_xml_regmap()
168
169    if reg_map is not None:
170        crosscheck_remote_xml(reg_map)
171        complete_and_diff(reg_map)
172
173
174#
175# This runs as the script it sourced (via -x, via run-test.py)
176#
177try:
178    inferior = gdb.selected_inferior()
179    arch = inferior.architecture()
180    print("ATTACHED: %s" % arch.name())
181except (gdb.error, AttributeError):
182    print("SKIPPING (not connected)", file=sys.stderr)
183    exit(0)
184
185if gdb.parse_and_eval('$pc') == 0:
186    print("SKIP: PC not set")
187    exit(0)
188
189try:
190    run_test()
191except (gdb.error):
192    print ("GDB Exception: %s" % (sys.exc_info()[0]))
193    failcount += 1
194    pass
195
196print("All tests complete: %d failures" % failcount)
197exit(failcount)
198