| 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 | |
| 9 | import gdb |
| 10 | import xml.etree.ElementTree as ET |
| 11 | from test_gdbstub import main, report |
| 12 | |
| 13 | |
| 14 | initial_vlen = 0 |
| 15 | |
| 16 | |
| 17 | def 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 | |
| 70 | def 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 | |
| 80 | def 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 | |
| 141 | def 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 | |
| 164 | def 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 | |
| 200 | def 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 | |
| 211 | main(run_test) |