| # 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) |