Do you use a debugger?

I’ve never used debuggers, and that’s not to say I don’t write bugs…I’ve just never developed skills with them, so I do a lot of print debugging.

  1. Who’s using a debugger? What’s you setup?
  2. What useful information does a debugger offer that I otherwise might not see?
  3. What’s debug info?
  4. Are debuggers only for IDEs, or are there standalone debugging tools?
  5. What are the legacy debugging tools?
  6. What are the solidly useful and standard debugging tools?
  7. What would you consider are the new / up and coming / promising debugging tools and what do they do differently?
1 Like

I really like nnd.
It’s fast, it supports watchers, registers etc… It shows both source code and assembly at the same time.
It also easy to use. In my programs i put a @breakpoint() and then just nnd your_program your arguments.

6 Likes

I was a heavy user of “Debugger as a REPL with context” when I was writing Kotlin/Java :scream: 10 years ago:

I mostly avoided debuggers since, as I switched to native languages, and debuggers for native languages are abysmal.

I do occasionally still reach for a debugger when something goes really wrong (“I am in a signal handler, but where’s my stack?” kind of wrong), and its been a uniformly mixed experience — half of the time debugger itself crashes on you, and the other half it prints random garbage, and anything more complicated than “what’s the contents of this register/memory” is untrustworthy.

1 Like

I’ve been using CodeLLDB with VS Code.

On x86_64, Zig no longer uses the LLVM backend by default, so I currently need to build with -fllvm for CodeLLDB to work properly.

Sometimes I also install the unit test artifact from build.zig so I can debug tests directly:

test_step.dependOn(&b.addInstallArtifact(mod_test, .{
    .dest_sub_path = "../test/test",
}).step);

This generates a standalone test executable that can be launched directly from the debugger.

In cases that lead to segmentation faults, variables in memory are often already corrupted by the time the crash happens.

I use the debugger to inspect how badly the memory is corrupted and try to infer when and where things first went wrong.

1 Like

Heya !

  1. I’m mainly using gdb by hand, aka. running gdb zig-out/bin/program. I’ve recently added nvim-dap (like a plugin to your editor you can think) to my nvim workflow to have integrations in my editor. DAP is a protocol for an editor / client to communicate with the debugger.
  2. In your debugger it is easier to see, step by step, what is your program doing. It avoids to add multiple print statement and recompile each time. You have also the ability to modify values on the fly, add breakpoints if and only if a values has changed to something, etc. It is a very powerful tool.
  3. Debug info is information attached to a binary that helps the debugger know what bytes in the compiled code correspond to what code in text in your files
  4. There are a lot of standalone debugging tools, most of them I would say are standalone. For zig / c / c++ / rust you might consider lldb (llvm’s debugger, preferred) or gdb (I chose it just because I have familiarity with it)
  5. I don’t know ? I’m not really sure what you refer by that
  6. I would advise lldb, don’t forget that you need to compile the binary using llvm (change your build.zig) to see correct information
  7. I’m not aware of new and upcoming debuggers, but others will know more about that subject than me
1 Like

additional 2 cents to the picture

2 main flows of debugger usage:

  • walking
  • catching

First one I walking within code, step by step, check logic, routing, values of variables, stack and so on

Second - run whole application or test and when breakpoint raised or debugger catched segfaults and so on - check status of program…

I always use debuggers, it does not mean that my code does not contain prints…

As you can see my Zig dev. env - JetBrains CLion (free for oss) + ZigBrains plugin + Gdb - Linux

On Windows - almost the same , just lldb instead of gdb (iirc)

CLion Debug configurations

take a look Debugging Zig (with a debugger) - it contains a lot of information

Happy debugging

1 Like

I only use them for strange bugs and in pretty much all cases I use a time travelling debugger for these cases.

Sadly there are no good ones with wide platform support except Undo UDB (which isn’t free).

Not very “general purpose”, but when I’m doing embedded Zig on an stm32 I use the Stm32CubeProgrammer debugger window to get some clues as to what’s gone horribly wrong. It typically will give a code for what has caused a hard fault if one has gone off, as well as the instruction address that may have lead to the fault.

It also gives register values which has made it nice to see when a sentinel value is showing up, yet again cluing me in to what has gone horribly wrong.

