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 xml.etree.ElementTree as ET
11from test_gdbstub import main, report
12
13
14initial_vlen = 0
15
16
17def fetch_xml_regmap():
18    """
19    Iterate through the XML descriptions and validate.
20
21    We check for any duplicate registers and report them. Return a
22    reg_map hash containing the names, regnums and initial values of
23    all registers.
24    """
25
26    # First check the XML descriptions we have sent. Most arches
27    # support XML but a few of the ancient ones don't in which case we
28    # need to gracefully fail.
29
30    try:
31        xml = gdb.execute("maint print xml-tdesc", False, True)
32    except (gdb.error):
33        print("SKIP: target does not support XML")
34        return None
35
36    total_regs = 0
37    reg_map = {}
38
39    tree = ET.fromstring(xml)
40    for f in tree.findall("feature"):
41        name = f.attrib["name"]
42        regs = f.findall("reg")
43
44        total = len(regs)
45        total_regs += total
46        base = int(regs[0].attrib["regnum"])
47        top = int(regs[-1].attrib["regnum"])
48
49        print(f"feature: {name} has {total} registers from {base} to {top}")
50
51        for r in regs:
52            name = r.attrib["name"]
53            regnum = int(r.attrib["regnum"])
54
55            entry = { "name": name, "regnum": regnum }
56
57            if name in reg_map:
58                report(False, f"duplicate register {entry} vs {reg_map[name]}")
59                continue
60
61            reg_map[name] = entry
62
63    # Validate we match
64    report(total_regs == len(reg_map.keys()),
65           f"counted all {total_regs} registers in XML")
66
67    return reg_map
68
69
70def get_register_by_regnum(reg_map, regnum):
71    """
72    Helper to find a register from the map via its XML regnum
73    """
74    for regname, entry in reg_map.items():
75        if entry['regnum'] == regnum:
76            return entry
77    return None
78
79
80def crosscheck_remote_xml(reg_map):
81    """
82    Cross-check the list of remote-registers with the XML info.
83    """
84
85    remote = gdb.execute("maint print remote-registers", False, True)
86    r_regs = remote.split("\n")
87
88    total_regs = len(reg_map.keys())
89    total_r_regs = 0
90    total_r_elided_regs = 0
91
92    for r in r_regs:
93        r = r.replace("long long", "long_long")
94        r = r.replace("long double", "long_double")
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            # Some registers are "hidden" so don't have a name
104            # although they still should have a register number
105            if r_name == "''":
106                total_r_elided_regs += 1
107                x_reg = get_register_by_regnum(reg_map, r_regnum)
108                if x_reg is not None:
109                    x_reg["hidden"] = True
110                continue
111
112            # check in the XML
113            try:
114                x_reg = reg_map[r_name]
115            except KeyError:
116                report(False, f"{r_name} not in XML description")
117                continue
118
119            x_reg["seen"] = True
120            x_regnum = x_reg["regnum"]
121            if r_regnum != x_regnum:
122                report(False, f"{r_name} {r_regnum} == {x_regnum} (xml)")
123            else:
124                total_r_regs += 1
125
126    report(total_regs == total_r_regs + total_r_elided_regs,
127           "All XML Registers accounted for")
128
129    print(f"xml-tdesc has {total_regs} registers")
130    print(f"remote-registers has {total_r_regs} registers")
131    print(f"of which {total_r_elided_regs} are hidden")
132
133    for x_key in reg_map.keys():
134        x_reg = reg_map[x_key]
135        if "hidden" in x_reg:
136            print(f"{x_reg} elided by gdb")
137        elif "seen" not in x_reg:
138            print(f"{x_reg} wasn't seen in remote-registers")
139
140
141def initial_register_read(reg_map):
142    """
143    Do an initial read of all registers that we know gdb cares about
144    (so ignore the elided ones).
145    """
146    frame = gdb.selected_frame()
147
148    for e in reg_map.values():
149        name = e["name"]
150        regnum = e["regnum"]
151
152        try:
153            if "hidden" in e:
154                value = frame.read_register(regnum)
155                e["initial"] = value
156            elif "seen" in e:
157                value = frame.read_register(name)
158                e["initial"] = value
159
160        except ValueError:
161                report(False, f"failed to read reg: {name}")
162
163
164def complete_and_diff(reg_map):
165    """
166    Let the program run to (almost) completion and then iterate
167    through all the registers we know about and report which ones have
168    changed.
169    """
170    # Let the program get to the end and we can check what changed
171    b = gdb.Breakpoint("_exit")
172    if b.pending: # workaround Microblaze weirdness
173        b.delete()
174        gdb.Breakpoint("_Exit")
175
176    gdb.execute("continue")
177
178    frame = gdb.selected_frame()
179    changed = 0
180
181    for e in reg_map.values():
182        if "initial" in e and "hidden" not in e:
183            name = e["name"]
184            old_val = e["initial"]
185
186            try:
187                new_val = frame.read_register(name)
188            except ValueError:
189                report(False, f"failed to read {name} at end of run")
190                continue
191
192            if new_val != old_val:
193                print(f"{name} changes from {old_val} to {new_val}")
194                changed += 1
195
196    # as long as something changed we can be confident its working
197    report(changed > 0, f"{changed} registers were changed")
198
199
200def run_test():
201    "Run through the tests"
202
203    reg_map = fetch_xml_regmap()
204
205    if reg_map is not None:
206        crosscheck_remote_xml(reg_map)
207        initial_register_read(reg_map)
208        complete_and_diff(reg_map)
209
210
211main(run_test)
212