AdaCore Blog

Time travel debugging in GNAT Studio with GDB and RR

Time travel debugging in GNAT Studio with GDB and RR

by Ghjuvan Lacambre

Intro­duc­tion #

Tra­di­tion­al debug­ging tools usu­al­ly let you place break­points and fol­low the program’s flow. You can see the evo­lu­tion of the program’s state, but if you want to know how the pro­gram arrived to a cer­tain state, you have to add log­ging facil­i­ties to your pro­gram in order to be able to retrace state’s his­to­ry. The reach a cer­tain point in the pro­gram -> dis­cov­er that a vari­able doesn’t have the right val­ue -> add log­ging to fig­ure out what changed it -> launch the pro­gram again” cycle can be a very time con­sum­ing process which can be made even slow­er by non-deter­min­is­tic, hard to repro­duce bugs. Note that time trav­el debug­ging is usu­al­ly very os-depen­dent and that the two tools described here are linux-only.

Time trav­el with GDB #

One solu­tion to this prob­lem is time trav­el debug­ging. GDB imple­ments time trav­el debug­ging with its record com­mand. To use it in GNAT Stu­dio, one just needs to make sure that GDB is used rather than GDB mi in GNAT Studio’s pref­er­ences (Edit > Pref­er­ences > Debug­ger), as shown in the screen­shot below.

Then, after build­ing the project in debug mode, and start­ing it with the start’ com­mand, GDB’s record full’ com­mand will start the record­ing of the program’s exe­cu­tion. Break­points can be then placed in points of inter­est and, when going back is time is need­ed, the reverse-next’, reverse-step’ and reverse-finish’ com­mands can be used.

GDB’s record and replay func­tions are extreme­ly use­ful: they sup­port a wide vari­ety of archi­tec­tures and allow record­ing only parts of a program’s exe­cu­tion. They have down­sides though: some­times the buffer used for record­ing exe­cu­tion will get full and you’ll have to dis­card your record his­to­ry. You can only record exe­cu­tion from GDB, they make exe­cu­tion slow­er and do not work with pro­grams that use AVX/SSE instructions.

Time trav­el with RR #

If the archi­tec­ture you’re tar­get­ing is x86_​64, there is a solu­tion to the above prob­lems: RR. RR (which stands for Record & Replay) is a tool devel­opped by Mozil­la to help with debug­ging Fire­fox. RR records a whole program’s exe­cu­tion, from start to fin­ish and lets you replay it as many times as you want, even across dif­fer­ent GDB ses­sions. It does make pro­gram exe­cu­tion slow­er, but not as much as GDB’s record and replay feature.

In order to use RR from GNAT Stu­dio, first install RR (instal­la­tion instruc­tions are avail­able at the bot­tom of the RR web­site). Once this is done, make sure that the set­ting that con­trols the gran­u­lar­i­ty of perf event report­ing in your ker­nel is set to 1 (this can be done by cat /proc/sys/kernel/perf_event_paranoid' and set­ting it to 1 is as sim­ple as 'echo 1 | sudo tee /proc/sys/kernel/perf_event_paranoid). Just like with time trav­el with GDB, make sure that you are using gdb’ and not gdb mi’ in your GNAT Stu­dio debug­ging preferences.

Here’s a mini-tuto­r­i­al that shows how to use RR with GNAT Stu­dio. In order to fol­low along, cre­ate a new Sim­ple Ada Project” in GNAT Stu­dio. Set the con­tent of src/main.adb’ to the following:

with Ada.Numerics.Discrete_Random;

procedure Main is

   package Rand_Positive is new Ada.Numerics.Discrete_Random(Positive);
   Generator : Rand_Positive.Generator;
   
   Error : exception;
   
   Bug : Boolean := False;

   procedure Make_Bug is
   begin
      Bug := True;
   end Make_Bug;

   procedure Do_Bug is
   begin
      Bug := True;
   end Do_Bug;

begin
   Rand_Positive.Reset(Generator);

   for I in 1..10 loop
      if Rand_Positive.Random(Generator) < (Positive'Last / 100) then
         if Rand_Positive.Random(Generator) < (Positive'Last / 2) then
            Make_Bug;
         else
            Do_Bug;
         end if;
      end if;
   end loop;

   if Bug then
      raise Error;
   end if;

end Main;

This pro­gram has a 10% chance of crash­ing, so we will first need to record a crash­ing run. To do that, com­pile the project in debug mode (‘View > Scenario’ , set Build Mode’ to debug’ and then press ‘<F4>’). This will pro­duce a bina­ry in your project’s obj/debug’ direc­to­ry. Nav­i­gate to this direc­to­ry in a shell and try to record the program’s exe­cu­tion with RR with the fol­low­ing com­mand: rr record ./main’. You can then run rr replay’ to have RR start GDB for you (but don’t do this — we’ll use GNAT Stu­dio instead!).

You might have been lucky enough to get a crash on your first record­ing, but if that is not the case, record­ing exe­cu­tions until you do get a crash is easy: when they raise an excep­tion that isn’t caught, Ada pro­grams exit with a non-zero exit code. We can use this to have the shell record exe­cu­tions until we do get a fail­ure, like this: while rr record ./main ; do true ; done’.

Now that we have record­ed a crash, we could debug it in GDB from our shell (with rr replay’), or from GNAT Stu­dio. We’ll try GNAT Studio.

In your shell, run rr replay -s 12345 &’. RR will print a mes­sage telling you to run GDB, which you shouldn’t do (because we’ll use a GDB process start­ed by GNAT Stu­dio). Copy the path print­ed at the end of the GDB com­mand to your clip­board. In GNAT Stu­dio, start the debug­ger (‘Debug > Initialize > Main’). You should get a GDB win­dow at the bot­tom of your screen. In that win­dow, run the fol­low­ing commands:

set sysroot /
target extended-remote 127.0.0.1:12345
file PATH_YOU_COPIED_FROM_THE_SHELL

GNAT Studio’s debug­ger should now be con­nect­ed to RR. This means that all GDB for time trav­el (reverse-next, reverse-step, reverse-fin­ish…) are avail­able to us. Since we know the error hap­pens on line 36, we can just put a break­point there: either click on line 36 in the num­ber col­umn or type break main.adb:36’ in GNAT Studio’s GDB win­dow. We can exe­cute the pro­gram until we reach that break­point with continue’, by press­ing ‘<F8>’ or by click­ing on the play” but­ton at the top of GNAT Stu­dio. print Bug’ (or hov­er­ing the mouse over the vari­able) shows that the pro­gram reached this line because the vari­able named Bug’ is set to true. But what pro­ce­dure set Bugs val­ue to true? Right click­ing on Bug’ and select­ing Debug > Set watchpoint on Bug’ (or run­ning watch Bug’ in the GDB win­dow) and typ­ing reverse-continue’ in the GDB win­dow will tell us that it hap­pened either in Make_Bug’ or Do_Bug’.

This is a very sim­ple exam­ple, but it hope­ful­ly shows how use­ful RR can be and how sim­ple it is to use it from GNAT Studio!

Posted in #IDE    #gdb   

About Ghjuvan Lacambre

Ghjuvan Lacambre is a GNAT compiler engineer at AdaCore. Passionate about everything more or less related to programming, he has a particular interest in programming language and static analysis and spends most of his free time improving the tools he uses for programming.