RubyKaigi Takeout 2020

mruby-rr: Time Traveling Debugger For mruby Using rr

Debugging bugs that don't happen every time is painful. It needs both technique and luck. When dealing with these, a mistyped continue command is irreversible and will take us a whole afternoon just to reproduce the issue again.

mruby-rr comes to rescue! It's a Time Traveling Debugger for mruby that based on Mozilla's rr. mruby-rr supports record and replay of mruby program execution. We can record the tough bug using mruby-rr for just once. Afterwards we can playback the execution as many times as we want. mruby-rr can also do time traveling operations like reverse-next and evaluating expressions.

RubyKaigi Takeout 2020

00:00:01.760 Hello everyone, welcome to my RubyKaigi talk. Today, I'm going to talk about mruby-rr, a time-traveling debugger for mruby using rr.
00:00:06.799 So, about me: I'm Lin Yu Hsiang from Taiwan, and my Twitter handle is @johnlee_bc. I'm a senior solution architect at West Pharmaceutical.
00:00:12.240 Today's agenda includes: first, an introduction to the use case for mruby-rr; then, I'll discuss the internals of mruby and rr; afterwards, I'll show the benchmarks; and finally, I will outline future work.
00:00:30.960 There are some bugs that are hard to reproduce in our common mruby programs. One of the most notorious is the race condition. This occurs when multiple threads or fibers are competing for some variables or data, making it difficult to reproduce the timings correctly.
00:00:46.399 Another type of bug is user interface-related, which requires specific user inputs, such as clicking certain buttons or following specific sequences. The third type is related to randomness; if you're using random data, reproducing the exact same scenario becomes challenging.
00:01:02.159 Time-related bugs can also be problematic, especially when they involve time now. Once time passes, it's impossible to go back. For instance, if we have a variable that captures the current time before running some processes, we might expect consistent results over two executions. However, if the code is faulty, the results will differ.
00:01:17.680 Using the standard mruby debugger, let's see how we can trace this issue. First, we'll invoke mruby with mrdbt.rb and set a breakpoint at line 2 of t.rb. We've chosen line 2 because the variable won't have a value assigned until then. After continuing execution, we can evaluate the value of variable 'a' at that point.
00:02:19.520 However, once we reach line 4, we cannot revert back to line 1, as there's no time machine to help us. So, what do we do? We need to find that time machine! In the programming world, this concept does exist; we call it a time-traveling debugger. Time travel debugging allows you to step back in time through the source code to understand what happened during execution. Essentially, it has all the functions of a standard debugger—breakpoints, continues, evaluations—with the added feature of reverse execution.
00:04:08.560 Today, I'm introducing mruby-rr, an experimental mruby time-traveling debugger that utilizes rr as its backend. I created mruby-rr as a proof of concept to demonstrate that time-travel debugging is indeed possible in Ruby. Mruby-rr uses record and replay functionalities to navigate and inspect programs, and it operates on Intel CPUs and Linux.
00:05:30.960 To trace a program with mruby-rr, we first record the program using `record` and pass the required arguments through a script file. While running the program, we collect output and store trace data.
00:06:25.760 Next, we run the replay command. After starting mruby-rr, we can tell it to break at line 2 of t.rb, establishing a breakpoint there. Continuing execution, we’ll evaluate the value of 'a' at that line. When we reach line 4, we again break to inspect the modified value of 'a'. Notably, once we reach line 4, we can use reverse continue to go back, allowing us to evaluate the value of 'a' again at its previous state.
00:07:44.480 The significant advantage here is that in mruby-rr, we can reverse our execution flow. This means we can go back in time until we hit another breakpoint and evaluate 'a' at that point.
00:08:40.240 Another remarkable feature of mruby-rr is that you can restart from recorded trace data. You can instruct it to start over from the beginning, and you will see that it retains the exact data from the previous execution, enabling consistent bug reproduction even for elusive bugs.
00:09:24.560 Now, how can you get your hands on this time machine called mruby-rr? First, you need to install it, which depends on rr. We'll discuss that later. The first step is to add mruby-rr to your build configuration for mruby and enable debugging support by adding 'ENABLE_DEBUG'. You need to set up the debug hook correctly to facilitate the debugging process.
00:10:30.720 After adding the necessary components to your configuration, you'll build mruby from source. Cloning the mruby repository will provide you with the debugger binaries. The basic commands for mruby-rr include 'record' for executing the script file and 'replay' for running the recorded trace.
00:12:01.839 The primary features of mruby-rr include setting breakpoints, stepping through the code, continuing execution, and evaluating expressions. Additional commands for time traveling include reverse continue and reverse next. These functionalities give you the ability to manipulate the execution flow, making debugging significantly easier.
00:12:44.960 To illustrate how mruby-rr works, we start by launching the debugger, which then starts a mruby process. The rr backend collects and stores various execution information such as breakpoints. The rr library, designed by Mozilla, is fast and efficient, making it suitable for time-travel debugging.
00:14:29.440 It's essential to understand how rr captures execution data. It intercepts system calls, recording both the parameters and results. The timing of these calls is also critical, allowing the debugger to reconstruct the program's execution state accurately, even in the presence of bugs.
00:16:53.440 Despite the capabilities of rr, there are limitations. rr is specific to Intel CPUs and operates only on Linux due to its reliance on ptrace. Additionally, execution is confined to a single core, which may affect performance for multi-core environments.
00:18:25.760 Communication between mruby-rr and rr is managed through the gdbmi interface. The user interface allows for interactions through command inputs and outputs, simplifying the debugging process. Features like breakpoints and evaluations enhance the debugging experience, enabling developers to delve deep into code execution.
00:19:25.440 Current issues with mruby-rr include limitations in recreating alternative execution states and several features still pending implementation. Replay speeds can reduce significantly if breakpoints are enabled.
00:21:14.880 Future work for mruby-rr includes completing the full set of features, aiming for similar capabilities as the underlying rr. There's also a focus on improving performance during replay with breakpoints and potentially adapting the code fetch hook for broader use within the mruby ecosystem.
00:22:47.120 To summarize, mruby-rr makes time-traveling debugging easier, and it is now possible to achieve this functionality within mruby. Thank you for listening to my talk today. You can find mruby-rr on GitHub, and feel free to reach out to me on Twitter at @johnnybc.
00:24:05.440 Thank you, everyone. Goodbye!