Top
Best
New

Posted by never_inline 7 days ago

Things you can do with a debugger but not with print debugging(mahesh-hegde.github.io)
254 points | 214 commentspage 3
t_mahmood 4 days ago|
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 4 days ago||
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 3 days ago||
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 4 days ago|||
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 3 days ago||
Yes, tried that, but it still failed.
mrugge 4 days ago||
claude code cli
yrand 4 days ago||
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 4 days ago|||
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 4 days ago|||
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.
dh2022 4 days ago||
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.

sriram_malhar 3 days ago||
"Thinking before debugging" ... advice from Rob Pike about Ken Thompson's approach.

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

untrimmed 4 days ago||
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_ 4 days ago||
https://lemire.me/blog/2016/06/21/i-do-not-use-a-debugger/

A bit of counterpoint here

willtemperley 4 days ago||
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 4 days ago|||
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 4 days ago||
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 4 days ago||
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 4 days ago||
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 4 days ago||
Looks like you invented telemetry
VikingCoder 4 days ago||
Many different ways to do it. Back in Visual Studio 6, this was the best way I could come up with!
iaalm 3 days ago||
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.
eviks 4 days ago||
> 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 3 days ago|
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 4 days ago||
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 4 days ago|
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 4 days ago||
> 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 4 days ago||
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.
scotty79 3 days ago|
Things you can do with print debugging but not with most debuggers.

Watch expressions with history of values. Especially aggregate history of multiple watch expressions.

More comments...