rr – record and replay debugger for C/C++
rr-project.org
rr-project.org
I'm not familiar with Rust, but I'm almost sure it has a good C interoperability. If a certain piece of software is working well, what is the benefit of rewriting it in Rust?
Presumablly the idea here is to support Rust replay debugging, not just rewrite a C/C++ targetting replay debugger in Rust
Or is it more of a lowest common denominator experience, where e.g. all of them are constrained to common semantics of C/C++?
But then again you don't need to code everything in the same language either. You could write a rust parser in another language. Or a modular interface to dispatch knowledge of a programming language (does Microsoft's "language server" concept work this way?)
The neutral way of debugging is really debugging the raw machine code of a process. This requires OS integration for your low level manipulation primitives. To add language support, you then need to figure out how to define the semantics of your manipulations in terms of the low-level primitives.
If you have a rich runtime, you can add language-level debugging facilities that can operate at a higher level. However, this requires you to implement portions of a debugger in your runtime. Now you have to maintain a language, runtime, and debugger. It also means that if new debugger techniques are invented, such as time travel debugging, you do not get them for free since you embedded a debugger of your own design. So, like many similar things, it is a trade-off of specialization versus maintenance. The perennial question of use a library, or do it yourself.
The "working" C program has a high risk of undiscovered bugs relating to concurrency and memory safety. Rust lets you rule out a large swathe of them by construction. Rust's type system is also far more expressive, which in many cases enables cleaner domain modelling.
Should be nuanced though, because the working C program has a risk, but the the risk is a function of the size of the codebase, its age, and the number of audits it has undergone.
It is definitely easier to write bugs in C due to the additional freedom you have, but it is not necessarily a "high" risk for mature C libraries.
It is definitely not as advisable to just replace all C with Rust, but it is advisable to prefer memory safety in new projects.
Rust is "only" mentioned in the context of a C / C++ replacement, which tends to be a different area.
Well, I guess that's it...
Maybe rust will take the same path as Ada.
This sort of property is nice to have in huge codebases where you really start losing confidence in shipping changes that don't subtly break things. But of course a huge codebase is hard to rewrite in general...
Compared to C, yes, but not compared to C++.
C++ doesn't have a real Empty Type, and it thinks Units have non-zero size. In practical terms this makes it incredibly wasteful and in terms of a clear abstraction it encourages you to come up with a hack that's unclear but efficient.
`[[no_unique_address]]` is far from perfect, and inherited the same limitations that inheriting from an empty base class had (which was the trick you had to use prior to C++20). The "no more than 1 of the same type" limitation actually forced me to keep using CRTP instead of making use of "deducing this" after adopting c++23: a `static_assert` on object size failed, because an object grew larger once an inherited instance, plus an instance inherited by a field, no longer had different template types.
So, I agree that it is annoying and seems totally unnecessary, and has wasted my time; a heavy cost for a "feature" (empty objects having addresses) I have never wanted. But, I still make a lot of use of empty objects in C++ without increasing the size of any of my non-empty objects.
C++20 concepts are nice for writing generic code, but (from what I have seen, not experienced) Rust traits look nice, too.
You may need to sit down. An empty type has no values. Not one value, like the unit type which C++ makes a poor job of as you explain, but no values. None at all.
Because it has no values we will never be called upon to store one, we can't call functions which take one as a parameter, operations whose result is an empty type must diverge (ie control flow escapes, we never get to use the value because there isn't one). Code paths which are predicated on the value of an empty type are dead and can be pruned. And so on.
Rust uses this all over the place. C++ can't express it.
What is this empty type for? Could you provide an old man with a nice concrete example of this in action? I've used empty types in C++ to mark the end of recursive templates - which I used implement typelists before variadic templates were available.
But then you mention being unable to call functions which take an empty type as a parameter. At which point I cease to understand the purpose.
What is the type of the expression "return x" ? Rust says that's ! pronounced Never, an empty type. This expression never had a value, control flow diverges.
So this means we can just use simple type arithmetic to decide that a branch which returns contributed nothing to the type of the expression - it has no possible value. This wasn't a special case, it's just type arithmetic.
Ok, lets introduce another. Rust has a suite of conversion traits. From, Into, TryFrom and TryInto. They're chained, so if I implement From<Goose> for Doodad, everybody gets the three other implied conversions. But the Try conversions are potentially fallible, hence the word Try. So they have an error type. Generic Code handling the Error type of potentially failing conversion will thus be written, even if in some cases the conversion undertaken chained back to my From<Goose> code. But wait, that conversation can't fail! Sure enough the chained TryFrom and TryInto produced will have the error type Infallible, which is an Empty Type.
So the compiler can trim all the error handling code, it depends upon this value which we know can't exist, therefore it never executes.
People who want void to be a type in C++ (proponents of "regular void") mostly want it to be a unit type. If they're really ambitious they want it to have zero size. Generally a few committee meetings will knock that out of them.
One benefit is it's easier to hide malicious code thanks to Rust's complicated syntax.
[0] https://robert.ocallahan.org/2020/12/rr-remix-efficient-repl...
My limited understanding says a debugger needs: a list of symbols (.pdb files on windows, can't remember what they are on linux), understanding of syscalls and a few other similar things. I thought they don't care too much what generated the binaries they are debugging (obviously as long as it's native code).
Doesn't rr work with other languages like rust, zig, odin, nim, and similar ones? Obviously, I wouldn't expect it to work for python, js, c# and other languages with managed memory.
Tho, it feels wrong to expect a tool designed for native binaries to work well with python in this context. And that's ok. It feels lucky when it works as much as it does.
"How I debug Python code with a Time Travel Debugger"
Works surprisingly well.
When you replay a recording, rr first starts its custom gdbserver (which reads from the recording instead of from a live process) then starts a gdb process that connects to it.
https://github.com/python/devguide/issues/1283
https://morepypy.blogspot.com/2016/07/reverse-debugging-for-...
https://github.com/mesalock-linux/mesapy/blob/mesapy2.7/READ...
Things that don't work are drivers that update mapped addresses directly. An example of this is CUDA in order to replay one would need to model the driver interactions (and that's even before you get to UVM)
Another great thing is that RR records the process tree and so you can easily look at different processes spawned by your executable.
(Unlike rr it's not open source though - sorry! We have lots of programmes working on it full time and they insist on getting paid every month :)
*Edit
Found this, https://zig.news/david_vanderson/using-rr-to-quickly-debug-m...
Despite that, it would be very, very, very cool if some languages built rr directly into their tooling. Obviously you can always "just" use rr/gdb, but imagine if rr invocations were as easy to set up and do as pdb is in Python!
There is Undodb which works on Mac and maybe with multithreading (not sure about that), but unfortunately it costs about $50k.
There's still the fundamental limitation that rr won't help you with weak memory orderings.
> [...] just click on the incorrect value. With full program history Pernosco can immediately explain where this value came from. The value is tracked backwards through events such as memcpys or moves in and out of registers until we reach a point where the value "originated" from, and each step in this process is displayed in the "Dataflow" panel that opens automatically. There is no need to read or understand the code and think about what might have happened, we can simply ask the debugger what did happen.
I assume rr provides more features and flexibility. Anyway I want to mention that GDB itself can already reverse debug for some time now.
> runs out of resources
RAM? What kind of dev box runs out of RAM in 2024? I built a 64GB RAM dev box during COVID-19 crisis. I have never once come close to using all that RAM, even with a squillion Chrome tabs open.Still, thank you to share your first-hand experience. Did you ask the GDB Dev team for any feedback on the slow performance?
rr, on the other hand, intercepts all system calls and other sources of nondeterminism but regular CPU instructions execute normally with no overhead. The details about rr are here: https://arxiv.org/abs/1705.05937
gdb reverse debugging was introduced in 2009 [2].
You can see a fairly comprehensive history of time travel debugging here [3].
Not to say the built-in gdb reverse debugging was any good. It had (has?) like 1,000,000% overhead which is basically unusable. At least some implementations in the history that were introduced earlier only had ~1,000% overhead or less in general. Yes, a literal 1,000x overhead difference.
[1] https://robert.ocallahan.org/2014/03/introducing-rr.html
It worked, it helped me track down the bug, but it was painfully slow, I had to do things to limit the size of the input to make it possible to use at all (and thankfully was luckily able to still repro the problem after doing so).
i have been able to use gdb's replay functionality usefully because i had an input file which crashed the program within a fraction of a second after startup. this meant that i could navigate backward from "this variable is wrong" to "how did this variable get set to that wrong value?" in only several minutes of waiting on the computer
https://www.forrestthewoods.com/blog/windbg-time-travelling-...
10% is 100x cheaper than WinDbg and cheap enough to leave on all the time in production. That is a game-changer.
If you’re gonna throw around numbers like this you need to cite an actual tool not “if I remember correctly there exists a unicorn”.
https://news.ycombinator.com/item?id=31617600 (June 2022)
Super useful, especially considering I know barely anything about x86-64.
rr record /tmp/Debug/bin/llvm-mc a.s && rr replay -d cgdb
I've have success story with some bugs only reproducible with LTO. Without rr it would be a significant challenge.
It would be nice if Linux kernel could be debugged with rr. Does anyone have success with kernel under rr+qemu ? :)