top | item 45156414

Things you can do with a debugger but not with print debugging

257 points| never_inline | 6 months ago |mahesh-hegde.github.io

214 comments

order
[+] jonhohle|5 months ago|reply
I don’t mind using a debugger, but one of the advantages to printed debugging is that it is universal. I work on a project with code in five languages (6 if you count the build system, 7 if you add shell scripts). Two of them I know the debugger very well. One of them I might be able to get by. One of the others I might have use the debugger once and the last I’ve never touched.

printf debugging works in all of them, even the build system and shell scripts.

A debugger can be great, no question about it, even for remote debugging. In my experience, I’ve seen fewer people effectively debug unfamiliar systems quickly.

[+] jbverschoor|5 months ago|reply
That’s just bc most tooling is utter crap, except for Java+eclipse
[+] swaits|5 months ago|reply
Author missed one of the best features: easy access to hardware breakpoints. Breaking on a memory read or write, either a raw address or via a symbol, is one of the most time saving debugging tools I know.
[+] jesse__|5 months ago|reply
Oh my god, same. This literally catches bugs with a smoking gun in their hand in a way that's completely impossible with printf. I'd upvote this 100 times if I could.
[+] coderatlarge|5 months ago|reply
windbg used to offer scripting capabilities that teams could use to trigger validation of any number of internal data structures essentially at every breakpoint or watchpoint trigger. it was a tremendous way to detect subtle state corruption. and sharing scripts across teams was also a way to share knowledge of a complex binary that was often not encoded in asserts or other aspects of the codebase.
[+] AndyKelley|5 months ago|reply
Combined with time travel it's mind-blowing.

Stumble upon some corrupted memory? Just put a watch point on it, and run the program backwards. Two weeks just turned into two minutes.

[+] praptak|5 months ago|reply
From the same toolbox: expression watch. Set a watch on the invariant being violated (say "bufpos < buflen") and get a breakpoint the moment it changes.
[+] roca|5 months ago|reply
Especially with combined with reverse-execution in rr or UndoDB!
[+] yakshaving_jgt|5 months ago|reply
Is there somewhere where this approach is described in more detail?
[+] makeitdouble|5 months ago|reply
While a debugger is of high value, having access to a REPL also covers the major use cases.

In particular, REPL tools will work on remote session, on pre-production servers etc. _if_ the code base is organized in a somewhat modular way, it can be more pleasant than a debugger at times.

Makes me wonder if the state of debugging improved in PHP land. It was mostly unusable for batch process debugging, or when the server memory wasn't infinite, which is kinda the case most of the time for us mere mortals.

[+] never_inline|5 months ago|reply
I am the author of the posted flamebait. I agree.

I use IPython / JShell REPLs often when the code is not finished and I have to call a random function without entrypoint.

In fact its possible to jump to the graphical debugger from the Python REPL when running locally. PyCharm has this feature natively. In VSCode you can use a simple workaround like this: https://mahesh-hegde.github.io/posts/vscode-ipython-debuggin...

[+] smlavine|5 months ago|reply
It's not a silver bullet, but Visual Studio is leaps and bounds ahead of gdb et. al. for debugging C/C++ code. "Attach to process" and being able to just click a window is so easy when debugging a large Windows app.
[+] jesse__|5 months ago|reply
lol, agree to disagree here. While the interface to gdb is annoying, there are many gui frontend alternatives.

VS, on the other hand, gets worse with every release. It is intolerably slow and buggy at this point. It used to be a fantastic piece of software, and is now a fantastic pile of shit.

[+] dahart|5 months ago|reply
It really depends. The VS debugger is excellent, and it is usually easier I agree there. But gdb is more easily scriptable and more powerful in certain situations, so I reach for gdb sometimes for the harder bugs even though I use VS more often. Of course, they don’t really compete with each other that much, since VS is mainly Windows and gdb is mainly Linux. Of course I know you can do either debugger on either OS, but in practice the debugger choice is primarily OS based.
[+] o11c|5 months ago|reply
Under X11 that can be done with:

  gdb -p `xprop _NET_WM_PID | sed 's/.*= //'`
I think under Wayland it might not be possible since Wayland gratuitously breaks all nice things.
[+] jasonjmcghee|5 months ago|reply
Something I haven't seen discussed here that is another type of debugging that can be very useful is historical / offline debugging.

Kind of a hybrid of logging and standard debugging. "everything" is logged and you can go spelunk.

For example:

https://rr-project.org/

[+] smj-edison|5 months ago|reply
I've loved working with rr! Unfortunately the most recent project I've been contributing to breaks it (honestly it might just be Ubuntu, as it works on my arch install, but doesn't work when deployed where I need to test it).
[+] cluckindan|5 months ago|reply
Print debugging is historical / offline debugging, just ad-hoc instead of systemic.

The ”debug” package on npm is something in between, as it requires inserting debug statements but they are hidden from output unless an envvar like DEBUG=scope.subscope.*,otherscope is used.

[+] squirrellous|5 months ago|reply
IME console-based debuggers work great for single-threaded code without a lot of console output. They don't work that well otherwise. GUI-based debuggers can probably fix both of those issues. I just haven't really tried them as much.

pdb is great for python, though.

[+] sfpotter|5 months ago|reply
It isn't either/or. Good programmers know how to use both and know how to choose the appropriate tool for the job.
[+] inglor_cz|5 months ago|reply
I don't really get the hate that debuggers sometimes get from old hands. "Who needs screwdrivers if we always used knives?" - You can still use your knife, but screwdriver is a useful tool.

It seems to me that this is one of the many phenomena where people want to judge and belittle their peers over something completely trivial.

Personally, I get the appeal of printing out debugging information, especially if some bug is rare and happens in unpredictable times (such as when you are sleeping). But the amount of info you get this way is necessarily lower than what can be gleaned from a debugger.

[+] old_bayes|5 months ago|reply
It may sound obvious to folks who already use a debugger, but in my experience a decent chunk of people don't use them because they just don't know about them.

Spread the good word!

[+] makeitdouble|5 months ago|reply
Depending on the language or setup debuggers can be really crappy. I think people here would just flee away and go find a better fitting stack, but for more pragmatic workers they'll just learn to debug with the other tools (REPL, structured logging, APMs etc.)
[+] giveita|5 months ago|reply
I had a think about where I first learned to use a debugger. The combo of M$ making it easy for .NET and VB6 and working professionally and learning from others was key. Surprised it is less popular. Tests have made it less necessary perhaps BUT debugging a unit test is a killer move. You quickly get to the breakpoint and can tweak the scenario.
[+] nchmy|5 months ago|reply
yeah, tons dont know they exist. But there's also a lot of people - new and veteran - who are just allergic to them, for various reasons.

Setting up a debugger is the very first thing i do when i start working with a new language, and always use it to explore the code on new projects.

[+] gonzo41|5 months ago|reply
This also applies to testing. So much legacy code out there that's untested.
[+] hippo22|5 months ago|reply
Most languages let you print the stack, so you can easily see the stack using print debugging.

Anecdotally, dynamic expressions are impossibly slow in the cases I’ve tried them.

As the author mentions, there are also a number of cases where debuggers don’t work. Personally, I’m going to reach for the tool that always works vs. sometimes works.

[+] ksenzee|5 months ago|reply
> I’m going to reach for the tool that always works vs. sometimes works.

This is only logical if you're limited to one tool. Would you never buy a power tool because sometimes the power goes out and a hand tool is your only choice?

[+] nchmy|5 months ago|reply
but can you go back in the stack and inspect the variables and related functions there in print debugging?
[+] wheybags|5 months ago|reply
In my experience, conditional breakpoints are so unreliable, that I just dont bother trying to use them. When I need one I add code like

    if (condition) 
        print("");
Then add a breakpoint on the print. Calling print ensures the line with the breakpoint won't be optimised out (rarely matters as I'm normally debugging in... debug mode, but it's just a reflex at this point, and I need to put something in there)
[+] ziml77|5 months ago|reply
I love debuggers but yeah conditional breakpoints are pretty bad. I tend to need to conditionally break when the same code is being called repeatedly with different inputs where only one of them causes the problem. The conditional breakpoint slows things down so much that I'd probably turn to dust before it ever actually halted execution.

And I work in C#, so I have no clue why there's no option to inject the condition and a call to Debugger.Break() and then have the JIT recompile the function live. It would actually make conditional breakpoints usable!

[+] fluoridation|5 months ago|reply
In my experience, they're not unreliable, but they do massively slow down the application being debugged. I often do what you suggest, except with __debugbreak() (which compiles to int3).
[+] m463|5 months ago|reply
debuggers are hard to use outside of userland.

For really hairy bugs in programs that can't be stopped (kernel/drivers/realtime, etc) logging works.

And when it doesn't, like when you can't do I/O or switching of any kind, log non-blocking to a buffer that is dumped elsewhere.

also, related. It is harder than it should be to debug the linux kernel. Just getting a symboled stack trace is ridiculously hard.

[+] bluishgreen|5 months ago|reply
There are two kinds of bugs: the rare, tricky race conditions and the everyday “oh shucks” ones. The rare ones show up maybe 1% of the time—they demand a debugger, careful tracing, and detective work. The “oh shucks” kind where I am half sure what it is when I see the shape of the exception message from across the room - that is all the rest of the time. A simple print statement usually does the trick for this kind.

Leave us be. We know what we’re doing.

[+] MangoToupe|5 months ago|reply
I think the obvious benefit of a debugger is the ability to introspect when you have the misfortune of investigating the behavior of a binary rather than source code. In the vast, vast majority other instances, it is more desirable (to me) to encode evidence of investigation in the source itself. This has all the other benefits of source code—you can persist it, share it, let ai play with it, fork it, commit it to source control, use git bisect, etc.

There are a few other instances where the interaction offers notable benefits—bugs in the compiler, debugging assembly, access to registers, a half-completed runtime or standard library that occludes access to state so that you might print it. If you have the misfortune of working with C or C++, you have the benefit of breaking on memory access—but I tend to file this in the "half-completed runtime" category. There are also a few "heisenbugs" that may actually prevent the bug from occurring by using print itself; but I've only run into this I think twice. This is also possible with the debugger, but I've only run into that once. The only way out of that mess is careful reasoning, and i recommend printing the code out and using a pen.

I also strongly suspect that preference for print debugging vs interactive debuggers comes down to internal conception of the runtime and aesthetic preference. I abhor debuggers—especially thosr in IDEs. I think they tend to reimplement the runtime of a language a second time, except with more bugs and a less intuitive interface. But I have the wherewithal to realize that this is ultimately a preference.

[+] fsniper|5 months ago|reply
Print debugging is, checking patient's life signs, eye color, blood pressure, skin inflammation and so on. However using debuggers are like putting the patient through an MRI machine. It can provide you very advanced diagnostic information, but it's expensive, time consuming, requires specialized hardware and education. Alike medicinal doctors it's easier and logical to use the basics until absolutely necessary.
[+] jasonjmcghee|5 months ago|reply
Every engineer should understand how to use a debugger and a time profiler (one that gives a call tree). Knowing how to do memory profiling is incredibly valuable too.

So many problems can be solved with these.

And then there's some more specialized tooling depending on what you're doing that can be a huge help.

For SQL, the query planner and index hit/miss / full table scan.

And things like valgrind or similar for cache hit/miss.

Proper observability (spans/ traces) for APIs...

Knowing that the tools exist and how to use them can be the difference between software and great software.

Though system design / architecture is very important as well.

[+] lock1|5 months ago|reply
So, uh, everything is important, and every engineer must know everything then?

I mean, don't get me wrong, I do agree engineers should at least be aware of the existence of debuggers & profilers and what problems they can solve. It's just that not all the stuff you've said belongs in the "must know" category.

I don't think you'll need valgrind or query planning in web frontend tasks. Knowing them won't hurt though.

[+] ajross|5 months ago|reply
Meh. None of these sway me. I'm a die hard printf() debugger and always will be. But I do use debuggers regularly, for circumstances where printf() isn't quite up to the task. And there really are only two such categories (neither of which appear in the linked article!):

1. Code where the granularity of state change is smaller than a function call. Sometimes you actually have to step through things one instruction at a time, and I'm lucky enough to have such problems to solve. You can't debug your assembly with printf(), basically[1a].

2. State changes that can't be easily isolated. Sometimes you want to log when something change but can't for the life of you figure out when it's changing. Debuggers have watchpoints.

But... that's really it. If I'm not hitting one of those I'm not reaching for the debugger. Logging is just faster, because you type it in right at the code you're already reading.

[1a] Though there's a caveat: sometimes you need to write assembly and don't even have anything like a printk. Bootstrap code for a new device is a blast. You just try stuff like writing one byte to a UART address or setting one GPIO pin as the first instructions and hope it works, then use that one bit of output to pull the rest up.

[+] branko_d|5 months ago|reply
Assuming you meant C's printf, why would you subject yourself to the pain of recompilation every time you need to look at a different part of code? Isn't the debugger easier than adding printf and then recompiling?
[+] smlavine|5 months ago|reply
Do you use snippets or something to help speed this up? Manually typing `printf("longvarname=%s secondvarname=%d\n", longvarname, secondvarname);` adds up over a debugging session, compared to a graphical debugger setup with well-chosen breakpoints, watches etc.
[+] zem|5 months ago|reply
things I can do with print statements but not a debugger: trace the flow of several values across a program, seeing their values at several different times and execution points in a single screen.
[+] willtemperley|5 months ago|reply
This is refreshing. I get triggered by people writing "I don't use a debugger because I'm too smart to need one".

Some other things I'd add:

Some debuggers allow you to add actions. For example logging at the breakpoint is great if I can't modify the source, plus there's nothing to revert afterward. This just scratches the surface. Some debuggers allow you to see entire GPU workloads, view textures etc.

Debuggers are extremely useful for exploring and helping edit code. I can't be the only person that sprinkles breakpoints during development which helps me visualise code flow and quickly jump between source locations.

They're not just for debugging.

[+] dh2022|5 months ago|reply
Two of the benefits listed (call stack and catch exceptions at the source) are available in logging as well. A good logging framework lets you add the method name, source file and line number for the logging call-after a few debugging sessions you will construct the call stack quite easily. And C# at least lets you print the exception call stack from where it was thrown.

I agree that adhoc dynamic expression evaluation at run time is very useful and can only be done in a debugger.