west: runners: Add `west rtt` command with pyocd implementation

This command runs separately from a debug server, instead of attaching
to a running server. This is both the easiest out of the box experience,
and also should be possible to implement consistently for most runners.

This commit includes an initial implementation for pyocd.

Signed-off-by: Tobias Pisani <mail@topisani.dev>
This commit is contained in:
Tobias Pisani 2024-07-05 12:42:12 +02:00 committed by Anas Nashif
parent 74bb5b334d
commit b3b8360f39
5 changed files with 106 additions and 6 deletions

View File

@ -46,6 +46,9 @@ west-commands:
- name: attach
class: Attach
help: interactively debug a board
- name: rtt
class: Rtt
help: open an rtt shell
- file: scripts/west_commands/export.py
commands:
- name: zephyr-export

View File

@ -74,3 +74,21 @@ class Attach(WestCommand):
def do_run(self, my_args, runner_args):
do_run_common(self, my_args, runner_args)
class Rtt(WestCommand):
def __init__(self):
super(Rtt, self).__init__(
'rtt',
# Keep this in sync with the string in west-commands.yml.
'open an rtt shell',
"",
accepts_unknown_args=True)
self.runner_key = 'rtt-runner' # in runners.yaml
def do_add_parser(self, parser_adder):
return add_parser_common(self, parser_adder)
def do_run(self, my_args, runner_args):
do_run_common(self, my_args, runner_args)

View File

@ -628,7 +628,8 @@ def get_runner_config(build_dir, yaml_path, runners_yaml, args=None):
filetype('file_type'),
config('gdb'),
config('openocd'),
config('openocd_search', []))
config('openocd_search', []),
config('rtt_address'))
def dump_traceback():
# Save the current exception to a file and return its path.

View File

