mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-12-21 04:42:53 +08:00
gdb: add 'maintenance print record-instruction' command
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>
This commit is contained in:
parent
502c7218da
commit
e24d337e21
8
gdb/NEWS
8
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.
|
||||
|
@ -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{]}
|
||||
|
@ -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);
|
||||
}
|
||||
|
25
gdb/testsuite/gdb.reverse/maint-print-instruction.c
Normal file
25
gdb/testsuite/gdb.reverse/maint-print-instruction.c
Normal file
@ -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;
|
||||
}
|
75
gdb/testsuite/gdb.reverse/maint-print-instruction.exp
Normal file
75
gdb/testsuite/gdb.reverse/maint-print-instruction.exp
Normal file
@ -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"
|
Loading…
Reference in New Issue
Block a user