Debug: revert cortex debug to lxml and drop DWT (#2651)

* Debug: revert cortex debug to lxml

* Debug: update PyCortexMDebug readme

* fbt: moved "debug" dir to "scripts" subfolder

* ufbt: added missing debug_other & debug_other_blackmagic targets; github: fixed script bundling

* lint: fixed formatting on debug scripts

* vscode: updated configuration for debug dir changes

---------

Co-authored-by: hedger <hedger@users.noreply.github.com>
Co-authored-by: hedger <hedger@nanode.su>
This commit is contained in:
あく 2023-05-09 08:31:44 +09:00 committed by GitHub
parent 241b4ef6e4
commit f57f0efc48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 113 additions and 898 deletions

View File

@ -60,7 +60,7 @@ jobs:
- name: 'Bundle scripts'
if: ${{ !github.event.pull_request.head.repo.fork }}
run: |
tar czpf artifacts/flipper-z-any-scripts-${SUFFIX}.tgz scripts debug
tar czpf artifacts/flipper-z-any-scripts-${SUFFIX}.tgz scripts
- name: 'Build the firmware'
run: |

View File

@ -27,22 +27,21 @@
"type": "cortex-debug",
"servertype": "openocd",
"device": "stlink",
"svdFile": "./debug/STM32WB55_CM4.svd",
"svdFile": "./scripts/debug/STM32WB55_CM4.svd",
// If you're debugging early in the boot process, before OS scheduler is running,
// you have to comment out the following line.
"rtos": "FreeRTOS",
"configFiles": [
"interface/stlink.cfg",
"./debug/stm32wbx.cfg",
"./scripts/debug/stm32wbx.cfg",
],
"postAttachCommands": [
"source debug/flipperversion.py",
"source scripts/debug/flipperversion.py",
"fw-version",
// "compare-sections",
"source debug/flipperapps.py",
"source scripts/debug/flipperapps.py",
"fap-set-debug-elf-root build/latest/.extapps",
// "source debug/FreeRTOS/FreeRTOS.py",
// "svd_load debug/STM32WB55_CM4.svd"
// "source scripts/debug/FreeRTOS/FreeRTOS.py",
]
// "showDevDebugOutput": "raw",
},
@ -54,16 +53,16 @@
"type": "cortex-debug",
"servertype": "external",
"gdbTarget": "${input:BLACKMAGIC}",
"svdFile": "./debug/STM32WB55_CM4.svd",
"svdFile": "./scripts/debug/STM32WB55_CM4.svd",
"rtos": "FreeRTOS",
"postAttachCommands": [
"monitor swdp_scan",
"attach 1",
"set confirm off",
"set mem inaccessible-by-default off",
"source debug/flipperversion.py",
"source scripts/debug/flipperversion.py",
"fw-version",
"source debug/flipperapps.py",
"source scripts/debug/flipperapps.py",
"fap-set-debug-elf-root build/latest/.extapps",
// "compare-sections",
]
@ -78,12 +77,12 @@
"servertype": "jlink",
"interface": "swd",
"device": "STM32WB55RG",
"svdFile": "./debug/STM32WB55_CM4.svd",
"svdFile": "./scripts/debug/STM32WB55_CM4.svd",
"rtos": "FreeRTOS",
"postAttachCommands": [
"source debug/flipperversion.py",
"source scripts/debug/flipperversion.py",
"fw-version",
"source debug/flipperapps.py",
"source scripts/debug/flipperapps.py",
"fap-set-debug-elf-root build/latest/.extapps",
]
// "showDevDebugOutput": "raw",
@ -96,16 +95,16 @@
"type": "cortex-debug",
"servertype": "openocd",
"device": "cmsis-dap",
"svdFile": "./debug/STM32WB55_CM4.svd",
"svdFile": "./scripts/debug/STM32WB55_CM4.svd",
"rtos": "FreeRTOS",
"configFiles": [
"interface/cmsis-dap.cfg",
"./debug/stm32wbx.cfg",
"./scripts/debug/stm32wbx.cfg",
],
"postAttachCommands": [
"source debug/flipperversion.py",
"source scripts/debug/flipperversion.py",
"fw-version",
"source debug/flipperapps.py",
"source scripts/debug/flipperapps.py",
"fap-set-debug-elf-root build/latest/.extapps",
],
// "showDevDebugOutput": "raw",

View File

