gdb: add 'maintenance print record-instruction' command
authorBruno Larsen <blarsen@redhat.com>
Thu, 3 Nov 2022 09:17:36 +0000 (10:17 +0100)
committerBruno Larsen <blarsen@redhat.com>
Wed, 4 Jan 2023 10:21:57 +0000 (11:21 +0100)
While chasing some reverse debugging bugs, I found myself wondering what
was recorded by GDB to undo and redo a certain instruction. This commit
implements a simple way of printing that information.

If there isn't enough history to print the desired instruction (such as
when the user hasn't started recording yet or when they request 2
instructions back but only 1 was recorded), GDB warns the user like so:

(gdb) maint print record-instruction
Not enough recorded history

If there is enough, GDB prints the instruction like so:

(gdb) maint print record-instruction
4 bytes of memory at address 0x00007fffffffd5dc changed from: 01 00 00 00
Register eflags changed: [ IF ]
Register rip changed: (void (*)()) 0x401115 <main+15>

Approved-by: Eli Zaretskii <eliz@gnu.org>
Reviewed-by: Alexandra Hajkova <ahajkova@redhat.com>
Reviewed-by: Lancelot Six <lsix@lancelotsix.com>
Approved-by: Tom Tromey <tom@tromey.com>
gdb/NEWS
gdb/doc/gdb.texinfo
gdb/record-full.c
gdb/testsuite/gdb.reverse/maint-print-instruction.c [new file with mode: 0644]
gdb/testsuite/gdb.reverse/maint-print-instruction.exp [new file with mode: 0644]

index 41d815567ce0dac10c8fe31c22a5647b72ec83de..d8f4f396712b87b3fcd5714f710d689071857212 100644 (file)
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -9,6 +9,14 @@
   This support requires that GDB be built with Python scripting
   enabled.
 
+* New commands
+
+maintenance print record-instruction [ N ]
+  Print the recorded information for a given instruction.  If N is not given
+  prints how GDB would undo the last instruction executed.  If N is negative,
+  prints how GDB would undo the N-th previous instruction, and if N is
+  positive, it prints how GDB will redo the N-th following instruction.
+
 *** Changes in GDB 13
 
 * MI version 1 is deprecated, and will be removed in GDB 14.
index ea54f25b08e0b55c25407add042422fefe6f98d7..dd8f8bc757c66ec87e40382ae0fdeb5348e9f91c 100644 (file)
@@ -40542,6 +40542,14 @@ that symbol is described.  The type chain produced by this command is
 a recursive definition of the data type as stored in @value{GDBN}'s
 data structures, including its flags and contained types.
 
+@kindex maint print record-instruction
+@item maint print record-instruction
+@itemx maint print record-instruction @var{N}
+print how GDB recorded a given instruction.  If @var{n} is not positive
+number, it prints the values stored by the inferior before the @var{n}-th previous
+instruction was exectued.  If @var{n} is positive, print the values after the @var{n}-th
+following instruction is executed.  If @var{n} is not given, 0 is assumed.
+
 @kindex maint selftest
 @cindex self tests
 @item maint selftest @r{[}-verbose@r{]} @r{[}@var{filter}@r{]}
index f830bb1256d97029489821628ee5f52ca71649fd..e3cfa3f45d599e0a6ad0b380533ecb3b06e43f41 100644 (file)
@@ -39,6 +39,7 @@
 #include "gdbsupport/gdb_unlinker.h"
 #include "gdbsupport/byte-vector.h"
 #include "async-event.h"
+#include "valprint.h"
 
 #include <signal.h>
 
@@ -2764,6 +2765,90 @@ set_record_full_insn_max_num (const char *args, int from_tty,
     }
 }
 
