Mein Blog    Projekte    Archiv    Impressum

Black Magic Probe in VSCode verwenden

Nachdem ich die Black Magic Probe erfolgreich in Neovim integriert habe, wollte ich jetzt noch einmal das Gleiche in Microsoft Visual Studio Code versuchen.

Im vorigen Post habe ich alles genau nachvollzogen, was nötig ist, um per Black Magic Probe (BMP) STM32 Mikrocontroller aus Neovim heraus zu debuggen. Das Ganze hat am Ende zwar funktioniert, aber war nicht ganz trivial und ich hatte keine Vorlage, auf die ich zurückgreifen konnte.

Jetzt habe ich mir mal angeschaut, wie das in Microsoft Visual Studio Code (VSCode) funktioniert. Bis auf eine kleine Herausforderung deutlich einfacher als in Neovim.

Installation

Zuerst habe ich VSCode gemäß deren Anleitung installiert. Anschließend habe ich drei Plugins installiert:

Die „Makefile Tools“ werden verwendet, um automatisch aus meinem Makefile die Build-Kommandos abzuleiten und daraus wiederum abzuleiten, welche Bibliotheken und Header für das Syntax-Highlighting bzw. die Code-Completion herangezogen werden sollen. Im Neovim Workflow musste dazu make über bear aufgerufen werden, um eine compile_commands.json zu erzeugen, die dann wiederum von Language Server herangezogen werden konnte.

Die Cortex-Debug Extension enthält Hilfsmittel, um ARM-Cortex, aber auch viele andere Mikrocontroller zu debuggen. Dabei wird die Black Magic Probe (neben diversen anderen Debuggern) offiziell unterstützt.

Konfiguration

Die C/C++ Extension führt einen bereits durch ein kleines Tutorial, was zeigt wie einfache C/C++-Projekte zu debuggen sind. Die in VSCode üblichen JSON-Dateien zur Konfiguration sehen für mein einfaches Hello-World-Blinken so aus:

tasks.json

{
    "tasks": [
        {
            "label": "Build (Makefile)",
            "type": "shell",
            "command": "make",
            "group": "build",
            "presentation": {
                "reveal": "always",
                "panel": "new"
            }
        },
        {
            "label": "Clean (Makefile)",
            "type": "shell",
            "command": "make clean",
            "group": "build",
            "presentation": {
                "reveal": "always",
                "panel": "new"
            }
        }
    ],
    "version": "2.0.0"
}

Hier werden einfach über eine Shell die Tasks aus meinem Makefile zugänglich gemacht. Beim Ausführen dieser Tasks ist die jeweilige Shell in einem Unterfenster sichtbar. Mit dem ausgeführten Kommando und dessen stdout-Ausgabe.

launch.json

{
    "configurations": [
        {
            "name": "Black Magic Probe (cortex-debug)",
            "type": "cortex-debug",
            "request": "launch",
            "cwd": "${workspaceRoot}",
            "executable": "${workspaceRoot}/build/${workspaceFolderBasename}.elf",
            "servertype": "bmp",
            "BMPGDBSerialPort": "/dev/ttyBmpGdb",
            "armToolchainPath": "/usr/bin",
            "gdbPath": "/usr/bin/gdb-multiarch",
            "showDevDebugOutput": "raw",
            "preLaunchTask": "Build (Makefile)"
        }
    ],
    "version": "2.0.0"
}

Hier wird ein Launch-Request über die Cortex-Debug Extension konfiguriert. Diese verwendet auch den lokal installierten gdb-multiarch um über eine extended-remote-Verbindung auf die BMP zuzugreifen. Der Ablauf wird von Cortex-Debug geregelt, man muss nur die individuell unterschiedlichen Pfade angeben.

Cortex-Debug verwendet nicht DAP, sondern GDB/MI um den GDB zu steuern. Über das “showDevDebugOutput”-Setting erhält man ähnlich granulare Informationen, wie in den DAP-Logs.

Über den optionalen Pre-Launch-Task kann ein Build-Task ausgewählt werden. Dazu einfach 1:1 den Namen wie in der tasks.json Datei übernehmen.

/etc/udev/rules.d/99-blackmagic-plugdev.rules

Auch im vorigen Post schon aktiv, aber noch nicht dokumentiert: Ich verwende die im Black Magic Projekt vorgeschlagene Datei mit udev-Regeln, damit die BMP immer unter derselben Device-Datei (/dev/BmpGdb) verfügbar, und ohne sudo-Rechte verwendbar ist:

# Black Magic Probe
# there are two connections, one for GDB and one for UART debugging
# copy this to /etc/udev/rules.d/99-blackmagic.rules
# and run sudo udevadm control -R
ACTION!="add|change|bind", GOTO="blackmagic_rules_end"
SUBSYSTEM=="tty", ACTION=="add", ATTRS{interface}=="Black Magic GDB Server", SYMLINK+="ttyBmpGdb"
SUBSYSTEM=="tty", ACTION=="add", ATTRS{interface}=="Black Magic UART Port", SYMLINK+="ttyBmpTarg"
SUBSYSTEM=="tty", ACTION=="add", ATTRS{interface}=="Black Magic GDB Server", SYMLINK+="ttyBmpGdb%E{ID_SERIAL_SHORT}"
SUBSYSTEM=="tty", ACTION=="add", ATTRS{interface}=="Black Magic UART Port", SYMLINK+="ttyBmpTarg%E{ID_SERIAL_SHORT}"
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="6017", MODE="0666", GROUP="plugdev", TAG+="uaccess"
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="6018", MODE="0666", GROUP="plugdev", TAG+="uaccess"
LABEL="blackmagic_rules_end"

Der GDB-Bug

Im Prinzip hätte damit schon alles funktionieren müssen. Es scheiterte aber immer unmittelbar nach dem „attach 1“ mit der Meldung:

../../gdb/thread.c:88: internal-error: thread_info* inferior_thread(): Assertion `current_thread_ != nullptr' failed.
A problem internal to GDB has been detected,
further debugging may prove unreliable.

Etwas Internetrecherche brachte zutage, dass das ein schon länger bekannter GDB-Bug ist. Diesem Bugzilla-Thread konnte ich dann aber auch entnehmen, dass neuere Versionen der BMP einen Workaround für diesen Bug haben. Also habe ich einfach die aktuellste Version der BMP-Firmware heruntergeladen, für meine Debugger-Hardware konfiguriert, kompiliert und geflasht. Und tatsächlich: Damit funktioniert das Debugging dann wie erwartet.

Bei der Gelegenheit habe ich auch gleich ein paar Config-Flags mehr gesetzt, sodass meine BMP jetzt auch in der Lage ist, RISC-V-Prozessoren zu debuggen. Dann kann ich mittelfristig auch damit mal experimentieren.

Mit VSCode in die Microsoft-Abhängigkeit?

Mache ich mich, wenn ich VSCode verwende, von Microsoft abhängig? Mit der vorgestellten Konfiguration nicht, denn der Quellcode meiner Projekte ist komplett unabhängig von Microsoft-Werkzeugen. Er kann über das klassische Make-Tool gebaut, und über GDB + BMP auf Mikrocontroller geflasht und debuggt werden. Das kann, wie im vorigen Post gezeigt, über Neovim, aber vermutlich auch die meisten anderen IDEs gemacht werden.

Insofern kann ich hier vom Komfort, den VSCode unzweifelhaft bietet, profitieren, ohne meine Projekte und mich in eine Microsoft-Abhängigkeit zu begeben.