Top
Best
New

Posted by never_inline 9/7/2025

Things you can do with a debugger but not with print debugging(mahesh-hegde.github.io)
257 points | 214 commentspage 3
t_mahmood 9/10/2025|
Maybe someone can give me idea, how can I debug this particular rust app, which is extremely annoying. It's a one of Rustdesk.

It won't run if I compile with debug info. I think it's due to a 3rd party proprietary library. So, to run the app I have to use release profile, with debug info stripped.

So, when I fire up gdb, I can't see any function information or anything, and it has so many system calls it's really difficult to follow through blindly.

So, what is the best way to handle this?

yrand 9/10/2025||
I'd investigate why it won't run with debug info in the first place. That feels like the core problem here, because it prevents you from using some debug tools.

Of course that may require digging down pretty low, which is difficult in itself.

Edit: also there's split-debuginfo which puts debug info in separate file. It could help if the reason you can't run it is the debug info itself. Which feels unlikely, but :shrug:.

t_mahmood 9/10/2025||
I tried to generate split-debuginfo, and it created another compiler issue in another library, haha, and I was too tired to dig more into it.

Curious if it's possible could it be because of protobuf implementation, which is used between UI and the server, and my error is occurring on the UI side.

So, after reading a bit, this is what I find

>Deterministic serialization is not canonical. The serializer can generate different output for many reasons, including but not limited to the following variations:

> The binary is built with different flags (eg. opt vs. debug).

My knowledge on this is pretty limited, so I could be wrong. But, this could be a reason. Maybe someone more knowledgeable on this matter can shade some lights. And I should've studied more on this before ... heh.

tomjakubowski 9/10/2025|||
You can add debug info to release builds. In Cargo.toml:

    [profile.release]
    debug = true
https://doc.rust-lang.org/cargo/reference/profiles.html#debu...
t_mahmood 9/10/2025||
Yes, tried that, but it still failed.
mrugge 9/10/2025||
claude code cli
yrand 9/10/2025||
Could you expand on what you meant? I'm curious.

Not related to OP, but debugging is often about finding where an invariant is broken, so it feels like using LLM to navigate a debugging loop may be useful as it's not a complicated but repetitive task. However in the morning I struggle to imagine how to do that.

mrugge 9/10/2025|||
I use claude code all day long to debug gnarly legacy code. Sometimes in languages I barely know. It works great especially as a second opinion or to get unstuck. It is very fun but can be addictive and exhausting.
mrugge 9/10/2025|||
More specifically I will stub out a simple unit test by hand to zoom in on where I think the issue is. It then turns into an exhilarating and wild ride from there.
karl_gluck 9/11/2025||
There was a time when I built games entirely using Visual Studio 6 Edit and Continue. These were the days when debuggers were reliable. Nowadays, I treat the debugger’s output like a best guess: it’s probably right about local variable values and the call stack, but it sometimes has nothing useful to say, and very occasionally is actively misleading.
jesse__ 9/11/2025|
The 'actively misleading' part is a real killer. I've gone down deep, dark rat holes a couple times because the debugger lied to me. I'm 100% in the "use the debugger" camp, but I sure wish they felt a bit more solid. I haven't used one for a long time that wasn't pretty buggy.
untrimmed 9/10/2025||
Honestly, I feel like the print vs. debugger debate isn't about the tool, it's about the mindset. Print statements feel like you're just trying to patch a leak, while the debugger is about understanding the plumbing. I’m starting to think relying only on print is a symptom of not truly wanting to understand the system you're working in.
someone_jain_ 9/10/2025||
https://lemire.me/blog/2016/06/21/i-do-not-use-a-debugger/

A bit of counterpoint here

willtemperley 9/10/2025||
I'm unconvinced with this article. Whilst obviously a smart guy, I don't think Lemire really works on the same problems most of us do. Looking at his posts and publications it's generally high peformance work on problems that are small in scope. I suspect most people actually work on far bigger projects than he does! Neither do I see from that arctile that he's actually used a modern debugger to its full potential. I rarely step through code in slow motion, but sometimes it's great to see program state. If there are thousands of files in a project I don't know well, I can put breakpoints to test hypotheses about my understanding.
kstrauser 9/10/2025|||
Interesting POV. I see it exactly the opposite: using a debugger most of the time feels like trying to see the current state of things without understanding what set of inputs led to it. Print debugging feels more like trying to understand the actual program logic that got us to this point, based on a few choice clues.

I’m not saying you’re wrong or I’m right, just that we have diametric opposite opinions on this.

ahartmetz 9/10/2025||
Call stacks and reading code give very different views of the codebase. The debugger tells you what's happening, reading tells you what can happen in many situations at once. You can generalize or focus, respectively, but their strengths and weaknesses remain.

Readable code, though, is written with the reading view in mind.

