[HN Gopher] Nnd - a TUI debugger alternative to GDB, LLDB
___________________________________________________________________
Nnd - a TUI debugger alternative to GDB, LLDB
Author : zX41ZdbW
Score : 269 points
Date : 2025-05-06 13:58 UTC (1 days ago)
(HTM) web link (github.com)
(TXT) w3m dump (github.com)
| dec0dedab0de wrote:
| 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 wrote:
| 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 wrote:
| 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 wrote:
| As someone who spends lots of time in Autodesk tools, this made
| me tear up a little bit.
| jmclnx wrote:
| Looks very nice, will need to give it a spin :)
| trollbridge wrote:
| 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 wrote:
| 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 wrote:
| 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 wrote:
| 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
| alfanick wrote:
| Sideline question: how do you end up with 2.5GB binary
| (ClickHouse as given in the readme)?
| PhilipRoman wrote:
| #include <iostream>
| mmoskal wrote:
| 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.
| mrazomor wrote:
| Heavy use of C++ templates can significantly increase the
| binary size. Same for the heavy use of generated code (e.g.
| protocol buffers etc.).
| zX41ZdbW wrote:
| This is visible in the embedded binary viewer:
| https://play.clickhouse.com/binary?user=binary&zoom=0&x=1620...
|
| (It shows only about 500 MB of machine code, and the rest of
| the gigabytes are debug info.)
| eclbg wrote:
| 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.
| dvektor wrote:
| 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 wrote:
| Why do you use LLDB on Linux?
| dvektor wrote:
| 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.
| saagarjha wrote:
| 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
| hippospark wrote:
| 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].
|
| [^1]: https://www.timdbg.com/posts/writing-a-debugger-from-
| scratch... [^2]: https://nostarch.com/building-a-debugger
| emmanueloga_ wrote:
| Cool! I did not know about that book. Added to [1]. :-)
|
| --
|
| 1:
| https://github.com/munificent/craftinginterpreters/issues/92...
| _benj wrote:
| Nice! Thanks for sharing :-)
|
| Another book added to my To-Read list
| phaistra wrote:
| I recently found a super cool article[1] which also gives a
| good overview of how a debugger works.
|
| [1]:https://keowu.re/posts/Writing-a-Windows-ARM64-Debugger-
| for-...
| FpUser wrote:
| It looks very useful. I will definitely test it. Thank you for
| such contribution.
| Zambyte wrote:
| Cool :D anyone get a chance to try this out with Zig yet?
| jcalabro wrote:
| 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!
| colesantiago wrote:
| 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 wrote:
| 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.
|
| All you need to do otherwise is add it in PATH.
| ablob wrote:
| You can always build it yourself and put the binary somewhere
| in your runtime path.
| degamad wrote:
| Download latest "nnd" file from
| https://github.com/al13n321/nnd/releases and place it somewhere
| you like.
|
| Set the executable bit (using chmod or your file explorer's
| properties tab).
|
| The program is now installed.
| Keyframe wrote:
| great! I already gave up on RemedyBG or RAD/Epic debugger ever on
| linux to happen.
| HexDecOctBin wrote:
| Why? Ryan has explicitly said that he will soon be working on
| the Linux port of RADDbg.
| theoperagoer wrote:
| Very cool. How many architectures do you support?
| baumschubser wrote:
| It does say so right in the readme:
|
| > Linux only > x86 only > 64-bit only
| theoperagoer wrote:
| no need to be a jerk
| yjftsjthsd-h wrote:
| 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 wrote:
| I disagree.
| philsnow wrote:
| 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).
| mbeavitt wrote:
| 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.
| cassepipe wrote:
| Since gdb uses readline, you can use vim binding on the
| command line by configuring your ~/.inputrc (will work for
| bash, rlwrap, etc.)
| arch_rust wrote:
| I developed https://github.com/wcampbell0x2a/heretek as a
| modern equivalent
| al13n wrote:
| 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 wrote:
| 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)
| shmerl wrote:
| nvim + nvim-dap + nvim-dap-ui + gdb beats gdb's barebones TUI
| by a huge margin.
| danhau wrote:
| Cool! Looks like the btop of debuggers. That's certainly a tool I
| would love to have.
| deagle50 wrote:
| the macOS drought continues
| _bohm wrote:
| 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?
| pimeys wrote:
| Kind of. I am a systems engineer and want to work on an open
| OS I can debug with my syseng skills...
| marssaxman wrote:
| 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 wrote:
| Linux is great, my issue is laptop hardware.
| dmitrygr wrote:
| 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.
| marssaxman wrote:
| 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 wrote:
| poor displays, trackpads, cooling, performance, and
| battery life.
| worldsavior wrote:
| It's because macOS has security measures.
| saagarjha wrote:
| 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.
| fcoury wrote:
| Is there anything similar to this that would support arm64?
| Unfortunately lldb is still not on par with even gdb.
| tieze wrote:
| 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.
| coldblues wrote:
| The RAD Debugger is getting a Linux port so hopefully we won't
| have to deal with the sad state of Linux debugging anymore.
| jpeeler wrote:
| Sounds like it's a little ways off though, no?
|
| https://github.com/EpicGamesExt/raddebugger/issues/23
| tw600040 wrote:
| 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..
| KerrAvon wrote:
| 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 wrote:
| are you considering emacs a tui? vi/vim? if you want to edit
| without a tui, i gotta recommend teco over ed/ex
| justinrubek wrote:
| 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?
| al13n wrote:
| 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.
| Linux-Fan wrote:
| I wrote about it extensively here:
| <https://masysma.net/37/why_terminal.xhtml>.
|
| 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 wrote:
| > No animations, No symbols, No touch optimization, no
| responsive design
|
| You don't have to make a GUI with any of those.
| perching_aix wrote:
| 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).
| wormius wrote:
| 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.
| JCattheATM wrote:
| Sometimes it's really nice having a terminal tool with a
| minimal interface without needing switches.
|
| Other times it's just a contrarian thing.
| yjftsjthsd-h wrote:
| 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.
| al13n wrote:
| Hi, author here :)
|
| 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.
| AtlasBarfed wrote:
| "what we mean by fast"
|
| I cannot tell you how much respect I feel for you
| mmastrac wrote:
| 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 wrote:
| 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_oth
| er_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 wrote:
| 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 wrote:
| 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 wrote:
| 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 wrote:
| This is amazing, thanks. What would you say was the most
| challenging part?
| jonstewart wrote:
| 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 wrote:
| 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.
| larusso wrote:
| 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.
|
| https://cgdb.github.io/
| udev wrote:
| 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.
| gitroom wrote:
| 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
___________________________________________________________________
(page generated 2025-05-07 23:01 UTC)