Skip to content

GDB hangs on breakpoint, infinitely traversing pretty printers #14487

@stgatilov

Description

@stgatilov

Environment

  • OS and version: Linux Mint 22.3 (inside Distrobox)
  • VS Code: 1.114.0 (e7fb5e96c0730b9deb70b33781f98e2f35975036)
  • C/C++ extension: 1.32.2
  • GDB version: 15.1

Bug Summary and Steps to Reproduce

Bug Summary:
As far as I understand, IDE + GDB should not call "children" method of a pretty-printer unless the user has asked to expand the value in GUI. Moreover, calling "children" method recursively is unacceptable, because linked data structures can be expanded infinitely.
And this is exactly what I see happening in this case: GDB recursively traverses the data structure and hangs because it has loops.

Steps to reproduce:

  1. Clone this repository: https://github.com/stgatilov/vscode-gdb-pretty-printers-hangs-repro-code
  2. Open it in VS Code.
  3. Build Debug build.
  4. Put breakpoint at the only line of badfunc.
  5. Run debugging using the included configuration "Launch with pretty printers".
  6. When debugger stops at the breakpoint, GDB hangs trying to evaluate local variable entity.

Debugger Configurations

{
            "name": "(gdb) Launch with pretty printers",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/build/proj",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${fileDirname}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing",
                },
                {
                    "description": "Initialize pretty printer",
                    "text": "source ${workspaceFolder}/printers.py"
                }
            ],
            "logging": {
                "engineLogging": true,
                "trace": true,
                "traceResponse": true
            }            
        },

Debugger Logs

--> E (output): {"type":"event","event":"output","body":{"category":"console","output":"1: (301) ->~\"Breakpoint 2, badfunc (entity=55555556d2b0 GameEntity(0): \\\"my_entity_0\\\" = {...}) at /home/stgatilov/cppdev/gdb_pp_hang_repro/main.cpp:21\\n\"\n"},"seq":421}
1: (301) ->~"Breakpoint 2, badfunc (entity=55555556d2b0 GameEntity(0): \"my_entity_0\" = {...}) at /home/stgatilov/cppdev/gdb_pp_hang_repro/main.cpp:21\n"
--> E (output): {"type":"event","event":"output","body":{"category":"console","output":"1: (301) ->~\"21\\t    entity->renderParams.color[2] += 1e-3f;\\n\"\n"},"seq":423}
1: (301) ->~"21\t    entity->renderParams.color[2] += 1e-3f;\n"
--> E (output): {"type":"event","event":"output","body":{"category":"stdout","output":"Breakpoint 2, badfunc (entity=55555556d2b0 GameEntity(0): \"my_entity_0\" = {...}) at /home/stgatilov/cppdev/gdb_pp_hang_repro/main.cpp:21\n"},"seq":425}
Breakpoint 2, badfunc (entity=55555556d2b0 GameEntity(0): "my_entity_0" = {...}) at /home/stgatilov/cppdev/gdb_pp_hang_repro/main.cpp:21
--> E (output): {"type":"event","event":"output","body":{"category":"console","output":"1: (301) ->*stopped,reason=\"breakpoint-hit\",disp=\"keep\",bkptno=\"2\",frame={addr=\"0x0000555555555195\",func=\"badfunc\",args=[{name=\"entity\",value=\"55555556d2b0 GameEntity(0): \\\"my_entity_0\\\" = {...}\"}],file=\"/home/stgatilov/cppdev/gdb_pp_hang_repro/main.cpp\",fullname=\"/home/stgatilov/cppdev/gdb_pp_hang_repro/main.cpp\",line=\"21\",arch=\"i386:x86-64\"},thread-id=\"1\",stopped-threads=\"all\",core=\"10\"\n"},"seq":426}
1: (301) ->*stopped,reason="breakpoint-hit",disp="keep",bkptno="2",frame={addr="0x0000555555555195",func="badfunc",args=[{name="entity",value="55555556d2b0 GameEntity(0): \"my_entity_0\" = {...}"}],file="/home/stgatilov/cppdev/gdb_pp_hang_repro/main.cpp",fullname="/home/stgatilov/cppdev/gdb_pp_hang_repro/main.cpp",line="21",arch="i386:x86-64"},thread-id="1",stopped-threads="all",core="10"
--> E (output): {"type":"event","event":"output","body":{"category":"stdout","output":"21\t    entity->renderParams.color[2] += 1e-3f;\n"},"seq":429}
21	    entity->renderParams.color[2] += 1e-3f;
--> E (output): {"type":"event","event":"output","body":{"category":"console","output":"1: (302) <-1025-stack-list-frames 0 1000\n"},"seq":431}
1: (302) <-1025-stack-list-frames 0 1000
--> E (output): {"type":"event","event":"output","body":{"category":"console","output":"1: (302) ->1025^done,stack=[frame={level=\"0\",addr=\"0x0000555555555195\",func=\"badfunc\",file=\"/home/stgatilov/cppdev/gdb_pp_hang_repro/main.cpp\",fullname=\"/home/stgatilov/cppdev/gdb_pp_hang_repro/main.cpp\",line=\"21\",arch=\"i386:x86-64\"},frame={level=\"1\",addr=\"0x00005555555553d0\",func=\"main\",file=\"/home/stgatilov/cppdev/gdb_pp_hang_repro/main.cpp\",fullname=\"/home/stgatilov/cppdev/gdb_pp_hang_repro/main.cpp\",line=\"44\",arch=\"i386:x86-64\"}]\n"},"seq":433}
1: (302) ->1025^done,stack=[frame={level="0",addr="0x0000555555555195",func="badfunc",file="/home/stgatilov/cppdev/gdb_pp_hang_repro/main.cpp",fullname="/home/stgatilov/cppdev/gdb_pp_hang_repro/main.cpp",line="21",arch="i386:x86-64"},frame={level="1",addr="0x00005555555553d0",func="main",file="/home/stgatilov/cppdev/gdb_pp_hang_repro/main.cpp",fullname="/home/stgatilov/cppdev/gdb_pp_hang_repro/main.cpp",line="44",arch="i386:x86-64"}]
--> E (output): {"type":"event","event":"output","body":{"category":"console","output":"1: (302) ->(gdb)\n"},"seq":435}
1: (302) ->(gdb)
--> E (output): {"type":"event","event":"output","body":{"category":"console","output":"1: (302) 1025: elapsed time 0\n"},"seq":437}
1: (302) 1025: elapsed time 0
--> E (output): {"type":"event","event":"output","body":{"category":"console","output":"1: (303) Send Event AD7BreakpointEvent\n"},"seq":439}
1: (303) Send Event AD7BreakpointEvent
--> E (output): {"type":"event","event":"output","body":{"category":"console","output":"Execute debugger commands using \"-exec <command>\", for example \"-exec info registers\" will list registers in use (when GDB is the debugger)\n"},"seq":441}
Execute debugger commands using "-exec <command>", for example "-exec info registers" will list registers in use (when GDB is the debugger)
--> E (stopped): {"type":"event","event":"stopped","body":{"reason":"breakpoint","threadId":2015535,"allThreadsStopped":true,"source":{"name":"main.cpp","path":"/home/stgatilov/cppdev/gdb_pp_hang_repro/main.cpp","sources":[],"checksums":[]},"line":21,"column":1},"seq":443}
<--   C (threads-12): {"command":"threads","type":"request","seq":12}
--> R (threads-12): {"type":"response","request_seq":12,"success":true,"command":"threads","body":{"threads":[{"id":2015535,"name":"proj [2015535]"}]},"seq":446}
<--   C (stackTrace-13): {"command":"stackTrace","arguments":{"threadId":2015535,"startFrame":0,"levels":20},"type":"request","seq":13}
--> E (output): {"type":"event","event":"output","body":{"category":"console","output":"1: (323) <-1026-list-features\n"},"seq":449}
1: (323) <-1026-list-features
--> E (output): {"type":"event","event":"output","body":{"category":"console","output":"1: (324) ->1026^done,features=[\"frozen-varobjs\",\"pending-breakpoints\",\"thread-info\",\"data-read-memory-bytes\",\"breakpoint-notifications\",\"ada-task-info\",\"language-option\",\"info-gdb-mi-command\",\"undefined-command-error-code\",\"exec-run-start-option\",\"data-disassemble-a-option\",\"simple-values-ref-types\",\"python\"]\n"},"seq":451}
1: (324) ->1026^done,features=["frozen-varobjs","pending-breakpoints","thread-info","data-read-memory-bytes","breakpoint-notifications","ada-task-info","language-option","info-gdb-mi-command","undefined-command-error-code","exec-run-start-option","data-disassemble-a-option","simple-values-ref-types","python"]
--> E (output): {"type":"event","event":"output","body":{"category":"console","output":"1: (324) ->(gdb)\n"},"seq":453}
1: (324) ->(gdb)
--> E (output): {"type":"event","event":"output","body":{"category":"console","output":"1: (325) 1026: elapsed time 1\n"},"seq":455}
1: (325) 1026: elapsed time 1
--> E (output): {"type":"event","event":"output","body":{"category":"console","output":"1: (326) <-1027-stack-list-arguments 2 0 1\n"},"seq":457}
1: (326) <-1027-stack-list-arguments 2 0 1

Other Extensions

No response

Additional Information

You can uncomment print statements in printers.py and confirm that children methods of pretty printers are called endlessly while GDB hangs. The last command that is sent to GDB is -stack-list-arguments 2 0 1, and GDB never responds to it.

According to GDB-MI docs, print-values = 2 or --simple-values means: "print the name, type and value for simple data types, and the name and type for arrays, structures and unions."
I suspect that pointers are also considered "simple data types", even when they are covered by pretty printers and have children. So GDB tries to return the value of a pointer by deep-printing it. Since the children graph in this case has cycles, deep-printing hangs.

The context: I'm trying to rewrite ~1000 LOC of natvis into GDB pretty printers for TheDarkMod game, so that Linux programmers could enjoy debugging too. It looks like GDB normally assumes that children graph of any value is a tree and it can be deep-printed easily. But I feel such a restriction heavily limits the usefulness of natvis/pretty printers. Sadly, this issue looks like a blocker in using GDB pretty printers reliably.

As an unrelated issue, the limited natvis support also hangs on this repro project. To reproduce, run "Launch with natvis" configuration, then stop at the same breakpoint and look into Locals, then expand entity and renderParams.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions