Mein Blog    Projekte    Archiv    Impressum

Black Magic Probe in Neovim verwenden

Ich spiele aktuell mit dem Gedanken, mir Neovim als Entwicklungsumgebung einzurichten. Ein Problem, für das ich aber bisher keine Lösung gefunden habe, ist, aus Neovim heraus Programme auf Mikrocontrollern zu debuggen. Jetzt schaue ich mir mal im Detail an, ob und wie das über GDB, das DAP-Protokoll und die Black Magic Probe möglich ist.

Das wird jetzt eine umfassende Analyse, ob und ggf. wie sich STM32 (u. a.) Mikrocontroller via Black Magic Probe (BMP) aus Neovim heraus debuggen lassen. Grundlage ist eine auf kickstart.nvim basierte Neovim-Konfiguration mit dem dort vorgestellten Debugging-Interface und dem nvim-dap Plugin. C/C++-Programme, die auf und für den lokalen (x86) Rechner compiliert wurden lassen sich damit problemlos debuggen. Die Frage ist, ob und wie eine Konfiguration aussieht, mit der auf Remote-Targets gearbeitet werden kann. Im Internet habe ich dazu nichts gefunden.

Um auf eine eigene Lösung zu kommen, werde ich alle (GDBs DAP-Interface ist nicht sehr umfangreich) potenziell geeigneten Pfade ablaufen und dokumentieren, was passiert, und ggf. warum das so nicht funktioniert. Selbst wenn am Ende keiner der Pfade funktioniert, werde ich zumindest einiges über Debugging mit GDB gelernt haben.

Beobachtung

Um nachvollziehen zu können was passiert muss das Neovim-DAP-Log (~/.cache/nvim/dap.log) als auch das GDB-DAP-Log (GDB Handbuch zum Logging) analysiert werden, damit genau nachvollzogen werden kann, was passiert (und warum).

Notiz: Ich starte GDB im folgenden immer mit –silent, damit der Disclaimer unterdrückt wird. Das macht die Logs übersichtlicher.

In Summe habe ich dann in meiner debug.lua folgende Grundeinstellungen für GDB, um meine Logs zu erhalten:

dap.adapters.gdbBlackMagic = {
	type = 'executable',
	command = 'gdb-multiarch',
	args = {
	'--silent',
	'--interpreter=dap',
	'--eval-command', 'set print pretty on',
	'--eval-command', 'set logging enabled on',
	'--eval-command', 'set logging file ~/gdb.log',
	'--eval-command', 'set debug dap-log-file ~/gdb-dap.log',
	'--eval-command', 'set debug dap-log-level debug',
	},
}

Außerdem werde ich, für jede der hier durchgesprochenen Varianten, eine eigene Konfiguration anlegen und speichern, sodass später nachvollziehbar ist, was passiert war, und ich ggf. einzelne Pfade noch weiter verfolgen kann.

Es gibt zwei mögliche DAP-Requests, über die sich Neovim mit GDB verbinden kann: launch und attach. Laut der GDB-Doku gibt es für diese beiden Requests auch nicht allzuviele Optionen. Ein Blick in den Code (in dem Fall: /usr/share/gdb/python/gdb/dap/launch.py) zeigt aber, dass die Doku vollständig ist.

Hier ist noch wichtig festzuhalten, dass DAP JSON-Objekte überträgt, und GDB darin nach den verwendeten Parametern sucht. Das heißt aber auch, dass zusätzliche, „unbekannte“ Parameter einfach ignoriert werden und nicht zu Warnungen/Fehlern führen.

Debugging ohne Neovim und DAP

Vorneweg zunächst die Schritte die erforderlich sind, um per BMP und GDB Software auf einem STM32 zu debuggen:

wombat@T470s:~$ gdb-multiarch
GNU gdb (Debian 16.3-1) 16.3
[...Disclaimer etc. ...]
(gdb) target extended-remote /dev/ttyBmpGdb
Remote debugging using /dev/ttyBmpGdb
(gdb) file ~/CubeProjects/Projek01/build/Projekt01.elf
Reading symbols from ~/CubeProjects/Projek01/build/Projekt01.elf...
(gdb) monitor swdp_scan
Target voltage: 3.28V
Available Targets:
No. Att Driver
 1      STM32F1 medium density M3
