Renode is a C# based emulator for embedded architecture, it’s like if QEMU was focused only on embedded. It’s lightweight and effective with several preconfigured chips ready to use.
The following uses the Renode Docker container distribution and integrates it into VSCode for visual debugging. The code and configuration for this example can be found here.
The basic goal is to have a contained {C,}Make project that can be effortlessly emulated and debugged without physical hardware.
Memfault has some useful tutorials on the topic and I used their code as the starting point:
Renode supports several architectures but their list of supported processors is pretty short. If you need to simulate the peripherals of a given chip (say for the previously discussed bootloader example) then you’ll need to stub, mock, or define them. If you’re working with an SOC with FPGA fabric, like a Zynq, and are defining your own peripherals in HDL then checkout the co-simulation features. For most projects you probably don’t have the HDL source so you’ll probably be sticking to stubs and mocks. Checkout the documentation on how to do that here.
For this step of getting the Docker image and VSCode debugging working I’ll rely on the included platforms.
When using the nightly build the container on dockerhub was missing glib2 and gtk3 which prevented the terminal emulators from running. This wasn’t an issue with antmicro/renode:1.15.3.
Note: If you’re using the nightly build image (antmicro/renode:nightly-dotnet) then you’ll need need to install libgtk-3-0 and libglib2.0-0 to use the GUI terminal emulators. This isn’t required for running headlessly with the –disable-gui flag.
# FROM antmicro/renode:nightly-dotnet
FROM antmicro/renode:1.15.3
# Install necessary dependencies for X11 and GTK. Use when using the nightly build.
# RUN apt-get update && \
# apt-get install -y libgtk-3-0 libglib2.0-0
WORKDIR /home/user
# Set the default command to run bash (this can be customized later as needed)
CMD ["renode"]
Build the image with:
docker build -f Dockerfile -t renode-1.15.3 .
To run with x11 forwarding with the current directory mounted at /home/user in the container:
docker run -it --rm \
-e DISPLAY=$DISPLAY \
-v /tmp/.X11-unix:/tmp/.X11-unix -v $(pwd):/home/user \
--name renode renode-1.15.3 renode
This will bring up the monitor window and you can use Renode normally inside.
Note: There is an early stage plugin for native installations here that’s worth keeping an eye on.
VSCode is popular for a reason and one of those is the plug-ins and customization (the price is probably a contributing factor). I’m a big fan of graphical debugging in IDEs, and using plain GDB always feels like trying to write with the wrong hand.
The Renode docker image comes with OpenOCD and GDB out-of-the-box all you need to do is start a session in your Renode script and point VSCode at it. Press F5 to start a debugging session and that’s it .
Check out Renode’s docs on this here.
We only need a slight alteration to the native installation instructions to use docker. Make sure all ports you’re using are forwarded to the host. At a minimum these will be the monitor on 1234 and GDB on 3333.
Before setting up VSCode let’s make sure everything is running correctly
docker run -it --rm \
-e DISPLAY=$DISPLAY -e DOTNET_BUNDLE_EXTRACT_BASE_DIR=/home/user \
-p 3333:3333 -p 1234:1234 \
-v /tmp/.X11-unix:/tmp/.X11-unix -v $(pwd):/home/user \
--name renode renode-1.15.3 renode --disable-gui blog/2025/08/27/renode-docker-setup-on-ubuntu-24.04/
telnet localhost 1234
...
(monitor) i $CWD/renode-config.resc
(STM32F4_Discovery)
Attach to the target by calling target remote localhost:3333
. See the GDB docs for more on connecting to a remote target.
We need a GDB instance that talks to our architecture (ARM in this case). Either gdb-multiarch or arm-none-eabi-gdb will work.
gdb-multiarch renode-example.elf
...
(gdb) target remote localhost:3333
Remote debugging using localhost:3333
main ()
at /home/user/maskset-examples/renode-docker/project/source/renode-example.c:86
86 } else if (button_is_pressed && !gpio_get(GPIOA, GPIO0)) {
Note: The VSCode project is already setup in maskset-examples/renode-docker.
Taking the Renode Debugging in VSCode docs and replacing with the docker commands above:
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug application in Renode",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/renode-example.elf",
"miDebuggerPath": "gdb-multiarch",
"miDebuggerServerAddress": "localhost:3333",
"MIMode": "gdb",
"preLaunchTask": "Run Renode",
"postDebugTask": "Close Renode",
"args": [],
"stopAtEntry": true,
"externalConsole": false,
"cwd": "${workspaceFolder}"
}
]
}
{
"version": "2.0.0",
"tasks": [
{
"label": "Build application",
"type": "shell",
"command": "make",
"args": [
"-j",
],
"problemMatcher": [
"$gcc"
],
"group": "build",
"presentation": {
"reveal": "always",
"panel": "dedicated"
}
},
{
"label": "Run Renode",
"type": "shell",
"command": "docker",
"args": [
"run",
"-t",
"--rm",
"-p", "3333:3333",
"-p", "1234:1234",
"-e", "DISPLAY=$DISPLAY",
"-v",
"/tmp/.X11-unix:/tmp/.X11-unix",
"-v",
"${workspaceFolder}:/home/user",
"--name", "renode",
"renode-1.15.3",
"renode",
"--disable-gui",
"/home/user/renode-config.resc"
],
"dependsOn": [
"Build application"
],
"isBackground": true,
"problemMatcher": {
"source": "Renode",
"pattern": {
"regexp": ""
},
"background": {
"activeOnStart": true,
"beginsPattern": "Renode, version .*",
"endsPattern": ".*GDB server with all CPUs started on port.*"
}
},
"group": "build",
"presentation": {
"reveal": "always",
"panel": "dedicated"
}
},
{
"label": "Close Renode",
"command": "docker",
"args": [
"stop", "renode"
],
"type": "shell",
"problemMatcher": []
}
],
"inputs": [
{
"id": "terminate",
"type": "command",
"command": "workbench.action.tasks.terminate",
"args": "terminateAll"
}
]
}
We can now build using our Makefile and use GDB graphically all within a container.