@ -1,84 +0,0 @@
PyCortexMDebug
==============
*A set of GDB/Python-based utilities to make life debugging ARM Cortex-M processors a bit easier*
It will consist of several modules which will hopefully become integrated as they evolve. Presently, there is only one:
## SVD
ARM defines an SVD (System View Description) file format in its CMSIS
standard as a means for Cortex-M-based chip manufacturers to provide a
common description of peripherals, registers, and register fields. You
can download SVD files for different manufacturers
[here](http://www.arm.com/products/processors/cortex-m/cortex-microcontroller-software-interface-standard.php).
My implementation so far has only tested STM32 chips but should hold for others. If others are like those from ST,
expect plenty of errors in the file. Like GPIOA having a register named GPIOB_OSPEEDR and lots of 16-bit registers
that are listed as 32!
The implementation consists of two components -- An xml parser module (pysvd) and a GDB file (gdb_svd).
I haven't yet worked out a perfect workflow for this, though it's quite easy to use when
you already tend to have a GDB initialization file for starting up OpenOCD and the like.
However your workflow works, just make sure to, in GDB:
source gdb_svd.py
svd_load [your_svd_file].svd
These files can be huge so it might take a second or two. Anyways, after that, you can do
svd
to list available peripherals with descriptions. Or you can do
svd [some_peripheral_name]
to see all of the registers (with their values) for a given peripheral. For more details, run
svd [some_peripheral_name] [some_register_name]
to see all of the field values with descriptions.
You can add format modifiers like:
* `svd/x` will display values in hex
* `svd/o` will display values in octal
* `svd/t` or `svd/b` will display values in binary
* `svd/a` will display values in hex and try to resolve symbols from the values
All field values are displayed at the correct lengths as provided by the SVD files.
Also, tab completion exists for nearly everything! When in doubt, run `svd help`.
### TODO
Enable writing to registers and individual fields
### Bugs
There are probably a few. All planning, writing, and testing of this was done in an afternoon. There may be
some oddities in working with non-STM32 parts. I'll play with this when I start working with other
controllers again. If something's giving you trouble, describe the problem and it shall be fixed.
## DWT
The ARM Data Watchpoint and Trace Unit (DWT) offers data watchpoints and a series of gated cycle counters. For now,
I only support the raw cycle counter but facilities are in place to make use of others. As this is independent of the
specific device under test, commands are simple and you can configure a clock speed to get real time values from
counters.
dwt configclk 48000000
will set the current core clock speed. Then
dwt cyccnt reset
dwt cyccnt enable
will reset and start the cycle counter. At any point
dwt cycnt
will then indicate the number of cycles and amount of time that has passed.
## ITM/ETM support
This is not implemented yet. I want to have more complete support for some of the nicer debug and trace features
on Cortex-M processors. Parts of this will probably be dependent on OpenOCD and possibly on specific interfaces.
I'll try to avoid this where possible but can't make any promises.

View File

@ -1,160 +0,0 @@
#!/usr/bin/env python
"""
This file is part of PyCortexMDebug
PyCortexMDebug is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
PyCortexMDebug is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with PyCortexMDebug. If not, see <http://www.gnu.org/licenses/>.
"""
import gdb
import struct
DWT_CTRL = 0xE0001000
DWT_CYCCNT = 0xE0001004
DWT_CPICNT = 0xE0001008
DWT_EXTCNT = 0xE000100C
DWT_SLEEPCNT = 0xE0001010
DWT_LSUCNT = 0xE0001014
DWT_FOLDCNT = 0xE0001018
DWT_PCSR = 0xE000101C
prefix = "dwt : "
class DWT(gdb.Command):
clk = None
is_init = False
def __init__(self):
gdb.Command.__init__(self, "dwt", gdb.COMMAND_DATA)
@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]
@staticmethod
def write(address, value, bits=32):
"""Set a value in memory"""
gdb.selected_inferior().write_memory(address, bytes(value), bits / 8)
def invoke(self, args, from_tty):
if not self.is_init:
self.write(0xE000EDFC, self.read(0xE000EDFC) | (1 << 24))
self.write(DWT_CTRL, 0)
self.is_init = True
s = list(map(lambda x: x.lower(), str(args).split(" ")))
# Check for empty command
if s[0] in ["", "help"]:
self.print_help()
return ()
if s[0] == "cyccnt":
if len(s) > 1:
if s[1][:2] == "en":
self.cyccnt_en()
elif s[1][0] == "r":
self.cyccnt_reset()
elif s[1][0] == "d":
self.cyccnt_dis()
gdb.write(
prefix
+ "CYCCNT ({}): ".format("ON" if (self.read(DWT_CTRL) & 1) else "OFF")
+ self.cycles_str(self.read(DWT_CYCCNT))
)
elif s[0] == "reset":
if len(s) > 1:
if s[1] == "cyccnt":
self.cyccnt_reset()
gdb.write(prefix + "CYCCNT reset\n")
if s[1] == "counters":
self.cyccnt_reset()
gdb.write(prefix + "CYCCNT reset\n")
else:
self.cyccnt_reset()
gdb.write(prefix + "CYCCNT reset\n")
else:
# Reset everything
self.cyccnt_reset()
gdb.write(prefix + "CYCCNT reset\n")
elif s[0] == "configclk":
if len(s) == 2:
try:
self.clk = float(s[1])
except:
self.print_help()
else:
self.print_help()
else:
# Try to figure out what stupid went on here
gdb.write(args)
self.print_help()
@staticmethod
def complete(text, word):
text = str(text).lower()
s = text.split(" ")
commands = ["configclk", "reset", "cyccnt"]
reset_commands = ["counters", "cyccnt"]
cyccnt_commands = ["enable", "reset", "disable"]
if len(s) == 1:
return filter(lambda x: x.startswith(s[0]), commands)
if len(s) == 2:
if s[0] == "reset":
return filter(lambda x: x.startswith(s[1]), reset_commands)
if s[0] == "cyccnt":
return filter(lambda x: x.startswith(s[1]), cyccnt_commands)
def cycles_str(self, cycles):
if self.clk:
return "%d cycles, %.3es\n" % (cycles, cycles * 1.0 / self.clk)
else:
return "%d cycles"
def cyccnt_en(self):
self.write(DWT_CTRL, self.read(DWT_CTRL) | 1)
def cyccnt_dis(self):
self.write(DWT_CTRL, self.read(DWT_CTRL) & 0xFFFFFFFE)
def cyccnt_reset(self, value=0):
self.write(DWT_CYCCNT, value)
def cpicnt_reset(self, value=0):
self.write(DWT_CPICNT, value & 0xFF)
@staticmethod
def print_help():
gdb.write("Usage:\n")
gdb.write("=========\n")
gdb.write("dwt:\n")
gdb.write("\tList available peripherals\n")
gdb.write("dwt configclk [Hz]:\n")
gdb.write("\tSet clock for rendering time values in seconds\n")
gdb.write("dwt reset:\n")
gdb.write("\tReset everything in DWT\n")
gdb.write("dwt reset counters:\n")
gdb.write("\tReset all DWT counters\n")
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
# Registers our class to GDB when sourced:
DWT()

