Skip to content

Extension hangs after disconnecting from external GDB server #1176

Open

Description

@lmapii

I'm seeing a similar behavior as described in #635; the debug extension hangs after I'm trying to disconnect from an external GDB server.

Image

The exact steps that I'm performing are:

  • starting a GDB server with west gdbserver, which starts an STLink GDB server.
  • attach to the external server
  • click the "Restart" button
  • click the "Disconnect" button

With this, the debug extension still shows as active until I reload the vscode window. The external GDB server is stopped normally, debugging as such also works, and it doesn't seem to be a problem if I don't restart the target (then, the disconnect closes the debug session normally).

Expected behavior

After disconnecting, the session should be closed.

Environment (please complete the following information):

  • Cortex-Debug Version 1.12.1
  • OS: Linux 36f1283a19f2 6.12.54-linuxkit 1 SMP Tue Nov 4 21:21:47 UTC 2025 aarch64 aarch64 aarch64 GNU/Linux
  • GNU gdb (Zephyr SDK 0.17.4) 12.1
  • arm-zephyr-eabi-gcc (Zephyr SDK 0.17.4) 12.2.0
  • ST-LINK_gdbserver version: 7.12.0
{
  "configurations": [
    {
      "name": "Attach to [host.docker.internal] `west debugserver` for STM32U0",
      "cwd": "${workspaceFolder}",
      "executable": "${workspaceFolder}/deps/zephyr/samples/basic/blinky/build/zephyr/zephyr.elf",
      "request": "attach",
      "type": "cortex-debug",
      "runToEntryPoint": "main",
      "servertype": "external",
      "gdbTarget": "host.docker.internal:61234",
      "gdbPath": "/opt/toolchains/zephyr-sdk-0.17.4/arm-zephyr-eabi/bin/arm-zephyr-eabi-gdb",
      // https://github.com/Marus/cortex-debug/wiki/External-gdb-server-configuration
      "overrideResetCommands": ["monitor reset", "monitor halt"],
      "overrideRestartCommands": ["monitor reset", "monitor halt"]
    }
  ]
}

Attach text from Debug Console

This is the output from the debug console when clicking "Disconnect"

80-break-delete
-> 80^done
81-target-detach
-> =thread-exited,id="1",group-id="i1"
-> =thread-group-exited,id="i1"
-> ~"[Inferior 1 (Remote target) detached]\n"
[Inferior 1 (Remote target) detached]
-> 81^done
82-target-disconnect
-> 82^done
-gdb-exit
-> ^exit
GDB session ended. exit-code: 0

