Просмотр исходного кода

Debug: update PyCortexMDebug to latest and refactor (#574)

* Debug: update PyCortexDebug to latest and refactor.
* Debug: format sources. Dockerfile: add missing dependency. Make: switch to gdb-py.
* Debug: port PyCortexMDebug to python2
* Docker: add missing debug dependencies
* Debug: cleanup local include in svd_gdb.py
あく 4 лет назад
Родитель
Сommit
5ae3d60101

+ 8 - 0
debug/PyCortexMDebug/scripts/gdb.py → debug/PyCortexMDebug/PyCortexMDebug.py

@@ -18,6 +18,14 @@ You should have received a copy of the GNU General Public License
 along with PyCortexMDebug.  If not, see <http://www.gnu.org/licenses/>.
 """
 
+from os import path
+import sys
+
+directory, file = path.split(__file__)
+directory = path.expanduser(directory)
+directory = path.abspath(directory)
+
+sys.path.append(directory)
 
 from cmdebug.svd_gdb import LoadSVD
 from cmdebug.dwt_gdb import DWT

+ 14 - 6
debug/PyCortexMDebug/cmdebug/__init__.py

@@ -38,12 +38,14 @@ class DWT(gdb.Command):
     def __init__(self):
         gdb.Command.__init__(self, "dwt", gdb.COMMAND_DATA)
 
-    def read(self, address, bits=32):
+    @staticmethod
+    def read(address, bits=32):
         """Read from memory (using print) and return an integer"""
         value = gdb.selected_inferior().read_memory(address, bits / 8)
         return struct.unpack_from("<i", value)[0]
 
-    def write(self, address, value, bits=32):
+    @staticmethod
+    def write(address, value, bits=32):
         """Set a value in memory"""
         gdb.selected_inferior().write_memory(address, bytes(value), bits / 8)
 
@@ -53,7 +55,7 @@ class DWT(gdb.Command):
             self.write(DWT_CTRL, 0)
             self.is_init = True
 
-        s = map(lambda x: x.lower(), str(args).split(" "))
+        s = list(map(lambda x: x.lower(), str(args).split(" ")))
         # Check for empty command
         if s[0] in ["", "help"]:
             self.print_help()
@@ -100,7 +102,8 @@ class DWT(gdb.Command):
             gdb.write(args)
             self.print_help()
 
-    def complete(self, text, word):
+    @staticmethod
+    def complete(text, word):
         text = str(text).lower()
         s = text.split(" ")
 
@@ -135,7 +138,8 @@ class DWT(gdb.Command):
     def cpicnt_reset(self, value=0):
         self.write(DWT_CPICNT, value & 0xFF)
 
-    def print_help(self):
+    @staticmethod
+    def print_help():
         gdb.write("Usage:\n")
         gdb.write("=========\n")
         gdb.write("dwt:\n")
@@ -149,4 +153,8 @@ class DWT(gdb.Command):
         gdb.write("dwt cyccnt\n")
         gdb.write("\tDisplay the cycle count\n")
         gdb.write("\td(default):decimal, x: hex, o: octal, b: binary\n")
-        return ()
+        return
+
+
+# Registers our class to GDB when sourced:
+DWT()

+ 246 - 86
debug/PyCortexMDebug/cmdebug/svd.py

@@ -1,3 +1,4 @@
+#!/usr/bin/env python
 """
 This file is part of PyCortexMDebug
 
@@ -15,13 +16,92 @@ You should have received a copy of the GNU General Public License
 along with PyCortexMDebug.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-
 import lxml.objectify as objectify
 import sys
-from copy import deepcopy
 from collections import OrderedDict
 import os
+import pickle
 import traceback
+import re
+import warnings
+
+
+class SmartDict:
+    """
+    Dictionary for search by case-insensitive lookup and/or prefix lookup
+    """
+
+    def __init__(self):
+        self.od = OrderedDict()
+        self.casemap = {}
+
+    def __getitem__(self, key):
+        if key in self.od:
+            return self.od[key]
+
+        if key.lower() in self.casemap:
+            return self.od[self.casemap[key.lower()]]
+
+        return self.od[self.prefix_match(key)]
+
+    def is_ambiguous(self, key):
+        return (
+            key not in self.od
+            and key not in self.casemap
+            and len(list(self.prefix_match_iter(key))) > 1
+        )
+
+    def prefix_match_iter(self, key):
+        name, number = re.match(r"^(.*?)([0-9]*)$", key.lower()).groups()
+        for entry, od_key in self.casemap.items():
+            if entry.startswith(name) and entry.endswith(number):
+                yield od_key
+
+    def prefix_match(self, key):
+        for od_key in self.prefix_match_iter(key):
+            return od_key
+        return None
+
+    def __setitem__(self, key, value):
+        if key in self.od:
+            warnings.warn("Duplicate entry %s", key)
+        elif key.lower() in self.casemap:
+            warnings.warn(
+                "Entry %s differs from duplicate %s only in cAsE",
+                key,
+                self.casemap[key.lower()],
+            )
+
+        self.casemap[key.lower()] = key
+        self.od[key] = value
+
+    def __delitem__(self, key):
+        if (
+            self.casemap[key.lower()] == key
+        ):  # Check that we did not overwrite this entry
+            del self.casemap[key.lower()]
+        del self.od[key]
+
+    def __contains__(self, key):
+        return key in self.od or key.lower() in self.casemap or self.prefix_match(key)
+
+    def __iter__(self):
+        return iter(self.od)
+
+    def __len__(self):
+        return len(self.od)
+
+    def items(self):
+        return self.od.items()
+
+    def keys(self):
+        return self.od.keys()
+
+    def values(self):
+        return self.od.values()
+
+    def __str__(self):
+        return str(self.od)
 
 
 class SVDNonFatalError(Exception):
@@ -40,29 +120,52 @@ class SVDNonFatalError(Exception):
 
 
 class SVDFile:
+    """
+    A parsed SVD file
+    """
+
     def __init__(self, fname):
+        """
+
+        Args:
+            fname: Filename for the SVD file
+        """
         f = objectify.parse(os.path.expanduser(fname))
         root = f.getroot()
         periph = root.peripherals.getchildren()
-        self.peripherals = OrderedDict()
+        self.peripherals = SmartDict()
+        self.base_address = 0
+
         # XML elements
         for p in periph:
             try:
-                self.peripherals[str(p.name)] = SVDPeripheral(p, self)
+                if p.tag == "peripheral":
+                    self.peripherals[str(p.name)] = SVDPeripheral(p, self)
+                else:
+                    # This is some other tag
+                    pass
             except SVDNonFatalError as e:
                 print(e)
 
 
 def add_register(parent, node):
+    """
+    Add a register node to a peripheral
+
+    Args:
+        parent: Parent SVDPeripheral object
+        node: XML file node fot of the register
+    """
+
     if hasattr(node, "dim"):
         dim = int(str(node.dim), 0)
         # dimension is not used, number of split indexes should be same
         incr = int(str(node.dimIncrement), 0)
         default_dim_index = ",".join((str(i) for i in range(dim)))
         dim_index = str(getattr(node, "dimIndex", default_dim_index))
-        indexes = dim_index.split(",")
+        indices = dim_index.split(",")
         offset = 0
-        for i in indexes:
+        for i in indices:
             name = str(node.name) % i
             reg = SVDPeripheralRegister(node, parent)
             reg.name = name
@@ -71,21 +174,30 @@ def add_register(parent, node):
             offset += incr
     else:
         try:
-            parent.registers[str(node.name)] = SVDPeripheralRegister(node, parent)
-        except:
-            pass
+            reg = SVDPeripheralRegister(node, parent)
+            name = str(node.name)
+            if name not in parent.registers:
+                parent.registers[name] = reg
+            else:
+                if hasattr(node, "alternateGroup"):
+                    print("Register %s has an alternate group", name)
+        except SVDNonFatalError as e:
+            print(e)
 
 
 def add_cluster(parent, node):
+    """
+    Add a register cluster to a peripheral
+    """
     if hasattr(node, "dim"):
         dim = int(str(node.dim), 0)
-        # dimension is not used, number of split indexes should be same
+        # dimension is not used, number of split indices should be same
         incr = int(str(node.dimIncrement), 0)
         default_dim_index = ",".join((str(i) for i in range(dim)))
         dim_index = str(getattr(node, "dimIndex", default_dim_index))
-        indexes = dim_index.split(",")
+        indices = dim_index.split(",")
         offset = 0
-        for i in indexes:
+        for i in indices:
             name = str(node.name) % i
             cluster = SVDRegisterCluster(node, parent)
             cluster.name = name
@@ -101,37 +213,58 @@ def add_cluster(parent, node):
 
 
 class SVDRegisterCluster:
+    """
+    Register cluster
+    """
+
     def __init__(self, svd_elem, parent):
-        self.parent = parent
+        """
+
+        Args:
+            svd_elem: XML element for the register cluster
+            parent: Parent SVDPeripheral object
+        """
+        self.parent_base_address = parent.base_address
+        self.parent_name = parent.name
         self.address_offset = int(str(svd_elem.addressOffset), 0)
-        self.base_address = self.address_offset + parent.base_address
+        self.base_address = self.address_offset + self.parent_base_address
         # This doesn't inherit registers from anything
         children = svd_elem.getchildren()
         self.description = str(svd_elem.description)
         self.name = str(svd_elem.name)
-        self.registers = OrderedDict()
-        self.clusters = OrderedDict()
+        self.registers = SmartDict()
+        self.clusters = SmartDict()
         for r in children:
             if r.tag == "register":
                 add_register(self, r)
 
     def refactor_parent(self, parent):
-        self.parent = parent
-        self.base_address = parent.base_address + self.address_offset
-        try:
-            values = self.registers.itervalues()
-        except AttributeError:
-            values = self.registers.values()
+        self.parent_base_address = parent.base_address
+        self.parent_name = parent.name
+        self.base_address = self.parent_base_address + self.address_offset
+        values = self.registers.values()
         for r in values:
             r.refactor_parent(self)
 
-    def __unicode__(self):
+    def __str__(self):
         return str(self.name)
 
 
 class SVDPeripheral:
+    """
+    This is a peripheral as defined in the SVD file
+    """
+
     def __init__(self, svd_elem, parent):
-        self.parent = parent
+        """
+
+        Args:
+            svd_elem: XML element for the peripheral
+            parent: Parent SVDFile object
+        """
+        self.parent_base_address = parent.base_address
+
+        # Look for a base address, as it is required
         if not hasattr(svd_elem, "baseAddress"):
             raise SVDNonFatalError("Periph without base address")
         self.base_address = int(str(svd_elem.baseAddress), 0)
@@ -139,78 +272,83 @@ class SVDPeripheral:
             derived_from = svd_elem.attrib["derivedFrom"]
             try:
                 self.name = str(svd_elem.name)
-            except:
+            except AttributeError:
                 self.name = parent.peripherals[derived_from].name
             try:
                 self.description = str(svd_elem.description)
-            except:
+            except AttributeError:
                 self.description = parent.peripherals[derived_from].description
-            self.registers = deepcopy(parent.peripherals[derived_from].registers)
-            self.clusters = deepcopy(parent.peripherals[derived_from].clusters)
+
+            # pickle is faster than deepcopy by up to 50% on svd files with a
+            # lot of derivedFrom definitions
+            def copier(a):
+                return pickle.loads(pickle.dumps(a))
+
+            self.registers = copier(parent.peripherals[derived_from].registers)
+            self.clusters = copier(parent.peripherals[derived_from].clusters)
             self.refactor_parent(parent)
         else:
             # This doesn't inherit registers from anything
-            registers = svd_elem.registers.getchildren()
             self.description = str(svd_elem.description)
             self.name = str(svd_elem.name)
-            self.registers = OrderedDict()
-            self.clusters = OrderedDict()
-            for r in registers:
-                if r.tag == "cluster":
-                    add_cluster(self, r)
-                else:
-                    add_register(self, r)
+            self.registers = SmartDict()
+            self.clusters = SmartDict()
+
+            if hasattr(svd_elem, "registers"):
+                registers = [
+                    r
+                    for r in svd_elem.registers.getchildren()
+                    if r.tag in ["cluster", "register"]
+                ]
+                for r in registers:
+                    if r.tag == "cluster":
+                        add_cluster(self, r)
+                    elif r.tag == "register":
+                        add_register(self, r)
 
     def refactor_parent(self, parent):
-        self.parent = parent
-        try:
-            values = self.registers.itervalues()
-        except AttributeError:
-            values = self.registers.values()
+        self.parent_base_address = parent.base_address
+        values = self.registers.values()
         for r in values:
             r.refactor_parent(self)
-        try:
-            for c in self.clusters.itervalues():
-                c.refactor_parent(self)
-        except AttributeError:
-            for c in self.clusters.values():
-                c.refactor_parent(self)
 
-    def __unicode__(self):
+        for c in self.clusters.values():
+            c.refactor_parent(self)
+
+    def __str__(self):
         return str(self.name)
 
 
 class SVDPeripheralRegister:
+    """
+    A register within a peripheral
+    """
+
     def __init__(self, svd_elem, parent):
-        self.parent = parent
+        self.parent_base_address = parent.base_address
         self.name = str(svd_elem.name)
         self.description = str(svd_elem.description)
         self.offset = int(str(svd_elem.addressOffset), 0)
-        try:
+        if hasattr(svd_elem, "access"):
             self.access = str(svd_elem.access)
-        except:
+        else:
             self.access = "read-write"
-        try:
+        if hasattr(svd_elem, "size"):
             self.size = int(str(svd_elem.size), 0)
-        except:
+        else:
             self.size = 0x20
-        self.fields = OrderedDict()
+        self.fields = SmartDict()
         if hasattr(svd_elem, "fields"):
-            fields = svd_elem.fields.getchildren()
+            # Filter fields to only consider those of tag "field"
+            fields = [f for f in svd_elem.fields.getchildren() if f.tag == "field"]
             for f in fields:
                 self.fields[str(f.name)] = SVDPeripheralRegisterField(f, self)
 
     def refactor_parent(self, parent):
-        self.parent = parent
-        try:
-            fields = self.fields.itervalues()
-        except AttributeError:
-            fields = self.fields.values()
-        for f in fields:
-            f.refactor_parent(self)
+        self.parent_base_address = parent.base_address
 
     def address(self):
-        return self.parent.base_address + self.offset
+        return self.parent_base_address + self.offset
 
     def readable(self):
         return self.access in ["read-only", "read-write", "read-writeOnce"]
@@ -223,44 +361,58 @@ class SVDPeripheralRegister:
             "read-writeOnce",
         ]
 