View File

@ -1,586 +0,0 @@
#!/usr/bin/env python
"Makes working with XML feel like you are working with JSON"
try:
from defusedexpat import pyexpat as expat
except ImportError:
from xml.parsers import expat
from xml.sax.saxutils import XMLGenerator
from xml.sax.xmlreader import AttributesImpl
try: # pragma no cover
from cStringIO import StringIO
except ImportError: # pragma no cover
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
from inspect import isgenerator
class ObjectDict(dict):
def __getattr__(self, name):
if name in self:
return self[name]
else:
raise AttributeError("No such attribute: " + name)
try: # pragma no cover
_basestring = basestring
except NameError: # pragma no cover
_basestring = str
try: # pragma no cover
_unicode = unicode
except NameError: # pragma no cover
_unicode = str
__author__ = "Martin Blech"
__version__ = "0.12.0"
__license__ = "MIT"
class ParsingInterrupted(Exception):
pass
class _DictSAXHandler(object):
def __init__(
self,
item_depth=0,
item_callback=lambda *args: True,
xml_attribs=True,
attr_prefix="@",
cdata_key="#text",
force_cdata=False,
cdata_separator="",
postprocessor=None,
dict_constructor=ObjectDict,
strip_whitespace=True,
namespace_separator=":",
namespaces=None,
force_list=None,
comment_key="#comment",
):
self.path = []
self.stack = []
self.data = []
self.item = None
self.item_depth = item_depth
self.xml_attribs = xml_attribs
self.item_callback = item_callback
self.attr_prefix = attr_prefix
self.cdata_key = cdata_key
self.force_cdata = force_cdata
self.cdata_separator = cdata_separator
self.postprocessor = postprocessor
self.dict_constructor = dict_constructor
self.strip_whitespace = strip_whitespace
self.namespace_separator = namespace_separator
self.namespaces = namespaces
self.namespace_declarations = ObjectDict()
self.force_list = force_list
self.comment_key = comment_key
def _build_name(self, full_name):
if self.namespaces is None:
return full_name
i = full_name.rfind(self.namespace_separator)
if i == -1:
return full_name
namespace, name = full_name[:i], full_name[i + 1 :]
try:
short_namespace = self.namespaces[namespace]
except KeyError:
short_namespace = namespace
if not short_namespace:
return name
else:
return self.namespace_separator.join((short_namespace, name))
def _attrs_to_dict(self, attrs):
if isinstance(attrs, dict):
return attrs
return self.dict_constructor(zip(attrs[0::2], attrs[1::2]))
def startNamespaceDecl(self, prefix, uri):
self.namespace_declarations[prefix or ""] = uri
def startElement(self, full_name, attrs):
name = self._build_name(full_name)
attrs = self._attrs_to_dict(attrs)
if attrs and self.namespace_declarations:
attrs["xmlns"] = self.namespace_declarations
self.namespace_declarations = ObjectDict()
self.path.append((name, attrs or None))
if len(self.path) > self.item_depth:
self.stack.append((self.item, self.data))
if self.xml_attribs:
attr_entries = []
for key, value in attrs.items():
key = self.attr_prefix + self._build_name(key)
if self.postprocessor:
entry = self.postprocessor(self.path, key, value)
else:
entry = (key, value)
if entry:
attr_entries.append(entry)
attrs = self.dict_constructor(attr_entries)
else:
attrs = None
self.item = attrs or None
self.data = []
def endElement(self, full_name):
name = self._build_name(full_name)
if len(self.path) == self.item_depth:
item = self.item
if item is None:
item = None if not self.data else self.cdata_separator.join(self.data)
should_continue = self.item_callback(self.path, item)
if not should_continue:
raise ParsingInterrupted()
if len(self.stack):
data = None if not self.data else self.cdata_separator.join(self.data)
item = self.item
self.item, self.data = self.stack.pop()
if self.strip_whitespace and data:
data = data.strip() or None
if data and self.force_cdata and item is None:
item = self.dict_constructor()
if item is not None:
if data:
self.push_data(item, self.cdata_key, data)
self.item = self.push_data(self.item, name, item)
else:
self.item = self.push_data(self.item, name, data)
else:
self.item = None
self.data = []
self.path.pop()
def characters(self, data):
if not self.data:
self.data = [data]
else:
self.data.append(data)
def comments(self, data):
if self.strip_whitespace:
data = data.strip()
self.item = self.push_data(self.item, self.comment_key, data)
def push_data(self, item, key, data):
if self.postprocessor is not None:
result = self.postprocessor(self.path, key, data)
if result is None:
return item
key, data = result
if item is None:
item = self.dict_constructor()
try:
value = item[key]
if isinstance(value, list):
value.append(data)
else:
item[key] = [value, data]
except KeyError:
if self._should_force_list(key, data):
item[key] = [data]
else:
item[key] = data
return item
def _should_force_list(self, key, value):
if not self.force_list:
return False
if isinstance(self.force_list, bool):
return self.force_list
try:
return key in self.force_list
except TypeError:
return self.force_list(self.path[:-1], key, value)
def parse(
xml_input,
encoding=None,
expat=expat,
process_namespaces=False,
namespace_separator=":",
disable_entities=True,
process_comments=False,
**kwargs
):
"""Parse the given XML input and convert it into a dictionary.
`xml_input` can either be a `string`, a file-like object, or a generator of strings.
If `xml_attribs` is `True`, element attributes are put in the dictionary
among regular child elements, using `@` as a prefix to avoid collisions. If
set to `False`, they are just ignored.
Simple example::
>>> import xmltodict
>>> doc = xmltodict.parse(\"\"\"
... <a prop="x">
... <b>1</b>
... <b>2</b>
... </a>
... \"\"\")
>>> doc['a']['@prop']
u'x'
>>> doc['a']['b']
[u'1', u'2']
If `item_depth` is `0`, the function returns a dictionary for the root
element (default behavior). Otherwise, it calls `item_callback` every time
an item at the specified depth is found and returns `None` in the end
(streaming mode).
The callback function receives two parameters: the `path` from the document
root to the item (name-attribs pairs), and the `item` (dict). If the
callback's return value is false-ish, parsing will be stopped with the
:class:`ParsingInterrupted` exception.
Streaming example::
>>> def handle(path, item):
... print('path:%s item:%s' % (path, item))
... return True
...
>>> xmltodict.parse(\"\"\"
... <a prop="x">
... <b>1</b>
... <b>2</b>
... </a>\"\"\", item_depth=2, item_callback=handle)
path:[(u'a', {u'prop': u'x'}), (u'b', None)] item:1
path:[(u'a', {u'prop': u'x'}), (u'b', None)] item:2
The optional argument `postprocessor` is a function that takes `path`,
`key` and `value` as positional arguments and returns a new `(key, value)`
pair where both `key` and `value` may have changed. Usage example::
>>> def postprocessor(path, key, value):
... try:
... return key + ':int', int(value)
... except (ValueError, TypeError):
... return key, value
>>> xmltodict.parse('<a><b>1</b><b>2</b><b>x</b></a>',
... postprocessor=postprocessor)
ObjectDict([(u'a', ObjectDict([(u'b:int', [1, 2]), (u'b', u'x')]))])
You can pass an alternate version of `expat` (such as `defusedexpat`) by
using the `expat` parameter. E.g:
>>> import defusedexpat
>>> xmltodict.parse('<a>hello</a>', expat=defusedexpat.pyexpat)
ObjectDict([(u'a', u'hello')])
You can use the force_list argument to force lists to be created even
when there is only a single child of a given level of hierarchy. The
force_list argument is a tuple of keys. If the key for a given level
of hierarchy is in the force_list argument, that level of hierarchy
will have a list as a child (even if there is only one sub-element).
The index_keys operation takes precedence over this. This is applied
after any user-supplied postprocessor has already run.
For example, given this input:
<servers>
<server>
<name>host1</name>
<os>Linux</os>
<interfaces>
<interface>
<name>em0</name>
<ip_address>10.0.0.1</ip_address>
</interface>
</interfaces>
</server>
</servers>
If called with force_list=('interface',), it will produce
this dictionary:
{'servers':
{'server':
{'name': 'host1',
'os': 'Linux'},
'interfaces':
{'interface':
[ {'name': 'em0', 'ip_address': '10.0.0.1' } ] } } }
`force_list` can also be a callable that receives `path`, `key` and
`value`. This is helpful in cases where the logic that decides whether
a list should be forced is more complex.
If `process_comment` is `True` then comment will be added with comment_key
(default=`'#comment'`) to then tag which contains comment
For example, given this input:
<a>
<b>
<!-- b comment -->
<c>
<!-- c comment -->
1
</c>
<d>2</d>
</b>
</a>
If called with process_comment=True, it will produce
this dictionary:
'a': {
'b': {
'#comment': 'b comment',
'c': {
'#comment': 'c comment',
'#text': '1',
},
'd': '2',
},
}
"""
handler = _DictSAXHandler(namespace_separator=namespace_separator, **kwargs)
if isinstance(xml_input, _unicode):
if not encoding:
encoding = "utf-8"
xml_input = xml_input.encode(encoding)
if not process_namespaces:
namespace_separator = None
parser = expat.ParserCreate(encoding, namespace_separator)
try:
parser.ordered_attributes = True
except AttributeError:
# Jython's expat does not support ordered_attributes
pass
parser.StartNamespaceDeclHandler = handler.startNamespaceDecl
parser.StartElementHandler = handler.startElement
parser.EndElementHandler = handler.endElement
parser.CharacterDataHandler = handler.characters
if process_comments:
parser.CommentHandler = handler.comments
parser.buffer_text = True
if disable_entities:
try:
# Attempt to disable DTD in Jython's expat parser (Xerces-J).
feature = "http://apache.org/xml/features/disallow-doctype-decl"
parser._reader.setFeature(feature, True)
except AttributeError:
# For CPython / expat parser.
# Anything not handled ends up here and entities aren't expanded.
parser.DefaultHandler = lambda x: None
# Expects an integer return; zero means failure -> expat.ExpatError.
parser.ExternalEntityRefHandler = lambda *x: 1
if hasattr(xml_input, "read"):
parser.ParseFile(xml_input)
elif isgenerator(xml_input):
for chunk in xml_input:
parser.Parse(chunk, False)
parser.Parse(b"", True)
else:
parser.Parse(xml_input, True)
return handler.item
def _process_namespace(name, namespaces, ns_sep=":", attr_prefix="@"):
if not namespaces:
return name
try:
ns, name = name.rsplit(ns_sep, 1)
except ValueError:
pass
else:
ns_res = namespaces.get(ns.strip(attr_prefix))
name = (
"{}{}{}{}".format(
attr_prefix if ns.startswith(attr_prefix) else "", ns_res, ns_sep, name
)
if ns_res
else name
)
return name
def _emit(
key,
value,
content_handler,
attr_prefix="@",
cdata_key="#text",
depth=0,
preprocessor=None,
pretty=False,
newl="\n",
indent="\t",
namespace_separator=":",
namespaces=None,
full_document=True,
expand_iter=None,
):
key = _process_namespace(key, namespaces, namespace_separator, attr_prefix)
if preprocessor is not None:
result = preprocessor(key, value)
if result is None:
return
key, value = result
if (
not hasattr(value, "__iter__")
or isinstance(value, _basestring)
or isinstance(value, dict)
):
value = [value]
for index, v in enumerate(value):
if full_document and depth == 0 and index > 0:
raise ValueError("document with multiple roots")
if v is None:
v = ObjectDict()
elif isinstance(v, bool):
if v:
v = _unicode("true")
else:
v = _unicode("false")
elif not isinstance(v, dict):
if (
expand_iter
and hasattr(v, "__iter__")
and not isinstance(v, _basestring)
):
v = ObjectDict(((expand_iter, v),))
else:
v = _unicode(v)
if isinstance(v, _basestring):
v = ObjectDict(((cdata_key, v),))
cdata = None
attrs = ObjectDict()
children = []
for ik, iv in v.items():
if ik == cdata_key:
cdata = iv
continue
if ik.startswith(attr_prefix):
ik = _process_namespace(
ik, namespaces, namespace_separator, attr_prefix
)
if ik == "@xmlns" and isinstance(iv, dict):
for k, v in iv.items():
attr = "xmlns{}".format(":{}".format(k) if k else "")
attrs[attr] = _unicode(v)
continue
if not isinstance(iv, _unicode):
iv = _unicode(iv)
attrs[ik[len(attr_prefix) :]] = iv
continue
children.append((ik, iv))
if pretty:
content_handler.ignorableWhitespace(depth * indent)
content_handler.startElement(key, AttributesImpl(attrs))
if pretty and children:
content_handler.ignorableWhitespace(newl)
for child_key, child_value in children:
_emit(
child_key,
child_value,
content_handler,
attr_prefix,
cdata_key,
depth + 1,
preprocessor,
pretty,
newl,
indent,
namespaces=namespaces,
namespace_separator=namespace_separator,
expand_iter=expand_iter,
)
if cdata is not None:
content_handler.characters(cdata)
if pretty and children:
content_handler.ignorableWhitespace(depth * indent)
content_handler.endElement(key)
if pretty and depth:
content_handler.ignorableWhitespace(newl)
def unparse(
input_dict,
output=None,
encoding="utf-8",
full_document=True,
short_empty_elements=False,
**kwargs
):
"""Emit an XML document for the given `input_dict` (reverse of `parse`).
The resulting XML document is returned as a string, but if `output` (a
file-like object) is specified, it is written there instead.
Dictionary keys prefixed with `attr_prefix` (default=`'@'`) are interpreted
as XML node attributes, whereas keys equal to `cdata_key`
(default=`'#text'`) are treated as character data.
The `pretty` parameter (default=`False`) enables pretty-printing. In this
mode, lines are terminated with `'\n'` and indented with `'\t'`, but this
can be customized with the `newl` and `indent` parameters.
"""
if full_document and len(input_dict) != 1:
raise ValueError("Document must have exactly one root.")
must_return = False
if output is None:
output = StringIO()
must_return = True
if short_empty_elements:
content_handler = XMLGenerator(output, encoding, True)
else:
content_handler = XMLGenerator(output, encoding)
if full_document:
content_handler.startDocument()
for key, value in input_dict.items():
_emit(key, value, content_handler, full_document=full_document, **kwargs)
if full_document:
content_handler.endDocument()
if must_return:
value = output.getvalue()
try: # pragma no cover
value = value.decode(encoding)
except AttributeError: # pragma no cover
pass
return value
if __name__ == "__main__": # pragma: no cover
import sys
import marshal
try:
stdin = sys.stdin.buffer
stdout = sys.stdout.buffer
except AttributeError:
stdin = sys.stdin
stdout = sys.stdout
(item_depth,) = sys.argv[1:]
item_depth = int(item_depth)
def handle_item(path, item):
marshal.dump((path, item), stdout)
return True
try:
root = parse(
stdin,
item_depth=item_depth,
item_callback=handle_item,
dict_constructor=dict,
)
if item_depth == 0:
handle_item([], root)
except KeyboardInterrupt:
pass