@ -29,6 +29,13 @@ from inspect import isabstract
from typing import Dict, List, NamedTuple, NoReturn, Optional, Set, Type, \
Union
try:
from elftools.elf.elffile import ELFFile
ELFTOOLS_MISSING = False
except ImportError:
ELFTOOLS_MISSING = True
# Turn on to enable just logging the commands that would be run (at
# info rather than debug level), without actually running them. This
# can break runners that are expecting output or if one command
@ -37,6 +44,27 @@ _DRY_RUN = False
_logger = logging.getLogger('runners')
# FIXME: I assume this code belongs somewhere else, but i couldn't figure out
# a good location for it, so i put it here for now
# We could potentially search for RTT blocks in hex or bin files as well,
# but since the magic string is "SEGGER RTT", i thought it might be better
# to avoid, at the risk of false positives.
def find_rtt_block(elf_file: str) -> Optional[int]:
if ELFTOOLS_MISSING:
raise RuntimeError('the Python dependency elftools was missing; '
'see the getting started guide for details on '
'how to fix')
with open(elf_file, 'rb') as f:
elffile = ELFFile(f)
for sect in elffile.iter_sections('SHT_SYMTAB'):
symbols = sect.get_symbol_by_name('_SEGGER_RTT')
if symbols is None:
continue
for s in symbols:
return s.entry.get('st_value')
return None
class _DebugDummyPopen:
@ -219,7 +247,7 @@ class MissingProgram(FileNotFoundError):
super().__init__(errno.ENOENT, os.strerror(errno.ENOENT), program)
_RUNNERCAPS_COMMANDS = {'flash', 'debug', 'debugserver', 'attach', 'simulate', 'robot'}
_RUNNERCAPS_COMMANDS = {'flash', 'debug', 'debugserver', 'attach', 'simulate', 'robot', 'rtt'}
@dataclass
class RunnerCaps:
@ -231,7 +259,7 @@ class RunnerCaps:
Available capabilities:
- commands: set of supported commands; default is {'flash',
'debug', 'debugserver', 'attach', 'simulate', 'robot'}.
'debug', 'debugserver', 'attach', 'simulate', 'robot', 'rtt'}.
- dev_id: whether the runner supports device identifiers, in the form of an
-i, --dev-id option. This is useful when the user has multiple debuggers
@ -268,6 +296,9 @@ class RunnerCaps:
discovered in the build directory.
- hide_load_files: whether the elf/hex/bin file arguments should be hidden.
- rtt: whether the runner supports SEGGER RTT. This adds a --rtt-address
option.
'''
commands: Set[str] = field(default_factory=lambda: set(_RUNNERCAPS_COMMANDS))
@ -279,6 +310,8 @@ class RunnerCaps:
tool_opt: bool = False
file: bool = False
hide_load_files: bool = False
rtt: bool = False # This capability exists separately from the rtt command
# to allow other commands to use the rtt address
def __post_init__(self):
if not self.commands.issubset(_RUNNERCAPS_COMMANDS):
@ -319,6 +352,7 @@ class RunnerConfig(NamedTuple):
gdb: Optional[str] = None # path to a usable gdb
openocd: Optional[str] = None # path to a usable openocd
openocd_search: List[str] = [] # add these paths to the openocd search path
rtt_address: Optional[int] = None # address of the rtt control block
_YN_CHOICES = ['Y', 'y', 'N', 'n', 'yes', 'no', 'YES', 'NO']
@ -572,6 +606,13 @@ class ZephyrBinaryRunner(abc.ABC):
help=(cls.tool_opt_help() if caps.tool_opt
else argparse.SUPPRESS))
if caps.rtt:
parser.add_argument('--rtt-address', dest='rtt_address',
type=lambda x: int(x, 0),
help="address of RTT control block. If not supplied, it will be autodetected if possible")
else:
parser.add_argument('--rtt-address', help=argparse.SUPPRESS)
# Runner-specific options.
cls.do_add_parser(parser)
@ -607,6 +648,8 @@ class ZephyrBinaryRunner(abc.ABC):
raise ValueError("--file-type requires --file")
if args.file_type and not caps.file:
_missing_cap(cls, '--file-type')
if args.rtt_address and not caps.rtt:
_missing_cap(cls, '--rtt-address')
ret = cls.do_create(cfg, args)
if args.erase:
@ -731,6 +774,19 @@ class ZephyrBinaryRunner(abc.ABC):
raise MissingProgram(program)
return ret
def get_rtt_address(self) -> int | None:
'''Helper method for extracting a the RTT control block address.
If args.rtt_address was supplied, returns that.
Otherwise, attempt to locate an rtt block in the elf file.
If this is not found, None is returned'''
if self.cfg.rtt_address is not None:
return self.cfg.rtt_address
elif self.cfg.elf_file is not None:
return find_rtt_block(self.cfg.elf_file)
return None
def run_server_and_client(self, server, client, **kwargs):
'''Run a server that ignores SIGINT, and a client that handles it.

View File

@ -76,9 +76,9 @@ class PyOcdBinaryRunner(ZephyrBinaryRunner):
@classmethod
def capabilities(cls):
return RunnerCaps(commands={'flash', 'debug', 'debugserver', 'attach'},
return RunnerCaps(commands={'flash', 'debug', 'debugserver', 'attach', 'rtt'},
dev_id=True, flash_addr=True, erase=True,
tool_opt=True)
tool_opt=True, rtt=True)
@classmethod
def dev_id_help(cls) -> str:
@ -142,7 +142,9 @@ class PyOcdBinaryRunner(ZephyrBinaryRunner):
def do_run(self, command, **kwargs):
self.require(self.pyocd)
if command == 'flash':
if command == 'rtt':
self.rtt(**kwargs)
elif command == 'flash':
self.flash(**kwargs)
else:
self.debug_debugserver(command, **kwargs)
@ -214,3 +216,23 @@ class PyOcdBinaryRunner(ZephyrBinaryRunner):
self.require(client_cmd[0])
self.log_gdbserver_message()
self.run_server_and_client(server_cmd, client_cmd)
def rtt(self):
rtt_addr = self.get_rtt_address()
if rtt_addr is None:
raise ValueError('RTT control block not found')
self.logger.debug(f'rtt address: 0x{rtt_addr:x}')
cmd = ([self.pyocd] +
['rtt'] +
self.pyocd_config_args +
self.daparg_args +
self.target_args +
self.board_args +
self.frequency_args +
self.tool_opt_args +
['-a', f'0x{rtt_addr:x}'])
self.check_call(cmd)