For those interested in writing a debugger:
There are a series of tutorials on how to write a debugger from scratch
for Windows x86-64 using Rust [1].
Additionally, there is a book titled "Building a Debugger - Write a Native x64 Debugger From Scratch" by Sy Brand [2].
Didn't expect it to be posted, readme maybe doesn't have enough context. It just says "Essential features are there". What are those? Most of what I've ever used in any debugger:
* Showing code, disassembly, threads, stack traces, local variables.
* Watches, with a little custom expression language. E.g. you can do pointer arithmetic, type casts, turn a pointer+length into an array, show as hex, etc. Access to local and global variables, thread-local variables, registers. Type introspection (e.g. sizeof and offsets of fields).
* Pretty printers for most C++ and Rust standard library types. Probably fragile and version-dependent (e.g. fields names often changes across versions), please report when they don't work.
* Automatically down-casting abstract classes to concrete classes.
* Breakpoints, conditional breakpoints (but no data breakpoints yet).
* Stepping: into/over/out a source code line, into/over a disassembly instruction, over a source code column (when there are multple statements one line, e.g. to skip evaluation of arguments of a function call). All places where control can stop (statements) are highlighted in the code, so you usually don't get surprised by where a step takes you. (...except when there's garbage in debug info, and you end up temporarily on line 0 or something. This happens frustratingly often, and there's not much I can do about it. I already added quite a few workarounds to make stepping less janky in such cases. If a step takes you to an unexpected place, it usually under-steps rather than over-steps, so you can just step again until you end up in the correct place.)
* Various searches: file by name, function by name, function by address (like addr2line), type by name, global variable by name, thread by stack trace.
* Debugging core dumps. There's also a gdump-like tool built in (`nnd --dump-core`) that makes core dump of a running process without killing it; it uses fork to minimize downtime (usually around a second even if there are tens of GB of memory to dump).
* Customizable key bindings, see `nnd --help-files` or `nnd --help-state`.
* TUI with mouse support, tooltips, etc.
mmastrac 1 days ago [-]
As a curiosity, is there a more heuristic approach and/or toolchain integrated approach that could be used for disassembly of stdlib components?
For example, a crate that could be linked in to provide some "well-known" object shapes (hashmaps, vec, hashset, etc) with marker values that could be heuristically analyzed to understand the debuggability of those objects?
Alternatively, I'd love to have a crate with recognizers and/or heuristics that could be somewhat debugger-independent and could be worked on for the benefit of other users. I'm quite an experienced Rust developer, just not really with debuggers, happy to help if there's a sandbox project that this could be plugged into.
al13n 23 hours ago [-]
Here's an overly long reply, sorry :)
For custom pretty-printers, the long-term plan is to make the watch expression language rich enough that you can just write one-liners in the watches window to pretty-print your struct. E.g. `for entry in my_hashmap.entries_ptr.[my_hashmap.num_entries] { if entry.has_value { yield struct {key: &entry.key, value: &entry.value}; } }`. Then allow loading a collection of such printers from a file; I guess each pretty-printer would have a regex of type names for which to use it (e.g. `std:.*:unordered_(multi)?(set|map)`). There are not very many containers in standard libraries (like, 10-20?), and hopefully most of their pretty-printers can be trivial one-liners, so they would be easy enough to add and maintain that incompatibility with other debuggers wouldn't be a big concern. Currently nnd doesn't have anything like that (e.g. there are no loops in the watch expression language), I don't have a good design for the language yet, not sure if I'll ever get around to it.
(Btw, "pretty-printers" is not a good name for what I'm talking about; rather, it transforms a value into another value, e.g. an std::vector into a slice, or an unordered_map into an array of pairs, which is then printed using a normal non-customizable printer. The transformed value ~fully replaces the original value, so you can e.g. do array indexing on std::vector as if it was a slice: `v[42]`. This seems like a better way to do it than a literal pretty-printer that outputs a string.)
What kind of cooperation from library authors would help with container recognition... The current recognizers are just looking for fields begin/end (pointers) or data/len (pointer and number), etc (see src/pretty.rs, though it's not very good code). So just use those names for fields and it should work :) . I'm not sure any more formal/bureaucratic contract is needed. But it would be easy for the recognizer to also check e.g. typedefs inside the struct (I guess most languages have something that translates to typedefs in debug info? at least C++ and Rust do). E.g. maybe a convention would say that if `typedef int THIS_IS_A_VECTOR` is present inside the struct then the struct should be shown as a vector even if it has additional unrecognized fields apart from begin/end/[capacity]; or `typedef int THIS_IS_NOT_A_CONTAINER` would make the debugger show the struct plainly even if has begin+end and nothing else. That's just off the top of my head, I haven't thought in the direction of adding markup to the code.
A maintained collection of recognizers (in some new declarative language?) for containers in various versions of various libraries sure sounds nice at least in theory (then maybe I wouldn't've needed to do all the terrible things that I did in `src/pretty.rs`). But I don't want to maintain such a thing myself, and don't have useful thoughts on how to go about doing it. Except maybe this: nnd got a lot of mileage from very loose duck-typed matching; it doesn't just look for fields "begin" and "end", it also (1) strips field names to remove common suffixes and prefixes: "_M_begin_", "__begin_", "c_begin" are all matched as "begin", (2) unwraps struct if it has just one field: `foo._M_t._M_head_impl._M_whatever_other_nonsense._M_actual_data` becomes just `foo._M_actual_data`; this transformation alone is enough to remove the need for any custom pretty-printer for std::unique_ptr - it just unwraps into a plain pointer automatically. Tricks like this cut down the number of different recognizers required by a large factor, but maybe would occasionally produce false positives ("pretty-print" something that's not a container).
(Dump of thoughts about the expression language, probably not very readable: The maximally ambitious version of the language would have something like: (1) compile to bytecode or machine code for fast conditional breakpoints, (2) be able to inject the expression bytecode+interpreter (or machine code) into the debuggee for super fast conditional breakpoints, and maybe for debuggee function calls along the way, (3) have two address spaces: debuggee memory and script memory, with pointers tagged with address space id either at runtime or at compile time, ideally both (at compile time for good typechecking and error messages, at runtime for being able to do something like `let elem = if container.empty {&dummy_element} else {container.start}`; or maybe the latter is not important in practice, and the address space id should just be part of the pointer type? idk; I guess the correct way to do it is to write lots of pretty-printers for real containers in an imaginary language and see what comes up), (4) some kind of template functions for pretty-printing, (5) templates not only by type, but also maybe by address space, by whether the value's address is known (e.g. a debuggee variable may live on the stack at one point in the program and in register in another part), by variable locations if they're compiled into the bytecode (e.g. same as in the previous pair of parentheses), (6) use the same type system for the scripting language and the debugged program's types, but without RAII etc (e.g. the script would be able to create an std::vector and assign its fields, but it would be a "dead" version of the struct, with no constructor and destructor), (7) but there's at least one simplification: the script is always short-lived, so script memory allocations can just use an arena and never deallocate, so the language doesn't need RAII, GC, or even defer, just malloc. The design space of languages with multiple address spaces and tagged pointers doesn't seem very explored, at least by me (should look for prior art), so it'll take a bunch of thinking and rewriting. Probably the maximally ambitious version is too complex, and it's better to choose some simpler set of requirements, but it's not clear which one. If you somehow understood any of that and have thoughts, lmk :) )
saagarjha 14 hours ago [-]
I don't know if you have looked at LLDB but when it evaluates (non-trivial) expressions it does actually compile and link code into the inferior's address space. One of the major selling points when it came out was that you could write "real code compiled by a a real compiler (LLVM)" rather than whatever ad-hoc thing that GDB knows how to do. In theory this gave better support out of the box for things that can't be represented with pointer dereferences or whatever most debuggers support for their data visualization. The downside is that LLDB is extremely slow, and it still fails a lot when dealing with templated types because it will claim (whether honestly or not) that the specialization it wants is not present. And it doesn't look at your source code to generate a new one, which would be an excellent showcase of the LLVM stack, but I guess a bridge too far for a debugger :/
For your thing: I think you can get pretty far with what you're doing, but I do want to point out that just the standard types will probably work for Rust but in C++ ever nontrivial project has their own standard library. Most also hide their data behind a void *impl or whatever so no debugger knows how to deal with it out of the box. I don't expect you to parse the codebase for operator[] or whatever but I think you'd ideally want a simple DSL for building pretty printers, with maybe memory reads and conditionals, plus some access to debug info (e.g. casts and offsetof). I don't think that would be too awful for complexity or performance.
mplanchard 21 hours ago [-]
Looks great, reminds me of the GUD interface in emacs. I’m unable to get it to find the code for a crate in a rust workspace. I’ve tried pointing the -d argument at the crate subdirectory, but nothing shows up in the code window. Any tips for debugging this issue?
al13n 10 hours ago [-]
Weird. Maybe the binary is just built without debug info? Does the list of binaries at the top right say whether debug info was loaded? Are file names and line numbers shown, e.g. in stack trace? Press 'o' in the code window to see+search the source code file paths as they appear in debug info. Also feel free to create a github issue, I'm likely to miss comments here.
endorphine 20 hours ago [-]
This is amazing, thanks. What would you say was the most challenging part?
AtlasBarfed 1 days ago [-]
"what we mean by fast"
I cannot tell you how much respect I feel for you
jonstewart 11 hours ago [-]
This looks great, thank you. I have been spending time in lldb the past couple days and lamenting how terrible of an experience it is compared to an IDE.
Do you know what would be involved in getting this to work on macOS?
al13n 10 hours ago [-]
I don't have all details, but seems like lots of work:
* Mach APIs instead of ptrace (probably a lot of changes).
* Mach-O instead of ELF.
* Some other APIs instead of /proc/<pid>/{maps,stat,...}
* Probably arm in addition to x86?
* Dealing with security stuff.
* Probably lots of other small differences everywhere.
Limiting the scope to one OS and CPU arhitecture was a big part of how I was able to make a usable debugger in a reasonable time.
dec0dedab0de 1 days ago [-]
I don't work with anything that would need this, but I love TUIs so I checked it out and saw this bit:
Operations that can't be instantaneous (loading debug info, searching for functions and types) should be reasonably efficient, multi-threaded, asynchronous, cancellable, and have progress bars.
I wish this were more common, especially the progress bars thing.
Conscat 1 days ago [-]
At my previous job, the product I worked on would open something like 200 DLL plugins on start-up. This would basically hang GDB while it's indexing the DWARF symbols, but LLDB gives you a very nice asynchronous progress indicator for that.
saagarjha 15 hours ago [-]
Yeah, they added it recently, it's quite nice. Of course what would be nicer is parsing symbols faster (come on, I have a bunch of cores on this machine) but it's better than just waiting forever that it used to do before.
PTOB 22 hours ago [-]
As someone who spends lots of time in Autodesk tools, this made me tear up a little bit.
philsnow 1 days ago [-]
GDB already has a TUI.. it's pretty "vintage" though. I looked at the screenshot at the top of the repo README and it looks like it has a lot more creature comforts (read: any at all compared to GDB).
al13n 1 days ago [-]
Fun fact: gdb tui was the last straw that made me start working on nnd. I tried it for the first time, and it was taking 2 seconds to respond to every input, and that made me so angry I started researching how to make a debugger :)
jcalabro 1 days ago [-]
I had pretty much the exact same experience with my debugger (uscope). Your debugger looks awesome, nice work! Hopefully I'll have time to get back to mine at some point (or hopefully RAD comes to Linux first haha)
mbeavitt 1 days ago [-]
check out [cgdb](https://github.com/cgdb/cgdb) - ncurses based gdb tui, with vim bindings. It's no longer actively worked on, but is decently maintained with bugfixes etc AFAIK.
(It shows only about 500 MB of machine code, and the rest of the gigabytes are debug info.)
mmoskal 1 days ago [-]
Very typical for anything with CUDA (they tend to compile everything for 10 different architectures times hundreds of template kernel parameters).
Not sure about ClickHouse though.
PhilipRoman 1 days ago [-]
#include <iostream>
1 days ago [-]
mrazomor 1 days ago [-]
Heavy use of C++ templates can significantly increase the binary size. Same for the heavy use of generated code (e.g. protocol buffers etc.).
eclbg 24 hours ago [-]
ClickHouse is a completely stand-alone binary that doesnt rely on any linked libraries. Not sure how much of this explains the large binary size, though.
coldblues 1 days ago [-]
The RAD Debugger is getting a Linux port so hopefully we won't have to deal with the sad state of Linux debugging anymore.
People here might also be interested in pwndbg, which adds a lot of qol improvements to the typical gdb/lldb experience. Including splitting dialogs over tmux panes, a lot more context info added to debug lines like where do pointers point to. Heap inspection. Colorization. makes for a much more friendly debugging experience.
trollbridge 1 days ago [-]
Awesome work. Reminds me of CodeView back in the day, which I've been wishing I could have back since, and no, a gigantic pile of Emacs or vim plugins is not the equivalent.
jebarker 1 days ago [-]
This is nice. I want something like this for python that I can use on remote servers and compute nodes of clusters. I've tried pudb but I find it doesn't behave well with terminal resizing and I wish the interface was a little more configurable.
sys_64738 1 days ago [-]
I couldn't python without adding -m pudb. That's not to say it's not temperamental, but I can live with the quirks once learned.
jebarker 1 days ago [-]
I should force myself to live with it for a week and see if I get more comfortable. One face of it it certainly does everything I currently rely on vscode remote for
tw600040 1 days ago [-]
Not related to this post, but why in the world is anyone using TUI. Either go with GUI or go with commandline. This no man's land in the middle is the worst of both worlds..
wormius 1 days ago [-]
TUIs are often more responsive in general. Some of us like the terminal and want to minimize as much mouse usage as possible (yes hotkeys exist in good GUI apps, but they're still primarily built around the WIMP model).
Command line often requires a lot of switch memorization. Command Line doesn't offer the full interactive/graphical power in this sort of situation. Command line is great for scripts and long running apps, or super simple interfaces.
Different apps have different requirements. Not everything needs a TUI, not everything needs a GUI, and if you want something similar to a GUI while staying in the terminal. Perhaps you don't have access to a windowing environment for some reason; perhaps you want to keep your requirements low in general.
Finally, why do you care? Some people like it others don't. Nobody comes in and shits on any programs that are GUI if they don't like it, they just don't use it.
So, to quote The Dude: "That's just, like, your opinion man". Sorry for the snark, but... It really is, and you're free to have it. But it seems an irrelevant point, and there may be better forums/posts (maybe an "Ask HN" question would be a good option) to discuss this question in depth beyond snark.
yjftsjthsd-h 19 hours ago [-]
IMHO TUIs are the best of both worlds. Generally light and responsive [0], transparent over SSH, neatly falls into a tab/pane/window in screen/tmux/zellij, offer essentially everything I wanted from a GUI except graphics [1] which isn't usually a problem, and delightfully free of the latest charming "innovations" in UI reinvention (GNOME, I am looking directly at you).
[0] It is if course possible to make a light GUI and a slow+bloated TUI, but both are less common than the alternative.
[1] Sixel et al. exist but IME they rarely work well. Sadly.
Short summary: No animations, No symbols, No touch optimization, no responsive design and I do most of the other stuff in the Terminal anyways so TUI is better "integration" YMMV :)
badsectoracula 1 days ago [-]
> No animations, No symbols, No touch optimization, no responsive design
You don't have to make a GUI with any of those.
perching_aix 1 days ago [-]
You don't, but others can and do. With these being limitations for TUIs however, others can't do either, making this a selling point (not a TUI afficionado, just passing by).
al13n 1 days ago [-]
In this post's case, I went with TUI over GUI to make it work over SSH, which is how I use it most of the time.
justinrubek 1 days ago [-]
I have many beloved TUI tools at this point, and I am considering investing further in TUI for some further projects I am building that I would want some kind of interface for beyond a command line. I'm not convinced by this argument. Would you mind elaborating on any specifics?
JCattheATM 1 days ago [-]
Sometimes it's really nice having a terminal tool with a minimal interface without needing switches.
Other times it's just a contrarian thing.
KerrAvon 1 days ago [-]
One common use case is remote debugging over serial or ssh.
edit: and a reason you would do this locally using ssh is debugging the UI layer itself. if you have to step through the window server, you can't be using the window server at the same time. Remote lldb/gdb debugging is often just flaky. I don't know why they're so unreliable, but they are.
fsckboy 1 days ago [-]
are you considering emacs a tui? vi/vim? if you want to edit without a tui, i gotta recommend teco over ed/ex
larusso 1 days ago [-]
Back in my native android days I used cgdb to have a split screen view of the sources I debug. The vim like interface was exactly what I needed. Just thought about it after seeing this project.
Love TUI debuggers since I first used the Borland IDE.
It is incredible how small and well done that IDE was: hyperlinked (!) documentation, with examples (!!), and awesome debugger.
All with TUI.
dvektor 1 days ago [-]
Very cool! I have been using LLDB quite a bit lately so I am eager to try this out. The state of debuggers dev experience really hasn't caught up to what things like Cargo have done for build systems, so I am glad to see people working on things like this.
worldsavior 1 days ago [-]
Why do you use LLDB on Linux?
saagarjha 15 hours ago [-]
Not OP but I use it occasionally because I know how to make it do stuff that I would have otherwise look up in GDB
dvektor 1 days ago [-]
I asked someone else what they use for rust. Normally I get by well enough with good structured tracing but lately that hasn't been cutting it.
Zambyte 1 days ago [-]
Cool :D anyone get a chance to try this out with Zig yet?
jcalabro 23 hours ago [-]
I just gave it a try on some zig code and no dice. Works great with C though, and I might just be doing it wrong!
gitroom 15 hours ago [-]
been messing around with tui apps for a while - always get drawn back to how much faster terminal stuff feels. this kinda project honestly feels like it gets what i want better than some huge gui setup
1 days ago [-]
fcoury 1 days ago [-]
Is there anything similar to this that would support arm64? Unfortunately lldb is still not on par with even gdb.
danhau 1 days ago [-]
Cool! Looks like the btop of debuggers. That‘s certainly a tool I would love to have.
Keyframe 1 days ago [-]
great! I already gave up on RemedyBG or RAD/Epic debugger ever on linux to happen.
HexDecOctBin 22 hours ago [-]
Why? Ryan has explicitly said that he will soon be working on the Linux port of RADDbg.
deagle50 1 days ago [-]
the macOS drought continues
_bohm 1 days ago [-]
It is really crazy how limited debugger options are on macOS. Is it simply the case that there are not that many people writing code in systems languages on macOS outside of XCode?
marssaxman 1 days ago [-]
I used to be such a person, but after years of feeling as though Apple found people like me irritating and wished we would all stop bothering them, I finally took the hint.
Linux may not be so pretty, but it's far more comfortable.
deagle50 1 days ago [-]
Linux is great, my issue is laptop hardware.
marssaxman 1 days ago [-]
What problems do you encounter? Which sorts of laptops do you prefer?
My "all Thinkpad, all the time" strategy has generally served me well (though I was disappointed by the most recent one, a T14, which would never sleep properly).
deagle50 9 hours ago [-]
poor displays, trackpads, cooling, performance, and battery life.
dmitrygr 1 days ago [-]
MacBookAir + aarch64 linux vm -- best of all the worlds. Linux for the 5% of things I need linux for, amazing battery life and hardware for the remaining 95% of things my laptop does.
saagarjha 1 days ago [-]
Apple continuously makes the life of third party debuggers difficult, to the point where doing so today on a “stock” system requires malware-like techniques to get around their mitigations.
pimeys 1 days ago [-]
Kind of. I am a systems engineer and want to work on an open OS I can debug with my syseng skills...
worldsavior 1 days ago [-]
It's because macOS has security measures.
theoperagoer 1 days ago [-]
Very cool. How many architectures do you support?
baumschubser 1 days ago [-]
It does say so right in the readme:
> Linux only
> x86 only
> 64-bit only
theoperagoer 1 days ago [-]
no need to be a jerk
yjftsjthsd-h 18 hours ago [-]
That didn't sound impolite to me. They answered the question, and while they pointed out that you could have gotten the answer yourself with one click and a little reading (it's the 3rd section in the readme), that's... true and they weren't rude about it.
theoperagoer 4 hours ago [-]
I disagree.
FpUser 1 days ago [-]
It looks very useful. I will definitely test it. Thank you for such contribution.
jmclnx 1 days ago [-]
Looks very nice, will need to give it a spin :)
colesantiago 1 days ago [-]
How does one install this?
I don't want to go through the curl | bash either for security reasons.
It would be nice to have some package manager support, but it looks cool.
laserbeam 1 days ago [-]
There’s only 1 artifact. A static executable. No “need” for a package manager. If someone builds a package manager they would just need to compile the src and ship the executable.
[^1]: https://www.timdbg.com/posts/writing-a-debugger-from-scratch... [^2]: https://nostarch.com/building-a-debugger
[1]:https://keowu.re/posts/Writing-a-Windows-ARM64-Debugger-for-...
--
1: https://github.com/munificent/craftinginterpreters/issues/92...
Another book added to my To-Read list
Didn't expect it to be posted, readme maybe doesn't have enough context. It just says "Essential features are there". What are those? Most of what I've ever used in any debugger:
* Showing code, disassembly, threads, stack traces, local variables.
* Watches, with a little custom expression language. E.g. you can do pointer arithmetic, type casts, turn a pointer+length into an array, show as hex, etc. Access to local and global variables, thread-local variables, registers. Type introspection (e.g. sizeof and offsets of fields).
* Pretty printers for most C++ and Rust standard library types. Probably fragile and version-dependent (e.g. fields names often changes across versions), please report when they don't work.
* Automatically down-casting abstract classes to concrete classes.
* Breakpoints, conditional breakpoints (but no data breakpoints yet).
* Stepping: into/over/out a source code line, into/over a disassembly instruction, over a source code column (when there are multple statements one line, e.g. to skip evaluation of arguments of a function call). All places where control can stop (statements) are highlighted in the code, so you usually don't get surprised by where a step takes you. (...except when there's garbage in debug info, and you end up temporarily on line 0 or something. This happens frustratingly often, and there's not much I can do about it. I already added quite a few workarounds to make stepping less janky in such cases. If a step takes you to an unexpected place, it usually under-steps rather than over-steps, so you can just step again until you end up in the correct place.)
* Various searches: file by name, function by name, function by address (like addr2line), type by name, global variable by name, thread by stack trace.
* Debugging core dumps. There's also a gdump-like tool built in (`nnd --dump-core`) that makes core dump of a running process without killing it; it uses fork to minimize downtime (usually around a second even if there are tens of GB of memory to dump).
* Customizable key bindings, see `nnd --help-files` or `nnd --help-state`.
* TUI with mouse support, tooltips, etc.
For example, a crate that could be linked in to provide some "well-known" object shapes (hashmaps, vec, hashset, etc) with marker values that could be heuristically analyzed to understand the debuggability of those objects?
Alternatively, I'd love to have a crate with recognizers and/or heuristics that could be somewhat debugger-independent and could be worked on for the benefit of other users. I'm quite an experienced Rust developer, just not really with debuggers, happy to help if there's a sandbox project that this could be plugged into.
For custom pretty-printers, the long-term plan is to make the watch expression language rich enough that you can just write one-liners in the watches window to pretty-print your struct. E.g. `for entry in my_hashmap.entries_ptr.[my_hashmap.num_entries] { if entry.has_value { yield struct {key: &entry.key, value: &entry.value}; } }`. Then allow loading a collection of such printers from a file; I guess each pretty-printer would have a regex of type names for which to use it (e.g. `std:.*:unordered_(multi)?(set|map)`). There are not very many containers in standard libraries (like, 10-20?), and hopefully most of their pretty-printers can be trivial one-liners, so they would be easy enough to add and maintain that incompatibility with other debuggers wouldn't be a big concern. Currently nnd doesn't have anything like that (e.g. there are no loops in the watch expression language), I don't have a good design for the language yet, not sure if I'll ever get around to it.
(Btw, "pretty-printers" is not a good name for what I'm talking about; rather, it transforms a value into another value, e.g. an std::vector into a slice, or an unordered_map into an array of pairs, which is then printed using a normal non-customizable printer. The transformed value ~fully replaces the original value, so you can e.g. do array indexing on std::vector as if it was a slice: `v[42]`. This seems like a better way to do it than a literal pretty-printer that outputs a string.)
What kind of cooperation from library authors would help with container recognition... The current recognizers are just looking for fields begin/end (pointers) or data/len (pointer and number), etc (see src/pretty.rs, though it's not very good code). So just use those names for fields and it should work :) . I'm not sure any more formal/bureaucratic contract is needed. But it would be easy for the recognizer to also check e.g. typedefs inside the struct (I guess most languages have something that translates to typedefs in debug info? at least C++ and Rust do). E.g. maybe a convention would say that if `typedef int THIS_IS_A_VECTOR` is present inside the struct then the struct should be shown as a vector even if it has additional unrecognized fields apart from begin/end/[capacity]; or `typedef int THIS_IS_NOT_A_CONTAINER` would make the debugger show the struct plainly even if has begin+end and nothing else. That's just off the top of my head, I haven't thought in the direction of adding markup to the code.
A maintained collection of recognizers (in some new declarative language?) for containers in various versions of various libraries sure sounds nice at least in theory (then maybe I wouldn't've needed to do all the terrible things that I did in `src/pretty.rs`). But I don't want to maintain such a thing myself, and don't have useful thoughts on how to go about doing it. Except maybe this: nnd got a lot of mileage from very loose duck-typed matching; it doesn't just look for fields "begin" and "end", it also (1) strips field names to remove common suffixes and prefixes: "_M_begin_", "__begin_", "c_begin" are all matched as "begin", (2) unwraps struct if it has just one field: `foo._M_t._M_head_impl._M_whatever_other_nonsense._M_actual_data` becomes just `foo._M_actual_data`; this transformation alone is enough to remove the need for any custom pretty-printer for std::unique_ptr - it just unwraps into a plain pointer automatically. Tricks like this cut down the number of different recognizers required by a large factor, but maybe would occasionally produce false positives ("pretty-print" something that's not a container).
(Dump of thoughts about the expression language, probably not very readable: The maximally ambitious version of the language would have something like: (1) compile to bytecode or machine code for fast conditional breakpoints, (2) be able to inject the expression bytecode+interpreter (or machine code) into the debuggee for super fast conditional breakpoints, and maybe for debuggee function calls along the way, (3) have two address spaces: debuggee memory and script memory, with pointers tagged with address space id either at runtime or at compile time, ideally both (at compile time for good typechecking and error messages, at runtime for being able to do something like `let elem = if container.empty {&dummy_element} else {container.start}`; or maybe the latter is not important in practice, and the address space id should just be part of the pointer type? idk; I guess the correct way to do it is to write lots of pretty-printers for real containers in an imaginary language and see what comes up), (4) some kind of template functions for pretty-printing, (5) templates not only by type, but also maybe by address space, by whether the value's address is known (e.g. a debuggee variable may live on the stack at one point in the program and in register in another part), by variable locations if they're compiled into the bytecode (e.g. same as in the previous pair of parentheses), (6) use the same type system for the scripting language and the debugged program's types, but without RAII etc (e.g. the script would be able to create an std::vector and assign its fields, but it would be a "dead" version of the struct, with no constructor and destructor), (7) but there's at least one simplification: the script is always short-lived, so script memory allocations can just use an arena and never deallocate, so the language doesn't need RAII, GC, or even defer, just malloc. The design space of languages with multiple address spaces and tagged pointers doesn't seem very explored, at least by me (should look for prior art), so it'll take a bunch of thinking and rewriting. Probably the maximally ambitious version is too complex, and it's better to choose some simpler set of requirements, but it's not clear which one. If you somehow understood any of that and have thoughts, lmk :) )
For your thing: I think you can get pretty far with what you're doing, but I do want to point out that just the standard types will probably work for Rust but in C++ ever nontrivial project has their own standard library. Most also hide their data behind a void *impl or whatever so no debugger knows how to deal with it out of the box. I don't expect you to parse the codebase for operator[] or whatever but I think you'd ideally want a simple DSL for building pretty printers, with maybe memory reads and conditionals, plus some access to debug info (e.g. casts and offsetof). I don't think that would be too awful for complexity or performance.
I cannot tell you how much respect I feel for you
Do you know what would be involved in getting this to work on macOS?
* Mach APIs instead of ptrace (probably a lot of changes).
* Mach-O instead of ELF.
* Some other APIs instead of /proc/<pid>/{maps,stat,...}
* Probably arm in addition to x86?
* Dealing with security stuff.
* Probably lots of other small differences everywhere.
Limiting the scope to one OS and CPU arhitecture was a big part of how I was able to make a usable debugger in a reasonable time.
Operations that can't be instantaneous (loading debug info, searching for functions and types) should be reasonably efficient, multi-threaded, asynchronous, cancellable, and have progress bars.
I wish this were more common, especially the progress bars thing.
(It shows only about 500 MB of machine code, and the rest of the gigabytes are debug info.)
Not sure about ClickHouse though.
https://github.com/EpicGamesExt/raddebugger/issues/23
Command line often requires a lot of switch memorization. Command Line doesn't offer the full interactive/graphical power in this sort of situation. Command line is great for scripts and long running apps, or super simple interfaces.
Different apps have different requirements. Not everything needs a TUI, not everything needs a GUI, and if you want something similar to a GUI while staying in the terminal. Perhaps you don't have access to a windowing environment for some reason; perhaps you want to keep your requirements low in general.
Finally, why do you care? Some people like it others don't. Nobody comes in and shits on any programs that are GUI if they don't like it, they just don't use it. So, to quote The Dude: "That's just, like, your opinion man". Sorry for the snark, but... It really is, and you're free to have it. But it seems an irrelevant point, and there may be better forums/posts (maybe an "Ask HN" question would be a good option) to discuss this question in depth beyond snark.
[0] It is if course possible to make a light GUI and a slow+bloated TUI, but both are less common than the alternative.
[1] Sixel et al. exist but IME they rarely work well. Sadly.
Short summary: No animations, No symbols, No touch optimization, no responsive design and I do most of the other stuff in the Terminal anyways so TUI is better "integration" YMMV :)
You don't have to make a GUI with any of those.
Other times it's just a contrarian thing.
edit: and a reason you would do this locally using ssh is debugging the UI layer itself. if you have to step through the window server, you can't be using the window server at the same time. Remote lldb/gdb debugging is often just flaky. I don't know why they're so unreliable, but they are.
https://cgdb.github.io/
It is incredible how small and well done that IDE was: hyperlinked (!) documentation, with examples (!!), and awesome debugger.
All with TUI.
Linux may not be so pretty, but it's far more comfortable.
My "all Thinkpad, all the time" strategy has generally served me well (though I was disappointed by the most recent one, a T14, which would never sleep properly).
> Linux only > x86 only > 64-bit only
I don't want to go through the curl | bash either for security reasons.
It would be nice to have some package manager support, but it looks cool.
All you need to do otherwise is add it in PATH.
Set the executable bit (using chmod or your file explorer's properties tab).
The program is now installed.