View File

@ -29,7 +29,6 @@ from FreeRTOSgdb.GDBCommands import ShowQueueInfo
class Scheduler:
def __init__(self):
self._blocked = ListInspector("xSuspendedTaskList")
self._delayed1 = ListInspector("xDelayedTaskList1")
self._delayed2 = ListInspector("xDelayedTaskList2")

View File

@ -61,7 +61,6 @@ class ShowQueueInfo(gdb.Command):
if maxCount == 0:
print(outputFmt % (q.GetName(), q.GetQueueMessagesWaiting(), "", ""))
else:
for i in range(0, maxCount):
txName = ""
if i < len(sendList):

View File

@ -48,7 +48,6 @@ class HandleRegistry:
print("%d: %3s %16s" % (i, h, name))
def FilterBy(self, qMode):
"""Retrieve a List of Mutex Queue Handles"""
resp = []
for i in range(self._minIndex, self._maxIndex):

View File

@ -56,7 +56,6 @@ class ListInspector:
of some of the TCB Task lists.
"""
if self._list != None:
CastType = None
if CastTypeStr != None:
if type(CastTypeStr) == str:
@ -73,7 +72,6 @@ class ListInspector:
index = self._list["pxIndex"]
if numElems > 0 and numElems < 200:
if startElem == 0:
curr = index
else:

View File

@ -47,7 +47,6 @@ QueueMode.Map = QueueMap
class QueueInspector:
QueueType = gdb.lookup_type("Queue_t")
def __init__(self, handle):

View File

@ -11,7 +11,6 @@ import gdb
class TaskInspector:
TCBType = gdb.lookup_type("TCB_t")
def __init__(self, handle):

View File

@ -28,7 +28,5 @@ directory = path.abspath(directory)
sys.path.append(directory)
from cmdebug.svd_gdb import LoadSVD
from cmdebug.dwt_gdb import DWT
DWT()
LoadSVD()

View File

@ -0,0 +1,35 @@
PyCortexMDebug
==============
## SVD
ARM defines an SVD (System View Description) file format in its CMSIS standard as a means for Cortex-M-based chip manufacturers to provide a common description of peripherals, registers, and register fields. You can download SVD files for different manufacturers [here](http://www.arm.com/products/processors/cortex-m/cortex-microcontroller-software-interface-standard.php).
The implementation consists of two components -- An lxml-based parser module (pysvd) and a GDB file (gdb_svd). I haven't yet worked out a perfect workflow for this, though it's quite easy to use when you already tend to have a GDB initialization file for starting up OpenOCD and the like. However your workflow works, just make sure to, in GDB:
source gdb_svd.py
svd_load [your_svd_file].svd
These files can be huge so it might take a second or two. Anyways, after that, you can do
svd
to list available peripherals with descriptions. Or you can do
svd [some_peripheral_name]
to see all of the registers (with their values) for a given peripheral. For more details, run
svd [some_peripheral_name] [some_register_name]
to see all of the field values with descriptions.
You can add format modifiers like:
* `svd/x` will display values in hex
* `svd/o` will display values in octal
* `svd/t` or `svd/b` will display values in binary
* `svd/a` will display values in hex and try to resolve symbols from the values
All field values are displayed at the correct lengths as provided by the SVD files.
Also, tab completion exists for nearly everything! When in doubt, run `svd help`.

View File

@ -16,15 +16,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 collections import OrderedDict
from . import x2d
import traceback
import warnings
import pickle
import lxml.objectify as objectify
import sys
from collections import OrderedDict
import os
import pickle
import traceback
import re
import warnings
class SmartDict:
@ -127,31 +126,26 @@ class SVDFile:
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 = SmartDict()
self.base_address = 0
xml_file_name = os.path.expanduser(fname)
pickle_file_name = xml_file_name + ".pickle"
root = None
if os.path.exists(pickle_file_name):
print("Loading pickled SVD")
root = pickle.load(open(pickle_file_name, "rb"))
else:
print("Loading XML SVD and pickling it")
root = x2d.parse(open(xml_file_name, "rb"))
pickle.dump(root, open(pickle_file_name, "wb"), pickle.HIGHEST_PROTOCOL)
print("Processing SVD tree")
# XML elements
for p in root["device"]["peripherals"]["peripheral"]:
for p in periph:
try:
self.peripherals[p["name"]] = SVDPeripheral(p, self)
except SVDNonFatalError as e:
# print(e)
if p.tag == "peripheral":
self.peripherals[str(p.name)] = SVDPeripheral(p, self)
else:
# This is some other tag
pass
print("SVD Ready")
except SVDNonFatalError as e:
print(e)
def add_register(parent, node):
@ -271,11 +265,11 @@ class SVDPeripheral:
self.parent_base_address = parent.base_address
# Look for a base address, as it is required
if "baseAddress" not in svd_elem:
if not hasattr(svd_elem, "baseAddress"):
raise SVDNonFatalError("Periph without base address")
self.base_address = int(str(svd_elem.baseAddress), 0)
if "@derivedFrom" in svd_elem:
derived_from = svd_elem["@derivedFrom"]
if "derivedFrom" in svd_elem.attrib:
derived_from = svd_elem.attrib["derivedFrom"]
try:
self.name = str(svd_elem.name)
except AttributeError:
@ -301,14 +295,16 @@ class SVDPeripheral:
self.clusters = SmartDict()
if hasattr(svd_elem, "registers"):
if "register" in svd_elem.registers:
for r in svd_elem.registers.register:
if isinstance(r, x2d.ObjectDict):
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)
if "cluster" in svd_elem.registers:
for c in svd_elem.registers.cluster:
if isinstance(c, x2d.ObjectDict):
add_cluster(self, c)
def refactor_parent(self, parent):
self.parent_base_address = parent.base_address
@ -342,10 +338,10 @@ class SVDPeripheralRegister:
else:
self.size = 0x20
self.fields = SmartDict()
if "fields" in svd_elem:
if hasattr(svd_elem, "fields"):
# Filter fields to only consider those of tag "field"
for f in svd_elem.fields.field:
if isinstance(f, x2d.ObjectDict):
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):

View File

@ -18,7 +18,7 @@ def GetDevices(env):
def generate(env, **kw):
env.AddMethod(GetDevices)
env.SetDefault(
FBT_DEBUG_DIR="${ROOT_DIR}/debug",
FBT_DEBUG_DIR="${FBT_SCRIPT_DIR}/debug",
)
if (adapter_serial := env.subst("$OPENOCD_ADAPTER_SERIAL")) != "auto":

View File

@ -170,7 +170,6 @@ class Main(App):
"update.dir",
"sdk_headers.dir",
"lib.dir",
"debug.dir",
"scripts.dir",
)

View File

@ -186,6 +186,33 @@ dist_env.PhonyTarget(
FBT_FAP_DEBUG_ELF_ROOT=path_as_posix(dist_env.subst("$FBT_FAP_DEBUG_ELF_ROOT")),
)
# Debug alien elf
debug_other_opts = [
"-ex",
"source ${FBT_DEBUG_DIR}/PyCortexMDebug/PyCortexMDebug.py",
"-ex",
"source ${FBT_DEBUG_DIR}/flipperversion.py",
"-ex",
"fw-version",
]
dist_env.PhonyTarget(
"debug_other",
"${GDBPYCOM}",
GDBOPTS="${GDBOPTS_BASE}",
GDBREMOTE="${OPENOCD_GDB_PIPE}",
GDBPYOPTS=debug_other_opts,
)
dist_env.PhonyTarget(
"debug_other_blackmagic",
"${GDBPYCOM}",
GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}",
GDBREMOTE="${BLACKMAGIC_ADDR}",
GDBPYOPTS=debug_other_opts,
)
dist_env.PhonyTarget(
"flash_blackmagic",
"$GDB $GDBOPTS $SOURCES $GDBFLASH",

View File

@ -78,10 +78,8 @@ def generate(env, **kw):
env.SetDefault(
# Paths
SDK_DEFINITION=env.File(sdk_data["sdk_symbols"]),
FBT_DEBUG_DIR=pathlib.Path(
sdk_current_sdk_dir_node.Dir(sdk_components["debug.dir"]).abspath
).as_posix(),
FBT_SCRIPT_DIR=scripts_dir,
FBT_DEBUG_DIR=scripts_dir.Dir("debug"),
LIBPATH=sdk_current_sdk_dir_node.Dir(sdk_components["lib.dir"]),
FW_ELF=sdk_current_sdk_dir_node.File(sdk_components["firmware.elf"]),
FW_BIN=sdk_current_sdk_dir_node.File(sdk_components["full.bin"]),