aboutsummaryrefslogtreecommitdiff
path: root/tests/tcg/multiarch/gdbstub/registers.py
blob: b3d13cb077f1bb43fe6c2f1afd9a9dd20c3119d9 (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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# Exercise the register functionality by exhaustively iterating
# through all supported registers on the system.
#
# This is launched via tests/guest-debug/run-test.py but you can also
# call it directly if using it for debugging/introspection:
#
# SPDX-License-Identifier: GPL-2.0-or-later

import gdb
import xml.etree.ElementTree as ET
from test_gdbstub import main, report


initial_vlen = 0


def fetch_xml_regmap():
    """
    Iterate through the XML descriptions and validate.

    We check for any duplicate registers and report them. Return a
    reg_map hash containing the names, regnums and initial values of
    all registers.
    """

    # First check the XML descriptions we have sent. Most arches
    # support XML but a few of the ancient ones don't in which case we
    # need to gracefully fail.

    try:
        xml = gdb.execute("maint print xml-tdesc", False, True)
    except (gdb.error):
        print("SKIP: target does not support XML")
        return None

    total_regs = 0
    reg_map = {}

    tree = ET.fromstring(xml)
    for f in tree.findall("feature"):
        name = f.attrib["name"]
        regs = f.findall("reg")

        total = len(regs)
        total_regs += total
        base = int(regs[0].attrib["regnum"])
        top = int(regs[-1].attrib["regnum"])

        print(f"feature: {name} has {total} registers from {base} to {top}")

        for r in regs:
            name = r.attrib["name"]
            regnum = int(r.attrib["regnum"])

            entry = { "name": name, "regnum": regnum }

            if name in reg_map:
                report(False, f"duplicate register {entry} vs {reg_map[name]}")
                continue

            reg_map[name] = entry

    # Validate we match
    report(total_regs == len(reg_map.keys()),
           f"counted all {total_regs} registers in XML")

    return reg_map


def get_register_by_regnum(reg_map, regnum):
    """
    Helper to find a register from the map via its XML regnum
    """
    for regname, entry in reg_map.items():
        if entry['regnum'] == regnum:
            return entry
    return None


def crosscheck_remote_xml(reg_map):
    """
    Cross-check the list of remote-registers with the XML info.
    """

    remote = gdb.execute("maint print remote-registers", False, True)
    r_regs = remote.split("\n")

    total_regs = len(reg_map.keys())
    total_r_regs = 0
    total_r_elided_regs = 0

    for r in r_regs:
        r = r.replace("long long", "long_long")
        r = r.replace("long double", "long_double")
        fields = r.split()
        # Some of the registers reported here are "pseudo" registers that
        # gdb invents based on actual registers so we need to filter them
        # out.
        if len(fields) == 8:
            r_name = fields[0]
            r_regnum = int(fields[6])

            # Some registers are "hidden" so don't have a name
            # although they still should have a register number
            if r_name == "''":
                total_r_elided_regs += 1
                x_reg = get_register_by_regnum(reg_map, r_regnum)
                if x_reg is not None:
                    x_reg["hidden"] = True
                continue

            # check in the XML
            try:
                x_reg = reg_map[r_name]
            except KeyError:
                report(False, f"{r_name} not in XML description")
                continue

            x_reg["seen"] = True
            x_regnum = x_reg["regnum"]
            if r_regnum != x_regnum:
                report(False, f"{r_name} {r_regnum} == {x_regnum} (xml)")
            else:
                total_r_regs += 1

    report(total_regs == total_r_regs + total_r_elided_regs,
           "All XML Registers accounted for")

    print(f"xml-tdesc has {total_regs} registers")
    print(f"remote-registers has {total_r_regs} registers")
    print(f"of which {total_r_elided_regs} are hidden")

    for x_key in reg_map.keys():
        x_reg = reg_map[x_key]
        if "hidden" in x_reg:
            print(f"{x_reg} elided by gdb")
        elif "seen" not in x_reg:
            print(f"{x_reg} wasn't seen in remote-registers")


def initial_register_read(reg_map):
    """
    Do an initial read of all registers that we know gdb cares about
    (so ignore the elided ones).
    """
    frame = gdb.selected_frame()

    for e in reg_map.values():
        name = e["name"]
        regnum = e["regnum"]

        try:
            if "hidden" in e:
                value = frame.read_register(regnum)
                e["initial"] = value
            elif "seen" in e:
                value = frame.read_register(name)
                e["initial"] = value

        except ValueError:
                report(False, f"failed to read reg: {name}")


def complete_and_diff(reg_map):
    """
    Let the program run to (almost) completion and then iterate
    through all the registers we know about and report which ones have
    changed.
    """
    # Let the program get to the end and we can check what changed
    b = gdb.Breakpoint("_exit")
    if b.pending: # workaround Microblaze weirdness
        b.delete()
        gdb.Breakpoint("_Exit")

    gdb.execute("continue")

    frame = gdb.selected_frame()
    changed = 0

    for e in reg_map.values():
        if "initial" in e and "hidden" not in e:
            name = e["name"]
            old_val = e["initial"]

            try:
                new_val = frame.read_register(name)
            except ValueError:
                report(False, f"failed to read {name} at end of run")
                continue

            if new_val != old_val:
                print(f"{name} changes from {old_val} to {new_val}")
                changed += 1

    # as long as something changed we can be confident its working
    report(changed > 0, f"{changed} registers were changed")


def run_test():
    "Run through the tests"

    reg_map = fetch_xml_regmap()

    if reg_map is not None:
        crosscheck_remote_xml(reg_map)
        initial_register_read(reg_map)
        complete_and_diff(reg_map)


main(run_test)