-    def __unicode__(self):
+    def __str__(self):
         return str(self.name)
 
 
 class SVDPeripheralRegisterField:
+    """
+    Field within a register
+    """
+
     def __init__(self, svd_elem, parent):
-        self.parent = parent
         self.name = str(svd_elem.name)
         self.description = str(getattr(svd_elem, "description", ""))
 
-        try:
+        # Try to extract a bit range (offset and width) from the available fields
+        if hasattr(svd_elem, "bitOffset") and hasattr(svd_elem, "bitWidth"):
             self.offset = int(str(svd_elem.bitOffset))
             self.width = int(str(svd_elem.bitWidth))
-        except:
-            try:
-                bitrange = list(
-                    map(int, str(svd_elem.bitRange).strip()[1:-1].split(":"))
-                )
-                self.offset = bitrange[1]
-                self.width = 1 + bitrange[0] - bitrange[1]
-            except:
-                lsb = int(str(svd_elem.lsb))
-                msb = int(str(svd_elem.msb))
-                self.offset = lsb
-                self.width = 1 + msb - lsb
+        elif hasattr(svd_elem, "bitRange"):
+            bitrange = list(map(int, str(svd_elem.bitRange).strip()[1:-1].split(":")))
+            self.offset = bitrange[1]
+            self.width = 1 + bitrange[0] - bitrange[1]
+        else:
+            assert hasattr(svd_elem, "lsb") and hasattr(
+                svd_elem, "msb"
+            ), "Range not found for field {} in register {}".format(self.name, parent)
+            lsb = int(str(svd_elem.lsb))
+            msb = int(str(svd_elem.msb))
+            self.offset = lsb
+            self.width = 1 + msb - lsb
+
         self.access = str(getattr(svd_elem, "access", parent.access))
         self.enum = {}
 
         if hasattr(svd_elem, "enumeratedValues"):