(gdb) attach 1
Attaching to program: /home/wombat/CubeProjects/Projek01/build/Projekt01.elf, Remote target
0x0800048e in HAL_GetTick () at Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal.c:306
306       return uwTick;
(gdb) load
Loading section .isr_vector, size 0x10c lma 0x8000000
Loading section .text, size 0xd0c lma 0x800010c
Loading section .rodata, size 0x24 lma 0x8000e18
Loading section .init_array, size 0x4 lma 0x8000e3c
Loading section .fini_array, size 0x4 lma 0x8000e40
Loading section .data, size 0xc lma 0x8000e44
Start address 0x08000db4, load size 3664
Transfer rate: 11 KB/sec, 407 bytes/write.
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/wombat/CubeProjects/Projek01/build/Projekt01.elf

D. h., ich kann einfach einen (ARM-fähigen, deshalb -multiarch) GDB starten, und dann alles Weitere per GDB-Kommandos erledigen. Die spannende Frage ist nur: Bekomme ich diesen Ablauf auch über DAP aus Neovim heraus hin?

Der Launch-Request

GDB bietet zwei Möglichkeiten, eine Session über DAP zu starten: Launch und Attach. Da nicht an einen bereits laufenden Prozess angedockt werden soll, versuche ich zunächst den Weg über den Launch-Request.

Die ganz naive Konfiguration

{
	name = 'Launch 1 (Naive Launch Config)',
	type = 'gdbBlackMagic',
	request = 'launch',
	program = '~/CubeProjects/Projek01/build/Projekt01.elf',
	stopAtBeginningOfMainSubprogram = true,
},

Das kann so natürlich nicht funktionieren, weil hier gar keine Befehlen enthalten sind, um GDB mit der BMP und die BMP mit dem Mikrocontroller zu verbinden. Aber es reicht aus, um schon mal anzutesten was ich alles über die Logs in Erfahrung bringen kann. Es wäre eine angemessene Konfiguration, wenn Projekt01.elf ein auf meinem Laptop lauffähiges Programm wäre.

Also, was ist in den Logs?

gdb-dap.log

Hier sehe ich, was per DAP-von GDB angefragt wurde (hier keine Abweichung von meiner Konfiguration). Später dann:

WROTE: <<<{"type": "event", "event": "process", "body": {"isLocalProcess": true, "startMethod": "process", "name": "/home/wombat/CubeProjects/Projek01/build/Projekt01.elf", "systemProcessId": 540993}}>>>
WROTE: <<<{"type": "event", "event": "thread", "body": {"reason": "started", "threadId": 1}}>>>
WROTE: <<<{"type": "event", "event": "output", "body": {"category": "stdout", "output": "/bin/bash: Zeile 1: /home/wombat/CubeProjects/Projek01/build/Projekt01.elf: Kann die Bin\u00e4rdatei nicht ausf\u00fchren: Fehler im Format der Programmdatei\n"}}>>>
WROTE: <<<{"type": "event", "event": "output", "body": {"category": "stdout", "output": "/bin/bash: Zeile 1: /home/wombat/CubeProjects/Projek01/build/Projekt01.elf: Erfolg\n"}}>>>
WROTE: <<<{"type": "event", "event": "thread", "body": {"reason": "exited", "threadId": 1}}>>>
WROTE: <<<{"type": "event", "event": "exited", "body": {"exitCode": 0}}>>>
WROTE: <<<{"type": "event", "event": "terminated"}>>>
Traceback (most recent call last):
  File "/usr/share/gdb/python/gdb/dap/startup.py", line 212, in exec_and_log
    output = gdb.execute(cmd, from_tty=True, to_string=True)
gdb.error: During startup program exited with code 126.
EOF
JSON reader: terminating
WROTE: <<<{"type": "event", "event": "output", "body": {"category": "stdout", "output": "Quit\n"}}>>>
JSON writer: terminating
DAP: terminating
+++ quit
GDB main: joining DAP thread ...
GDB main: joining DAP thread done

Mit anderen Worten, es wird versucht das Programm als einen Prozess (auf dem Host-System) zu starten, und das scheitert, weil das ARM-Binary nicht zu meinem x86-System passt. Aber: Ich kann genau sehen, was versucht wurde und warum es nicht funktioniert hat. Das ist eine gute Ausgangssituation.

Wenn ich versuche einfach direkt das ARM-Binary Projekt01.elf über die Kommandozeile zu starten, via:

wombat@T470s:~$ /home/wombat/CubeProjects/Projek01/build/Projekt01.elf

und anschließend via:

wombat@T470s:~$ echo $?
126

den Exit-Code abfrage, erhalte ich auch die 126.

Warum steht weiter oben zusätzlich der Exit-Code 0 und „Erfolg“? Das kann ich gerade nicht so richtig nachvollziehen. Es passiert auch dann, wenn ich versuche, das Programm direkt via GDB (ohne DAP zu starten):

(gdb) file /home/wombat/CubeProjects/Projek01/build/Projekt01.elf
Reading symbols from /home/wombat/CubeProjects/Projek01/build/Projekt01.elf...
(gdb) run
Starting program: /home/wombat/CubeProjects/Projek01/build/Projekt01.elf 
/bin/bash: Zeile 1: /home/wombat/CubeProjects/Projek01/build/Projekt01.elf: Kann die Binärdatei nicht ausführen: Fehler im Format der Programmdatei
/bin/bash: Zeile 1: /home/wombat/CubeProjects/Projek01/build/Projekt01.elf: Erfolg
During startup program exited with code 126.

Um das genauer zu untersuchen, verwende ich strace:

wombat@T470s:~$ strace -e trace=process -o strace-gdb.txt gdb-multiarch /home/wombat/CubeProjects/Projek01/build/Projekt01.elf --eval-command run --eval-command exit

Die Option „-e trace=process“ filtert die sehr umfangreiche Ausgabe von strace. Das Ergebnis sieht dann so aus:

execve("/usr/bin/gdb-multiarch", ["gdb-multiarch", "/home/wombat/CubeProjects/Projek"..., "--eval-command", "run", "--eval-command", "exit"], 0x7ffe01ffbef0 /* 62 vars */) = 0
clone3({flags=CLONE_VM|CLONE_VFORK|CLONE_CLEAR_SIGHAND, exit_signal=SIGCHLD, stack=0x7f1381fef000, stack_size=0x9000}, 88) = 558887
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=558887, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
wait4(558887, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 558887
clone3({flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, child_tid=0x7f137f3ff990, parent_tid=0x7f137f3ff990, exit_signal=0, stack=0x7f137ebff000, stack_size=0x7ff4c0, tls=0x7f137f3ff6c0} => {parent_tid=[558888]}, 88) = 558888
clone3({flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, child_tid=0x7f137ebfe990, parent_tid=0x7f137ebfe990, exit_signal=0, stack=0x7f137e3fe000, stack_size=0x7ff4c0, tls=0x7f137ebfe6c0} => {parent_tid=[558889]}, 88) = 558889
clone3({flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, child_tid=0x7f137e3fd990, parent_tid=0x7f137e3fd990, exit_signal=0, stack=0x7f137dbfd000, stack_size=0x7ff4c0, tls=0x7f137e3fd6c0} => {parent_tid=[558890]}, 88) = 558890
clone3({flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, child_tid=0x7f137dbfc990, parent_tid=0x7f137dbfc990, exit_signal=0, stack=0x7f137d3fc000, stack_size=0x7ff4c0, tls=0x7f137dbfc6c0} => {parent_tid=[558891]}, 88) = 558891
vfork()                                 = 558892
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_TRAPPED, si_pid=558892, si_uid=1000, si_status=SIGTRAP, si_utime=0, si_stime=0} ---
wait4(-1, [{WIFSTOPPED(s) && WSTOPSIG(s) == SIGTRAP}], WNOHANG|__WALL, NULL) = 558892
wait4(-1, 0x7fff3436b89c, WNOHANG|__WALL, NULL) = 0
wait4(-1, 0x7fff3436b89c, WNOHANG|__WALL, NULL) = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=558892, si_uid=1000, si_status=126, si_utime=0, si_stime=0} ---
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 126}], WNOHANG|__WALL, NULL) = 558892
wait4(-1, 0x7fff3436b89c, WNOHANG|__WALL, NULL) = -1 ECHILD (Keine Kind-Prozesse)
wait4(558892, 0x7fff3436bc34, 0, NULL)  = -1 ECHILD (Keine Kind-Prozesse)
exit_group(0)                           = ?
+++ exited with 0 +++

Damit habe ich die Process-IDs der beiden Prozesse (in diesem Fall 558892 für den fehlschlagenden Prozess). Ich kann aber nicht sehen, was genau diese Prozesse, speziell der erste der beiden, machen. Es ist aber offensichtlich irgend ein GDB-interner Vorgang. Das ist vielleicht eine Frage für die GDB-Mailingliste.

Nur zum Vergleich habe ich auf die gleiche Weise noch ein einfaches Hello-World C-Programm für meinen Laptop kompiliert und mit dem gleichen Aufruf gestartet. Hier erhalte ich dann zweimal „WEXITSTATUS(s) == 0“ im strace-Log. D. h. der mir unbekannte erste Prozess tritt da auch auf.

Launch mit Parametern um eine Verbindung zur BMP aufzubauen

Ich starte wieder per launch-Request, aber versuche durch zusätzliche Parameter dafür zu sorgen, dass die Verbindung zur BMP aufgebaut wird.

program-Parameter

Ein Blick in /usr/share/gdb/python/gdb/dap/launch.py zeigt, dass dieser Parameter einfach dem GDB-file-Kommando übergeben wird. D. h. das darf so bleiben, denn es entspricht einem der gdb-Kommandos die ich ohnehin ausführe. Die Frage ist, wie bringe ich die weiteren GDB-Kommandos zur Anwendung?

Weitere Parameter des DAP-Launch-Request

Die Parameter die GDB für einen launch-Request vorsieht, bieten dafür keine Möglichkeit:

  • args für Argumente an das zu debuggende Programm
  • cwd um das Arbeitsverzeichnis zu ändern
  • env um Umgebungsvariablen an das zu debuggende Programm zu übergeben
  • program (s. o.)
  • stopAtBeginningOfMainSubprogram
  • stopOnEntry

Der Weg über einen DAP-Launch-Request mit Parametern um eine Verbindung zur BMP aufzubauen scheidet also aus.

Launch-Request an bereits mit BMP verbundene GDB-Instanz

Es gibt aber eine andere Variante: Nämlich dafür sorgen, dass GDB bereits mit der BMP verbunden ist, wenn der DAP-Launch-Request eintrifft. Dazu erweitere ich die oben gezeigte DAP-Adapter-Konfiguration um weitere Argumente die an das GDB-Binary bei dessen Start übergeben werden. Das „–eval-command“-Argument lässt mich GDB-Kommandos unmittelbar nach dem GDB-Start ausführen.

Die neue Adapter-Konfiguration sieht dann so aus:

dap.adapters.gdbBlackMagic2 = {
	type = 'executable',
	command = 'gdb-multiarch',
	args = {
	'--silent',
	'--interpreter=dap',
	'--eval-command', 'set print pretty on',
	'--eval-command', 'set logging enabled on',
	'--eval-command', 'set logging file ~/gdb.log',
	'--eval-command', 'set debug dap-log-file ~/gdb-dap.log',
	'--eval-command', 'set debug dap-log-level debug',
	'--eval-command', 'target extended-remote /dev/ttyBmpGdb',
	'--eval-command', 'monitor swdp_scan',
	'--eval-command', 'attach 1',
	'--eval-command', 'load',
	},
}

Die DAP-Konfiguration selbst bleibt bis auf den neuen Adapter-Namen unverändert.

Logfiles

In GDBs DAP Logfile steht jetzt fast nichts drin:

WROTE: <<<{"type": "event", "event": "thread", "body": {"reason": "started", "threadId": 1}}>>>

Deshalb prüfe ich auch Neovims DAP-Log (unter /home/wombat/.cache/nvim/dap.log):

[DEBUG] 2025-12-29 16:21:22 dap/session.lua:1529        "Spawning debug adapter"        {
  args = { "--silent", "--interpreter=dap", "--eval-command", "set print pretty on", "--eval-command", "set logging enabled on", "--eval-command", "set logging file ~/gdb.log", "--eval-command", "set debug dap-log-file ~/gdb-dap.log", "--eval-command", "set debug dap-log-level debug", "--eval-command", "target extended-remote /dev/ttyBmpGdb", "--eval-command", "monitor swdp_scan", "--eval-command", "attach 1", "--eval-command", "load" },
  command = "gdb-multiarch",
  type = "executable"
}
[DEBUG] 2025-12-29 16:21:22 dap/session.lua:1868        "request"       {
  arguments = {
    adapterID = "nvim-dap",
    clientID = "neovim",
    clientName = "neovim",
    columnsStartAt1 = true,
    linesStartAt1 = true,
    locale = "de_DE.UTF-8",
    pathFormat = "path",
    supportsProgressReporting = true,
    supportsRunInTerminalRequest = true,
    supportsStartDebuggingRequest = true,
    supportsVariableType = true
  },
  command = "initialize",
  seq = 1,
  type = "request"
}
[INFO] 2025-12-29 16:21:22 dap/session.lua:1589 "Process exit"  "gdb-multiarch" 0       653434

D. h. der GDB verabschiedet sich hier einfach direkt wieder, und das mit Exitcode 0, also „erfolgreich“. Um das nachzuvollziehen, mache ich jetzt erst einmal den gleichen GDB-Aufruf (bis auf das DAP-Interface):

wombat@T470s:~$ gdb-multiarch --silent --eval-command "set print pretty on" --eval-command "set logging enabled on" --eval-command "set logging file ~/gdb.log" --eval-command "set debug dap-log-file ~/gdb-dap.log" --eval-command "set debug dap-log-level debug" --eval-command "target extended-remote /dev/ttyBmpGdb" --eval-command "monitor swdp_scan" --eval-command "attach 1" --eval-command "load"

Hier fehlt natürlich noch das file-Kommando, aber das würde erst dann per DAP kommen, nachdem der DAP-Adapter erfolgreich initialisiert wurde. Das ist hier noch nicht der Fall. Ich lande direkt in einer GDB-Session, die bereits mit dem Mikrocontroller verbunden ist (mein Hello-World-Blinken lässt sich per „continue“ starten und per Strg+C anhalten), und deren einziger „Makel“ der Mangel an Symbolen ist (was ohne file-Kommando zu erwarten war).

Es gibt noch zwei Fehlermeldungen, dass die dap-log-Settings undefiniert sind, aber auch das ist durch das nicht aktivierte DAP-Interface erklärbar.

Aber warum funktioniert es ohne DAP wie erwartet, und geht mit DAP einfach zu?

Launch ohne alles

Einfach ein Launch-Request, ohne Programm und ohne initiale Kommandos. Läuft an, und GDB bleibt, wie über:

wombat@T470s:~$ ps aux | grep gdb

ersichtlich ist aktiv, solange in Neovim die Debugging-Ansicht noch offen ist. Es führt außerdem dazu, dass ich in der Debugansicht in Neovim in das Fenster mit den GDB-Ausgaben meine GDB-Kommandos eingeben und so eine Verbindung zum Mikrocontroller herstellen kann. Ab da funktionieren auch die weiteren UI-Elemente (z. B. die Play/Pause Knöpfe), Break-Points können über das Source-Code-Fenster gesetzt werden etc. Das ist schon ein gewaltiger schritt in die richtige Richtung.

Anhand des DAP-Logs kann ich sehen, dass die nachträglich von mir in Neovim eingegebenen GDB-Kommandos auch via DAP übertragen wurden. Jetzt muss dieser Schritt nur noch automatisiert werden.

Automatisierung der GDB-Kommandos in Neovim

Das ist jetzt „nur“ noch ein bisschen Fleißarbeit in Neovim-Konfiguration / Lua-Programmierung. nvim-dap bietet Listeners für sämtliche Events, und die Möglichkeit diese mit Callback-Funktionen zu versehen. Ein erster Ansatz:

dap.listeners.after.event_initialized['initialized'] = function(session)
	if session.config.type == 'gdbBlackMagic' then
		session:request('evaluate', { expression = 'target extended-remote /dev/ttyBmpGdb', context = 'repl' })
		session:request('evaluate', { expression = 'monitor swdp_scan', context = 'repl' })
		session:request('evaluate', { expression = 'attach 1', context = 'repl' })
	end
end

Hier wird noch auf den Typ der session.config geprüft, sodass diese Kommandos bei anderen GDB-Setups (z. B. für Debugging auf dem lokalen Rechner) nicht zum Einsatz kommen.

Dieser erste Ansatz funktioniert aber nicht. Ein Blick ins DAP-Log zeigt auch warum: Alle drei Requests wurden sofort abgesendet. Insbesondere dass das „attach 1“ ankommt, bevor die Änderung des Targets abgeschlossen ist, führt dazu, dass GDB einfach versucht, sich auf der lokalen Maschine mit Prozess-ID 1 zu verbinden, was fehlschlägt.

Die Lösung ist, jeden weiteren Request als Callback-Funktion des vorigen Requests zu definieren. Um nicht zu viel tippen zu müssen erfolgt das als rekursive Funktion, die das in der jeweiligen Iteration erforderliche Kommando aus einer Liste entnimmt:

dap.listeners.after.event_initialized['initialized'] = function(session)
	-- only send GDB commands for the BlackMagicProbe config:
	if session.config.type == 'gdb' or session.config.type == 'gdbBlackMagic' then
	-- define commands
	local commands = {
		'target extended-remote /dev/ttyBmpGdb',
		'monitor swdp_scan',
		'attach 1',
	}
	-- define function to recursively send one command
	-- in the callback function of the previous command
	local function send_commands(idx, commands)
		if idx <= #commands then
		session:request('evaluate', {
			expression = commands[idx],
			context = 'repl',
		}, function()
			send_commands(idx + 1, commands)
		end)
		end
	end
	-- execute that newly defined function
	send_commands(1, commands)
	end
end

Hiermit funktioniert das ganze dann tatsächlich so, wie ich es mir vorstelle: Ich starte den Debugmodus in Neovim und damit automatisch eine Debugsession auf meinem Mikrocontroller.

Finaler Setup

Im (vorläufig) finalen Setup sind noch ein paar mehr GDB-Kommandos enthalten:

local commands = {
	'target extended-remote /dev/ttyBmpGdb',
	'monitor connect_srst enable',
	'file /home/wombat/CubeProjects/Projek01/build/Projekt01.elf',
	'monitor swdp_scan',
	'attach 1',
	'load',
}

Diese sorgen dafür, dass der Mikrocontroller zurückgesetzt wird, sobald die Verbindung aufgebaut wird, und laden das Programm auf den Controller. Ein noch ausstehender Schritt ist es, die fixen Dateipfade durch generische (z. B.: ./build/*.elf) zu ersetzen.

Nicht zum Einsatz gekommen

Dinge auf die ich auch noch gestoßen bin, und vielleicht auch versucht hätte, sollen hier zumindest als Notizen für möglichen späteren Gebrauch verbleiben:

  • LLDB statt GDB
  • Veränderungen am DAP-Interface von GDB (das ist über einige überschaubare Python Skripte implementiert und vielleicht einfach veränder-/erweiterbar)
  • GDB-Init-Dateien (siehe hier: TODO)
  • DAP-attach-Request (irgendwie umbiegen, dass nicht auf remote sondern extended-remote attached wird)

Fazit

Nach einigem Suchen und Experimentieren bin ich auf eine funktionierende Konfiguration gekommen und habe dabei einiges gelernt. Es war aber doch ein relativ großer Aufwand. Und auch der Umstand dass, zumindest nach den Ergebnissen meiner diverser Internetsuchen, Niemand anders einen derartiges Setup verwendet ist vielleicht ein Zeichen, dass das nicht der sinnvollste Ansatz ist.

Ich werde mir zum Vergleich noch einmal anschauen, wie leicht sich so ein Debugging-Setup in anderen IDEs einrichten lässt. Schließlich möchte ich am Ende meine Zeit mit Mikrocontroller-Programmierung, und nicht mit IDE-Debugging verbringen.