Arm CMSIS Debugger
The Arm® CMSIS Debugger extension pack is a comprehensive debug platform for Arm Cortex-M processor-based devices that uses the GDB/MI protocol.
- Supports single and multi-core processor systems.
- Built-in RTOS kernel support for FreeRTOS, RTX, ThreadX, and Zephyr.
- Wide debug adapter support for CMSIS-DAP (ULink, MCULink, NuLink, etc.), JLink, and ST-Link.
- Can be combined with other VS Code debug extensions, such as those for Linux application debugging.
The Arm CMSIS Debugger includes pyOCD for target connection and Flash download, GNU GDB for core debug features, and adds these VS Code extensions:
This extension is free to use and you can install it individually or as part of the Arm Keil® Studio pack. For optimum debugger experience, use it with these extensions (included in the Arm Keil Studio pack):
Debugger Configuration
VS Code uses the file .vscode/launch.json
to configure target-specific debug parameters such as project files, device, and debug adapter. The Arm CMSIS Solution automatically generates this file based on the csolution project with all required settings, streamlining this setup. It provides both launch and attach configurations; for a multi-processor system, each core gets an attach configuration, while the start core also gets a launch configuration.
To start debugging, the CMSIS Solution offers action buttons and menu commands.

- Load & Debug application starts the CMSIS Debugger with launch configuration.
- Load & Run application starts program execution and the GDB server; use then attach configurations to connect to the running system.
- Target Information (...) shows the available debugger adapters.
Debugger User Interface
Many features of the CMSIS Debugger extension are exposed in the Run and Debug view of VS Code.
- Start debugging selects a configuration: launch to start download/debug, attach to connect with a running system.
- Debug Toolbar has buttons for the most common debugging actions that control execution.
- Debug Statusbar shows the configuration along with the workspace name. A color change indicates an active debug session.

Most editor features are available during debugging. For example, developers can use Find and edit source code to correct program errors.
The Run and Debug view provides:
- VARIABLES section, which includes local function variables and CPU register values.
- WATCH section, which allows viewing user-defined expressions, for example, variable values.
- CALL STACK section that shows active RTOS threads along with the call stack.
- BREAKPOINTS section for managing stop points in application execution to inspect the state.
TIP
Click on a line number badge to navigate to the source code line.
Other debugger specific views or features:
- Disassembly shows assembly instructions and supports run control, for example with stepping and breakpoints.
- Debug Console lists debug output messages and allows entering expressions or GDB commands.
- Peripherals show the device peripheral registers and allow changing their values.
- Serial Monitor uses serial or TCP communication to interact with application I/O functions (
printf
, getc
, etc.).
- CPU Time shows execution timing and statistics of the past five breakpoints.
- Multi-Core Debug to view and control several processors in a device.
During debugging, the Debug toolbar contains actions to control the flow of the debug session, such as stepping through code, pausing execution, and stopping the debug session.

Action |
Description |
Continue/Pause |
Continue: Resume normal program execution (up to the next breakpoint). Pause: Inspect code executing at the current location. |
Step Over |
Execute the next statement as a single command without inspecting or following its component steps. |
Step Into |
Enter the next statement to follow its execution line-by-line. |
Step Out |
When inside a function, return to the earlier execution context by completing remaining lines of the current method as though it were a single command. |
Restart |
Terminate the current program execution and start debugging again using the current run configuration. |
Stop/Disconnect |
Stop: Terminate the current debug session. Disconnect: Detach debugger from a core without changing the execution status (running/pause). |
Debug Session |
For multi-core devices, the list of active debug sessions and switch between them. |
Reset Target |
Reset the target device. |
VARIABLES
During debugging, you can inspect variables, expressions, and registers in the VARIABLES section of the Run and Debug view or by hovering over a variable or expression in the source code editor. Variable values and expressions are evaluated in the context of the selected stack frame in the CALL STACK section. In the case of multi-core, the content is relative to the active debug session.

To change the value of a variable during the debugging session, right-click on the variable in the VARIABLES section and select Set Value.
You can use the Copy Value action to copy the variable's value, or the Copy as Expression action to copy an
expression to access the variable. You can then use this expression in the WATCH section.
To filter variables by their name or value, use the Alt/Opt + Ctrl/Cmd + F keyboard shortcut while the focus is on the
VARIABLES section, and type a search term.

WATCH
Variables and expressions can also be evaluated and watched in the WATCH section.
You can use the Copy Value action to copy the variable's value, or the Copy as Expression action to copy an expression to access the variable. You can then use this expression in the WATCH section.

CALL STACK
The CALL STACK section shows the function call tree that is currently on the stack. Threads are shown for applications
that use an RTOS. Each function call is associated to its location and when source code is available a line number badge is shown. A click on this badge navigates to source file location.
The window content is updated whenever program execution stops.

BREAKPOINTS
A breakpoint pauses the code execution at a specific point, so you can inspect the state of your
application at that point. There are several breakpoint types.
Setting breakpoints
To set or unset a breakpoint, click on the editor margin or use F9 on the current line.
- Breakpoints in the editor margin are normally shown as red-filled circles.
- Disabled breakpoints have a filled grey circle.
- When a debugging session starts, breakpoints that can't be registered with the debugger change to a grey hollow
circle. The same might happen if the source is edited while a debug session without live-edit support is running.

For more control of breakpoints, use the BREAKPOINTS section that lists and manages all breakpoints.

Breakpoint types
Conditional breakpoints
Set breakpoint conditions based on expressions, hit counts, or a combination of both.
- Expression condition: The breakpoint is hit whenever the expression evaluates to true.
- Hit count: The hit count controls how many times a breakpoint needs to be hit before it interrupts execution.
- Wait for breakpoint: The breakpoint is activated when another breakpoint is hit (triggered breakpoint).
To add a conditional breakpoint:
Create a conditional breakpoint
- Right-click in the editor margin and select Add Conditional Breakpoint.
- Use the Add Conditional Breakpoint command in the Command Palette (⇧⌘P).
Choose the type of condition you want to set (expression, hit count, or wait for a breakpoint).

To add a condition to an existing breakpoint:
Edit an existing breakpoint
- Right-click on the breakpoint in the editor margin and select Edit Breakpoint.
- Select the pencil icon next for an existing breakpoint in the BREAKPOINTS section of the Run and Debug view.
Edit the condition (expression, hit count, or wait for breakpoint).
Triggered breakpoints
A triggered breakpoint is type of conditional breakpoint that is enabled once another breakpoint is hit. They can
be useful when diagnosing failure cases in code that happen only after a certain precondition.
Triggered breakpoints can be set by right-clicking on the glyph margin, selecting Add Triggered Breakpoint, and
then, choose which other breakpoint enables the breakpoint.

Inline breakpoints
Inline breakpoints are only hit when the execution reaches the column associated with the inline breakpoint.
This is useful when debugging minified code, which contains multiple statements in a single line.
An inline breakpoint can be set using Shift + F9 or through the context menu during a debug session.
Inline breakpoints are shown inline in the editor.
Inline breakpoints can also have conditions. Editing multiple breakpoints on a line is possible through the
context menu in the editor's left margin.
Function breakpoints
Instead of placing breakpoints directly in source code, a debugger can support creating breakpoints by specifying
a function name. This is useful in situations where the source is not available but a function name is known.
To create a function breakpoint, select the + button in the BREAKPOINTS section header and enter the function
name. Function breakpoints are shown with a red triangle in the BREAKPOINTS section.
Data breakpoints
If a debugger supports data breakpoints, they can be set from the context menu in the VARIABLES section. The Break
on Value Change/Read/Access commands add a data breakpoint that is hit when the value of the underlying variable
changes/is read/is accessed. Data breakpoints are shown with a red hexagon in the BREAKPOINTS section.
Logpoints
A logpoint is a variant of a breakpoint that does not interrupt the debugger, but instead logs a message to the
debug console. Logpoints can help you save time by not having to add or remove logging statements in your code.
A logpoint is represented by a diamond-shaped icon. Log messages are plain text, but can also include expressions to be
evaluated within curly braces ('{}').
To add a logpoint, right-click in the editor left margin and select Add Logpoint, or use the
Debug: Add Logpoint... command in the Command Palette (Ctrl/Cmd + Shift + p).

Just like regular breakpoints, logpoints can be enabled or disabled and can also be controlled by a condition
and/or hit count.
CPU Time
Most Arm Cortex-M processors (except Cortex-M0/M0+/M23) include a DWT->CYCCNT
register that counts CPU states. In combination with the CMSIS variable SystemCoreClock
the CMSIS Debugger calculates execution time and displays it along with the selected processor core in the CPU Time Status bar. A click on the CPU Time Status bar opens the related VS Code command palette.
Command |
Description |
CPU Time |
Print CPU execution time and history of past program stops. |
Reset CPU Time |
Reset CPU execution time and history. Set new reference time (zero point). |

📝 Notes:
- The first program stop (typically at function
main
) is the initial reference time (zero point).
DWT->CYCCNT
is a 32-bit register incremented with SystemCoreClock
frequency. The time calculation copes with one overflow between program stops. Multiple overflows between program stops deliver wrong time information.
- Each processor in a multi-processor system has and independent
DWT->CYCCNT
register.
Multi-Core Debug
A GDB server provides multiple connections to the processor cores (identified with pname
) of a device. The list below shows the output of pyOCD in the DEBUG CONSOLE of VS Code.
0000680 I Target device: MCXN947VDF [cbuild_run]
0001585 I core 0: Cortex-M33 r0p4, pname: cm33_core0 [cbuild_run]
0001585 I core 1: Cortex-M33 r0p4, pname: cm33_core1 [cbuild_run]
0001585 I start-pname: cm33_core0 [cbuild_run]
0001600 I Semihost server started on port 4444 (core 0) [server]
0001636 I GDB server started on port 3333 (core 0) [gdbserver]
0001641 I Semihost server started on port 4445 (core 1) [server]
0001642 I GDB server started on port 3334 (core 1) [gdbserver]
0007560 I Client connected to port 3333! [gdbserver]
The start-pname
indicates the processor that starts first and boots the system. A debug launch command connects to this processor. Use a debug attach command to connect to processors that are running. The picture below highlights the parts of the user interface that interact with processors.
- Select a processor and Start Debug. This connects the debugger.
- Select a Processor in the debug toolbar, or
- Click in CALL STACK on a thread or function name to select a processor.
- The selected processor is also shown in the CPU Time Status bar. This processor context is used in the VARIABLES and WATCH view.

📝 Notes:
- The SEGGER JLink GDB server uses a launch command to connect to a running processor whereas other GDB servers use an attach command.
- A Disassembly View opens only for a selected processor; otherwise the command is shown as disabled.
Peripherals
The Peripherals view shows the device peripheral registers and allows to change their values. It uses the CMSIS-SVD files that are provided by silicon vendors and distributed as part of the CMSIS Device Family Packs (DFP).

For more information, refer to the
Peripheral Inspector GitHub repository.
Memory Inspector
The Memory Inspector provides a powerful and configurable memory viewer that features:
- Configurable Memory Display: Shows memory data with various display options.
- Address Navigation: Easily jump to and scroll through memory addresses.
- Variable Highlights: Colors memory ranges for variables.
- Multiple Memory Formats: Shows memory data on hover in multiple formats.
- Edit Memory: Allows in-place memory editing if the debug adapter supports the WriteMemoryRequest.
- Memory Management: Enables saving and restoring memory data for specific address ranges (Intel Hex format).
- Customized Views: Create and customize as many memory views as you need.
- Lock Views: Keep views static, unaffected by updates from the debug session.
- Periodic Refresh: Automatically refresh the memory data.
- Multiple Debug Sessions: Switch between multiple debug sessions using a dropdown in the memory view.

For more information, refer to the
Memory Inspector GitHub repository.
Disassembly
The command Open Disassembly View (available from command palette or context menus) shows the assembler instructions of the program intermixed with the source code. Using this view allows single stepping or managing breakpoints at the CPU instruction level.

📝 Note:
- Enable the VS Code setting Features > Debug > Disassembly View: Show Source Code to show assembler instructions interleaved with source code.
Debug Console
The Debug Console enables viewing and interacting with the output of your code running in the debugger.
Expressions can
be evaluated with the Debug Console REPL (Read-Eval-Print Loop) feature.
With the CMSIS Debug extension, you can use the Debug Console REPL to enter
GDB commands while debugging. Before entering
a GDB command, you have to explicitly enter a "greater-than"-character >
so that the following strings can be
evaluated as a GDB command.
Debug Console input uses the mode of the active editor, which means that it supports syntax coloring, indentation, auto
closing of quotes and other language features.
Example
The following example shows how to check the currently set breakpoints with the > info break
command. Afterwards, the
application is run with the > continue
command.

Serial Monitor
The Serial Monitor allows users to configure, monitor, and communicate with serial or TCP ports.
Extension Functionality
This extension adds functionality to work seamlessly with other extensions.
- A debug configuration provider
for the type
gdbtarget
which comes with the CDT GDB Debug Adapter Extension.
This provider manages the use of tools shipped with the extension:
- If option
target
>server
is set to pyocd
, then it expands to the absolute path of the built-in pyOCD distribution.
- CMSIS specific launch configuration items for the
*
debugger type, i.e. visible for all debugger types.
It depends on the actually used debug adapter type if this information is known and utilized.
Known Limitations and Workarounds
Internal Errors on stepping through code
There is an chip errata that single stepping on Cortex-M7 r0p1 processors enters the pending exception handler incorrectly which may result in error messages. Check the processor revision that is shown at debug start in the DEBUG CONSOLE.
Workaround/Solution:
Some devices allow to stop timer interrupts with control registers. For the example the STM32 devices have DbgMCU_APB1_Fz
registers. Stop all timers that are active in your application. This can be typially configured in the *.dbgconf
file of your project.
pyOCD fails to load *.cbuild-run.yml
in the default configuration
When I use the default debug configuration for pyOCD, I get errors that pyOCD cannot find the solutions
*.cbuild-run.yml
file.
Possible Reasons:
- The application's CMSIS solution was initially built with a CMSIS-Toolbox version prior to v2.8.0 which is
the first version to generate
*.cbuild-run.yml
files.
- You are using an Arm CMSIS Solution extension
prior to v1.52.0 which is the first version to fully support the
${command:cmsis-csolution.getCbuildRunFile}
command.
Workarounds/Solutions:
- Update the CMSIS-Toolbox to the latest version. Additionally, you may have to run
cbuild setup --update-rte
in a terminal for a first-time generation of *.cbuild-run.yml
file in an existing workspace.
- Update to Arm CMSIS Solution extension v1.52.0. Alternatively, replace
${command:cmsis-csolution.getCbuildRunFile}
with the path to the *.cbuild-run.yml
in your workspace
(cmsis
>cbuildRunFile
debug configuration setting).
When I download an AXF file built with Arm Compiler 6, I see the following warning and my application
does not execute correctly. This happens regardless of the selected GDB server.
warning: Loadable section "RW_RAM0" outside of ELF segments
in /path/to/my/application.axf
Possible Reason: arm-none-eabi-gdb
does not correctly load ELF program segments due to the way that
Arm Compiler 6 generates section and program header information when scatter loading is used.
Workaround: You can generate a HEX file for the program download, and the ELF file for debug purposes only.
The following steps are required if you build a CSolution-based
application with the CMSIS-Toolbox:
- Edit the
*.cproject.yml
file(s) of your application.
- Modify the
output:type:
node
to generate both an elf
and a hex
file:
output:
type:
- elf
- hex
- Build the solution.
- Keep the default configuration's
program
setting as is.
"program": "${command:cmsis-csolution.getBinaryFile}",
- Modify the default debug configuration's
initCommands
list, so that the load
command gets the relative
path to the generated HEX file.
"initCommands": [
"load ./relative/path/to/my/application.hex",
"break main"
],
This instructs the debugger to load the debug information from the ELF file and to use the HEX file
for program download.
arm-none-eabi-gdb
generates the following warnings when I debug ELF files with DWARF debug
information of standard version 4 and earlier. And the debug illusion seems to be broken in many places.
warning: (Internal error: pc 0x8006a18 in read in CU, but not in symtab.)
Possible Reason: arm-none-eabi-gdb
works best with DWARF debug information of standard version 5.
Solution: Make sure to build your application ELF file with DWARF version 5 debug information. Please refer to
your toolchain's user reference manual. This may require updates to all build tools like compiler and assembler.
For example use -gdwarf-5
for armclang
.
Broken debug illusion
When debugging ELF files with DWARF debug information of standard version 4 and earlier,
arm-none-eabi-gdb
generates the following warnings:
warning: (Internal error: pc 0x8006a18 in read in CU, but not in symtab.)
The debug illusion will be broken in many places.
Possible Reason: Missing DWARF5 debug information
arm-none-eabi-gdb
works best with DWARF debug information of standard version 5.
Solution: Build the ELF file using DWARF5
Make sure to build your application ELF file with DWARF version 5 debug information.
pyOCD port not available
When starting a debug session, you might see this error:

Possible reason: A running instance of pyOCD
This error might occur if a previous debug session has ended prematuerly and pyOCD has not exited. The orphaned instance
will still keep the port open (usually 3333) and thus you won't be able to open the port again in the new session.
Solution: Check open files and kill pyOCD
On Linux and macOS you can check the running open files using the lsof
command:
sudo lsof -i -n -P | grep 3333
Python 41836 user01 3u IPv4 0xa6ef66ad5be49a4f 0t0 TCP *:3333 (LISTEN)
pyocd 41842 user01 8u IPv4 0x9d09900145f3ca41
To kill the running pyOCD process, use:
sudo killall pyocd
On Windows systems, use the
Windows Task Manager
or the Process Explorer to find orphaned
processes.
Requirements
GDB supporting the GDB remote protocol. arm-none-eabi-gdb
is included in CMSIS Debugger extension. To use a different GDB installation, enter the full path/filename to the executable in the gdb:
node of the launch.json
file.
pyOCD for connecting to a target. pyOCD is included in CMSIS Debugger extension. To use a different pyOCD installation, enter the full path/filename to the executable in the target:
server:
node of the launch.json
file.
SEGGER® J-LINK® is an alternative GDB server for target connection. Install the latest
J-LINK Software and Documentation Pack
from SEGGER. Ensure all required drivers and host platform-specific settings are done. The extension expects that the PATH
environment variable is set to the J-Link executables.
Related open source projects are:
- The Open-CMSIS-Pack project includes the CMSIS Debugger extension.
- Eclipse® CDT.cloud™ hosts a number of components and
best practices for building customizable web-based C/C++ tools.
- pyOCD, a Python based tool and API for debugging, programming, and exploring Arm Cortex®
microcontrollers.
- GDB, the debugger of the GNU Project.
Trademarks
- Arm and Cortex are registered trademarks of Arm Limited (or its subsidiaries or affiliates) in the US and/or
elsewhere.
- Windows, Visual Studio Code, VS Code, and the Visual Studio Code icon are trademarks of Microsoft Corporation.
- Mac and macOS are trademarks of Apple Inc., registered in the U.S. and other countries and regions.
- Eclipse, CDT, and CDT.cloud are trademarks of Eclipse Foundation, Inc.
- SEGGER and J-LINK are registered trademarks of SEGGER Microcontroller GmbH.
- Node.js is a registered trademark of the OpenJS Foundation.
- GDB and GCC are part of the GNU Project and are maintained by the Free Software Foundation.