-            for v in svd_elem.enumeratedValues.getchildren():
-                if v.tag == "name":
+            values = [
+                v
+                for v in svd_elem.enumeratedValues.getchildren()
+                if v.tag == "enumeratedValue"
+            ]
+            for v in values:
+                # Skip the "name" tag and any entries that don't have a value
+                if v.tag == "name" or not hasattr(v, "value"):
                     continue
                 # Some Kinetis parts have values with # instead of 0x...
                 value = str(v.value).replace("#", "0x")
-                self.enum[int(value, 0)] = (str(v.name), str(v.description))
-
-    def refactor_parent(self, parent):
-        self.parent = parent
+                description = str(v.description) if hasattr(v, "description") else ""
+                try:
+                    index = int(value, 0)
+                    self.enum[int(value, 0)] = (str(v.name), description)
+                except ValueError:
+                    # If the value couldn't be converted as a single integer, skip it
+                    pass
 
     def readable(self):
         return self.access in ["read-only", "read-write", "read-writeOnce"]
@@ -273,11 +425,15 @@ class SVDPeripheralRegisterField:
             "read-writeOnce",
         ]
 
-    def __unicode__(self):
+    def __str__(self):
         return str(self.name)
 
 
-if __name__ == "__main__":
+def _main():
+    """
+    Basic test to parse a file and do some things
+    """
+
     for f in sys.argv[1:]:
         print("Testing file: {}".format(f))
         svd = SVDFile(f)
