diff options
Diffstat (limited to 'tests/tcg/multiarch/gdbstub/registers.py')
-rw-r--r-- | tests/tcg/multiarch/gdbstub/registers.py | 211 |
1 files changed, 211 insertions, 0 deletions
diff --git a/tests/tcg/multiarch/gdbstub/registers.py b/tests/tcg/multiarch/gdbstub/registers.py new file mode 100644 index 0000000000..b3d13cb077 --- /dev/null +++ b/tests/tcg/multiarch/gdbstub/registers.py @@ -0,0 +1,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) |