I can second this, although it has some quirks regarding some Zig language constructs its an amazing debugging experience.

With all due respect to those who like to debug Zig, it’s just a nightmare. My only experience with Zig debugging was based on the invisibility of variables after exiting the main function and entering another one. Therefore, I gave up on these futile attempts to debug. I hope that this will be fixed someday.

I pretty much refuse to work in any language that doesn’t allow to tightly integrate step-debugging into the development workflow (thankfully with VSCode and the Debug Adapter Protocol the situation is much better than 15 years ago, even if the DAP is quite barebones).

My typical dev-workflow is basically: write a couple of lines of code (or more than a couple), press F5 (build, and start into debug session), and step through the code. I use this time to reflect on the code (does the init code require too much boilerplate? is this loop here too heavy? could this expression be simplified?), and generally to get and stay familiar with a codebase (e.g. I also step though old code from time to time just to confirm that it doesn’t do dumb shit).

E.g. without a debugger, any codebase always feels ‘alien’ to me. I need to step through it to really get the code into my brain (or rather: what the code does to data - debugging a program basically adds the time dimension to programming).

…also this is just CPU-debugging… but for rendering work, GPU debuggers like RenderDoc or the Metal and D3D debuggers integrated into Xcode and Visual Studio are even more essential.

The memory debuggers in Visual Studio and Xcode (via Instruments) also come in handy when debugging or optimizing memory management (you basically get a realtime view of how much memory your app currently allocates, can take snapshots and drill down to each unique allocation).

Also important: I use those debuggers 99% of the time not for actual debugging, but for runtime code- and data-exploration. This early time in the debugger drastically reduces time spent later in the debugger when shit has hit the fan :wink:

To answer your questions one by one:

  1. Who’s using a debugger? What’s you setup?

Me :slight_smile: Setup for C/C++ code is either VStudio, XCode or more commonly VSCode (each of those tools have their up- and downsides, also depending on platform). For Zig and other languages that support DWARF debug info I typically use VSCode with the CodeLLDB debug extension.

  1. What useful information does a debugger offer that I otherwise might not see?

Runtime control flow and runtime data updates, obviously :slight_smile: You can set all sorts of ‘smart’ breakpoints (break when an expression is true, break when any code reads or writes a specific memory location, etc…). Also for “actual” post-mortem debugging (e.g. a bug happens out in the wild, you get a “minidump” from the user (ideally) and load that into the debugger - even with optimization enabled this usually gives you an inspectable callstack at the time of the crash, with most of the runtime data state around the crash inspectable).

  1. What’s debug info?

Additional info associated with the executable which maps machine code locations back to source code locations, and the same for type definitions (e.g. what structs exists and how they map to memory). This debug info is sometimes embedded in the exe (usually on GCC and Clang), and sometimes lives in external files (usually on MSVC with .pdb files)

  1. Are debuggers only for IDEs, or are there standalone debugging tools?

There are standalone debuggers like gdb, lldb or RemedyBG (RemedyBG by remedybg), but personally I prefer IDE integration. GDB and LLDB might sometimes be useful on a barebones terminal (like connecting via SSH to a Linux server, but such remote debug session are also trivial with VScode). Note though that most non-Visual-Studio debugger integrations are simply GUI frontends for GDB or LLDB.

  1. What are the legacy debugging tools?

Are there legacy debugging tools? :smiley: Maybe gdb and lddb, but with a graphical frontend they are as ‘modern’ as anything else.

  1. What are the solidly useful and standard debugging tools?

The best debugger is the one in Visual Studio, it’s as simple as that. Shame that everything else in Visual Studio sucks. But in general, there hasn’t been much progress in the last 40 years or so, with the notable exception of rr: GitHub - rr-debugger/rr: Record and Replay Framework · GitHub (which comes with too many caveats though, and still lacks a proper and easy to use GUI AFAIK).

For a 3D API debugger I really like the Metal debugger that’s integrated into Xcode, it’s very ‘visual’:

…also some home computer emulator have really nice debuggers (shameless plug):

  1. What would you consider are the new / up and coming / promising debugging tools and what do they do differently?