@@ -286,3 +442,7 @@ if __name__ == "__main__":
         print("Registers in peripheral '{}':".format(key))
         print(svd.peripherals[key].registers)
         print("Done testing file: {}".format(f))
+
+
+if __name__ == "__main__":
+    _main()

+ 121 - 85
debug/PyCortexMDebug/cmdebug/svd_gdb.py

@@ -16,7 +16,6 @@ You should have received a copy of the GNU General Public License
 along with PyCortexMDebug.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-import binascii
 import gdb
 import re
 import math
@@ -24,10 +23,7 @@ import sys
 import struct
 import pkg_resources
 
-sys.path.append(".")
-from cmdebug.svd import SVDFile
-
-# from svd_test import *
+from .svd import SVDFile
 
 BITS_TO_UNPACK_FORMAT = {
     8: "B",
@@ -83,7 +79,8 @@ class LoadSVD(gdb.Command):
             return [fname for fname in filenames if fname.lower().startswith(prefix)]
         return gdb.COMPLETE_NONE
 
-    def invoke(self, args, from_tty):
+    @staticmethod
+    def invoke(args, from_tty):
         args = gdb.string_to_argv(args)
         argc = len(args)
         if argc == 1:
@@ -130,36 +127,39 @@ class SVD(gdb.Command):
         except AttributeError:
             regs_iter = registers.values()
         gdb.write("Registers in %s:\n" % container_name)
-        regList = []
+        reg_list = []
         for r in regs_iter:
             if r.readable():
-                data = self.read(r.address(), r.size)
-                data = self.format(data, form, r.size)
-                if form == "a":
-                    data += (
-                        " <"
-                        + re.sub(
-                            r"\s+",
-                            " ",
-                            gdb.execute(
-                                "info symbol {}".format(data), True, True
-                            ).strip(),
+                try:
+                    data = self.read(r.address(), r.size)
+                    data = self.format(data, form, r.size)
+                    if form == "a":
+                        data += (
+                            " <"
+                            + re.sub(
+                                r"\s+",
+                                " ",
+                                gdb.execute(
+                                    "info symbol {}".format(data), True, True
+                                ).strip(),
+                            )
+                            + ">"
                         )
-                        + ">"
-                    )
+                except gdb.MemoryError:
+                    data = "(error reading)"
             else:
                 data = "(not readable)"
             desc = re.sub(r"\s+", " ", r.description)
-            regList.append((r.name, data, desc))
+            reg_list.append((r.name, data, desc))
 
-        column1Width = max(len(reg[0]) for reg in regList) + 2  # padding
-        column2Width = max(len(reg[1]) for reg in regList)
-        for reg in regList:
+        column1_width = max(len(reg[0]) for reg in reg_list) + 2  # padding
+        column2_width = max(len(reg[1]) for reg in reg_list)
+        for reg in reg_list:
             gdb.write(
                 "\t{}:{}{}".format(
                     reg[0],
-                    "".ljust(column1Width - len(reg[0])),
-                    reg[1].rjust(column2Width),
+                    "".ljust(column1_width - len(reg[0])),
+                    reg[1].rjust(column2_width),
                 )
             )
             if reg[2] != reg[0]:
@@ -173,7 +173,7 @@ class SVD(gdb.Command):
             data = 0
         else:
             data = self.read(register.address(), register.size)
-        fieldList = []
+        field_list = []
         try:
             fields_iter = fields.itervalues()
         except AttributeError:
@@ -193,16 +193,16 @@ class SVD(gdb.Command):
                     val = self.format(val, form, f.width)
             else:
                 val = "(not readable)"
-            fieldList.append((f.name, val, desc))
+            field_list.append((f.name, val, desc))
 
-        column1Width = max(len(field[0]) for field in fieldList) + 2  # padding
-        column2Width = max(len(field[1]) for field in fieldList)  # padding
-        for field in fieldList:
+        column1_width = max(len(field[0]) for field in field_list) + 2  # padding
+        column2_width = max(len(field[1]) for field in field_list)  # padding
+        for field in field_list:
             gdb.write(
                 "\t{}:{}{}".format(
                     field[0],
-                    "".ljust(column1Width - len(field[0])),
-                    field[1].rjust(column2Width),
+                    "".ljust(column1_width - len(field[0])),
+                    field[1].rjust(column2_width),
                 )
             )
             if field[2] != field[0]:
@@ -234,6 +234,10 @@ class SVD(gdb.Command):
             gdb.write("svd/[format_character] ...\n")
             gdb.write("\tFormat values using that character\n")
             gdb.write("\td(default):decimal, x: hex, o: octal, b: binary\n")
+            gdb.write("\n")
+            gdb.write(
+                "Both prefix matching and case-insensitive matching is supported for peripherals, registers, clusters and fields.\n"
+            )
             return
 
         if not len(s[0]):
@@ -242,7 +246,7 @@ class SVD(gdb.Command):
                 peripherals = self.svd_file.peripherals.itervalues()
             except AttributeError:
                 peripherals = self.svd_file.peripherals.values()
-            columnWidth = max(len(p.name) for p in peripherals) + 2  # padding
+            column_width = max(len(p.name) for p in peripherals) + 2  # padding
             try:
                 peripherals = self.svd_file.peripherals.itervalues()
             except AttributeError:
@@ -251,11 +255,19 @@ class SVD(gdb.Command):
                 desc = re.sub(r"\s+", " ", p.description)
                 gdb.write(
                     "\t{}:{}{}\n".format(
-                        p.name, "".ljust(columnWidth - len(p.name)), desc
+                        p.name, "".ljust(column_width - len(p.name)), desc
                     )
                 )
             return
 
+        def warn_if_ambiguous(smart_dict, key):
+            if smart_dict.is_ambiguous(key):
+                gdb.write(
+                    "Warning: {} could prefix match any of: {}\n".format(
+                        key, ", ".join(smart_dict.prefix_match_iter(key))
+                    )
+                )
+
         registers = None
         if len(s) >= 1:
             peripheral_name = s[0]
@@ -263,29 +275,31 @@ class SVD(gdb.Command):
                 gdb.write("Peripheral {} does not exist!\n".format(s[0]))
                 return
 
+            warn_if_ambiguous(self.svd_file.peripherals, peripheral_name)
+
             peripheral = self.svd_file.peripherals[peripheral_name]
 
         if len(s) == 1:
-            self._print_registers(s[0], form, peripheral.registers)
+            self._print_registers(peripheral.name, form, peripheral.registers)
             if len(peripheral.clusters) > 0:
                 try:
                     clusters_iter = peripheral.clusters.itervalues()
                 except AttributeError:
                     clusters_iter = peripheral.clusters.values()
-                gdb.write("Clusters in %s:\n" % peripheral_name)
-                regList = []
+                gdb.write("Clusters in %s:\n" % peripheral.name)
+                reg_list = []
                 for r in clusters_iter:
                     desc = re.sub(r"\s+", " ", r.description)
-                    regList.append((r.name, "", desc))
+                    reg_list.append((r.name, "", desc))
 
-                column1Width = max(len(reg[0]) for reg in regList) + 2  # padding
-                column2Width = max(len(reg[1]) for reg in regList)
-                for reg in regList:
+                column1_width = max(len(reg[0]) for reg in reg_list) + 2  # padding
+                column2_width = max(len(reg[1]) for reg in reg_list)
+                for reg in reg_list:
                     gdb.write(
                         "\t{}:{}{}".format(
                             reg[0],
-                            "".ljust(column1Width - len(reg[0])),
-                            reg[1].rjust(column2Width),
+                            "".ljust(column1_width - len(reg[0])),
+                            reg[1].rjust(column2_width),
                         )
                     )
                     if reg[2] != reg[0]:
@@ -295,19 +309,23 @@ class SVD(gdb.Command):
 
         cluster = None
         if len(s) == 2:
-            container = " ".join(s[:2])
             if s[1] in peripheral.clusters:
-                self._print_registers(
-                    container, form, peripheral.clusters[s[1]].registers
-                )
+                warn_if_ambiguous(peripheral.clusters, s[1])
+                cluster = peripheral.clusters[s[1]]
+                container = peripheral.name + " > " + cluster.name
+                self._print_registers(container, form, cluster.registers)
+
             elif s[1] in peripheral.registers:
-                self._print_register_fields(
-                    container, form, self.svd_file.peripherals[s[0]].registers[s[1]]
-                )
+                warn_if_ambiguous(peripheral.registers, s[1])
+                register = peripheral.registers[s[1]]
+                container = peripheral.name + " > " + register.name
+
+                self._print_register_fields(container, form, register)
+
             else:
                 gdb.write(
                     "Register/cluster {} in peripheral {} does not exist!\n".format(
-                        s[1], s[0]
+                        s[1], peripheral.name
                     )
                 )
             return
@@ -315,42 +333,55 @@ class SVD(gdb.Command):
         if len(s) == 3:
             if s[1] not in peripheral.clusters:
                 gdb.write(
-                    "Cluster {} in peripheral {} does not exist!\n".format(s[1], s[0])
+                    "Cluster {} in peripheral {} does not exist!\n".format(
+                        s[1], peripheral.name
+                    )
                 )
-            elif s[2] not in peripheral.clusters[s[1]].registers:
+                return
+            warn_if_ambiguous(peripheral.clusters, s[1])
+
+            cluster = peripheral.clusters[s[1]]
+            if s[2] not in cluster.registers:
                 gdb.write(
                     "Register {} in cluster {} in peripheral {} does not exist!\n".format(
-                        s[2], s[1], s[0]
+                        s[2], cluster.name, peripheral.name
                     )
                 )
-            else:
-                container = " ".join(s[:3])
-                cluster = peripheral.clusters[s[1]]
-                self._print_register_fields(container, form, cluster.registers[s[2]])
+                return
+            warn_if_ambiguous(cluster.registers, s[2])
+
+            register = cluster.registers[s[2]]
+            container = " > ".join([peripheral.name, cluster.name, register.name])
+            self._print_register_fields(container, form, register)
             return
 
         if len(s) == 4:
-            try:
-                reg = self.svd_file.peripherals[s[0]].registers[s[1]]
-            except KeyError:
+            if s[1] not in peripheral.registers:
                 gdb.write(
-                    "Register {} in peripheral {} does not exist!\n".format(s[1], s[0])
+                    "Register {} in peripheral {} does not exist!\n".format(
+                        s[1], peripheral.name
+                    )
                 )
                 return
-            try:
-                field = reg.fields[s[2]]
-            except KeyError:
+            warn_if_ambiguous(peripheral.registers, s[1])
+
+            reg = peripheral.registers[s[1]]
+
+            if s[2] not in reg.fields:
                 gdb.write(
                     "Field {} in register {} in peripheral {} does not exist!\n".format(
-                        s[2], s[1], s[0]
+                        s[2], reg.name, peripheral.name
                     )
                 )
                 return
+            warn_if_ambiguous(reg.fields, s[2])
+
+            field = reg.fields[s[2]]
 
             if not field.writable() or not reg.writable():
                 gdb.write(
                     "Field {} in register {} in peripheral {} is read-only!\n".format(
-                        s[2], s[1], s[0]
+                        field.name, reg.name, peripheral.name
                     )
                 )
                 return
@@ -359,9 +390,8 @@ class SVD(gdb.Command):
                 val = int(s[3], 0)
             except ValueError:
                 gdb.write(
-                    "{} is not a valid number! You can prefix numbers with 0x for hex, 0b for binary, or any python int literal\n".format(
-                        s[3]
-                    )
+                    "{} is not a valid number! You can prefix numbers with 0x for hex, 0b for binary, or any python "
+                    "int literal\n".format(s[3])
                 )
                 return
 
@@ -378,7 +408,7 @@ class SVD(gdb.Command):
             else:
                 data = self.read(reg.address(), reg.size)
             data &= ~(((1 << field.width) - 1) << field.offset)
-            data |= (val) << field.offset
+            data |= val << field.offset
             self.write(reg.address(), data, reg.size)
             return
 
@@ -394,22 +424,26 @@ class SVD(gdb.Command):
             if len(s) > 1:
                 s = s[1:]
             else:
-                return
+                return []  # completion after e.g. "svd/x" but before trailing space
 
         if len(s) == 1:
-            return filter(
-                lambda x: x.lower().startswith(s[0].lower()),
-                self.peripheral_list() + ["help"],
-            )
+            return list(self.svd_file.peripherals.prefix_match_iter(s[0]))
 
         if len(s) == 2:
             reg = s[1].upper()
             if len(reg) and reg[0] == "&":
                 reg = reg[1:]
-            filt = filter(lambda x: x.startswith(reg), self.register_list(s[0].upper()))
-            return filt
 
-    def read(self, address, bits=32):
+            if s[0] not in self.svd_file.peripherals:
+                return []
+
+            per = self.svd_file.peripherals[s[0]]
+            return list(per.registers.prefix_match_iter(s[1]))
+
+        return []
+
+    @staticmethod
+    def read(address, bits=32):
         """Read from memory and return an integer"""
         value = gdb.selected_inferior().read_memory(address, bits / 8)
         unpack_format = "I"
@@ -418,15 +452,17 @@ class SVD(gdb.Command):
         # gdb.write("{:x} {}\n".format(address, binascii.hexlify(value)))
         return struct.unpack_from("<" + unpack_format, value)[0]
 
-    def write(self, address, data, bits=32):
+    @staticmethod
+    def write(address, data, bits=32):
         """Write data to memory"""
         gdb.selected_inferior().write_memory(address, bytes(data), bits / 8)
 
-    def format(self, value, form, length=32):
+    @staticmethod
+    def format(value, form, length=32):
         """Format a number based on a format character and length"""
         # get current gdb radix setting
         radix = int(
-            re.search("\d+", gdb.execute("show output-radix", True, True)).group(0)
+            re.search(r"\d+", gdb.execute("show output-radix", True, True)).group(0)
         )
 
         # override it if asked to
@@ -454,7 +490,7 @@ class SVD(gdb.Command):
         try:
             keys = self.svd_file.peripherals.iterkeys()
         except AttributeError:
-            keys = elf.svd_file.peripherals.keys()
+            keys = self.svd_file.peripherals.keys()
         return list(keys)
 
     def register_list(self, peripheral):
@@ -470,7 +506,7 @@ class SVD(gdb.Command):
 
     def field_list(self, peripheral, register):
         try:
-            periph = svd_file.peripherals[peripheral]
+            periph = self.svd_file.peripherals[peripheral]
             reg = periph.registers[register]
             try:
                 regs = reg.fields.iterkeys()

+ 6 - 1
docker/Dockerfile

@@ -20,7 +20,9 @@ RUN apt-get update && \
         unzip \
         build-essential \
         python \
+        python-dev \
         python-pip \
+        python-setuptools \
         python3 \
         imagemagick \
         srecord \
@@ -29,6 +31,9 @@ RUN apt-get update && \
         dfu-util \
         && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
 
+RUN pip install lxml
+
+
 SHELL ["/bin/bash", "-eo", "pipefail", "-c"]
 RUN wget --progress=dot:giga -O - "https://apt.llvm.org/llvm-snapshot.gpg.key" | apt-key add - && add-apt-repository "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-12 main"
 
@@ -48,7 +53,7 @@ RUN wget --progress=dot:giga "https://developer.arm.com/-/media/Files/downloads/
     cd gcc-arm-none-eabi-10-2020-q4-major/bin/ && \
     for file in * ; do ln -s "${PWD}/${file}" "/usr/bin/${file}" ; done && \
     cd / && arm-none-eabi-gcc -v && arm-none-eabi-gdb -v
-    
+
 # install hex2dfu
 
 # hadolint ignore=DL3003

+ 3 - 3
make/rules.mk

@@ -72,11 +72,11 @@ flash: $(OBJ_DIR)/flash
 upload: $(OBJ_DIR)/upload
 
 debug: flash
-	arm-none-eabi-gdb \
+	arm-none-eabi-gdb-py \
 		-ex 'target extended-remote | openocd -c "gdb_port pipe" $(OPENOCD_OPTS)' \
 		-ex "set confirm off" \
 		-ex "source ../debug/FreeRTOS/FreeRTOS.py" \
-		-ex "source ../debug/PyCortexMDebug/scripts/gdb.py" \
+		-ex "source ../debug/PyCortexMDebug/PyCortexMDebug.py" \
 		-ex "svd_load $(SVD_FILE)" \
 		-ex "compare-sections" \
 		$(OBJ_DIR)/$(PROJECT).elf; \
@@ -86,7 +86,7 @@ openocd:
 
 bm_debug: flash
 	set -m; blackmagic & echo $$! > $(OBJ_DIR)/agent.PID
-	arm-none-eabi-gdb \
+	arm-none-eabi-gdb-py \
 		-ex "target extended-remote 127.0.0.1:2000" \
 		-ex "set confirm off" \
 		-ex "monitor debug_bmp enable"\