Activity

  1. haneefdm commented on Dec 13, 2025

    @haneefdm
    Collaborator

    Hmmm. We probably did not notify VSCode that the session ended. That would be strange because that is tied to gdb exiting (because we started it) and since it is external, we don't wait for the server exiting...in fact people let the server running after a disconnect (with some servers)

    It is possible the second gdb (for live-watch) is still holding on and not letting the session end. Will look into it.

  2. haneefdm commented on Dec 13, 2025

    @haneefdm
    Collaborator

    Btw, for us, "Restart" button ends the current session and VSCode starts a brand new one. We don't handle a "Restart". To us, it is a brand new session start, with no history of the prior session. We did this because VSCode went back and forth on what actually happens during a "Restart". So, we decided VSCode to handle it like a total Restart.

    Are there any logs just after you did a "Restart". overrideRestartCommands was even deprecated a couple of years ago.

  3. lmapii commented on Dec 13, 2025

    @lmapii
    Author

    I admit that I clicked "Reset" and "Restart" exchangeably since for the GDB server usecase that didn't seem to make much of a difference (it doesn't reset the GDB server anyhow). Now it seems that

    • with a "reset device" I can always cleanly disconnect
    • only the "restart" seems to leave it hanging.

    Regarding deprecation I didn't find that much since those options are still listed in https://github.com/Marus/cortex-debug/blob/master/debug_attributes.md, also vscode doesn't seem to complain that the option is deprecated.

    This is a debug log where

    • the log was cleared right before hitting "restart"
    • after "restart" there's no step but simply a "disconnect"
    • and I end up with the "stuck" UI
    41-interpreter-exec console "monitor reset"
    -> @"STM32 Successfully completed reset operation (System reset)\n"
    STM32 Successfully completed reset operation (System reset)
    -> 41^done
    42-interpreter-exec console "monitor halt"
    -> @"Trying to halt core...\n"
    Trying to halt core...
    -> 42^done
    43-break-insert -t --function main
    -> 43^done,bkpt={number="2",type="breakpoint",disp="del",enabled="y",addr="0x08000350",func="main",file="/path/to/zephyr/samples/basic/blinky/src/main.c",fullname="/path/to/zephyr/samples/basic/blinky/src/main.c",line="24",thread-groups=["i1"],times="0",original-location="-function main"}
    44-exec-continue --all
    -> 44^running
    -> *running,thread-id="all"
    mi2.status = running
    -> =breakpoint-modified,bkpt={number="2",type="breakpoint",disp="del",enabled="y",addr="0x08000350",func="main",file="/path/to/zephyr/samples/basic/blinky/src/main.c",fullname="/path/to/zephyr/samples/basic/blinky/src/main.c",line="24",thread-groups=["i1"],times="1",original-location="-function main"}
    -> ~"\n"
    
    -> ~"Temporary breakpoint 2, main () at /path/to/zephyr/samples/basic/blinky/src/main.c:24\n"
    Temporary breakpoint 2, main () at /path/to/zephyr/samples/basic/blinky/src/main.c:24
    -> ~"24\t{\n"
    24	{
    -> *stopped,reason="breakpoint-hit",disp="del",bkptno="2",frame={addr="0x08000350",func="main",args=[],file="/path/to/zephyr/samples/basic/blinky/src/main.c",fullname="/path/to/zephyr/samples/basic/blinky/src/main.c",line="24",arch="armv6s-m"},thread-id="1",stopped-threads="all",core="0"
    mi2.status = stopped
    -> =breakpoint-deleted,id="2"
    45-thread-list-ids
    -> 45^done,thread-ids={thread-id="1"},current-thread-id="1",number-of-threads="1"
    46-thread-list-ids
    -> 46^done,thread-ids={thread-id="1"},current-thread-id="1",number-of-threads="1"
    47-thread-info 1
    -> 47^done,threads=[{id="1",target-id="Thread 1",name="main",frame={level="0",addr="0x08000350",func="main",args=[],file="/path/to/zephyr/samples/basic/blinky/src/main.c",fullname="/path/to/zephyr/samples/basic/blinky/src/main.c",line="24",arch="armv6s-m"},state="stopped",core="0"}]
    48-thread-info 1
    -> 48^done,threads=[{id="1",target-id="Thread 1",name="main",frame={level="0",addr="0x08000350",func="main",args=[],file="/path/to/zephyr/samples/basic/blinky/src/main.c",fullname="/path/to/zephyr/samples/basic/blinky/src/main.c",line="24",arch="armv6s-m"},state="stopped",core="0"}]
    49-stack-list-frames --thread 1 0 19
    -> 49^done,stack=[frame={level="0",addr="0x08000350",func="main",file="/path/to/zephyr/samples/basic/blinky/src/main.c",fullname="/path/to/zephyr/samples/basic/blinky/src/main.c",line="24",arch="armv6s-m"}]
    50-stack-list-frames --thread 1 0 0
    -> 50^done,stack=[frame={level="0",addr="0x08000350",func="main",file="/path/to/zephyr/samples/basic/blinky/src/main.c",fullname="/path/to/zephyr/samples/basic/blinky/src/main.c",line="24",arch="armv6s-m"}]
    51-var-create --thread 1 --frame 0 hover_7a75c957b76689d3b6a0a85c8c658938ef2dc0b775bca777cfb982c515a1b9e3 @ "_tx_thread_created_ptr"
    -> 51^error,msg="-var-create: unable to create variable object"
    hover -var-create: unable to create variable object (from var-create --thread 1 --frame 0 hover_7a75c957b76689d3b6a0a85c8c658938ef2dc0b775bca777cfb982c515a1b9e3 @ "_tx_thread_created_ptr")52-var-create --thread 1 --frame 0 hover_a0caade21aee976e19237a7c24094af6def1d2acf887dea47c867fd543f48cc2 @ "pxReadyTasksLists"
    -> 52^error,msg="-var-create: unable to create variable object"
    hover -var-create: unable to create variable object (from var-create --thread 1 --frame 0 hover_a0caade21aee976e19237a7c24094af6def1d2acf887dea47c867fd543f48cc2 @ "pxReadyTasksLists")53-var-create --thread 1 --frame 0 hover_3a036eb0c8fd1a02d65dd10c96ee568e6997b1105097f5efe0bf86d1c1fd3176 @ "_kernel_thread_info_offsets"
    -> 53^error,msg="-var-create: unable to create variable object"
    hover -var-create: unable to create variable object (from var-create --thread 1 --frame 0 hover_3a036eb0c8fd1a02d65dd10c96ee568e6997b1105097f5efe0bf86d1c1fd3176 @ "_kernel_thread_info_offsets")54-var-create --thread 1 --frame 0 hover_88a8e759d0b575f6f113f5badafcf28ed3aeb200cbe075b3bf4af9a8ae216f56 @ "_track_list_k_mutex"
    -> 54^error,msg="-var-create: unable to create variable object"
    hover -var-create: unable to create variable object (from var-create --thread 1 --frame 0 hover_88a8e759d0b575f6f113f5badafcf28ed3aeb200cbe075b3bf4af9a8ae216f56 @ "_track_list_k_mutex")55-stack-info-frame --thread 1 --frame 0
    -> 55^done,frame={level="0",addr="0x08000350",func="main",file="/path/to/zephyr/samples/basic/blinky/src/main.c",fullname="/path/to/zephyr/samples/basic/blinky/src/main.c",line="24",arch="armv6s-m"}
    56-stack-list-variables --thread 1 --frame 0 --simple-values
    -> 56^done,variables=[{name="ret",type="int",value="<optimized out>"},{name="led_state",type="_Bool",value="<optimized out>"}]
    57-var-update --thread 1 --frame 0 --all-values var_ret_4096
    -> 57^done,changelist=[{name="var_ret_4096",value="<optimized out>",in_scope="true",type_changed="false",has_more="0"}]
    58-var-update --thread 1 --frame 0 --all-values var_led_state_4096
    -> 58^done,changelist=[{name="var_led_state_4096",value="<optimized out>",in_scope="true",type_changed="false",has_more="0"}]
    59-break-delete
    -> 59^done
    60-target-detach
    -> =thread-exited,id="1",group-id="i1"
    -> =thread-group-exited,id="i1"
    -> ~"[Inferior 1 (Remote target) detached]\n"
    [Inferior 1 (Remote target) detached]
    -> 60^done
    61-target-disconnect
    -> 61^done
    -gdb-exit
    -> ^exit
    GDB session ended. exit-code: 0
    
  4. lmapii commented on Dec 13, 2025

    @lmapii
    Author

    What is also a bit of a pity is that we can't tell cortex-debug what kind of GDB server we're using. I read the https://github.com/Marus/cortex-debug/wiki/External-gdb-server-configuration which claims that with servertype:external it can't know what GDB commands to use, e.g., for a reset. It'd be great if there was another option to tell cortex-debug that this external GDB server is a stlink and it should use the same commands as if it was launched by the extension.

  5. haneefdm commented on Dec 13, 2025

    @haneefdm
    Collaborator

    The difference between reset (handled by us) and restart (handled by Microsoft) is huge.

    We don't do it that way because the orchestration can be very tricky and unreliable...because we don't know what commands you have issued, what command-line args you used etc, and we also know the quirks of every other gdb-server. You can clearly see what commands we give to the gdb-server (both in source code and in the Debug Console). There is nothing hidden.

    https://github.com/Marus/cortex-debug/blob/master/src/stlink.ts

    Just in case, have you tried our pre-release version?

    https://github.com/Marus/cortex-debug/releases

    Another thing. The first line I see in the logfile says

    41-interpreter-exec console "monitor reset"

    That is a LOT of commands (40) that preceded this one. That doesn't make sense. There should be less than 10 at this point. So, I am lost until I can duplicate it. It feels like Microsoft didn't actually do a Restart.

  6. lmapii commented on Dec 13, 2025

    @lmapii
    Author

    Yeah there's a lot happening, in the following logs I copied the log before executing the restart so you really have the full log - vscode clears the debug log each time you hit reset ...

    With the officially released version of your extension you're right, it seems like it isn't really restarted. With the pre-released version (from VSIX) the debug session restarts.

    With the pre-released version the restart only fails since the STLink GDB server terminates after a client disconnects (I fear that's not configurable, but not a problem with vscode and/or your extension of course).

    debug.log
    debug-prerelease.log

  7. haneefdm commented on Dec 13, 2025

    @haneefdm
    Collaborator

    This is in the ST gdbserver manual -- use the --persistent mode when you start yur stlink. Default is single-shot

    • -e, --persistant
    Specifying the option -e starts the server in persistent mode. By default, the persistent mode is disabled,
    meaning that the server starts in non-persistent mode.

    But then, you have to kill the server manually. Pick your evil :-)

    Btw, curious, why are you using the external mode -- doesn't look like you are doing anything special. With all those override options and pre/post thingys, most people can manage.

  8. lmapii commented on Dec 13, 2025

    @lmapii
    Author

    Ah yes, its just not (yet) exposed as an argument in the Zephyr wrapper/runner, but that works perfectly for me, thanks!

    I'm exploring options for a containerized development environment, but I'm on a mac, which means I can't forward USB interfaces into the container. So my current approach is to minimize the tools that I need to install on the host whilst everything else is only installed in the image.

    Due to the USB forwarding restriction (docker/for-mac#900) I'm launching the GDB server on the host, and connect to it from within the container for debugging. It's not ideal but I hate trashing my computer with tools (which won't even be compatible between projects).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions