textual-debugger 0.2.6__tar.gz → 0.2.10__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/PKG-INFO +553 -51
  2. {textual_debugger-0.2.6/src/tdb → textual_debugger-0.2.10}/README.md +551 -49
  3. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/pyproject.toml +3 -2
  4. {textual_debugger-0.2.6 → textual_debugger-0.2.10/src/tdb}/README.md +551 -49
  5. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/__init__.py +1 -1
  6. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/_timeouts.py +4 -0
  7. textual_debugger-0.2.10/src/tdb/adapters/ocaml/lldb_formatters.py +153 -0
  8. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/perl/server.py +21 -10
  9. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/perl/session.py +9 -1
  10. textual_debugger-0.2.10/src/tdb/adapters/powershell/__init__.py +3 -0
  11. textual_debugger-0.2.10/src/tdb/adapters/powershell/__main__.py +23 -0
  12. textual_debugger-0.2.10/src/tdb/adapters/powershell/locate.py +121 -0
  13. textual_debugger-0.2.10/src/tdb/adapters/powershell/output.py +80 -0
  14. textual_debugger-0.2.10/src/tdb/adapters/powershell/server.py +913 -0
  15. textual_debugger-0.2.10/src/tdb/adapters/powershell/tdb_launch.ps1 +14 -0
  16. textual_debugger-0.2.10/src/tdb/adapters/ruby/__init__.py +1 -0
  17. textual_debugger-0.2.10/src/tdb/adapters/ruby/__main__.py +23 -0
  18. textual_debugger-0.2.10/src/tdb/adapters/ruby/server.py +1120 -0
  19. textual_debugger-0.2.10/src/tdb/adapters/seqs.py +62 -0
  20. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/app.py +31 -17
  21. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/app_handlers/dap_events.py +3 -0
  22. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/app_handlers/inspection.py +171 -1
  23. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/app_handlers/routing.py +72 -0
  24. textual_debugger-0.2.10/src/tdb/app_handlers/ui_panels.py +143 -0
  25. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/breakpoint_hook.py +16 -1
  26. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/cli.py +249 -18
  27. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/dap/client.py +80 -1
  28. textual_debugger-0.2.10/src/tdb/eval_mode.py +314 -0
  29. textual_debugger-0.2.10/src/tdb/go_concurrency/analyzer.py +112 -0
  30. textual_debugger-0.2.10/src/tdb/go_concurrency/classifier.py +59 -0
  31. textual_debugger-0.2.10/src/tdb/go_concurrency/collector.py +122 -0
  32. textual_debugger-0.2.10/src/tdb/go_concurrency/models.py +123 -0
  33. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/languages/base.py +88 -1
  34. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/languages/bash.py +3 -1
  35. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/languages/cpp.py +49 -6
  36. textual_debugger-0.2.10/src/tdb/languages/errors.py +661 -0
  37. textual_debugger-0.2.10/src/tdb/languages/go.py +217 -0
  38. textual_debugger-0.2.10/src/tdb/languages/ocaml.py +399 -0
  39. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/languages/perl.py +3 -1
  40. textual_debugger-0.2.10/src/tdb/languages/powershell.py +124 -0
  41. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/languages/python.py +6 -4
  42. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/languages/registry.py +96 -11
  43. textual_debugger-0.2.10/src/tdb/languages/ruby.py +117 -0
  44. textual_debugger-0.2.10/src/tdb/languages/rust.py +158 -0
  45. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/languages/tcsh.py +3 -1
  46. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/mcp/server.py +53 -9
  47. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/mcp/session.py +29 -5
  48. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/replay.py +12 -2
  49. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/run_mode.py +64 -30
  50. textual_debugger-0.2.10/src/tdb/rust_concurrency/__init__.py +41 -0
  51. textual_debugger-0.2.10/src/tdb/rust_concurrency/analyzer.py +505 -0
  52. textual_debugger-0.2.10/src/tdb/rust_concurrency/classifier.py +255 -0
  53. textual_debugger-0.2.10/src/tdb/rust_concurrency/collector.py +337 -0
  54. textual_debugger-0.2.10/src/tdb/rust_concurrency/models.py +269 -0
  55. textual_debugger-0.2.10/src/tdb/rust_concurrency/probes/__init__.py +18 -0
  56. textual_debugger-0.2.10/src/tdb/rust_concurrency/probes/base.py +166 -0
  57. textual_debugger-0.2.10/src/tdb/rust_concurrency/probes/gdb.py +27 -0
  58. textual_debugger-0.2.10/src/tdb/rust_concurrency/probes/gdb_script.py +238 -0
  59. textual_debugger-0.2.10/src/tdb/rust_concurrency/probes/lldb.py +35 -0
  60. textual_debugger-0.2.10/src/tdb/rust_concurrency/probes/lldb_script.py +276 -0
  61. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/server/app.py +1 -1
  62. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/server/handlers.py +42 -0
  63. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/server/rpc_types.py +17 -0
  64. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/server/runner.py +19 -3
  65. textual_debugger-0.2.10/src/tdb/session/__init__.py +0 -0
  66. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/session/controller.py +195 -17
  67. textual_debugger-0.2.10/src/tdb/session/errors.py +23 -0
  68. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/session/inspect_service.py +33 -15
  69. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/session/recorder.py +3 -0
  70. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/session/state.py +9 -0
  71. textual_debugger-0.2.10/src/tdb/widgets/__init__.py +0 -0
  72. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/widgets/_inspection_modal.py +11 -2
  73. textual_debugger-0.2.10/src/tdb/widgets/goroutines_modal.py +354 -0
  74. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/widgets/modals.py +21 -10
  75. textual_debugger-0.2.10/src/tdb/widgets/rust_concurrency_modal.py +397 -0
  76. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/widgets/stack_view.py +6 -2
  77. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/widgets/threads_modal.py +55 -8
  78. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/textual_debugger.egg-info/PKG-INFO +553 -51
  79. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/textual_debugger.egg-info/SOURCES.txt +37 -0
  80. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/textual_debugger.egg-info/requires.txt +1 -1
  81. textual_debugger-0.2.6/src/tdb/app_handlers/ui_panels.py +0 -65
  82. textual_debugger-0.2.6/src/tdb/languages/errors.py +0 -235
  83. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/LICENSE +0 -0
  84. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/setup.cfg +0 -0
  85. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/__main__.py +0 -0
  86. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/__init__.py +0 -0
  87. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/bash/__init__.py +0 -0
  88. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/bash/__main__.py +0 -0
  89. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/bash/declares.py +0 -0
  90. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/bash/server.py +0 -0
  91. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/bash/session.py +0 -0
  92. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/bash/tdb_harness.sh +0 -0
  93. {textual_debugger-0.2.6/src/tdb/adapters/perl → textual_debugger-0.2.10/src/tdb/adapters/ocaml}/__init__.py +0 -0
  94. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/perl/Devel/TdbCompile.pm +0 -0
  95. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/perl/Devel/TdbRemote.pm +0 -0
  96. {textual_debugger-0.2.6/src/tdb/dap → textual_debugger-0.2.10/src/tdb/adapters/perl}/__init__.py +0 -0
  97. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/perl/__main__.py +0 -0
  98. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/perl/helpers.pl +0 -0
  99. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/perl/protocol.py +0 -0
  100. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/perl/refs.py +0 -0
  101. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/tcsh/__init__.py +0 -0
  102. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/tcsh/__main__.py +0 -0
  103. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/tcsh/breakpoints.py +0 -0
  104. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/tcsh/cli.py +0 -0
  105. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/tcsh/guardian.py +0 -0
  106. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/tcsh/inspect.py +0 -0
  107. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/tcsh/instrumenter.py +0 -0
  108. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/tcsh/models.py +0 -0
  109. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/tcsh/protocol.py +0 -0
  110. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/tcsh/runtime.py +0 -0
  111. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/tcsh/scanner.py +0 -0
  112. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/tcsh/server.py +0 -0
  113. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/tcsh/session.py +0 -0
  114. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/adapters/tcsh/transport.py +0 -0
  115. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/app_handlers/__init__.py +0 -0
  116. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/app_helpers.py +0 -0
  117. {textual_debugger-0.2.6/src/tdb/server → textual_debugger-0.2.10/src/tdb/dap}/__init__.py +0 -0
  118. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/dap/messages.py +0 -0
  119. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/dap/protocol.py +0 -0
  120. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/dap/reverse.py +0 -0
  121. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/dap/types.py +0 -0
  122. {textual_debugger-0.2.6/src/tdb/session → textual_debugger-0.2.10/src/tdb/go_concurrency}/__init__.py +0 -0
  123. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/inspection.py +0 -0
  124. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/inspection_full.py +0 -0
  125. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/keybindings/__init__.py +0 -0
  126. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/languages/__init__.py +0 -0
  127. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/mcp/__init__.py +0 -0
  128. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/mcp/__main__.py +0 -0
  129. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/persist.py +0 -0
  130. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/post_mortem.py +0 -0
  131. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/processes_cache.py +0 -0
  132. {textual_debugger-0.2.6/src/tdb/widgets → textual_debugger-0.2.10/src/tdb/server}/__init__.py +0 -0
  133. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/server/event_handler.py +0 -0
  134. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/session/child_processes.py +0 -0
  135. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/session/event_bus.py +0 -0
  136. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/session/messages.py +0 -0
  137. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/session/post_mortem_loader.py +0 -0
  138. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/session/statement_stepper.py +0 -0
  139. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/session/terminal.py +0 -0
  140. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/session/textual_handler.py +0 -0
  141. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/source_analysis.py +0 -0
  142. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/widgets/async_tasks_modal.py +0 -0
  143. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/widgets/breakpoint_view.py +0 -0
  144. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/widgets/code_view.py +0 -0
  145. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/widgets/console_view.py +0 -0
  146. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/widgets/evaluate_console.py +0 -0
  147. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/widgets/full_contents_modal.py +0 -0
  148. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/widgets/menu_bar.py +0 -0
  149. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/widgets/processes_modal.py +0 -0
  150. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/widgets/status_bar.py +0 -0
  151. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/tdb/widgets/variable_view.py +0 -0
  152. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/textual_debugger.egg-info/dependency_links.txt +0 -0
  153. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/textual_debugger.egg-info/entry_points.txt +0 -0
  154. {textual_debugger-0.2.6 → textual_debugger-0.2.10}/src/textual_debugger.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: textual-debugger
3
- Version: 0.2.6
3
+ Version: 0.2.10
4
4
  Summary: A TUI debugger for Python, Perl, and other languages. Based on textual.
5
5
  Author-email: Al Danial <al.danial@gmail.com>
6
6
  License: textual-debugger is licensed to you under the MIT/X Consortium license:
@@ -49,7 +49,7 @@ Requires-Python: >=3.11
49
49
  Description-Content-Type: text/markdown
50
50
  License-File: LICENSE
51
51
  Requires-Dist: textual>=8.0.0
52
- Requires-Dist: debugpy>=1.8.0
52
+ Requires-Dist: debugpy>=1.8.12
53
53
  Requires-Dist: pygments>=2.17.0
54
54
  Requires-Dist: fastapi>=0.115.0
55
55
  Requires-Dist: uvicorn>=0.30.0
@@ -73,16 +73,18 @@ a full-featured terminal-based debugger for Python and other languages
73
73
  with a Debug Adapter Protocol (DAP) implementation. In addition to Python,
74
74
  `tdb` comes with built-in support for
75
75
  - C and C++ (via `gdb` or `lldb-dap`)
76
+ - Rust (via `gdb` or `lldb-dap`, selected explicitly)
76
77
  - Perl (via `perl -d`)
77
78
  - Bash (via bash's own `DEBUG` trap; bash ≥ 4.4)
78
79
  - Tcsh (via source instrumentation of a stock `tcsh`)
80
+ - Ruby (via the [debug gem](https://github.com/ruby/debug)'s `rdbg`; debug ≥ 1.9)
81
+ - OCaml (native via `lldb-dap`/`gdb`, bytecode via [ocamlearlybird](https://github.com/hackwaly/ocamlearlybird))
82
+ - Go (via [Delve](https://github.com/go-delve/delve)'s `dlv dap`)
83
+ - PowerShell 7 (via [PowerShell Editor Services](https://github.com/PowerShell/PowerShellEditorServices); Linux/macOS, Windows experimental)
79
84
 
80
85
  `tdb` is built with [textual](https://github.com/Textualize/textual) and speaks
81
- DAP to a pluggable debug adapter: [debugpy](https://github.com/microsoft/debugpy)
82
- (the engine behind VS Code's Python debugger) for Python,
83
- [GDB's DAP mode](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Debugger-Adapter-Protocol.html) or
84
- [lldb-dap](https://lldb.llvm.org/resources/lldbdap.html)
85
- for compiled code. It provides a rich interactive interface for stepping through
86
+ DAP to a pluggable debug adapter.
87
+ It provides a rich interactive interface for stepping through
86
88
  code, inspecting variables, managing breakpoints, and evaluating expressions in
87
89
  complex programs.
88
90
 
@@ -96,10 +98,14 @@ MIT License. Copyright 2026 by Al Danial.
96
98
  `tdb`:
97
99
 
98
100
  - debugs multiple languages through the Debug Adapter Protocol: Python (via `debugpy`,
99
- the richest feature set), C/C++ (via `gdb -i dap` or `lldb-dap`), Perl (via `perl -d`),
100
- Bash (via bash's own `DEBUG` trap), and Tcsh (via source instrumentation of a
101
- stock `tcsh`), with the language
102
- auto-detected from the target (ref. [Multi-Language Debugging](#multi-language-debugging)).
101
+ the richest feature set), C/C++ (via `gdb -i dap` or `lldb-dap`), Rust (via `gdb` or
102
+ `lldb-dap`), Perl (via `perl -d`),
103
+ Bash (via bash's own `DEBUG` trap), Tcsh (via source instrumentation of a
104
+ stock `tcsh`), Ruby (via the debug gem's `rdbg`), OCaml (native via
105
+ `lldb-dap`/`gdb`, bytecode via ocamlearlybird), Go (via Delve's `dlv
106
+ dap`), and PowerShell 7 (via PowerShell Editor Services). Most languages are
107
+ auto-detected from the target; Rust intentionally requires `--lang rust`
108
+ (ref. [Multi-Language Debugging](#multi-language-debugging)).
103
109
 
104
110
  - supports debugging of synchronous, asynchronous, multi-threaded, and multi-process Python code.
105
111
  It specifically supports modules
@@ -107,7 +113,7 @@ It specifically supports modules
107
113
  - `threading` (with a thread inspector)
108
114
  - `multiprocessing` / `concurrent.futures` (with automatic child process attachment and a process inspector)
109
115
 
110
- - supports remote attachment to debugpy-enabled Python programs
116
+ - supports remote attachment to Python, Perl, Ruby, Rust, C/C++, and Go programs
111
117
 
112
118
  - includes a JSON-RPC server mode, an MCP mode, and a `SKILL.md` file that enable
113
119
  programmatic debug control, making it suitable for
@@ -142,7 +148,8 @@ as open source.
142
148
  - OpenAI, for providing access to Codex through the
143
149
  [Codex for Open Source](https://developers.openai.com/community/codex-for-oss) program.
144
150
 
145
- This project was inspired by Andreas Klöckner's excellent [pudb](https://pypi.org/project/pudb/) debugger.
151
+ This project was inspired by Andreas Klöckner's excellent [pudb](https://pypi.org/project/pudb/)
152
+ Python debugger.
146
153
 
147
154
  ## Gallery
148
155
  <p align="center">
@@ -208,6 +215,10 @@ tdb -k 20 -k 35 -k module.py:14 my_program.py arg1 arg2
208
215
  # sessions (-t is -k minus the persistence)
209
216
  tdb -t 20 my_program.py
210
217
 
218
+ # no TUI: run at (almost) full speed, printing nr each time line 25 is
219
+ # reached, then continuing (-e may be repeated; see "Eval Mode" below)
220
+ tdb --eval my_program.py:25 "print(f'{nr=}')" my_program.py
221
+
211
222
  # use a specific virtualenv
212
223
  tdb --python /path/to/venv/bin/python my_program.py
213
224
 
@@ -234,6 +245,11 @@ tdb -r remotehost:5678 -k my_program.py:42
234
245
  # than code on the remote host
235
246
  tdb -r remotehost:5678 --local-root /my/code/dir --remote-root /app -k my_program.py:42
236
247
 
248
+ # attach to a program that pauses itself (debugpy.breakpoint() right after
249
+ # debugpy.wait_for_client()); without this flag tdb pauses the program on
250
+ # attach, which can leave it suspended after tdb quits
251
+ tdb -r remotehost:5678 --no-pause-on-attach
252
+
237
253
  # separate tdb arguments from debuggee arguments with `--`
238
254
  tdb --python /path/to/venv/bin/python -- my_program.py -k 17 --max 23.3
239
255
  ```
@@ -246,32 +262,58 @@ python -m tdb my_program.py
246
262
 
247
263
  ## Multi-Language Debugging
248
264
 
249
- `tdb` debugs any language that has a Debug Adapter Protocol backend. Five
265
+ `tdb` debugs any language that has a Debug Adapter Protocol backend. Ten
250
266
  languages are supported out of the box:
251
267
 
252
268
  | Language | Adapter(s) | Dependencies | Feature level |
253
269
  |----------|------------|------------------------|---------------|
254
270
  | Python | `debugpy` (default) | Python ≥ 3.11 | everything in this README |
255
- | C / C++ (any native binary) | `gdb` (default), `lldb-dap` (alternate) | `gdb -i dap` requires GDB ≥ 14; `lldb-dap` ships with LLVM ≥ 17 (e.g. `apt install lldb`) | core debugging: breakpoints, stepping, stack, variables, evaluate console |
271
+ | C / C++ (any native binary) | `gdb` (default), `lldb-dap` (alternate) | `gdb -i dap` requires GDB ≥ 14; `lldb-dap` ships with LLVM ≥ 17 (e.g. `apt install lldb`) | core debugging: breakpoints, stepping, stack, variables, evaluate console + remote attach (gdbserver/lldb-server, local symbol-bearing executable required) |
272
+ | Rust (explicit `--lang rust`) | `gdb` (Linux default), `lldb-dap` (macOS default) | any rustc building with debug info; GDB ≥ 14 or LLVM `lldb-dap` ≥ 17 (concurrency ownership evidence additionally requires stable Rust 1.98) | core debugging + best-effort Rust concurrency inspection + remote attach |
256
273
  | Perl | perl-tdb (bundled) | perl ≥ 5.18 on PATH | core debugging + remote attach |
257
274
  | Bash | bash-tdb (bundled) | bash ≥ 4.4 on PATH | core debugging (no remote attach) |
258
275
  | Tcsh | tcsh-tdb (bundled) | tcsh on PATH | core debugging (no remote attach, no conditional breakpoints, no pause) |
276
+ | Ruby | `rdbg` (the [debug gem](https://github.com/ruby/debug)) | `rdbg` ≥ 1.9 on PATH | core debugging + remote attach |
277
+ | OCaml (native executable) | `lldb-dap` (default), `gdb` (alternate) | `lldb-dap` ships with LLVM ≥ 17; `gdb -i dap` requires GDB ≥ 14 | core debugging + domains-as-threads; Variables view shows no OCaml locals (upstream DWARF limitation); evaluate console is lldb/C-level, not OCaml |
278
+ | OCaml (bytecode executable) | `ocamlearlybird` (default) | `opam install earlybird` | core debugging + rich OCaml locals; no pause/`--run`, no evaluate responses, no fatal-error modal (ocamlearlybird 1.3.6 limitations); single-domain only |
279
+ | Go | `dlv` (the [Delve](https://github.com/go-delve/delve) DAP server) | Delve ≥ 1.21 on PATH (`go install github.com/go-delve/delve/cmd/dlv@latest`) | core debugging + goroutine inspection (wait graph, findings) + remote attach (no `--terminal`) |
280
+ | PowerShell 7 | `pses` (PowerShell Editor Services, driven by tdb's bundled proxy) | `pwsh` ≥ 7.2 on PATH + the PSES module (see [PowerShell](#powershell)) | core debugging (breakpoints incl. conditional/hit-count/log, stepping, stack, variables, evaluate console) + `--run`; no `--terminal`, no remote attach; Windows untested |
259
281
 
260
282
  ### Language detection and selection
261
283
 
262
284
  The language is auto-detected from the debug target:
263
285
 
264
- 1. File extension: `.py` → Python; `.pl` / `.pm` / `.t` → Perl; `.sh` / `.bash`
265
- → Bash; `.csh` / `.tcsh` → Tcsh.
266
- 2. Native executables (ELF, Mach-O, PE magic bytes) → C/C++.
267
- 3. A `#!...python`, `#!...perl`, `#!...bash`, or `#!...csh`/`#!...tcsh`
268
- shebang → Python / Perl / Bash / Tcsh respectively.
269
- 4. C/C++/Rust *source* files (`.c`, `.cpp`, `.rs`, …) produce an error with a
286
+ 1. A directory → Go, if it contains any `*.go` file (a Go package
287
+ directory, e.g. `tdb ./pkg`); otherwise an error naming `--lang`.
288
+ 2. File extension: `.py` → Python; `.go` → Go; `.pl` / `.pm` / `.t` → Perl;
289
+ `.sh` / `.bash` → Bash; `.csh` / `.tcsh` → Tcsh; `.rb` → Ruby; `.ps1` /
290
+ `.psm1` → PowerShell. (`.ml` / `.mli` do *not* auto-select OCaml, see
291
+ point 6.)
292
+ 3. Native executables (ELF, Mach-O, PE magic bytes) → C/C++, unless
293
+ byte-checking finds an OCaml marker (a native binary's
294
+ `caml_program`/`caml_startup` runtime symbols, a bytecode file's
295
+ trailing `Caml1999` marker, or a `#!...ocamlrun` shebang) → OCaml
296
+ (native or bytecode respectively), or a Go buildinfo blob (the marker
297
+ `go version` itself locates, scanned in the first 16MB of the file) →
298
+ Go. A stripped native OCaml binary, or a Go binary whose buildinfo
299
+ blob sits beyond the 16MB scan window, that byte-checking can't
300
+ identify falls back to C/C++; force it with `--lang ocaml` or `--lang
301
+ go` respectively. Rust is never inferred from an executable; select it
302
+ explicitly with `--lang rust`.
303
+ 4. A `#!...python`, `#!...perl`, `#!...bash`, `#!...csh`/`#!...tcsh`,
304
+ `#!...ruby`, or `#!...pwsh` shebang → Python / Perl / Bash / Tcsh / Ruby /
305
+ PowerShell respectively.
306
+ 5. C/C++/Rust *source* files (`.c`, `.cpp`, `.rs`, …) produce an error with a
270
307
  hint: compile with debug info (`g++ -g -O0`) and debug the binary.
271
- 5. Anything else produces an error naming the `--lang` override.
308
+ 6. `.ml` / `.mli` (OCaml source) produce an error too: build the project
309
+ first (dune's dev profile keeps debug info) and pass the **built
310
+ executable** to `tdb` (`tdb ./_build/default/bin/main.exe`), never the
311
+ `.ml` file.
312
+ 7. Anything else produces an error naming the `--lang` override.
272
313
 
273
314
  `--lang` forces the language; `--adapter` picks a non-default adapter within
274
- it (`tdb --lang cpp --adapter lldb-dap ./myprog`).
315
+ it (`tdb --lang cpp --adapter lldb-dap ./myprog`). Rust always requires the
316
+ explicit language selection (`tdb --lang rust target/debug/app`).
275
317
 
276
318
  > **Migration note:** extensionless Python scripts without a `python` shebang
277
319
  > were previously assumed to be Python; they now require `--lang python`.
@@ -299,18 +341,28 @@ and the JSON-RPC / MCP programmatic modes.
299
341
 
300
342
  Python-specific features are hidden or return "not supported for this
301
343
  language" message when debugging other languages: statement-granularity
302
- stepping (non-Python languages always step per line), the async task /
303
- process inspectors and wait graph, the evaluate console's trailing-`?` help,
344
+ stepping (non-Python languages step at their debugger's native granularity
345
+ — per line, except PowerShell, whose debugger stops per statement, see
346
+ [PowerShell](#powershell)), the async task / process inspectors and wait
347
+ graph, the evaluate console's trailing-`?` help,
304
348
  `--python`/`--pv`, `--no-subprocess`, automatic child-process attachment, and
305
349
  the post-mortem / `tdb.breakpoint()` hooks (those hooks live inside Python
306
350
  programs by nature). Remote attach (`-r`) also works for Perl (see
307
- [Perl](#perl), `Devel::TdbRemote` in place of `debugpy.listen()`), but not
308
- for C/C++, Bash, or Tcsh. `--terminal` works for every launch-mode
309
- language — Python, Perl, Bash, Tcsh, and C/C++ via `--adapter lldb-dap` —
310
- see [External Terminal Support](#external-terminal-support).
351
+ [Perl](#perl), `Devel::TdbRemote` in place of `debugpy.listen()`), Ruby
352
+ (see [Ruby](#ruby), `rdbg --open` in place of `debugpy.listen()`), Go (see
353
+ [Go](#go), `dlv dap --listen` in place of `debugpy.listen()`), and for
354
+ Rust and C/C++ native binaries (against a gdbserver/lldb-server stub, with
355
+ a local symbol-bearing executable; see [Rust](#rust) — the same flags work
356
+ for a C/C++ target without `--lang`), but not for Bash, Tcsh, OCaml, or
357
+ PowerShell. `--terminal` works for every launch-mode
358
+ language — Python, Perl, Bash, Tcsh, Ruby, and C/C++, OCaml native, or Rust
359
+ sessions via `--adapter lldb-dap` — see
360
+ [External Terminal Support](#external-terminal-support). Go and PowerShell do
361
+ not support `--terminal` yet (`dlv dap` and PSES have no terminal-routing
362
+ mode).
311
363
 
312
364
  **Bash limitations (v1):** the bash adapter uses bash's own `DEBUG` trap and
313
- has a smaller feature envelope than Python/Perl:
365
+ has a smaller feature set than Python:
314
366
 
315
367
  - Debuggee code that installs its own `DEBUG` trap clobbers the harness;
316
368
  debugging silently degrades to free-running.
@@ -354,9 +406,53 @@ paths and line numbers are preserved in everything tdb displays):
354
406
 
355
407
  See [Tcsh](#tcsh) below for launch details.
356
408
 
409
+ **OCaml limitations (v1):** OCaml has two independent adapters with
410
+ different, non-overlapping gaps; see [OCaml](#ocaml) below for the full
411
+ picture:
412
+
413
+ - **Native (`lldb-dap`/`gdb`):** the Variables view shows no named OCaml
414
+ locals in an OCaml frame (only a `Registers` scope). This is an
415
+ upstream OCaml/DWARF limitation on stock OCaml 5.4, not a `tdb` bug; use
416
+ the bytecode adapter when you need to inspect locals. The evaluate
417
+ console evaluates lldb/C-level expressions (runtime spelunking), not
418
+ OCaml expressions.
419
+ - **Bytecode (`ocamlearlybird`):** single-domain only (no multicore); no
420
+ `pause` while running (so `--run` is unavailable); the evaluate console
421
+ never gets a response for any expression (an `ocamlearlybird` 1.3.6
422
+ limitation); and the fatal-error modal doesn't appear for an uncaught
423
+ exception (`ocamlearlybird` swallows the real exception text before it
424
+ reaches `tdb`). Run the program outside the debugger to see the
425
+ backtrace instead.
426
+ - No Windows support, no remote attach, and a stripped native binary that
427
+ byte-checking can't identify needs `--lang ocaml`.
428
+
429
+ **Go limitations (v1):**
430
+
431
+ - `--terminal` is not supported (`dlv dap` has no terminal-routing mode).
432
+ - The Goroutines workspace's wait graph shows channel-send/-receive,
433
+ mutex, and `WaitGroup` edges; a goroutine parked in a `select` is
434
+ classified but contributes no wait edge (Delve can't tell which of the
435
+ `select`'s cases it's waiting on).
436
+ - No mutex-holder identification: Go's `sync.Mutex` doesn't record an
437
+ owner at the runtime level, so unlike Python's Lock/Semaphore inspector
438
+ the wait graph can name what a goroutine is blocked on but never who
439
+ holds it.
440
+ - Goroutines spawned by `exec.Command` (child OS processes) are not
441
+ automatically attached or debugged, unlike Python's automatic
442
+ multiprocessing child attach.
443
+ - Detection of a Go binary via buildinfo check scans only the first
444
+ 16MB of the file; a huge binary whose buildinfo blob sits beyond that
445
+ falls back to C/C++ — force it with `--lang go`.
446
+ - `-a`/`--attach`'s language auto-detection (reading the target pid's
447
+ buildinfo from `/proc/PID/exe`) is Linux-only; on macOS/Windows pass
448
+ `--lang go` alongside `-a`.
449
+
450
+ See [Go](#go) below for launch details.
451
+
357
452
  ### C/C++ tips
358
453
 
359
- - Compile with `-g` (ideally `-g -O0`). If no breakpoint in a file can be
454
+ - Compile with `-g` (ideally `-g -O0` or `-g3 -O0`).
455
+ If no breakpoint in a file can be
360
456
  bound, `tdb` prints a console warning suggesting the program may lack
361
457
  debug info.
362
458
  - Stack frames pointing into system libraries often have no source on disk;
@@ -373,6 +469,72 @@ See [Tcsh](#tcsh) below for launch details.
373
469
  requires `--adapter lldb-dap`; GDB's DAP mode has no terminal integration
374
470
  and `tdb` refuses `--terminal` with the default `gdb` adapter.
375
471
 
472
+ ### Rust
473
+
474
+ Rust debugging is intentionally explicit: build a normal debug executable,
475
+ leave it unmodified, and pass that executable with `--lang rust`. Linux and
476
+ macOS are supported; Windows is not yet (MSVC-toolchain binaries carry PDB
477
+ debug info that neither `gdb -i dap` nor this release's probes read). `tdb` does
478
+ not compile, instrument, or auto-detect Rust programs. Core debugging
479
+ (breakpoints, stepping, stacks, variables, remote attach) works with any
480
+ rustc that emits debug info; the concurrency inspector's layout-specific
481
+ *ownership evidence* additionally requires the current stable Rust
482
+ **1.98** standard-library layout — on other versions the workspace still
483
+ opens with stack-based wait classification and a compatibility warning.
484
+ Build with debug information and no optimization for the most useful
485
+ source locations:
486
+
487
+ ```bash
488
+ cargo rustc -- -C debuginfo=2 -C opt-level=0
489
+ # Equivalent direct rustc settings: rustc -C debuginfo=2 -C opt-level=0 src/main.rs
490
+ ```
491
+
492
+ A panic that terminates the debuggee opens the error modal with the parsed
493
+ panic message and backtrace; `tdb` injects `RUST_BACKTRACE=1` into launched
494
+ programs (your own value wins if you set one) so the frames are present.
495
+
496
+ The common commands are:
497
+
498
+ ```bash
499
+ cargo build
500
+ tdb --lang rust target/debug/app
501
+ tdb --lang rust --adapter lldb-dap --run target/debug/app
502
+ tdb --lang rust --adapter lldb-dap --terminal xterm target/debug/app
503
+ tdb --lang rust --adapter gdb --remote-attach host:2345 target/debug/app
504
+ ```
505
+
506
+ `--run` and normal stopped-on-entry launch work with both adapters. On Linux,
507
+ use GDB (the default) or `lldb-dap`; on macOS, use `lldb-dap` (the default).
508
+ External terminals are LLDB-only: GDB's DAP mode cannot provide terminal
509
+ integration. The mode/platform matrix is:
510
+
511
+ | Platform | Normal / `--run` | `--terminal` | `--remote-attach` |
512
+ |----------|------------------|--------------|-------------------|
513
+ | Linux | GDB or `lldb-dap` | `lldb-dap` only | GDB or `lldb-dap` |
514
+ | macOS | `lldb-dap` | `lldb-dap` only | `lldb-dap` |
515
+
516
+ Remote attach connects to a GDB-remote server already running on the target.
517
+ Pass the matching **local, unmodified executable with debug symbols** as the
518
+ program argument; `tdb` requires it for symbols and Rust concurrency evidence.
519
+ When source paths differ, pair `--local-root` with `--remote-root`. Do not
520
+ expose the remote debug port to an untrusted network: prefer an SSH tunnel,
521
+ for example `ssh -L 2345:127.0.0.1:2345 host`, then attach to `127.0.0.1:2345`.
522
+
523
+ In a Rust session the Threads action (`Alt+T`, or the Threads menu entry)
524
+ opens the Rust Concurrency workspace while the debuggee is stopped — a
525
+ fresh, bounded snapshot of threads, wait edges, and findings. The graph is
526
+ best effort: `confirmed` evidence is directly observed by the debugger,
527
+ `probable` is a supported inference, and `unknown` means ownership could not
528
+ be established. A deadlock is confirmed only when every edge in its cycle is
529
+ confirmed. A suspected cycle or whole-program stall is a diagnostic lead, not
530
+ a proof: it means a cycle has incomplete evidence, or all observed application
531
+ threads are blocked while the graph cannot safely invent an owner. Refresh
532
+ after the next stop to collect a new snapshot.
533
+
534
+ The built-in evidence recognizes current `std` synchronization layouts. A
535
+ future helper crate may expose stable, application-provided synchronization
536
+ metadata for broader primitives and stronger ownership evidence.
537
+
376
538
  ### Perl
377
539
 
378
540
  `tdb` bundles its own Perl adapter (`perl-tdb`) so only need
@@ -549,6 +711,269 @@ text directly in the paused shell. It can inspect *and mutate* state
549
711
  (`set name = value` takes effect immediately), and a syntax error in
550
712
  evaluated text can terminate the debuggee; there is no isolation.
551
713
 
714
+ ### Ruby
715
+
716
+ tdb debugs Ruby via the [debug gem](https://github.com/ruby/debug)'s
717
+ `rdbg` (Ruby >= 3.1 ships it; otherwise `gem install debug`; tdb needs
718
+ debug >= 1.9). `rdbg` must be on PATH, or point tdb at it with
719
+ `{"adapters": {"rdbg": "/path/to/rdbg"}}` in config.json.
720
+
721
+ ```bash
722
+ tdb script.rb # launch, stop at first line
723
+ tdb --run script.rb # run immediately, debug on demand
724
+ tdb --terminal script.rb # program I/O in its own terminal
725
+ ```
726
+
727
+ Remote attach: start the program with
728
+ `rdbg --open --port 5678 --host 0.0.0.0 script.rb` (add `--nonstop` to
729
+ let it run before you attach), then `tdb -r HOST:5678 --lang ruby`.
730
+ Note: rdbg's `--cookie` authentication is not part of DAP and is not
731
+ supported; bind to localhost and tunnel over SSH instead.
732
+ `--local-root`/`--remote-root` path mappings are not supported for Ruby
733
+ yet. Bundler projects work when your environment resolves `rdbg`
734
+ (`gem install debug` into the project's Ruby); there is no `bundle
735
+ exec` integration yet.
736
+
737
+ > **Startup note:** the debug gem's `rdbg` has an occasional handshake
738
+ > race on launch; tdb detects a stalled/corrupted handshake and retries
739
+ > once automatically with a fresh `rdbg`, so this is usually invisible.
740
+
741
+ ### OCaml
742
+
743
+ `tdb` debugs OCaml through two independent adapters, chosen automatically
744
+ from how the executable was built:
745
+
746
+ - **Native** (`ocamlopt`/dune's default build) → **`lldb-dap`** (default;
747
+ `--adapter gdb` also works). OCaml 5's domains show up as threads.
748
+ - **Bytecode** (`ocamlc -g`) → **`ocamlearlybird`** (`opam install
749
+ earlybird`). Single-domain only, but with rich, real OCaml locals.
750
+
751
+ **Build requirement:** compile with `-g` (dune's dev profile already
752
+ does). Always pass the **built executable** to `tdb`, never the `.ml`
753
+ source, as in `tdb ./_build/default/bin/main.exe`, not `tdb main.ml` (which
754
+ errors with this exact guidance).
755
+
756
+ Earlybird additionally as a fragile relative path matching so for
757
+ best results provide a fully quallified path to the source files.
758
+
759
+ ```
760
+ ocamlc -g -o my_program.byte "$PWD/my_program.ml"
761
+ ```
762
+
763
+ Then, to ensure transition from bytecode to OCaml source in the
764
+ Code view, provide an explicit temporary breakpoint (`-t`)
765
+ at the first line of execution:
766
+
767
+ ```
768
+ tdb --lang ocaml --adapter ocamlearlybird -t "$PWD/my_program.ml:2" ./my_program.byte
769
+ ```
770
+
771
+ **OCaml version:** tdb requires **OCaml ≥ 4.12**. Older runtimes print
772
+ `Printexc` backtraces without function names, which the fatal-error
773
+ modal's parser does not understand (frames would be dropped and the
774
+ modal would wrongly suggest recompiling with `-g`).
775
+
776
+ With native GDB, the default entry stop is translated to the first
777
+ executable line in project OCaml source; the generated C/ELF startup code is
778
+ not shown.
779
+ Unfortunately OCaml's native debug information does not always identify
780
+ tihs line correctly.
781
+ `--no-stop-on-entry` and explicit CLI breakpoints retain their
782
+ usual behavior.
783
+
784
+ A stripped native binary that byte-checking can't identify as OCaml falls
785
+ back to C/C++; force it with `--lang ocaml`. Override the adapter or point
786
+ at a non-`PATH` install in `config.json` (see
787
+ [Configuration](#configuration)):
788
+
789
+ ```json
790
+ {
791
+ "adapters": {"ocamlearlybird": "/path/to/ocamlearlybird"},
792
+ "default_adapters": {"ocaml": "gdb"}
793
+ }
794
+ ```
795
+
796
+ **Domains as threads (native only):** OCaml 5's domains are presented in
797
+ the Threads modal (`Alt+T` or the **Threads (N)** menu label) as `Domain 0
798
+ (main)`, `Domain 1`, and so on, in creation order. The runtime's internal
799
+ "backup thread" (one per domain, used for I/O blocking) is hidden by
800
+ default since it's never running user code; press `a` in the Threads
801
+ modal to reveal it alongside the domains. Breakpoints, stepping, continue,
802
+ and pause all work normally against any domain. In both the Stack view
803
+ and the Threads modal, OCaml frame names are demangled from the
804
+ runtime's raw `camlModule__name_NNN` convention into readable
805
+ `Module.name` form (e.g. `camlOcaml_domains.worker_297` displays as
806
+ `Ocaml_domains.worker`); runtime C frames (`caml_start_program`,
807
+ `caml_callback_exn`, and the like) are shown as-is, unchanged.
808
+
809
+ **Variables view (native):** stock OCaml 5.4 native DWARF shows **no
810
+ named locals**. The `scopes`/`variables` round trip succeeds, but every
811
+ OCaml frame's `Locals` and `Globals` scopes come back empty; only a
812
+ `Registers` scope has data (the raw register file, not decoded OCaml
813
+ values). This is an upstream OCaml compiler/DWARF limitation on the
814
+ verified toolchain (OCaml 5.4.0 / lldb 21), not a `tdb` bug. If you need
815
+ to inspect local variables, use the bytecode adapter instead.
816
+
817
+ **Variables view (bytecode):** `ocamlearlybird` reports real local names
818
+ and values, exactly like Python locals.
819
+
820
+ **Evaluate console:** the two adapters give fundamentally different
821
+ consoles. Native (`lldb-dap`) evaluates **lldb/C-level expressions**.
822
+ This is runtime spelunking (registers, raw memory, C symbol names), not
823
+ OCaml expression evaluation. Bytecode (`ocamlearlybird`) is meant to
824
+ evaluate real OCaml expressions in scope, but in the verified
825
+ `ocamlearlybird` 1.3.6 release its `evaluate` request never responds (a
826
+ confirmed upstream limitation, not specific to `tdb`); the console
827
+ silently returns nothing for any expression.
828
+
829
+ **Uncaught exceptions:** native sessions set a breakpoint on
830
+ `caml_fatal_uncaught_exception` so the debugger stops there, and `tdb`
831
+ also parses the `OCAMLRUNPARAM=b` backtrace it injects into the debuggee's
832
+ environment once the process exits, opening the same error modal Python
833
+ tracebacks get. Bytecode sessions do **not** get this: `ocamlearlybird`
834
+ intercepts the uncaught exception itself and only reports a generic
835
+ "Program exited due to Uncaught_exc" message, so the real exception text
836
+ never reaches `tdb`. Run the program outside the debugger
837
+ (`OCAMLRUNPARAM=b ./main.byte`) to see its backtrace.
838
+
839
+ **Limitations (v1):** no Windows support; no remote attach; bytecode
840
+ sessions can't `pause` a running program (so `--run` is unavailable for
841
+ bytecode. Native sessions support `--run` normally); `Unix.fork` and Eio
842
+ fibers are out of scope (only OCaml 5 domains are presented as threads).
843
+
844
+ ### Go
845
+
846
+ `tdb` debugs Go through [Delve](https://github.com/go-delve/delve)'s own
847
+ DAP server, `dlv dap` (Delve ≥ 1.21). Install it with:
848
+
849
+ ```bash
850
+ go install github.com/go-delve/delve/cmd/dlv@latest
851
+ ```
852
+
853
+ `dlv` must be on `PATH`, or point tdb at it with
854
+ `{"adapters": {"dlv": "/path/to/dlv"}}` in `config.json`.
855
+
856
+ **Launch modes** — `tdb` picks the right Delve mode automatically:
857
+
858
+ ```bash
859
+ tdb main.go # source file: `go run`-style debug mode
860
+ tdb ./pkg # package directory: `go run`-style debug mode
861
+ tdb ./binary # pre-built Go binary (buildinfo-detected): exec mode
862
+ tdb --test ./pkg # Go test package: Delve test mode
863
+ tdb -a PID # attach to a running local Go process by pid
864
+ tdb -r host:port --lang go # attach to a `dlv dap --listen` already running remotely
865
+ ```
866
+
867
+ `tdb --test ./pkg` debugs the package's tests under Delve's `test` mode;
868
+ pass test-binary flags after `--`, e.g. `tdb --test ./pkg -- -run TestFoo`.
869
+ `-a`/`--attach` currently supports Go only; on Linux, and only on Linux,
870
+ `tdb` can identify the target as Go from `/proc/PID/exe`'s buildinfo
871
+ without `--lang` (elsewhere pass `--lang go` alongside `-a`). Attaching
872
+ stops the process immediately so you get control right away.
873
+
874
+ **Remote attach:** start Delve's own DAP server against your program
875
+ first —
876
+
877
+ ```bash
878
+ dlv dap --listen=host:port ./binary
879
+ ```
880
+
881
+ — then connect from `tdb`:
882
+
883
+ ```bash
884
+ tdb -r host:port --lang go
885
+ ```
886
+
887
+ Do not expose the port to an untrusted network; prefer an SSH tunnel.
888
+
889
+ **Goroutine inspection:** in a Go session the Threads action (`Alt+T`, or
890
+ the **Goroutines (N)** menu label) opens the Goroutines workspace while
891
+ the debuggee is stopped — a fresh, bounded snapshot of goroutines, wait
892
+ edges, and findings, the same shape as the Rust Concurrency workspace.
893
+ Goroutines parked entirely in Go-runtime internals (GC workers,
894
+ finalizers, netpoll) are hidden by default; press `a` to reveal them
895
+ alongside the rest. The **Wait Graph** tab groups goroutines by the
896
+ channel, mutex, or `WaitGroup` they're blocked on; the **Findings** tab
897
+ surfaces stuck channels, mutex convoys, and likely leaks with a
898
+ confidence level (`confirmed` vs `probable`) — Go mutexes never record an
899
+ owner, so, unlike Python's Lock/Semaphore inspector, findings can name
900
+ what's blocked but not who holds it. The snapshot collects up to 150
901
+ goroutines; a busier program reports the header
902
+ `Goroutines (150) — N more not collected` rather than silently truncating.
903
+ Refresh (`r`) after the next stop to collect a new snapshot.
904
+
905
+ **Limitations (v1):** `--terminal` is not supported (`dlv dap` has no
906
+ terminal-routing mode); a goroutine parked in a `select` is classified but
907
+ contributes no wait edge (Delve can't tell which case it's waiting on);
908
+ no mutex-holder identification (impossible for Go's runtime); goroutines
909
+ spawned indirectly via `exec.Command` child OS processes are not
910
+ automatically attached; buildinfo-based binary detection scans only the
911
+ first 16MB of the file (`--lang go` overrides for anything larger); `-a`'s
912
+ `/proc`-based language auto-detection is Linux-only (pass `--lang go`
913
+ alongside `-a` on macOS/Windows).
914
+
915
+ ### PowerShell
916
+
917
+ tdb debugs PowerShell 7 scripts through [PowerShell Editor Services](https://github.com/PowerShell/PowerShellEditorServices)
918
+ (PSES), the Debug Adapter Protocol server behind the VS Code PowerShell
919
+ extension. You need `pwsh` (>= 7.2; https://aka.ms/powershell) on PATH
920
+ and the PSES module. tdb looks for the module in this order:
921
+
922
+ 1. `{"adapters": {"pses": "/path/to/PowerShellEditorServices"}}` in `config.json`
923
+ 2. the `TDB_PSES_PATH` environment variable
924
+ 3. the copy bundled with an installed VS Code PowerShell extension
925
+ (`~/.vscode/extensions/ms-vscode.powershell-*/modules/PowerShellEditorServices`)
926
+
927
+ Without VS Code, download the release once:
928
+
929
+ ```bash
930
+ mkdir -p ~/.local/share/tdb/pses && cd ~/.local/share/tdb/pses
931
+ curl -sLO https://github.com/PowerShell/PowerShellEditorServices/releases/download/v4.7.0/PowerShellEditorServices.zip
932
+ unzip -q PowerShellEditorServices.zip
933
+ export TDB_PSES_PATH=~/.local/share/tdb/pses/PowerShellEditorServices
934
+ ```
935
+
936
+ A `pwsh` that isn't on PATH can be named with `{"adapters": {"pwsh": "/path/to/pwsh"}}`.
937
+
938
+ ```bash
939
+ tdb script.ps1 # launch, stop at the first statement
940
+ tdb --run script.ps1 # run immediately, debug on demand (pause works)
941
+ ```
942
+
943
+ Notes:
944
+
945
+ - Entry stop lands on the first statement of your script (comments and
946
+ function definitions are skipped): PSES has no native `stopOnEntry` for
947
+ this launch shape, so tdb sets a synthetic breakpoint on the bundled
948
+ launcher, steps in, and clears it — no line-1 breakpoint appears in the
949
+ Breakpoint view.
950
+ - The script runs inside that small bundled launcher (`tdb_launch.ps1`) so
951
+ tdb can report the script's exit code. In the Stack view, top to bottom:
952
+ PSES's `<Breakpoint>` marker frame (the frame to inspect — it tracks the
953
+ accurate current position), your script's own frames, the launcher's
954
+ `<ScriptBlock>`, and a source-less `Interactive Session` frame at the
955
+ bottom.
956
+ - Stepping is statement-granular, as in VS Code: a line containing a
957
+ `$( ... )` subexpression takes two `next` presses to clear, and `stepIn`
958
+ first lands on the called function's declaration line before its body.
959
+ - An uncaught terminating error ends the script with exit code 1 and opens
960
+ the fatal-error modal from pwsh's concise error text. Non-terminating
961
+ errors (`Write-Error`, failing cmdlets without `-ErrorAction Stop`) print
962
+ the same concise error block to the Console as stderr and let the script
963
+ continue with exit code 0, as in pwsh itself; there is no break-on-error
964
+ yet. The error block names the launcher's `& $Script` line rather than
965
+ your script's actual line for some error kinds (a known cosmetic
966
+ limitation).
967
+ - The fatal-error modal is gated on a non-zero exit code, since pwsh prints
968
+ the identical block for terminating and non-terminating errors. A script
969
+ that reports a non-terminating error and then `exit 3` can therefore still
970
+ raise the modal, showing the last error block that names a file of yours;
971
+ blocks attributed to tdb's own `tdb_launch.ps1` are skipped, so a bare
972
+ `Write-Error` before the `exit` opens no modal at all.
973
+ - `--terminal` and remote attach are not supported for PowerShell yet.
974
+ - Windows PowerShell 5.1 is not supported (pwsh 7 only). Running tdb *on*
975
+ Windows against pwsh is designed for but not yet verified (experimental).
976
+
552
977
  ## Layout
553
978
 
554
979
  ```
@@ -604,6 +1029,11 @@ A cursor line (blue) tracks your position; the current execution line is highlig
604
1029
  | `Alt+A` | Async Tasks |
605
1030
  | `Alt+H` | Help (Documentation, About) |
606
1031
 
1032
+ File > Open works for every supported language: the file picker filters to
1033
+ the current session's language (by extension) and refuses a pick that
1034
+ detects as a different language, so you can only switch to another script
1035
+ written in the same language as the one currently being debugged.
1036
+
607
1037
  **Navigation (vim-style by default):**
608
1038
 
609
1039
  By default the Code View is in Debug mode. Hit `Escape` to switch to Navigate mode
@@ -661,7 +1091,7 @@ results = await asyncio.gather(
661
1091
  fetch(2, 1),
662
1092
  fetch(3, 3),
663
1093
  )
664
- print(results) # next stop lands here, not on each sub-line above
1094
+ print(results) # next stop lands here, not on each sub-line above
665
1095
  ```
666
1096
 
667
1097
  lands on `print(results)`, not on each interior sub-line of the `gather` call. Switch to
@@ -670,7 +1100,9 @@ stops on each physical line--useful for inspecting how a complex expression is b
670
1100
  The choice is saved to `~/.config/tdb/config.json`.
671
1101
 
672
1102
  Statement mode requires a source-language model and is currently Python-only;
673
- other languages always step per line (the Step Mode menu says so if you try).
1103
+ other languages step at their debugger's native granularity — per line,
1104
+ except PowerShell, whose debugger stops per statement, see
1105
+ [PowerShell](#powershell) (the Step Mode menu says so if you try).
674
1106
 
675
1107
  ### Breakpoints
676
1108
 
@@ -771,6 +1203,7 @@ need to launch through `tdb` up front. Install the hook once at the top of your
771
1203
  ```python
772
1204
  import sys
773
1205
  import tdb
1206
+
774
1207
  sys.excepthook = tdb.exception_hook
775
1208
  ```
776
1209
 
@@ -819,6 +1252,7 @@ continuing--use `tdb.breakpoint()`:
819
1252
  ```python
820
1253
  import tdb
821
1254
 
1255
+
822
1256
  def compute(n):
823
1257
  total = sum(range(n))
824
1258
  tdb.breakpoint() # pause here and drop into tdb
@@ -961,6 +1395,7 @@ where you want to attach the debugger:
961
1395
  ```python
962
1396
  # In the target program:
963
1397
  import debugpy
1398
+
964
1399
  debugpy.listen(("0.0.0.0", 5678))
965
1400
  print("Waiting for tdb to attach on port 5678...")
966
1401
  debugpy.wait_for_client() # optional: pause until debugger connects
@@ -1028,15 +1463,19 @@ tdb --terminal xterm my_tui_app.py
1028
1463
  ```
1029
1464
 
1030
1465
  `--terminal` works for every language `tdb` can *launch* (as opposed to
1031
- attach to): Python, Perl, Bash, Tcsh, and C/C++ via `--adapter lldb-dap`.
1032
- The default C/C++ adapter, `gdb -i dap`, has no terminal integration --
1033
- `tdb` rejects `--terminal` up front with an error pointing at
1034
- `--adapter lldb-dap` instead. `--terminal` also only applies when `tdb`
1035
- launches the program itself; it is rejected for remote-attach (`-r`), since
1036
- there's no program for `tdb` to spawn a terminal around.
1466
+ attach to): Python, Perl, Bash, Tcsh, Ruby, and C/C++ or native OCaml
1467
+ sessions via `--adapter lldb-dap`. Neither `gdb -i dap` (the default C/C++
1468
+ adapter, and an alternate for native OCaml) nor `ocamlearlybird` (the
1469
+ bytecode OCaml adapter) has any terminal integration -- `tdb` rejects
1470
+ `--terminal` up front for both, with an error pointing at `--adapter
1471
+ lldb-dap` instead. Go and PowerShell don't support `--terminal` either
1472
+ (`dlv dap` has no terminal-routing mode; PSES likewise). `--terminal` also
1473
+ only applies when `tdb` launches the program itself; it is rejected for
1474
+ remote-attach (`-r`), since there's no program for `tdb` to spawn a
1475
+ terminal around.
1037
1476
 
1038
1477
  The debuggee runs in a separate window of the specified terminal, including
1039
- all of its stdin/stdout/stderr -- keyboard input, program output, and
1478
+ all of its stdin/stdout/stderr. Keyboard input, program output, and
1040
1479
  anything the program itself draws to the terminal all happen in that
1041
1480
  window, not in `tdb`'s Console View. Supported choices:
1042
1481
  `xterm`, `konsole`, `gnome-terminal`, `ghostty`, `kitty`, `iterm2`, `warp`,
@@ -1048,9 +1487,17 @@ This feature only works in graphical environments where external terminals are a
1048
1487
 
1049
1488
  ### Run Mode (`--run`)
1050
1489
 
1051
- For inspecting a program that appears hung: run it headless, at full speed, with no
1052
- breakpoints installed and no stop-on-entry, then drop into the full TUI the moment you
1053
- actually need it -- without paying for either up front.
1490
+ `tdb`'s run mode invokes an unmodified program nearly at full speed, without bringing up the TUI.
1491
+ At any point afterwards, hitting `Ctrl-C` in that terminal pauses the program
1492
+ and drops you into the full TUI.
1493
+ From there you can inspect the program's state, navigate the stack, set and continue
1494
+ to breakpoints, and so on. After you finish inspecting, hitting `q` to quit brings up
1495
+ a modal that lets you either detach from the TUI and let the program resume running,
1496
+ or lets you terminate it.
1497
+
1498
+ The primary use cases for run mode are: (1) allowing inspection of a program that
1499
+ appears hung, and (2) finding where a program execution path lands after it
1500
+ receives a problematic input.
1054
1501
 
1055
1502
  ```bash
1056
1503
  tdb --run my_program.py args...
@@ -1066,7 +1513,7 @@ tdb --run my_program.py; echo $? # my_program.py's own exit code
1066
1513
  **Interrupting:** press Ctrl-C in the terminal (any platform), or, on Unix, send
1067
1514
  `SIGUSR1` to `tdb`'s pid from another terminal: `kill -USR1 <tdb pid>`. Either one pauses
1068
1515
  the debuggee at its currently executing line and opens the TUI with full debugging
1069
- available -- breakpoints, stepping, variable inspection, the evaluate console. The
1516
+ available (breakpoints, stepping, variable inspection, the evaluate console). The
1070
1517
  debuggee itself never receives the signal (`tdb`'s adapter runs in its own process
1071
1518
  group), so its own `SIGINT` handling is undisturbed.
1072
1519
 
@@ -1084,8 +1531,11 @@ nonzero `sys.exit()`, which `debugpy` treats as an uncaught `SystemExit` -- `tdb
1084
1531
  the TUI at the point of failure instead of exiting silently, so you can inspect the
1085
1532
  crash. Exit-code passthrough applies only to a clean exit during the headless phase.
1086
1533
 
1087
- Supported languages: Python, Perl, Bash, Tcsh, and C/C++ (both `gdb` and `lldb-dap`).
1088
- `--run` is rejected up front for any other language.
1534
+ Supported languages: Python, Perl, Bash, Tcsh, Ruby, PowerShell 7, C/C++
1535
+ (both `gdb` and `lldb-dap`), and native OCaml (both `gdb` and `lldb-dap`;
1536
+ bytecode/`ocamlearlybird` sessions don't support pause, so `--run` is
1537
+ unavailable for those). `--run` is rejected up front for any other
1538
+ language.
1089
1539
 
1090
1540
  **Limitation:** pausing is cooperative. If the debuggee is blocked inside a single
1091
1541
  blocking external call or syscall, the pause can't land until that call returns --
@@ -1094,6 +1544,48 @@ blocking external call or syscall, the pause can't land until that call returns
1094
1544
  **Incompatible flags:** `-r`, `-k`/`-t`, `--record`, `--replay`, `--server`,
1095
1545
  `--headless`, `--mcp`, `--terminal`.
1096
1546
 
1547
+ ### Eval Mode (`-e`/`--eval`)
1548
+
1549
+ Eval mode automates a common debugging loop--set a breakpoint, run to it,
1550
+ evaluate an expression in the Evaluate console, continue--without
1551
+ opening the TUI. `-e` takes two arguments, a location and an expression:
1552
+
1553
+ ```bash
1554
+ tdb --eval examples/digits_of_pi.py:25 "print(f'{nr=}')" examples/digits_of_pi.py
1555
+ ```
1556
+
1557
+ sets a breakpoint at line 25, runs the program, and each time that line is
1558
+ reached evaluates `print(f'{nr=}')` in the stopped frame, then continues.
1559
+ Program output and evaluation output both stream to the terminal; when the
1560
+ program exits, `tdb` exits with the same code (and with `1` if the session
1561
+ ends without one--as with a crash or killed adapter--so a scripted `tdb -e ...
1562
+ && next` can trust the exit status). `Ctrl-C` stops the program and exits
1563
+ `130`. Under `multiprocessing`, an eval point placed in child code is
1564
+ evaluated in each child process that reaches it.
1565
+
1566
+ - The location is `FILE:LINE`, or a bare `LINE` of the program being debugged,
1567
+ and is snapped to the start of a logical statement just like `-k`.
1568
+ - `-e` may be repeated to plant several eval points in one run.
1569
+ - The expression is evaluated through the debug adapter (DAP `evaluate`,
1570
+ context `repl`), just like the Evaluate console: side effects are real
1571
+ and persist in the running program, for example
1572
+ `-e my_program.py:25 "limit = 10"` changes `limit` from line 25 onward.
1573
+ - A bare expression's result is printed to stdout; statement-style
1574
+ expressions (`print(...)`, assignments) print nothing beyond their own
1575
+ output. An expression error is reported on stderr and the run continues --
1576
+ a typo isn't fatal, matching the Evaluate console.
1577
+ - Saved breakpoints are ignored, and any other stop (e.g. an
1578
+ uncaught-exception stop) is continued untouched, so the program runs to
1579
+ completion unattended.
1580
+ - For non-Python debuggees the expression must be in the debug adapter's
1581
+ own syntax, for example gdb's `print x` / `set var x=3` for C/C++, and not
1582
+ necessarily the debuggee's language. The adapter may bind the breakpoint
1583
+ to the next executable line (gdb moves one off a declaration or brace);
1584
+ the eval point matches that bound line, so the expression still runs.
1585
+
1586
+ **Incompatible flags:** `--run`, `-r`, `-a`, `-k`/`-t`, `--record`,
1587
+ `--replay`, `--server`, `--headless`, `--mcp`, `--terminal`.
1588
+
1097
1589
  ### Keybinding Schemes
1098
1590
 
1099
1591
  ```bash
@@ -1129,7 +1621,9 @@ Both the interactive TUI and the JSON-RPC server run simultaneously.
1129
1621
  ### RPC Protocol
1130
1622
 
1131
1623
  Send POST requests with `{"action": "...", "params": [...]}`. Responses return
1132
- `{"timestamp": "...", "success": true/false, "value": "..."}`.
1624
+ `{"timestamp": "...", "success": true/false, "value": "..."}`. Actions with a
1625
+ structured result (today only `rust_concurrency`) additionally carry a `data`
1626
+ object holding the full JSON payload; `value` then holds a one-line summary.
1133
1627
 
1134
1628
  ```bash
1135
1629
  # Check status
@@ -1187,6 +1681,7 @@ curl -s -X POST http://127.0.0.1:8150/rpc \
1187
1681
  | `list_tasks` | `[]` | List all asyncio tasks |
1188
1682
  | `inspect_task` | `["task_name"]` | Inspect a specific asyncio task |
1189
1683
  | `wait_graph` | `[]` | Show wait graph + any deadlock cycles |
1684
+ | `rust_concurrency` | `[]` | Structured Rust thread/wait/deadlock snapshot in `data` (Rust sessions, stopped) |
1190
1685
  | `restart` | `[]` | Restart session (preserves breakpoints) |
1191
1686
  | `quit` | `[]` | Shut down |
1192
1687
 
@@ -1269,7 +1764,7 @@ point), `tdb --mcp` (the main CLI with the `--mcp` switch), and
1269
1764
  `python -m tdb.mcp` (module form). Pick whichever matches how your MCP
1270
1765
  client expects to launch servers.
1271
1766
 
1272
- ### Tool surface (16 tools, curated)
1767
+ ### Tool surface (17 tools, curated)
1273
1768
 
1274
1769
  | Cluster | Tools |
1275
1770
  |---------|-------|
@@ -1277,7 +1772,7 @@ client expects to launch servers.
1277
1772
  | Control | `control(action, timeout_s=30)` where `action ∈ {continue, next, step_in, step_out, pause, wait_for_stop}` |
1278
1773
  | Inspection | `inspect(expressions)`, `read_source(file_path)`, `stack_trace()`, `status()`, `get_output()` |
1279
1774
  | Breakpoints | `set_breakpoint(spec, condition?, hit_condition?)`, `remove_breakpoint(spec)`, `list_breakpoints()` |
1280
- | Differentiators | `threads(thread_id?)`, `tasks(task_name?)`, `processes(name_or_pid?)`, `wait_graph()` |
1775
+ | Differentiators | `threads(thread_id?)`, `tasks(task_name?)`, `processes(name_or_pid?)`, `wait_graph()`, `rust_concurrency()` |
1281
1776
 
1282
1777
  `control` is intentionally one tool that takes an action enum. The six
1283
1778
  underlying RPC actions share a return shape, and agents perform
@@ -1288,9 +1783,10 @@ single optional argument for the same reason.
1288
1783
  `debug_launch` accepts optional `lang` and `adapter` parameters mirroring the
1289
1784
  CLI's `--lang`/`--adapter`; when omitted, the language is auto-detected from
1290
1785
  `program`, so an agent can hand it a compiled binary directly. The
1291
- `tasks`/`processes`/`wait_graph` tools stay registered for every language but
1786
+ exception is Rust: compiled native binaries require explicit `lang="rust"`.
1787
+ The `tasks`/`processes`/`wait_graph` tools stay registered for every language but
1292
1788
  return a structured "not supported when debugging C/C++"-style error for
1293
- non-Python debuggees.
1789
+ non-Python debuggees; `rust_concurrency` does the same for non-Rust debuggees.
1294
1790
 
1295
1791
  ### Agent flow for a long-running step
1296
1792
 
@@ -1385,6 +1881,10 @@ found; it's Python code, not an external executable), so
1385
1881
  `{"adapters": {"perl": "/path/to/perl"}}`. Use this when the `perl` on
1386
1882
  `PATH` is too old (< 5.18) or you need a specific `perlbrew`/`plenv` version.
1387
1883
 
1884
+ **PowerShell** follows the same pattern with two keys: `adapters.pwsh` names
1885
+ the interpreter and `adapters.pses` names the PowerShell Editor Services
1886
+ module directory (see [PowerShell](#powershell)).
1887
+
1388
1888
  Breakpoints are saved on exit and restored when debugging a program in the same
1389
1889
  directory. Each project's breakpoints are independent. Breakpoints set with
1390
1890
  `-t`/`--to-line` are the exception: they behave like `-k` breakpoints during
@@ -1408,6 +1908,8 @@ fires as expected.
1408
1908
  - [textual](https://github.com/Textualize/textual) : TUI framework
1409
1909
  - [debugpy](https://github.com/microsoft/debugpy) : Debug Adapter Protocol implementation for Python
1410
1910
  - [gdb](https://sourceware.org/gdb/) / [lldb-dap](https://lldb.llvm.org/resources/lldbdap.html) : optional, user-installed DAP adapters for C/C++
1911
+ - [debug gem](https://github.com/ruby/debug) (`rdbg`) : optional, user-installed DAP adapter for Ruby
1912
+ - [ocamlearlybird](https://github.com/hackwaly/ocamlearlybird) : optional, user-installed DAP adapter for OCaml bytecode (native OCaml reuses `gdb`/`lldb-dap`)
1411
1913
  - [pygments](https://pygments.org/) : Syntax highlighting
1412
1914
  - [FastAPI](https://fastapi.tiangolo.com/) + [uvicorn](https://www.uvicorn.org/) : JSON-RPC server
1413
1915