Skip to content

How To Setup Debugging in VSCode For Zephyr

Although you can get far with just using Zephyr’s powerful logging system for debugging, in certain situations it’s much more useful to have a step-by-step debugger. This page will show you how to setup debugging in VSCode for Zephyr. This page will show you how to setup “RTOS aware” debugging, in where you can see the state and stack of every Zephyr thread.

For this tutorial, we’ll pick a nRF52 SoC as our target, and pretend we are developing within WSL2 (Windows Subsystem for Linux 2, essentially a Linux VM running on Windows). However, the steps should be transferable to other MCUs and operating systems without to much difficulty.

Prerequisites

We assume the following has already been setup:

  • A Zephyr SDK installed: I had v0.17.0 installed at ~/zephyr-sdk-0.17.0.
  • A Zephyr-based project: It doesn’t matter where this is installed. It should already be compiling correctly in WSL using west build (specifying the board with -b the first time you run it).
  • VSCode. If using WSL, this should be installed in the Windows host, but opened into the WSL project folder.
  • If using WSL, pass-through of the JLink USB device into WSL using usbipd.

Installing cortex-debug

All three of the paths below require the VSCode cortex-debug extension to be installed. Install this from VSCode’s extension marketplace.

Using JLink was my default choice since the nRF52 development boards I use as a programmer already come with an emulated JLink, and it has what I’ve been using for basic programming using the command west flash --runner jlink for some time. If you’ve been using Windows, this is also probably what you have been using via Nordic’s nRF Connect for Desktop tools.

Another reason for defaulting with the JLink option is that I somewhat naively assume it is the fastest, given I’m using a JLink debugger probe.

I installed v7.94i.

Luckily, when you install JLink you also get some “RTOS aware” libraries that you can use with the JLink. For me they were installed in the following directory:

/opt/SEGGER/JLink_V794i/GDBServer/

The one we are after is RTOSPlugin_Zephyr.so, this the full path is /opt/SEGGER/JLink_V794i/GDBServer/RTOSPlugin_Zephyr.so.

launch.json

To allows us to use VSCode’s “Run and Debug” feature, we need to create a launch.json file telling it how to debug our Zephyr application. Each one of these configurations will show up as an option in the “Run and Debug” menu in VSCode. We’ll define a:

  • Launch: Builds, programs and starts the debugger.
  • Attach: Attaches to an already running Zephyr application. Useful if you’ve already caught a hard to reproduce bug (e.g. an assert fires which halts the system) or you otherwise don’t want to reset the device. Once you’ve attached you can hit pause and inspect the state of the system, including the state of all the threads.

Here is our launch.json file:

.vscode/launch.json
{
"version": "0.2.0",
"configurations": [
// Adopted from https://github.com/Marus/cortex-debug/issues/969
{
"name": "Launch",
"device": "nRF52840_xxAA",
"cwd": "${workspaceFolder}",
"executable": "app/build/app/zephyr/zephyr.elf",
"request": "launch",
"type": "cortex-debug",
"runToEntryPoint": "main",
"servertype": "jlink",
"gdbPath": "/home/gbmhunter/zephyr-sdk-0.17.0/arm-zephyr-eabi/bin/arm-zephyr-eabi-gdb",
"rtos": "/opt/SEGGER/JLink_V794i/GDBServer/RTOSPlugin_Zephyr.so",
"preLaunchTask": "Build and Flash Firmware"
},
{
"name": "Attach",
"device": "nRF52840_xxAA",
"cwd": "${workspaceFolder}",
"executable": "app/build/app/zephyr/zephyr.elf",
"request": "attach",
"type": "cortex-debug",
"runToEntryPoint": "main",
"servertype": "jlink",
"gdbPath": "/home/gbmhunter/zephyr-sdk-0.17.0/arm-zephyr-eabi/bin/arm-zephyr-eabi-gdb",
"rtos": "/opt/SEGGER/JLink_V794i/GDBServer/RTOSPlugin_Zephyr.so",
},
]
}

The key things to define are:

  • name: The name of the configuration. This is what will show up in the “Run and Debug” menu in VSCode.
  • device: The device we are debugging.
  • cwd: The working directory.
  • executable: The path to the ELF file we want to debug. By default Zephyr put this at build/app/zephyr/zephyr.elf. I note that in a prior version of Zephyr this was build/zephyr/zephyr.elf. This project had all the app source code under app/. You might have to update this for your specific project.
  • request: Whether we are launching or attaching to the debugger.
  • type: The type of debugger we are using.
  • servertype: This can be jlink, openocd or pyocd.
  • gdbPath: The path to the GDB executable. This is inside the Zephyr SDK, which is installed separate from the project.
  • rtos: The path to the RTOS plugin for JLink. This is important for the “RTOS aware” debugging. Without this, we only get information for the current thread.

Using OpenOCD

Instead of JLink, we can set the servertype to openocd and use the openocd command to start the debugger.

Using pyOCD

Instead of JLink or OpenOCD, we can use pyOCD to debug our Zephyr application. I had success making this “RTOS aware”.