I’d really like to see “Edit & Continue” and “Rewind” available in all debuggers and generally better data visualizations (realtime memory read/write heatmaps, image viewers, graphs to watch values over time etc…). Some of that might be better done with a Dear ImGui debugging UI that’s integrated right into the debuggee… but why not have a debugger where the debuggee can plug in and provide custom visualization code via Dear ImGui? :slight_smile:

“Edit & Continue” so that there’s no real difference between the “editing mode” and “debugging mode” in a typical development session, instead you always develop on the ‘live state’ of the program.

“Rewind” simply as a little time slider at the bottom of the debugger which lets you easily rewind to an earlier state in program execution (or at least give me manually created whole-program-state snapshots which I can return to).

PS: I sometimes wonder if famous people talking shit about debugging tools are responsible for why debuggers are basically stuck in the 1990s. E.g. Brian W Kernighans “The most effective debugging tool is still careful thought, coupled with judiciously placed print statements.” - first, keep in mind that this was written in 1979, and second, even otherwise decent and intelligent people can be spectacularly wrong on some topics :wink:

8 Likes

Yesterday I was testing my friend’s (C) project and found that it segfaulted on startup. simply doing lldb ./app-name and running the program gave me the line it crashed on, which gave me the info I needed in order to form a hypothesis (which was that since I have several virtual audio devices with a very large number of channels, my friend’s program was walking past the end of an allocated array trying to process them).

Then I could see what the correct upper bound for the number of channels might be by inspecting the local variable capturing the channel number at the point of crash and make a one-line PR to an unfamiliar codebase fixing the bug for me.

Obviously none of this is impossible with print debugging, but it would have taken me a lot longer to do :slight_smile:

5 Likes

Something something lipstick on a pig :smiley: .

IMO debugging tools are over cumbered by legacy and inertia. DWARF itself is unnecessarily complex to parse, and that’s the basis for all debug info on Linux programs.

This is such an opaque problem space that very few people ever touch this, we end up with little iteration in that space, so little that even a new frontend for GDB feels old.

But I believe you agree with this since you have talked about “Edit & Continue” and “Rewind”, we need so much more of that! Also much simpler debugging info would facilitate for people to make ad-hoc debugging tools rather than rely on monolithic CLI-based tools like GDB and LLDB.

2 Likes

I use a debugger. I’m still learning the tool, so most of my use is fairly basic: set breakpoints, step through the code, print out variables.

I typically use lldb. Stock works fine if you compile your program with the llvm backend. However, I built the Zig fork and have been using that with the default debug backend. It’s been great.

I’ve used nvim-dap with it as well. This is really nice for easily toggling breakpoints from the source code. Still getting used to this. Normally I just use lldb from the cli.

I’m hoping RAD debugger linux support improves because I would love to start using that as well.

2 Likes

I do use a debugger (LLDB and GDB). However it is complicated by the fact that using zig’s default debug backend means you get DWARF version 5 debug info. Despite this being a standard for nearly a decade, neither of these debuggers support it.

My current “passion” project is building a debugger that is made for debugging Zig specifically, with support for C/C++ etc as secondary objectives. I’m curious to know why people don’t use debuggers, and those that do what workflows are the most important to them as well as what they wish they could have from a debugger.

I think you are right about this. Dwarf is pretty arcane, it was originally designed by, from my understanding, a fairly academic group. It also bears the weight of decisions from the 90’s that were pertinent then, but now seem more like unnecessary complexity. Furthermore there are multiple versions (v4/v5 mentioned above). Obviously you need to be able to support both versions as some compilation units may use one or the other within the same program.

4 Likes

I’m just going to add Undo UDB to this.

Also, this one has less downsides than rr, but has other downsides downsides in exchange.

1 Like

No, never—they confuse me.

This sort of thing has been my most frequent use of GDB as of late, but instead of re-running the crashing program, I would often use coredumpctl debug, which uses GDB (by default) to inspect the memory of the last crashed program, provided that you have core dumps enabled (which is usually the case).

You can do that every time a program ends with (core dumped). That happens on SIGSEGV and also SIGABRT, the latter of which Zig does when the program panics, so super useful.

Other than that, I usually stick to print debugging, even more with Zig than with C.

1 Like