VikingCoder 9/10/2025||
I have counter-points to several of these... But this one is my favorite (This didn't go very far, but I loved the idea of it...):

I once wrote a program that opened up all of my code, and at every single code curly brace, it added a macro call, and a guid.

  void main() { DEBUGVIKINGCODER("72111b10c07b4a959510562a295cb2ac");
    ...
  }
I had to avoid doing that inside other macros, or inside Struct or Class definitions, enums, etc. But it wasn't hard, and it was a pretty sizeable codebase.

The DEBUGVIKINGCODER macro, or whatever I called it, was a no-op in release. But in Debug or testing builds, would do something like:

  DebugVikingCoder coder##__LINE__("72111b10c07b4a959510562a295cb2ac");
(Using the right macros to append __LINE__ to the variable, so there's no collisions.)

The constructor for DebugVikingCoder used a thread-local variable to write to a file (named after the thread id). It would write, essentially,

  Enter 72111b10c07b4a959510562a295cb2ac (epoch time)
The destructor, when that scope was exited, would write to the same file:

  Exit 72111b10c07b4a959510562a295cb2ac (epoch time)
So when I'd run the program, I'd get a directory full of files, one per thread.

Then I wrote another program that would read those all up, and would also read the code, and learn the File Name, Line Number of every GUID...

And, in Visual Studio, this tool program would print to the Output window, the File Name and Line Number, of every call and return.

And, in Visual Studio, you can step forward AND BACK in this Output window, and if you format it correctly, it'll open the file at that point, too.

So I could step forwards and backwards, through the code, to see who called where, etc. I could search in this Output window to jump to the function call I was looking for, and then walk backwards...

Then I added some code that would compare one run to another, and argued we could use that to figure out which of our automated tests formed a "basis set" to execute all of our code...

And to recommend which automated tests we should run, based on past analysis.

In addition to being able to time calls to functions, of course.

So then I added printing out some variables... And printing out lines in the middle of functions, when I wanted to time a section...

And if people respected the GUIDs, making a new one when they forked code, and leaving it alone if they moved code, we could have tracked how unit tests and other automation changed over time.

That got me really wishing that every new call scope really did have a GUID, in all the code we write... And I wished that it was essentially hidden from the developers, because who wants to see that? But, wow, it'd be nice if it was there.

I know there are debuggers that can go backwards and forwards in time... But I feel like being able to compare runs, over weeks and months, as the code is changing, is an under-appreciated objective.

mrheosuper 9/10/2025||
Looklike you invented "tracing", but since you added a hook at every "curly bracket", it would be much more detail than average tracing.

And slower of course, they are not free.

zephyrthenoble 9/10/2025||
Looks like you invented telemetry
VikingCoder 9/10/2025||
Many different ways to do it. Back in Visual Studio 6, this was the best way I could come up with!
sriram_malhar 9/11/2025||
"Thinking before debugging" ... advice from Rob Pike about Ken Thompson's approach.

https://www.informit.com/articles/article.aspx?p=1941206

eviks 9/10/2025||
> Some would’ve also heard about time travel debuggers (TTD) which let you step back in time. But most languages do not have a mature TTD implementation. So I am not writing about that.

Shame as that's likely the only option with significant universal UX advantage vs. sprinkling prints...

SAI_Peregrinus 9/10/2025|
Watchpoints (breakpoint that triggers when a given memory address is read/written/both) are the other huge UX advantage over prints. They're not quite as universal: you can almost always "print" by blinking an LED even if you don't have the luxury of a UART or other console, and some extremely low-end MCUs don't have any sort of watchpoints, but almost everything else does. A watchpoint that logs which source line wrote to the address in question & continues execution automatically is one of the easiest ways to debug memory corruption.
troupo 9/10/2025||
Don't show the discussion to John Carmack. He's baffled why people are so allergic to debuggers: https://youtu.be/tzr7hRXcwkw?si=beXGdoePRkbgfTtL
tayo42 9/10/2025|
I'm pretty sure in that interview at some point he realized becasue the debugger experience for developers using Linux sucks compared to Windows where he does most of his work.

Alot of programmers work in a Linux environment.

It seems like windows, ide and languages are all pretty nicely integrated together?

troupo 9/10/2025||
> It seems like windows, ide and languages are all pretty nicely integrated together?

Not only, and not really. After all, for all its warts Visual Studio is still a decent debugger for C/C++. IntelliJ has pretty good debuggers across all of their IDEs for almost all languages (including things like automatically downloading and/or decompiling sources when you step into external libraries).

Even browsers ship with built-in debuggers (and Chrome's is really good). I still see a lot of people (including my colleagues) often spend inordinate amounts of time console.log'ing when just stepping though the program would suffice.

I think it's the question of culture: people are so used to using subpar tools, they can't even imagine what a good one may look like. And these tools constantly evolve. Here's RAD Debugger by Ryan Fleury: https://threadreaderapp.com/thread/1920345634026238106.html

binary132 9/10/2025||
I would add to that list the important point that in a large codebase rebuilding after changing a line of code can take a very long time. In fact this is one of the most important reasons to get familiar with your debugger.
iaalm 9/11/2025||
While I enjoy using debuggers and find them valuable tools, the reality is that many environments have network or security constraints that make debugging significantly more challenging than simply using logs.
bagels 9/10/2025|
"you can’t use them when your application is running on remote environments"

This isn't always the case. Maybe it's really hard in a lot of cases, but it's always not impossible.

makeitdouble 9/10/2025|
I read it as dealing with applications you only get shell access to and can't forward ports.
More comments...