Renode Docker Setup on Ubuntu 24.04

Emulating and debugging a Cortex-M4 MCU with Docker + VSCode + OpenOCD + GDB + Renode

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.

What to use it for and why you should care:

  • Easier, automated testing of distributed systems (think CANBus networks, distributed control robotics, mutli-sensor systems etc)
  • CI/CD: Integration and smoke tests done with your actual build, one step down from hardware-in-the-loop with way less setup
  • Low level development (i.e bootloaders): Bootloaders are a pain to write and need to be rock solid. You spend a lot of time dealing with the low level details which you can’t test with unittests on a different architecture without emulation. Emulation lets you speed up iteration time and increase visibility.

Goals

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:

Setup

Setting up your processor

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.

Docker Image

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 .

Running Normally w/ GUI

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.

VSCode

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

  1. Start the Renode instance. Note: Call this from the directory that includes renode-config.resc
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/
  1. Check the monitor, it should look like this when there are no errors
telnet localhost 1234

...

(monitor) i $CWD/renode-config.resc
(STM32F4_Discovery) 
  1. Run GDB and connect to the running server

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)) {
  1. Integrate into VSCode

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:

launch.json

{
  "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}"
    }
  ]
}

tasks.json

{
    "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.

GDB visual debugging in VSCode.

Resources & References