+/* Implement the 'maintenance print record-instruction' command.  */
+
+static void
+maintenance_print_record_instruction (const char *args, int from_tty)
+{
+  struct record_full_entry *to_print = record_full_list;
+
+  if (args != nullptr)
+    {
+      int offset = value_as_long (parse_and_eval (args));
+      if (offset > 0)
+       {
+         /* Move forward OFFSET instructions.  We know we found the
+            end of an instruction when to_print->type is record_full_end.  */
+         while (to_print->next != nullptr && offset > 0)
+           {
+             to_print = to_print->next;
+             if (to_print->type == record_full_end)
+               offset--;
+           }
+         if (offset != 0)
+           error (_("Not enough recorded history"));
+       }
+      else
+       {
+         while (to_print->prev != nullptr && offset < 0)
+           {
+             to_print = to_print->prev;
+             if (to_print->type == record_full_end)
+               offset++;
+           }
+         if (offset != 0)
+           error (_("Not enough recorded history"));
+       }
+    }
+  gdb_assert (to_print != nullptr);
+
+  /* Go back to the start of the instruction.  */
+  while (to_print->prev != nullptr && to_print->prev->type != record_full_end)
+    to_print = to_print->prev;
+
+  /* if we're in the first record, there are no actual instructions
+     recorded.  Warn the user and leave.  */
+  if (to_print == &record_full_first)
+    error (_("Not enough recorded history"));
+
+  while (to_print->type != record_full_end)
+    {
+      switch (to_print->type)
+       {
+         case record_full_reg:
+           {
+             type *regtype = gdbarch_register_type (target_gdbarch (),
+                                                    to_print->u.reg.num);
+             value *val
+                 = value_from_contents (regtype,
+                                        record_full_get_loc (to_print));
+             gdb_printf ("Register %s changed: ",
+                         gdbarch_register_name (target_gdbarch (),
+                                                to_print->u.reg.num));
+             struct value_print_options opts;
+             get_user_print_options (&opts);
+             opts.raw = true;
+             value_print (val, gdb_stdout, &opts);
+             gdb_printf ("\n");
+             break;
+           }
+         case record_full_mem:
+           {
+             gdb_byte *b = record_full_get_loc (to_print);
+             gdb_printf ("%d bytes of memory at address %s changed from:",
+                         to_print->u.mem.len,
+                         print_core_address (target_gdbarch (),
+                                             to_print->u.mem.addr));
+             for (int i = 0; i < to_print->u.mem.len; i++)
+               gdb_printf (" %02x", b[i]);
+             gdb_printf ("\n");
+             break;
+           }
+       }
+      to_print = to_print->next;
+    }
+}
+
 void _initialize_record_full ();
 void
 _initialize_record_full ()
@@ -2868,4 +2953,14 @@ When ON, query if PREC cannot record memory change of next instruction."),
   c = add_alias_cmd ("memory-query", record_full_memory_query_cmds.show,
                     no_class, 1,&show_record_cmdlist);
   deprecate_cmd (c, "show record full memory-query");
+
+  add_cmd ("record-instruction", class_maintenance,
+          maintenance_print_record_instruction,
+          _("\
+Print a recorded instruction.\n\
+If no argument is provided, print the last instruction recorded.\n\
+If a negative argument is given, prints how the nth previous \
+instruction will be undone.\n\
+If a positive argument is given, prints \
+how the nth following instruction will be redone."), &maintenanceprintlist);
 }
diff --git a/gdb/testsuite/gdb.reverse/maint-print-instruction.c b/gdb/testsuite/gdb.reverse/maint-print-instruction.c
new file mode 100644 (file)
index 0000000..292af6d
--- /dev/null
@@ -0,0 +1,25 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2022-2023 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+int
+main ()
+{
+  int x = 0;
+  x ++;
+  x --;
+  return x;
+}
diff --git a/gdb/testsuite/gdb.reverse/maint-print-instruction.exp b/gdb/testsuite/gdb.reverse/maint-print-instruction.exp
new file mode 100644 (file)
index 0000000..41b2817
--- /dev/null
@@ -0,0 +1,75 @@
+#   Copyright 2022-2023 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the GDB testsuite.  It tests the functionality of
+# the maintenance print record-instruction command, but does not check the
+# syntax, only if the command finds or fails to find recorded history.
+# This is done by putting the inferior in mulpitle states with and without
+# history to be printed, then checking if GDB is able to print an
+# instruction or not.
+# To identify if GDB has printed an instruction, we can see if some
+# change is printed, since any instruction must have at least a change
+# to the PC.
+
+if ![supports_reverse] {
+    return
+}
+
+standard_testfile
+
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile] } {
+    return -1
+}
+
+proc test_print { has_history level test_name } {
+    gdb_test_multiple "maint print record-instruction $level" $test_name {
+       -re -wrap ".*Not enough recorded history.*" {
+           gdb_assert !$has_history $test_name
+       }
+
+    -re -wrap ".*changed.*" {
+           gdb_assert $has_history $test_name
+       }
+    }
+}
+
+if { ![runto_main] } {
+    return 0
+}
+
+#confirm that GDB doesn't go crazy if recording isn't enabled
+test_print false "" "print before starting to record"
+
+if ![supports_process_record] {
+    # No point in testing the rest if we can't record anything
+    return
+}
+
+gdb_test_no_output "record" "turn on process record"
+
+test_print false "" "print before any instruction"
+
+gdb_test "stepi 3" ".*" "collecting history"
+test_print true "" "print current after executing a bit"
+test_print true "-1" "print previous after executing a bit"
+test_print false "1" "print following after executing a bit"
+
+gdb_test "reverse-stepi" ".*" "moving back"
+test_print true "" "print current after reversing"
+test_print true "-1" "print previous after reversing"
+test_print true "1" "print following after reversing"
+
+test_print false "-10" "trying to print too far back"
+test_print false "10" "trying to print too far forward"