Skip to content

Commit 4fa1123

Browse files
Merge pull request #1771
Add support for cbuild-run.yml
2 parents 28de6ed + 1f5da0c commit 4fa1123

File tree

12 files changed

+1124
-23
lines changed

12 files changed

+1124
-23
lines changed

docs/target_support.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,3 +211,19 @@ _Note:_ PyOCD can work with expanded packs just like zipped .pack files. Pass th
211211
of the pack using the `--pack` argument, as above. This is very useful for cases such as development or
212212
debugging of a pack, or for working with other CMSIS-Pack managers that store packs in decompressed form.
213213

214+
#### CMSIS-Toolbox Run and Debug Management
215+
216+
Run and Debug Management is a part of the [CMSIS-Toolbox](https://github.com/Open-CMSIS-Pack/cmsis-toolbox) build
217+
information files when working with CSolution projects. Each `*.cbuild-run.yml` file contains relevant run and
218+
debug information for a single `target-type` defined in a CSolution project. The tool gathers and filters the
219+
target information from the BSP and DFP.
220+
221+
To use the `cbuild-run` target information in PyOCD, pass the `--cbuild-run` option followed by the path to
222+
desired `*.cbuild-run.yml` file. The target information is not cached in PyOCD, which means the command-line
223+
option should always be passed when launching the debugger.
224+
225+
Because a single `target-type` is described in the YAML file, the `--target`
226+
command-line argument is not needed, as the target is automatically selected.
227+
228+
More information on CMSIS-Toolbox and the CSolution project format for Run and Debug Management can be found in the
229+
[CMSIS-Toolbox Documentation](https://open-cmsis-pack.github.io/cmsis-toolbox/YML-CBuild-Format/#run-and-debug-management).

pyocd/board/board.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# pyOCD debugger
2-
# Copyright (c) 2006-2013,2018 Arm Limited
2+
# Copyright (c) 2006-2013,2018,2025 Arm Limited
33
# Copyright (c) 2021-2022 Chris Reed
44
# Copyright (c) 2023 Benjamin Sølberg
55
# SPDX-License-Identifier: Apache-2.0
@@ -22,6 +22,7 @@
2222
from ..core import exceptions
2323
from ..target import (TARGET, normalise_target_type_name)
2424
from ..target.pack import pack_target
25+
from ..target.pack.cbuild_run import CbuildRun
2526
from ..utility.graph import GraphNode
2627

2728
if TYPE_CHECKING:
@@ -63,10 +64,18 @@ def __init__(self,
6364
"""
6465
super().__init__()
6566

67+
# Create cbuild_run if option is provided
68+
if session.options.is_set('cbuild_run'):
69+
cbuild_run = CbuildRun(session.options.get('cbuild_run'))
70+
else:
71+
cbuild_run = None
72+
6673
# Use the session option if no target type was given to us.
6774
if target is None:
6875
if session.options.is_set('target_override'):
6976
target = session.options.get('target_override')
77+
elif cbuild_run and cbuild_run.target:
78+
target = cbuild_run.target
7079
elif board_info:
7180
target = board_info.target
7281

@@ -99,11 +108,15 @@ def __init__(self,
99108
self._delegate = None
100109
self._inited = False
101110

111+
# Create target from cbuild-run information.
112+
if cbuild_run and cbuild_run.target:
113+
cbuild_run.populate_target(target)
114+
102115
# Create targets from provided CMSIS pack.
103116
if session.options['pack'] is not None:
104117
pack_target.PackTargets.populate_targets_from_pack(session.options['pack'])
105118

106-
# Create targets from the cmsis-pack-manager cache.
119+
# Create target from the cmsis-pack-manager cache.
107120
if self._target_type not in TARGET:
108121
pack_target.ManagedPacks.populate_target(target)
109122

pyocd/gdbserver/gdbserver.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ class GDBServer(threading.Thread):
108108
## Timer delay for sending the notification that the server is listening.
109109
START_LISTENING_NOTIFY_DELAY = 0.03 # 30 ms
110110

111-
def __init__(self, session, core=None):
111+
def __init__(self, session, core=None, port=None):
112112
super().__init__()
113113
self.session = session
114114
self.board = session.board
@@ -120,9 +120,13 @@ def __init__(self, session, core=None):
120120
self.target = self.board.target.cores[core]
121121
self.name = "gdb-server-core%d" % self.core
122122

123-
self.port = session.options.get('gdbserver_port')
124-
if self.port != 0:
125-
self.port += self.core
123+
if port is None:
124+
self.port = session.options.get('gdbserver_port')
125+
if self.port != 0:
126+
self.port += self.core
127+
else:
128+
self.port = port
129+
126130
self.telnet_port = session.options.get('telnet_port')
127131
if self.telnet_port != 0:
128132
self.telnet_port += self.core
@@ -328,7 +332,7 @@ def run(self):
328332

329333
while not self.shutdown_event.is_set():
330334
connected = self.abstract_socket.connect()
331-
if connected != None:
335+
if connected is not None:
332336
self.packet_io = GDBServerPacketIOThread(self.abstract_socket)
333337
break
334338

pyocd/subcommands/base.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# pyOCD debugger
22
# Copyright (c) 2021-2023 Chris Reed
3+
# Copyright (c) 2025 Arm Limited
34
# SPDX-License-Identifier: Apache-2.0
45
#
56
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -68,6 +69,8 @@ class CommonOptions:
6869
help="(Deprecated) Send setting to DAPAccess layer.")
6970
CONFIG_GROUP.add_argument("--pack", metavar="PATH", action="append",
7071
help="Path to the .pack file for a CMSIS Device Family Pack.")
72+
CONFIG_GROUP.add_argument("--cbuild-run", metavar="PATH",
73+
help="Path to the .cbuild-run.yml file for CSolution Run and Debug Management.")
7174

7275
# Define common options for all subcommands, including logging options.
7376
COMMON = argparse.ArgumentParser(description='common',

pyocd/subcommands/erase_cmd.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# pyOCD debugger
22
# Copyright (c) 2021 Chris Reed
3+
# Copyright (c) 2025 Arm Limited
34
# SPDX-License-Identifier: Apache-2.0
45
#
56
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -80,6 +81,7 @@ def invoke(self) -> int:
8081
user_script=self._args.script,
8182
no_config=self._args.no_config,
8283
pack=self._args.pack,
84+
cbuild_run=self._args.cbuild_run,
8385
unique_id=self._args.unique_id,
8486
target_override=self._args.target_override,
8587
frequency=self._args.frequency,
@@ -99,5 +101,3 @@ def invoke(self) -> int:
99101
eraser.erase(addresses)
100102

101103
return 0
102-
103-

pyocd/subcommands/gdbserver_cmd.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# pyOCD debugger
22
# Copyright (c) 2021 Chris Reed
3+
# Copyright (c) 2025 Arm Limited
34
# SPDX-License-Identifier: Apache-2.0
45
#
56
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -91,6 +92,8 @@ def get_args(cls) -> List[argparse.ArgumentParser]:
9192
help="Run command (OpenOCD compatibility).")
9293
gdbserver_options.add_argument("-bh", "--soft-bkpt-as-hard", dest="soft_bkpt_as_hard", default=False, action="store_true",
9394
help="Replace software breakpoints with hardware breakpoints.")
95+
gdbserver_options.add_argument("--reset-run", action="store_true",
96+
help="Reset and run before running GDB server")
9497

9598
return [cls.CommonOptions.COMMON, cls.CommonOptions.CONNECT, gdbserver_parser]
9699

@@ -182,6 +185,7 @@ def invoke(self) -> int:
182185
config_file=self._args.config,
183186
no_config=self._args.no_config,
184187
pack=self._args.pack,
188+
cbuild_run=self._args.cbuild_run,
185189
unique_id=self._args.unique_id,
186190
target_override=self._args.target_override,
187191
frequency=self._args.frequency,
@@ -215,6 +219,10 @@ def invoke(self) -> int:
215219
session.probeserver = probe_server
216220
probe_server.start()
217221

222+
# Reset and run the target
223+
if self._args.reset_run:
224+
session.board.target.reset()
225+
218226
# Start up the gdbservers.
219227
for core_number, core in session.board.target.cores.items():
220228
# Don't create a server for CPU-less memory Access Port.
@@ -223,7 +231,11 @@ def invoke(self) -> int:
223231
# Don't create a server if this core is not listed by the user.
224232
if core_number not in core_list:
225233
continue
226-
gdb = GDBServer(session, core=core_number)
234+
# Read pname and port mapping from cbuild-run.
235+
port_number = None
236+
if self._args.cbuild_run:
237+
port_number = session.target.get_gdbserver_port(core.node_name)
238+
gdb = GDBServer(session, core=core_number, port=port_number)
227239
# Only subscribe to the server for the first core, so echo messages aren't printed
228240
# multiple times.
229241
if not gdbs:

pyocd/subcommands/json_cmd.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# pyOCD debugger
22
# Copyright (c) 2021 Chris Reed
3+
# Copyright (c) 2025 Arm Limited
34
# SPDX-License-Identifier: Apache-2.0
45
#
56
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,7 +24,7 @@
2324
from .base import SubcommandBase
2425
from ..core.session import Session
2526
from ..tools.lists import ListGenerator
26-
from ..target.pack import pack_target
27+
from ..target.pack import pack_target, cbuild_run
2728
from ..utility.cmdline import convert_session_options
2829
from .. import __version__
2930

@@ -92,13 +93,17 @@ def invoke(self) -> int:
9293
config_file=self._args.config,
9394
no_config=self._args.no_config,
9495
pack=self._args.pack,
96+
cbuild_run=self._args.cbuild_run,
9597
**convert_session_options(self._args.options)
9698
)
9799

98100
if self._args.targets or self._args.boards:
99101
# Create targets from provided CMSIS pack.
100102
if session.options['pack'] is not None:
101103
pack_target.PackTargets.populate_targets_from_pack(session.options['pack'])
104+
# Create target from provided CbuildRun file.
105+
if session.options['cbuild_run'] is not None:
106+
cbuild_run.CbuildRun(session.options['cbuild_run']).populate_target()
102107

103108
if self._args.probes:
104109
obj = ListGenerator.list_probes()
@@ -122,4 +127,3 @@ def invoke(self) -> int:
122127

123128
print(json.dumps(obj, indent=4))
124129
return exit_status
125-

pyocd/subcommands/list_cmd.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# pyOCD debugger
22
# Copyright (c) 2021 Chris Reed
3+
# Copyright (c) 2025 Arm Limited
34
# SPDX-License-Identifier: Apache-2.0
45
#
56
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,7 +23,7 @@
2223
from ..core.helpers import ConnectHelper
2324
from ..core.session import Session
2425
from ..tools.lists import ListGenerator
25-
from ..target.pack import pack_target
26+
from ..target.pack import pack_target, cbuild_run
2627
from ..utility.cmdline import convert_session_options
2728

2829
LOG = logging.getLogger(__name__)
@@ -60,7 +61,7 @@ def get_args(cls) -> List[argparse.ArgumentParser]:
6061
help="Restrict listing to items matching the given name substring. Applies to targets and boards.")
6162
list_options.add_argument('-r', '--vendor',
6263
help="Restrict listing to items whose vendor matches the given name substring. Applies only to targets.")
63-
list_options.add_argument('-s', '--source', choices=('builtin', 'pack'),
64+
list_options.add_argument('-s', '--source', choices=('builtin', 'pack', 'cbuild-run'),
6465
help="Restrict listing to targets from the specified source. Applies to targets.")
6566
list_options.add_argument('-H', '--no-header', action='store_true',
6667
help="Don't print a table header.")
@@ -87,6 +88,7 @@ def invoke(self) -> int:
8788
config_file=self._args.config,
8889
no_config=self._args.no_config,
8990
pack=self._args.pack,
91+
cbuild_run=self._args.cbuild_run,
9092
**convert_session_options(self._args.options)
9193
)
9294

@@ -96,6 +98,9 @@ def invoke(self) -> int:
9698
# Create targets from provided CMSIS pack.
9799
if session.options['pack'] is not None:
98100
pack_target.PackTargets.populate_targets_from_pack(session.options['pack'])
101+
# Create target from provided CbuildRun file.
102+
if session.options['cbuild_run'] is not None:
103+
cbuild_run.CbuildRun(session.options['cbuild_run']).populate_target()
99104

100105
obj = ListGenerator.list_targets(name_filter=self._args.name,
101106
vendor_filter=self._args.vendor,
@@ -135,4 +140,3 @@ def invoke(self) -> int:
135140
print(pt)
136141

137142
return 0
138-

pyocd/subcommands/load_cmd.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# pyOCD debugger
22
# Copyright (c) 2021-2022 Chris Reed
3+
# Copyright (c) 2025 Arm Limited
34
# SPDX-License-Identifier: Apache-2.0
45
#
56
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -65,9 +66,9 @@ def get_args(cls) -> List[argparse.ArgumentParser]:
6566
parser_options.add_argument("--no-reset", action="store_true",
6667
help="Specify to prevent resetting device after programming has finished.")
6768

68-
parser.add_argument("file", metavar="<file-path>", nargs="+",
69+
parser.add_argument("file", metavar="<file-path>", nargs="*",
6970
help="File to write to memory. Binary files can have an optional base address appended to the file "
70-
"name as '@<address>', for instance 'app.bin@0x20000'.")
71+
"name as '@<address>', for instance 'app.bin@0x20000'. Optional if '--cbuild-run' is used.")
7172

7273
return [cls.CommonOptions.COMMON, cls.CommonOptions.CONNECT, parser]
7374

@@ -76,6 +77,9 @@ def invoke(self) -> int:
7677
self._increase_logging(["pyocd.flash.loader", __name__])
7778

7879
# Validate arguments.
80+
if (self._args.cbuild_run is None) and not self._args.file:
81+
raise ValueError("Positional argument <file-path> is required when '--cbuild-run' is not used.")
82+
7983
if (self._args.base_address is not None) and (len(self._args.file) > 1):
8084
raise ValueError("--base-address cannot be set when loading more than one file; "
8185
"use a base address suffix instead")
@@ -86,6 +90,7 @@ def invoke(self) -> int:
8690
user_script=self._args.script,
8791
no_config=self._args.no_config,
8892
pack=self._args.pack,
93+
cbuild_run=self._args.cbuild_run,
8994
unique_id=self._args.unique_id,
9095
target_override=self._args.target_override,
9196
frequency=self._args.frequency,
@@ -102,6 +107,10 @@ def invoke(self) -> int:
102107
chip_erase=self._args.erase,
103108
trust_crc=self._args.trust_crc,
104109
no_reset=self._args.no_reset)
110+
if not self._args.file and self._args.cbuild_run:
111+
# Populate file list from cbuild-run output if not provided explicitly
112+
cbuild_files = session.target.get_output()
113+
self._args.file = cbuild_files.keys()
105114
for filename in self._args.file:
106115
# Get an initial path with the argument as-is.
107116
file_path = Path(filename).expanduser()
@@ -117,6 +126,8 @@ def invoke(self) -> int:
117126
return 1
118127
else:
119128
base_address = self._args.base_address
129+
if base_address is None and self._args.cbuild_run:
130+
base_address = cbuild_files[filename]
120131

121132
# Resolve our path.
122133
file_path = Path(filename).expanduser().resolve()
@@ -133,5 +144,3 @@ def invoke(self) -> int:
133144
file_format=self._args.format)
134145

135146
return 0
136-
137-

pyocd/subcommands/reset_cmd.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# pyOCD debugger
22
# Copyright (c) 2021-2022 Chris Reed
3+
# Copyright (c) 2025 Arm Limited
34
# SPDX-License-Identifier: Apache-2.0
45
#
56
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -52,7 +53,7 @@ def get_args(cls) -> List[argparse.ArgumentParser]:
5253
"from the core chosen with --core will be used (usually 'sw' but can be differ based "
5354
"on the target type).")
5455
reset_options.add_argument("-c", "--core", default=0, type=int_base_0,
55-
help="Core number used to perform software reset. Only applies to software reset methods."
56+
help="Core number used to perform software reset. Only applies to software reset methods. "
5657
"Default is core 0.")
5758
reset_options.add_argument("-l", "--halt", action="store_true",
5859
help="Halt the core on the first instruction after reset. Defaults to disabled.")
@@ -77,6 +78,7 @@ def invoke(self) -> None:
7778
user_script=self._args.script,
7879
no_config=self._args.no_config,
7980
pack=self._args.pack,
81+
cbuild_run=self._args.cbuild_run,
8082
unique_id=self._args.unique_id,
8183
target_override=self._args.target_override,
8284
frequency=self._args.frequency,
@@ -130,4 +132,3 @@ def invoke(self) -> None:
130132
LOG.info("Done.")
131133
finally:
132134
session.close()
133-

0 commit comments

Comments
 (0)