Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
* [PATCH v3 0/7] refactor the internals of record-full
@ 2026-05-15 16:36 Guinevere Larsen
  2026-05-15 16:36 ` [PATCH v3 1/7] gdb/record: Refactor record history Guinevere Larsen
                   ` (9 more replies)
  0 siblings, 10 replies; 27+ messages in thread
From: Guinevere Larsen @ 2026-05-15 16:36 UTC (permalink / raw)
  To: gdb-patches; +Cc: Guinevere Larsen

This series introduces a refactor and modernization to the record-full
history, moving it form a manually managed doubly-linked list into an
std::deque, which can make better use of caching and might make
execution faster.

The main driving force behind this change is the plan to implement
support for multithreaded inferiors, since each instruction will need to
know the thread that executed it, and it'll be much easier if there is a
more consistent spot to find all this information.

Changes for v3: Made the former "cleanup" functions into actual
destructors. To do that and avoid double-frees, I made it so
record_full_entry (and the 2 classes under it) can't be copied, only
moved. Also implemented almost all the feedback from Christina on the
changes.

Guinevere Larsen (7):
  gdb/record: Refactor record history
  gdb/record: remove record_full_insn_num
  gdb/record: c++ify internal structures of record-full.c
  gdb/record: make record_full_history more c++-like
  gdb/record: extract the PC to record_full_instruction
  gdb/record: Define new version of the record-save section
  gdb/record: rename record_full_list to record_full_log

 gdb/NEWS               |    4 +
 gdb/aarch64-tdep.c     |    2 -
 gdb/amd64-linux-tdep.c |    3 -
 gdb/arm-tdep.c         |    2 -
 gdb/i386-linux-tdep.c  |    3 -
 gdb/i386-tdep.c        |    4 -
 gdb/loongarch-tdep.c   |    2 -
 gdb/moxie-tdep.c       |    2 -
 gdb/ppc-linux-tdep.c   |    3 -
 gdb/record-full.c      | 1724 +++++++++++++++++++---------------------
 gdb/record-full.h      |    1 -
 gdb/riscv-tdep.c       |    3 -
 gdb/rs6000-tdep.c      |    4 -
 gdb/s390-linux-tdep.c  |    3 -
 gdb/s390-tdep.c        |    2 -
 15 files changed, 827 insertions(+), 935 deletions(-)


base-commit: 62cc7210c87a08a9e10955d9a240cfe0c0b5ee13
-- 
2.54.0


^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v3 1/7] gdb/record: Refactor record history
  2026-05-15 16:36 [PATCH v3 0/7] refactor the internals of record-full Guinevere Larsen
@ 2026-05-15 16:36 ` Guinevere Larsen
  2026-05-26  8:33   ` Schimpe, Christina
  2026-05-15 16:36 ` [PATCH v3 2/7] gdb/record: remove record_full_insn_num Guinevere Larsen
                   ` (8 subsequent siblings)
  9 siblings, 1 reply; 27+ messages in thread
From: Guinevere Larsen @ 2026-05-15 16:36 UTC (permalink / raw)
  To: gdb-patches; +Cc: Guinevere Larsen, Thiago Jung Bauermann

This is the first step in a large refactor in how GDB keeps execution
history.  Rather than using a linked list where multiple entries can
describe a single instruction, the history will now be stored in an
std::deque, each instruction being one entry in the deque.

The choice was initially to use an std::vector, but it would become
unwieldy because it needs all the memory to be consecutive, which is
hard for 200 thousand entries. Deque was picked because it was a nice
midpoint between vector (maximum cache cohesion) and linked list
(maximum ease of finding space to store more).

Each instruction in memory will be now one record_full_instruction
entry, which for this commit just contains a vector of
record_full_entry for the effects of the instruction, and the data that
was stored in the record_full_end entry (that is, the instruction number
and the signal, if any).

This change introduced a minimal performance improvement (what's
important is that it isn't a degradation) and a  reduction in the total
memory footprint of roughly 20% if the entire history is used.

Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
---
 gdb/aarch64-tdep.c     |    2 -
 gdb/amd64-linux-tdep.c |    3 -
 gdb/arm-tdep.c         |    2 -
 gdb/i386-linux-tdep.c  |    3 -
 gdb/i386-tdep.c        |    4 -
 gdb/loongarch-tdep.c   |    2 -
 gdb/moxie-tdep.c       |    2 -
 gdb/ppc-linux-tdep.c   |    3 -
 gdb/record-full.c      | 1186 ++++++++++++++++------------------------
 gdb/record-full.h      |    1 -
 gdb/riscv-tdep.c       |    3 -
 gdb/rs6000-tdep.c      |    4 -
 gdb/s390-linux-tdep.c  |    3 -
 gdb/s390-tdep.c        |    2 -
 14 files changed, 484 insertions(+), 736 deletions(-)

diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c
index 4befaa2720d..a532992b1d8 100644
--- a/gdb/aarch64-tdep.c
+++ b/gdb/aarch64-tdep.c
@@ -6210,8 +6210,6 @@ aarch64_process_record (struct gdbarch *gdbarch, struct regcache *regcache,
 	       aarch64_record.aarch64_mems[rec_no].len))
 	    ret = -1;
 
-      if (record_full_arch_list_add_end ())
-	ret = -1;
     }
 
   deallocate_reg_mem (&aarch64_record);
diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c
index a5ac26654cf..9b23db72bbe 100644
--- a/gdb/amd64-linux-tdep.c
+++ b/gdb/amd64-linux-tdep.c
@@ -1591,9 +1591,6 @@ amd64_linux_record_signal (struct gdbarch *gdbarch,
 				     + AMD64_LINUX_frame_size))
     return -1;
 
-  if (record_full_arch_list_add_end ())
-    return -1;
-
   return 0;
 }
 
diff --git a/gdb/arm-tdep.c b/gdb/arm-tdep.c
index 08cce9dbad8..1ce0927f26b 100644
--- a/gdb/arm-tdep.c
+++ b/gdb/arm-tdep.c
@@ -14911,8 +14911,6 @@ arm_process_record (struct gdbarch *gdbarch, struct regcache *regcache,
 	    }
 	}
 
-      if (record_full_arch_list_add_end ())
-	ret = -1;
     }
 
 
diff --git a/gdb/i386-linux-tdep.c b/gdb/i386-linux-tdep.c
index 23aeccac9fc..4f33766fcdd 100644
--- a/gdb/i386-linux-tdep.c
+++ b/gdb/i386-linux-tdep.c
@@ -949,9 +949,6 @@ i386_linux_record_signal (struct gdbarch *gdbarch,
 				     I386_LINUX_xstate + I386_LINUX_frame_size))
     return -1;
 
-  if (record_full_arch_list_add_end ())
-    return -1;
-
   return 0;
 }
 \f
diff --git a/gdb/i386-tdep.c b/gdb/i386-tdep.c
index fa935b5fcdb..fee36b46a73 100644
--- a/gdb/i386-tdep.c
+++ b/gdb/i386-tdep.c
@@ -5201,8 +5201,6 @@ i386_record_vex (struct i386_record_s *ir, uint8_t vex_w, uint8_t vex_r,
     }
 
   record_full_arch_list_add_reg (ir->regcache, ir->regmap[X86_RECORD_REIP_REGNUM]);
-  if (record_full_arch_list_add_end ())
-    return -1;
 
   return 0;
 }
@@ -8359,8 +8357,6 @@ Do you want to stop the program?"),
 
   /* In the future, maybe still need to deal with need_dasm.  */
   I386_RECORD_FULL_ARCH_LIST_ADD_REG (X86_RECORD_REIP_REGNUM);
-  if (record_full_arch_list_add_end ())
-    return -1;
 
   return 0;
 
diff --git a/gdb/loongarch-tdep.c b/gdb/loongarch-tdep.c
index 225b1abb703..5ec6b2a48d0 100644
--- a/gdb/loongarch-tdep.c
+++ b/gdb/loongarch-tdep.c
@@ -2789,8 +2789,6 @@ loongarch_process_record (struct gdbarch *gdbarch, struct regcache *regcache,
 					 LOONGARCH_PC_REGNUM))
 	return -1;
 
-      if (record_full_arch_list_add_end ())
-	return -1;
     }
 
   return ret;
diff --git a/gdb/moxie-tdep.c b/gdb/moxie-tdep.c
index c8a04d21f59..2f8f0186488 100644
--- a/gdb/moxie-tdep.c
+++ b/gdb/moxie-tdep.c
@@ -1040,8 +1040,6 @@ moxie_process_record (struct gdbarch *gdbarch, struct regcache *regcache,
 
   if (record_full_arch_list_add_reg (regcache, MOXIE_PC_REGNUM))
     return -1;
-  if (record_full_arch_list_add_end ())
-    return -1;
   return 0;
 }
 
diff --git a/gdb/ppc-linux-tdep.c b/gdb/ppc-linux-tdep.c
index 19698eaacbe..e2f53551709 100644
--- a/gdb/ppc-linux-tdep.c
+++ b/gdb/ppc-linux-tdep.c
@@ -1565,9 +1565,6 @@ ppc_linux_record_signal (struct gdbarch *gdbarch, struct regcache *regcache,
   if (record_full_arch_list_add_mem (sp, SIGNAL_FRAMESIZE + sizeof_rt_sigframe))
     return -1;
 
-  if (record_full_arch_list_add_end ())
-    return -1;
-
   return 0;
 }
 
diff --git a/gdb/record-full.c b/gdb/record-full.c
index 69d2c100e57..02a57726a95 100644
--- a/gdb/record-full.c
+++ b/gdb/record-full.c
@@ -47,6 +47,9 @@
 #include "interps.h"
 #include "cli/cli-style.h"
 
+#include <vector>
+#include <optional>
+#include <deque>
 #include <signal.h>
 
 /* This module implements "target record-full", also known as "process
@@ -72,20 +75,17 @@
 #define DEFAULT_RECORD_FULL_INSN_MAX_NUM	200000
 
 #define RECORD_FULL_IS_REPLAY \
-  (record_full_list->next || ::execution_direction == EXEC_REVERSE)
+  ((record_full_next_insn != record_full_list.size ()) \
+    || ::execution_direction == EXEC_REVERSE)
 
 #define RECORD_FULL_FILE_MAGIC	netorder32(0x20091016)
 
 /* These are the core structs of the process record functionality.
 
    A record_full_entry is a record of the value change of a register
-   ("record_full_reg") or a part of memory ("record_full_mem").  And each
-   instruction must have a struct record_full_entry ("record_full_end")
-   that indicates that this is the last struct record_full_entry of this
-   instruction.
-
-   Each struct record_full_entry is linked to "record_full_list" by "prev"
-   and "next" pointers.  */
+   ("record_full_reg") or a part of memory ("record_full_mem").
+   These are saved on the record_full_instruction struct, which also
+   contains some extra information, such as delivered signals.  */
 
 struct record_full_mem_entry
 {
@@ -112,47 +112,14 @@ struct record_full_reg_entry
   } u;
 };
 
-struct record_full_end_entry
-{
-  enum gdb_signal sigval;
-  ULONGEST insn_num;
-};
-
 enum record_full_type
 {
-  record_full_end = 0,
   record_full_reg,
   record_full_mem
 };
 
-/* This is the data structure that makes up the execution log.
-
-   The execution log consists of a single linked list of entries
-   of type "struct record_full_entry".  It is doubly linked so that it
-   can be traversed in either direction.
-
-   The start of the list is anchored by a struct called
-   "record_full_first".  The pointer "record_full_list" either points
-   to the last entry that was added to the list (in record mode), or to
-   the next entry in the list that will be executed (in replay mode).
-
-   Each list element (struct record_full_entry), in addition to next
-   and prev pointers, consists of a union of three entry types: mem,
-   reg, and end.  A field called "type" determines which entry type is
-   represented by a given list element.
-
-   Each instruction that is added to the execution log is represented
-   by a variable number of list elements ('entries').  The instruction
-   will have one "reg" entry for each register that is changed by
-   executing the instruction (including the PC in every case).  It
-   will also have one "mem" entry for each memory change.  Finally,
-   each instruction will have an "end" entry that separates it from
-   the changes associated with the next instruction.  */
-
 struct record_full_entry
 {
-  struct record_full_entry *prev;
-  struct record_full_entry *next;
   enum record_full_type type;
   union
   {
@@ -160,11 +127,31 @@ struct record_full_entry
     struct record_full_reg_entry reg;
     /* mem */
     struct record_full_mem_entry mem;
-    /* end */
-    struct record_full_end_entry end;
   } u;
 };
 
+/* This is the main structure that comprises the execution log.
+   Each instruction is comprised of:
+   * The instruction number: How many instructions were recorded before
+     this one;
+   * sigval: Whether the inferior received a signal while the following
+     instruction was being recorded;
+   * effects: A list of record_full_entry structures, each of which
+     describing one effect that the instruction has on the inferior.
+
+   Note, the signal is stored in the previous instruction for historical
+   reasons.  This is how it was first implemented, and no one has gotten
+   around to changing it yet.  */
+
+struct record_full_instruction
+{
+  /* This might be different from the index if
+     we had to remove the first few instructions.  */
+  uint32_t insn_num;
+  std::optional<gdb_signal> sigval;
+  std::vector<record_full_entry> effects;
+};
+
 /* If true, query if PREC cannot record memory
    change of next instruction.  */
 bool record_full_memory_query = false;
@@ -181,27 +168,25 @@ static detached_regcache *record_full_core_regbuf = NULL;
 static std::vector<target_section> record_full_core_sections;
 static struct record_full_core_buf_entry *record_full_core_buf_list = NULL;
 
-/* The following variables are used for managing the linked list that
-   represents the execution log.
+/* The following variables are used for managing the history of executed
+   instructions from the inferior.
 
-   record_full_first is the anchor that holds down the beginning of
-   the list.
+   record_full_list contains all instructions that were fully executed and
+   saved to the log, so that we can replay the execution.
 
-   record_full_list serves two functions:
-     1) In record mode, it anchors the end of the list.
-     2) In replay mode, it traverses the list and points to
-	the next instruction that must be emulated.
+   record_full_next_insn always points to the next instruction that would
+   be executed if the inferior executes forward.  In the special case when
+   the inferior is not replaying, record_full_next_insn points past the
+   end of the history.
 
-   record_full_arch_list_head and record_full_arch_list_tail are used
-   to manage a separate list, which is used to build up the change
-   elements of the currently executing instruction during record mode.
-   When this instruction has been completely annotated in the "arch
-   list", it will be appended to the main execution log.  */
+   record_full_incomplete_instruction holds a partial instruction, while
+   the lower target is disassembling the instruction, or as partial xfers are
+   happening.  It is manipulated by the "arch list" functions for historical
+   reasons.  */
 
-static struct record_full_entry record_full_first;
-static struct record_full_entry *record_full_list = &record_full_first;
-static struct record_full_entry *record_full_arch_list_head = NULL;
-static struct record_full_entry *record_full_arch_list_tail = NULL;
+static std::deque<record_full_instruction> record_full_list;
+static record_full_instruction record_full_incomplete_instruction;
+static int record_full_next_insn;
 
 /* true ask user. false auto delete the last struct record_full_entry.  */
 static bool record_full_stop_at_limit = true;
@@ -361,6 +346,14 @@ record_full_target::kill ()
   record_kill (this);
 }
 
+static void
+record_full_reset_incomplete ()
+{
+  record_full_incomplete_instruction.effects.clear ();
+  record_full_incomplete_instruction.sigval.reset ();
+  record_full_incomplete_instruction.insn_num = 0;
+}
+
 /* See record-full.h.  */
 
 int
@@ -390,156 +383,124 @@ static struct cmd_list_element *show_record_full_cmdlist;
 /* Command list for "record full".  */
 static struct cmd_list_element *record_full_cmdlist;
 
-static void record_full_goto_insn (struct record_full_entry *entry,
+static void record_full_goto_insn (size_t target_insn,
 				   enum exec_direction_kind dir);
 
-/* Alloc and free functions for record_full_reg, record_full_mem, and
-   record_full_end entries.  */
+/* Initialization and cleanup functions for record_full_reg and
+   record_full_mem entries.  */
 
-/* Alloc a record_full_reg record entry.  */
+/* Init a record_full_reg record entry.  */
 
-static inline struct record_full_entry *
-record_full_reg_alloc (struct regcache *regcache, int regnum)
+static inline record_full_entry
+record_full_reg_init (struct regcache *regcache, int regnum)
 {
-  struct record_full_entry *rec;
+  record_full_entry rec;
   struct gdbarch *gdbarch = regcache->arch ();
 
-  rec = XCNEW (struct record_full_entry);
-  rec->type = record_full_reg;
-  rec->u.reg.num = regnum;
-  rec->u.reg.len = register_size (gdbarch, regnum);
-  if (rec->u.reg.len > sizeof (rec->u.reg.u.buf))
-    rec->u.reg.u.ptr = (gdb_byte *) xmalloc (rec->u.reg.len);
+  rec.type = record_full_reg;
+  rec.u.reg.num = regnum;
+  rec.u.reg.len = register_size (gdbarch, regnum);
+  if (rec.u.reg.len > sizeof (rec.u.reg.u.buf))
+    rec.u.reg.u.ptr = (gdb_byte *) xmalloc (rec.u.reg.len);
 
   return rec;
 }
 
-/* Free a record_full_reg record entry.  */
+/* Cleanup a record_full_reg record entry.  */
 
 static inline void
-record_full_reg_release (struct record_full_entry *rec)
+record_full_reg_cleanup (record_full_entry rec)
 {
-  gdb_assert (rec->type == record_full_reg);
-  if (rec->u.reg.len > sizeof (rec->u.reg.u.buf))
-    xfree (rec->u.reg.u.ptr);
-  xfree (rec);
+  gdb_assert (rec.type == record_full_reg);
+  if (rec.u.reg.len > sizeof (rec.u.reg.u.buf))
+    xfree (rec.u.reg.u.ptr);
 }
 
-/* Alloc a record_full_mem record entry.  */
+/* Init a record_full_mem record entry.  */
 
-static inline struct record_full_entry *
-record_full_mem_alloc (CORE_ADDR addr, int len)
+static inline record_full_entry
+record_full_mem_init (CORE_ADDR addr, int len)
 {
-  struct record_full_entry *rec;
+  record_full_entry rec;
 
-  rec = XCNEW (struct record_full_entry);
-  rec->type = record_full_mem;
-  rec->u.mem.addr = addr;
-  rec->u.mem.len = len;
-  if (rec->u.mem.len > sizeof (rec->u.mem.u.buf))
-    rec->u.mem.u.ptr = (gdb_byte *) xmalloc (len);
+  rec.type = record_full_mem;
+  rec.u.mem.addr = addr;
+  rec.u.mem.len = len;
+  if (rec.u.mem.len > sizeof (rec.u.mem.u.buf))
+    rec.u.mem.u.ptr = (gdb_byte *) xmalloc (len);
+  rec.u.mem.mem_entry_not_accessible = 0;
 
   return rec;
 }
 
-/* Free a record_full_mem record entry.  */
+/* Cleanup a record_full_mem record entry.  */
 
 static inline void
-record_full_mem_release (struct record_full_entry *rec)
+record_full_mem_cleanup (record_full_entry rec)
 {
-  gdb_assert (rec->type == record_full_mem);
-  if (rec->u.mem.len > sizeof (rec->u.mem.u.buf))
-    xfree (rec->u.mem.u.ptr);
-  xfree (rec);
+  gdb_assert (rec.type == record_full_mem);
+  if (rec.u.mem.len > sizeof (rec.u.mem.u.buf))
+    xfree (rec.u.mem.u.ptr);
 }
 
-/* Alloc a record_full_end record entry.  */
-
-static inline struct record_full_entry *
-record_full_end_alloc (void)
-{
-  struct record_full_entry *rec;
-
-  rec = XCNEW (struct record_full_entry);
-  rec->type = record_full_end;
-
-  return rec;
-}
-
-/* Free a record_full_end record entry.  */
+/* Free one record entry, any type.  */
 
 static inline void
-record_full_end_release (struct record_full_entry *rec)
+record_full_entry_cleanup (record_full_entry rec)
 {
-  xfree (rec);
-}
-
-/* Free one record entry, any type.
-   Return entry->type, in case caller wants to know.  */
 
-static inline enum record_full_type
-record_full_entry_release (struct record_full_entry *rec)
-{
-  enum record_full_type type = rec->type;
-
-  switch (type) {
+  switch (rec.type) {
   case record_full_reg:
-    record_full_reg_release (rec);
+    record_full_reg_cleanup (rec);
     break;
   case record_full_mem:
-    record_full_mem_release (rec);
-    break;
-  case record_full_end:
-    record_full_end_release (rec);
+    record_full_mem_cleanup (rec);
     break;
   }
-  return type;
 }
 
-/* Free all record entries in list pointed to by REC.  */
-
 static void
-record_full_list_release (struct record_full_entry *rec)
+record_full_reset_history ()
 {
-  if (!rec)
-    return;
-
-  while (rec->next)
-    rec = rec->next;
+  record_full_insn_num = 0;
+  record_full_insn_count = 0;
+  record_full_next_insn = 0;
 
-  while (rec->prev)
+  for (auto &insn : record_full_list)
     {
-      rec = rec->prev;
-      record_full_entry_release (rec->next);
+      for (auto &entry : insn.effects)
+	record_full_entry_cleanup (entry);
     }
 
-  if (rec == &record_full_first)
+  record_full_list.clear ();
+}
+
+static void
+record_full_list_release_following (int index)
+{
+  for (int i = record_full_list.size () - 1; i > index; i--)
     {
-      record_full_insn_num = 0;
-      record_full_first.next = NULL;
+      for (auto &entry : record_full_list[i].effects)
+	record_full_entry_cleanup (entry);
+      record_full_list.pop_back ();
     }
-  else
-    record_full_entry_release (rec);
+  /* Set the next instruction to be past the end of the log so we
+     start recording if the user moves forward again.  */
+  record_full_next_insn = index;
 }
 
-/* Free all record entries forward of the given list position.  */
+/* Save the incomplete instruction in the log.  */
 
 static void
-record_full_list_release_following (struct record_full_entry *rec)
+record_full_save_instruction ()
 {
-  struct record_full_entry *tmp = rec->next;
+  ++record_full_insn_count;
+  record_full_incomplete_instruction.insn_num = record_full_insn_count;
+  record_full_incomplete_instruction.effects.shrink_to_fit ();
+  record_full_list.push_back (std::move (record_full_incomplete_instruction));
+  record_full_next_insn++;
 
-  rec->next = NULL;
-  while (tmp)
-    {
-      rec = tmp->next;
-      if (record_full_entry_release (tmp) == record_full_end)
-	{
-	  record_full_insn_num--;
-	  record_full_insn_count--;
-	}
-      tmp = rec;
-    }
+  record_full_reset_incomplete ();
 }
 
 /* Delete the first instruction from the beginning of the log, to make
@@ -550,52 +511,22 @@ record_full_list_release_following (struct record_full_entry *rec)
 static void
 record_full_list_release_first (void)
 {
-  struct record_full_entry *tmp;
-
-  if (!record_full_first.next)
+  if (record_full_list.empty ())
     return;
 
-  /* Loop until a record_full_end.  */
-  while (1)
-    {
-      /* Cut record_full_first.next out of the linked list.  */
-      tmp = record_full_first.next;
-      record_full_first.next = tmp->next;
-      tmp->next->prev = &record_full_first;
-
-      /* tmp is now isolated, and can be deleted.  */
-      if (record_full_entry_release (tmp) == record_full_end)
-	break;	/* End loop at first record_full_end.  */
+  for (auto &entry : record_full_list[0].effects)
+    record_full_entry_cleanup (entry);
 
-      if (!record_full_first.next)
-	{
-	  gdb_assert (record_full_insn_num == 1);
-	  break;	/* End loop when list is empty.  */
-	}
-    }
+  record_full_list.pop_front ();
+  --record_full_next_insn;
 }
 
 /* Add a struct record_full_entry to record_full_arch_list.  */
 
 static void
-record_full_arch_list_add (struct record_full_entry *rec)
+record_full_arch_list_add (record_full_entry &rec)
 {
-  if (record_debug > 1)
-    gdb_printf (gdb_stdlog,
-		"Process record: record_full_arch_list_add %s.\n",
-		host_address_to_string (rec));
-
-  if (record_full_arch_list_tail)
-    {
-      record_full_arch_list_tail->next = rec;
-      rec->prev = record_full_arch_list_tail;
-      record_full_arch_list_tail = rec;
-    }
-  else
-    {
-      record_full_arch_list_head = rec;
-      record_full_arch_list_tail = rec;
-    }
+  record_full_incomplete_instruction.effects.push_back (rec);
 }
 
 /* Return the value storage location of a record entry.  */
@@ -613,7 +544,6 @@ record_full_get_loc (struct record_full_entry *rec)
       return rec->u.reg.u.ptr;
     else
       return rec->u.reg.u.buf;
-  case record_full_end:
   default:
     gdb_assert_not_reached ("unexpected record_full_entry type");
     return NULL;
@@ -625,7 +555,7 @@ record_full_get_loc (struct record_full_entry *rec)
 int
 record_full_arch_list_add_reg (struct regcache *regcache, int regnum)
 {
-  struct record_full_entry *rec;
+  record_full_entry rec;
 
   if (record_debug > 1)
     gdb_printf (gdb_stdlog,
@@ -633,9 +563,9 @@ record_full_arch_list_add_reg (struct regcache *regcache, int regnum)
 		"record list.\n",
 		regnum);
 
-  rec = record_full_reg_alloc (regcache, regnum);
+  rec = record_full_reg_init (regcache, regnum);
 
-  regcache->cooked_read (regnum, record_full_get_loc (rec));
+  regcache->cooked_read (regnum, record_full_get_loc (&rec));
 
   record_full_arch_list_add (rec);
 
@@ -648,7 +578,7 @@ record_full_arch_list_add_reg (struct regcache *regcache, int regnum)
 int
 record_full_arch_list_add_mem (CORE_ADDR addr, int len)
 {
-  struct record_full_entry *rec;
+  record_full_entry rec;
 
   if (record_debug > 1)
     gdb_printf (gdb_stdlog,
@@ -659,12 +589,12 @@ record_full_arch_list_add_mem (CORE_ADDR addr, int len)
   if (!addr)	/* FIXME: Why?  Some arch must permit it...  */
     return 0;
 
-  rec = record_full_mem_alloc (addr, len);
+  rec = record_full_mem_init (addr, len);
 
   if (record_read_memory (current_inferior ()->arch (), addr,
-			  record_full_get_loc (rec), len))
+			  record_full_get_loc (&rec), len))
     {
-      record_full_mem_release (rec);
+      record_full_mem_cleanup (rec);
       return -1;
     }
 
@@ -673,27 +603,6 @@ record_full_arch_list_add_mem (CORE_ADDR addr, int len)
   return 0;
 }
 
-/* Add a record_full_end type struct record_full_entry to
-   record_full_arch_list.  */
-
-int
-record_full_arch_list_add_end (void)
-{
-  struct record_full_entry *rec;
-
-  if (record_debug > 1)
-    gdb_printf (gdb_stdlog,
-		"Process record: add end to arch list.\n");
-
-  rec = record_full_end_alloc ();
-  rec->u.end.sigval = GDB_SIGNAL_0;
-  rec->u.end.insn_num = ++record_full_insn_count;
-
-  record_full_arch_list_add (rec);
-
-  return 0;
-}
-
 static void
 record_full_check_insn_num (void)
 {
@@ -725,8 +634,7 @@ record_full_message (struct regcache *regcache, enum gdb_signal signal)
 
   try
     {
-      record_full_arch_list_head = NULL;
-      record_full_arch_list_tail = NULL;
+      record_full_reset_incomplete ();
 
       /* Check record_full_insn_num.  */
       record_full_check_insn_num ();
@@ -746,20 +654,16 @@ record_full_message (struct regcache *regcache, enum gdb_signal signal)
 	 the user says something different, like "deliver this signal"
 	 during the replay mode).
 
-	 User should understand that nothing he does during the replay
-	 mode will change the behavior of the child.  If he tries,
+	 User should understand that nothing they do during the replay
+	 mode will change the behavior of the child.  If they try,
 	 then that is a user error.
 
 	 But we should still deliver the signal to gdb during the replay,
 	 if we delivered it during the recording.  Therefore we should
 	 record the signal during record_full_wait, not
 	 record_full_resume.  */
-      if (record_full_list != &record_full_first)  /* FIXME better way
-						      to check */
-	{
-	  gdb_assert (record_full_list->type == record_full_end);
-	  record_full_list->u.end.sigval = signal;
-	}
+      if (signal != GDB_SIGNAL_0 && !record_full_list.empty ())
+	record_full_list[record_full_next_insn - 1].sigval = signal;
 
       if (signal == GDB_SIGNAL_0
 	  || !gdbarch_process_record_signal_p (gdbarch))
@@ -778,13 +682,11 @@ record_full_message (struct regcache *regcache, enum gdb_signal signal)
     }
   catch (const gdb_exception &ex)
     {
-      record_full_list_release (record_full_arch_list_tail);
+      record_full_reset_incomplete ();
       throw;
     }
 
-  record_full_list->next = record_full_arch_list_head;
-  record_full_arch_list_head->prev = record_full_list;
-  record_full_list = record_full_arch_list_tail;
+  record_full_save_instruction ();
 
   if (record_full_insn_num == record_full_insn_max_num)
     record_full_list_release_first ();
@@ -829,9 +731,9 @@ static enum target_stop_reason record_full_stop_reason
    entries and memory entries, followed by an 'end' entry.  */
 
 static inline void
-record_full_exec_insn (struct regcache *regcache,
-		       struct gdbarch *gdbarch,
-		       struct record_full_entry *entry)
+record_full_exec_entry (regcache *regcache,
+			gdbarch *gdbarch,
+			record_full_entry *entry)
 {
   switch (entry->type)
     {
@@ -909,6 +811,15 @@ record_full_exec_insn (struct regcache *regcache,
     }
 }
 
+static inline void
+record_full_exec_insn (regcache *regcache,
+		       gdbarch *gdbarch,
+		       record_full_instruction &insn)
+{
+  for (auto &entry : insn.effects)
+    record_full_exec_entry (regcache, gdbarch, &entry);
+}
+
 static void record_full_restore (struct bfd &cbfd);
 
 /* Asynchronous signal handle registered as event loop source for when
@@ -983,10 +894,7 @@ record_full_open (const char *args, int from_tty)
   record_preopen ();
 
   /* Reset */
-  record_full_insn_num = 0;
-  record_full_insn_count = 0;
-  record_full_list = &record_full_first;
-  record_full_list->next = NULL;
+  record_full_reset_history ();
 
   bfd *cbfd = get_inferior_core_bfd (current_inferior ());
   if (cbfd != nullptr)
@@ -1014,7 +922,7 @@ record_full_base_target::close ()
   if (record_debug)
     gdb_printf (gdb_stdlog, "Process record: record_full_close\n");
 
-  record_full_list_release (record_full_list);
+  record_full_reset_history ();
 
   /* Release record_full_core_regbuf.  */
   if (record_full_core_regbuf)
@@ -1313,7 +1221,6 @@ record_full_wait_1 (struct target_ops *ops,
       struct gdbarch *gdbarch = regcache->arch ();
       const address_space *aspace = current_inferior ()->aspace.get ();
       int continue_flag = 1;
-      int first_record_full_end = 1;
 
       try
 	{
@@ -1322,7 +1229,6 @@ record_full_wait_1 (struct target_ops *ops,
 	  record_full_stop_reason = TARGET_STOPPED_BY_NO_REASON;
 	  status->set_stopped (GDB_SIGNAL_0);
 
-	  /* Check breakpoint when forward execute.  */
 	  if (execution_direction == EXEC_FORWARD)
 	    {
 	      tmp_pc = regcache_read_pc (regcache);
@@ -1330,8 +1236,7 @@ record_full_wait_1 (struct target_ops *ops,
 						      &record_full_stop_reason))
 		{
 		  if (record_debug)
-		    gdb_printf (gdb_stdlog,
-				"Process record: break at %s.\n",
+		    gdb_printf (gdb_stdlog, "Process record: break at %s.\n",
 				paddress (gdbarch, tmp_pc));
 		  goto replay_out;
 		}
@@ -1344,10 +1249,10 @@ record_full_wait_1 (struct target_ops *ops,
 	     the signal.  */
 	  target_terminal::ours ();
 
-	  /* In EXEC_FORWARD mode, record_full_list points to the tail of prev
-	     instruction.  */
-	  if (execution_direction == EXEC_FORWARD && record_full_list->next)
-	    record_full_list = record_full_list->next;
+	  /* In EXEC_FORWARD mode, record_full_next_insn is the next
+	     instruction to be executed.  */
+	  if (execution_direction == EXEC_REVERSE)
+	    record_full_next_insn--;
 
 	  /* Loop over the record_full_list, looking for the next place to
 	     stop.  */
@@ -1355,108 +1260,92 @@ record_full_wait_1 (struct target_ops *ops,
 	    {
 	      /* Check for beginning and end of log.  */
 	      if (execution_direction == EXEC_REVERSE
-		  && record_full_list == &record_full_first)
+		  && record_full_next_insn < 0)
 		{
 		  /* Hit beginning of record log in reverse.  */
 		  status->set_no_history ();
+		  record_full_next_insn = 0;
 		  break;
 		}
 	      if (execution_direction != EXEC_REVERSE
-		  && !record_full_list->next)
+		  && record_full_next_insn == record_full_list.size ())
 		{
 		  /* Hit end of record log going forward.  */
 		  status->set_no_history ();
 		  break;
 		}
 
-	      record_full_exec_insn (regcache, gdbarch, record_full_list);
+	      record_full_exec_insn
+		(regcache, gdbarch,
+		 record_full_list[record_full_next_insn]);
 
-	      if (record_full_list->type == record_full_end)
+	      /* step */
+	      if (record_full_resume_step)
 		{
 		  if (record_debug > 1)
-		    gdb_printf
-		      (gdb_stdlog,
-		       "Process record: record_full_end %s to "
-		       "inferior.\n",
-		       host_address_to_string (record_full_list));
-
-		  if (first_record_full_end
-		      && execution_direction == EXEC_REVERSE)
-		    {
-		      /* When reverse execute, the first
-			 record_full_end is the part of current
-			 instruction.  */
-		      first_record_full_end = 0;
-		    }
-		  else
-		    {
-		      /* In EXEC_REVERSE mode, this is the
-			 record_full_end of prev instruction.  In
-			 EXEC_FORWARD mode, this is the
-			 record_full_end of current instruction.  */
-		      /* step */
-		      if (record_full_resume_step)
-			{
-			  if (record_debug > 1)
-			    gdb_printf (gdb_stdlog,
-					"Process record: step.\n");
-			  continue_flag = 0;
-			}
-
-		      /* check breakpoint */
-		      tmp_pc = regcache_read_pc (regcache);
-		      if (record_check_stopped_by_breakpoint
-			  (aspace, tmp_pc, &record_full_stop_reason))
-			{
-			  if (record_debug)
-			    gdb_printf (gdb_stdlog,
-					"Process record: break "
-					"at %s.\n",
-					paddress (gdbarch, tmp_pc));
+		    gdb_printf (gdb_stdlog,
+				"Process record: step.\n");
+		  continue_flag = 0;
+		}
 
-			  continue_flag = 0;
-			}
+	      /* check breakpoint */
+	      tmp_pc = regcache_read_pc (regcache);
+	      if (record_check_stopped_by_breakpoint
+		  (aspace, tmp_pc, &record_full_stop_reason))
+		{
+		  if (record_debug)
+		    gdb_printf (gdb_stdlog,
+				"Process record: break "
+				"at %s.\n",
+				paddress (gdbarch, tmp_pc));
 
-		      if (record_full_stop_reason
-			  == TARGET_STOPPED_BY_WATCHPOINT)
-			{
-			  if (record_debug)
-			    gdb_printf (gdb_stdlog,
-					"Process record: hit hw "
-					"watchpoint.\n");
-			  continue_flag = 0;
-			}
-		      /* Check target signal */
-		      if (record_full_list->u.end.sigval != GDB_SIGNAL_0)
-			/* FIXME: better way to check */
-			continue_flag = 0;
-		    }
+		  continue_flag = 0;
 		}
 
-	      if (continue_flag)
+	      if (record_full_stop_reason
+		  == TARGET_STOPPED_BY_WATCHPOINT)
 		{
-		  if (execution_direction == EXEC_REVERSE)
-		    {
-		      if (record_full_list->prev)
-			record_full_list = record_full_list->prev;
-		    }
-		  else
-		    {
-		      if (record_full_list->next)
-			record_full_list = record_full_list->next;
-		    }
+		  if (record_debug)
+		    gdb_printf (gdb_stdlog,
+				"Process record: hit hw "
+				"watchpoint.\n");
+		  continue_flag = 0;
 		}
+	      if (record_full_list[record_full_next_insn].sigval.has_value ())
+		continue_flag = 0;
+
+	      if (execution_direction == EXEC_REVERSE)
+		record_full_next_insn--;
+	      else
+		record_full_next_insn++;
 	    }
 	  while (continue_flag);
 
+	  if (record_full_next_insn < 0)
+	    {
+	      gdb_assert (execution_direction == EXEC_REVERSE);
+	      record_full_next_insn = 0;
+	    }
+	  else if (record_full_next_insn > record_full_list.size ())
+	    {
+	      gdb_assert (execution_direction == EXEC_FORWARD);
+	      record_full_next_insn = record_full_list.size ();
+	    }
+	  /* Reset the current instruction to point to the one to be replayed
+	     moving forward.  */
+	  else if (execution_direction == EXEC_REVERSE)
+	    record_full_next_insn++;
+
 	replay_out:
 	  if (status->kind () == TARGET_WAITKIND_STOPPED)
 	    {
+	      int insn = (execution_direction == EXEC_FORWARD)
+			 ? record_full_next_insn - 1 : record_full_next_insn;
 	      if (record_full_get_sig)
 		status->set_stopped (GDB_SIGNAL_INT);
-	      else if (record_full_list->u.end.sigval != GDB_SIGNAL_0)
-		/* FIXME: better way to check */
-		status->set_stopped (record_full_list->u.end.sigval);
+	      else if (record_full_list[insn].sigval.has_value ())
+		status->set_stopped
+		  (record_full_list[insn].sigval.value ());
 	      else
 		status->set_stopped (GDB_SIGNAL_TRAP);
 	    }
@@ -1464,12 +1353,9 @@ record_full_wait_1 (struct target_ops *ops,
       catch (const gdb_exception &ex)
 	{
 	  if (execution_direction == EXEC_REVERSE)
-	    {
-	      if (record_full_list->next)
-		record_full_list = record_full_list->next;
-	    }
+	    record_full_next_insn++;
 	  else
-	    record_full_list = record_full_list->prev;
+	    record_full_next_insn--;
 
 	  throw;
 	}
@@ -1557,8 +1443,7 @@ record_full_registers_change (struct regcache *regcache, int regnum)
   /* Check record_full_insn_num.  */
   record_full_check_insn_num ();
 
-  record_full_arch_list_head = NULL;
-  record_full_arch_list_tail = NULL;
+  record_full_reset_incomplete ();
 
   if (regnum < 0)
     {
@@ -1568,7 +1453,7 @@ record_full_registers_change (struct regcache *regcache, int regnum)
 	{
 	  if (record_full_arch_list_add_reg (regcache, i))
 	    {
-	      record_full_list_release (record_full_arch_list_tail);
+	      record_full_reset_incomplete ();
 	      error (_("Process record: failed to record execution log."));
 	    }
 	}
@@ -1577,18 +1462,11 @@ record_full_registers_change (struct regcache *regcache, int regnum)
     {
       if (record_full_arch_list_add_reg (regcache, regnum))
 	{
-	  record_full_list_release (record_full_arch_list_tail);
+	  record_full_reset_incomplete ();
 	  error (_("Process record: failed to record execution log."));
 	}
     }
-  if (record_full_arch_list_add_end ())
-    {
-      record_full_list_release (record_full_arch_list_tail);
-      error (_("Process record: failed to record execution log."));
-    }
-  record_full_list->next = record_full_arch_list_head;
-  record_full_arch_list_head->prev = record_full_list;
-  record_full_list = record_full_arch_list_tail;
+  record_full_save_instruction ();
 
   if (record_full_insn_num == record_full_insn_max_num)
     record_full_list_release_first ();
@@ -1607,7 +1485,7 @@ record_full_target::store_registers (struct regcache *regcache, int regno)
 	{
 	  int n;
 
-	  /* Let user choose if he wants to write register or not.  */
+	  /* Let user choose if they want to write register or not.  */
 	  if (regno < 0)
 	    n =
 	      query (_("Because GDB is in replay mode, changing the "
@@ -1642,7 +1520,7 @@ record_full_target::store_registers (struct regcache *regcache, int regno)
 	    }
 
 	  /* Destroy the record from here forward.  */
-	  record_full_list_release_following (record_full_list);
+	  record_full_list_release_following (record_full_next_insn);
 	}
 
       record_full_registers_change (regcache, regno);
@@ -1675,36 +1553,24 @@ record_full_target::xfer_partial (enum target_object object,
 	    error (_("Process record canceled the operation."));
 
 	  /* Destroy the record from here forward.  */
-	  record_full_list_release_following (record_full_list);
+	  record_full_list_release_following (record_full_next_insn);
 	}
 
       /* Check record_full_insn_num */
       record_full_check_insn_num ();
 
       /* Record registers change to list as an instruction.  */
-      record_full_arch_list_head = NULL;
-      record_full_arch_list_tail = NULL;
+      record_full_reset_incomplete ();
       if (record_full_arch_list_add_mem (offset, len))
 	{
-	  record_full_list_release (record_full_arch_list_tail);
-	  if (record_debug)
-	    gdb_printf (gdb_stdlog,
-			"Process record: failed to record "
-			"execution log.");
-	  return TARGET_XFER_E_IO;
-	}
-      if (record_full_arch_list_add_end ())
-	{
-	  record_full_list_release (record_full_arch_list_tail);
+	  record_full_reset_incomplete ();
 	  if (record_debug)
 	    gdb_printf (gdb_stdlog,
 			"Process record: failed to record "
 			"execution log.");
 	  return TARGET_XFER_E_IO;
 	}
-      record_full_list->next = record_full_arch_list_head;
-      record_full_arch_list_head->prev = record_full_list;
-      record_full_list = record_full_arch_list_tail;
+      record_full_save_instruction ();
 
       if (record_full_insn_num == record_full_insn_max_num)
 	record_full_list_release_first ();
@@ -1865,9 +1731,11 @@ record_full_base_target::get_bookmark (const char *args, int from_tty)
 {
   char *ret = NULL;
 
+  if (record_full_list.empty ())
+    return (gdb_byte *) ret;
+
   /* Return stringified form of instruction count.  */
-  if (record_full_list && record_full_list->type == record_full_end)
-    ret = xstrdup (pulongest (record_full_list->u.end.insn_num));
+  ret = xstrdup (pulongest (record_full_list[record_full_next_insn].insn_num));
 
   if (record_debug)
     {
@@ -1923,30 +1791,22 @@ record_full_base_target::record_method (ptid_t ptid)
 void
 record_full_base_target::info_record ()
 {
-  struct record_full_entry *p;
-
   if (RECORD_FULL_IS_REPLAY)
     gdb_printf (_("Replay mode:\n"));
   else
     gdb_printf (_("Record mode:\n"));
 
-  /* Find entry for first actual instruction in the log.  */
-  for (p = record_full_first.next;
-       p != NULL && p->type != record_full_end;
-       p = p->next)
-    ;
-
   /* Do we have a log at all?  */
-  if (p != NULL && p->type == record_full_end)
+  if (!record_full_list.empty ())
     {
       /* Display instruction number for first instruction in the log.  */
-      gdb_printf (_("Lowest recorded instruction number is %s.\n"),
-		  pulongest (p->u.end.insn_num));
+      gdb_printf (_("Lowest recorded instruction number is %u.\n"),
+		  record_full_list[0].insn_num);
 
       /* If in replay mode, display where we are in the log.  */
       if (RECORD_FULL_IS_REPLAY)
-	gdb_printf (_("Current instruction number is %s.\n"),
-		    pulongest (record_full_list->u.end.insn_num));
+	gdb_printf (_("Current instruction number is %u.\n"),
+		    record_full_list[record_full_next_insn].insn_num);
 
       /* Display instruction number for last instruction in the log.  */
       gdb_printf (_("Highest recorded instruction number is %s.\n"),
@@ -1975,7 +1835,7 @@ record_full_base_target::supports_delete_record ()
 void
 record_full_base_target::delete_record ()
 {
-  record_full_list_release_following (record_full_list);
+  record_full_reset_history ();
 }
 
 /* The "record_is_replaying" target method.  */
@@ -2001,23 +1861,23 @@ record_full_base_target::record_will_replay (ptid_t ptid, int dir)
 /* Go to a specific entry.  */
 
 static void
-record_full_goto_entry (struct record_full_entry *p)
+record_full_goto_entry (size_t target_insn)
 {
-  if (p == NULL)
+  if (target_insn >= record_full_list.size ())
     error (_("Target insn not found."));
-  else if (p == record_full_list)
+  else if (target_insn == record_full_next_insn)
     error (_("Already at target insn."));
-  else if (p->u.end.insn_num > record_full_list->u.end.insn_num)
+  else if (target_insn > record_full_next_insn)
     {
       gdb_printf (_("Go forward to insn number %s\n"),
-		  pulongest (p->u.end.insn_num));
-      record_full_goto_insn (p, EXEC_FORWARD);
+		  pulongest (record_full_list[target_insn].insn_num));
+      record_full_goto_insn (target_insn, EXEC_FORWARD);
     }
   else
     {
       gdb_printf (_("Go backward to insn number %s\n"),
-		  pulongest (p->u.end.insn_num));
-      record_full_goto_insn (p, EXEC_REVERSE);
+		  pulongest (target_insn));
+      record_full_goto_insn (target_insn, EXEC_REVERSE);
     }
 
   registers_changed ();
@@ -2033,13 +1893,7 @@ record_full_goto_entry (struct record_full_entry *p)
 void
 record_full_base_target::goto_record_begin ()
 {
-  struct record_full_entry *p = NULL;
-
-  for (p = &record_full_first; p != NULL; p = p->next)
-    if (p->type == record_full_end)
-      break;
-
-  record_full_goto_entry (p);
+  record_full_goto_entry (0);
 }
 
 /* The "goto_record_end" target method.  */
@@ -2047,15 +1901,7 @@ record_full_base_target::goto_record_begin ()
 void
 record_full_base_target::goto_record_end ()
 {
-  struct record_full_entry *p = NULL;
-
-  for (p = record_full_list; p->next != NULL; p = p->next)
-    ;
-  for (; p!= NULL; p = p->prev)
-    if (p->type == record_full_end)
-      break;
-
-  record_full_goto_entry (p);
+  record_full_goto_entry (record_full_list.size () - 1);
 }
 
 /* The "goto_record" target method.  */
@@ -2063,13 +1909,7 @@ record_full_base_target::goto_record_end ()
 void
 record_full_base_target::goto_record (ULONGEST target_insn)
 {
-  struct record_full_entry *p = NULL;
-
-  for (p = &record_full_first; p != NULL; p = p->next)
-    if (p->type == record_full_end && p->u.end.insn_num == target_insn)
-      break;
-
-  record_full_goto_entry (p);
+  record_full_goto_entry (target_insn);
 }
 
 /* The "record_stop_replaying" target method.  */
@@ -2339,19 +2179,96 @@ netorder32 (uint32_t input)
   return ret;
 }
 
+static void
+record_full_read_entry_from_bfd (bfd *cbfd, asection *osec, int *bfd_offset)
+{
+  uint8_t rectype;
+  uint32_t regnum, len;
+  uint64_t addr;
+  regcache *cache = get_thread_regcache (inferior_thread ());
+
+  bfdcore_read (cbfd, osec, &rectype, sizeof (rectype), bfd_offset);
+  switch (rectype)
+    {
+    case record_full_reg: /* reg */
+      {
+	/* Get register number to regnum.  */
+	bfdcore_read (cbfd, osec, &regnum, sizeof (regnum), bfd_offset);
+	regnum = netorder32 (regnum);
+
+	record_full_entry rec;
+
+	rec = record_full_reg_init (cache, regnum);
+
+	/* Get val.  */
+	bfdcore_read (cbfd, osec, record_full_get_loc (&rec),
+		      rec.u.reg.len, bfd_offset);
+
+	if (record_debug)
+	  gdb_printf (gdb_stdlog,
+		      "  Reading register %d (1 "
+		      "plus %lu plus %d bytes)\n",
+		      rec.u.reg.num,
+		      (unsigned long) sizeof (regnum),
+		      rec.u.reg.len);
+
+	record_full_arch_list_add (rec);
+	break;
+      }
+
+    case record_full_mem: /* mem */
+      {
+	/* Get len.  */
+	bfdcore_read (cbfd, osec, &len, sizeof (len), bfd_offset);
+	len = netorder32 (len);
+
+	/* Get addr.  */
+	bfdcore_read (cbfd, osec, &addr, sizeof (addr),
+		      bfd_offset);
+	addr = netorder64 (addr);
+
+	record_full_entry rec;
+	rec = record_full_mem_init (addr, len);
+
+	/* Get val.  */
+	bfdcore_read (cbfd, osec, record_full_get_loc (&rec), len,
+		      bfd_offset);
+
+	if (record_debug)
+	  gdb_printf (gdb_stdlog,
+		      "  Reading memory %s (1 plus "
+		      "%lu plus %lu plus %d bytes)\n",
+		      paddress (get_current_arch (),
+				rec.u.mem.addr),
+		      (unsigned long) sizeof (addr),
+		      (unsigned long) sizeof (len),
+		      len);
+
+	record_full_arch_list_add (rec);
+	break;
+      }
+
+    default:
+      error (_("Bad entry type %d, offset %d of %lu in core file %ps."),
+	     rectype, *bfd_offset, bfd_section_size (osec),
+	     styled_string (file_name_style.style (),
+			    bfd_get_filename (cbfd)));
+      break;
+    }
+}
+
 /* Restore the execution log from core file CBFD.  */
 
 static void
 record_full_restore (struct bfd &cbfd)
 {
   uint32_t magic;
-  struct record_full_entry *rec;
   asection *osec;
   uint32_t osec_size;
   int bfd_offset = 0;
 
   /* "record_full_restore" can only be called when record list is empty.  */
-  gdb_assert (record_full_first.next == NULL);
+  gdb_assert (record_full_list.empty ());
 
   if (record_debug)
     gdb_printf (gdb_stdlog, "Restoring recording from core file.\n");
@@ -2379,124 +2296,47 @@ record_full_restore (struct bfd &cbfd)
 		"RECORD_FULL_FILE_MAGIC (0x%s)\n",
 		phex_nz (netorder32 (magic), 4));
 
-  /* Restore the entries in recfd into record_full_arch_list_head and
-     record_full_arch_list_tail.  */
-  record_full_arch_list_head = NULL;
-  record_full_arch_list_tail = NULL;
   record_full_insn_num = 0;
 
   try
     {
-      regcache *regcache = get_thread_regcache (inferior_thread ());
-
-      while (1)
+      while (bfd_offset < osec_size)
 	{
-	  uint8_t rectype;
-	  uint32_t regnum, len, signal, count;
-	  uint64_t addr;
+	  record_full_reset_incomplete ();
+	  uint32_t eff_count = 0;
+	  uint8_t sigval;
+	  uint32_t insn_num;
 
-	  /* We are finished when offset reaches osec_size.  */
-	  if (bfd_offset >= osec_size)
-	    break;
-	  bfdcore_read (&cbfd, osec, &rectype, sizeof (rectype), &bfd_offset);
+	  /* First read the generic information for an instruction.  */
+	  bfdcore_read (&cbfd, osec, &sigval, sizeof (uint8_t), &bfd_offset);
+	  bfdcore_read (&cbfd, osec, &eff_count, sizeof (uint32_t),
+			&bfd_offset);
+	  bfdcore_read (&cbfd, osec, &insn_num, sizeof (uint32_t),
+			&bfd_offset);
 
-	  switch (rectype)
-	    {
-	    case record_full_reg: /* reg */
-	      /* Get register number to regnum.  */
-	      bfdcore_read (&cbfd, osec, &regnum, sizeof (regnum),
-			    &bfd_offset);
-	      regnum = netorder32 (regnum);
-
-	      rec = record_full_reg_alloc (regcache, regnum);
-
-	      /* Get val.  */
-	      bfdcore_read (&cbfd, osec, record_full_get_loc (rec),
-			    rec->u.reg.len, &bfd_offset);
-
-	      if (record_debug)
-		gdb_printf (gdb_stdlog,
-			    "  Reading register %d (1 "
-			    "plus %lu plus %d bytes)\n",
-			    rec->u.reg.num,
-			    (unsigned long) sizeof (regnum),
-			    rec->u.reg.len);
-	      break;
+	  record_full_incomplete_instruction.insn_num = netorder32 (insn_num);
+	  if (sigval != GDB_SIGNAL_0)
+	    record_full_incomplete_instruction.sigval = (gdb_signal) sigval;
 
-	    case record_full_mem: /* mem */
-	      /* Get len.  */
-	      bfdcore_read (&cbfd, osec, &len, sizeof (len), &bfd_offset);
-	      len = netorder32 (len);
-
-	      /* Get addr.  */
-	      bfdcore_read (&cbfd, osec, &addr, sizeof (addr), &bfd_offset);
-	      addr = netorder64 (addr);
-
-	      rec = record_full_mem_alloc (addr, len);
-
-	      /* Get val.  */
-	      bfdcore_read (&cbfd, osec, record_full_get_loc (rec),
-			    rec->u.mem.len, &bfd_offset);
-
-	      if (record_debug)
-		gdb_printf (gdb_stdlog,
-			    "  Reading memory %s (1 plus "
-			    "%lu plus %lu plus %d bytes)\n",
-			    paddress (get_current_arch (),
-				      rec->u.mem.addr),
-			    (unsigned long) sizeof (addr),
-			    (unsigned long) sizeof (len),
-			    rec->u.mem.len);
-	      break;
+	  eff_count = netorder32 (eff_count);
 
-	    case record_full_end: /* end */
-	      rec = record_full_end_alloc ();
-	      record_full_insn_num ++;
-
-	      /* Get signal value.  */
-	      bfdcore_read (&cbfd, osec, &signal, sizeof (signal),
-			    &bfd_offset);
-	      signal = netorder32 (signal);
-	      rec->u.end.sigval = (enum gdb_signal) signal;
-
-	      /* Get insn count.  */
-	      bfdcore_read (&cbfd, osec, &count, sizeof (count), &bfd_offset);
-	      count = netorder32 (count);
-	      rec->u.end.insn_num = count;
-	      record_full_insn_count = count + 1;
-	      if (record_debug)
-		gdb_printf (gdb_stdlog,
-			    "  Reading record_full_end (1 + "
-			    "%lu + %lu bytes), offset == %s\n",
-			    (unsigned long) sizeof (signal),
-			    (unsigned long) sizeof (count),
-			    paddress (get_current_arch (),
-				      bfd_offset));
-	      break;
+	  /* This deals with all the side effects.  */
+	 while (eff_count > 0)
+	   {
+	     eff_count--;
 
-	    default:
-	      error (_("Bad entry type in core file %ps."),
-		     styled_string (file_name_style.style (),
-				    bfd_get_filename (&cbfd)));
-	      break;
-	    }
+	     record_full_read_entry_from_bfd (&cbfd, osec, &bfd_offset);
+	   }
 
-	  /* Add rec to record arch list.  */
-	  record_full_arch_list_add (rec);
+	  record_full_save_instruction ();
 	}
     }
   catch (const gdb_exception &ex)
     {
-      record_full_list_release (record_full_arch_list_tail);
+      record_full_reset_incomplete ();
       throw;
     }
 
-  /* Add record_full_arch_list_head to the end of record list.  */
-  record_full_first.next = record_full_arch_list_head;
-  record_full_arch_list_head->prev = &record_full_first;
-  record_full_arch_list_tail->next = NULL;
-  record_full_list = &record_full_first;
-
   /* Update record_full_insn_max_num.  */
   if (record_full_insn_num > record_full_insn_max_num)
     {
@@ -2505,6 +2345,10 @@ record_full_restore (struct bfd &cbfd)
 	       record_full_insn_max_num);
     }
 
+  /* When loading a recording, we'll always start at the oldest possible
+     instruction, no matter where the original recording was stopped.  */
+  record_full_next_insn = 0;
+
   /* Succeeded.  */
   gdb_printf (_("Restored records from core file %s.\n"),
 	      bfd_get_filename (&cbfd));
@@ -2538,13 +2382,73 @@ cmd_record_full_restore (const char *args, int from_tty)
   record_full_open (nullptr, from_tty);
 }
 
+static void
+record_full_write_entry_to_bfd (record_full_entry &entry,
+				gdb_bfd_ref_ptr obfd,
+				asection *osec, int *bfd_offset,
+				gdbarch *gdbarch)
+{
+  /* Save entry.  */
+  uint8_t type;
+  uint32_t regnum, len;
+  uint64_t addr;
+
+  type = entry.type;
+  bfdcore_write (obfd.get (), osec, &type, sizeof (type), bfd_offset);
+
+  switch (entry.type)
+    {
+    case record_full_reg: /* reg */
+      if (record_debug)
+	gdb_printf (gdb_stdlog,
+		    "  Writing register %d (1 "
+		    "plus %lu plus %d bytes)\n",
+		    entry.u.reg.num,
+		    (unsigned long) sizeof (regnum),
+		    entry.u.reg.len);
+
+      /* Write regnum.  */
+      regnum = netorder32 (entry.u.reg.num);
+      bfdcore_write (obfd.get (), osec, &regnum, sizeof (regnum), bfd_offset);
+
+      /* Write regval.  */
+      bfdcore_write (obfd.get (), osec, record_full_get_loc (&entry),
+		     entry.u.reg.len, bfd_offset);
+      break;
+
+    case record_full_mem: /* mem */
+      if (record_debug)
+	gdb_printf (gdb_stdlog,
+		    "  Writing memory %s (1 plus "
+		    "%lu plus %lu plus %d bytes)\n",
+		    paddress (gdbarch,
+			      entry.u.mem.addr),
+		    (unsigned long) sizeof (addr),
+		    (unsigned long) sizeof (len),
+		    entry.u.mem.len);
+
+      /* Write memlen.  */
+      len = netorder32 (entry.u.mem.len);
+      bfdcore_write (obfd.get (), osec, &len, sizeof (len), bfd_offset);
+
+      /* Write memaddr.  */
+      addr = netorder64 (entry.u.mem.addr);
+      bfdcore_write (obfd.get (), osec, &addr, sizeof (addr), bfd_offset);
+
+      /* Write memval.  */
+      bfdcore_write (obfd.get (), osec, record_full_get_loc (&entry),
+		     entry.u.mem.len, bfd_offset);
+      break;
+    }
+
+}
+
 /* Save the execution log to a file.  We use a modified elf corefile
    format, with an extra section for our data.  */
 
 void
 record_full_base_target::save_record (const char *recfilename)
 {
-  struct record_full_entry *cur_record_full_list;
   uint32_t magic;
   struct gdbarch *gdbarch;
   int save_size = 0;
@@ -2562,9 +2466,6 @@ record_full_base_target::save_record (const char *recfilename)
   /* Arrange to remove the output file on failure.  */
   gdb::unlinker unlink_file (recfilename);
 
-  /* Save the current record entry to "cur_record_full_list".  */
-  cur_record_full_list = record_full_list;
-
   /* Get the values of regcache and gdbarch.  */
   regcache *regcache = get_thread_regcache (inferior_thread ());
   gdbarch = regcache->arch ();
@@ -2574,34 +2475,27 @@ record_full_base_target::save_record (const char *recfilename)
     = record_full_gdb_operation_disable_set ();
 
   /* Reverse execute to the begin of record list.  */
-  while (1)
-    {
-      /* Check for beginning and end of log.  */
-      if (record_full_list == &record_full_first)
-	break;
-
-      record_full_exec_insn (regcache, gdbarch, record_full_list);
-
-      if (record_full_list->prev)
-	record_full_list = record_full_list->prev;
-    }
+  for (int i = record_full_next_insn - 1; i >= 0; i--)
+    record_full_exec_insn (regcache, gdbarch,
+			   record_full_list[i]);
 
   /* Compute the size needed for the extra bfd section.  */
   save_size = 4;	/* magic cookie */
-  for (record_full_list = record_full_first.next; record_full_list;
-       record_full_list = record_full_list->next)
-    switch (record_full_list->type)
-      {
-      case record_full_end:
-	save_size += 1 + 4 + 4;
-	break;
-      case record_full_reg:
-	save_size += 1 + 4 + record_full_list->u.reg.len;
-	break;
-      case record_full_mem:
-	save_size += 1 + 4 + 8 + record_full_list->u.mem.len;
-	break;
-      }
+  for (int i = record_full_list.size () - 1; i >= 0; i--)
+    {
+      /* Number of effects of an instruction.  */
+      save_size += sizeof (uint32_t) + sizeof (uint8_t) + sizeof (uint32_t);
+      for (auto &entry : record_full_list[i].effects)
+	switch (entry.type)
+	  {
+	  case record_full_reg:
+	    save_size += 1 + 4 + entry.u.reg.len;
+	    break;
+	  case record_full_mem:
+	    save_size += 1 + 4 + 8 + entry.u.mem.len;
+	    break;
+	  }
+    }
 
   /* Make the new bfd section.  */
   osec = bfd_make_section_anyway_with_flags (obfd.get (), "precord",
@@ -2630,108 +2524,32 @@ record_full_base_target::save_record (const char *recfilename)
 
   /* Save the entries to recfd and forward execute to the end of
      record list.  */
-  record_full_list = &record_full_first;
-  while (1)
+  for (int i = 0; i < record_full_list.size (); i++)
     {
-      /* Save entry.  */
-      if (record_full_list != &record_full_first)
+      uint32_t eff_count = (uint32_t) record_full_list[i].effects.size ();
+      uint32_t insn_num = record_full_list[i].insn_num;
+      uint8_t sigval = (record_full_list[i].sigval.has_value ())
+			 ? record_full_list[i].sigval.value ()
+			 : GDB_SIGNAL_0;
+
+      /* Signal.  */
+      bfdcore_write (obfd.get (), osec, &sigval, sizeof (sigval), &bfd_offset);
+      eff_count = netorder32 (eff_count);
+      /* Number of effects.  */
+      bfdcore_write (obfd.get (), osec, &eff_count, sizeof (eff_count),
+		     &bfd_offset);
+      /* Instruction number.  */
+      bfdcore_write (obfd.get (), osec, &insn_num, sizeof (uint32_t),
+		     &bfd_offset);
+
+      for (auto &entry : record_full_list[i].effects)
 	{
-	  uint8_t type;
-	  uint32_t regnum, len, signal, count;
-	  uint64_t addr;
-
-	  type = record_full_list->type;
-	  bfdcore_write (obfd.get (), osec, &type, sizeof (type), &bfd_offset);
-
-	  switch (record_full_list->type)
-	    {
-	    case record_full_reg: /* reg */
-	      if (record_debug)
-		gdb_printf (gdb_stdlog,
-			    "  Writing register %d (1 "
-			    "plus %lu plus %d bytes)\n",
-			    record_full_list->u.reg.num,
-			    (unsigned long) sizeof (regnum),
-			    record_full_list->u.reg.len);
-
-	      /* Write regnum.  */
-	      regnum = netorder32 (record_full_list->u.reg.num);
-	      bfdcore_write (obfd.get (), osec, &regnum,
-			     sizeof (regnum), &bfd_offset);
-
-	      /* Write regval.  */
-	      bfdcore_write (obfd.get (), osec,
-			     record_full_get_loc (record_full_list),
-			     record_full_list->u.reg.len, &bfd_offset);
-	      break;
-
-	    case record_full_mem: /* mem */
-	      if (record_debug)
-		gdb_printf (gdb_stdlog,
-			    "  Writing memory %s (1 plus "
-			    "%lu plus %lu plus %d bytes)\n",
-			    paddress (gdbarch,
-				      record_full_list->u.mem.addr),
-			    (unsigned long) sizeof (addr),
-			    (unsigned long) sizeof (len),
-			    record_full_list->u.mem.len);
-
-	      /* Write memlen.  */
-	      len = netorder32 (record_full_list->u.mem.len);
-	      bfdcore_write (obfd.get (), osec, &len, sizeof (len),
-			     &bfd_offset);
-
-	      /* Write memaddr.  */
-	      addr = netorder64 (record_full_list->u.mem.addr);
-	      bfdcore_write (obfd.get (), osec, &addr,
-			     sizeof (addr), &bfd_offset);
-
-	      /* Write memval.  */
-	      bfdcore_write (obfd.get (), osec,
-			     record_full_get_loc (record_full_list),
-			     record_full_list->u.mem.len, &bfd_offset);
-	      break;
-
-	      case record_full_end:
-		if (record_debug)
-		  gdb_printf (gdb_stdlog,
-			      "  Writing record_full_end (1 + "
-			      "%lu + %lu bytes)\n",
-			      (unsigned long) sizeof (signal),
-			      (unsigned long) sizeof (count));
-		/* Write signal value.  */
-		signal = netorder32 (record_full_list->u.end.sigval);
-		bfdcore_write (obfd.get (), osec, &signal,
-			       sizeof (signal), &bfd_offset);
-
-		/* Write insn count.  */
-		count = netorder32 (record_full_list->u.end.insn_num);
-		bfdcore_write (obfd.get (), osec, &count,
-			       sizeof (count), &bfd_offset);
-		break;
-	    }
+	  record_full_write_entry_to_bfd (entry, obfd, osec, &bfd_offset,
+					  gdbarch);
 	}
-
       /* Execute entry.  */
-      record_full_exec_insn (regcache, gdbarch, record_full_list);
-
-      if (record_full_list->next)
-	record_full_list = record_full_list->next;
-      else
-	break;
-    }
-
-  /* Reverse execute to cur_record_full_list.  */
-  while (1)
-    {
-      /* Check for beginning and end of log.  */
-      if (record_full_list == cur_record_full_list)
-	break;
-
-      record_full_exec_insn (regcache, gdbarch, record_full_list);
-
-      if (record_full_list->prev)
-	record_full_list = record_full_list->prev;
+      if (i < record_full_next_insn)
+	record_full_exec_insn (regcache, gdbarch, record_full_list[i]);
     }
 
   unlink_file.keep ();
@@ -2742,11 +2560,11 @@ record_full_base_target::save_record (const char *recfilename)
 }
 
 /* record_full_goto_insn -- rewind the record log (forward or backward,
-   depending on DIR) to the given entry, changing the program state
-   correspondingly.  */
+   depending on DIR) to the entry in position TARGET_INSN in the history,
+   changing the program state correspondingly.  */
 
 static void
-record_full_goto_insn (struct record_full_entry *entry,
+record_full_goto_insn (size_t target_insn,
 		       enum exec_direction_kind dir)
 {
   scoped_restore restore_operation_disable
@@ -2757,17 +2575,12 @@ record_full_goto_insn (struct record_full_entry *entry,
   /* Assume everything is valid: we will hit the entry,
      and we will not hit the end of the recording.  */
 
-  if (dir == EXEC_FORWARD)
-    record_full_list = record_full_list->next;
-
-  do
-    {
-      record_full_exec_insn (regcache, gdbarch, record_full_list);
-      if (dir == EXEC_REVERSE)
-	record_full_list = record_full_list->prev;
-      else
-	record_full_list = record_full_list->next;
-    } while (record_full_list != entry);
+  if (dir == EXEC_REVERSE)
+    for (int i = record_full_next_insn; i > target_insn; i--)
+      record_full_exec_insn (regcache, gdbarch, record_full_list[i - 1]);
+  else
+    for (int i = record_full_next_insn; i < target_insn; i++)
+      record_full_exec_insn (regcache, gdbarch, record_full_list[i]);
 }
 
 /* Alias for "target record-full".  */
@@ -2798,61 +2611,36 @@ set_record_full_insn_max_num (const char *args, int from_tty,
 static void
 maintenance_print_record_instruction (const char *args, int from_tty)
 {
-  struct record_full_entry *to_print = record_full_list;
+  if (record_full_list.empty ())
+    error (_("Not enough recorded history"));
 
+  int offset = record_full_next_insn - 1;
+  /* Reduce the offset by 1 if the record_full_next_insn is after the end
+     so that we show the last recorded instruction instead of crashing.  */
+  if (offset == record_full_list.size ())
+    offset--;
   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"));
-	}
+      offset += value_as_long (parse_and_eval (args));
+      if (offset >= record_full_list.size () || offset < 0)
+	error (_("Not enough recorded history"));
     }
-  gdb_assert (to_print != nullptr);
+  auto to_print = record_full_list.begin () + offset;
 
   gdbarch *arch = current_inferior ()->arch ();
 
-  /* 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)
+  for (auto entry : to_print->effects)
     {
-      switch (to_print->type)
+      switch (entry.type)
 	{
 	  case record_full_reg:
 	    {
-	      type *regtype = gdbarch_register_type (arch, to_print->u.reg.num);
+	      type *regtype = gdbarch_register_type (arch, entry.u.reg.num);
 	      value *val
 		  = value_from_contents (regtype,
-					 record_full_get_loc (to_print));
+					 record_full_get_loc (&entry));
 	      gdb_printf ("Register %s changed: ",
-			  gdbarch_register_name (arch, to_print->u.reg.num));
+			  gdbarch_register_name (arch, entry.u.reg.num));
 	      struct value_print_options opts;
 	      get_user_print_options (&opts);
 	      opts.raw = true;
@@ -2862,17 +2650,16 @@ maintenance_print_record_instruction (const char *args, int from_tty)
 	    }
 	  case record_full_mem:
 	    {
-	      gdb_byte *b = record_full_get_loc (to_print);
+	      gdb_byte *b = record_full_get_loc (&entry);
 	      gdb_printf ("%d bytes of memory at address %s changed from:",
-			  to_print->u.mem.len,
-			  print_core_address (arch, to_print->u.mem.addr));
-	      for (int i = 0; i < to_print->u.mem.len; i++)
+			  entry.u.mem.len,
+			  print_core_address (arch, entry.u.mem.addr));
+	      for (int i = 0; i < entry.u.mem.len; i++)
 		gdb_printf (" %02x", b[i]);
 	      gdb_printf ("\n");
 	      break;
 	    }
 	}
-      to_print = to_print->next;
     }
 }
 
@@ -2880,11 +2667,6 @@ INIT_GDB_FILE (record_full)
 {
   struct cmd_list_element *c;
 
-  /* Init record_full_first.  */
-  record_full_first.prev = NULL;
-  record_full_first.next = NULL;
-  record_full_first.type = record_full_end;
-
   add_target (record_full_target_info, record_full_open);
   add_deprecated_target_alias (record_full_target_info, "record");
   add_target (record_full_core_target_info, record_full_open);
diff --git a/gdb/record-full.h b/gdb/record-full.h
index 51effe74560..c327d879a8a 100644
--- a/gdb/record-full.h
+++ b/gdb/record-full.h
@@ -41,7 +41,6 @@ enum record_result
 
 extern int record_full_arch_list_add_reg (struct regcache *regcache, int num);
 extern int record_full_arch_list_add_mem (CORE_ADDR addr, int len);
-extern int record_full_arch_list_add_end (void);
 
 /* Returns true if the process record target is open.  */
 extern int record_full_is_used (void);
diff --git a/gdb/riscv-tdep.c b/gdb/riscv-tdep.c
index 6b5c9c02d46..f5b1f66844c 100644
--- a/gdb/riscv-tdep.c
+++ b/gdb/riscv-tdep.c
@@ -5466,8 +5466,5 @@ riscv_process_record (struct gdbarch *gdbarch, struct regcache *regcache,
   if (res != RECORD_SUCCESS)
     return res;
 
-  if (record_full_arch_list_add_end ())
-    return -1;
-
   return 0;
 }
diff --git a/gdb/rs6000-tdep.c b/gdb/rs6000-tdep.c
index e135df94a40..f964889a49f 100644
--- a/gdb/rs6000-tdep.c
+++ b/gdb/rs6000-tdep.c
@@ -7138,8 +7138,6 @@ ppc_process_prefix_instruction (int insn_prefix, int insn_suffix,
   if (record_full_arch_list_add_reg (regcache, PPC_PC_REGNUM))
     return -1;
 
-  if (record_full_arch_list_add_end ())
-    return -1;
   return 0;
 }
 
@@ -7447,8 +7445,6 @@ ppc_process_record (struct gdbarch *gdbarch, struct regcache *regcache,
 
   if (record_full_arch_list_add_reg (regcache, PPC_PC_REGNUM))
     return -1;
-  if (record_full_arch_list_add_end ())
-    return -1;
   return 0;
 }
 
diff --git a/gdb/s390-linux-tdep.c b/gdb/s390-linux-tdep.c
index 3cd6b359d13..6bea30f08dc 100644
--- a/gdb/s390-linux-tdep.c
+++ b/gdb/s390-linux-tdep.c
@@ -910,9 +910,6 @@ s390_linux_record_signal (struct gdbarch *gdbarch, struct regcache *regcache,
   if (record_full_arch_list_add_mem (sp, sizeof_rt_sigframe))
     return -1;
 
-  if (record_full_arch_list_add_end ())
-    return -1;
-
   return 0;
 }
 
diff --git a/gdb/s390-tdep.c b/gdb/s390-tdep.c
index f9d7bdd04e4..36ce659ea15 100644
--- a/gdb/s390-tdep.c
+++ b/gdb/s390-tdep.c
@@ -7019,8 +7019,6 @@ s390_process_record (struct gdbarch *gdbarch, struct regcache *regcache,
 
   if (record_full_arch_list_add_reg (regcache, S390_PSWA_REGNUM))
     return -1;
-  if (record_full_arch_list_add_end ())
-    return -1;
   return 0;
 }
 
-- 
2.54.0


^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v3 2/7] gdb/record: remove record_full_insn_num
  2026-05-15 16:36 [PATCH v3 0/7] refactor the internals of record-full Guinevere Larsen
  2026-05-15 16:36 ` [PATCH v3 1/7] gdb/record: Refactor record history Guinevere Larsen
@ 2026-05-15 16:36 ` Guinevere Larsen
  2026-05-26  8:35   ` Schimpe, Christina
  2026-05-15 16:37 ` [PATCH v3 3/7] gdb/record: c++ify internal structures of record-full.c Guinevere Larsen
                   ` (7 subsequent siblings)
  9 siblings, 1 reply; 27+ messages in thread
From: Guinevere Larsen @ 2026-05-15 16:36 UTC (permalink / raw)
  To: gdb-patches; +Cc: Guinevere Larsen, Thiago Jung Bauermann

Now that record_full_list uses a deque, we don't need a variable to
track the amount of items recorded, so it is just dropped.

Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
---
 gdb/record-full.c | 41 ++++++++++++-----------------------------
 1 file changed, 12 insertions(+), 29 deletions(-)

diff --git a/gdb/record-full.c b/gdb/record-full.c
index 02a57726a95..e4dd07dc300 100644
--- a/gdb/record-full.c
+++ b/gdb/record-full.c
@@ -193,8 +193,6 @@ static bool record_full_stop_at_limit = true;
 /* Maximum allowed number of insns in execution log.  */
 static unsigned int record_full_insn_max_num
 	= DEFAULT_RECORD_FULL_INSN_MAX_NUM;
-/* Actual count of insns presently in execution log.  */
-static unsigned int record_full_insn_num = 0;
 /* Count of insns logged so far (may be larger
    than count of insns presently in execution log).  */
 static ULONGEST record_full_insn_count;
@@ -462,7 +460,6 @@ record_full_entry_cleanup (record_full_entry rec)
 static void
 record_full_reset_history ()
 {
-  record_full_insn_num = 0;
   record_full_insn_count = 0;
   record_full_next_insn = 0;
 
@@ -504,9 +501,7 @@ record_full_save_instruction ()
 }
 
 /* Delete the first instruction from the beginning of the log, to make
-   room for adding a new instruction at the end of the log.
-
-   Note -- this function does not modify record_full_insn_num.  */
+   room for adding a new instruction at the end of the log.  */
 
 static void
 record_full_list_release_first (void)
@@ -606,7 +601,7 @@ record_full_arch_list_add_mem (CORE_ADDR addr, int len)
 static void
 record_full_check_insn_num (void)
 {
-  if (record_full_insn_num == record_full_insn_max_num)
+  if (record_full_list.size () == record_full_insn_max_num)
     {
       /* Ask user what to do.  */
       if (record_full_stop_at_limit)
@@ -688,10 +683,8 @@ record_full_message (struct regcache *regcache, enum gdb_signal signal)
 
   record_full_save_instruction ();
 
-  if (record_full_insn_num == record_full_insn_max_num)
+  if (record_full_list.size () == record_full_insn_max_num)
     record_full_list_release_first ();
-  else
-    record_full_insn_num++;
 }
 
 static bool
@@ -1468,10 +1461,8 @@ record_full_registers_change (struct regcache *regcache, int regnum)
     }
   record_full_save_instruction ();
 
-  if (record_full_insn_num == record_full_insn_max_num)
+  if (record_full_list.size () == record_full_insn_max_num)
     record_full_list_release_first ();
-  else
-    record_full_insn_num++;
 }
 
 /* "store_registers" method for process record target.  */
@@ -1572,10 +1563,8 @@ record_full_target::xfer_partial (enum target_object object,
 	}
       record_full_save_instruction ();
 
-      if (record_full_insn_num == record_full_insn_max_num)
+      if (record_full_list.size () == record_full_insn_max_num)
 	record_full_list_release_first ();
-      else
-	record_full_insn_num++;
     }
 
   return this->beneath ()->xfer_partial (object, annex, readbuf, writebuf,
@@ -1813,8 +1802,8 @@ record_full_base_target::info_record ()
 		  pulongest (record_full_insn_count));
 
       /* Display log count.  */
-      gdb_printf (_("Log contains %u instructions.\n"),
-		  record_full_insn_num);
+      gdb_printf (_("Log contains %lu instructions.\n"),
+		  (unsigned long int) record_full_list.size ());
     }
   else
     gdb_printf (_("No instructions have been logged.\n"));
@@ -2296,8 +2285,6 @@ record_full_restore (struct bfd &cbfd)
 		"RECORD_FULL_FILE_MAGIC (0x%s)\n",
 		phex_nz (netorder32 (magic), 4));
 
-  record_full_insn_num = 0;
-
   try
     {
       while (bfd_offset < osec_size)
@@ -2338,9 +2325,9 @@ record_full_restore (struct bfd &cbfd)
     }
 
   /* Update record_full_insn_max_num.  */
-  if (record_full_insn_num > record_full_insn_max_num)
+  if (record_full_list.size () > record_full_insn_max_num)
     {
-      record_full_insn_max_num = record_full_insn_num;
+      record_full_insn_max_num = record_full_list.size ();
       warning (_("Auto increase record/replay buffer limit to %u."),
 	       record_full_insn_max_num);
     }
@@ -2595,14 +2582,10 @@ static void
 set_record_full_insn_max_num (const char *args, int from_tty,
 			      struct cmd_list_element *c)
 {
-  if (record_full_insn_num > record_full_insn_max_num)
+  if (record_full_list.size () > record_full_insn_max_num)
     {
-      /* Count down record_full_insn_num while releasing records from list.  */
-      while (record_full_insn_num > record_full_insn_max_num)
-       {
-	 record_full_list_release_first ();
-	 record_full_insn_num--;
-       }
+      while (record_full_list.size () > record_full_insn_max_num)
+	record_full_list_release_first ();
     }
 }
 
-- 
2.54.0


^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v3 3/7] gdb/record: c++ify internal structures of record-full.c
  2026-05-15 16:36 [PATCH v3 0/7] refactor the internals of record-full Guinevere Larsen
  2026-05-15 16:36 ` [PATCH v3 1/7] gdb/record: Refactor record history Guinevere Larsen
  2026-05-15 16:36 ` [PATCH v3 2/7] gdb/record: remove record_full_insn_num Guinevere Larsen
@ 2026-05-15 16:37 ` Guinevere Larsen
  2026-05-26  9:00   ` Schimpe, Christina
  2026-05-15 16:37 ` [PATCH v3 4/7] gdb/record: make record_full_history more c++-like Guinevere Larsen
                   ` (6 subsequent siblings)
  9 siblings, 1 reply; 27+ messages in thread
From: Guinevere Larsen @ 2026-05-15 16:37 UTC (permalink / raw)
  To: gdb-patches; +Cc: Guinevere Larsen, Thiago Jung Bauermann

This commit adds a constructor, destructor, and some methods to the
structures record_full_entry, record_full_reg_entry and
record_full_mem_entry. This is a move to disentangle the internal
representation of the data and how record-full manipulates it for
replaying.

Along with this change, record_full_entry is changed to use an
std::variant, since it was basically doing that already, but now we have
the stdlibc++ error checking to make sure we're only accessing elements
we're allowed to.

Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
---
 gdb/record-full.c | 601 +++++++++++++++++++++++++---------------------
 1 file changed, 321 insertions(+), 280 deletions(-)

diff --git a/gdb/record-full.c b/gdb/record-full.c
index e4dd07dc300..24e94eaf5da 100644
--- a/gdb/record-full.c
+++ b/gdb/record-full.c
@@ -51,6 +51,7 @@
 #include <optional>
 #include <deque>
 #include <signal.h>
+#include <variant>
 
 /* This module implements "target record-full", also known as "process
    record and replay".  This target sits on top of a "normal" target
@@ -93,12 +94,113 @@ struct record_full_mem_entry
   int len;
   /* Set this flag if target memory for this entry
      can no longer be accessed.  */
-  int mem_entry_not_accessible;
+  bool mem_entry_accessible;
   union
   {
     gdb_byte *ptr;
     gdb_byte buf[sizeof (gdb_byte *)];
   } u;
+
+  record_full_mem_entry () : addr (0), len (0) { }
+
+  record_full_mem_entry (CORE_ADDR mem_addr, int mem_len)
+  {
+    addr = mem_addr;
+    len = mem_len;
+    if (len > sizeof (u.buf))
+      u.ptr = new gdb_byte[len];
+    mem_entry_accessible = true;
+  }
+
+  record_full_mem_entry (record_full_mem_entry &&other)
+  {
+    addr = other.addr;
+    len = other.len;
+    memcpy (u.buf, other.u.buf, sizeof (u.buf));
+    mem_entry_accessible = other.mem_entry_accessible;
+    /* With len == 0, OTHER is guaranteed to not try to free the
+       memory when it is destructed.  */
+    other.len = 0;
+  }
+
+  ~record_full_mem_entry ()
+  {
+    if (len > sizeof (u.buf))
+      delete[] u.ptr;
+  }
+
+  record_full_mem_entry &operator= (record_full_mem_entry &&other)
+  {
+    addr = other.addr;
+    len = other.len;
+    memcpy (u.buf, other.u.buf, sizeof (u.buf));
+    mem_entry_accessible = other.mem_entry_accessible;
+    /* With len == 0, OTHER is guaranteed to not try to free the
+       memory when it is destructed.  */
+    other.len = 0;
+    return *this;
+  }
+
+  DISABLE_COPY_AND_ASSIGN (record_full_mem_entry);
+
+  gdb_byte *get_loc ()
+  {
+    if (len > sizeof (u.buf))
+      return u.ptr;
+    else
+      return u.buf;
+  }
+
+  bool execute (gdbarch *gdbarch)
+  {
+    /* Nothing to do if the memory is flagged not_accessible.  */
+    if (!mem_entry_accessible)
+      return false;
+
+    gdb::byte_vector buf (len);
+
+    if (record_debug > 1)
+      gdb_printf (gdb_stdlog,
+		  "Process record: record_full_mem %s to "
+		  "inferior addr = %s len = %d.\n",
+		  host_address_to_string (this),
+		  paddress (gdbarch, addr),
+		  len);
+
+    if (record_read_memory (gdbarch, addr, buf.data (), len))
+      mem_entry_accessible = false;
+    else
+      {
+	if (target_write_memory (addr,
+				 get_loc (),
+				 len))
+	  {
+	    mem_entry_accessible = false;
+	    if (record_debug)
+	      warning (_("Process record: error writing memory at "
+			 "addr = %s len = %d."),
+		       paddress (gdbarch, addr),
+		       len);
+	  }
+	else
+	  {
+	    memcpy (get_loc (), buf.data (), len);
+
+	    /* We've changed memory --- check if a hardware
+	       watchpoint should trap.  Note that this
+	       presently assumes the target beneath supports
+	       continuable watchpoints.  On non-continuable
+	       watchpoints target, we'll want to check this
+	       _before_ actually doing the memory change, and
+	       not doing the change at all if the watchpoint
+	       traps.  */
+	    if (hardware_watchpoint_inserted_in_range
+		(current_inferior ()->aspace.get (), addr, len))
+	      return true;
+	  }
+      }
+    return false;
+  }
 };
 
 struct record_full_reg_entry
@@ -110,6 +212,70 @@ struct record_full_reg_entry
     gdb_byte *ptr;
     gdb_byte buf[2 * sizeof (gdb_byte *)];
   } u;
+
+  record_full_reg_entry () : num (0), len (0) { }
+
+  record_full_reg_entry (gdbarch *gdbarch, int regnum)
+  {
+    num = regnum;
+    len = register_size (gdbarch, regnum);
+    if (len > sizeof (u.buf))
+      u.ptr = new gdb_byte[len];
+  }
+
+  record_full_reg_entry (record_full_reg_entry &&other)
+  {
+    num = other.num;
+    len = other.len;
+    memcpy (u.buf, other.u.buf, sizeof (u.buf));
+    /* With len == 0, OTHER is guaranteed to not try to free the
+       memory when it is destructed.  */
+    other.len = 0;
+  }
+
+  ~record_full_reg_entry ()
+  {
+    if (len > sizeof (u.buf))
+      delete[] u.ptr;
+  }
+
+  record_full_reg_entry &operator=(record_full_reg_entry &&other)
+  {
+    num = other.num;
+    len = other.len;
+    memcpy (u.buf, other.u.buf, sizeof (u.buf));
+    /* With len == 0, OTHER is guaranteed to not try to free the
+       memory when it is destructed.  */
+    other.len = 0;
+    return *this;
+  }
+
+  DISABLE_COPY_AND_ASSIGN (record_full_reg_entry);
+
+  gdb_byte *get_loc ()
+  {
+    if (len > sizeof (u.buf))
+      return u.ptr;
+    else
+      return u.buf;
+  }
+
+  bool execute (regcache *regcache)
+  {
+    gdb::byte_vector buf (len);
+
+    if (record_debug > 1)
+      gdb_printf (gdb_stdlog,
+		  "Process record: record_full_reg %s to "
+		  "inferior num = %d.\n",
+		  host_address_to_string (this),
+		  num);
+
+    regcache->cooked_read (num, buf);
+    regcache->cooked_write (num, get_loc ());
+    memcpy (get_loc (), buf.data (), len);
+    return false;
+  }
 };
 
 enum record_full_type
@@ -118,16 +284,88 @@ enum record_full_type
   record_full_mem
 };
 
-struct record_full_entry
+class record_full_entry
 {
-  enum record_full_type type;
-  union
+  std::variant<record_full_reg_entry, record_full_mem_entry> entry;
+
+public:
+  record_full_entry () : entry (record_full_reg_entry ()) {}
+
+  /* Constructor for a register entry.  Type is here to make it
+     easier to recognize it in the constructor calls, it isn't
+     actually important.  */
+  record_full_entry (record_full_type reg_type, gdbarch *gdbarch,
+		     int regnum)
+  : entry(record_full_reg_entry (gdbarch, regnum))
   {
-    /* reg */
-    struct record_full_reg_entry reg;
-    /* mem */
-    struct record_full_mem_entry mem;
-  } u;
+    gdb_assert (reg_type == record_full_reg);
+  }
+
+  record_full_entry (record_full_type mem_type, CORE_ADDR addr, int len)
+  : entry(record_full_mem_entry (addr, len))
+  {
+    gdb_assert (mem_type == record_full_mem);
+  }
+
+  record_full_entry (record_full_entry &&other)
+  : entry (std::move (other.entry))
+  {
+  }
+
+  DISABLE_COPY_AND_ASSIGN (record_full_entry);
+
+  record_full_reg_entry& reg ()
+  {
+    gdb_assert (type () == record_full_reg);
+    return std::get<record_full_reg_entry> (entry);
+  }
+
+  record_full_mem_entry& mem ()
+  {
+    gdb_assert (type () == record_full_mem);
+    return std::get<record_full_mem_entry> (entry);
+  }
+
+  record_full_type type ()
+  {
+    switch (entry.index ())
+    {
+    case 0:
+      return record_full_reg;
+    case 1:
+      return record_full_mem;
+    }
+    gdb_assert_not_reached ("Impossible variant index");
+  }
+
+  /* Get the pointer to the data stored by this entry.  */
+  gdb_byte *get_loc ()
+  {
+    switch (type ())
+    {
+    case record_full_reg:
+      return reg ().get_loc ();
+    case record_full_mem:
+      return mem ().get_loc ();
+    }
+    gdb_assert_not_reached ("Impossible entry type");
+  }
+
+  /* Execute this entry, swapping the appropriate values from memory or
+     register and the recorded ones.  Returns TRUE if the execution was
+     stopped by a watchpoint.  */
+
+  bool execute (regcache *regcache)
+  {
+    switch (type ())
+      {
+      case record_full_reg:
+	return reg ().execute (regcache);
+      case record_full_mem:
+	return mem ().execute (regcache->arch ());
+      }
+    return false;
+  }
 };
 
 /* This is the main structure that comprises the execution log.
@@ -384,91 +622,12 @@ static struct cmd_list_element *record_full_cmdlist;
 static void record_full_goto_insn (size_t target_insn,
 				   enum exec_direction_kind dir);
 
-/* Initialization and cleanup functions for record_full_reg and
-   record_full_mem entries.  */
-
-/* Init a record_full_reg record entry.  */
-
-static inline record_full_entry
-record_full_reg_init (struct regcache *regcache, int regnum)
-{
-  record_full_entry rec;
-  struct gdbarch *gdbarch = regcache->arch ();
-
-  rec.type = record_full_reg;
-  rec.u.reg.num = regnum;
-  rec.u.reg.len = register_size (gdbarch, regnum);
-  if (rec.u.reg.len > sizeof (rec.u.reg.u.buf))
-    rec.u.reg.u.ptr = (gdb_byte *) xmalloc (rec.u.reg.len);
-
-  return rec;
-}
-
-/* Cleanup a record_full_reg record entry.  */
-
-static inline void
-record_full_reg_cleanup (record_full_entry rec)
-{
-  gdb_assert (rec.type == record_full_reg);
-  if (rec.u.reg.len > sizeof (rec.u.reg.u.buf))
-    xfree (rec.u.reg.u.ptr);
-}
-
-/* Init a record_full_mem record entry.  */
-
-static inline record_full_entry
-record_full_mem_init (CORE_ADDR addr, int len)
-{
-  record_full_entry rec;
-
-  rec.type = record_full_mem;
-  rec.u.mem.addr = addr;
-  rec.u.mem.len = len;
-  if (rec.u.mem.len > sizeof (rec.u.mem.u.buf))
-    rec.u.mem.u.ptr = (gdb_byte *) xmalloc (len);
-  rec.u.mem.mem_entry_not_accessible = 0;
-
-  return rec;
-}
-
-/* Cleanup a record_full_mem record entry.  */
-
-static inline void
-record_full_mem_cleanup (record_full_entry rec)
-{
-  gdb_assert (rec.type == record_full_mem);
-  if (rec.u.mem.len > sizeof (rec.u.mem.u.buf))
-    xfree (rec.u.mem.u.ptr);
-}
-
-/* Free one record entry, any type.  */
-
-static inline void
-record_full_entry_cleanup (record_full_entry rec)
-{
-
-  switch (rec.type) {
-  case record_full_reg:
-    record_full_reg_cleanup (rec);
-    break;
-  case record_full_mem:
-    record_full_mem_cleanup (rec);
-    break;
-  }
-}
-
 static void
 record_full_reset_history ()
 {
   record_full_insn_count = 0;
   record_full_next_insn = 0;
 
-  for (auto &insn : record_full_list)
-    {
-      for (auto &entry : insn.effects)
-	record_full_entry_cleanup (entry);
-    }
-
   record_full_list.clear ();
 }
 
@@ -476,11 +635,7 @@ static void
 record_full_list_release_following (int index)
 {
   for (int i = record_full_list.size () - 1; i > index; i--)
-    {
-      for (auto &entry : record_full_list[i].effects)
-	record_full_entry_cleanup (entry);
-      record_full_list.pop_back ();
-    }
+    record_full_list.pop_back ();
   /* Set the next instruction to be past the end of the log so we
      start recording if the user moves forward again.  */
   record_full_next_insn = index;
@@ -509,9 +664,6 @@ record_full_list_release_first (void)
   if (record_full_list.empty ())
     return;
 
-  for (auto &entry : record_full_list[0].effects)
-    record_full_entry_cleanup (entry);
-
   record_full_list.pop_front ();
   --record_full_next_insn;
 }
@@ -521,28 +673,7 @@ record_full_list_release_first (void)
 static void
 record_full_arch_list_add (record_full_entry &rec)
 {
-  record_full_incomplete_instruction.effects.push_back (rec);
-}
-
-/* Return the value storage location of a record entry.  */
-static inline gdb_byte *
-record_full_get_loc (struct record_full_entry *rec)
-{
-  switch (rec->type) {
-  case record_full_mem:
-    if (rec->u.mem.len > sizeof (rec->u.mem.u.buf))
-      return rec->u.mem.u.ptr;
-    else
-      return rec->u.mem.u.buf;
-  case record_full_reg:
-    if (rec->u.reg.len > sizeof (rec->u.reg.u.buf))
-      return rec->u.reg.u.ptr;
-    else
-      return rec->u.reg.u.buf;
-  default:
-    gdb_assert_not_reached ("unexpected record_full_entry type");
-    return NULL;
-  }
+  record_full_incomplete_instruction.effects.push_back (std::move (rec));
 }
 
 /* Record the value of a register NUM to record_full_arch_list.  */
@@ -550,7 +681,7 @@ record_full_get_loc (struct record_full_entry *rec)
 int
 record_full_arch_list_add_reg (struct regcache *regcache, int regnum)
 {
-  record_full_entry rec;
+  record_full_entry rec (record_full_reg, regcache->arch (), regnum);
 
   if (record_debug > 1)
     gdb_printf (gdb_stdlog,
@@ -558,9 +689,7 @@ record_full_arch_list_add_reg (struct regcache *regcache, int regnum)
 		"record list.\n",
 		regnum);
 
-  rec = record_full_reg_init (regcache, regnum);
-
-  regcache->cooked_read (regnum, record_full_get_loc (&rec));
+  regcache->cooked_read (regnum, rec.get_loc ());
 
   record_full_arch_list_add (rec);
 
@@ -573,7 +702,7 @@ record_full_arch_list_add_reg (struct regcache *regcache, int regnum)
 int
 record_full_arch_list_add_mem (CORE_ADDR addr, int len)
 {
-  record_full_entry rec;
+  record_full_entry rec (record_full_mem, addr, len);
 
   if (record_debug > 1)
     gdb_printf (gdb_stdlog,
@@ -584,14 +713,9 @@ record_full_arch_list_add_mem (CORE_ADDR addr, int len)
   if (!addr)	/* FIXME: Why?  Some arch must permit it...  */
     return 0;
 
-  rec = record_full_mem_init (addr, len);
-
   if (record_read_memory (current_inferior ()->arch (), addr,
-			  record_full_get_loc (&rec), len))
-    {
-      record_full_mem_cleanup (rec);
-      return -1;
-    }
+			  rec.get_loc (), len))
+    return -1;
 
   record_full_arch_list_add (rec);
 
@@ -719,98 +843,14 @@ record_full_gdb_operation_disable_set (void)
 static enum target_stop_reason record_full_stop_reason
   = TARGET_STOPPED_BY_NO_REASON;
 
-/* Execute one instruction from the record log.  Each instruction in
-   the log will be represented by an arbitrary sequence of register
-   entries and memory entries, followed by an 'end' entry.  */
-
-static inline void
-record_full_exec_entry (regcache *regcache,
-			gdbarch *gdbarch,
-			record_full_entry *entry)
-{
-  switch (entry->type)
-    {
-    case record_full_reg: /* reg */
-      {
-	gdb::byte_vector reg (entry->u.reg.len);
-
-	if (record_debug > 1)
-	  gdb_printf (gdb_stdlog,
-		      "Process record: record_full_reg %s to "
-		      "inferior num = %d.\n",
-		      host_address_to_string (entry),
-		      entry->u.reg.num);
-
-	regcache->cooked_read (entry->u.reg.num, reg.data ());
-	regcache->cooked_write (entry->u.reg.num, record_full_get_loc (entry));
-	memcpy (record_full_get_loc (entry), reg.data (), entry->u.reg.len);
-      }
-      break;
-
-    case record_full_mem: /* mem */
-      {
-	/* Nothing to do if the entry is flagged not_accessible.  */
-	if (!entry->u.mem.mem_entry_not_accessible)
-	  {
-	    gdb::byte_vector mem (entry->u.mem.len);
-
-	    if (record_debug > 1)
-	      gdb_printf (gdb_stdlog,
-			  "Process record: record_full_mem %s to "
-			  "inferior addr = %s len = %d.\n",
-			  host_address_to_string (entry),
-			  paddress (gdbarch, entry->u.mem.addr),
-			  entry->u.mem.len);
-
-	    if (record_read_memory (gdbarch,
-				    entry->u.mem.addr, mem.data (),
-				    entry->u.mem.len))
-	      entry->u.mem.mem_entry_not_accessible = 1;
-	    else
-	      {
-		if (target_write_memory (entry->u.mem.addr,
-					 record_full_get_loc (entry),
-					 entry->u.mem.len))
-		  {
-		    entry->u.mem.mem_entry_not_accessible = 1;
-		    if (record_debug)
-		      warning (_("Process record: error writing memory at "
-				 "addr = %s len = %d."),
-			       paddress (gdbarch, entry->u.mem.addr),
-			       entry->u.mem.len);
-		  }
-		else
-		  {
-		    memcpy (record_full_get_loc (entry), mem.data (),
-			    entry->u.mem.len);
-
-		    /* We've changed memory --- check if a hardware
-		       watchpoint should trap.  Note that this
-		       presently assumes the target beneath supports
-		       continuable watchpoints.  On non-continuable
-		       watchpoints target, we'll want to check this
-		       _before_ actually doing the memory change, and
-		       not doing the change at all if the watchpoint
-		       traps.  */
-		    if (hardware_watchpoint_inserted_in_range
-			(current_inferior ()->aspace.get (),
-			 entry->u.mem.addr, entry->u.mem.len))
-		      record_full_stop_reason = TARGET_STOPPED_BY_WATCHPOINT;
-		  }
-	      }
-	  }
-      }
-      break;
-    }
-}
-
 static inline void
 record_full_exec_insn (regcache *regcache,
 		       gdbarch *gdbarch,
 		       record_full_instruction &insn)
 {
   for (auto &entry : insn.effects)
-    record_full_exec_entry (regcache, gdbarch, &entry);
+    if (entry.execute (regcache))
+      record_full_stop_reason = TARGET_STOPPED_BY_WATCHPOINT;
 }
 
 static void record_full_restore (struct bfd &cbfd);
@@ -2185,21 +2225,19 @@ record_full_read_entry_from_bfd (bfd *cbfd, asection *osec, int *bfd_offset)
 	bfdcore_read (cbfd, osec, &regnum, sizeof (regnum), bfd_offset);
 	regnum = netorder32 (regnum);
 
-	record_full_entry rec;
-
-	rec = record_full_reg_init (cache, regnum);
+	record_full_entry rec (record_full_reg, cache->arch (), regnum);
 
 	/* Get val.  */
-	bfdcore_read (cbfd, osec, record_full_get_loc (&rec),
-		      rec.u.reg.len, bfd_offset);
+	bfdcore_read (cbfd, osec, rec.get_loc (),
+		      rec.reg ().len, bfd_offset);
 
 	if (record_debug)
 	  gdb_printf (gdb_stdlog,
 		      "  Reading register %d (1 "
 		      "plus %lu plus %d bytes)\n",
-		      rec.u.reg.num,
+		      rec.reg ().num,
 		      (unsigned long) sizeof (regnum),
-		      rec.u.reg.len);
+		      rec.reg ().len);
 
 	record_full_arch_list_add (rec);
 	break;
@@ -2216,19 +2254,17 @@ record_full_read_entry_from_bfd (bfd *cbfd, asection *osec, int *bfd_offset)
 		      bfd_offset);
 	addr = netorder64 (addr);
 
-	record_full_entry rec;
-	rec = record_full_mem_init (addr, len);
+	record_full_entry rec (record_full_mem, addr, len);
 
 	/* Get val.  */
-	bfdcore_read (cbfd, osec, record_full_get_loc (&rec), len,
-		      bfd_offset);
+	bfdcore_read (cbfd, osec, rec.get_loc (), len, bfd_offset);
 
 	if (record_debug)
 	  gdb_printf (gdb_stdlog,
 		      "  Reading memory %s (1 plus "
 		      "%lu plus %lu plus %d bytes)\n",
 		      paddress (get_current_arch (),
-				rec.u.mem.addr),
+				rec.mem ().addr),
 		      (unsigned long) sizeof (addr),
 		      (unsigned long) sizeof (len),
 		      len);
@@ -2380,52 +2416,56 @@ record_full_write_entry_to_bfd (record_full_entry &entry,
   uint32_t regnum, len;
   uint64_t addr;
 
-  type = entry.type;
+  type = entry.type ();
   bfdcore_write (obfd.get (), osec, &type, sizeof (type), bfd_offset);
 
-  switch (entry.type)
+  switch (type)
     {
     case record_full_reg: /* reg */
-      if (record_debug)
-	gdb_printf (gdb_stdlog,
-		    "  Writing register %d (1 "
-		    "plus %lu plus %d bytes)\n",
-		    entry.u.reg.num,
-		    (unsigned long) sizeof (regnum),
-		    entry.u.reg.len);
-
-      /* Write regnum.  */
-      regnum = netorder32 (entry.u.reg.num);
-      bfdcore_write (obfd.get (), osec, &regnum, sizeof (regnum), bfd_offset);
-
-      /* Write regval.  */
-      bfdcore_write (obfd.get (), osec, record_full_get_loc (&entry),
-		     entry.u.reg.len, bfd_offset);
-      break;
+      {
+	auto &reg = entry.reg ();
+	if (record_debug)
+	  gdb_printf (gdb_stdlog,
+		      "  Writing register %d (1 "
+		      "plus %lu plus %d bytes)\n",
+		      reg.num,
+		      (unsigned long) sizeof (regnum),
+		      reg.len);
+
+	/* Write regnum.  */
+	regnum = netorder32 (reg.num);
+	bfdcore_write (obfd.get (), osec, &regnum, sizeof (regnum), bfd_offset);
+
+	/* Write regval.  */
+	bfdcore_write (obfd.get (), osec, entry.get_loc (), reg.len, bfd_offset);
+	break;
+      }
 
     case record_full_mem: /* mem */
-      if (record_debug)
-	gdb_printf (gdb_stdlog,
-		    "  Writing memory %s (1 plus "
-		    "%lu plus %lu plus %d bytes)\n",
-		    paddress (gdbarch,
-			      entry.u.mem.addr),
-		    (unsigned long) sizeof (addr),
-		    (unsigned long) sizeof (len),
-		    entry.u.mem.len);
-
-      /* Write memlen.  */
-      len = netorder32 (entry.u.mem.len);
-      bfdcore_write (obfd.get (), osec, &len, sizeof (len), bfd_offset);
-
-      /* Write memaddr.  */
-      addr = netorder64 (entry.u.mem.addr);
-      bfdcore_write (obfd.get (), osec, &addr, sizeof (addr), bfd_offset);
-
-      /* Write memval.  */
-      bfdcore_write (obfd.get (), osec, record_full_get_loc (&entry),
-		     entry.u.mem.len, bfd_offset);
-      break;
+      {
+	auto &mem = entry.mem ();
+	if (record_debug)
+	  gdb_printf (gdb_stdlog,
+		      "  Writing memory %s (1 plus "
+		      "%lu plus %lu plus %d bytes)\n",
+		      paddress (gdbarch, mem.addr),
+		      (unsigned long) sizeof (addr),
+		      (unsigned long) sizeof (len),
+		      mem.len);
+
+	/* Write memlen.  */
+	len = netorder32 (mem.len);
+	bfdcore_write (obfd.get (), osec, &len, sizeof (len), bfd_offset);
+
+	/* Write memaddr.  */
+	addr = netorder64 (mem.addr);
+	bfdcore_write (obfd.get (), osec, &addr, sizeof (addr), bfd_offset);
+
+	/* Write memval.  */
+	bfdcore_write (obfd.get (), osec, entry.get_loc (), mem.len,
+		       bfd_offset);
+	break;
+      }
     }
 
 }
@@ -2473,13 +2513,13 @@ record_full_base_target::save_record (const char *recfilename)
       /* Number of effects of an instruction.  */
       save_size += sizeof (uint32_t) + sizeof (uint8_t) + sizeof (uint32_t);
       for (auto &entry : record_full_list[i].effects)
-	switch (entry.type)
+	switch (entry.type ())
 	  {
 	  case record_full_reg:
-	    save_size += 1 + 4 + entry.u.reg.len;
+	    save_size += 1 + 4 + entry.reg ().len;
 	    break;
 	  case record_full_mem:
-	    save_size += 1 + 4 + 8 + entry.u.mem.len;
+	    save_size += 1 + 4 + 8 + entry.mem ().len;
 	    break;
 	  }
     }
@@ -2612,18 +2652,18 @@ maintenance_print_record_instruction (const char *args, int from_tty)
 
   gdbarch *arch = current_inferior ()->arch ();
 
-  for (auto entry : to_print->effects)
+  for (auto &entry : to_print->effects)
     {
-      switch (entry.type)
+      switch (entry.type ())
 	{
 	  case record_full_reg:
 	    {
-	      type *regtype = gdbarch_register_type (arch, entry.u.reg.num);
+	      type *regtype = gdbarch_register_type (arch, entry.reg ().num);
 	      value *val
 		  = value_from_contents (regtype,
-					 record_full_get_loc (&entry));
+					 entry.get_loc ());
 	      gdb_printf ("Register %s changed: ",
-			  gdbarch_register_name (arch, entry.u.reg.num));
+			  gdbarch_register_name (arch, entry.reg ().num));
 	      struct value_print_options opts;
 	      get_user_print_options (&opts);
 	      opts.raw = true;
@@ -2633,11 +2673,12 @@ maintenance_print_record_instruction (const char *args, int from_tty)
 	    }
 	  case record_full_mem:
 	    {
-	      gdb_byte *b = record_full_get_loc (&entry);
+	      record_full_mem_entry& mem = entry.mem ();
+	      gdb_byte *b = entry.get_loc ();
 	      gdb_printf ("%d bytes of memory at address %s changed from:",
-			  entry.u.mem.len,
-			  print_core_address (arch, entry.u.mem.addr));
-	      for (int i = 0; i < entry.u.mem.len; i++)
+			  mem.len,
+			  print_core_address (arch, mem.addr));
+	      for (int i = 0; i < mem.len; i++)
 		gdb_printf (" %02x", b[i]);
 	      gdb_printf ("\n");
 	      break;
-- 
2.54.0


^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v3 4/7] gdb/record: make record_full_history more c++-like
  2026-05-15 16:36 [PATCH v3 0/7] refactor the internals of record-full Guinevere Larsen
                   ` (2 preceding siblings ...)
  2026-05-15 16:37 ` [PATCH v3 3/7] gdb/record: c++ify internal structures of record-full.c Guinevere Larsen
@ 2026-05-15 16:37 ` Guinevere Larsen
  2026-05-16  1:03   ` Thiago Jung Bauermann
  2026-05-26  9:04   ` Schimpe, Christina
  2026-05-15 16:37 ` [PATCH v3 5/6] gdb/record: Define new version of the record-save section Guinevere Larsen
                   ` (5 subsequent siblings)
  9 siblings, 2 replies; 27+ messages in thread
From: Guinevere Larsen @ 2026-05-15 16:37 UTC (permalink / raw)
  To: gdb-patches; +Cc: Guinevere Larsen

Move some functions to being a method of record_full_history, instead of
floating functions.
---
 gdb/record-full.c | 25 +++++++++++--------------
 1 file changed, 11 insertions(+), 14 deletions(-)

diff --git a/gdb/record-full.c b/gdb/record-full.c
index 24e94eaf5da..8cabd6e9438 100644
--- a/gdb/record-full.c
+++ b/gdb/record-full.c
@@ -388,6 +388,10 @@ struct record_full_instruction
   uint32_t insn_num;
   std::optional<gdb_signal> sigval;
   std::vector<record_full_entry> effects;
+
+  /* Execute the full instruction.  As a side effect, set
+     record_full_stop_reason.  */
+  void exec_insn (regcache *regcache);
 };
 
 /* If true, query if PREC cannot record memory
@@ -843,12 +847,9 @@ record_full_gdb_operation_disable_set (void)
 static enum target_stop_reason record_full_stop_reason
   = TARGET_STOPPED_BY_NO_REASON;
 
-static inline void
-record_full_exec_insn (regcache *regcache,
-		       gdbarch *gdbarch,
-		       record_full_instruction &insn)
+void record_full_instruction::exec_insn (regcache *regcache)
 {
-  for (auto &entry : insn.effects)
+  for (auto &entry : effects)
     if (entry.execute (regcache))
       record_full_stop_reason = TARGET_STOPPED_BY_WATCHPOINT;
 }
@@ -1308,9 +1309,7 @@ record_full_wait_1 (struct target_ops *ops,
 		  break;
 		}
 
-	      record_full_exec_insn
-		(regcache, gdbarch,
-		 record_full_list[record_full_next_insn]);
+	      record_full_list[record_full_next_insn].exec_insn (regcache);
 
 	      /* step */
 	      if (record_full_resume_step)
@@ -2503,8 +2502,7 @@ record_full_base_target::save_record (const char *recfilename)
 
   /* Reverse execute to the begin of record list.  */
   for (int i = record_full_next_insn - 1; i >= 0; i--)
-    record_full_exec_insn (regcache, gdbarch,
-			   record_full_list[i]);
+    record_full_list[i].exec_insn (regcache);
 
   /* Compute the size needed for the extra bfd section.  */
   save_size = 4;	/* magic cookie */
@@ -2576,7 +2574,7 @@ record_full_base_target::save_record (const char *recfilename)
 	}
       /* Execute entry.  */
       if (i < record_full_next_insn)
-	record_full_exec_insn (regcache, gdbarch, record_full_list[i]);
+	record_full_list[i].exec_insn (regcache);
     }
 
   unlink_file.keep ();
@@ -2597,17 +2595,16 @@ record_full_goto_insn (size_t target_insn,
   scoped_restore restore_operation_disable
     = record_full_gdb_operation_disable_set ();
   regcache *regcache = get_thread_regcache (inferior_thread ());
-  struct gdbarch *gdbarch = regcache->arch ();
 
   /* Assume everything is valid: we will hit the entry,
      and we will not hit the end of the recording.  */
 
   if (dir == EXEC_REVERSE)
     for (int i = record_full_next_insn; i > target_insn; i--)
-      record_full_exec_insn (regcache, gdbarch, record_full_list[i - 1]);
+      record_full_list[i-1].exec_insn (regcache);
   else
     for (int i = record_full_next_insn; i < target_insn; i++)
-      record_full_exec_insn (regcache, gdbarch, record_full_list[i]);
+      record_full_list[i].exec_insn (regcache);
 }
 
 /* Alias for "target record-full".  */
-- 
2.54.0


^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v3 5/6] gdb/record: Define new version of the record-save section
  2026-05-15 16:36 [PATCH v3 0/7] refactor the internals of record-full Guinevere Larsen
                   ` (3 preceding siblings ...)
  2026-05-15 16:37 ` [PATCH v3 4/7] gdb/record: make record_full_history more c++-like Guinevere Larsen
@ 2026-05-15 16:37 ` Guinevere Larsen
  2026-05-15 16:37 ` [PATCH v3 5/7] gdb/record: extract the PC to record_full_instruction Guinevere Larsen
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 27+ messages in thread
From: Guinevere Larsen @ 2026-05-15 16:37 UTC (permalink / raw)
  To: gdb-patches; +Cc: Guinevere Larsen, Thiago Jung Bauermann, Eli Zaretskii

With the changes to the internal representation of the history, we can
no longer support the previous record save format. This commit makes it
official, documenting the new format and changing the magic number.

Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
---
 gdb/NEWS          |  4 ++++
 gdb/record-full.c | 35 +++++++++++++++++++++++++++++++----
 2 files changed, 35 insertions(+), 4 deletions(-)

diff --git a/gdb/NEWS b/gdb/NEWS
index 48a80856aa1..c06e83bb1ba 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -51,6 +51,10 @@
 
 * Support for the binary file format dbx has been removed.
 
+* The format for a saved execution record, created when the command
+  'record save' is used, has been updated, and previous formats are
+  no longer supported.
+
 * When connected to an extended-remote target GDB can now
   automatically set the 'remote exec-file' in some cases.  GDB will
   auto set the remote exec-file only if the remote wasn't started with
diff --git a/gdb/record-full.c b/gdb/record-full.c
index 17696e07c0d..055d2c66346 100644
--- a/gdb/record-full.c
+++ b/gdb/record-full.c
@@ -79,7 +79,8 @@
   ((record_full_next_insn != record_full_list.size ()) \
     || ::execution_direction == EXEC_REVERSE)
 
-#define RECORD_FULL_FILE_MAGIC	netorder32(0x20091016)
+#define RECORD_FULL_FILE_MAGIC_OLD	netorder32(0x20091016)
+#define RECORD_FULL_FILE_MAGIC	netorder32(0x20260415)
 
 /* These are the core structs of the process record functionality.
 
@@ -2205,6 +2206,27 @@ record_full_core_target::has_execution (inferior *inf)
        8 bytes: memory address (network byte order).
        n bytes: memory value (n == memory length).
 
+   Version 3 (all numbers are in network order).
+     4 bytes: Magic number (0x20260415).
+       NOTE: be sure to change whenever this file format changes!
+
+    Records:
+      record_full_instruction:
+	1 byte: signal.
+	4 bytes: number of reg and mem entries for this instruction.
+	4 bytes: instruction sequence number.
+	4 bytes: PC register ID.
+	N bytes: PC address of instruction (N == size of PC).
+	Effects:
+	  record_full_reg:
+	    1 byte: record_type (record_full_reg, see enum record_full_type).
+	    4 bytes: Register ID.
+	    n bytes: register value (n == actual register size).
+	  record_full_mem:
+	    1 byte: record_type (record_full_mem, see enum record_full_type).
+	    4 bytes: memory length.
+	    8 bytes: memory address.
+	    n bytes: memory value (n = memory length).
 */
 
 /* bfdcore_read -- read bytes from a core file section.  */
@@ -2400,9 +2422,14 @@ record_full_restore (struct bfd &cbfd)
   /* Check the magic code.  */
   bfdcore_read (&cbfd, osec, &magic, sizeof (magic), &bfd_offset);
   if (magic != RECORD_FULL_FILE_MAGIC)
-    error (_("Version mismatch or file format error in core file %ps."),
-	   styled_string (file_name_style.style (),
-			  bfd_get_filename (&cbfd)));
+    {
+      if (magic == RECORD_FULL_FILE_MAGIC_OLD)
+	error (_("This old recording format is no longer supported."));
+      else
+	error (_("Version mismatch or file format error in core file %ps."),
+	       styled_string (file_name_style.style (),
+			      bfd_get_filename (&cbfd)));
+    }
   if (record_debug)
     gdb_printf (gdb_stdlog,
 		"  Reading 4-byte magic cookie "
-- 
2.54.0


^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v3 5/7] gdb/record: extract the PC to record_full_instruction
  2026-05-15 16:36 [PATCH v3 0/7] refactor the internals of record-full Guinevere Larsen
                   ` (4 preceding siblings ...)
  2026-05-15 16:37 ` [PATCH v3 5/6] gdb/record: Define new version of the record-save section Guinevere Larsen
@ 2026-05-15 16:37 ` Guinevere Larsen
  2026-05-16  1:04   ` Thiago Jung Bauermann
  2026-05-29 12:40   ` Schimpe, Christina
  2026-05-15 16:37 ` [PATCH v3 6/7] gdb/record: Define new version of the record-save section Guinevere Larsen
                   ` (3 subsequent siblings)
  9 siblings, 2 replies; 27+ messages in thread
From: Guinevere Larsen @ 2026-05-15 16:37 UTC (permalink / raw)
  To: gdb-patches; +Cc: Guinevere Larsen

This commit makes it so the PC is not saved as part of the
record_full_instruction effects, but rather gets a special location.
That is because a couple of commands would really benefit from it being
easy to find the PC (especially ones from record-btrace that's haven't
been implemented to record-full yet, such as the ones in PR
record/18059), while also possibly allowing for one fewer resizing of
the effect vector (and saving an entire byte in the process).

This commit also refactored record_full_read_entry_from_bfd and
record_full_write_entry_to_bfd, to make them methods of
record_full_reg_entry and record_full_mem_entry, and also creates
similar methods for record_full_entry and record_full_instruction.
These could be turned into constructors in a future step of
c++ification, but it felt like too much change for a single commit.
---
 gdb/record-full.c | 386 ++++++++++++++++++++++++++++------------------
 1 file changed, 239 insertions(+), 147 deletions(-)

diff --git a/gdb/record-full.c b/gdb/record-full.c
index 8cabd6e9438..17696e07c0d 100644
--- a/gdb/record-full.c
+++ b/gdb/record-full.c
@@ -143,6 +143,14 @@ struct record_full_mem_entry
 
   DISABLE_COPY_AND_ASSIGN (record_full_mem_entry);
 
+  /* Create a mem_entry from a bfd file, when restoring a recording.  */
+  static record_full_mem_entry from_bfd (bfd *cbfd, asection* osec,
+					 int *bfd_offset);
+
+  /* Save this mem entry to a bfd file.  */
+  void to_bfd (gdb_bfd_ref_ptr obfd, asection *osec, int *bfd_offset,
+	       gdbarch *gdbarch);
+
   gdb_byte *get_loc ()
   {
     if (len > sizeof (u.buf))
@@ -252,6 +260,14 @@ struct record_full_reg_entry
 
   DISABLE_COPY_AND_ASSIGN (record_full_reg_entry);
 
+  /* Create a reg_entry from a bfd file, when restoring a recording.  */
+  static record_full_reg_entry from_bfd (bfd *cbfd, asection* osec,
+					 int *bfd_offset);
+
+  /* Save this reg entry to a bfd file.  */
+  void to_bfd (gdb_bfd_ref_ptr obfd, asection *osec, int *bfd_offset,
+	       gdbarch *gdbarch);
+
   gdb_byte *get_loc ()
   {
     if (len > sizeof (u.buf))
@@ -314,6 +330,13 @@ class record_full_entry
 
   DISABLE_COPY_AND_ASSIGN (record_full_entry);
 
+  /* Create a generic entry from a bfd file, when restoring a recording.  */
+  static void from_bfd (bfd *cbfd, asection* osec, int *bfd_offset);
+
+  /* Save this entry to a bfd file.  */
+  void to_bfd (gdb_bfd_ref_ptr obfd, asection *osec, int *bfd_offset,
+	       gdbarch *gdbarch);
+
   record_full_reg_entry& reg ()
   {
     gdb_assert (type () == record_full_reg);
@@ -374,6 +397,7 @@ class record_full_entry
      this one;
    * sigval: Whether the inferior received a signal while the following
      instruction was being recorded;
+   * pc: The Program Counter for where the instruction starts;
    * effects: A list of record_full_entry structures, each of which
      describing one effect that the instruction has on the inferior.
 
@@ -388,10 +412,18 @@ struct record_full_instruction
   uint32_t insn_num;
   std::optional<gdb_signal> sigval;
   std::vector<record_full_entry> effects;
+  record_full_reg_entry pc;
 
   /* Execute the full instruction.  As a side effect, set
      record_full_stop_reason.  */
   void exec_insn (regcache *regcache);
+
+  /* Create a full recorded instruction from a bfd.  */
+  static void from_bfd (bfd *cbfd, asection* osec, int *bfd_offset);
+
+  /* Save this instruction to a bfd file.  */
+  void to_bfd (gdb_bfd_ref_ptr obfd, asection *osec, int *bfd_offset,
+	       gdbarch *gdbarch);
 };
 
 /* If true, query if PREC cannot record memory
@@ -695,7 +727,10 @@ record_full_arch_list_add_reg (struct regcache *regcache, int regnum)
 
   regcache->cooked_read (regnum, rec.get_loc ());
 
-  record_full_arch_list_add (rec);
+  if (regnum == gdbarch_pc_regnum (regcache->arch ()))
+      record_full_incomplete_instruction.pc = std::move (rec.reg ());
+  else
+    record_full_arch_list_add (rec);
 
   return 0;
 }
@@ -849,6 +884,7 @@ static enum target_stop_reason record_full_stop_reason
 
 void record_full_instruction::exec_insn (regcache *regcache)
 {
+  pc.execute (regcache);
   for (auto &entry : effects)
     if (entry.execute (regcache))
       record_full_stop_reason = TARGET_STOPPED_BY_WATCHPOINT;
@@ -2207,68 +2243,88 @@ netorder32 (uint32_t input)
   return ret;
 }
 
-static void
-record_full_read_entry_from_bfd (bfd *cbfd, asection *osec, int *bfd_offset)
+record_full_reg_entry
+record_full_reg_entry::from_bfd (bfd *cbfd, asection *osec, int *bfd_offset)
 {
-  uint8_t rectype;
-  uint32_t regnum, len;
-  uint64_t addr;
+  uint32_t regnum;
   regcache *cache = get_thread_regcache (inferior_thread ());
 
+  /* Get register number to regnum.  */
+  bfdcore_read (cbfd, osec, &regnum, sizeof (regnum),
+		bfd_offset);
+  regnum = netorder32 (regnum);
+
+  record_full_reg_entry reg (cache->arch (), regnum);
+
+  /* Get val.  */
+  bfdcore_read (cbfd, osec, reg.get_loc (),
+		reg.len, bfd_offset);
+
+  if (record_debug)
+    gdb_printf (gdb_stdlog,
+		"  Reading register %d (1 "
+		"plus %lu plus %d bytes)\n",
+		reg.num,
+		(unsigned long) sizeof (regnum),
+		reg.len);
+  return reg;
+
+}
+
+record_full_mem_entry
+record_full_mem_entry::from_bfd (bfd *cbfd, asection *osec, int *bfd_offset)
+{
+  uint32_t len;
+  uint64_t addr;
+
+  /* Get len.  */
+  bfdcore_read (cbfd, osec, &len, sizeof (len), bfd_offset);
+  len = netorder32 (len);
+
+  /* Get addr.  */
+  bfdcore_read (cbfd, osec, &addr, sizeof (addr),
+		bfd_offset);
+  addr = netorder64 (addr);
+
+  record_full_mem_entry mem (addr, len);
+
+  /* Get val.  */
+  bfdcore_read (cbfd, osec, mem.get_loc (),
+		len, bfd_offset);
+
+  if (record_debug)
+    gdb_printf (gdb_stdlog,
+		"  Reading memory %s (1 plus "
+		"%lu plus %lu plus %d bytes)\n",
+		paddress (get_current_arch (),
+			  mem.addr),
+		(unsigned long) sizeof (addr),
+		(unsigned long) sizeof (len),
+		len);
+
+  return mem;
+}
+
+void
+record_full_entry::from_bfd (bfd *cbfd, asection *osec, int *bfd_offset)
+{
+  uint8_t rectype;
+  record_full_entry rec;
+
   bfdcore_read (cbfd, osec, &rectype, sizeof (rectype), bfd_offset);
   switch (rectype)
     {
     case record_full_reg: /* reg */
       {
-	/* Get register number to regnum.  */
-	bfdcore_read (cbfd, osec, &regnum, sizeof (regnum), bfd_offset);
-	regnum = netorder32 (regnum);
-
-	record_full_entry rec (record_full_reg, cache->arch (), regnum);
-
-	/* Get val.  */
-	bfdcore_read (cbfd, osec, rec.get_loc (),
-		      rec.reg ().len, bfd_offset);
-
-	if (record_debug)
-	  gdb_printf (gdb_stdlog,
-		      "  Reading register %d (1 "
-		      "plus %lu plus %d bytes)\n",
-		      rec.reg ().num,
-		      (unsigned long) sizeof (regnum),
-		      rec.reg ().len);
-
-	record_full_arch_list_add (rec);
+	rec.entry = std::move (record_full_reg_entry::from_bfd
+				(cbfd, osec, bfd_offset));
 	break;
       }
 
     case record_full_mem: /* mem */
       {
-	/* Get len.  */
-	bfdcore_read (cbfd, osec, &len, sizeof (len), bfd_offset);
-	len = netorder32 (len);
-
-	/* Get addr.  */
-	bfdcore_read (cbfd, osec, &addr, sizeof (addr),
-		      bfd_offset);
-	addr = netorder64 (addr);
-
-	record_full_entry rec (record_full_mem, addr, len);
-
-	/* Get val.  */
-	bfdcore_read (cbfd, osec, rec.get_loc (), len, bfd_offset);
-
-	if (record_debug)
-	  gdb_printf (gdb_stdlog,
-		      "  Reading memory %s (1 plus "
-		      "%lu plus %lu plus %d bytes)\n",
-		      paddress (get_current_arch (),
-				rec.mem ().addr),
-		      (unsigned long) sizeof (addr),
-		      (unsigned long) sizeof (len),
-		      len);
-
-	record_full_arch_list_add (rec);
+	rec.entry = std::move (record_full_mem_entry::from_bfd
+				(cbfd, osec, bfd_offset));
 	break;
       }
 
@@ -2279,6 +2335,39 @@ record_full_read_entry_from_bfd (bfd *cbfd, asection *osec, int *bfd_offset)
 			    bfd_get_filename (cbfd)));
       break;
     }
+  record_full_arch_list_add (rec);
+}
+
+void
+record_full_instruction::from_bfd (bfd *cbfd, asection *osec, int *bfd_offset)
+{
+  uint32_t eff_count = 0;
+  uint8_t sigval;
+  uint32_t insn_num;
+
+  /* First read the generic information for an instruction.  */
+  bfdcore_read (cbfd, osec, &sigval, sizeof (uint8_t), bfd_offset);
+  bfdcore_read (cbfd, osec, &eff_count, sizeof (uint32_t),
+		bfd_offset);
+  bfdcore_read (cbfd, osec, &insn_num, sizeof (uint32_t),
+		bfd_offset);
+
+  record_full_incomplete_instruction.insn_num = netorder32 (insn_num);
+  if (sigval != GDB_SIGNAL_0)
+    record_full_incomplete_instruction.sigval = (gdb_signal) sigval;
+
+  record_full_incomplete_instruction.pc
+    = record_full_reg_entry::from_bfd (cbfd, osec, bfd_offset);
+
+  eff_count = netorder32 (eff_count);
+
+  /* This deals with all the side effects.  */
+ while (eff_count > 0)
+    {
+      eff_count--;
+
+      record_full_entry::from_bfd (cbfd, osec, bfd_offset);
+    }
 }
 
 /* Restore the execution log from core file CBFD.  */
@@ -2325,30 +2414,8 @@ record_full_restore (struct bfd &cbfd)
       while (bfd_offset < osec_size)
 	{
 	  record_full_reset_incomplete ();
-	  uint32_t eff_count = 0;
-	  uint8_t sigval;
-	  uint32_t insn_num;
-
-	  /* First read the generic information for an instruction.  */
-	  bfdcore_read (&cbfd, osec, &sigval, sizeof (uint8_t), &bfd_offset);
-	  bfdcore_read (&cbfd, osec, &eff_count, sizeof (uint32_t),
-			&bfd_offset);
-	  bfdcore_read (&cbfd, osec, &insn_num, sizeof (uint32_t),
-			&bfd_offset);
-
-	  record_full_incomplete_instruction.insn_num = netorder32 (insn_num);
-	  if (sigval != GDB_SIGNAL_0)
-	    record_full_incomplete_instruction.sigval = (gdb_signal) sigval;
 
-	  eff_count = netorder32 (eff_count);
-
-	  /* This deals with all the side effects.  */
-	 while (eff_count > 0)
-	   {
-	     eff_count--;
-
-	     record_full_read_entry_from_bfd (&cbfd, osec, &bfd_offset);
-	   }
+	  record_full_instruction::from_bfd (&cbfd, osec, &bfd_offset);
 
 	  record_full_save_instruction ();
 	}
@@ -2404,71 +2471,109 @@ cmd_record_full_restore (const char *args, int from_tty)
   record_full_open (nullptr, from_tty);
 }
 
-static void
-record_full_write_entry_to_bfd (record_full_entry &entry,
-				gdb_bfd_ref_ptr obfd,
-				asection *osec, int *bfd_offset,
-				gdbarch *gdbarch)
+void
+record_full_reg_entry::to_bfd (gdb_bfd_ref_ptr obfd,
+			       asection *osec, int *bfd_offset,
+			       gdbarch *gdbarch)
+{
+  uint32_t regnum;
+
+  if (record_debug)
+    gdb_printf (gdb_stdlog,
+		"  Writing register %d (1 "
+		"plus %lu plus %d bytes)\n",
+		this->num,
+		(unsigned long) sizeof (regnum),
+		this->len);
+
+  /* Write regnum.  */
+  regnum = netorder32 (this->num);
+  bfdcore_write (obfd.get (), osec, &regnum, sizeof (regnum), bfd_offset);
+
+  /* Write regval.  */
+  bfdcore_write (obfd.get (), osec, this->get_loc (), this->len, bfd_offset);
+}
+
+void
+record_full_mem_entry::to_bfd (gdb_bfd_ref_ptr obfd,
+			       asection *osec, int *bfd_offset,
+			       gdbarch *gdbarch)
+{
+  uint32_t len;
+  uint64_t addr;
+
+  if (record_debug)
+    gdb_printf (gdb_stdlog,
+		"  Writing memory %s (1 plus "
+		"%lu plus %lu plus %d bytes)\n",
+		paddress (gdbarch, this->addr),
+		(unsigned long) sizeof (addr),
+		(unsigned long) sizeof (len),
+		this->len);
+
+  /* Write memlen.  */
+  len = netorder32 (this->len);
+  bfdcore_write (obfd.get (), osec, &len, sizeof (len), bfd_offset);
+
+  /* Write memaddr.  */
+  addr = netorder64 (this->addr);
+  bfdcore_write (obfd.get (), osec, &addr, sizeof (addr), bfd_offset);
+
+  /* Write memval.  */
+  bfdcore_write (obfd.get (), osec, this->get_loc (), this->len, bfd_offset);
+}
+
+void
+record_full_entry::to_bfd (gdb_bfd_ref_ptr obfd, asection *osec,
+			   int *bfd_offset, gdbarch *gdbarch)
 {
   /* Save entry.  */
   uint8_t type;
-  uint32_t regnum, len;
-  uint64_t addr;
 
-  type = entry.type ();
+  type = this->type ();
   bfdcore_write (obfd.get (), osec, &type, sizeof (type), bfd_offset);
 
   switch (type)
     {
     case record_full_reg: /* reg */
-      {
-	auto &reg = entry.reg ();
-	if (record_debug)
-	  gdb_printf (gdb_stdlog,
-		      "  Writing register %d (1 "
-		      "plus %lu plus %d bytes)\n",
-		      reg.num,
-		      (unsigned long) sizeof (regnum),
-		      reg.len);
-
-	/* Write regnum.  */
-	regnum = netorder32 (reg.num);
-	bfdcore_write (obfd.get (), osec, &regnum, sizeof (regnum), bfd_offset);
-
-	/* Write regval.  */
-	bfdcore_write (obfd.get (), osec, entry.get_loc (), reg.len, bfd_offset);
-	break;
-      }
+      reg ().to_bfd (obfd, osec, bfd_offset, gdbarch);
+      break;
 
     case record_full_mem: /* mem */
-      {
-	auto &mem = entry.mem ();
-	if (record_debug)
-	  gdb_printf (gdb_stdlog,
-		      "  Writing memory %s (1 plus "
-		      "%lu plus %lu plus %d bytes)\n",
-		      paddress (gdbarch, mem.addr),
-		      (unsigned long) sizeof (addr),
-		      (unsigned long) sizeof (len),
-		      mem.len);
-
-	/* Write memlen.  */
-	len = netorder32 (mem.len);
-	bfdcore_write (obfd.get (), osec, &len, sizeof (len), bfd_offset);
-
-	/* Write memaddr.  */
-	addr = netorder64 (mem.addr);
-	bfdcore_write (obfd.get (), osec, &addr, sizeof (addr), bfd_offset);
-
-	/* Write memval.  */
-	bfdcore_write (obfd.get (), osec, entry.get_loc (), mem.len,
-		       bfd_offset);
-	break;
-      }
+      mem ().to_bfd (obfd, osec, bfd_offset, gdbarch);
+      break;
     }
 
 }
 
+void
+record_full_instruction::to_bfd (gdb_bfd_ref_ptr obfd, asection *osec,
+				 int *bfd_offset, gdbarch *gdbarch)
+{
+  uint32_t eff_count = (uint32_t) this->effects.size ();
+  uint32_t insn_num = this->insn_num;
+  uint8_t sigval = (this->sigval.has_value ())
+		     ? this->sigval.value ()
+		     : GDB_SIGNAL_0;
+
+  /* Signal.  */
+  bfdcore_write (obfd.get (), osec, &sigval, sizeof (sigval), bfd_offset);
+  eff_count = netorder32 (eff_count);
+  /* Number of effects.  */
+  bfdcore_write (obfd.get (), osec, &eff_count, sizeof (eff_count),
+		 bfd_offset);
+  /* Instruction number.  */
+  bfdcore_write (obfd.get (), osec, &insn_num, sizeof (uint32_t),
+		 bfd_offset);
+
+  this->pc.to_bfd (obfd, osec, bfd_offset, gdbarch);
+
+  for (auto &entry : this->effects)
+    {
+      entry.to_bfd (obfd, osec, bfd_offset, gdbarch);
+    }
+}
+
 /* Save the execution log to a file.  We use a modified elf corefile
    format, with an extra section for our data.  */
 
@@ -2510,6 +2615,7 @@ record_full_base_target::save_record (const char *recfilename)
     {
       /* Number of effects of an instruction.  */
       save_size += sizeof (uint32_t) + sizeof (uint8_t) + sizeof (uint32_t);
+      save_size += 4 + record_full_list[i].pc.len;
       for (auto &entry : record_full_list[i].effects)
 	switch (entry.type ())
 	  {
@@ -2551,27 +2657,7 @@ record_full_base_target::save_record (const char *recfilename)
      record list.  */
   for (int i = 0; i < record_full_list.size (); i++)
     {
-      uint32_t eff_count = (uint32_t) record_full_list[i].effects.size ();
-      uint32_t insn_num = record_full_list[i].insn_num;
-      uint8_t sigval = (record_full_list[i].sigval.has_value ())
-			 ? record_full_list[i].sigval.value ()
-			 : GDB_SIGNAL_0;
-
-      /* Signal.  */
-      bfdcore_write (obfd.get (), osec, &sigval, sizeof (sigval), &bfd_offset);
-      eff_count = netorder32 (eff_count);
-      /* Number of effects.  */
-      bfdcore_write (obfd.get (), osec, &eff_count, sizeof (eff_count),
-		     &bfd_offset);
-      /* Instruction number.  */
-      bfdcore_write (obfd.get (), osec, &insn_num, sizeof (uint32_t),
-		     &bfd_offset);
-
-      for (auto &entry : record_full_list[i].effects)
-	{
-	  record_full_write_entry_to_bfd (entry, obfd, osec, &bfd_offset,
-					  gdbarch);
-	}
+      record_full_list[i].to_bfd (obfd, osec, &bfd_offset, gdbarch);
       /* Execute entry.  */
       if (i < record_full_next_insn)
 	record_full_list[i].exec_insn (regcache);
@@ -2648,6 +2734,9 @@ maintenance_print_record_instruction (const char *args, int from_tty)
   auto to_print = record_full_list.begin () + offset;
 
   gdbarch *arch = current_inferior ()->arch ();
+  struct value_print_options opts;
+  get_user_print_options (&opts);
+  opts.raw = true;
 
   for (auto &entry : to_print->effects)
     {
@@ -2661,9 +2750,6 @@ maintenance_print_record_instruction (const char *args, int from_tty)
 					 entry.get_loc ());
 	      gdb_printf ("Register %s changed: ",
 			  gdbarch_register_name (arch, entry.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;
@@ -2682,6 +2768,12 @@ maintenance_print_record_instruction (const char *args, int from_tty)
 	    }
 	}
     }
+  type *regtype = gdbarch_register_type (arch, to_print->pc.num);
+  value *val = value_from_contents (regtype, to_print->pc.get_loc ());
+  gdb_printf ("Register %s changed: ",
+	      gdbarch_register_name (arch, to_print->pc.num));
+  value_print (val, gdb_stdout, &opts);
+  gdb_printf ("\n");
 }
 
 INIT_GDB_FILE (record_full)
-- 
2.54.0


^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v3 6/7] gdb/record: Define new version of the record-save section
  2026-05-15 16:36 [PATCH v3 0/7] refactor the internals of record-full Guinevere Larsen
                   ` (5 preceding siblings ...)
  2026-05-15 16:37 ` [PATCH v3 5/7] gdb/record: extract the PC to record_full_instruction Guinevere Larsen
@ 2026-05-15 16:37 ` Guinevere Larsen
  2026-05-15 16:37 ` [PATCH v3 6/6] gdb/record: rename record_full_list to record_full_log Guinevere Larsen
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 27+ messages in thread
From: Guinevere Larsen @ 2026-05-15 16:37 UTC (permalink / raw)
  To: gdb-patches; +Cc: Guinevere Larsen, Thiago Jung Bauermann, Eli Zaretskii

With the changes to the internal representation of the history, we can
no longer support the previous record save format. This commit makes it
official, documenting the new format and changing the magic number.

Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
---
 gdb/NEWS          |  4 ++++
 gdb/record-full.c | 35 +++++++++++++++++++++++++++++++----
 2 files changed, 35 insertions(+), 4 deletions(-)

diff --git a/gdb/NEWS b/gdb/NEWS
index 48a80856aa1..c06e83bb1ba 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -51,6 +51,10 @@
 
 * Support for the binary file format dbx has been removed.
 
+* The format for a saved execution record, created when the command
+  'record save' is used, has been updated, and previous formats are
+  no longer supported.
+
 * When connected to an extended-remote target GDB can now
   automatically set the 'remote exec-file' in some cases.  GDB will
   auto set the remote exec-file only if the remote wasn't started with
diff --git a/gdb/record-full.c b/gdb/record-full.c
index 17696e07c0d..055d2c66346 100644
--- a/gdb/record-full.c
+++ b/gdb/record-full.c
@@ -79,7 +79,8 @@
   ((record_full_next_insn != record_full_list.size ()) \
     || ::execution_direction == EXEC_REVERSE)
 
-#define RECORD_FULL_FILE_MAGIC	netorder32(0x20091016)
+#define RECORD_FULL_FILE_MAGIC_OLD	netorder32(0x20091016)
+#define RECORD_FULL_FILE_MAGIC	netorder32(0x20260415)
 
 /* These are the core structs of the process record functionality.
 
@@ -2205,6 +2206,27 @@ record_full_core_target::has_execution (inferior *inf)
        8 bytes: memory address (network byte order).
        n bytes: memory value (n == memory length).
 
+   Version 3 (all numbers are in network order).
+     4 bytes: Magic number (0x20260415).
+       NOTE: be sure to change whenever this file format changes!
+
+    Records:
+      record_full_instruction:
+	1 byte: signal.
+	4 bytes: number of reg and mem entries for this instruction.
+	4 bytes: instruction sequence number.
+	4 bytes: PC register ID.
+	N bytes: PC address of instruction (N == size of PC).
+	Effects:
+	  record_full_reg:
+	    1 byte: record_type (record_full_reg, see enum record_full_type).
+	    4 bytes: Register ID.
+	    n bytes: register value (n == actual register size).
+	  record_full_mem:
+	    1 byte: record_type (record_full_mem, see enum record_full_type).
+	    4 bytes: memory length.
+	    8 bytes: memory address.
+	    n bytes: memory value (n = memory length).
 */
 
 /* bfdcore_read -- read bytes from a core file section.  */
@@ -2400,9 +2422,14 @@ record_full_restore (struct bfd &cbfd)
   /* Check the magic code.  */
   bfdcore_read (&cbfd, osec, &magic, sizeof (magic), &bfd_offset);
   if (magic != RECORD_FULL_FILE_MAGIC)
-    error (_("Version mismatch or file format error in core file %ps."),
-	   styled_string (file_name_style.style (),
-			  bfd_get_filename (&cbfd)));
+    {
+      if (magic == RECORD_FULL_FILE_MAGIC_OLD)
+	error (_("This old recording format is no longer supported."));
+      else
+	error (_("Version mismatch or file format error in core file %ps."),
+	       styled_string (file_name_style.style (),
+			      bfd_get_filename (&cbfd)));
+    }
   if (record_debug)
     gdb_printf (gdb_stdlog,
 		"  Reading 4-byte magic cookie "
-- 
2.54.0


^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v3 6/6] gdb/record: rename record_full_list to record_full_log
  2026-05-15 16:36 [PATCH v3 0/7] refactor the internals of record-full Guinevere Larsen
                   ` (6 preceding siblings ...)
  2026-05-15 16:37 ` [PATCH v3 6/7] gdb/record: Define new version of the record-save section Guinevere Larsen
@ 2026-05-15 16:37 ` Guinevere Larsen
  2026-05-29 12:05   ` Schimpe, Christina
  2026-05-15 16:37 ` [PATCH v3 7/7] " Guinevere Larsen
  2026-05-15 17:45 ` [PATCH v3 0/7] refactor the internals of record-full Guinevere Larsen
  9 siblings, 1 reply; 27+ messages in thread
From: Guinevere Larsen @ 2026-05-15 16:37 UTC (permalink / raw)
  To: gdb-patches; +Cc: Guinevere Larsen

Now that we no longer use a linked list to manage the history, the name
"record_full_list" is no longer meaningful. This commit changes the name
to record_full_log since it is the execution log. I decided to not say
"record_full_execution_log" to avoid overly long lines when accessing
it.
---
 gdb/record-full.c | 120 +++++++++++++++++++++++-----------------------
 1 file changed, 60 insertions(+), 60 deletions(-)

diff --git a/gdb/record-full.c b/gdb/record-full.c
index 055d2c66346..3c63753d24e 100644
--- a/gdb/record-full.c
+++ b/gdb/record-full.c
@@ -76,7 +76,7 @@
 #define DEFAULT_RECORD_FULL_INSN_MAX_NUM	200000
 
 #define RECORD_FULL_IS_REPLAY \
-  ((record_full_next_insn != record_full_list.size ()) \
+  ((record_full_next_insn != record_full_log.size ()) \
     || ::execution_direction == EXEC_REVERSE)
 
 #define RECORD_FULL_FILE_MAGIC_OLD	netorder32(0x20091016)
@@ -446,7 +446,7 @@ static struct record_full_core_buf_entry *record_full_core_buf_list = NULL;
 /* The following variables are used for managing the history of executed
    instructions from the inferior.
 
-   record_full_list contains all instructions that were fully executed and
+   record_full_log contains all instructions that were fully executed and
    saved to the log, so that we can replay the execution.
 
    record_full_next_insn always points to the next instruction that would
@@ -459,7 +459,7 @@ static struct record_full_core_buf_entry *record_full_core_buf_list = NULL;
    happening.  It is manipulated by the "arch list" functions for historical
    reasons.  */
 
-static std::deque<record_full_instruction> record_full_list;
+static std::deque<record_full_instruction> record_full_log;
 static record_full_instruction record_full_incomplete_instruction;
 static int record_full_next_insn;
 
@@ -665,14 +665,14 @@ record_full_reset_history ()
   record_full_insn_count = 0;
   record_full_next_insn = 0;
 
-  record_full_list.clear ();
+  record_full_log.clear ();
 }
 
 static void
-record_full_list_release_following (int index)
+record_full_log_release_following (int index)
 {
-  for (int i = record_full_list.size () - 1; i > index; i--)
-    record_full_list.pop_back ();
+  for (int i = record_full_log.size () - 1; i > index; i--)
+    record_full_log.pop_back ();
   /* Set the next instruction to be past the end of the log so we
      start recording if the user moves forward again.  */
   record_full_next_insn = index;
@@ -686,7 +686,7 @@ record_full_save_instruction ()
   ++record_full_insn_count;
   record_full_incomplete_instruction.insn_num = record_full_insn_count;
   record_full_incomplete_instruction.effects.shrink_to_fit ();
-  record_full_list.push_back (std::move (record_full_incomplete_instruction));
+  record_full_log.push_back (std::move (record_full_incomplete_instruction));
   record_full_next_insn++;
 
   record_full_reset_incomplete ();
@@ -696,12 +696,12 @@ record_full_save_instruction ()
    room for adding a new instruction at the end of the log.  */
 
 static void
-record_full_list_release_first (void)
+record_full_log_release_first (void)
 {
-  if (record_full_list.empty ())
+  if (record_full_log.empty ())
     return;
 
-  record_full_list.pop_front ();
+  record_full_log.pop_front ();
   --record_full_next_insn;
 }
 
@@ -765,7 +765,7 @@ record_full_arch_list_add_mem (CORE_ADDR addr, int len)
 static void
 record_full_check_insn_num (void)
 {
-  if (record_full_list.size () == record_full_insn_max_num)
+  if (record_full_log.size () == record_full_insn_max_num)
     {
       /* Ask user what to do.  */
       if (record_full_stop_at_limit)
@@ -781,9 +781,9 @@ record_full_check_insn_num (void)
 
 /* Before inferior step (when GDB record the running message, inferior
    only can step), GDB will call this function to record the values to
-   record_full_list.  This function will call gdbarch_process_record to
+   record_full_log.  This function will call gdbarch_process_record to
    record the running message of inferior and set them to
-   record_full_arch_list, and add it to record_full_list.  */
+   record_full_arch_list, and add it to record_full_log.  */
 
 static void
 record_full_message (struct regcache *regcache, enum gdb_signal signal)
@@ -821,8 +821,8 @@ record_full_message (struct regcache *regcache, enum gdb_signal signal)
 	 if we delivered it during the recording.  Therefore we should
 	 record the signal during record_full_wait, not
 	 record_full_resume.  */
-      if (signal != GDB_SIGNAL_0 && !record_full_list.empty ())
-	record_full_list[record_full_next_insn - 1].sigval = signal;
+      if (signal != GDB_SIGNAL_0 && !record_full_log.empty ())
+	record_full_log[record_full_next_insn - 1].sigval = signal;
 
       if (signal == GDB_SIGNAL_0
 	  || !gdbarch_process_record_signal_p (gdbarch))
@@ -847,8 +847,8 @@ record_full_message (struct regcache *regcache, enum gdb_signal signal)
 
   record_full_save_instruction ();
 
-  if (record_full_list.size () == record_full_insn_max_num)
-    record_full_list_release_first ();
+  if (record_full_log.size () == record_full_insn_max_num)
+    record_full_log_release_first ();
 }
 
 static bool
@@ -1325,7 +1325,7 @@ record_full_wait_1 (struct target_ops *ops,
 	  if (execution_direction == EXEC_REVERSE)
 	    record_full_next_insn--;
 
-	  /* Loop over the record_full_list, looking for the next place to
+	  /* Loop over the record_full_log, looking for the next place to
 	     stop.  */
 	  do
 	    {
@@ -1339,14 +1339,14 @@ record_full_wait_1 (struct target_ops *ops,
 		  break;
 		}
 	      if (execution_direction != EXEC_REVERSE
-		  && record_full_next_insn == record_full_list.size ())
+		  && record_full_next_insn == record_full_log.size ())
 		{
 		  /* Hit end of record log going forward.  */
 		  status->set_no_history ();
 		  break;
 		}
 
-	      record_full_list[record_full_next_insn].exec_insn (regcache);
+	      record_full_log[record_full_next_insn].exec_insn (regcache);
 
 	      /* step */
 	      if (record_full_resume_step)
@@ -1380,7 +1380,7 @@ record_full_wait_1 (struct target_ops *ops,
 				"watchpoint.\n");
 		  continue_flag = 0;
 		}
-	      if (record_full_list[record_full_next_insn].sigval.has_value ())
+	      if (record_full_log[record_full_next_insn].sigval.has_value ())
 		continue_flag = 0;
 
 	      if (execution_direction == EXEC_REVERSE)
@@ -1395,10 +1395,10 @@ record_full_wait_1 (struct target_ops *ops,
 	      gdb_assert (execution_direction == EXEC_REVERSE);
 	      record_full_next_insn = 0;
 	    }
-	  else if (record_full_next_insn > record_full_list.size ())
+	  else if (record_full_next_insn > record_full_log.size ())
 	    {
 	      gdb_assert (execution_direction == EXEC_FORWARD);
-	      record_full_next_insn = record_full_list.size ();
+	      record_full_next_insn = record_full_log.size ();
 	    }
 	  /* Reset the current instruction to point to the one to be replayed
 	     moving forward.  */
@@ -1412,9 +1412,9 @@ record_full_wait_1 (struct target_ops *ops,
 			 ? record_full_next_insn - 1 : record_full_next_insn;
 	      if (record_full_get_sig)
 		status->set_stopped (GDB_SIGNAL_INT);
-	      else if (record_full_list[insn].sigval.has_value ())
+	      else if (record_full_log[insn].sigval.has_value ())
 		status->set_stopped
-		  (record_full_list[insn].sigval.value ());
+		  (record_full_log[insn].sigval.value ());
 	      else
 		status->set_stopped (GDB_SIGNAL_TRAP);
 	    }
@@ -1537,8 +1537,8 @@ record_full_registers_change (struct regcache *regcache, int regnum)
     }
   record_full_save_instruction ();
 
-  if (record_full_list.size () == record_full_insn_max_num)
-    record_full_list_release_first ();
+  if (record_full_log.size () == record_full_insn_max_num)
+    record_full_log_release_first ();
 }
 
 /* "store_registers" method for process record target.  */
@@ -1587,7 +1587,7 @@ record_full_target::store_registers (struct regcache *regcache, int regno)
 	    }
 
 	  /* Destroy the record from here forward.  */
-	  record_full_list_release_following (record_full_next_insn);
+	  record_full_log_release_following (record_full_next_insn);
 	}
 
       record_full_registers_change (regcache, regno);
@@ -1620,7 +1620,7 @@ record_full_target::xfer_partial (enum target_object object,
 	    error (_("Process record canceled the operation."));
 
 	  /* Destroy the record from here forward.  */
-	  record_full_list_release_following (record_full_next_insn);
+	  record_full_log_release_following (record_full_next_insn);
 	}
 
       /* Check record_full_insn_num */
@@ -1639,8 +1639,8 @@ record_full_target::xfer_partial (enum target_object object,
 	}
       record_full_save_instruction ();
 
-      if (record_full_list.size () == record_full_insn_max_num)
-	record_full_list_release_first ();
+      if (record_full_log.size () == record_full_insn_max_num)
+	record_full_log_release_first ();
     }
 
   return this->beneath ()->xfer_partial (object, annex, readbuf, writebuf,
@@ -1796,11 +1796,11 @@ record_full_base_target::get_bookmark (const char *args, int from_tty)
 {
   char *ret = NULL;
 
-  if (record_full_list.empty ())
+  if (record_full_log.empty ())
     return (gdb_byte *) ret;
 
   /* Return stringified form of instruction count.  */
-  ret = xstrdup (pulongest (record_full_list[record_full_next_insn].insn_num));
+  ret = xstrdup (pulongest (record_full_log[record_full_next_insn].insn_num));
 
   if (record_debug)
     {
@@ -1862,16 +1862,16 @@ record_full_base_target::info_record ()
     gdb_printf (_("Record mode:\n"));
 
   /* Do we have a log at all?  */
-  if (!record_full_list.empty ())
+  if (!record_full_log.empty ())
     {
       /* Display instruction number for first instruction in the log.  */
       gdb_printf (_("Lowest recorded instruction number is %u.\n"),
-		  record_full_list[0].insn_num);
+		  record_full_log[0].insn_num);
 
       /* If in replay mode, display where we are in the log.  */
       if (RECORD_FULL_IS_REPLAY)
 	gdb_printf (_("Current instruction number is %u.\n"),
-		    record_full_list[record_full_next_insn].insn_num);
+		    record_full_log[record_full_next_insn].insn_num);
 
       /* Display instruction number for last instruction in the log.  */
       gdb_printf (_("Highest recorded instruction number is %s.\n"),
@@ -1879,7 +1879,7 @@ record_full_base_target::info_record ()
 
       /* Display log count.  */
       gdb_printf (_("Log contains %lu instructions.\n"),
-		  (unsigned long int) record_full_list.size ());
+		  (unsigned long int) record_full_log.size ());
     }
   else
     gdb_printf (_("No instructions have been logged.\n"));
@@ -1928,14 +1928,14 @@ record_full_base_target::record_will_replay (ptid_t ptid, int dir)
 static void
 record_full_goto_entry (size_t target_insn)
 {
-  if (target_insn >= record_full_list.size ())
+  if (target_insn >= record_full_log.size ())
     error (_("Target insn not found."));
   else if (target_insn == record_full_next_insn)
     error (_("Already at target insn."));
   else if (target_insn > record_full_next_insn)
     {
       gdb_printf (_("Go forward to insn number %s\n"),
-		  pulongest (record_full_list[target_insn].insn_num));
+		  pulongest (record_full_log[target_insn].insn_num));
       record_full_goto_insn (target_insn, EXEC_FORWARD);
     }
   else
@@ -1966,7 +1966,7 @@ record_full_base_target::goto_record_begin ()
 void
 record_full_base_target::goto_record_end ()
 {
-  record_full_goto_entry (record_full_list.size () - 1);
+  record_full_goto_entry (record_full_log.size () - 1);
 }
 
 /* The "goto_record" target method.  */
@@ -2403,7 +2403,7 @@ record_full_restore (struct bfd &cbfd)
   int bfd_offset = 0;
 
   /* "record_full_restore" can only be called when record list is empty.  */
-  gdb_assert (record_full_list.empty ());
+  gdb_assert (record_full_log.empty ());
 
   if (record_debug)
     gdb_printf (gdb_stdlog, "Restoring recording from core file.\n");
@@ -2454,9 +2454,9 @@ record_full_restore (struct bfd &cbfd)
     }
 
   /* Update record_full_insn_max_num.  */
-  if (record_full_list.size () > record_full_insn_max_num)
+  if (record_full_log.size () > record_full_insn_max_num)
     {
-      record_full_insn_max_num = record_full_list.size ();
+      record_full_insn_max_num = record_full_log.size ();
       warning (_("Auto increase record/replay buffer limit to %u."),
 	       record_full_insn_max_num);
     }
@@ -2634,16 +2634,16 @@ record_full_base_target::save_record (const char *recfilename)
 
   /* Reverse execute to the begin of record list.  */
   for (int i = record_full_next_insn - 1; i >= 0; i--)
-    record_full_list[i].exec_insn (regcache);
+    record_full_log[i].exec_insn (regcache);
 
   /* Compute the size needed for the extra bfd section.  */
   save_size = 4;	/* magic cookie */
-  for (int i = record_full_list.size () - 1; i >= 0; i--)
+  for (int i = record_full_log.size () - 1; i >= 0; i--)
     {
       /* Number of effects of an instruction.  */
       save_size += sizeof (uint32_t) + sizeof (uint8_t) + sizeof (uint32_t);
-      save_size += 4 + record_full_list[i].pc.len;
-      for (auto &entry : record_full_list[i].effects)
+      save_size += 4 + record_full_log[i].pc.len;
+      for (auto &entry : record_full_log[i].effects)
 	switch (entry.type ())
 	  {
 	  case record_full_reg:
@@ -2682,12 +2682,12 @@ record_full_base_target::save_record (const char *recfilename)
 
   /* Save the entries to recfd and forward execute to the end of
      record list.  */
-  for (int i = 0; i < record_full_list.size (); i++)
+  for (int i = 0; i < record_full_log.size (); i++)
     {
-      record_full_list[i].to_bfd (obfd, osec, &bfd_offset, gdbarch);
+      record_full_log[i].to_bfd (obfd, osec, &bfd_offset, gdbarch);
       /* Execute entry.  */
       if (i < record_full_next_insn)
-	record_full_list[i].exec_insn (regcache);
+	record_full_log[i].exec_insn (regcache);
     }
 
   unlink_file.keep ();
@@ -2714,10 +2714,10 @@ record_full_goto_insn (size_t target_insn,
 
   if (dir == EXEC_REVERSE)
     for (int i = record_full_next_insn; i > target_insn; i--)
-      record_full_list[i-1].exec_insn (regcache);
+      record_full_log[i-1].exec_insn (regcache);
   else
     for (int i = record_full_next_insn; i < target_insn; i++)
-      record_full_list[i].exec_insn (regcache);
+      record_full_log[i].exec_insn (regcache);
 }
 
 /* Alias for "target record-full".  */
@@ -2732,10 +2732,10 @@ static void
 set_record_full_insn_max_num (const char *args, int from_tty,
 			      struct cmd_list_element *c)
 {
-  if (record_full_list.size () > record_full_insn_max_num)
+  if (record_full_log.size () > record_full_insn_max_num)
     {
-      while (record_full_list.size () > record_full_insn_max_num)
-	record_full_list_release_first ();
+      while (record_full_log.size () > record_full_insn_max_num)
+	record_full_log_release_first ();
     }
 }
 
@@ -2744,21 +2744,21 @@ set_record_full_insn_max_num (const char *args, int from_tty,
 static void
 maintenance_print_record_instruction (const char *args, int from_tty)
 {
-  if (record_full_list.empty ())
+  if (record_full_log.empty ())
     error (_("Not enough recorded history"));
 
   int offset = record_full_next_insn - 1;
   /* Reduce the offset by 1 if the record_full_next_insn is after the end
      so that we show the last recorded instruction instead of crashing.  */
-  if (offset == record_full_list.size ())
+  if (offset == record_full_log.size ())
     offset--;
   if (args != nullptr)
     {
       offset += value_as_long (parse_and_eval (args));
-      if (offset >= record_full_list.size () || offset < 0)
+      if (offset >= record_full_log.size () || offset < 0)
 	error (_("Not enough recorded history"));
     }
-  auto to_print = record_full_list.begin () + offset;
+  auto to_print = record_full_log.begin () + offset;
 
   gdbarch *arch = current_inferior ()->arch ();
   struct value_print_options opts;
-- 
2.54.0


^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v3 7/7] gdb/record: rename record_full_list to record_full_log
  2026-05-15 16:36 [PATCH v3 0/7] refactor the internals of record-full Guinevere Larsen
                   ` (7 preceding siblings ...)
  2026-05-15 16:37 ` [PATCH v3 6/6] gdb/record: rename record_full_list to record_full_log Guinevere Larsen
@ 2026-05-15 16:37 ` Guinevere Larsen
  2026-05-16  1:07   ` Thiago Jung Bauermann
  2026-05-15 17:45 ` [PATCH v3 0/7] refactor the internals of record-full Guinevere Larsen
  9 siblings, 1 reply; 27+ messages in thread
From: Guinevere Larsen @ 2026-05-15 16:37 UTC (permalink / raw)
  To: gdb-patches; +Cc: Guinevere Larsen

Now that we no longer use a linked list to manage the history, the name
"record_full_list" is no longer meaningful. This commit changes the name
to record_full_log since it is the execution log. I decided to not say
"record_full_execution_log" to avoid overly long lines when accessing
it.
---
 gdb/record-full.c | 120 +++++++++++++++++++++++-----------------------
 1 file changed, 60 insertions(+), 60 deletions(-)

diff --git a/gdb/record-full.c b/gdb/record-full.c
index 055d2c66346..3c63753d24e 100644
--- a/gdb/record-full.c
+++ b/gdb/record-full.c
@@ -76,7 +76,7 @@
 #define DEFAULT_RECORD_FULL_INSN_MAX_NUM	200000
 
 #define RECORD_FULL_IS_REPLAY \
-  ((record_full_next_insn != record_full_list.size ()) \
+  ((record_full_next_insn != record_full_log.size ()) \
     || ::execution_direction == EXEC_REVERSE)
 
 #define RECORD_FULL_FILE_MAGIC_OLD	netorder32(0x20091016)
@@ -446,7 +446,7 @@ static struct record_full_core_buf_entry *record_full_core_buf_list = NULL;
 /* The following variables are used for managing the history of executed
    instructions from the inferior.
 
-   record_full_list contains all instructions that were fully executed and
+   record_full_log contains all instructions that were fully executed and
    saved to the log, so that we can replay the execution.
 
    record_full_next_insn always points to the next instruction that would
@@ -459,7 +459,7 @@ static struct record_full_core_buf_entry *record_full_core_buf_list = NULL;
    happening.  It is manipulated by the "arch list" functions for historical
    reasons.  */
 
-static std::deque<record_full_instruction> record_full_list;
+static std::deque<record_full_instruction> record_full_log;
 static record_full_instruction record_full_incomplete_instruction;
 static int record_full_next_insn;
 
@@ -665,14 +665,14 @@ record_full_reset_history ()
   record_full_insn_count = 0;
   record_full_next_insn = 0;
 
-  record_full_list.clear ();
+  record_full_log.clear ();
 }
 
 static void
-record_full_list_release_following (int index)
+record_full_log_release_following (int index)
 {
-  for (int i = record_full_list.size () - 1; i > index; i--)
-    record_full_list.pop_back ();
+  for (int i = record_full_log.size () - 1; i > index; i--)
+    record_full_log.pop_back ();
   /* Set the next instruction to be past the end of the log so we
      start recording if the user moves forward again.  */
   record_full_next_insn = index;
@@ -686,7 +686,7 @@ record_full_save_instruction ()
   ++record_full_insn_count;
   record_full_incomplete_instruction.insn_num = record_full_insn_count;
   record_full_incomplete_instruction.effects.shrink_to_fit ();
-  record_full_list.push_back (std::move (record_full_incomplete_instruction));
+  record_full_log.push_back (std::move (record_full_incomplete_instruction));
   record_full_next_insn++;
 
   record_full_reset_incomplete ();
@@ -696,12 +696,12 @@ record_full_save_instruction ()
    room for adding a new instruction at the end of the log.  */
 
 static void
-record_full_list_release_first (void)
+record_full_log_release_first (void)
 {
-  if (record_full_list.empty ())
+  if (record_full_log.empty ())
     return;
 
-  record_full_list.pop_front ();
+  record_full_log.pop_front ();
   --record_full_next_insn;
 }
 
@@ -765,7 +765,7 @@ record_full_arch_list_add_mem (CORE_ADDR addr, int len)
 static void
 record_full_check_insn_num (void)
 {
-  if (record_full_list.size () == record_full_insn_max_num)
+  if (record_full_log.size () == record_full_insn_max_num)
     {
       /* Ask user what to do.  */
       if (record_full_stop_at_limit)
@@ -781,9 +781,9 @@ record_full_check_insn_num (void)
 
 /* Before inferior step (when GDB record the running message, inferior
    only can step), GDB will call this function to record the values to
-   record_full_list.  This function will call gdbarch_process_record to
+   record_full_log.  This function will call gdbarch_process_record to
    record the running message of inferior and set them to
-   record_full_arch_list, and add it to record_full_list.  */
+   record_full_arch_list, and add it to record_full_log.  */
 
 static void
 record_full_message (struct regcache *regcache, enum gdb_signal signal)
@@ -821,8 +821,8 @@ record_full_message (struct regcache *regcache, enum gdb_signal signal)
 	 if we delivered it during the recording.  Therefore we should
 	 record the signal during record_full_wait, not
 	 record_full_resume.  */
-      if (signal != GDB_SIGNAL_0 && !record_full_list.empty ())
-	record_full_list[record_full_next_insn - 1].sigval = signal;
+      if (signal != GDB_SIGNAL_0 && !record_full_log.empty ())
+	record_full_log[record_full_next_insn - 1].sigval = signal;
 
       if (signal == GDB_SIGNAL_0
 	  || !gdbarch_process_record_signal_p (gdbarch))
@@ -847,8 +847,8 @@ record_full_message (struct regcache *regcache, enum gdb_signal signal)
 
   record_full_save_instruction ();
 
-  if (record_full_list.size () == record_full_insn_max_num)
-    record_full_list_release_first ();
+  if (record_full_log.size () == record_full_insn_max_num)
+    record_full_log_release_first ();
 }
 
 static bool
@@ -1325,7 +1325,7 @@ record_full_wait_1 (struct target_ops *ops,
 	  if (execution_direction == EXEC_REVERSE)
 	    record_full_next_insn--;
 
-	  /* Loop over the record_full_list, looking for the next place to
+	  /* Loop over the record_full_log, looking for the next place to
 	     stop.  */
 	  do
 	    {
@@ -1339,14 +1339,14 @@ record_full_wait_1 (struct target_ops *ops,
 		  break;
 		}
 	      if (execution_direction != EXEC_REVERSE
-		  && record_full_next_insn == record_full_list.size ())
+		  && record_full_next_insn == record_full_log.size ())
 		{
 		  /* Hit end of record log going forward.  */
 		  status->set_no_history ();
 		  break;
 		}
 
-	      record_full_list[record_full_next_insn].exec_insn (regcache);
+	      record_full_log[record_full_next_insn].exec_insn (regcache);
 
 	      /* step */
 	      if (record_full_resume_step)
@@ -1380,7 +1380,7 @@ record_full_wait_1 (struct target_ops *ops,
 				"watchpoint.\n");
 		  continue_flag = 0;
 		}
-	      if (record_full_list[record_full_next_insn].sigval.has_value ())
+	      if (record_full_log[record_full_next_insn].sigval.has_value ())
 		continue_flag = 0;
 
 	      if (execution_direction == EXEC_REVERSE)
@@ -1395,10 +1395,10 @@ record_full_wait_1 (struct target_ops *ops,
 	      gdb_assert (execution_direction == EXEC_REVERSE);
 	      record_full_next_insn = 0;
 	    }
-	  else if (record_full_next_insn > record_full_list.size ())
+	  else if (record_full_next_insn > record_full_log.size ())
 	    {
 	      gdb_assert (execution_direction == EXEC_FORWARD);
-	      record_full_next_insn = record_full_list.size ();
+	      record_full_next_insn = record_full_log.size ();
 	    }
 	  /* Reset the current instruction to point to the one to be replayed
 	     moving forward.  */
@@ -1412,9 +1412,9 @@ record_full_wait_1 (struct target_ops *ops,
 			 ? record_full_next_insn - 1 : record_full_next_insn;
 	      if (record_full_get_sig)
 		status->set_stopped (GDB_SIGNAL_INT);
-	      else if (record_full_list[insn].sigval.has_value ())
+	      else if (record_full_log[insn].sigval.has_value ())
 		status->set_stopped
-		  (record_full_list[insn].sigval.value ());
+		  (record_full_log[insn].sigval.value ());
 	      else
 		status->set_stopped (GDB_SIGNAL_TRAP);
 	    }
@@ -1537,8 +1537,8 @@ record_full_registers_change (struct regcache *regcache, int regnum)
     }
   record_full_save_instruction ();
 
-  if (record_full_list.size () == record_full_insn_max_num)
-    record_full_list_release_first ();
+  if (record_full_log.size () == record_full_insn_max_num)
+    record_full_log_release_first ();
 }
 
 /* "store_registers" method for process record target.  */
@@ -1587,7 +1587,7 @@ record_full_target::store_registers (struct regcache *regcache, int regno)
 	    }
 
 	  /* Destroy the record from here forward.  */
-	  record_full_list_release_following (record_full_next_insn);
+	  record_full_log_release_following (record_full_next_insn);
 	}
 
       record_full_registers_change (regcache, regno);
@@ -1620,7 +1620,7 @@ record_full_target::xfer_partial (enum target_object object,
 	    error (_("Process record canceled the operation."));
 
 	  /* Destroy the record from here forward.  */
-	  record_full_list_release_following (record_full_next_insn);
+	  record_full_log_release_following (record_full_next_insn);
 	}
 
       /* Check record_full_insn_num */
@@ -1639,8 +1639,8 @@ record_full_target::xfer_partial (enum target_object object,
 	}
       record_full_save_instruction ();
 
-      if (record_full_list.size () == record_full_insn_max_num)
-	record_full_list_release_first ();
+      if (record_full_log.size () == record_full_insn_max_num)
+	record_full_log_release_first ();
     }
 
   return this->beneath ()->xfer_partial (object, annex, readbuf, writebuf,
@@ -1796,11 +1796,11 @@ record_full_base_target::get_bookmark (const char *args, int from_tty)
 {
   char *ret = NULL;
 
-  if (record_full_list.empty ())
+  if (record_full_log.empty ())
     return (gdb_byte *) ret;
 
   /* Return stringified form of instruction count.  */
-  ret = xstrdup (pulongest (record_full_list[record_full_next_insn].insn_num));
+  ret = xstrdup (pulongest (record_full_log[record_full_next_insn].insn_num));
 
   if (record_debug)
     {
@@ -1862,16 +1862,16 @@ record_full_base_target::info_record ()
     gdb_printf (_("Record mode:\n"));
 
   /* Do we have a log at all?  */
-  if (!record_full_list.empty ())
+  if (!record_full_log.empty ())
     {
       /* Display instruction number for first instruction in the log.  */
       gdb_printf (_("Lowest recorded instruction number is %u.\n"),
-		  record_full_list[0].insn_num);
+		  record_full_log[0].insn_num);
 
       /* If in replay mode, display where we are in the log.  */
       if (RECORD_FULL_IS_REPLAY)
 	gdb_printf (_("Current instruction number is %u.\n"),
-		    record_full_list[record_full_next_insn].insn_num);
+		    record_full_log[record_full_next_insn].insn_num);
 
       /* Display instruction number for last instruction in the log.  */
       gdb_printf (_("Highest recorded instruction number is %s.\n"),
@@ -1879,7 +1879,7 @@ record_full_base_target::info_record ()
 
       /* Display log count.  */
       gdb_printf (_("Log contains %lu instructions.\n"),
-		  (unsigned long int) record_full_list.size ());
+		  (unsigned long int) record_full_log.size ());
     }
   else
     gdb_printf (_("No instructions have been logged.\n"));
@@ -1928,14 +1928,14 @@ record_full_base_target::record_will_replay (ptid_t ptid, int dir)
 static void
 record_full_goto_entry (size_t target_insn)
 {
-  if (target_insn >= record_full_list.size ())
+  if (target_insn >= record_full_log.size ())
     error (_("Target insn not found."));
   else if (target_insn == record_full_next_insn)
     error (_("Already at target insn."));
   else if (target_insn > record_full_next_insn)
     {
       gdb_printf (_("Go forward to insn number %s\n"),
-		  pulongest (record_full_list[target_insn].insn_num));
+		  pulongest (record_full_log[target_insn].insn_num));
       record_full_goto_insn (target_insn, EXEC_FORWARD);
     }
   else
@@ -1966,7 +1966,7 @@ record_full_base_target::goto_record_begin ()
 void
 record_full_base_target::goto_record_end ()
 {
-  record_full_goto_entry (record_full_list.size () - 1);
+  record_full_goto_entry (record_full_log.size () - 1);
 }
 
 /* The "goto_record" target method.  */
@@ -2403,7 +2403,7 @@ record_full_restore (struct bfd &cbfd)
   int bfd_offset = 0;
 
   /* "record_full_restore" can only be called when record list is empty.  */
-  gdb_assert (record_full_list.empty ());
+  gdb_assert (record_full_log.empty ());
 
   if (record_debug)
     gdb_printf (gdb_stdlog, "Restoring recording from core file.\n");
@@ -2454,9 +2454,9 @@ record_full_restore (struct bfd &cbfd)
     }
 
   /* Update record_full_insn_max_num.  */
-  if (record_full_list.size () > record_full_insn_max_num)
+  if (record_full_log.size () > record_full_insn_max_num)
     {
-      record_full_insn_max_num = record_full_list.size ();
+      record_full_insn_max_num = record_full_log.size ();
       warning (_("Auto increase record/replay buffer limit to %u."),
 	       record_full_insn_max_num);
     }
@@ -2634,16 +2634,16 @@ record_full_base_target::save_record (const char *recfilename)
 
   /* Reverse execute to the begin of record list.  */
   for (int i = record_full_next_insn - 1; i >= 0; i--)
-    record_full_list[i].exec_insn (regcache);
+    record_full_log[i].exec_insn (regcache);
 
   /* Compute the size needed for the extra bfd section.  */
   save_size = 4;	/* magic cookie */
-  for (int i = record_full_list.size () - 1; i >= 0; i--)
+  for (int i = record_full_log.size () - 1; i >= 0; i--)
     {
       /* Number of effects of an instruction.  */
       save_size += sizeof (uint32_t) + sizeof (uint8_t) + sizeof (uint32_t);
-      save_size += 4 + record_full_list[i].pc.len;
-      for (auto &entry : record_full_list[i].effects)
+      save_size += 4 + record_full_log[i].pc.len;
+      for (auto &entry : record_full_log[i].effects)
 	switch (entry.type ())
 	  {
 	  case record_full_reg:
@@ -2682,12 +2682,12 @@ record_full_base_target::save_record (const char *recfilename)
 
   /* Save the entries to recfd and forward execute to the end of
      record list.  */
-  for (int i = 0; i < record_full_list.size (); i++)
+  for (int i = 0; i < record_full_log.size (); i++)
     {
-      record_full_list[i].to_bfd (obfd, osec, &bfd_offset, gdbarch);
+      record_full_log[i].to_bfd (obfd, osec, &bfd_offset, gdbarch);
       /* Execute entry.  */
       if (i < record_full_next_insn)
-	record_full_list[i].exec_insn (regcache);
+	record_full_log[i].exec_insn (regcache);
     }
 
   unlink_file.keep ();
@@ -2714,10 +2714,10 @@ record_full_goto_insn (size_t target_insn,
 
   if (dir == EXEC_REVERSE)
     for (int i = record_full_next_insn; i > target_insn; i--)
-      record_full_list[i-1].exec_insn (regcache);
+      record_full_log[i-1].exec_insn (regcache);
   else
     for (int i = record_full_next_insn; i < target_insn; i++)
-      record_full_list[i].exec_insn (regcache);
+      record_full_log[i].exec_insn (regcache);
 }
 
 /* Alias for "target record-full".  */
@@ -2732,10 +2732,10 @@ static void
 set_record_full_insn_max_num (const char *args, int from_tty,
 			      struct cmd_list_element *c)
 {
-  if (record_full_list.size () > record_full_insn_max_num)
+  if (record_full_log.size () > record_full_insn_max_num)
     {
-      while (record_full_list.size () > record_full_insn_max_num)
-	record_full_list_release_first ();
+      while (record_full_log.size () > record_full_insn_max_num)
+	record_full_log_release_first ();
     }
 }
 
@@ -2744,21 +2744,21 @@ set_record_full_insn_max_num (const char *args, int from_tty,
 static void
 maintenance_print_record_instruction (const char *args, int from_tty)
 {
-  if (record_full_list.empty ())
+  if (record_full_log.empty ())
     error (_("Not enough recorded history"));
 
   int offset = record_full_next_insn - 1;
   /* Reduce the offset by 1 if the record_full_next_insn is after the end
      so that we show the last recorded instruction instead of crashing.  */
-  if (offset == record_full_list.size ())
+  if (offset == record_full_log.size ())
     offset--;
   if (args != nullptr)
     {
       offset += value_as_long (parse_and_eval (args));
-      if (offset >= record_full_list.size () || offset < 0)
+      if (offset >= record_full_log.size () || offset < 0)
 	error (_("Not enough recorded history"));
     }
-  auto to_print = record_full_list.begin () + offset;
+  auto to_print = record_full_log.begin () + offset;
 
   gdbarch *arch = current_inferior ()->arch ();
   struct value_print_options opts;
-- 
2.54.0


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v3 0/7] refactor the internals of record-full
  2026-05-15 16:36 [PATCH v3 0/7] refactor the internals of record-full Guinevere Larsen
                   ` (8 preceding siblings ...)
  2026-05-15 16:37 ` [PATCH v3 7/7] " Guinevere Larsen
@ 2026-05-15 17:45 ` Guinevere Larsen
  9 siblings, 0 replies; 27+ messages in thread
From: Guinevere Larsen @ 2026-05-15 17:45 UTC (permalink / raw)
  To: gdb-patches

Oops, sorry about the noise, apparently some mis-labelled patch files 
were kept from one iteration to the next and I sent some emails twice. 
Please ignore all files that act like there are 6 patches in the series
On 5/15/26 1:36 PM, Guinevere Larsen wrote:
> This series introduces a refactor and modernization to the record-full
> history, moving it form a manually managed doubly-linked list into an
> std::deque, which can make better use of caching and might make
> execution faster.
>
> The main driving force behind this change is the plan to implement
> support for multithreaded inferiors, since each instruction will need to
> know the thread that executed it, and it'll be much easier if there is a
> more consistent spot to find all this information.
>
> Changes for v3: Made the former "cleanup" functions into actual
> destructors. To do that and avoid double-frees, I made it so
> record_full_entry (and the 2 classes under it) can't be copied, only
> moved. Also implemented almost all the feedback from Christina on the
> changes.
>
> Guinevere Larsen (7):
>    gdb/record: Refactor record history
>    gdb/record: remove record_full_insn_num
>    gdb/record: c++ify internal structures of record-full.c
>    gdb/record: make record_full_history more c++-like
>    gdb/record: extract the PC to record_full_instruction
>    gdb/record: Define new version of the record-save section
>    gdb/record: rename record_full_list to record_full_log
>
>   gdb/NEWS               |    4 +
>   gdb/aarch64-tdep.c     |    2 -
>   gdb/amd64-linux-tdep.c |    3 -
>   gdb/arm-tdep.c         |    2 -
>   gdb/i386-linux-tdep.c  |    3 -
>   gdb/i386-tdep.c        |    4 -
>   gdb/loongarch-tdep.c   |    2 -
>   gdb/moxie-tdep.c       |    2 -
>   gdb/ppc-linux-tdep.c   |    3 -
>   gdb/record-full.c      | 1724 +++++++++++++++++++---------------------
>   gdb/record-full.h      |    1 -
>   gdb/riscv-tdep.c       |    3 -
>   gdb/rs6000-tdep.c      |    4 -
>   gdb/s390-linux-tdep.c  |    3 -
>   gdb/s390-tdep.c        |    2 -
>   15 files changed, 827 insertions(+), 935 deletions(-)
>
>
> base-commit: 62cc7210c87a08a9e10955d9a240cfe0c0b5ee13


-- 
Cheers,
Guinevere Larsen
It/she


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v3 4/7] gdb/record: make record_full_history more c++-like
  2026-05-15 16:37 ` [PATCH v3 4/7] gdb/record: make record_full_history more c++-like Guinevere Larsen
@ 2026-05-16  1:03   ` Thiago Jung Bauermann
  2026-05-26  9:04   ` Schimpe, Christina
  1 sibling, 0 replies; 27+ messages in thread
From: Thiago Jung Bauermann @ 2026-05-16  1:03 UTC (permalink / raw)
  To: Guinevere Larsen; +Cc: gdb-patches

Hello Guinevere,

Regarding patch 3: Nice that you were able to turn the cleanup functions
into destructors!

Guinevere Larsen <guinevere@redhat.com> writes:

> Move some functions to being a method of record_full_history, instead of
> floating functions.
> ---
>  gdb/record-full.c | 25 +++++++++++--------------
>  1 file changed, 11 insertions(+), 14 deletions(-)

Regarding this patch:

Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>

-- 
Thiago

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v3 5/7] gdb/record: extract the PC to record_full_instruction
  2026-05-15 16:37 ` [PATCH v3 5/7] gdb/record: extract the PC to record_full_instruction Guinevere Larsen
@ 2026-05-16  1:04   ` Thiago Jung Bauermann
  2026-05-29 12:40   ` Schimpe, Christina
  1 sibling, 0 replies; 27+ messages in thread
From: Thiago Jung Bauermann @ 2026-05-16  1:04 UTC (permalink / raw)
  To: Guinevere Larsen; +Cc: gdb-patches

Guinevere Larsen <guinevere@redhat.com> writes:

> This commit makes it so the PC is not saved as part of the
> record_full_instruction effects, but rather gets a special location.
> That is because a couple of commands would really benefit from it being
> easy to find the PC (especially ones from record-btrace that's haven't
> been implemented to record-full yet, such as the ones in PR
> record/18059), while also possibly allowing for one fewer resizing of
> the effect vector (and saving an entire byte in the process).
>
> This commit also refactored record_full_read_entry_from_bfd and
> record_full_write_entry_to_bfd, to make them methods of
> record_full_reg_entry and record_full_mem_entry, and also creates
> similar methods for record_full_entry and record_full_instruction.
> These could be turned into constructors in a future step of
> c++ification, but it felt like too much change for a single commit.
> ---
>  gdb/record-full.c | 386 ++++++++++++++++++++++++++++------------------
>  1 file changed, 239 insertions(+), 147 deletions(-)

Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
-- 
Thiago

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v3 7/7] gdb/record: rename record_full_list to record_full_log
  2026-05-15 16:37 ` [PATCH v3 7/7] " Guinevere Larsen
@ 2026-05-16  1:07   ` Thiago Jung Bauermann
  0 siblings, 0 replies; 27+ messages in thread
From: Thiago Jung Bauermann @ 2026-05-16  1:07 UTC (permalink / raw)
  To: Guinevere Larsen; +Cc: gdb-patches

Guinevere Larsen <guinevere@redhat.com> writes:

> Now that we no longer use a linked list to manage the history, the name
> "record_full_list" is no longer meaningful. This commit changes the name
> to record_full_log since it is the execution log. I decided to not say
> "record_full_execution_log" to avoid overly long lines when accessing
> it.
> ---
>  gdb/record-full.c | 120 +++++++++++++++++++++++-----------------------
>  1 file changed, 60 insertions(+), 60 deletions(-)

Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>

-- 
Thiago

^ permalink raw reply	[flat|nested] 27+ messages in thread

* RE: [PATCH v3 1/7] gdb/record: Refactor record history
  2026-05-15 16:36 ` [PATCH v3 1/7] gdb/record: Refactor record history Guinevere Larsen
@ 2026-05-26  8:33   ` Schimpe, Christina
  2026-05-28 13:20     ` Guinevere Larsen
  0 siblings, 1 reply; 27+ messages in thread
From: Schimpe, Christina @ 2026-05-26  8:33 UTC (permalink / raw)
  To: Guinevere Larsen, gdb-patches; +Cc: Thiago Jung Bauermann

Hi Guinevere,

Please find some further comments below.

> -----Original Message-----
> From: Guinevere Larsen <guinevere@redhat.com>
> Sent: Freitag, 15. Mai 2026 18:37
> To: gdb-patches@sourceware.org
> Cc: Guinevere Larsen <guinevere@redhat.com>; Thiago Jung Bauermann
> <thiago.bauermann@linaro.org>
> Subject: [PATCH v3 1/7] gdb/record: Refactor record history
> 
> This is the first step in a large refactor in how GDB keeps execution
> history.  Rather than using a linked list where multiple entries can
> describe a single instruction, the history will now be stored in an
> std::deque, each instruction being one entry in the deque.
> 
> The choice was initially to use an std::vector, but it would become
> unwieldy because it needs all the memory to be consecutive, which is
> hard for 200 thousand entries. Deque was picked because it was a nice
> midpoint between vector (maximum cache cohesion) and linked list
> (maximum ease of finding space to store more).
> 
> Each instruction in memory will be now one record_full_instruction
> entry, which for this commit just contains a vector of
> record_full_entry for the effects of the instruction, and the data that
> was stored in the record_full_end entry (that is, the instruction number
> and the signal, if any).
> 
> This change introduced a minimal performance improvement (what's
> important is that it isn't a degradation) and a  reduction in the total
> memory footprint of roughly 20% if the entire history is used.
> 
> Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> ---
>  gdb/aarch64-tdep.c     |    2 -
>  gdb/amd64-linux-tdep.c |    3 -
>  gdb/arm-tdep.c         |    2 -
>  gdb/i386-linux-tdep.c  |    3 -
>  gdb/i386-tdep.c        |    4 -
>  gdb/loongarch-tdep.c   |    2 -
>  gdb/moxie-tdep.c       |    2 -
>  gdb/ppc-linux-tdep.c   |    3 -
>  gdb/record-full.c      | 1186 ++++++++++++++++------------------------
>  gdb/record-full.h      |    1 -
>  gdb/riscv-tdep.c       |    3 -
>  gdb/rs6000-tdep.c      |    4 -
>  gdb/s390-linux-tdep.c  |    3 -
>  gdb/s390-tdep.c        |    2 -
>  14 files changed, 484 insertions(+), 736 deletions(-)
> 
> diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c
> index 4befaa2720d..a532992b1d8 100644
> --- a/gdb/aarch64-tdep.c
> +++ b/gdb/aarch64-tdep.c
> @@ -6210,8 +6210,6 @@ aarch64_process_record (struct gdbarch
> *gdbarch, struct regcache *regcache,
>  	       aarch64_record.aarch64_mems[rec_no].len))
>  	    ret = -1;
> 
> -      if (record_full_arch_list_add_end ())
> -	ret = -1;
>      }
> 
>    deallocate_reg_mem (&aarch64_record);
> diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c
> index a5ac26654cf..9b23db72bbe 100644
> --- a/gdb/amd64-linux-tdep.c
> +++ b/gdb/amd64-linux-tdep.c
> @@ -1591,9 +1591,6 @@ amd64_linux_record_signal (struct gdbarch
> *gdbarch,
>  				     + AMD64_LINUX_frame_size))
>      return -1;
> 
> -  if (record_full_arch_list_add_end ())
> -    return -1;
> -
>    return 0;
>  }
> 
> diff --git a/gdb/arm-tdep.c b/gdb/arm-tdep.c
> index 08cce9dbad8..1ce0927f26b 100644
> --- a/gdb/arm-tdep.c
> +++ b/gdb/arm-tdep.c
> @@ -14911,8 +14911,6 @@ arm_process_record (struct gdbarch *gdbarch,
> struct regcache *regcache,
>  	    }
>  	}
> 
> -      if (record_full_arch_list_add_end ())
> -	ret = -1;
>      }
> 
> 
> diff --git a/gdb/i386-linux-tdep.c b/gdb/i386-linux-tdep.c
> index 23aeccac9fc..4f33766fcdd 100644
> --- a/gdb/i386-linux-tdep.c
> +++ b/gdb/i386-linux-tdep.c
> @@ -949,9 +949,6 @@ i386_linux_record_signal (struct gdbarch *gdbarch,
>  				     I386_LINUX_xstate +
> I386_LINUX_frame_size))
>      return -1;
> 
> -  if (record_full_arch_list_add_end ())
> -    return -1;
> -
>    return 0;
>  }
>  

> 
> diff --git a/gdb/i386-tdep.c b/gdb/i386-tdep.c
> index fa935b5fcdb..fee36b46a73 100644
> --- a/gdb/i386-tdep.c
> +++ b/gdb/i386-tdep.c
> @@ -5201,8 +5201,6 @@ i386_record_vex (struct i386_record_s *ir, uint8_t
> vex_w, uint8_t vex_r,
>      }
> 
>    record_full_arch_list_add_reg (ir->regcache, ir-
> >regmap[X86_RECORD_REIP_REGNUM]);
> -  if (record_full_arch_list_add_end ())
> -    return -1;
> 
>    return 0;
>  }
> @@ -8359,8 +8357,6 @@ Do you want to stop the program?"),
> 
>    /* In the future, maybe still need to deal with need_dasm.  */
>    I386_RECORD_FULL_ARCH_LIST_ADD_REG (X86_RECORD_REIP_REGNUM);
> -  if (record_full_arch_list_add_end ())
> -    return -1;
> 
>    return 0;
> 
> diff --git a/gdb/loongarch-tdep.c b/gdb/loongarch-tdep.c
> index 225b1abb703..5ec6b2a48d0 100644
> --- a/gdb/loongarch-tdep.c
> +++ b/gdb/loongarch-tdep.c
> @@ -2789,8 +2789,6 @@ loongarch_process_record (struct gdbarch
> *gdbarch, struct regcache *regcache,
>  					 LOONGARCH_PC_REGNUM))
>  	return -1;
> 
> -      if (record_full_arch_list_add_end ())
> -	return -1;
>      }
> 
>    return ret;
> diff --git a/gdb/moxie-tdep.c b/gdb/moxie-tdep.c
> index c8a04d21f59..2f8f0186488 100644
> --- a/gdb/moxie-tdep.c
> +++ b/gdb/moxie-tdep.c
> @@ -1040,8 +1040,6 @@ moxie_process_record (struct gdbarch *gdbarch,
> struct regcache *regcache,
> 
>    if (record_full_arch_list_add_reg (regcache, MOXIE_PC_REGNUM))
>      return -1;
> -  if (record_full_arch_list_add_end ())
> -    return -1;
>    return 0;
>  }
> 
> diff --git a/gdb/ppc-linux-tdep.c b/gdb/ppc-linux-tdep.c
> index 19698eaacbe..e2f53551709 100644
> --- a/gdb/ppc-linux-tdep.c
> +++ b/gdb/ppc-linux-tdep.c
> @@ -1565,9 +1565,6 @@ ppc_linux_record_signal (struct gdbarch *gdbarch,
> struct regcache *regcache,
>    if (record_full_arch_list_add_mem (sp, SIGNAL_FRAMESIZE +
> sizeof_rt_sigframe))
>      return -1;
> 
> -  if (record_full_arch_list_add_end ())
> -    return -1;
> -
>    return 0;
>  }
> 
> diff --git a/gdb/record-full.c b/gdb/record-full.c
> index 69d2c100e57..02a57726a95 100644
> --- a/gdb/record-full.c
> +++ b/gdb/record-full.c
> @@ -47,6 +47,9 @@
>  #include "interps.h"
>  #include "cli/cli-style.h"
> 
> +#include <vector>
> +#include <optional>
> +#include <deque>
>  #include <signal.h>
> 
>  /* This module implements "target record-full", also known as "process
> @@ -72,20 +75,17 @@
>  #define DEFAULT_RECORD_FULL_INSN_MAX_NUM	200000
> 
>  #define RECORD_FULL_IS_REPLAY \
> -  (record_full_list->next || ::execution_direction == EXEC_REVERSE)
> +  ((record_full_next_insn != record_full_list.size ()) \
> +    || ::execution_direction == EXEC_REVERSE)
> 
>  #define RECORD_FULL_FILE_MAGIC	netorder32(0x20091016)
> 
>  /* These are the core structs of the process record functionality.
> 
>     A record_full_entry is a record of the value change of a register
> -   ("record_full_reg") or a part of memory ("record_full_mem").  And each
> -   instruction must have a struct record_full_entry ("record_full_end")
> -   that indicates that this is the last struct record_full_entry of this
> -   instruction.
> -
> -   Each struct record_full_entry is linked to "record_full_list" by "prev"
> -   and "next" pointers.  */
> +   ("record_full_reg") or a part of memory ("record_full_mem").
> +   These are saved on the record_full_instruction struct, which also
> +   contains some extra information, such as delivered signals.  */

Optional suggestion (since it has been like this before already):

The description for record_full_entry is not above the actual struct and the
further structs miss a comment. In the description above the enum values
are described '("record_full_reg") or a part of memory ("record_full_mem")',
but not the actual structs.  I think it would be helpful to write this out a bit more, 
add comments to each struct and move the one for record_full_entry
above its actual struct.
Given that this commit is already big enough, a separate commit might be better. 

>  struct record_full_mem_entry
>  {
> @@ -112,47 +112,14 @@ struct record_full_reg_entry
>    } u;
>  };
> 
> -struct record_full_end_entry
> -{
> -  enum gdb_signal sigval;
> -  ULONGEST insn_num;
> -};
> -
>  enum record_full_type
>  {
> -  record_full_end = 0,
>    record_full_reg,
>    record_full_mem
>  };
> 
> -/* This is the data structure that makes up the execution log.
> -
> -   The execution log consists of a single linked list of entries
> -   of type "struct record_full_entry".  It is doubly linked so that it
> -   can be traversed in either direction.
> -
> -   The start of the list is anchored by a struct called
> -   "record_full_first".  The pointer "record_full_list" either points
> -   to the last entry that was added to the list (in record mode), or to
> -   the next entry in the list that will be executed (in replay mode).
> -
> -   Each list element (struct record_full_entry), in addition to next
> -   and prev pointers, consists of a union of three entry types: mem,
> -   reg, and end.  A field called "type" determines which entry type is
> -   represented by a given list element.
> -
> -   Each instruction that is added to the execution log is represented
> -   by a variable number of list elements ('entries').  The instruction
> -   will have one "reg" entry for each register that is changed by
> -   executing the instruction (including the PC in every case).  It
> -   will also have one "mem" entry for each memory change.  Finally,
> -   each instruction will have an "end" entry that separates it from
> -   the changes associated with the next instruction.  */
> -
>  struct record_full_entry
>  {
> -  struct record_full_entry *prev;
> -  struct record_full_entry *next;
>    enum record_full_type type;
>    union
>    {
> @@ -160,11 +127,31 @@ struct record_full_entry
>      struct record_full_reg_entry reg;
>      /* mem */
>      struct record_full_mem_entry mem;
> -    /* end */
> -    struct record_full_end_entry end;
>    } u;
>  };
> 
> +/* This is the main structure that comprises the execution log.
> +   Each instruction is comprised of:
> +   * The instruction number: How many instructions were recorded before
> +     this one;
> +   * sigval: Whether the inferior received a signal while the following
> +     instruction was being recorded;
> +   * effects: A list of record_full_entry structures, each of which
> +     describing one effect that the instruction has on the inferior.
> +
> +   Note, the signal is stored in the previous instruction for historical
> +   reasons.  This is how it was first implemented, and no one has gotten
> +   around to changing it yet.  */
> +
> +struct record_full_instruction
> +{
> +  /* This might be different from the index if
> +     we had to remove the first few instructions.  */
> +  uint32_t insn_num;
> +  std::optional<gdb_signal> sigval;
> +  std::vector<record_full_entry> effects;
> +};
> +
>  /* If true, query if PREC cannot record memory
>     change of next instruction.  */
>  bool record_full_memory_query = false;
> @@ -181,27 +168,25 @@ static detached_regcache
> *record_full_core_regbuf = NULL;
>  static std::vector<target_section> record_full_core_sections;
>  static struct record_full_core_buf_entry *record_full_core_buf_list = NULL;
> 
> -/* The following variables are used for managing the linked list that
> -   represents the execution log.
> +/* The following variables are used for managing the history of executed
> +   instructions from the inferior.
> 
> -   record_full_first is the anchor that holds down the beginning of
> -   the list.
> +   record_full_list contains all instructions that were fully executed and
> +   saved to the log, so that we can replay the execution.
> 
> -   record_full_list serves two functions:
> -     1) In record mode, it anchors the end of the list.
> -     2) In replay mode, it traverses the list and points to
> -	the next instruction that must be emulated.
> +   record_full_next_insn always points to the next instruction that would
> +   be executed if the inferior executes forward.  In the special case when
> +   the inferior is not replaying, record_full_next_insn points past the
> +   end of the history.
> 
> -   record_full_arch_list_head and record_full_arch_list_tail are used
> -   to manage a separate list, which is used to build up the change
> -   elements of the currently executing instruction during record mode.
> -   When this instruction has been completely annotated in the "arch
> -   list", it will be appended to the main execution log.  */
> +   record_full_incomplete_instruction holds a partial instruction, while
> +   the lower target is disassembling the instruction, or as partial xfers are
> +   happening.  It is manipulated by the "arch list" functions for historical
> +   reasons.  */
> 
> -static struct record_full_entry record_full_first;
> -static struct record_full_entry *record_full_list = &record_full_first;
> -static struct record_full_entry *record_full_arch_list_head = NULL;
> -static struct record_full_entry *record_full_arch_list_tail = NULL;
> +static std::deque<record_full_instruction> record_full_list;
> +static record_full_instruction record_full_incomplete_instruction;
> +static int record_full_next_insn;
> 
>  /* true ask user. false auto delete the last struct record_full_entry.  */
>  static bool record_full_stop_at_limit = true;
> @@ -361,6 +346,14 @@ record_full_target::kill ()
>    record_kill (this);
>  }

This  function is still missing a comment.

> +static void
> +record_full_reset_incomplete ()
> +{
> +  record_full_incomplete_instruction.effects.clear ();
> +  record_full_incomplete_instruction.sigval.reset ();
> +  record_full_incomplete_instruction.insn_num = 0;
> +}
> +
>  /* See record-full.h.  */
> 
>  int
> @@ -390,156 +383,124 @@ static struct cmd_list_element
> *show_record_full_cmdlist;
>  /* Command list for "record full".  */
>  static struct cmd_list_element *record_full_cmdlist;
> 
> -static void record_full_goto_insn (struct record_full_entry *entry,
> +static void record_full_goto_insn (size_t target_insn,
>  				   enum exec_direction_kind dir);

Is this some general guideline to call this cleanup/init instead of release/alloc ?
Since this patch is already so big, I just wonder why you changed it. 😊

> -/* Alloc and free functions for record_full_reg, record_full_mem, and
> -   record_full_end entries.  */
> +/* Initialization and cleanup functions for record_full_reg and
> +   record_full_mem entries.  */
> 
> -/* Alloc a record_full_reg record entry.  */
> +/* Init a record_full_reg record entry.  */
> 
> -static inline struct record_full_entry *
> -record_full_reg_alloc (struct regcache *regcache, int regnum)
> +static inline record_full_entry
> +record_full_reg_init (struct regcache *regcache, int regnum)
>  {
> -  struct record_full_entry *rec;
> +  record_full_entry rec;
>    struct gdbarch *gdbarch = regcache->arch ();
> 
> -  rec = XCNEW (struct record_full_entry);
> -  rec->type = record_full_reg;
> -  rec->u.reg.num = regnum;
> -  rec->u.reg.len = register_size (gdbarch, regnum);
> -  if (rec->u.reg.len > sizeof (rec->u.reg.u.buf))
> -    rec->u.reg.u.ptr = (gdb_byte *) xmalloc (rec->u.reg.len);
> +  rec.type = record_full_reg;
> +  rec.u.reg.num = regnum;
> +  rec.u.reg.len = register_size (gdbarch, regnum);
> +  if (rec.u.reg.len > sizeof (rec.u.reg.u.buf))
> +    rec.u.reg.u.ptr = (gdb_byte *) xmalloc (rec.u.reg.len);
> 
>    return rec;
>  }
> 
> -/* Free a record_full_reg record entry.  */
> +/* Cleanup a record_full_reg record entry.  */
> 
>  static inline void
> -record_full_reg_release (struct record_full_entry *rec)
> +record_full_reg_cleanup (record_full_entry rec)
>  {
> -  gdb_assert (rec->type == record_full_reg);
> -  if (rec->u.reg.len > sizeof (rec->u.reg.u.buf))
> -    xfree (rec->u.reg.u.ptr);
> -  xfree (rec);
> +  gdb_assert (rec.type == record_full_reg);
> +  if (rec.u.reg.len > sizeof (rec.u.reg.u.buf))
> +    xfree (rec.u.reg.u.ptr);
>  }
> 
> -/* Alloc a record_full_mem record entry.  */
> +/* Init a record_full_mem record entry.  */
> 
> -static inline struct record_full_entry *
> -record_full_mem_alloc (CORE_ADDR addr, int len)
> +static inline record_full_entry
> +record_full_mem_init (CORE_ADDR addr, int len)
>  {
> -  struct record_full_entry *rec;
> +  record_full_entry rec;
> 
> -  rec = XCNEW (struct record_full_entry);
> -  rec->type = record_full_mem;
> -  rec->u.mem.addr = addr;
> -  rec->u.mem.len = len;
> -  if (rec->u.mem.len > sizeof (rec->u.mem.u.buf))
> -    rec->u.mem.u.ptr = (gdb_byte *) xmalloc (len);
> +  rec.type = record_full_mem;
> +  rec.u.mem.addr = addr;
> +  rec.u.mem.len = len;
> +  if (rec.u.mem.len > sizeof (rec.u.mem.u.buf))
> +    rec.u.mem.u.ptr = (gdb_byte *) xmalloc (len);
> +  rec.u.mem.mem_entry_not_accessible = 0;
> 
>    return rec;
>  }
> 
> -/* Free a record_full_mem record entry.  */
> +/* Cleanup a record_full_mem record entry.  */
> 
>  static inline void
> -record_full_mem_release (struct record_full_entry *rec)
> +record_full_mem_cleanup (record_full_entry rec)
>  {
> -  gdb_assert (rec->type == record_full_mem);
> -  if (rec->u.mem.len > sizeof (rec->u.mem.u.buf))
> -    xfree (rec->u.mem.u.ptr);
> -  xfree (rec);
> +  gdb_assert (rec.type == record_full_mem);
> +  if (rec.u.mem.len > sizeof (rec.u.mem.u.buf))
> +    xfree (rec.u.mem.u.ptr);
>  }
> 
> -/* Alloc a record_full_end record entry.  */
> -
> -static inline struct record_full_entry *
> -record_full_end_alloc (void)
> -{
> -  struct record_full_entry *rec;
> -
> -  rec = XCNEW (struct record_full_entry);
> -  rec->type = record_full_end;
> -
> -  return rec;
> -}
> -
> -/* Free a record_full_end record entry.  */
> +/* Free one record entry, any type.  */
> 
>  static inline void
> -record_full_end_release (struct record_full_entry *rec)
> +record_full_entry_cleanup (record_full_entry rec)
>  {
> -  xfree (rec);
> -}
> -
> -/* Free one record entry, any type.
> -   Return entry->type, in case caller wants to know.  */
> 
> -static inline enum record_full_type
> -record_full_entry_release (struct record_full_entry *rec)
> -{
> -  enum record_full_type type = rec->type;
> -
> -  switch (type) {
> +  switch (rec.type) {
>    case record_full_reg:
> -    record_full_reg_release (rec);
> +    record_full_reg_cleanup (rec);
>      break;
>    case record_full_mem:
> -    record_full_mem_release (rec);
> -    break;
> -  case record_full_end:
> -    record_full_end_release (rec);
> +    record_full_mem_cleanup (rec);
>      break;
>    }
> -  return type;
>  }
> 
> -/* Free all record entries in list pointed to by REC.  */
> -
>  static void
> -record_full_list_release (struct record_full_entry *rec)
> +record_full_reset_history ()
>  {
> -  if (!rec)
> -    return;
> -
> -  while (rec->next)
> -    rec = rec->next;
> +  record_full_insn_num = 0;
> +  record_full_insn_count = 0;
> +  record_full_next_insn = 0;
> 
> -  while (rec->prev)
> +  for (auto &insn : record_full_list)
>      {
> -      rec = rec->prev;
> -      record_full_entry_release (rec->next);
> +      for (auto &entry : insn.effects)
> +	record_full_entry_cleanup (entry);
>      }
> 
> -  if (rec == &record_full_first)
> +  record_full_list.clear ();
> +}

This function is missing a comment.

> +static void
> +record_full_list_release_following (int index)
> +{
> +  for (int i = record_full_list.size () - 1; i > index; i--)
>      {
> -      record_full_insn_num = 0;
> -      record_full_first.next = NULL;
> +      for (auto &entry : record_full_list[i].effects)
> +	record_full_entry_cleanup (entry);
> +      record_full_list.pop_back ();
>      }
> -  else
> -    record_full_entry_release (rec);
> +  /* Set the next instruction to be past the end of the log so we
> +     start recording if the user moves forward again.  */
> +  record_full_next_insn = index;
>  }
> 
> -/* Free all record entries forward of the given list position.  */
> +/* Save the incomplete instruction in the log.  */
> 
>  static void
> -record_full_list_release_following (struct record_full_entry *rec)
> +record_full_save_instruction ()
>  {
> -  struct record_full_entry *tmp = rec->next;
> +  ++record_full_insn_count;
> +  record_full_incomplete_instruction.insn_num = record_full_insn_count;
> +  record_full_incomplete_instruction.effects.shrink_to_fit ();
> +  record_full_list.push_back (std::move
> (record_full_incomplete_instruction));
> +  record_full_next_insn++;
> 
> -  rec->next = NULL;
> -  while (tmp)
> -    {
> -      rec = tmp->next;
> -      if (record_full_entry_release (tmp) == record_full_end)
> -	{
> -	  record_full_insn_num--;
> -	  record_full_insn_count--;
> -	}
> -      tmp = rec;
> -    }
> +  record_full_reset_incomplete ();
>  }
> 
>  /* Delete the first instruction from the beginning of the log, to make
> @@ -550,52 +511,22 @@ record_full_list_release_following (struct
> record_full_entry *rec)
>  static void
>  record_full_list_release_first (void)
>  {
> -  struct record_full_entry *tmp;
> -
> -  if (!record_full_first.next)
> +  if (record_full_list.empty ())
>      return;
> 
> -  /* Loop until a record_full_end.  */
> -  while (1)
> -    {
> -      /* Cut record_full_first.next out of the linked list.  */
> -      tmp = record_full_first.next;
> -      record_full_first.next = tmp->next;
> -      tmp->next->prev = &record_full_first;
> -
> -      /* tmp is now isolated, and can be deleted.  */
> -      if (record_full_entry_release (tmp) == record_full_end)
> -	break;	/* End loop at first record_full_end.  */
> +  for (auto &entry : record_full_list[0].effects)
> +    record_full_entry_cleanup (entry);
> 
> -      if (!record_full_first.next)
> -	{
> -	  gdb_assert (record_full_insn_num == 1);
> -	  break;	/* End loop when list is empty.  */
> -	}
> -    }
> +  record_full_list.pop_front ();
> +  --record_full_next_insn;
>  }
> 
>  /* Add a struct record_full_entry to record_full_arch_list.  */
> 
>  static void
> -record_full_arch_list_add (struct record_full_entry *rec)
> +record_full_arch_list_add (record_full_entry &rec)
>  {
> -  if (record_debug > 1)
> -    gdb_printf (gdb_stdlog,
> -		"Process record: record_full_arch_list_add %s.\n",
> -		host_address_to_string (rec));
> -
> -  if (record_full_arch_list_tail)
> -    {
> -      record_full_arch_list_tail->next = rec;
> -      rec->prev = record_full_arch_list_tail;
> -      record_full_arch_list_tail = rec;
> -    }
> -  else
> -    {
> -      record_full_arch_list_head = rec;
> -      record_full_arch_list_tail = rec;
> -    }
> +  record_full_incomplete_instruction.effects.push_back (rec);

Now since this is one line only, do we really need a separate function for this?

>  }
> 
>  /* Return the value storage location of a record entry.  */
> @@ -613,7 +544,6 @@ record_full_get_loc (struct record_full_entry *rec)
>        return rec->u.reg.u.ptr;
>      else
>        return rec->u.reg.u.buf;
> -  case record_full_end:
>    default:
>      gdb_assert_not_reached ("unexpected record_full_entry type");
>      return NULL;
> @@ -625,7 +555,7 @@ record_full_get_loc (struct record_full_entry *rec)
>  int
>  record_full_arch_list_add_reg (struct regcache *regcache, int regnum)
>  {
> -  struct record_full_entry *rec;
> +  record_full_entry rec;
> 
>    if (record_debug > 1)
>      gdb_printf (gdb_stdlog,
> @@ -633,9 +563,9 @@ record_full_arch_list_add_reg (struct regcache
> *regcache, int regnum)
>  		"record list.\n",
>  		regnum);
> 
> -  rec = record_full_reg_alloc (regcache, regnum);
> +  rec = record_full_reg_init (regcache, regnum);
> 
> -  regcache->cooked_read (regnum, record_full_get_loc (rec));
> +  regcache->cooked_read (regnum, record_full_get_loc (&rec));
> 
>    record_full_arch_list_add (rec);
> 
> @@ -648,7 +578,7 @@ record_full_arch_list_add_reg (struct regcache
> *regcache, int regnum)
>  int
>  record_full_arch_list_add_mem (CORE_ADDR addr, int len)
>  {
> -  struct record_full_entry *rec;
> +  record_full_entry rec;
> 
>    if (record_debug > 1)
>      gdb_printf (gdb_stdlog,
> @@ -659,12 +589,12 @@ record_full_arch_list_add_mem (CORE_ADDR
> addr, int len)
>    if (!addr)	/* FIXME: Why?  Some arch must permit it...  */
>      return 0;
> 
> -  rec = record_full_mem_alloc (addr, len);
> +  rec = record_full_mem_init (addr, len);
> 
>    if (record_read_memory (current_inferior ()->arch (), addr,
> -			  record_full_get_loc (rec), len))
> +			  record_full_get_loc (&rec), len))
>      {
> -      record_full_mem_release (rec);
> +      record_full_mem_cleanup (rec);
>        return -1;
>      }
> 
> @@ -673,27 +603,6 @@ record_full_arch_list_add_mem (CORE_ADDR addr,
> int len)
>    return 0;
>  }
> 
> -/* Add a record_full_end type struct record_full_entry to
> -   record_full_arch_list.  */
> -
> -int
> -record_full_arch_list_add_end (void)
> -{
> -  struct record_full_entry *rec;
> -
> -  if (record_debug > 1)
> -    gdb_printf (gdb_stdlog,
> -		"Process record: add end to arch list.\n");
> -
> -  rec = record_full_end_alloc ();
> -  rec->u.end.sigval = GDB_SIGNAL_0;
> -  rec->u.end.insn_num = ++record_full_insn_count;
> -
> -  record_full_arch_list_add (rec);
> -
> -  return 0;
> -}
> -
>  static void
>  record_full_check_insn_num (void)
>  {
> @@ -725,8 +634,7 @@ record_full_message (struct regcache *regcache,
> enum gdb_signal signal)
> 
>    try
>      {
> -      record_full_arch_list_head = NULL;
> -      record_full_arch_list_tail = NULL;
> +      record_full_reset_incomplete ();
> 
>        /* Check record_full_insn_num.  */
>        record_full_check_insn_num ();
> @@ -746,20 +654,16 @@ record_full_message (struct regcache *regcache,
> enum gdb_signal signal)
>  	 the user says something different, like "deliver this signal"
>  	 during the replay mode).
> 
> -	 User should understand that nothing he does during the replay
> -	 mode will change the behavior of the child.  If he tries,
> +	 User should understand that nothing they do during the replay
> +	 mode will change the behavior of the child.  If they try,
>  	 then that is a user error.
> 
>  	 But we should still deliver the signal to gdb during the replay,
>  	 if we delivered it during the recording.  Therefore we should
>  	 record the signal during record_full_wait, not
>  	 record_full_resume.  */
> -      if (record_full_list != &record_full_first)  /* FIXME better way
> -						      to check */
> -	{
> -	  gdb_assert (record_full_list->type == record_full_end);
> -	  record_full_list->u.end.sigval = signal;
> -	}
> +      if (signal != GDB_SIGNAL_0 && !record_full_list.empty ())
> +	record_full_list[record_full_next_insn - 1].sigval = signal;
> 
>        if (signal == GDB_SIGNAL_0
>  	  || !gdbarch_process_record_signal_p (gdbarch))
> @@ -778,13 +682,11 @@ record_full_message (struct regcache *regcache,
> enum gdb_signal signal)
>      }
>    catch (const gdb_exception &ex)
>      {
> -      record_full_list_release (record_full_arch_list_tail);
> +      record_full_reset_incomplete ();
>        throw;
>      }
> 
> -  record_full_list->next = record_full_arch_list_head;
> -  record_full_arch_list_head->prev = record_full_list;
> -  record_full_list = record_full_arch_list_tail;
> +  record_full_save_instruction ();
> 
>    if (record_full_insn_num == record_full_insn_max_num)
>      record_full_list_release_first ();
> @@ -829,9 +731,9 @@ static enum target_stop_reason
> record_full_stop_reason
>     entries and memory entries, followed by an 'end' entry.  */
> 
>  static inline void
> -record_full_exec_insn (struct regcache *regcache,
> -		       struct gdbarch *gdbarch,
> -		       struct record_full_entry *entry)
> +record_full_exec_entry (regcache *regcache,
> +			gdbarch *gdbarch,
> +			record_full_entry *entry)
>  {
>    switch (entry->type)
>      {
> @@ -909,6 +811,15 @@ record_full_exec_insn (struct regcache *regcache,
>      }
>  }

Again function comment.. 😉

> +static inline void
> +record_full_exec_insn (regcache *regcache,
> +		       gdbarch *gdbarch,
> +		       record_full_instruction &insn)
> +{
> +  for (auto &entry : insn.effects)
> +    record_full_exec_entry (regcache, gdbarch, &entry);
> +}
> +
>  static void record_full_restore (struct bfd &cbfd);
> 
>  /* Asynchronous signal handle registered as event loop source for when
> @@ -983,10 +894,7 @@ record_full_open (const char *args, int from_tty)
>    record_preopen ();
> 
>    /* Reset */
> -  record_full_insn_num = 0;
> -  record_full_insn_count = 0;
> -  record_full_list = &record_full_first;
> -  record_full_list->next = NULL;
> +  record_full_reset_history ();
> 
>    bfd *cbfd = get_inferior_core_bfd (current_inferior ());
>    if (cbfd != nullptr)
> @@ -1014,7 +922,7 @@ record_full_base_target::close ()
>    if (record_debug)
>      gdb_printf (gdb_stdlog, "Process record: record_full_close\n");
> 
> -  record_full_list_release (record_full_list);
> +  record_full_reset_history ();
> 
>    /* Release record_full_core_regbuf.  */
>    if (record_full_core_regbuf)
> @@ -1313,7 +1221,6 @@ record_full_wait_1 (struct target_ops *ops,
>        struct gdbarch *gdbarch = regcache->arch ();
>        const address_space *aspace = current_inferior ()->aspace.get ();
>        int continue_flag = 1;
> -      int first_record_full_end = 1;
> 
>        try
>  	{
> @@ -1322,7 +1229,6 @@ record_full_wait_1 (struct target_ops *ops,
>  	  record_full_stop_reason = TARGET_STOPPED_BY_NO_REASON;
>  	  status->set_stopped (GDB_SIGNAL_0);
> 
> -	  /* Check breakpoint when forward execute.  */
>  	  if (execution_direction == EXEC_FORWARD)
>  	    {
>  	      tmp_pc = regcache_read_pc (regcache);
> @@ -1330,8 +1236,7 @@ record_full_wait_1 (struct target_ops *ops,
> 
> &record_full_stop_reason))
>  		{
>  		  if (record_debug)
> -		    gdb_printf (gdb_stdlog,
> -				"Process record: break at %s.\n",
> +		    gdb_printf (gdb_stdlog, "Process record: break at %s.\n",
>  				paddress (gdbarch, tmp_pc));
>  		  goto replay_out;
>  		}
> @@ -1344,10 +1249,10 @@ record_full_wait_1 (struct target_ops *ops,
>  	     the signal.  */
>  	  target_terminal::ours ();
> 
> -	  /* In EXEC_FORWARD mode, record_full_list points to the tail of
> prev
> -	     instruction.  */
> -	  if (execution_direction == EXEC_FORWARD && record_full_list-
> >next)
> -	    record_full_list = record_full_list->next;
> +	  /* In EXEC_FORWARD mode, record_full_next_insn is the next
> +	     instruction to be executed.  */
> +	  if (execution_direction == EXEC_REVERSE)
> +	    record_full_next_insn--;
> 
>  	  /* Loop over the record_full_list, looking for the next place to
>  	     stop.  */
> @@ -1355,108 +1260,92 @@ record_full_wait_1 (struct target_ops *ops,
>  	    {
>  	      /* Check for beginning and end of log.  */
>  	      if (execution_direction == EXEC_REVERSE
> -		  && record_full_list == &record_full_first)
> +		  && record_full_next_insn < 0)
>  		{
>  		  /* Hit beginning of record log in reverse.  */
>  		  status->set_no_history ();
> +		  record_full_next_insn = 0;
>  		  break;
>  		}
>  	      if (execution_direction != EXEC_REVERSE
> -		  && !record_full_list->next)
> +		  && record_full_next_insn == record_full_list.size ())
>  		{
>  		  /* Hit end of record log going forward.  */
>  		  status->set_no_history ();
>  		  break;
>  		}
> 
> -	      record_full_exec_insn (regcache, gdbarch, record_full_list);
> +	      record_full_exec_insn
> +		(regcache, gdbarch,
> +		 record_full_list[record_full_next_insn]);
> 
> -	      if (record_full_list->type == record_full_end)
> +	      /* step */
> +	      if (record_full_resume_step)
>  		{
>  		  if (record_debug > 1)
> -		    gdb_printf
> -		      (gdb_stdlog,
> -		       "Process record: record_full_end %s to "
> -		       "inferior.\n",
> -		       host_address_to_string (record_full_list));
> -
> -		  if (first_record_full_end
> -		      && execution_direction == EXEC_REVERSE)
> -		    {
> -		      /* When reverse execute, the first
> -			 record_full_end is the part of current
> -			 instruction.  */
> -		      first_record_full_end = 0;
> -		    }
> -		  else
> -		    {
> -		      /* In EXEC_REVERSE mode, this is the
> -			 record_full_end of prev instruction.  In
> -			 EXEC_FORWARD mode, this is the
> -			 record_full_end of current instruction.  */
> -		      /* step */
> -		      if (record_full_resume_step)
> -			{
> -			  if (record_debug > 1)
> -			    gdb_printf (gdb_stdlog,
> -					"Process record: step.\n");
> -			  continue_flag = 0;
> -			}
> -
> -		      /* check breakpoint */
> -		      tmp_pc = regcache_read_pc (regcache);
> -		      if (record_check_stopped_by_breakpoint
> -			  (aspace, tmp_pc, &record_full_stop_reason))
> -			{
> -			  if (record_debug)
> -			    gdb_printf (gdb_stdlog,
> -					"Process record: break "
> -					"at %s.\n",
> -					paddress (gdbarch, tmp_pc));
> +		    gdb_printf (gdb_stdlog,
> +				"Process record: step.\n");
> +		  continue_flag = 0;
> +		}
> 
> -			  continue_flag = 0;
> -			}
> +	      /* check breakpoint */
> +	      tmp_pc = regcache_read_pc (regcache);
> +	      if (record_check_stopped_by_breakpoint
> +		  (aspace, tmp_pc, &record_full_stop_reason))
> +		{
> +		  if (record_debug)
> +		    gdb_printf (gdb_stdlog,
> +				"Process record: break "
> +				"at %s.\n",
> +				paddress (gdbarch, tmp_pc));
> 
> -		      if (record_full_stop_reason
> -			  == TARGET_STOPPED_BY_WATCHPOINT)
> -			{
> -			  if (record_debug)
> -			    gdb_printf (gdb_stdlog,
> -					"Process record: hit hw "
> -					"watchpoint.\n");
> -			  continue_flag = 0;
> -			}
> -		      /* Check target signal */
> -		      if (record_full_list->u.end.sigval != GDB_SIGNAL_0)
> -			/* FIXME: better way to check */
> -			continue_flag = 0;
> -		    }
> +		  continue_flag = 0;
>  		}
> 
> -	      if (continue_flag)
> +	      if (record_full_stop_reason
> +		  == TARGET_STOPPED_BY_WATCHPOINT)
>  		{
> -		  if (execution_direction == EXEC_REVERSE)
> -		    {
> -		      if (record_full_list->prev)
> -			record_full_list = record_full_list->prev;
> -		    }
> -		  else
> -		    {
> -		      if (record_full_list->next)
> -			record_full_list = record_full_list->next;
> -		    }
> +		  if (record_debug)
> +		    gdb_printf (gdb_stdlog,
> +				"Process record: hit hw "
> +				"watchpoint.\n");
> +		  continue_flag = 0;
>  		}
> +	      if (record_full_list[record_full_next_insn].sigval.has_value ())
> +		continue_flag = 0;
> +
> +	      if (execution_direction == EXEC_REVERSE)
> +		record_full_next_insn--;
> +	      else
> +		record_full_next_insn++;
>  	    }
>  	  while (continue_flag);
> 
> +	  if (record_full_next_insn < 0)
> +	    {
> +	      gdb_assert (execution_direction == EXEC_REVERSE);
> +	      record_full_next_insn = 0;
> +	    }
> +	  else if (record_full_next_insn > record_full_list.size ())
> +	    {
> +	      gdb_assert (execution_direction == EXEC_FORWARD);
> +	      record_full_next_insn = record_full_list.size ();
> +	    }
> +	  /* Reset the current instruction to point to the one to be replayed
> +	     moving forward.  */
> +	  else if (execution_direction == EXEC_REVERSE)
> +	    record_full_next_insn++;
> +
>  	replay_out:
>  	  if (status->kind () == TARGET_WAITKIND_STOPPED)
>  	    {
> +	      int insn = (execution_direction == EXEC_FORWARD)
> +			 ? record_full_next_insn - 1 : record_full_next_insn;
>  	      if (record_full_get_sig)
>  		status->set_stopped (GDB_SIGNAL_INT);
> -	      else if (record_full_list->u.end.sigval != GDB_SIGNAL_0)
> -		/* FIXME: better way to check */
> -		status->set_stopped (record_full_list->u.end.sigval);
> +	      else if (record_full_list[insn].sigval.has_value ())
> +		status->set_stopped
> +		  (record_full_list[insn].sigval.value ());
>  	      else
>  		status->set_stopped (GDB_SIGNAL_TRAP);
>  	    }
> @@ -1464,12 +1353,9 @@ record_full_wait_1 (struct target_ops *ops,
>        catch (const gdb_exception &ex)
>  	{
>  	  if (execution_direction == EXEC_REVERSE)
> -	    {
> -	      if (record_full_list->next)
> -		record_full_list = record_full_list->next;
> -	    }
> +	    record_full_next_insn++;
>  	  else
> -	    record_full_list = record_full_list->prev;
> +	    record_full_next_insn--;
> 
>  	  throw;
>  	}
> @@ -1557,8 +1443,7 @@ record_full_registers_change (struct regcache
> *regcache, int regnum)
>    /* Check record_full_insn_num.  */
>    record_full_check_insn_num ();
> 
> -  record_full_arch_list_head = NULL;
> -  record_full_arch_list_tail = NULL;
> +  record_full_reset_incomplete ();
> 
>    if (regnum < 0)
>      {
> @@ -1568,7 +1453,7 @@ record_full_registers_change (struct regcache
> *regcache, int regnum)
>  	{
>  	  if (record_full_arch_list_add_reg (regcache, i))
>  	    {
> -	      record_full_list_release (record_full_arch_list_tail);
> +	      record_full_reset_incomplete ();
>  	      error (_("Process record: failed to record execution log."));
>  	    }
>  	}
> @@ -1577,18 +1462,11 @@ record_full_registers_change (struct regcache
> *regcache, int regnum)
>      {
>        if (record_full_arch_list_add_reg (regcache, regnum))
>  	{
> -	  record_full_list_release (record_full_arch_list_tail);
> +	  record_full_reset_incomplete ();
>  	  error (_("Process record: failed to record execution log."));
>  	}
>      }
> -  if (record_full_arch_list_add_end ())
> -    {
> -      record_full_list_release (record_full_arch_list_tail);
> -      error (_("Process record: failed to record execution log."));
> -    }
> -  record_full_list->next = record_full_arch_list_head;
> -  record_full_arch_list_head->prev = record_full_list;
> -  record_full_list = record_full_arch_list_tail;
> +  record_full_save_instruction ();
> 
>    if (record_full_insn_num == record_full_insn_max_num)
>      record_full_list_release_first ();
> @@ -1607,7 +1485,7 @@ record_full_target::store_registers (struct
> regcache *regcache, int regno)
>  	{
>  	  int n;
> 
> -	  /* Let user choose if he wants to write register or not.  */
> +	  /* Let user choose if they want to write register or not.  */

It might be cleaner to move such changes in a separate commit,
since they just make this big patch even bigger.

>  	  if (regno < 0)
>  	    n =
>  	      query (_("Because GDB is in replay mode, changing the "
> @@ -1642,7 +1520,7 @@ record_full_target::store_registers (struct
> regcache *regcache, int regno)
>  	    }
> 
>  	  /* Destroy the record from here forward.  */
> -	  record_full_list_release_following (record_full_list);
> +	  record_full_list_release_following (record_full_next_insn);
>  	}
> 
>        record_full_registers_change (regcache, regno);
> @@ -1675,36 +1553,24 @@ record_full_target::xfer_partial (enum
> target_object object,
>  	    error (_("Process record canceled the operation."));
> 
>  	  /* Destroy the record from here forward.  */
> -	  record_full_list_release_following (record_full_list);
> +	  record_full_list_release_following (record_full_next_insn);
>  	}
> 
>        /* Check record_full_insn_num */
>        record_full_check_insn_num ();
> 
>        /* Record registers change to list as an instruction.  */
> -      record_full_arch_list_head = NULL;
> -      record_full_arch_list_tail = NULL;
> +      record_full_reset_incomplete ();
>        if (record_full_arch_list_add_mem (offset, len))
>  	{
> -	  record_full_list_release (record_full_arch_list_tail);
> -	  if (record_debug)
> -	    gdb_printf (gdb_stdlog,
> -			"Process record: failed to record "
> -			"execution log.");
> -	  return TARGET_XFER_E_IO;
> -	}
> -      if (record_full_arch_list_add_end ())
> -	{
> -	  record_full_list_release (record_full_arch_list_tail);
> +	  record_full_reset_incomplete ();
>  	  if (record_debug)
>  	    gdb_printf (gdb_stdlog,
>  			"Process record: failed to record "
>  			"execution log.");
>  	  return TARGET_XFER_E_IO;
>  	}
> -      record_full_list->next = record_full_arch_list_head;
> -      record_full_arch_list_head->prev = record_full_list;
> -      record_full_list = record_full_arch_list_tail;
> +      record_full_save_instruction ();
> 
>        if (record_full_insn_num == record_full_insn_max_num)
>  	record_full_list_release_first ();
> @@ -1865,9 +1731,11 @@ record_full_base_target::get_bookmark (const
> char *args, int from_tty)
>  {
>    char *ret = NULL;
> 
> +  if (record_full_list.empty ())
> +    return (gdb_byte *) ret;
> +
>    /* Return stringified form of instruction count.  */
> -  if (record_full_list && record_full_list->type == record_full_end)
> -    ret = xstrdup (pulongest (record_full_list->u.end.insn_num));
> +  ret = xstrdup (pulongest
> (record_full_list[record_full_next_insn].insn_num));
> 
>    if (record_debug)
>      {
> @@ -1923,30 +1791,22 @@ record_full_base_target::record_method
> (ptid_t ptid)
>  void
>  record_full_base_target::info_record ()
>  {
> -  struct record_full_entry *p;
> -
>    if (RECORD_FULL_IS_REPLAY)
>      gdb_printf (_("Replay mode:\n"));
>    else
>      gdb_printf (_("Record mode:\n"));
> 
> -  /* Find entry for first actual instruction in the log.  */
> -  for (p = record_full_first.next;
> -       p != NULL && p->type != record_full_end;
> -       p = p->next)
> -    ;
> -
>    /* Do we have a log at all?  */
> -  if (p != NULL && p->type == record_full_end)
> +  if (!record_full_list.empty ())
>      {
>        /* Display instruction number for first instruction in the log.  */
> -      gdb_printf (_("Lowest recorded instruction number is %s.\n"),
> -		  pulongest (p->u.end.insn_num));
> +      gdb_printf (_("Lowest recorded instruction number is %u.\n"),
> +		  record_full_list[0].insn_num);
> 
>        /* If in replay mode, display where we are in the log.  */
>        if (RECORD_FULL_IS_REPLAY)
> -	gdb_printf (_("Current instruction number is %s.\n"),
> -		    pulongest (record_full_list->u.end.insn_num));
> +	gdb_printf (_("Current instruction number is %u.\n"),
> +		    record_full_list[record_full_next_insn].insn_num);
> 
>        /* Display instruction number for last instruction in the log.  */
>        gdb_printf (_("Highest recorded instruction number is %s.\n"),
> @@ -1975,7 +1835,7 @@ record_full_base_target::supports_delete_record
> ()
>  void
>  record_full_base_target::delete_record ()
>  {
> -  record_full_list_release_following (record_full_list);
> +  record_full_reset_history ();
>  }
> 
>  /* The "record_is_replaying" target method.  */
> @@ -2001,23 +1861,23 @@ record_full_base_target::record_will_replay
> (ptid_t ptid, int dir)
>  /* Go to a specific entry.  */
> 
>  static void
> -record_full_goto_entry (struct record_full_entry *p)
> +record_full_goto_entry (size_t target_insn)
>  {
> -  if (p == NULL)
> +  if (target_insn >= record_full_list.size ())
>      error (_("Target insn not found."));
> -  else if (p == record_full_list)
> +  else if (target_insn == record_full_next_insn)
>      error (_("Already at target insn."));
> -  else if (p->u.end.insn_num > record_full_list->u.end.insn_num)
> +  else if (target_insn > record_full_next_insn)
>      {
>        gdb_printf (_("Go forward to insn number %s\n"),
> -		  pulongest (p->u.end.insn_num));
> -      record_full_goto_insn (p, EXEC_FORWARD);
> +		  pulongest (record_full_list[target_insn].insn_num));
> +      record_full_goto_insn (target_insn, EXEC_FORWARD);
>      }
>    else
>      {
>        gdb_printf (_("Go backward to insn number %s\n"),
> -		  pulongest (p->u.end.insn_num));
> -      record_full_goto_insn (p, EXEC_REVERSE);
> +		  pulongest (target_insn));
> +      record_full_goto_insn (target_insn, EXEC_REVERSE);
>      }
> 
>    registers_changed ();
> @@ -2033,13 +1893,7 @@ record_full_goto_entry (struct record_full_entry
> *p)
>  void
>  record_full_base_target::goto_record_begin ()
>  {
> -  struct record_full_entry *p = NULL;
> -
> -  for (p = &record_full_first; p != NULL; p = p->next)
> -    if (p->type == record_full_end)
> -      break;
> -
> -  record_full_goto_entry (p);
> +  record_full_goto_entry (0);
>  }
> 
>  /* The "goto_record_end" target method.  */
> @@ -2047,15 +1901,7 @@ record_full_base_target::goto_record_begin ()
>  void
>  record_full_base_target::goto_record_end ()
>  {
> -  struct record_full_entry *p = NULL;
> -
> -  for (p = record_full_list; p->next != NULL; p = p->next)
> -    ;
> -  for (; p!= NULL; p = p->prev)
> -    if (p->type == record_full_end)
> -      break;
> -
> -  record_full_goto_entry (p);
> +  record_full_goto_entry (record_full_list.size () - 1);
>  }
> 
>  /* The "goto_record" target method.  */
> @@ -2063,13 +1909,7 @@ record_full_base_target::goto_record_end ()
>  void
>  record_full_base_target::goto_record (ULONGEST target_insn)
>  {
> -  struct record_full_entry *p = NULL;
> -
> -  for (p = &record_full_first; p != NULL; p = p->next)
> -    if (p->type == record_full_end && p->u.end.insn_num == target_insn)
> -      break;
> -
> -  record_full_goto_entry (p);
> +  record_full_goto_entry (target_insn);
>  }
> 
>  /* The "record_stop_replaying" target method.  */
> @@ -2339,19 +2179,96 @@ netorder32 (uint32_t input)
>    return ret;
>  }

Function comment. And I think it would be cleaner to move this refactoring in
a separate patch, too. Or is this separate function only possible due to the change
from using a list to std::deque? 

> +static void
> +record_full_read_entry_from_bfd (bfd *cbfd, asection *osec, int *bfd_offset)
> +{
> +  uint8_t rectype;
> +  uint32_t regnum, len;
> +  uint64_t addr;
> +  regcache *cache = get_thread_regcache (inferior_thread ());
> +
> +  bfdcore_read (cbfd, osec, &rectype, sizeof (rectype), bfd_offset);
> +  switch (rectype)
> +    {
> +    case record_full_reg: /* reg */
> +      {
> +	/* Get register number to regnum.  */
> +	bfdcore_read (cbfd, osec, &regnum, sizeof (regnum), bfd_offset);
> +	regnum = netorder32 (regnum);
> +
> +	record_full_entry rec;
> +
> +	rec = record_full_reg_init (cache, regnum);
> +
> +	/* Get val.  */
> +	bfdcore_read (cbfd, osec, record_full_get_loc (&rec),
> +		      rec.u.reg.len, bfd_offset);
> +
> +	if (record_debug)
> +	  gdb_printf (gdb_stdlog,
> +		      "  Reading register %d (1 "
> +		      "plus %lu plus %d bytes)\n",
> +		      rec.u.reg.num,
> +		      (unsigned long) sizeof (regnum),
> +		      rec.u.reg.len);
> +
> +	record_full_arch_list_add (rec);
> +	break;
> +      }
> +
> +    case record_full_mem: /* mem */
> +      {
> +	/* Get len.  */
> +	bfdcore_read (cbfd, osec, &len, sizeof (len), bfd_offset);
> +	len = netorder32 (len);
> +
> +	/* Get addr.  */
> +	bfdcore_read (cbfd, osec, &addr, sizeof (addr),
> +		      bfd_offset);
> +	addr = netorder64 (addr);
> +
> +	record_full_entry rec;
> +	rec = record_full_mem_init (addr, len);
> +
> +	/* Get val.  */
> +	bfdcore_read (cbfd, osec, record_full_get_loc (&rec), len,
> +		      bfd_offset);
> +
> +	if (record_debug)
> +	  gdb_printf (gdb_stdlog,
> +		      "  Reading memory %s (1 plus "
> +		      "%lu plus %lu plus %d bytes)\n",
> +		      paddress (get_current_arch (),
> +				rec.u.mem.addr),
> +		      (unsigned long) sizeof (addr),
> +		      (unsigned long) sizeof (len),
> +		      len);
> +
> +	record_full_arch_list_add (rec);
> +	break;
> +      }
> +
> +    default:
> +      error (_("Bad entry type %d, offset %d of %lu in core file %ps."),
> +	     rectype, *bfd_offset, bfd_section_size (osec),
> +	     styled_string (file_name_style.style (),
> +			    bfd_get_filename (cbfd)));
> +      break;
> +    }
> +}
> +
>  /* Restore the execution log from core file CBFD.  */
> 
>  static void
>  record_full_restore (struct bfd &cbfd)
>  {
>    uint32_t magic;
> -  struct record_full_entry *rec;
>    asection *osec;
>    uint32_t osec_size;
>    int bfd_offset = 0;
> 
>    /* "record_full_restore" can only be called when record list is empty.  */
> -  gdb_assert (record_full_first.next == NULL);
> +  gdb_assert (record_full_list.empty ());
> 
>    if (record_debug)
>      gdb_printf (gdb_stdlog, "Restoring recording from core file.\n");
> @@ -2379,124 +2296,47 @@ record_full_restore (struct bfd &cbfd)
>  		"RECORD_FULL_FILE_MAGIC (0x%s)\n",
>  		phex_nz (netorder32 (magic), 4));
> 
> -  /* Restore the entries in recfd into record_full_arch_list_head and
> -     record_full_arch_list_tail.  */
> -  record_full_arch_list_head = NULL;
> -  record_full_arch_list_tail = NULL;
>    record_full_insn_num = 0;
> 
>    try
>      {
> -      regcache *regcache = get_thread_regcache (inferior_thread ());
> -
> -      while (1)
> +      while (bfd_offset < osec_size)
>  	{
> -	  uint8_t rectype;
> -	  uint32_t regnum, len, signal, count;
> -	  uint64_t addr;
> +	  record_full_reset_incomplete ();
> +	  uint32_t eff_count = 0;
> +	  uint8_t sigval;
> +	  uint32_t insn_num;
> 
> -	  /* We are finished when offset reaches osec_size.  */
> -	  if (bfd_offset >= osec_size)
> -	    break;
> -	  bfdcore_read (&cbfd, osec, &rectype, sizeof (rectype), &bfd_offset);
> +	  /* First read the generic information for an instruction.  */
> +	  bfdcore_read (&cbfd, osec, &sigval, sizeof (uint8_t), &bfd_offset);
> +	  bfdcore_read (&cbfd, osec, &eff_count, sizeof (uint32_t),
> +			&bfd_offset);
> +	  bfdcore_read (&cbfd, osec, &insn_num, sizeof (uint32_t),
> +			&bfd_offset);
> 
> -	  switch (rectype)
> -	    {
> -	    case record_full_reg: /* reg */
> -	      /* Get register number to regnum.  */
> -	      bfdcore_read (&cbfd, osec, &regnum, sizeof (regnum),
> -			    &bfd_offset);
> -	      regnum = netorder32 (regnum);
> -
> -	      rec = record_full_reg_alloc (regcache, regnum);
> -
> -	      /* Get val.  */
> -	      bfdcore_read (&cbfd, osec, record_full_get_loc (rec),
> -			    rec->u.reg.len, &bfd_offset);
> -
> -	      if (record_debug)
> -		gdb_printf (gdb_stdlog,
> -			    "  Reading register %d (1 "
> -			    "plus %lu plus %d bytes)\n",
> -			    rec->u.reg.num,
> -			    (unsigned long) sizeof (regnum),
> -			    rec->u.reg.len);
> -	      break;
> +	  record_full_incomplete_instruction.insn_num = netorder32
> (insn_num);
> +	  if (sigval != GDB_SIGNAL_0)
> +	    record_full_incomplete_instruction.sigval = (gdb_signal) sigval;
> 
> -	    case record_full_mem: /* mem */
> -	      /* Get len.  */
> -	      bfdcore_read (&cbfd, osec, &len, sizeof (len), &bfd_offset);
> -	      len = netorder32 (len);
> -
> -	      /* Get addr.  */
> -	      bfdcore_read (&cbfd, osec, &addr, sizeof (addr), &bfd_offset);
> -	      addr = netorder64 (addr);
> -
> -	      rec = record_full_mem_alloc (addr, len);
> -
> -	      /* Get val.  */
> -	      bfdcore_read (&cbfd, osec, record_full_get_loc (rec),
> -			    rec->u.mem.len, &bfd_offset);
> -
> -	      if (record_debug)
> -		gdb_printf (gdb_stdlog,
> -			    "  Reading memory %s (1 plus "
> -			    "%lu plus %lu plus %d bytes)\n",
> -			    paddress (get_current_arch (),
> -				      rec->u.mem.addr),
> -			    (unsigned long) sizeof (addr),
> -			    (unsigned long) sizeof (len),
> -			    rec->u.mem.len);
> -	      break;
> +	  eff_count = netorder32 (eff_count);
> 
> -	    case record_full_end: /* end */
> -	      rec = record_full_end_alloc ();
> -	      record_full_insn_num ++;
> -
> -	      /* Get signal value.  */
> -	      bfdcore_read (&cbfd, osec, &signal, sizeof (signal),
> -			    &bfd_offset);
> -	      signal = netorder32 (signal);
> -	      rec->u.end.sigval = (enum gdb_signal) signal;
> -
> -	      /* Get insn count.  */
> -	      bfdcore_read (&cbfd, osec, &count, sizeof (count), &bfd_offset);
> -	      count = netorder32 (count);
> -	      rec->u.end.insn_num = count;
> -	      record_full_insn_count = count + 1;
> -	      if (record_debug)
> -		gdb_printf (gdb_stdlog,
> -			    "  Reading record_full_end (1 + "
> -			    "%lu + %lu bytes), offset == %s\n",
> -			    (unsigned long) sizeof (signal),
> -			    (unsigned long) sizeof (count),
> -			    paddress (get_current_arch (),
> -				      bfd_offset));
> -	      break;
> +	  /* This deals with all the side effects.  */
> +	 while (eff_count > 0)
> +	   {
> +	     eff_count--;
> 
> -	    default:
> -	      error (_("Bad entry type in core file %ps."),
> -		     styled_string (file_name_style.style (),
> -				    bfd_get_filename (&cbfd)));
> -	      break;
> -	    }
> +	     record_full_read_entry_from_bfd (&cbfd, osec, &bfd_offset);
> +	   }
> 
> -	  /* Add rec to record arch list.  */
> -	  record_full_arch_list_add (rec);
> +	  record_full_save_instruction ();
>  	}
>      }
>    catch (const gdb_exception &ex)
>      {
> -      record_full_list_release (record_full_arch_list_tail);
> +      record_full_reset_incomplete ();
>        throw;
>      }
> 
> -  /* Add record_full_arch_list_head to the end of record list.  */
> -  record_full_first.next = record_full_arch_list_head;
> -  record_full_arch_list_head->prev = &record_full_first;
> -  record_full_arch_list_tail->next = NULL;
> -  record_full_list = &record_full_first;
> -
>    /* Update record_full_insn_max_num.  */
>    if (record_full_insn_num > record_full_insn_max_num)
>      {
> @@ -2505,6 +2345,10 @@ record_full_restore (struct bfd &cbfd)
>  	       record_full_insn_max_num);
>      }
> 
> +  /* When loading a recording, we'll always start at the oldest possible
> +     instruction, no matter where the original recording was stopped.  */
> +  record_full_next_insn = 0;
> +
>    /* Succeeded.  */
>    gdb_printf (_("Restored records from core file %s.\n"),
>  	      bfd_get_filename (&cbfd));
> @@ -2538,13 +2382,73 @@ cmd_record_full_restore (const char *args, int
> from_tty)
>    record_full_open (nullptr, from_tty);
>  }

Function comment. And similar comment for separate patch.

I believe sth. like a follow-up commit that introduces the functions
record_full_read_entry_from_bfd and record_full_write_entry_to_bfd
would make this much easier to review.

> +static void
> +record_full_write_entry_to_bfd (record_full_entry &entry,
> +				gdb_bfd_ref_ptr obfd,
> +				asection *osec, int *bfd_offset,
> +				gdbarch *gdbarch)
> +{
> +  /* Save entry.  */
> +  uint8_t type;
> +  uint32_t regnum, len;
> +  uint64_t addr;
> +
> +  type = entry.type;
> +  bfdcore_write (obfd.get (), osec, &type, sizeof (type), bfd_offset);
> +
> +  switch (entry.type)
> +    {
> +    case record_full_reg: /* reg */
> +      if (record_debug)
> +	gdb_printf (gdb_stdlog,
> +		    "  Writing register %d (1 "
> +		    "plus %lu plus %d bytes)\n",
> +		    entry.u.reg.num,
> +		    (unsigned long) sizeof (regnum),
> +		    entry.u.reg.len);
> +
> +      /* Write regnum.  */
> +      regnum = netorder32 (entry.u.reg.num);
> +      bfdcore_write (obfd.get (), osec, &regnum, sizeof (regnum), bfd_offset);
> +
> +      /* Write regval.  */
> +      bfdcore_write (obfd.get (), osec, record_full_get_loc (&entry),
> +		     entry.u.reg.len, bfd_offset);
> +      break;
> +
> +    case record_full_mem: /* mem */
> +      if (record_debug)
> +	gdb_printf (gdb_stdlog,
> +		    "  Writing memory %s (1 plus "
> +		    "%lu plus %lu plus %d bytes)\n",
> +		    paddress (gdbarch,
> +			      entry.u.mem.addr),
> +		    (unsigned long) sizeof (addr),
> +		    (unsigned long) sizeof (len),
> +		    entry.u.mem.len);
> +
> +      /* Write memlen.  */
> +      len = netorder32 (entry.u.mem.len);
> +      bfdcore_write (obfd.get (), osec, &len, sizeof (len), bfd_offset);
> +
> +      /* Write memaddr.  */
> +      addr = netorder64 (entry.u.mem.addr);
> +      bfdcore_write (obfd.get (), osec, &addr, sizeof (addr), bfd_offset);
> +
> +      /* Write memval.  */
> +      bfdcore_write (obfd.get (), osec, record_full_get_loc (&entry),
> +		     entry.u.mem.len, bfd_offset);
> +      break;
> +    }
> +
> +}
> +
>  /* Save the execution log to a file.  We use a modified elf corefile
>     format, with an extra section for our data.  */
> 
>  void
>  record_full_base_target::save_record (const char *recfilename)
>  {
> -  struct record_full_entry *cur_record_full_list;
>    uint32_t magic;
>    struct gdbarch *gdbarch;
>    int save_size = 0;
> @@ -2562,9 +2466,6 @@ record_full_base_target::save_record (const char
> *recfilename)
>    /* Arrange to remove the output file on failure.  */
>    gdb::unlinker unlink_file (recfilename);
> 
> -  /* Save the current record entry to "cur_record_full_list".  */
> -  cur_record_full_list = record_full_list;
> -
>    /* Get the values of regcache and gdbarch.  */
>    regcache *regcache = get_thread_regcache (inferior_thread ());
>    gdbarch = regcache->arch ();
> @@ -2574,34 +2475,27 @@ record_full_base_target::save_record (const
> char *recfilename)
>      = record_full_gdb_operation_disable_set ();
> 
>    /* Reverse execute to the begin of record list.  */
> -  while (1)
> -    {
> -      /* Check for beginning and end of log.  */
> -      if (record_full_list == &record_full_first)
> -	break;
> -
> -      record_full_exec_insn (regcache, gdbarch, record_full_list);
> -
> -      if (record_full_list->prev)
> -	record_full_list = record_full_list->prev;
> -    }
> +  for (int i = record_full_next_insn - 1; i >= 0; i--)
> +    record_full_exec_insn (regcache, gdbarch,
> +			   record_full_list[i]);
> 
>    /* Compute the size needed for the extra bfd section.  */
>    save_size = 4;	/* magic cookie */
> -  for (record_full_list = record_full_first.next; record_full_list;
> -       record_full_list = record_full_list->next)
> -    switch (record_full_list->type)
> -      {
> -      case record_full_end:
> -	save_size += 1 + 4 + 4;
> -	break;
> -      case record_full_reg:
> -	save_size += 1 + 4 + record_full_list->u.reg.len;
> -	break;
> -      case record_full_mem:
> -	save_size += 1 + 4 + 8 + record_full_list->u.mem.len;
> -	break;
> -      }
> +  for (int i = record_full_list.size () - 1; i >= 0; i--)
> +    {
> +      /* Number of effects of an instruction.  */
> +      save_size += sizeof (uint32_t) + sizeof (uint8_t) + sizeof (uint32_t);
> +      for (auto &entry : record_full_list[i].effects)
> +	switch (entry.type)
> +	  {
> +	  case record_full_reg:
> +	    save_size += 1 + 4 + entry.u.reg.len;
> +	    break;
> +	  case record_full_mem:
> +	    save_size += 1 + 4 + 8 + entry.u.mem.len;
> +	    break;
> +	  }
> +    }
> 
>    /* Make the new bfd section.  */
>    osec = bfd_make_section_anyway_with_flags (obfd.get (), "precord",
> @@ -2630,108 +2524,32 @@ record_full_base_target::save_record (const
> char *recfilename)
> 
>    /* Save the entries to recfd and forward execute to the end of
>       record list.  */
> -  record_full_list = &record_full_first;
> -  while (1)
> +  for (int i = 0; i < record_full_list.size (); i++)
>      {
> -      /* Save entry.  */
> -      if (record_full_list != &record_full_first)
> +      uint32_t eff_count = (uint32_t) record_full_list[i].effects.size ();
> +      uint32_t insn_num = record_full_list[i].insn_num;
> +      uint8_t sigval = (record_full_list[i].sigval.has_value ())
> +			 ? record_full_list[i].sigval.value ()
> +			 : GDB_SIGNAL_0;
> +
> +      /* Signal.  */
> +      bfdcore_write (obfd.get (), osec, &sigval, sizeof (sigval), &bfd_offset);
> +      eff_count = netorder32 (eff_count);
> +      /* Number of effects.  */
> +      bfdcore_write (obfd.get (), osec, &eff_count, sizeof (eff_count),
> +		     &bfd_offset);
> +      /* Instruction number.  */
> +      bfdcore_write (obfd.get (), osec, &insn_num, sizeof (uint32_t),
> +		     &bfd_offset);
> +
> +      for (auto &entry : record_full_list[i].effects)
>  	{
> -	  uint8_t type;
> -	  uint32_t regnum, len, signal, count;
> -	  uint64_t addr;
> -
> -	  type = record_full_list->type;
> -	  bfdcore_write (obfd.get (), osec, &type, sizeof (type), &bfd_offset);
> -
> -	  switch (record_full_list->type)
> -	    {
> -	    case record_full_reg: /* reg */
> -	      if (record_debug)
> -		gdb_printf (gdb_stdlog,
> -			    "  Writing register %d (1 "
> -			    "plus %lu plus %d bytes)\n",
> -			    record_full_list->u.reg.num,
> -			    (unsigned long) sizeof (regnum),
> -			    record_full_list->u.reg.len);
> -
> -	      /* Write regnum.  */
> -	      regnum = netorder32 (record_full_list->u.reg.num);
> -	      bfdcore_write (obfd.get (), osec, &regnum,
> -			     sizeof (regnum), &bfd_offset);
> -
> -	      /* Write regval.  */
> -	      bfdcore_write (obfd.get (), osec,
> -			     record_full_get_loc (record_full_list),
> -			     record_full_list->u.reg.len, &bfd_offset);
> -	      break;
> -
> -	    case record_full_mem: /* mem */
> -	      if (record_debug)
> -		gdb_printf (gdb_stdlog,
> -			    "  Writing memory %s (1 plus "
> -			    "%lu plus %lu plus %d bytes)\n",
> -			    paddress (gdbarch,
> -				      record_full_list->u.mem.addr),
> -			    (unsigned long) sizeof (addr),
> -			    (unsigned long) sizeof (len),
> -			    record_full_list->u.mem.len);
> -
> -	      /* Write memlen.  */
> -	      len = netorder32 (record_full_list->u.mem.len);
> -	      bfdcore_write (obfd.get (), osec, &len, sizeof (len),
> -			     &bfd_offset);
> -
> -	      /* Write memaddr.  */
> -	      addr = netorder64 (record_full_list->u.mem.addr);
> -	      bfdcore_write (obfd.get (), osec, &addr,
> -			     sizeof (addr), &bfd_offset);
> -
> -	      /* Write memval.  */
> -	      bfdcore_write (obfd.get (), osec,
> -			     record_full_get_loc (record_full_list),
> -			     record_full_list->u.mem.len, &bfd_offset);
> -	      break;
> -
> -	      case record_full_end:
> -		if (record_debug)
> -		  gdb_printf (gdb_stdlog,
> -			      "  Writing record_full_end (1 + "
> -			      "%lu + %lu bytes)\n",
> -			      (unsigned long) sizeof (signal),
> -			      (unsigned long) sizeof (count));
> -		/* Write signal value.  */
> -		signal = netorder32 (record_full_list->u.end.sigval);
> -		bfdcore_write (obfd.get (), osec, &signal,
> -			       sizeof (signal), &bfd_offset);
> -
> -		/* Write insn count.  */
> -		count = netorder32 (record_full_list->u.end.insn_num);
> -		bfdcore_write (obfd.get (), osec, &count,
> -			       sizeof (count), &bfd_offset);
> -		break;
> -	    }
> +	  record_full_write_entry_to_bfd (entry, obfd, osec, &bfd_offset,
> +					  gdbarch);
>  	}
> -
>        /* Execute entry.  */
> -      record_full_exec_insn (regcache, gdbarch, record_full_list);
> -
> -      if (record_full_list->next)
> -	record_full_list = record_full_list->next;
> -      else
> -	break;
> -    }
> -
> -  /* Reverse execute to cur_record_full_list.  */
> -  while (1)
> -    {
> -      /* Check for beginning and end of log.  */
> -      if (record_full_list == cur_record_full_list)
> -	break;
> -
> -      record_full_exec_insn (regcache, gdbarch, record_full_list);
> -
> -      if (record_full_list->prev)
> -	record_full_list = record_full_list->prev;
> +      if (i < record_full_next_insn)
> +	record_full_exec_insn (regcache, gdbarch, record_full_list[i]);
>      }
> 
>    unlink_file.keep ();
> @@ -2742,11 +2560,11 @@ record_full_base_target::save_record (const
> char *recfilename)
>  }
> 
>  /* record_full_goto_insn -- rewind the record log (forward or backward,
> -   depending on DIR) to the given entry, changing the program state
> -   correspondingly.  */
> +   depending on DIR) to the entry in position TARGET_INSN in the history,
> +   changing the program state correspondingly.  */
> 
>  static void
> -record_full_goto_insn (struct record_full_entry *entry,
> +record_full_goto_insn (size_t target_insn,
>  		       enum exec_direction_kind dir)
>  {
>    scoped_restore restore_operation_disable
> @@ -2757,17 +2575,12 @@ record_full_goto_insn (struct record_full_entry
> *entry,
>    /* Assume everything is valid: we will hit the entry,
>       and we will not hit the end of the recording.  */
> 
> -  if (dir == EXEC_FORWARD)
> -    record_full_list = record_full_list->next;
> -
> -  do
> -    {
> -      record_full_exec_insn (regcache, gdbarch, record_full_list);
> -      if (dir == EXEC_REVERSE)
> -	record_full_list = record_full_list->prev;
> -      else
> -	record_full_list = record_full_list->next;
> -    } while (record_full_list != entry);
> +  if (dir == EXEC_REVERSE)
> +    for (int i = record_full_next_insn; i > target_insn; i--)
> +      record_full_exec_insn (regcache, gdbarch, record_full_list[i - 1]);
> +  else
> +    for (int i = record_full_next_insn; i < target_insn; i++)
> +      record_full_exec_insn (regcache, gdbarch, record_full_list[i]);
>  }
> 
>  /* Alias for "target record-full".  */
> @@ -2798,61 +2611,36 @@ set_record_full_insn_max_num (const char
> *args, int from_tty,
>  static void
>  maintenance_print_record_instruction (const char *args, int from_tty)
>  {
> -  struct record_full_entry *to_print = record_full_list;
> +  if (record_full_list.empty ())
> +    error (_("Not enough recorded history"));
> 
> +  int offset = record_full_next_insn - 1;
> +  /* Reduce the offset by 1 if the record_full_next_insn is after the end
> +     so that we show the last recorded instruction instead of crashing.  */
> +  if (offset == record_full_list.size ())
> +    offset--;
>    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"));
> -	}
> +      offset += value_as_long (parse_and_eval (args));
> +      if (offset >= record_full_list.size () || offset < 0)
> +	error (_("Not enough recorded history"));
>      }
> -  gdb_assert (to_print != nullptr);
> +  auto to_print = record_full_list.begin () + offset;
> 
>    gdbarch *arch = current_inferior ()->arch ();
> 
> -  /* 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)
> +  for (auto entry : to_print->effects)
>      {
> -      switch (to_print->type)
> +      switch (entry.type)
>  	{
>  	  case record_full_reg:
>  	    {
> -	      type *regtype = gdbarch_register_type (arch, to_print-
> >u.reg.num);
> +	      type *regtype = gdbarch_register_type (arch, entry.u.reg.num);
>  	      value *val
>  		  = value_from_contents (regtype,
> -					 record_full_get_loc (to_print));
> +					 record_full_get_loc (&entry));
>  	      gdb_printf ("Register %s changed: ",
> -			  gdbarch_register_name (arch, to_print-
> >u.reg.num));
> +			  gdbarch_register_name (arch, entry.u.reg.num));
>  	      struct value_print_options opts;
>  	      get_user_print_options (&opts);
>  	      opts.raw = true;
> @@ -2862,17 +2650,16 @@ maintenance_print_record_instruction (const
> char *args, int from_tty)
>  	    }
>  	  case record_full_mem:
>  	    {
> -	      gdb_byte *b = record_full_get_loc (to_print);
> +	      gdb_byte *b = record_full_get_loc (&entry);
>  	      gdb_printf ("%d bytes of memory at address %s changed from:",
> -			  to_print->u.mem.len,
> -			  print_core_address (arch, to_print->u.mem.addr));
> -	      for (int i = 0; i < to_print->u.mem.len; i++)
> +			  entry.u.mem.len,
> +			  print_core_address (arch, entry.u.mem.addr));
> +	      for (int i = 0; i < entry.u.mem.len; i++)
>  		gdb_printf (" %02x", b[i]);
>  	      gdb_printf ("\n");
>  	      break;
>  	    }
>  	}
> -      to_print = to_print->next;
>      }
>  }
> 
> @@ -2880,11 +2667,6 @@ INIT_GDB_FILE (record_full)
>  {
>    struct cmd_list_element *c;
> 
> -  /* Init record_full_first.  */
> -  record_full_first.prev = NULL;
> -  record_full_first.next = NULL;
> -  record_full_first.type = record_full_end;
> -
>    add_target (record_full_target_info, record_full_open);
>    add_deprecated_target_alias (record_full_target_info, "record");
>    add_target (record_full_core_target_info, record_full_open);
> diff --git a/gdb/record-full.h b/gdb/record-full.h
> index 51effe74560..c327d879a8a 100644
> --- a/gdb/record-full.h
> +++ b/gdb/record-full.h
> @@ -41,7 +41,6 @@ enum record_result
> 
>  extern int record_full_arch_list_add_reg (struct regcache *regcache, int
> num);
>  extern int record_full_arch_list_add_mem (CORE_ADDR addr, int len);
> -extern int record_full_arch_list_add_end (void);
> 
>  /* Returns true if the process record target is open.  */
>  extern int record_full_is_used (void);
> diff --git a/gdb/riscv-tdep.c b/gdb/riscv-tdep.c
> index 6b5c9c02d46..f5b1f66844c 100644
> --- a/gdb/riscv-tdep.c
> +++ b/gdb/riscv-tdep.c
> @@ -5466,8 +5466,5 @@ riscv_process_record (struct gdbarch *gdbarch,
> struct regcache *regcache,
>    if (res != RECORD_SUCCESS)
>      return res;
> 
> -  if (record_full_arch_list_add_end ())
> -    return -1;
> -
>    return 0;
>  }
> diff --git a/gdb/rs6000-tdep.c b/gdb/rs6000-tdep.c
> index e135df94a40..f964889a49f 100644
> --- a/gdb/rs6000-tdep.c
> +++ b/gdb/rs6000-tdep.c
> @@ -7138,8 +7138,6 @@ ppc_process_prefix_instruction (int insn_prefix,
> int insn_suffix,
>    if (record_full_arch_list_add_reg (regcache, PPC_PC_REGNUM))
>      return -1;
> 
> -  if (record_full_arch_list_add_end ())
> -    return -1;
>    return 0;
>  }
> 
> @@ -7447,8 +7445,6 @@ ppc_process_record (struct gdbarch *gdbarch,
> struct regcache *regcache,
> 
>    if (record_full_arch_list_add_reg (regcache, PPC_PC_REGNUM))
>      return -1;
> -  if (record_full_arch_list_add_end ())
> -    return -1;
>    return 0;
>  }
> 
> diff --git a/gdb/s390-linux-tdep.c b/gdb/s390-linux-tdep.c
> index 3cd6b359d13..6bea30f08dc 100644
> --- a/gdb/s390-linux-tdep.c
> +++ b/gdb/s390-linux-tdep.c
> @@ -910,9 +910,6 @@ s390_linux_record_signal (struct gdbarch *gdbarch,
> struct regcache *regcache,
>    if (record_full_arch_list_add_mem (sp, sizeof_rt_sigframe))
>      return -1;
> 
> -  if (record_full_arch_list_add_end ())
> -    return -1;
> -
>    return 0;
>  }
> 
> diff --git a/gdb/s390-tdep.c b/gdb/s390-tdep.c
> index f9d7bdd04e4..36ce659ea15 100644
> --- a/gdb/s390-tdep.c
> +++ b/gdb/s390-tdep.c
> @@ -7019,8 +7019,6 @@ s390_process_record (struct gdbarch *gdbarch,
> struct regcache *regcache,
> 
>    if (record_full_arch_list_add_reg (regcache, S390_PSWA_REGNUM))
>      return -1;
> -  if (record_full_arch_list_add_end ())
> -    return -1;
>    return 0;
>  }
> 
> --
> 2.54.0
> 

Christina

Intel Deutschland GmbH
Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
Tel: +49 89 991 430, www.intel.de
Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
Chairperson of the Supervisory Board: Nicole Lau
Registered Seat: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928

^ permalink raw reply	[flat|nested] 27+ messages in thread

* RE: [PATCH v3 2/7] gdb/record: remove record_full_insn_num
  2026-05-15 16:36 ` [PATCH v3 2/7] gdb/record: remove record_full_insn_num Guinevere Larsen
@ 2026-05-26  8:35   ` Schimpe, Christina
  0 siblings, 0 replies; 27+ messages in thread
From: Schimpe, Christina @ 2026-05-26  8:35 UTC (permalink / raw)
  To: Guinevere Larsen, gdb-patches; +Cc: Thiago Jung Bauermann

> -----Original Message-----
> From: Guinevere Larsen <guinevere@redhat.com>
> Sent: Freitag, 15. Mai 2026 18:37
> To: gdb-patches@sourceware.org
> Cc: Guinevere Larsen <guinevere@redhat.com>; Thiago Jung Bauermann
> <thiago.bauermann@linaro.org>
> Subject: [PATCH v3 2/7] gdb/record: remove record_full_insn_num
> 
> Now that record_full_list uses a deque, we don't need a variable to track the
> amount of items recorded, so it is just dropped.
> 
> Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> ---
>  gdb/record-full.c | 41 ++++++++++++-----------------------------
>  1 file changed, 12 insertions(+), 29 deletions(-)
> 
> diff --git a/gdb/record-full.c b/gdb/record-full.c index
> 02a57726a95..e4dd07dc300 100644
> --- a/gdb/record-full.c
> +++ b/gdb/record-full.c
> @@ -193,8 +193,6 @@ static bool record_full_stop_at_limit = true;
>  /* Maximum allowed number of insns in execution log.  */  static unsigned int
> record_full_insn_max_num
>  	= DEFAULT_RECORD_FULL_INSN_MAX_NUM;
> -/* Actual count of insns presently in execution log.  */ -static unsigned int
> record_full_insn_num = 0;
>  /* Count of insns logged so far (may be larger
>     than count of insns presently in execution log).  */  static ULONGEST
> record_full_insn_count; @@ -462,7 +460,6 @@ record_full_entry_cleanup
> (record_full_entry rec)  static void  record_full_reset_history ()  {
> -  record_full_insn_num = 0;
>    record_full_insn_count = 0;
>    record_full_next_insn = 0;
> 
> @@ -504,9 +501,7 @@ record_full_save_instruction ()  }
> 
>  /* Delete the first instruction from the beginning of the log, to make
> -   room for adding a new instruction at the end of the log.
> -
> -   Note -- this function does not modify record_full_insn_num.  */
> +   room for adding a new instruction at the end of the log.  */
> 
>  static void
>  record_full_list_release_first (void)
> @@ -606,7 +601,7 @@ record_full_arch_list_add_mem (CORE_ADDR addr,
> int len)  static void  record_full_check_insn_num (void)  {
> -  if (record_full_insn_num == record_full_insn_max_num)
> +  if (record_full_list.size () == record_full_insn_max_num)
>      {
>        /* Ask user what to do.  */
>        if (record_full_stop_at_limit)
> @@ -688,10 +683,8 @@ record_full_message (struct regcache *regcache,
> enum gdb_signal signal)
> 
>    record_full_save_instruction ();
> 
> -  if (record_full_insn_num == record_full_insn_max_num)
> +  if (record_full_list.size () == record_full_insn_max_num)
>      record_full_list_release_first ();
> -  else
> -    record_full_insn_num++;
>  }
> 
>  static bool
> @@ -1468,10 +1461,8 @@ record_full_registers_change (struct regcache
> *regcache, int regnum)
>      }
>    record_full_save_instruction ();
> 
> -  if (record_full_insn_num == record_full_insn_max_num)
> +  if (record_full_list.size () == record_full_insn_max_num)
>      record_full_list_release_first ();
> -  else
> -    record_full_insn_num++;
>  }
> 
>  /* "store_registers" method for process record target.  */ @@ -1572,10
> +1563,8 @@ record_full_target::xfer_partial (enum target_object object,
>  	}
>        record_full_save_instruction ();
> 
> -      if (record_full_insn_num == record_full_insn_max_num)
> +      if (record_full_list.size () == record_full_insn_max_num)
>  	record_full_list_release_first ();
> -      else
> -	record_full_insn_num++;
>      }
> 
>    return this->beneath ()->xfer_partial (object, annex, readbuf, writebuf, @@ -
> 1813,8 +1802,8 @@ record_full_base_target::info_record ()
>  		  pulongest (record_full_insn_count));
> 
>        /* Display log count.  */
> -      gdb_printf (_("Log contains %u instructions.\n"),
> -		  record_full_insn_num);
> +      gdb_printf (_("Log contains %lu instructions.\n"),
> +		  (unsigned long int) record_full_list.size ());
>      }
>    else
>      gdb_printf (_("No instructions have been logged.\n")); @@ -2296,8 +2285,6
> @@ record_full_restore (struct bfd &cbfd)
>  		"RECORD_FULL_FILE_MAGIC (0x%s)\n",
>  		phex_nz (netorder32 (magic), 4));
> 
> -  record_full_insn_num = 0;
> -
>    try
>      {
>        while (bfd_offset < osec_size)
> @@ -2338,9 +2325,9 @@ record_full_restore (struct bfd &cbfd)
>      }
> 
>    /* Update record_full_insn_max_num.  */
> -  if (record_full_insn_num > record_full_insn_max_num)
> +  if (record_full_list.size () > record_full_insn_max_num)
>      {
> -      record_full_insn_max_num = record_full_insn_num;
> +      record_full_insn_max_num = record_full_list.size ();
>        warning (_("Auto increase record/replay buffer limit to %u."),
>  	       record_full_insn_max_num);
>      }
> @@ -2595,14 +2582,10 @@ static void
>  set_record_full_insn_max_num (const char *args, int from_tty,
>  			      struct cmd_list_element *c)
>  {
> -  if (record_full_insn_num > record_full_insn_max_num)
> +  if (record_full_list.size () > record_full_insn_max_num)
>      {
> -      /* Count down record_full_insn_num while releasing records from list.  */
> -      while (record_full_insn_num > record_full_insn_max_num)
> -       {
> -	 record_full_list_release_first ();
> -	 record_full_insn_num--;
> -       }
> +      while (record_full_list.size () > record_full_insn_max_num)
> +	record_full_list_release_first ();
>      }
>  }
> 
> --
> 2.54.0
> 


LGTM.

Reviewed-By: Christina Schimpe <christina.schimpe@intel.com>

Christina


Intel Deutschland GmbH
Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
Tel: +49 89 991 430, www.intel.de
Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
Chairperson of the Supervisory Board: Nicole Lau
Registered Seat: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928

^ permalink raw reply	[flat|nested] 27+ messages in thread

* RE: [PATCH v3 3/7] gdb/record: c++ify internal structures of record-full.c
  2026-05-15 16:37 ` [PATCH v3 3/7] gdb/record: c++ify internal structures of record-full.c Guinevere Larsen
@ 2026-05-26  9:00   ` Schimpe, Christina
  2026-05-27 20:13     ` Guinevere Larsen
  0 siblings, 1 reply; 27+ messages in thread
From: Schimpe, Christina @ 2026-05-26  9:00 UTC (permalink / raw)
  To: Guinevere Larsen, gdb-patches; +Cc: Thiago Jung Bauermann

> -----Original Message-----
> From: Guinevere Larsen <guinevere@redhat.com>
> Sent: Freitag, 15. Mai 2026 18:37
> To: gdb-patches@sourceware.org
> Cc: Guinevere Larsen <guinevere@redhat.com>; Thiago Jung Bauermann
> <thiago.bauermann@linaro.org>
> Subject: [PATCH v3 3/7] gdb/record: c++ify internal structures of record-full.c
> 
> This commit adds a constructor, destructor, and some methods to the
> structures record_full_entry, record_full_reg_entry and
> record_full_mem_entry. This is a move to disentangle the internal
> representation of the data and how record-full manipulates it for replaying.
> 
> Along with this change, record_full_entry is changed to use an std::variant,
> since it was basically doing that already, but now we have the stdlibc++ error
> checking to make sure we're only accessing elements we're allowed to.
> 
> Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> ---
>  gdb/record-full.c | 601 +++++++++++++++++++++++++---------------------
>  1 file changed, 321 insertions(+), 280 deletions(-)
> 
> diff --git a/gdb/record-full.c b/gdb/record-full.c index
> e4dd07dc300..24e94eaf5da 100644
> --- a/gdb/record-full.c
> +++ b/gdb/record-full.c
> @@ -51,6 +51,7 @@
>  #include <optional>
>  #include <deque>
>  #include <signal.h>
> +#include <variant>
> 
>  /* This module implements "target record-full", also known as "process
>     record and replay".  This target sits on top of a "normal" target @@ -93,12
> +94,113 @@ struct record_full_mem_entry
>    int len;
>    /* Set this flag if target memory for this entry
>       can no longer be accessed.  */
> -  int mem_entry_not_accessible;
> +  bool mem_entry_accessible;

Shouldn't we update the comment then, too ?
Nit: you could consider moving this into a separate patch. I would make the review a bit easier IMO.

The same applies for each structure/class that you refactor in this patch. 
But this is rather an optional suggestion, too. 😊 

>    union
>    {
>      gdb_byte *ptr;
>      gdb_byte buf[sizeof (gdb_byte *)];
>    } u;
> +
> +  record_full_mem_entry () : addr (0), len (0) { }

Any specific reason why you don't init mem_entry_accessible and u here ?

> +
> +  record_full_mem_entry (CORE_ADDR mem_addr, int mem_len)  {
> +    addr = mem_addr;
> +    len = mem_len;
> +    if (len > sizeof (u.buf))
> +      u.ptr = new gdb_byte[len];
> +    mem_entry_accessible = true;
> +  }
> +
> +  record_full_mem_entry (record_full_mem_entry &&other)  {
> +    addr = other.addr;
> +    len = other.len;
> +    memcpy (u.buf, other.u.buf, sizeof (u.buf));
> +    mem_entry_accessible = other.mem_entry_accessible;
> +    /* With len == 0, OTHER is guaranteed to not try to free the
> +       memory when it is destructed.  */
> +    other.len = 0;
> +  }
> +
> +  ~record_full_mem_entry ()
> +  {
> +    if (len > sizeof (u.buf))
> +      delete[] u.ptr;
> +  }
> +
> +  record_full_mem_entry &operator= (record_full_mem_entry &&other)  {
> +    addr = other.addr;
> +    len = other.len;
> +    memcpy (u.buf, other.u.buf, sizeof (u.buf));

Shouldn't we check for self-assignment here ?

Also, like the destructor, I believe we must delete u.ptr here:
~~~
  if (len > sizeof (u.buf))
      delete[] u.ptr;
~~~

Same feedback for record_full_mem_entry &operator=

> +    mem_entry_accessible = other.mem_entry_accessible;
> +    /* With len == 0, OTHER is guaranteed to not try to free the
> +       memory when it is destructed.  */
> +    other.len = 0;
> +    return *this;
> +  }
> +
> +  DISABLE_COPY_AND_ASSIGN (record_full_mem_entry);
> +
> +  gdb_byte *get_loc ()

I don't think this is too important, but since I saw it: 
This should be const I think.

IIUC we should then also use a const reference in loops which call get_loc (), 
like this one:

  for (auto &entry : to_print->effects)

And the same applies for type, reg and mem. 

> +  {
> +    if (len > sizeof (u.buf))
> +      return u.ptr;
> +    else
> +      return u.buf;
> +  }
> +
> +  bool execute (gdbarch *gdbarch)
> +  {
> +    /* Nothing to do if the memory is flagged not_accessible.  */
> +    if (!mem_entry_accessible)
> +      return false;
> +
> +    gdb::byte_vector buf (len);
> +
> +    if (record_debug > 1)
> +      gdb_printf (gdb_stdlog,
> +		  "Process record: record_full_mem %s to "
> +		  "inferior addr = %s len = %d.\n",
> +		  host_address_to_string (this),
> +		  paddress (gdbarch, addr),
> +		  len);
> +
> +    if (record_read_memory (gdbarch, addr, buf.data (), len))
> +      mem_entry_accessible = false;
> +    else
> +      {
> +	if (target_write_memory (addr,
> +				 get_loc (),
> +				 len))
> +	  {
> +	    mem_entry_accessible = false;
> +	    if (record_debug)
> +	      warning (_("Process record: error writing memory at "
> +			 "addr = %s len = %d."),
> +		       paddress (gdbarch, addr),
> +		       len);
> +	  }
> +	else
> +	  {
> +	    memcpy (get_loc (), buf.data (), len);
> +
> +	    /* We've changed memory --- check if a hardware
> +	       watchpoint should trap.  Note that this
> +	       presently assumes the target beneath supports
> +	       continuable watchpoints.  On non-continuable
> +	       watchpoints target, we'll want to check this
> +	       _before_ actually doing the memory change, and
> +	       not doing the change at all if the watchpoint
> +	       traps.  */
> +	    if (hardware_watchpoint_inserted_in_range
> +		(current_inferior ()->aspace.get (), addr, len))
> +	      return true;
> +	  }
> +      }
> +    return false;
> +  }
>  };
> 
>  struct record_full_reg_entry
> @@ -110,6 +212,70 @@ struct record_full_reg_entry
>      gdb_byte *ptr;
>      gdb_byte buf[2 * sizeof (gdb_byte *)];
>    } u;
> +
> +  record_full_reg_entry () : num (0), len (0) { }
> +
> +  record_full_reg_entry (gdbarch *gdbarch, int regnum)  {
> +    num = regnum;
> +    len = register_size (gdbarch, regnum);
> +    if (len > sizeof (u.buf))
> +      u.ptr = new gdb_byte[len];
> +  }
> +
> +  record_full_reg_entry (record_full_reg_entry &&other)  {
> +    num = other.num;
> +    len = other.len;
> +    memcpy (u.buf, other.u.buf, sizeof (u.buf));
> +    /* With len == 0, OTHER is guaranteed to not try to free the
> +       memory when it is destructed.  */
> +    other.len = 0;
> +  }
> +
> +  ~record_full_reg_entry ()
> +  {
> +    if (len > sizeof (u.buf))
> +      delete[] u.ptr;
> +  }
> +
> +  record_full_reg_entry &operator=(record_full_reg_entry &&other)  {
> +    num = other.num;
> +    len = other.len;
> +    memcpy (u.buf, other.u.buf, sizeof (u.buf));
> +    /* With len == 0, OTHER is guaranteed to not try to free the
> +       memory when it is destructed.  */
> +    other.len = 0;
> +    return *this;
> +  }
> +
> +  DISABLE_COPY_AND_ASSIGN (record_full_reg_entry);
> +
> +  gdb_byte *get_loc ()
> +  {
> +    if (len > sizeof (u.buf))
> +      return u.ptr;
> +    else
> +      return u.buf;
> +  }
> +
> +  bool execute (regcache *regcache)
> +  {
> +    gdb::byte_vector buf (len);
> +
> +    if (record_debug > 1)
> +      gdb_printf (gdb_stdlog,
> +		  "Process record: record_full_reg %s to "
> +		  "inferior num = %d.\n",
> +		  host_address_to_string (this),
> +		  num);
> +
> +    regcache->cooked_read (num, buf);
> +    regcache->cooked_write (num, get_loc ());
> +    memcpy (get_loc (), buf.data (), len);
> +    return false;
> +  }
>  };
> 
>  enum record_full_type
> @@ -118,16 +284,88 @@ enum record_full_type
>    record_full_mem
>  };
> 
> -struct record_full_entry
> +class record_full_entry
>  {
> -  enum record_full_type type;
> -  union
> +  std::variant<record_full_reg_entry, record_full_mem_entry> entry;
> +
> +public:
> +  record_full_entry () : entry (record_full_reg_entry ()) {}
> +
> +  /* Constructor for a register entry.  Type is here to make it
> +     easier to recognize it in the constructor calls, it isn't
> +     actually important.  */
> +  record_full_entry (record_full_type reg_type, gdbarch *gdbarch,
> +		     int regnum)
> +  : entry(record_full_reg_entry (gdbarch, regnum))
>    {
> -    /* reg */
> -    struct record_full_reg_entry reg;
> -    /* mem */
> -    struct record_full_mem_entry mem;
> -  } u;
> +    gdb_assert (reg_type == record_full_reg);  }
> +
> +  record_full_entry (record_full_type mem_type, CORE_ADDR addr, int
> + len)
> +  : entry(record_full_mem_entry (addr, len))  {
> +    gdb_assert (mem_type == record_full_mem);  }
> +
> +  record_full_entry (record_full_entry &&other)
> +  : entry (std::move (other.entry))
> +  {
> +  }
> +
> +  DISABLE_COPY_AND_ASSIGN (record_full_entry);
> +
> +  record_full_reg_entry& reg ()
> +  {
> +    gdb_assert (type () == record_full_reg);
> +    return std::get<record_full_reg_entry> (entry);  }
> +
> +  record_full_mem_entry& mem ()
> +  {
> +    gdb_assert (type () == record_full_mem);
> +    return std::get<record_full_mem_entry> (entry);  }
> +
> +  record_full_type type ()
> +  {
> +    switch (entry.index ())
> +    {
> +    case 0:
> +      return record_full_reg;
> +    case 1:
> +      return record_full_mem;
> +    }
> +    gdb_assert_not_reached ("Impossible variant index");  }
> +
> +  /* Get the pointer to the data stored by this entry.  */  gdb_byte
> + *get_loc ()  {
> +    switch (type ())
> +    {
> +    case record_full_reg:
> +      return reg ().get_loc ();
> +    case record_full_mem:
> +      return mem ().get_loc ();
> +    }
> +    gdb_assert_not_reached ("Impossible entry type");  }
> +
> +  /* Execute this entry, swapping the appropriate values from memory or
> +     register and the recorded ones.  Returns TRUE if the execution was
> +     stopped by a watchpoint.  */
> +
> +  bool execute (regcache *regcache)
> +  {
> +    switch (type ())
> +      {
> +      case record_full_reg:
> +	return reg ().execute (regcache);
> +      case record_full_mem:
> +	return mem ().execute (regcache->arch ());
> +      }
> +    return false;
> +  }
>  };
> 
>  /* This is the main structure that comprises the execution log.
> @@ -384,91 +622,12 @@ static struct cmd_list_element
> *record_full_cmdlist;  static void record_full_goto_insn (size_t target_insn,
>  				   enum exec_direction_kind dir);
> 
> -/* Initialization and cleanup functions for record_full_reg and
> -   record_full_mem entries.  */
> -
> -/* Init a record_full_reg record entry.  */
> -
> -static inline record_full_entry
> -record_full_reg_init (struct regcache *regcache, int regnum) -{
> -  record_full_entry rec;
> -  struct gdbarch *gdbarch = regcache->arch ();
> -
> -  rec.type = record_full_reg;
> -  rec.u.reg.num = regnum;
> -  rec.u.reg.len = register_size (gdbarch, regnum);
> -  if (rec.u.reg.len > sizeof (rec.u.reg.u.buf))
> -    rec.u.reg.u.ptr = (gdb_byte *) xmalloc (rec.u.reg.len);
> -
> -  return rec;
> -}
> -
> -/* Cleanup a record_full_reg record entry.  */
> -
> -static inline void
> -record_full_reg_cleanup (record_full_entry rec) -{
> -  gdb_assert (rec.type == record_full_reg);
> -  if (rec.u.reg.len > sizeof (rec.u.reg.u.buf))
> -    xfree (rec.u.reg.u.ptr);
> -}
> -
> -/* Init a record_full_mem record entry.  */
> -
> -static inline record_full_entry
> -record_full_mem_init (CORE_ADDR addr, int len) -{
> -  record_full_entry rec;
> -
> -  rec.type = record_full_mem;
> -  rec.u.mem.addr = addr;
> -  rec.u.mem.len = len;
> -  if (rec.u.mem.len > sizeof (rec.u.mem.u.buf))
> -    rec.u.mem.u.ptr = (gdb_byte *) xmalloc (len);
> -  rec.u.mem.mem_entry_not_accessible = 0;
> -
> -  return rec;
> -}
> -
> -/* Cleanup a record_full_mem record entry.  */
> -
> -static inline void
> -record_full_mem_cleanup (record_full_entry rec) -{
> -  gdb_assert (rec.type == record_full_mem);
> -  if (rec.u.mem.len > sizeof (rec.u.mem.u.buf))
> -    xfree (rec.u.mem.u.ptr);
> -}
> -
> -/* Free one record entry, any type.  */
> -
> -static inline void
> -record_full_entry_cleanup (record_full_entry rec) -{
> -
> -  switch (rec.type) {
> -  case record_full_reg:
> -    record_full_reg_cleanup (rec);
> -    break;
> -  case record_full_mem:
> -    record_full_mem_cleanup (rec);
> -    break;
> -  }
> -}
> -
>  static void
>  record_full_reset_history ()
>  {
>    record_full_insn_count = 0;
>    record_full_next_insn = 0;
> 
> -  for (auto &insn : record_full_list)
> -    {
> -      for (auto &entry : insn.effects)
> -	record_full_entry_cleanup (entry);
> -    }
> -
>    record_full_list.clear ();
>  }
> 
> @@ -476,11 +635,7 @@ static void
>  record_full_list_release_following (int index)  {
>    for (int i = record_full_list.size () - 1; i > index; i--)
> -    {
> -      for (auto &entry : record_full_list[i].effects)
> -	record_full_entry_cleanup (entry);
> -      record_full_list.pop_back ();
> -    }
> +    record_full_list.pop_back ();
>    /* Set the next instruction to be past the end of the log so we
>       start recording if the user moves forward again.  */
>    record_full_next_insn = index;
> @@ -509,9 +664,6 @@ record_full_list_release_first (void)
>    if (record_full_list.empty ())
>      return;
> 
> -  for (auto &entry : record_full_list[0].effects)
> -    record_full_entry_cleanup (entry);
> -
>    record_full_list.pop_front ();
>    --record_full_next_insn;
>  }
> @@ -521,28 +673,7 @@ record_full_list_release_first (void)  static void
> record_full_arch_list_add (record_full_entry &rec)  {
> -  record_full_incomplete_instruction.effects.push_back (rec); -}
> -
> -/* Return the value storage location of a record entry.  */ -static inline
> gdb_byte * -record_full_get_loc (struct record_full_entry *rec) -{
> -  switch (rec->type) {
> -  case record_full_mem:
> -    if (rec->u.mem.len > sizeof (rec->u.mem.u.buf))
> -      return rec->u.mem.u.ptr;
> -    else
> -      return rec->u.mem.u.buf;
> -  case record_full_reg:
> -    if (rec->u.reg.len > sizeof (rec->u.reg.u.buf))
> -      return rec->u.reg.u.ptr;
> -    else
> -      return rec->u.reg.u.buf;
> -  default:
> -    gdb_assert_not_reached ("unexpected record_full_entry type");
> -    return NULL;
> -  }
> +  record_full_incomplete_instruction.effects.push_back (std::move
> + (rec));
>  }
> 
>  /* Record the value of a register NUM to record_full_arch_list.  */ @@ -550,7
> +681,7 @@ record_full_get_loc (struct record_full_entry *rec)  int
> record_full_arch_list_add_reg (struct regcache *regcache, int regnum)  {
> -  record_full_entry rec;
> +  record_full_entry rec (record_full_reg, regcache->arch (), regnum);
> 
>    if (record_debug > 1)
>      gdb_printf (gdb_stdlog,
> @@ -558,9 +689,7 @@ record_full_arch_list_add_reg (struct regcache
> *regcache, int regnum)
>  		"record list.\n",
>  		regnum);
> 
> -  rec = record_full_reg_init (regcache, regnum);
> -
> -  regcache->cooked_read (regnum, record_full_get_loc (&rec));
> +  regcache->cooked_read (regnum, rec.get_loc ());
> 
>    record_full_arch_list_add (rec);
> 
> @@ -573,7 +702,7 @@ record_full_arch_list_add_reg (struct regcache
> *regcache, int regnum)  int  record_full_arch_list_add_mem (CORE_ADDR
> addr, int len)  {
> -  record_full_entry rec;
> +  record_full_entry rec (record_full_mem, addr, len);
> 
>    if (record_debug > 1)
>      gdb_printf (gdb_stdlog,
> @@ -584,14 +713,9 @@ record_full_arch_list_add_mem (CORE_ADDR addr,
> int len)
>    if (!addr)	/* FIXME: Why?  Some arch must permit it...  */
>      return 0;
> 
> -  rec = record_full_mem_init (addr, len);
> -
>    if (record_read_memory (current_inferior ()->arch (), addr,
> -			  record_full_get_loc (&rec), len))
> -    {
> -      record_full_mem_cleanup (rec);
> -      return -1;
> -    }
> +			  rec.get_loc (), len))
> +    return -1;
> 
>    record_full_arch_list_add (rec);
> 
> @@ -719,98 +843,14 @@ record_full_gdb_operation_disable_set (void)
> static enum target_stop_reason record_full_stop_reason
>    = TARGET_STOPPED_BY_NO_REASON;
> 
> -/* Execute one instruction from the record log.  Each instruction in
> -   the log will be represented by an arbitrary sequence of register
> -   entries and memory entries, followed by an 'end' entry.  */
> -
> -static inline void
> -record_full_exec_entry (regcache *regcache,
> -			gdbarch *gdbarch,
> -			record_full_entry *entry)
> -{
> -  switch (entry->type)
> -    {
> -    case record_full_reg: /* reg */
> -      {
> -	gdb::byte_vector reg (entry->u.reg.len);
> -
> -	if (record_debug > 1)
> -	  gdb_printf (gdb_stdlog,
> -		      "Process record: record_full_reg %s to "
> -		      "inferior num = %d.\n",
> -		      host_address_to_string (entry),
> -		      entry->u.reg.num);
> -
> -	regcache->cooked_read (entry->u.reg.num, reg.data ());
> -	regcache->cooked_write (entry->u.reg.num, record_full_get_loc
> (entry));
> -	memcpy (record_full_get_loc (entry), reg.data (), entry->u.reg.len);
> -      }
> -      break;
> -
> -    case record_full_mem: /* mem */
> -      {
> -	/* Nothing to do if the entry is flagged not_accessible.  */
> -	if (!entry->u.mem.mem_entry_not_accessible)
> -	  {
> -	    gdb::byte_vector mem (entry->u.mem.len);
> -
> -	    if (record_debug > 1)
> -	      gdb_printf (gdb_stdlog,
> -			  "Process record: record_full_mem %s to "
> -			  "inferior addr = %s len = %d.\n",
> -			  host_address_to_string (entry),
> -			  paddress (gdbarch, entry->u.mem.addr),
> -			  entry->u.mem.len);
> -
> -	    if (record_read_memory (gdbarch,
> -				    entry->u.mem.addr, mem.data (),
> -				    entry->u.mem.len))
> -	      entry->u.mem.mem_entry_not_accessible = 1;
> -	    else
> -	      {
> -		if (target_write_memory (entry->u.mem.addr,
> -					 record_full_get_loc (entry),
> -					 entry->u.mem.len))
> -		  {
> -		    entry->u.mem.mem_entry_not_accessible = 1;
> -		    if (record_debug)
> -		      warning (_("Process record: error writing memory at "
> -				 "addr = %s len = %d."),
> -			       paddress (gdbarch, entry->u.mem.addr),
> -			       entry->u.mem.len);
> -		  }
> -		else
> -		  {
> -		    memcpy (record_full_get_loc (entry), mem.data (),
> -			    entry->u.mem.len);
> -
> -		    /* We've changed memory --- check if a hardware
> -		       watchpoint should trap.  Note that this
> -		       presently assumes the target beneath supports
> -		       continuable watchpoints.  On non-continuable
> -		       watchpoints target, we'll want to check this
> -		       _before_ actually doing the memory change, and
> -		       not doing the change at all if the watchpoint
> -		       traps.  */
> -		    if (hardware_watchpoint_inserted_in_range
> -			(current_inferior ()->aspace.get (),
> -			 entry->u.mem.addr, entry->u.mem.len))
> -		      record_full_stop_reason =
> TARGET_STOPPED_BY_WATCHPOINT;
> -		  }
> -	      }
> -	  }
> -      }
> -      break;
> -    }
> -}
> -
>  static inline void
>  record_full_exec_insn (regcache *regcache,
>  		       gdbarch *gdbarch,
>  		       record_full_instruction &insn)  {
>    for (auto &entry : insn.effects)
> -    record_full_exec_entry (regcache, gdbarch, &entry);
> +    if (entry.execute (regcache))
> +      record_full_stop_reason = TARGET_STOPPED_BY_WATCHPOINT;
>  }
> 
>  static void record_full_restore (struct bfd &cbfd); @@ -2185,21 +2225,19
> @@ record_full_read_entry_from_bfd (bfd *cbfd, asection *osec, int
> *bfd_offset)
>  	bfdcore_read (cbfd, osec, &regnum, sizeof (regnum), bfd_offset);
>  	regnum = netorder32 (regnum);
> 
> -	record_full_entry rec;
> -
> -	rec = record_full_reg_init (cache, regnum);
> +	record_full_entry rec (record_full_reg, cache->arch (), regnum);
> 
>  	/* Get val.  */
> -	bfdcore_read (cbfd, osec, record_full_get_loc (&rec),
> -		      rec.u.reg.len, bfd_offset);
> +	bfdcore_read (cbfd, osec, rec.get_loc (),
> +		      rec.reg ().len, bfd_offset);
> 
>  	if (record_debug)
>  	  gdb_printf (gdb_stdlog,
>  		      "  Reading register %d (1 "
>  		      "plus %lu plus %d bytes)\n",
> -		      rec.u.reg.num,
> +		      rec.reg ().num,
>  		      (unsigned long) sizeof (regnum),
> -		      rec.u.reg.len);
> +		      rec.reg ().len);
> 
>  	record_full_arch_list_add (rec);
>  	break;
> @@ -2216,19 +2254,17 @@ record_full_read_entry_from_bfd (bfd *cbfd,
> asection *osec, int *bfd_offset)
>  		      bfd_offset);
>  	addr = netorder64 (addr);
> 
> -	record_full_entry rec;
> -	rec = record_full_mem_init (addr, len);
> +	record_full_entry rec (record_full_mem, addr, len);
> 
>  	/* Get val.  */
> -	bfdcore_read (cbfd, osec, record_full_get_loc (&rec), len,
> -		      bfd_offset);
> +	bfdcore_read (cbfd, osec, rec.get_loc (), len, bfd_offset);
> 
>  	if (record_debug)
>  	  gdb_printf (gdb_stdlog,
>  		      "  Reading memory %s (1 plus "
>  		      "%lu plus %lu plus %d bytes)\n",
>  		      paddress (get_current_arch (),
> -				rec.u.mem.addr),
> +				rec.mem ().addr),
>  		      (unsigned long) sizeof (addr),
>  		      (unsigned long) sizeof (len),
>  		      len);
> @@ -2380,52 +2416,56 @@ record_full_write_entry_to_bfd
> (record_full_entry &entry,
>    uint32_t regnum, len;
>    uint64_t addr;
> 
> -  type = entry.type;
> +  type = entry.type ();
>    bfdcore_write (obfd.get (), osec, &type, sizeof (type), bfd_offset);
> 
> -  switch (entry.type)
> +  switch (type)
>      {
>      case record_full_reg: /* reg */
> -      if (record_debug)
> -	gdb_printf (gdb_stdlog,
> -		    "  Writing register %d (1 "
> -		    "plus %lu plus %d bytes)\n",
> -		    entry.u.reg.num,
> -		    (unsigned long) sizeof (regnum),
> -		    entry.u.reg.len);
> -
> -      /* Write regnum.  */
> -      regnum = netorder32 (entry.u.reg.num);
> -      bfdcore_write (obfd.get (), osec, &regnum, sizeof (regnum), bfd_offset);
> -
> -      /* Write regval.  */
> -      bfdcore_write (obfd.get (), osec, record_full_get_loc (&entry),
> -		     entry.u.reg.len, bfd_offset);
> -      break;
> +      {
> +	auto &reg = entry.reg ();
> +	if (record_debug)
> +	  gdb_printf (gdb_stdlog,
> +		      "  Writing register %d (1 "
> +		      "plus %lu plus %d bytes)\n",
> +		      reg.num,
> +		      (unsigned long) sizeof (regnum),
> +		      reg.len);
> +
> +	/* Write regnum.  */
> +	regnum = netorder32 (reg.num);
> +	bfdcore_write (obfd.get (), osec, &regnum, sizeof (regnum),
> +bfd_offset);
> +
> +	/* Write regval.  */
> +	bfdcore_write (obfd.get (), osec, entry.get_loc (), reg.len, bfd_offset);
> +	break;
> +      }
> 
>      case record_full_mem: /* mem */
> -      if (record_debug)
> -	gdb_printf (gdb_stdlog,
> -		    "  Writing memory %s (1 plus "
> -		    "%lu plus %lu plus %d bytes)\n",
> -		    paddress (gdbarch,
> -			      entry.u.mem.addr),
> -		    (unsigned long) sizeof (addr),
> -		    (unsigned long) sizeof (len),
> -		    entry.u.mem.len);
> -
> -      /* Write memlen.  */
> -      len = netorder32 (entry.u.mem.len);
> -      bfdcore_write (obfd.get (), osec, &len, sizeof (len), bfd_offset);
> -
> -      /* Write memaddr.  */
> -      addr = netorder64 (entry.u.mem.addr);
> -      bfdcore_write (obfd.get (), osec, &addr, sizeof (addr), bfd_offset);
> -
> -      /* Write memval.  */
> -      bfdcore_write (obfd.get (), osec, record_full_get_loc (&entry),
> -		     entry.u.mem.len, bfd_offset);
> -      break;
> +      {
> +	auto &mem = entry.mem ();
> +	if (record_debug)
> +	  gdb_printf (gdb_stdlog,
> +		      "  Writing memory %s (1 plus "
> +		      "%lu plus %lu plus %d bytes)\n",
> +		      paddress (gdbarch, mem.addr),
> +		      (unsigned long) sizeof (addr),
> +		      (unsigned long) sizeof (len),
> +		      mem.len);
> +
> +	/* Write memlen.  */
> +	len = netorder32 (mem.len);
> +	bfdcore_write (obfd.get (), osec, &len, sizeof (len), bfd_offset);
> +
> +	/* Write memaddr.  */
> +	addr = netorder64 (mem.addr);
> +	bfdcore_write (obfd.get (), osec, &addr, sizeof (addr), bfd_offset);
> +
> +	/* Write memval.  */
> +	bfdcore_write (obfd.get (), osec, entry.get_loc (), mem.len,
> +		       bfd_offset);
> +	break;
> +      }
>      }
> 
>  }
> @@ -2473,13 +2513,13 @@ record_full_base_target::save_record (const
> char *recfilename)
>        /* Number of effects of an instruction.  */
>        save_size += sizeof (uint32_t) + sizeof (uint8_t) + sizeof (uint32_t);
>        for (auto &entry : record_full_list[i].effects)
> -	switch (entry.type)
> +	switch (entry.type ())
>  	  {
>  	  case record_full_reg:
> -	    save_size += 1 + 4 + entry.u.reg.len;
> +	    save_size += 1 + 4 + entry.reg ().len;
>  	    break;
>  	  case record_full_mem:
> -	    save_size += 1 + 4 + 8 + entry.u.mem.len;
> +	    save_size += 1 + 4 + 8 + entry.mem ().len;
>  	    break;
>  	  }
>      }
> @@ -2612,18 +2652,18 @@ maintenance_print_record_instruction (const
> char *args, int from_tty)
> 
>    gdbarch *arch = current_inferior ()->arch ();
> 
> -  for (auto entry : to_print->effects)
> +  for (auto &entry : to_print->effects)
>      {
> -      switch (entry.type)
> +      switch (entry.type ())
>  	{
>  	  case record_full_reg:
>  	    {
> -	      type *regtype = gdbarch_register_type (arch, entry.u.reg.num);
> +	      type *regtype = gdbarch_register_type (arch, entry.reg ().num);
>  	      value *val
>  		  = value_from_contents (regtype,
> -					 record_full_get_loc (&entry));
> +					 entry.get_loc ());
>  	      gdb_printf ("Register %s changed: ",
> -			  gdbarch_register_name (arch, entry.u.reg.num));
> +			  gdbarch_register_name (arch, entry.reg ().num));
>  	      struct value_print_options opts;
>  	      get_user_print_options (&opts);
>  	      opts.raw = true;
> @@ -2633,11 +2673,12 @@ maintenance_print_record_instruction (const
> char *args, int from_tty)
>  	    }
>  	  case record_full_mem:
>  	    {
> -	      gdb_byte *b = record_full_get_loc (&entry);
> +	      record_full_mem_entry& mem = entry.mem ();
> +	      gdb_byte *b = entry.get_loc ();
>  	      gdb_printf ("%d bytes of memory at address %s changed from:",
> -			  entry.u.mem.len,
> -			  print_core_address (arch, entry.u.mem.addr));
> -	      for (int i = 0; i < entry.u.mem.len; i++)
> +			  mem.len,
> +			  print_core_address (arch, mem.addr));
> +	      for (int i = 0; i < mem.len; i++)
>  		gdb_printf (" %02x", b[i]);
>  	      gdb_printf ("\n");
>  	      break;
> --
> 2.54.0
> 

Christina
Intel Deutschland GmbH
Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
Tel: +49 89 991 430, www.intel.de
Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
Chairperson of the Supervisory Board: Nicole Lau
Registered Seat: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928

^ permalink raw reply	[flat|nested] 27+ messages in thread

* RE: [PATCH v3 4/7] gdb/record: make record_full_history more c++-like
  2026-05-15 16:37 ` [PATCH v3 4/7] gdb/record: make record_full_history more c++-like Guinevere Larsen
  2026-05-16  1:03   ` Thiago Jung Bauermann
@ 2026-05-26  9:04   ` Schimpe, Christina
  2026-05-26 20:07     ` Guinevere Larsen
  1 sibling, 1 reply; 27+ messages in thread
From: Schimpe, Christina @ 2026-05-26  9:04 UTC (permalink / raw)
  To: Guinevere Larsen, gdb-patches

> -----Original Message-----
> From: Guinevere Larsen <guinevere@redhat.com>
> Sent: Freitag, 15. Mai 2026 18:37
> To: gdb-patches@sourceware.org
> Cc: Guinevere Larsen <guinevere@redhat.com>
> Subject: [PATCH v3 4/7] gdb/record: make record_full_history more c++-like
> 
> Move some functions to being a method of record_full_history, instead of
> floating functions.

Commit message:

- Shouldn't it be "a method of struct record_full_instruction" (also for the header)?
- it's only one function, so you could simply write the function name instead of "Move some functions" 

> ---
>  gdb/record-full.c | 25 +++++++++++--------------
>  1 file changed, 11 insertions(+), 14 deletions(-)
> 
> diff --git a/gdb/record-full.c b/gdb/record-full.c index
> 24e94eaf5da..8cabd6e9438 100644
> --- a/gdb/record-full.c
> +++ b/gdb/record-full.c
> @@ -388,6 +388,10 @@ struct record_full_instruction
>    uint32_t insn_num;
>    std::optional<gdb_signal> sigval;
>    std::vector<record_full_entry> effects;
> +
> +  /* Execute the full instruction.  As a side effect, set
> +     record_full_stop_reason.  */
> +  void exec_insn (regcache *regcache);
>  };
> 
>  /* If true, query if PREC cannot record memory @@ -843,12 +847,9 @@
> record_full_gdb_operation_disable_set (void)  static enum target_stop_reason
> record_full_stop_reason
>    = TARGET_STOPPED_BY_NO_REASON;
> 
> -static inline void
> -record_full_exec_insn (regcache *regcache,
> -		       gdbarch *gdbarch,
> -		       record_full_instruction &insn)
> +void record_full_instruction::exec_insn (regcache *regcache)
>  {
> -  for (auto &entry : insn.effects)
> +  for (auto &entry : effects)
>      if (entry.execute (regcache))
>        record_full_stop_reason = TARGET_STOPPED_BY_WATCHPOINT;  } @@ -
> 1308,9 +1309,7 @@ record_full_wait_1 (struct target_ops *ops,
>  		  break;
>  		}
> 
> -	      record_full_exec_insn
> -		(regcache, gdbarch,
> -		 record_full_list[record_full_next_insn]);
> +	      record_full_list[record_full_next_insn].exec_insn (regcache);
> 
>  	      /* step */
>  	      if (record_full_resume_step)
> @@ -2503,8 +2502,7 @@ record_full_base_target::save_record (const char
> *recfilename)
> 
>    /* Reverse execute to the begin of record list.  */
>    for (int i = record_full_next_insn - 1; i >= 0; i--)
> -    record_full_exec_insn (regcache, gdbarch,
> -			   record_full_list[i]);
> +    record_full_list[i].exec_insn (regcache);
> 
>    /* Compute the size needed for the extra bfd section.  */
>    save_size = 4;	/* magic cookie */
> @@ -2576,7 +2574,7 @@ record_full_base_target::save_record (const char
> *recfilename)
>  	}
>        /* Execute entry.  */
>        if (i < record_full_next_insn)
> -	record_full_exec_insn (regcache, gdbarch, record_full_list[i]);
> +	record_full_list[i].exec_insn (regcache);
>      }
> 
>    unlink_file.keep ();
> @@ -2597,17 +2595,16 @@ record_full_goto_insn (size_t target_insn,
>    scoped_restore restore_operation_disable
>      = record_full_gdb_operation_disable_set ();
>    regcache *regcache = get_thread_regcache (inferior_thread ());
> -  struct gdbarch *gdbarch = regcache->arch ();
> 
>    /* Assume everything is valid: we will hit the entry,
>       and we will not hit the end of the recording.  */
> 
>    if (dir == EXEC_REVERSE)
>      for (int i = record_full_next_insn; i > target_insn; i--)
> -      record_full_exec_insn (regcache, gdbarch, record_full_list[i - 1]);
> +      record_full_list[i-1].exec_insn (regcache);

Nit: spaces missing in i-1 => i - i

>    else
>      for (int i = record_full_next_insn; i < target_insn; i++)
> -      record_full_exec_insn (regcache, gdbarch, record_full_list[i]);
> +      record_full_list[i].exec_insn (regcache);
>  }
> 
>  /* Alias for "target record-full".  */
> --
> 2.54.0
> 

With the nits fixed, LGTM.

Reviewed-By: Christina Schimpe <christina.schimpe@intel.com>

Christina

Intel Deutschland GmbH
Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
Tel: +49 89 991 430, www.intel.de
Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
Chairperson of the Supervisory Board: Nicole Lau
Registered Seat: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v3 4/7] gdb/record: make record_full_history more c++-like
  2026-05-26  9:04   ` Schimpe, Christina
@ 2026-05-26 20:07     ` Guinevere Larsen
  0 siblings, 0 replies; 27+ messages in thread
From: Guinevere Larsen @ 2026-05-26 20:07 UTC (permalink / raw)
  To: Schimpe, Christina, gdb-patches

On 5/26/26 6:04 AM, Schimpe, Christina wrote:
>> -----Original Message-----
>> From: Guinevere Larsen <guinevere@redhat.com>
>> Sent: Freitag, 15. Mai 2026 18:37
>> To: gdb-patches@sourceware.org
>> Cc: Guinevere Larsen <guinevere@redhat.com>
>> Subject: [PATCH v3 4/7] gdb/record: make record_full_history more c++-like
>>
>> Move some functions to being a method of record_full_history, instead of
>> floating functions.
> Commit message:
>
> - Shouldn't it be "a method of struct record_full_instruction" (also for the header)?
> - it's only one function, so you could simply write the function name instead of "Move some functions"
>
>> ---
>>   gdb/record-full.c | 25 +++++++++++--------------
>>   1 file changed, 11 insertions(+), 14 deletions(-)
>>
>> diff --git a/gdb/record-full.c b/gdb/record-full.c index
>> 24e94eaf5da..8cabd6e9438 100644
>> --- a/gdb/record-full.c
>> +++ b/gdb/record-full.c
>> @@ -388,6 +388,10 @@ struct record_full_instruction
>>     uint32_t insn_num;
>>     std::optional<gdb_signal> sigval;
>>     std::vector<record_full_entry> effects;
>> +
>> +  /* Execute the full instruction.  As a side effect, set
>> +     record_full_stop_reason.  */
>> +  void exec_insn (regcache *regcache);
>>   };
>>
>>   /* If true, query if PREC cannot record memory @@ -843,12 +847,9 @@
>> record_full_gdb_operation_disable_set (void)  static enum target_stop_reason
>> record_full_stop_reason
>>     = TARGET_STOPPED_BY_NO_REASON;
>>
>> -static inline void
>> -record_full_exec_insn (regcache *regcache,
>> -		       gdbarch *gdbarch,
>> -		       record_full_instruction &insn)
>> +void record_full_instruction::exec_insn (regcache *regcache)
>>   {
>> -  for (auto &entry : insn.effects)
>> +  for (auto &entry : effects)
>>       if (entry.execute (regcache))
>>         record_full_stop_reason = TARGET_STOPPED_BY_WATCHPOINT;  } @@ -
>> 1308,9 +1309,7 @@ record_full_wait_1 (struct target_ops *ops,
>>   		  break;
>>   		}
>>
>> -	      record_full_exec_insn
>> -		(regcache, gdbarch,
>> -		 record_full_list[record_full_next_insn]);
>> +	      record_full_list[record_full_next_insn].exec_insn (regcache);
>>
>>   	      /* step */
>>   	      if (record_full_resume_step)
>> @@ -2503,8 +2502,7 @@ record_full_base_target::save_record (const char
>> *recfilename)
>>
>>     /* Reverse execute to the begin of record list.  */
>>     for (int i = record_full_next_insn - 1; i >= 0; i--)
>> -    record_full_exec_insn (regcache, gdbarch,
>> -			   record_full_list[i]);
>> +    record_full_list[i].exec_insn (regcache);
>>
>>     /* Compute the size needed for the extra bfd section.  */
>>     save_size = 4;	/* magic cookie */
>> @@ -2576,7 +2574,7 @@ record_full_base_target::save_record (const char
>> *recfilename)
>>   	}
>>         /* Execute entry.  */
>>         if (i < record_full_next_insn)
>> -	record_full_exec_insn (regcache, gdbarch, record_full_list[i]);
>> +	record_full_list[i].exec_insn (regcache);
>>       }
>>
>>     unlink_file.keep ();
>> @@ -2597,17 +2595,16 @@ record_full_goto_insn (size_t target_insn,
>>     scoped_restore restore_operation_disable
>>       = record_full_gdb_operation_disable_set ();
>>     regcache *regcache = get_thread_regcache (inferior_thread ());
>> -  struct gdbarch *gdbarch = regcache->arch ();
>>
>>     /* Assume everything is valid: we will hit the entry,
>>        and we will not hit the end of the recording.  */
>>
>>     if (dir == EXEC_REVERSE)
>>       for (int i = record_full_next_insn; i > target_insn; i--)
>> -      record_full_exec_insn (regcache, gdbarch, record_full_list[i - 1]);
>> +      record_full_list[i-1].exec_insn (regcache);
> Nit: spaces missing in i-1 => i - i
>
>>     else
>>       for (int i = record_full_next_insn; i < target_insn; i++)
>> -      record_full_exec_insn (regcache, gdbarch, record_full_list[i]);
>> +      record_full_list[i].exec_insn (regcache);
>>   }
>>
>>   /* Alias for "target record-full".  */
>> --
>> 2.54.0
>>
> With the nits fixed, LGTM.
>
> Reviewed-By: Christina Schimpe <christina.schimpe@intel.com>
Fixed all the nits! thanks for the review!
>
> Christina
>
> Intel Deutschland GmbH
> Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
> Tel: +49 89 991 430, www.intel.de
> Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
> Chairperson of the Supervisory Board: Nicole Lau
> Registered Seat: Munich
> Commercial Register: Amtsgericht Muenchen HRB 186928
>

-- 
Cheers,
Guinevere Larsen
It/she


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v3 3/7] gdb/record: c++ify internal structures of record-full.c
  2026-05-26  9:00   ` Schimpe, Christina
@ 2026-05-27 20:13     ` Guinevere Larsen
  2026-05-29 12:09       ` Schimpe, Christina
  0 siblings, 1 reply; 27+ messages in thread
From: Guinevere Larsen @ 2026-05-27 20:13 UTC (permalink / raw)
  To: Schimpe, Christina, gdb-patches; +Cc: Thiago Jung Bauermann

On 5/26/26 6:00 AM, Schimpe, Christina wrote:
>> -----Original Message-----
>> From: Guinevere Larsen <guinevere@redhat.com>
>> Sent: Freitag, 15. Mai 2026 18:37
>> To: gdb-patches@sourceware.org
>> Cc: Guinevere Larsen <guinevere@redhat.com>; Thiago Jung Bauermann
>> <thiago.bauermann@linaro.org>
>> Subject: [PATCH v3 3/7] gdb/record: c++ify internal structures of record-full.c
>>
>> This commit adds a constructor, destructor, and some methods to the
>> structures record_full_entry, record_full_reg_entry and
>> record_full_mem_entry. This is a move to disentangle the internal
>> representation of the data and how record-full manipulates it for replaying.
>>
>> Along with this change, record_full_entry is changed to use an std::variant,
>> since it was basically doing that already, but now we have the stdlibc++ error
>> checking to make sure we're only accessing elements we're allowed to.
>>
>> Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
>> ---
>>   gdb/record-full.c | 601 +++++++++++++++++++++++++---------------------
>>   1 file changed, 321 insertions(+), 280 deletions(-)
>>
>> diff --git a/gdb/record-full.c b/gdb/record-full.c index
>> e4dd07dc300..24e94eaf5da 100644
>> --- a/gdb/record-full.c
>> +++ b/gdb/record-full.c
>> @@ -51,6 +51,7 @@
>>   #include <optional>
>>   #include <deque>
>>   #include <signal.h>
>> +#include <variant>
>>
>>   /* This module implements "target record-full", also known as "process
>>      record and replay".  This target sits on top of a "normal" target @@ -93,12
>> +94,113 @@ struct record_full_mem_entry
>>     int len;
>>     /* Set this flag if target memory for this entry
>>        can no longer be accessed.  */
>> -  int mem_entry_not_accessible;
>> +  bool mem_entry_accessible;
> Shouldn't we update the comment then, too ?
Yes, good point!
> Nit: you could consider moving this into a separate patch. I would make the review a bit easier IMO.
>
> The same applies for each structure/class that you refactor in this patch.
> But this is rather an optional suggestion, too. 😊
Thiago suggested that, since I'm already messing with the class, I may 
as well invert the name and logic of the variable. I guess my thinking 
was that, since he already seen my previous version this wouldn't be 
much of a change to review, but you make a good point, will keep it in 
mind for the future
>
>>     union
>>     {
>>       gdb_byte *ptr;
>>       gdb_byte buf[sizeof (gdb_byte *)];
>>     } u;
>> +
>> +  record_full_mem_entry () : addr (0), len (0) { }
> Any specific reason why you don't init mem_entry_accessible and u here ?
Mainly because I wanted to treat "len == 0" as effectively 
uninitialized, so it didn't seem important, but I see that I didn't 
actually follow through with it that much so I suppose I should at least 
initialize mem_entry_accessible. u seems unnecessary to setup, since 
we'll do 0-sized reads (which will return the local buffer and not a 
random pointer)....
>
>> +
>> +  record_full_mem_entry (CORE_ADDR mem_addr, int mem_len)  {
>> +    addr = mem_addr;
>> +    len = mem_len;
>> +    if (len > sizeof (u.buf))
>> +      u.ptr = new gdb_byte[len];
>> +    mem_entry_accessible = true;
>> +  }
>> +
>> +  record_full_mem_entry (record_full_mem_entry &&other)  {
>> +    addr = other.addr;
>> +    len = other.len;
>> +    memcpy (u.buf, other.u.buf, sizeof (u.buf));
>> +    mem_entry_accessible = other.mem_entry_accessible;
>> +    /* With len == 0, OTHER is guaranteed to not try to free the
>> +       memory when it is destructed.  */
>> +    other.len = 0;
>> +  }
>> +
>> +  ~record_full_mem_entry ()
>> +  {
>> +    if (len > sizeof (u.buf))
>> +      delete[] u.ptr;
>> +  }
>> +
>> +  record_full_mem_entry &operator= (record_full_mem_entry &&other)  {
>> +    addr = other.addr;
>> +    len = other.len;
>> +    memcpy (u.buf, other.u.buf, sizeof (u.buf));
> Shouldn't we check for self-assignment here ?

I don't think it would be possible to self-assign here, since this is a 
move operator. We shouldn't be able to create a copy anywhere, so I 
think it shouldn't be a large thing.

I can add an assert, since if it ever happens this is a large bug.

>
> Also, like the destructor, I believe we must delete u.ptr here:
> ~~~
>    if (len > sizeof (u.buf))
>        delete[] u.ptr;
> ~~~
>
> Same feedback for record_full_mem_entry &operator=

The idea of a move operation (constructor or assignment) is that we 
won't need to allocate new memory, we'll just pass the pointer to the 
new holder and that's it. If the pointer is deleted here, since it was 
copied over in the memcpy call, when the next object calls its 
destructor, we'll get a double free.

>
>> +    mem_entry_accessible = other.mem_entry_accessible;
>> +    /* With len == 0, OTHER is guaranteed to not try to free the
>> +       memory when it is destructed.  */
>> +    other.len = 0;
>> +    return *this;
>> +  }
>> +
>> +  DISABLE_COPY_AND_ASSIGN (record_full_mem_entry);
>> +
>> +  gdb_byte *get_loc ()
> I don't think this is too important, but since I saw it:
> This should be const I think.
>
> IIUC we should then also use a const reference in loops which call get_loc (),
> like this one:
>
>    for (auto &entry : to_print->effects)
>
> And the same applies for type, reg and mem.

There's a good amount of places that would need changing for adding 
const to get_loc, I think, including bfdcore_read and bfdcore_write, so 
it feels like its too much for this patch. reg and mem throw errors 
about the get discarding qualifiers.

I did add const to "type" though, thanks for pointing that out.

>
>> +  {
>> +    if (len > sizeof (u.buf))
>> +      return u.ptr;
>> +    else
>> +      return u.buf;
>> +  }
>> +
>> +  bool execute (gdbarch *gdbarch)
>> +  {
>> +    /* Nothing to do if the memory is flagged not_accessible.  */
>> +    if (!mem_entry_accessible)
>> +      return false;
>> +
>> +    gdb::byte_vector buf (len);
>> +
>> +    if (record_debug > 1)
>> +      gdb_printf (gdb_stdlog,
>> +		  "Process record: record_full_mem %s to "
>> +		  "inferior addr = %s len = %d.\n",
>> +		  host_address_to_string (this),
>> +		  paddress (gdbarch, addr),
>> +		  len);
>> +
>> +    if (record_read_memory (gdbarch, addr, buf.data (), len))
>> +      mem_entry_accessible = false;
>> +    else
>> +      {
>> +	if (target_write_memory (addr,
>> +				 get_loc (),
>> +				 len))
>> +	  {
>> +	    mem_entry_accessible = false;
>> +	    if (record_debug)
>> +	      warning (_("Process record: error writing memory at "
>> +			 "addr = %s len = %d."),
>> +		       paddress (gdbarch, addr),
>> +		       len);
>> +	  }
>> +	else
>> +	  {
>> +	    memcpy (get_loc (), buf.data (), len);
>> +
>> +	    /* We've changed memory --- check if a hardware
>> +	       watchpoint should trap.  Note that this
>> +	       presently assumes the target beneath supports
>> +	       continuable watchpoints.  On non-continuable
>> +	       watchpoints target, we'll want to check this
>> +	       _before_ actually doing the memory change, and
>> +	       not doing the change at all if the watchpoint
>> +	       traps.  */
>> +	    if (hardware_watchpoint_inserted_in_range
>> +		(current_inferior ()->aspace.get (), addr, len))
>> +	      return true;
>> +	  }
>> +      }
>> +    return false;
>> +  }
>>   };
>>
>>   struct record_full_reg_entry
>> @@ -110,6 +212,70 @@ struct record_full_reg_entry
>>       gdb_byte *ptr;
>>       gdb_byte buf[2 * sizeof (gdb_byte *)];
>>     } u;
>> +
>> +  record_full_reg_entry () : num (0), len (0) { }
>> +
>> +  record_full_reg_entry (gdbarch *gdbarch, int regnum)  {
>> +    num = regnum;
>> +    len = register_size (gdbarch, regnum);
>> +    if (len > sizeof (u.buf))
>> +      u.ptr = new gdb_byte[len];
>> +  }
>> +
>> +  record_full_reg_entry (record_full_reg_entry &&other)  {
>> +    num = other.num;
>> +    len = other.len;
>> +    memcpy (u.buf, other.u.buf, sizeof (u.buf));
>> +    /* With len == 0, OTHER is guaranteed to not try to free the
>> +       memory when it is destructed.  */
>> +    other.len = 0;
>> +  }
>> +
>> +  ~record_full_reg_entry ()
>> +  {
>> +    if (len > sizeof (u.buf))
>> +      delete[] u.ptr;
>> +  }
>> +
>> +  record_full_reg_entry &operator=(record_full_reg_entry &&other)  {
>> +    num = other.num;
>> +    len = other.len;
>> +    memcpy (u.buf, other.u.buf, sizeof (u.buf));
>> +    /* With len == 0, OTHER is guaranteed to not try to free the
>> +       memory when it is destructed.  */
>> +    other.len = 0;
>> +    return *this;
>> +  }
>> +
>> +  DISABLE_COPY_AND_ASSIGN (record_full_reg_entry);
>> +
>> +  gdb_byte *get_loc ()
>> +  {
>> +    if (len > sizeof (u.buf))
>> +      return u.ptr;
>> +    else
>> +      return u.buf;
>> +  }
>> +
>> +  bool execute (regcache *regcache)
>> +  {
>> +    gdb::byte_vector buf (len);
>> +
>> +    if (record_debug > 1)
>> +      gdb_printf (gdb_stdlog,
>> +		  "Process record: record_full_reg %s to "
>> +		  "inferior num = %d.\n",
>> +		  host_address_to_string (this),
>> +		  num);
>> +
>> +    regcache->cooked_read (num, buf);
>> +    regcache->cooked_write (num, get_loc ());
>> +    memcpy (get_loc (), buf.data (), len);
>> +    return false;
>> +  }
>>   };
>>
>>   enum record_full_type
>> @@ -118,16 +284,88 @@ enum record_full_type
>>     record_full_mem
>>   };
>>
>> -struct record_full_entry
>> +class record_full_entry
>>   {
>> -  enum record_full_type type;
>> -  union
>> +  std::variant<record_full_reg_entry, record_full_mem_entry> entry;
>> +
>> +public:
>> +  record_full_entry () : entry (record_full_reg_entry ()) {}
>> +
>> +  /* Constructor for a register entry.  Type is here to make it
>> +     easier to recognize it in the constructor calls, it isn't
>> +     actually important.  */
>> +  record_full_entry (record_full_type reg_type, gdbarch *gdbarch,
>> +		     int regnum)
>> +  : entry(record_full_reg_entry (gdbarch, regnum))
>>     {
>> -    /* reg */
>> -    struct record_full_reg_entry reg;
>> -    /* mem */
>> -    struct record_full_mem_entry mem;
>> -  } u;
>> +    gdb_assert (reg_type == record_full_reg);  }
>> +
>> +  record_full_entry (record_full_type mem_type, CORE_ADDR addr, int
>> + len)
>> +  : entry(record_full_mem_entry (addr, len))  {
>> +    gdb_assert (mem_type == record_full_mem);  }
>> +
>> +  record_full_entry (record_full_entry &&other)
>> +  : entry (std::move (other.entry))
>> +  {
>> +  }
>> +
>> +  DISABLE_COPY_AND_ASSIGN (record_full_entry);
>> +
>> +  record_full_reg_entry& reg ()
>> +  {
>> +    gdb_assert (type () == record_full_reg);
>> +    return std::get<record_full_reg_entry> (entry);  }
>> +
>> +  record_full_mem_entry& mem ()
>> +  {
>> +    gdb_assert (type () == record_full_mem);
>> +    return std::get<record_full_mem_entry> (entry);  }
>> +
>> +  record_full_type type ()
>> +  {
>> +    switch (entry.index ())
>> +    {
>> +    case 0:
>> +      return record_full_reg;
>> +    case 1:
>> +      return record_full_mem;
>> +    }
>> +    gdb_assert_not_reached ("Impossible variant index");  }
>> +
>> +  /* Get the pointer to the data stored by this entry.  */  gdb_byte
>> + *get_loc ()  {
>> +    switch (type ())
>> +    {
>> +    case record_full_reg:
>> +      return reg ().get_loc ();
>> +    case record_full_mem:
>> +      return mem ().get_loc ();
>> +    }
>> +    gdb_assert_not_reached ("Impossible entry type");  }
>> +
>> +  /* Execute this entry, swapping the appropriate values from memory or
>> +     register and the recorded ones.  Returns TRUE if the execution was
>> +     stopped by a watchpoint.  */
>> +
>> +  bool execute (regcache *regcache)
>> +  {
>> +    switch (type ())
>> +      {
>> +      case record_full_reg:
>> +	return reg ().execute (regcache);
>> +      case record_full_mem:
>> +	return mem ().execute (regcache->arch ());
>> +      }
>> +    return false;
>> +  }
>>   };
>>
>>   /* This is the main structure that comprises the execution log.
>> @@ -384,91 +622,12 @@ static struct cmd_list_element
>> *record_full_cmdlist;  static void record_full_goto_insn (size_t target_insn,
>>   				   enum exec_direction_kind dir);
>>
>> -/* Initialization and cleanup functions for record_full_reg and
>> -   record_full_mem entries.  */
>> -
>> -/* Init a record_full_reg record entry.  */
>> -
>> -static inline record_full_entry
>> -record_full_reg_init (struct regcache *regcache, int regnum) -{
>> -  record_full_entry rec;
>> -  struct gdbarch *gdbarch = regcache->arch ();
>> -
>> -  rec.type = record_full_reg;
>> -  rec.u.reg.num = regnum;
>> -  rec.u.reg.len = register_size (gdbarch, regnum);
>> -  if (rec.u.reg.len > sizeof (rec.u.reg.u.buf))
>> -    rec.u.reg.u.ptr = (gdb_byte *) xmalloc (rec.u.reg.len);
>> -
>> -  return rec;
>> -}
>> -
>> -/* Cleanup a record_full_reg record entry.  */
>> -
>> -static inline void
>> -record_full_reg_cleanup (record_full_entry rec) -{
>> -  gdb_assert (rec.type == record_full_reg);
>> -  if (rec.u.reg.len > sizeof (rec.u.reg.u.buf))
>> -    xfree (rec.u.reg.u.ptr);
>> -}
>> -
>> -/* Init a record_full_mem record entry.  */
>> -
>> -static inline record_full_entry
>> -record_full_mem_init (CORE_ADDR addr, int len) -{
>> -  record_full_entry rec;
>> -
>> -  rec.type = record_full_mem;
>> -  rec.u.mem.addr = addr;
>> -  rec.u.mem.len = len;
>> -  if (rec.u.mem.len > sizeof (rec.u.mem.u.buf))
>> -    rec.u.mem.u.ptr = (gdb_byte *) xmalloc (len);
>> -  rec.u.mem.mem_entry_not_accessible = 0;
>> -
>> -  return rec;
>> -}
>> -
>> -/* Cleanup a record_full_mem record entry.  */
>> -
>> -static inline void
>> -record_full_mem_cleanup (record_full_entry rec) -{
>> -  gdb_assert (rec.type == record_full_mem);
>> -  if (rec.u.mem.len > sizeof (rec.u.mem.u.buf))
>> -    xfree (rec.u.mem.u.ptr);
>> -}
>> -
>> -/* Free one record entry, any type.  */
>> -
>> -static inline void
>> -record_full_entry_cleanup (record_full_entry rec) -{
>> -
>> -  switch (rec.type) {
>> -  case record_full_reg:
>> -    record_full_reg_cleanup (rec);
>> -    break;
>> -  case record_full_mem:
>> -    record_full_mem_cleanup (rec);
>> -    break;
>> -  }
>> -}
>> -
>>   static void
>>   record_full_reset_history ()
>>   {
>>     record_full_insn_count = 0;
>>     record_full_next_insn = 0;
>>
>> -  for (auto &insn : record_full_list)
>> -    {
>> -      for (auto &entry : insn.effects)
>> -	record_full_entry_cleanup (entry);
>> -    }
>> -
>>     record_full_list.clear ();
>>   }
>>
>> @@ -476,11 +635,7 @@ static void
>>   record_full_list_release_following (int index)  {
>>     for (int i = record_full_list.size () - 1; i > index; i--)
>> -    {
>> -      for (auto &entry : record_full_list[i].effects)
>> -	record_full_entry_cleanup (entry);
>> -      record_full_list.pop_back ();
>> -    }
>> +    record_full_list.pop_back ();
>>     /* Set the next instruction to be past the end of the log so we
>>        start recording if the user moves forward again.  */
>>     record_full_next_insn = index;
>> @@ -509,9 +664,6 @@ record_full_list_release_first (void)
>>     if (record_full_list.empty ())
>>       return;
>>
>> -  for (auto &entry : record_full_list[0].effects)
>> -    record_full_entry_cleanup (entry);
>> -
>>     record_full_list.pop_front ();
>>     --record_full_next_insn;
>>   }
>> @@ -521,28 +673,7 @@ record_full_list_release_first (void)  static void
>> record_full_arch_list_add (record_full_entry &rec)  {
>> -  record_full_incomplete_instruction.effects.push_back (rec); -}
>> -
>> -/* Return the value storage location of a record entry.  */ -static inline
>> gdb_byte * -record_full_get_loc (struct record_full_entry *rec) -{
>> -  switch (rec->type) {
>> -  case record_full_mem:
>> -    if (rec->u.mem.len > sizeof (rec->u.mem.u.buf))
>> -      return rec->u.mem.u.ptr;
>> -    else
>> -      return rec->u.mem.u.buf;
>> -  case record_full_reg:
>> -    if (rec->u.reg.len > sizeof (rec->u.reg.u.buf))
>> -      return rec->u.reg.u.ptr;
>> -    else
>> -      return rec->u.reg.u.buf;
>> -  default:
>> -    gdb_assert_not_reached ("unexpected record_full_entry type");
>> -    return NULL;
>> -  }
>> +  record_full_incomplete_instruction.effects.push_back (std::move
>> + (rec));
>>   }
>>
>>   /* Record the value of a register NUM to record_full_arch_list.  */ @@ -550,7
>> +681,7 @@ record_full_get_loc (struct record_full_entry *rec)  int
>> record_full_arch_list_add_reg (struct regcache *regcache, int regnum)  {
>> -  record_full_entry rec;
>> +  record_full_entry rec (record_full_reg, regcache->arch (), regnum);
>>
>>     if (record_debug > 1)
>>       gdb_printf (gdb_stdlog,
>> @@ -558,9 +689,7 @@ record_full_arch_list_add_reg (struct regcache
>> *regcache, int regnum)
>>   		"record list.\n",
>>   		regnum);
>>
>> -  rec = record_full_reg_init (regcache, regnum);
>> -
>> -  regcache->cooked_read (regnum, record_full_get_loc (&rec));
>> +  regcache->cooked_read (regnum, rec.get_loc ());
>>
>>     record_full_arch_list_add (rec);
>>
>> @@ -573,7 +702,7 @@ record_full_arch_list_add_reg (struct regcache
>> *regcache, int regnum)  int  record_full_arch_list_add_mem (CORE_ADDR
>> addr, int len)  {
>> -  record_full_entry rec;
>> +  record_full_entry rec (record_full_mem, addr, len);
>>
>>     if (record_debug > 1)
>>       gdb_printf (gdb_stdlog,
>> @@ -584,14 +713,9 @@ record_full_arch_list_add_mem (CORE_ADDR addr,
>> int len)
>>     if (!addr)	/* FIXME: Why?  Some arch must permit it...  */
>>       return 0;
>>
>> -  rec = record_full_mem_init (addr, len);
>> -
>>     if (record_read_memory (current_inferior ()->arch (), addr,
>> -			  record_full_get_loc (&rec), len))
>> -    {
>> -      record_full_mem_cleanup (rec);
>> -      return -1;
>> -    }
>> +			  rec.get_loc (), len))
>> +    return -1;
>>
>>     record_full_arch_list_add (rec);
>>
>> @@ -719,98 +843,14 @@ record_full_gdb_operation_disable_set (void)
>> static enum target_stop_reason record_full_stop_reason
>>     = TARGET_STOPPED_BY_NO_REASON;
>>
>> -/* Execute one instruction from the record log.  Each instruction in
>> -   the log will be represented by an arbitrary sequence of register
>> -   entries and memory entries, followed by an 'end' entry.  */
>> -
>> -static inline void
>> -record_full_exec_entry (regcache *regcache,
>> -			gdbarch *gdbarch,
>> -			record_full_entry *entry)
>> -{
>> -  switch (entry->type)
>> -    {
>> -    case record_full_reg: /* reg */
>> -      {
>> -	gdb::byte_vector reg (entry->u.reg.len);
>> -
>> -	if (record_debug > 1)
>> -	  gdb_printf (gdb_stdlog,
>> -		      "Process record: record_full_reg %s to "
>> -		      "inferior num = %d.\n",
>> -		      host_address_to_string (entry),
>> -		      entry->u.reg.num);
>> -
>> -	regcache->cooked_read (entry->u.reg.num, reg.data ());
>> -	regcache->cooked_write (entry->u.reg.num, record_full_get_loc
>> (entry));
>> -	memcpy (record_full_get_loc (entry), reg.data (), entry->u.reg.len);
>> -      }
>> -      break;
>> -
>> -    case record_full_mem: /* mem */
>> -      {
>> -	/* Nothing to do if the entry is flagged not_accessible.  */
>> -	if (!entry->u.mem.mem_entry_not_accessible)
>> -	  {
>> -	    gdb::byte_vector mem (entry->u.mem.len);
>> -
>> -	    if (record_debug > 1)
>> -	      gdb_printf (gdb_stdlog,
>> -			  "Process record: record_full_mem %s to "
>> -			  "inferior addr = %s len = %d.\n",
>> -			  host_address_to_string (entry),
>> -			  paddress (gdbarch, entry->u.mem.addr),
>> -			  entry->u.mem.len);
>> -
>> -	    if (record_read_memory (gdbarch,
>> -				    entry->u.mem.addr, mem.data (),
>> -				    entry->u.mem.len))
>> -	      entry->u.mem.mem_entry_not_accessible = 1;
>> -	    else
>> -	      {
>> -		if (target_write_memory (entry->u.mem.addr,
>> -					 record_full_get_loc (entry),
>> -					 entry->u.mem.len))
>> -		  {
>> -		    entry->u.mem.mem_entry_not_accessible = 1;
>> -		    if (record_debug)
>> -		      warning (_("Process record: error writing memory at "
>> -				 "addr = %s len = %d."),
>> -			       paddress (gdbarch, entry->u.mem.addr),
>> -			       entry->u.mem.len);
>> -		  }
>> -		else
>> -		  {
>> -		    memcpy (record_full_get_loc (entry), mem.data (),
>> -			    entry->u.mem.len);
>> -
>> -		    /* We've changed memory --- check if a hardware
>> -		       watchpoint should trap.  Note that this
>> -		       presently assumes the target beneath supports
>> -		       continuable watchpoints.  On non-continuable
>> -		       watchpoints target, we'll want to check this
>> -		       _before_ actually doing the memory change, and
>> -		       not doing the change at all if the watchpoint
>> -		       traps.  */
>> -		    if (hardware_watchpoint_inserted_in_range
>> -			(current_inferior ()->aspace.get (),
>> -			 entry->u.mem.addr, entry->u.mem.len))
>> -		      record_full_stop_reason =
>> TARGET_STOPPED_BY_WATCHPOINT;
>> -		  }
>> -	      }
>> -	  }
>> -      }
>> -      break;
>> -    }
>> -}
>> -
>>   static inline void
>>   record_full_exec_insn (regcache *regcache,
>>   		       gdbarch *gdbarch,
>>   		       record_full_instruction &insn)  {
>>     for (auto &entry : insn.effects)
>> -    record_full_exec_entry (regcache, gdbarch, &entry);
>> +    if (entry.execute (regcache))
>> +      record_full_stop_reason = TARGET_STOPPED_BY_WATCHPOINT;
>>   }
>>
>>   static void record_full_restore (struct bfd &cbfd); @@ -2185,21 +2225,19
>> @@ record_full_read_entry_from_bfd (bfd *cbfd, asection *osec, int
>> *bfd_offset)
>>   	bfdcore_read (cbfd, osec, &regnum, sizeof (regnum), bfd_offset);
>>   	regnum = netorder32 (regnum);
>>
>> -	record_full_entry rec;
>> -
>> -	rec = record_full_reg_init (cache, regnum);
>> +	record_full_entry rec (record_full_reg, cache->arch (), regnum);
>>
>>   	/* Get val.  */
>> -	bfdcore_read (cbfd, osec, record_full_get_loc (&rec),
>> -		      rec.u.reg.len, bfd_offset);
>> +	bfdcore_read (cbfd, osec, rec.get_loc (),
>> +		      rec.reg ().len, bfd_offset);
>>
>>   	if (record_debug)
>>   	  gdb_printf (gdb_stdlog,
>>   		      "  Reading register %d (1 "
>>   		      "plus %lu plus %d bytes)\n",
>> -		      rec.u.reg.num,
>> +		      rec.reg ().num,
>>   		      (unsigned long) sizeof (regnum),
>> -		      rec.u.reg.len);
>> +		      rec.reg ().len);
>>
>>   	record_full_arch_list_add (rec);
>>   	break;
>> @@ -2216,19 +2254,17 @@ record_full_read_entry_from_bfd (bfd *cbfd,
>> asection *osec, int *bfd_offset)
>>   		      bfd_offset);
>>   	addr = netorder64 (addr);
>>
>> -	record_full_entry rec;
>> -	rec = record_full_mem_init (addr, len);
>> +	record_full_entry rec (record_full_mem, addr, len);
>>
>>   	/* Get val.  */
>> -	bfdcore_read (cbfd, osec, record_full_get_loc (&rec), len,
>> -		      bfd_offset);
>> +	bfdcore_read (cbfd, osec, rec.get_loc (), len, bfd_offset);
>>
>>   	if (record_debug)
>>   	  gdb_printf (gdb_stdlog,
>>   		      "  Reading memory %s (1 plus "
>>   		      "%lu plus %lu plus %d bytes)\n",
>>   		      paddress (get_current_arch (),
>> -				rec.u.mem.addr),
>> +				rec.mem ().addr),
>>   		      (unsigned long) sizeof (addr),
>>   		      (unsigned long) sizeof (len),
>>   		      len);
>> @@ -2380,52 +2416,56 @@ record_full_write_entry_to_bfd
>> (record_full_entry &entry,
>>     uint32_t regnum, len;
>>     uint64_t addr;
>>
>> -  type = entry.type;
>> +  type = entry.type ();
>>     bfdcore_write (obfd.get (), osec, &type, sizeof (type), bfd_offset);
>>
>> -  switch (entry.type)
>> +  switch (type)
>>       {
>>       case record_full_reg: /* reg */
>> -      if (record_debug)
>> -	gdb_printf (gdb_stdlog,
>> -		    "  Writing register %d (1 "
>> -		    "plus %lu plus %d bytes)\n",
>> -		    entry.u.reg.num,
>> -		    (unsigned long) sizeof (regnum),
>> -		    entry.u.reg.len);
>> -
>> -      /* Write regnum.  */
>> -      regnum = netorder32 (entry.u.reg.num);
>> -      bfdcore_write (obfd.get (), osec, &regnum, sizeof (regnum), bfd_offset);
>> -
>> -      /* Write regval.  */
>> -      bfdcore_write (obfd.get (), osec, record_full_get_loc (&entry),
>> -		     entry.u.reg.len, bfd_offset);
>> -      break;
>> +      {
>> +	auto &reg = entry.reg ();
>> +	if (record_debug)
>> +	  gdb_printf (gdb_stdlog,
>> +		      "  Writing register %d (1 "
>> +		      "plus %lu plus %d bytes)\n",
>> +		      reg.num,
>> +		      (unsigned long) sizeof (regnum),
>> +		      reg.len);
>> +
>> +	/* Write regnum.  */
>> +	regnum = netorder32 (reg.num);
>> +	bfdcore_write (obfd.get (), osec, &regnum, sizeof (regnum),
>> +bfd_offset);
>> +
>> +	/* Write regval.  */
>> +	bfdcore_write (obfd.get (), osec, entry.get_loc (), reg.len, bfd_offset);
>> +	break;
>> +      }
>>
>>       case record_full_mem: /* mem */
>> -      if (record_debug)
>> -	gdb_printf (gdb_stdlog,
>> -		    "  Writing memory %s (1 plus "
>> -		    "%lu plus %lu plus %d bytes)\n",
>> -		    paddress (gdbarch,
>> -			      entry.u.mem.addr),
>> -		    (unsigned long) sizeof (addr),
>> -		    (unsigned long) sizeof (len),
>> -		    entry.u.mem.len);
>> -
>> -      /* Write memlen.  */
>> -      len = netorder32 (entry.u.mem.len);
>> -      bfdcore_write (obfd.get (), osec, &len, sizeof (len), bfd_offset);
>> -
>> -      /* Write memaddr.  */
>> -      addr = netorder64 (entry.u.mem.addr);
>> -      bfdcore_write (obfd.get (), osec, &addr, sizeof (addr), bfd_offset);
>> -
>> -      /* Write memval.  */
>> -      bfdcore_write (obfd.get (), osec, record_full_get_loc (&entry),
>> -		     entry.u.mem.len, bfd_offset);
>> -      break;
>> +      {
>> +	auto &mem = entry.mem ();
>> +	if (record_debug)
>> +	  gdb_printf (gdb_stdlog,
>> +		      "  Writing memory %s (1 plus "
>> +		      "%lu plus %lu plus %d bytes)\n",
>> +		      paddress (gdbarch, mem.addr),
>> +		      (unsigned long) sizeof (addr),
>> +		      (unsigned long) sizeof (len),
>> +		      mem.len);
>> +
>> +	/* Write memlen.  */
>> +	len = netorder32 (mem.len);
>> +	bfdcore_write (obfd.get (), osec, &len, sizeof (len), bfd_offset);
>> +
>> +	/* Write memaddr.  */
>> +	addr = netorder64 (mem.addr);
>> +	bfdcore_write (obfd.get (), osec, &addr, sizeof (addr), bfd_offset);
>> +
>> +	/* Write memval.  */
>> +	bfdcore_write (obfd.get (), osec, entry.get_loc (), mem.len,
>> +		       bfd_offset);
>> +	break;
>> +      }
>>       }
>>
>>   }
>> @@ -2473,13 +2513,13 @@ record_full_base_target::save_record (const
>> char *recfilename)
>>         /* Number of effects of an instruction.  */
>>         save_size += sizeof (uint32_t) + sizeof (uint8_t) + sizeof (uint32_t);
>>         for (auto &entry : record_full_list[i].effects)
>> -	switch (entry.type)
>> +	switch (entry.type ())
>>   	  {
>>   	  case record_full_reg:
>> -	    save_size += 1 + 4 + entry.u.reg.len;
>> +	    save_size += 1 + 4 + entry.reg ().len;
>>   	    break;
>>   	  case record_full_mem:
>> -	    save_size += 1 + 4 + 8 + entry.u.mem.len;
>> +	    save_size += 1 + 4 + 8 + entry.mem ().len;
>>   	    break;
>>   	  }
>>       }
>> @@ -2612,18 +2652,18 @@ maintenance_print_record_instruction (const
>> char *args, int from_tty)
>>
>>     gdbarch *arch = current_inferior ()->arch ();
>>
>> -  for (auto entry : to_print->effects)
>> +  for (auto &entry : to_print->effects)
>>       {
>> -      switch (entry.type)
>> +      switch (entry.type ())
>>   	{
>>   	  case record_full_reg:
>>   	    {
>> -	      type *regtype = gdbarch_register_type (arch, entry.u.reg.num);
>> +	      type *regtype = gdbarch_register_type (arch, entry.reg ().num);
>>   	      value *val
>>   		  = value_from_contents (regtype,
>> -					 record_full_get_loc (&entry));
>> +					 entry.get_loc ());
>>   	      gdb_printf ("Register %s changed: ",
>> -			  gdbarch_register_name (arch, entry.u.reg.num));
>> +			  gdbarch_register_name (arch, entry.reg ().num));
>>   	      struct value_print_options opts;
>>   	      get_user_print_options (&opts);
>>   	      opts.raw = true;
>> @@ -2633,11 +2673,12 @@ maintenance_print_record_instruction (const
>> char *args, int from_tty)
>>   	    }
>>   	  case record_full_mem:
>>   	    {
>> -	      gdb_byte *b = record_full_get_loc (&entry);
>> +	      record_full_mem_entry& mem = entry.mem ();
>> +	      gdb_byte *b = entry.get_loc ();
>>   	      gdb_printf ("%d bytes of memory at address %s changed from:",
>> -			  entry.u.mem.len,
>> -			  print_core_address (arch, entry.u.mem.addr));
>> -	      for (int i = 0; i < entry.u.mem.len; i++)
>> +			  mem.len,
>> +			  print_core_address (arch, mem.addr));
>> +	      for (int i = 0; i < mem.len; i++)
>>   		gdb_printf (" %02x", b[i]);
>>   	      gdb_printf ("\n");
>>   	      break;
>> --
>> 2.54.0
>>
> Christina
> Intel Deutschland GmbH
> Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
> Tel: +49 89 991 430, www.intel.de
> Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
> Chairperson of the Supervisory Board: Nicole Lau
> Registered Seat: Munich
> Commercial Register: Amtsgericht Muenchen HRB 186928
>

-- 
Cheers,
Guinevere Larsen
It/she


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v3 1/7] gdb/record: Refactor record history
  2026-05-26  8:33   ` Schimpe, Christina
@ 2026-05-28 13:20     ` Guinevere Larsen
  2026-05-29 13:47       ` Schimpe, Christina
  0 siblings, 1 reply; 27+ messages in thread
From: Guinevere Larsen @ 2026-05-28 13:20 UTC (permalink / raw)
  To: Schimpe, Christina, gdb-patches; +Cc: Thiago Jung Bauermann

On 5/26/26 5:33 AM, Schimpe, Christina wrote:
> Hi Guinevere,
>
> Please find some further comments below.
>
>> -----Original Message-----
>> From: Guinevere Larsen <guinevere@redhat.com>
>> Sent: Freitag, 15. Mai 2026 18:37
>> To: gdb-patches@sourceware.org
>> Cc: Guinevere Larsen <guinevere@redhat.com>; Thiago Jung Bauermann
>> <thiago.bauermann@linaro.org>
>> Subject: [PATCH v3 1/7] gdb/record: Refactor record history
>>
>> This is the first step in a large refactor in how GDB keeps execution
>> history.  Rather than using a linked list where multiple entries can
>> describe a single instruction, the history will now be stored in an
>> std::deque, each instruction being one entry in the deque.
>>
>> The choice was initially to use an std::vector, but it would become
>> unwieldy because it needs all the memory to be consecutive, which is
>> hard for 200 thousand entries. Deque was picked because it was a nice
>> midpoint between vector (maximum cache cohesion) and linked list
>> (maximum ease of finding space to store more).
>>
>> Each instruction in memory will be now one record_full_instruction
>> entry, which for this commit just contains a vector of
>> record_full_entry for the effects of the instruction, and the data that
>> was stored in the record_full_end entry (that is, the instruction number
>> and the signal, if any).
>>
>> This change introduced a minimal performance improvement (what's
>> important is that it isn't a degradation) and a  reduction in the total
>> memory footprint of roughly 20% if the entire history is used.
>>
>> Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
>> ---
>>   gdb/aarch64-tdep.c     |    2 -
>>   gdb/amd64-linux-tdep.c |    3 -
>>   gdb/arm-tdep.c         |    2 -
>>   gdb/i386-linux-tdep.c  |    3 -
>>   gdb/i386-tdep.c        |    4 -
>>   gdb/loongarch-tdep.c   |    2 -
>>   gdb/moxie-tdep.c       |    2 -
>>   gdb/ppc-linux-tdep.c   |    3 -
>>   gdb/record-full.c      | 1186 ++++++++++++++++------------------------
>>   gdb/record-full.h      |    1 -
>>   gdb/riscv-tdep.c       |    3 -
>>   gdb/rs6000-tdep.c      |    4 -
>>   gdb/s390-linux-tdep.c  |    3 -
>>   gdb/s390-tdep.c        |    2 -
>>   14 files changed, 484 insertions(+), 736 deletions(-)
>>
>> diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c
>> index 4befaa2720d..a532992b1d8 100644
>> --- a/gdb/aarch64-tdep.c
>> +++ b/gdb/aarch64-tdep.c
>> @@ -6210,8 +6210,6 @@ aarch64_process_record (struct gdbarch
>> *gdbarch, struct regcache *regcache,
>>   	       aarch64_record.aarch64_mems[rec_no].len))
>>   	    ret = -1;
>>
>> -      if (record_full_arch_list_add_end ())
>> -	ret = -1;
>>       }
>>
>>     deallocate_reg_mem (&aarch64_record);
>> diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c
>> index a5ac26654cf..9b23db72bbe 100644
>> --- a/gdb/amd64-linux-tdep.c
>> +++ b/gdb/amd64-linux-tdep.c
>> @@ -1591,9 +1591,6 @@ amd64_linux_record_signal (struct gdbarch
>> *gdbarch,
>>   				     + AMD64_LINUX_frame_size))
>>       return -1;
>>
>> -  if (record_full_arch_list_add_end ())
>> -    return -1;
>> -
>>     return 0;
>>   }
>>
>> diff --git a/gdb/arm-tdep.c b/gdb/arm-tdep.c
>> index 08cce9dbad8..1ce0927f26b 100644
>> --- a/gdb/arm-tdep.c
>> +++ b/gdb/arm-tdep.c
>> @@ -14911,8 +14911,6 @@ arm_process_record (struct gdbarch *gdbarch,
>> struct regcache *regcache,
>>   	    }
>>   	}
>>
>> -      if (record_full_arch_list_add_end ())
>> -	ret = -1;
>>       }
>>
>>
>> diff --git a/gdb/i386-linux-tdep.c b/gdb/i386-linux-tdep.c
>> index 23aeccac9fc..4f33766fcdd 100644
>> --- a/gdb/i386-linux-tdep.c
>> +++ b/gdb/i386-linux-tdep.c
>> @@ -949,9 +949,6 @@ i386_linux_record_signal (struct gdbarch *gdbarch,
>>   				     I386_LINUX_xstate +
>> I386_LINUX_frame_size))
>>       return -1;
>>
>> -  if (record_full_arch_list_add_end ())
>> -    return -1;
>> -
>>     return 0;
>>   }
>>   
>> diff --git a/gdb/i386-tdep.c b/gdb/i386-tdep.c
>> index fa935b5fcdb..fee36b46a73 100644
>> --- a/gdb/i386-tdep.c
>> +++ b/gdb/i386-tdep.c
>> @@ -5201,8 +5201,6 @@ i386_record_vex (struct i386_record_s *ir, uint8_t
>> vex_w, uint8_t vex_r,
>>       }
>>
>>     record_full_arch_list_add_reg (ir->regcache, ir-
>>> regmap[X86_RECORD_REIP_REGNUM]);
>> -  if (record_full_arch_list_add_end ())
>> -    return -1;
>>
>>     return 0;
>>   }
>> @@ -8359,8 +8357,6 @@ Do you want to stop the program?"),
>>
>>     /* In the future, maybe still need to deal with need_dasm.  */
>>     I386_RECORD_FULL_ARCH_LIST_ADD_REG (X86_RECORD_REIP_REGNUM);
>> -  if (record_full_arch_list_add_end ())
>> -    return -1;
>>
>>     return 0;
>>
>> diff --git a/gdb/loongarch-tdep.c b/gdb/loongarch-tdep.c
>> index 225b1abb703..5ec6b2a48d0 100644
>> --- a/gdb/loongarch-tdep.c
>> +++ b/gdb/loongarch-tdep.c
>> @@ -2789,8 +2789,6 @@ loongarch_process_record (struct gdbarch
>> *gdbarch, struct regcache *regcache,
>>   					 LOONGARCH_PC_REGNUM))
>>   	return -1;
>>
>> -      if (record_full_arch_list_add_end ())
>> -	return -1;
>>       }
>>
>>     return ret;
>> diff --git a/gdb/moxie-tdep.c b/gdb/moxie-tdep.c
>> index c8a04d21f59..2f8f0186488 100644
>> --- a/gdb/moxie-tdep.c
>> +++ b/gdb/moxie-tdep.c
>> @@ -1040,8 +1040,6 @@ moxie_process_record (struct gdbarch *gdbarch,
>> struct regcache *regcache,
>>
>>     if (record_full_arch_list_add_reg (regcache, MOXIE_PC_REGNUM))
>>       return -1;
>> -  if (record_full_arch_list_add_end ())
>> -    return -1;
>>     return 0;
>>   }
>>
>> diff --git a/gdb/ppc-linux-tdep.c b/gdb/ppc-linux-tdep.c
>> index 19698eaacbe..e2f53551709 100644
>> --- a/gdb/ppc-linux-tdep.c
>> +++ b/gdb/ppc-linux-tdep.c
>> @@ -1565,9 +1565,6 @@ ppc_linux_record_signal (struct gdbarch *gdbarch,
>> struct regcache *regcache,
>>     if (record_full_arch_list_add_mem (sp, SIGNAL_FRAMESIZE +
>> sizeof_rt_sigframe))
>>       return -1;
>>
>> -  if (record_full_arch_list_add_end ())
>> -    return -1;
>> -
>>     return 0;
>>   }
>>
>> diff --git a/gdb/record-full.c b/gdb/record-full.c
>> index 69d2c100e57..02a57726a95 100644
>> --- a/gdb/record-full.c
>> +++ b/gdb/record-full.c
>> @@ -47,6 +47,9 @@
>>   #include "interps.h"
>>   #include "cli/cli-style.h"
>>
>> +#include <vector>
>> +#include <optional>
>> +#include <deque>
>>   #include <signal.h>
>>
>>   /* This module implements "target record-full", also known as "process
>> @@ -72,20 +75,17 @@
>>   #define DEFAULT_RECORD_FULL_INSN_MAX_NUM	200000
>>
>>   #define RECORD_FULL_IS_REPLAY \
>> -  (record_full_list->next || ::execution_direction == EXEC_REVERSE)
>> +  ((record_full_next_insn != record_full_list.size ()) \
>> +    || ::execution_direction == EXEC_REVERSE)
>>
>>   #define RECORD_FULL_FILE_MAGIC	netorder32(0x20091016)
>>
>>   /* These are the core structs of the process record functionality.
>>
>>      A record_full_entry is a record of the value change of a register
>> -   ("record_full_reg") or a part of memory ("record_full_mem").  And each
>> -   instruction must have a struct record_full_entry ("record_full_end")
>> -   that indicates that this is the last struct record_full_entry of this
>> -   instruction.
>> -
>> -   Each struct record_full_entry is linked to "record_full_list" by "prev"
>> -   and "next" pointers.  */
>> +   ("record_full_reg") or a part of memory ("record_full_mem").
>> +   These are saved on the record_full_instruction struct, which also
>> +   contains some extra information, such as delivered signals.  */
> Optional suggestion (since it has been like this before already):
>
> The description for record_full_entry is not above the actual struct and the
> further structs miss a comment. In the description above the enum values
> are described '("record_full_reg") or a part of memory ("record_full_mem")',
> but not the actual structs.  I think it would be helpful to write this out a bit more,
> add comments to each struct and move the one for record_full_entry
> above its actual struct.
> Given that this commit is already big enough, a separate commit might be better.
Yeah, these can probably be better documented. I'll try to think of 
something and either add a commit at the end of this series, or send a 
follow up patch later (depends on how hard it is for me to think of 
something :) )
>
>>   struct record_full_mem_entry
>>   {
>> @@ -112,47 +112,14 @@ struct record_full_reg_entry
>>     } u;
>>   };
>>
>> -struct record_full_end_entry
>> -{
>> -  enum gdb_signal sigval;
>> -  ULONGEST insn_num;
>> -};
>> -
>>   enum record_full_type
>>   {
>> -  record_full_end = 0,
>>     record_full_reg,
>>     record_full_mem
>>   };
>>
>> -/* This is the data structure that makes up the execution log.
>> -
>> -   The execution log consists of a single linked list of entries
>> -   of type "struct record_full_entry".  It is doubly linked so that it
>> -   can be traversed in either direction.
>> -
>> -   The start of the list is anchored by a struct called
>> -   "record_full_first".  The pointer "record_full_list" either points
>> -   to the last entry that was added to the list (in record mode), or to
>> -   the next entry in the list that will be executed (in replay mode).
>> -
>> -   Each list element (struct record_full_entry), in addition to next
>> -   and prev pointers, consists of a union of three entry types: mem,
>> -   reg, and end.  A field called "type" determines which entry type is
>> -   represented by a given list element.
>> -
>> -   Each instruction that is added to the execution log is represented
>> -   by a variable number of list elements ('entries').  The instruction
>> -   will have one "reg" entry for each register that is changed by
>> -   executing the instruction (including the PC in every case).  It
>> -   will also have one "mem" entry for each memory change.  Finally,
>> -   each instruction will have an "end" entry that separates it from
>> -   the changes associated with the next instruction.  */
>> -
>>   struct record_full_entry
>>   {
>> -  struct record_full_entry *prev;
>> -  struct record_full_entry *next;
>>     enum record_full_type type;
>>     union
>>     {
>> @@ -160,11 +127,31 @@ struct record_full_entry
>>       struct record_full_reg_entry reg;
>>       /* mem */
>>       struct record_full_mem_entry mem;
>> -    /* end */
>> -    struct record_full_end_entry end;
>>     } u;
>>   };
>>
>> +/* This is the main structure that comprises the execution log.
>> +   Each instruction is comprised of:
>> +   * The instruction number: How many instructions were recorded before
>> +     this one;
>> +   * sigval: Whether the inferior received a signal while the following
>> +     instruction was being recorded;
>> +   * effects: A list of record_full_entry structures, each of which
>> +     describing one effect that the instruction has on the inferior.
>> +
>> +   Note, the signal is stored in the previous instruction for historical
>> +   reasons.  This is how it was first implemented, and no one has gotten
>> +   around to changing it yet.  */
>> +
>> +struct record_full_instruction
>> +{
>> +  /* This might be different from the index if
>> +     we had to remove the first few instructions.  */
>> +  uint32_t insn_num;
>> +  std::optional<gdb_signal> sigval;
>> +  std::vector<record_full_entry> effects;
>> +};
>> +
>>   /* If true, query if PREC cannot record memory
>>      change of next instruction.  */
>>   bool record_full_memory_query = false;
>> @@ -181,27 +168,25 @@ static detached_regcache
>> *record_full_core_regbuf = NULL;
>>   static std::vector<target_section> record_full_core_sections;
>>   static struct record_full_core_buf_entry *record_full_core_buf_list = NULL;
>>
>> -/* The following variables are used for managing the linked list that
>> -   represents the execution log.
>> +/* The following variables are used for managing the history of executed
>> +   instructions from the inferior.
>>
>> -   record_full_first is the anchor that holds down the beginning of
>> -   the list.
>> +   record_full_list contains all instructions that were fully executed and
>> +   saved to the log, so that we can replay the execution.
>>
>> -   record_full_list serves two functions:
>> -     1) In record mode, it anchors the end of the list.
>> -     2) In replay mode, it traverses the list and points to
>> -	the next instruction that must be emulated.
>> +   record_full_next_insn always points to the next instruction that would
>> +   be executed if the inferior executes forward.  In the special case when
>> +   the inferior is not replaying, record_full_next_insn points past the
>> +   end of the history.
>>
>> -   record_full_arch_list_head and record_full_arch_list_tail are used
>> -   to manage a separate list, which is used to build up the change
>> -   elements of the currently executing instruction during record mode.
>> -   When this instruction has been completely annotated in the "arch
>> -   list", it will be appended to the main execution log.  */
>> +   record_full_incomplete_instruction holds a partial instruction, while
>> +   the lower target is disassembling the instruction, or as partial xfers are
>> +   happening.  It is manipulated by the "arch list" functions for historical
>> +   reasons.  */
>>
>> -static struct record_full_entry record_full_first;
>> -static struct record_full_entry *record_full_list = &record_full_first;
>> -static struct record_full_entry *record_full_arch_list_head = NULL;
>> -static struct record_full_entry *record_full_arch_list_tail = NULL;
>> +static std::deque<record_full_instruction> record_full_list;
>> +static record_full_instruction record_full_incomplete_instruction;
>> +static int record_full_next_insn;
>>
>>   /* true ask user. false auto delete the last struct record_full_entry.  */
>>   static bool record_full_stop_at_limit = true;
>> @@ -361,6 +346,14 @@ record_full_target::kill ()
>>     record_kill (this);
>>   }
> This  function is still missing a comment.
oops, fixed.
>> +static void
>> +record_full_reset_incomplete ()
>> +{
>> +  record_full_incomplete_instruction.effects.clear ();
>> +  record_full_incomplete_instruction.sigval.reset ();
>> +  record_full_incomplete_instruction.insn_num = 0;
>> +}
>> +
>>   /* See record-full.h.  */
>>
>>   int
>> @@ -390,156 +383,124 @@ static struct cmd_list_element
>> *show_record_full_cmdlist;
>>   /* Command list for "record full".  */
>>   static struct cmd_list_element *record_full_cmdlist;
>>
>> -static void record_full_goto_insn (struct record_full_entry *entry,
>> +static void record_full_goto_insn (size_t target_insn,
>>   				   enum exec_direction_kind dir);
> Is this some general guideline to call this cleanup/init instead of release/alloc ?
> Since this patch is already so big, I just wonder why you changed it. 😊

There isn't any guideline, I just figured that alloc/release was no 
longer correct, since we were no longer allocating or releasing dynamic 
memory... This was before I realized how far I could take the 
C++ification and make them into proper constructors.

I can undo the change if you'd like, but since they are going to become 
the constructor I feel like it is not really necessary either way...

>> -/* Alloc and free functions for record_full_reg, record_full_mem, and
>> -   record_full_end entries.  */
>> +/* Initialization and cleanup functions for record_full_reg and
>> +   record_full_mem entries.  */
>>
>> -/* Alloc a record_full_reg record entry.  */
>> +/* Init a record_full_reg record entry.  */
>>
>> -static inline struct record_full_entry *
>> -record_full_reg_alloc (struct regcache *regcache, int regnum)
>> +static inline record_full_entry
>> +record_full_reg_init (struct regcache *regcache, int regnum)
>>   {
>> -  struct record_full_entry *rec;
>> +  record_full_entry rec;
>>     struct gdbarch *gdbarch = regcache->arch ();
>>
>> -  rec = XCNEW (struct record_full_entry);
>> -  rec->type = record_full_reg;
>> -  rec->u.reg.num = regnum;
>> -  rec->u.reg.len = register_size (gdbarch, regnum);
>> -  if (rec->u.reg.len > sizeof (rec->u.reg.u.buf))
>> -    rec->u.reg.u.ptr = (gdb_byte *) xmalloc (rec->u.reg.len);
>> +  rec.type = record_full_reg;
>> +  rec.u.reg.num = regnum;
>> +  rec.u.reg.len = register_size (gdbarch, regnum);
>> +  if (rec.u.reg.len > sizeof (rec.u.reg.u.buf))
>> +    rec.u.reg.u.ptr = (gdb_byte *) xmalloc (rec.u.reg.len);
>>
>>     return rec;
>>   }
>>
>> -/* Free a record_full_reg record entry.  */
>> +/* Cleanup a record_full_reg record entry.  */
>>
>>   static inline void
>> -record_full_reg_release (struct record_full_entry *rec)
>> +record_full_reg_cleanup (record_full_entry rec)
>>   {
>> -  gdb_assert (rec->type == record_full_reg);
>> -  if (rec->u.reg.len > sizeof (rec->u.reg.u.buf))
>> -    xfree (rec->u.reg.u.ptr);
>> -  xfree (rec);
>> +  gdb_assert (rec.type == record_full_reg);
>> +  if (rec.u.reg.len > sizeof (rec.u.reg.u.buf))
>> +    xfree (rec.u.reg.u.ptr);
>>   }
>>
>> -/* Alloc a record_full_mem record entry.  */
>> +/* Init a record_full_mem record entry.  */
>>
>> -static inline struct record_full_entry *
>> -record_full_mem_alloc (CORE_ADDR addr, int len)
>> +static inline record_full_entry
>> +record_full_mem_init (CORE_ADDR addr, int len)
>>   {
>> -  struct record_full_entry *rec;
>> +  record_full_entry rec;
>>
>> -  rec = XCNEW (struct record_full_entry);
>> -  rec->type = record_full_mem;
>> -  rec->u.mem.addr = addr;
>> -  rec->u.mem.len = len;
>> -  if (rec->u.mem.len > sizeof (rec->u.mem.u.buf))
>> -    rec->u.mem.u.ptr = (gdb_byte *) xmalloc (len);
>> +  rec.type = record_full_mem;
>> +  rec.u.mem.addr = addr;
>> +  rec.u.mem.len = len;
>> +  if (rec.u.mem.len > sizeof (rec.u.mem.u.buf))
>> +    rec.u.mem.u.ptr = (gdb_byte *) xmalloc (len);
>> +  rec.u.mem.mem_entry_not_accessible = 0;
>>
>>     return rec;
>>   }
>>
>> -/* Free a record_full_mem record entry.  */
>> +/* Cleanup a record_full_mem record entry.  */
>>
>>   static inline void
>> -record_full_mem_release (struct record_full_entry *rec)
>> +record_full_mem_cleanup (record_full_entry rec)
>>   {
>> -  gdb_assert (rec->type == record_full_mem);
>> -  if (rec->u.mem.len > sizeof (rec->u.mem.u.buf))
>> -    xfree (rec->u.mem.u.ptr);
>> -  xfree (rec);
>> +  gdb_assert (rec.type == record_full_mem);
>> +  if (rec.u.mem.len > sizeof (rec.u.mem.u.buf))
>> +    xfree (rec.u.mem.u.ptr);
>>   }
>>
>> -/* Alloc a record_full_end record entry.  */
>> -
>> -static inline struct record_full_entry *
>> -record_full_end_alloc (void)
>> -{
>> -  struct record_full_entry *rec;
>> -
>> -  rec = XCNEW (struct record_full_entry);
>> -  rec->type = record_full_end;
>> -
>> -  return rec;
>> -}
>> -
>> -/* Free a record_full_end record entry.  */
>> +/* Free one record entry, any type.  */
>>
>>   static inline void
>> -record_full_end_release (struct record_full_entry *rec)
>> +record_full_entry_cleanup (record_full_entry rec)
>>   {
>> -  xfree (rec);
>> -}
>> -
>> -/* Free one record entry, any type.
>> -   Return entry->type, in case caller wants to know.  */
>>
>> -static inline enum record_full_type
>> -record_full_entry_release (struct record_full_entry *rec)
>> -{
>> -  enum record_full_type type = rec->type;
>> -
>> -  switch (type) {
>> +  switch (rec.type) {
>>     case record_full_reg:
>> -    record_full_reg_release (rec);
>> +    record_full_reg_cleanup (rec);
>>       break;
>>     case record_full_mem:
>> -    record_full_mem_release (rec);
>> -    break;
>> -  case record_full_end:
>> -    record_full_end_release (rec);
>> +    record_full_mem_cleanup (rec);
>>       break;
>>     }
>> -  return type;
>>   }
>>
>> -/* Free all record entries in list pointed to by REC.  */
>> -
>>   static void
>> -record_full_list_release (struct record_full_entry *rec)
>> +record_full_reset_history ()
>>   {
>> -  if (!rec)
>> -    return;
>> -
>> -  while (rec->next)
>> -    rec = rec->next;
>> +  record_full_insn_num = 0;
>> +  record_full_insn_count = 0;
>> +  record_full_next_insn = 0;
>>
>> -  while (rec->prev)
>> +  for (auto &insn : record_full_list)
>>       {
>> -      rec = rec->prev;
>> -      record_full_entry_release (rec->next);
>> +      for (auto &entry : insn.effects)
>> +	record_full_entry_cleanup (entry);
>>       }
>>
>> -  if (rec == &record_full_first)
>> +  record_full_list.clear ();
>> +}
> This function is missing a comment.
oops, fixed
>
>> +static void
>> +record_full_list_release_following (int index)
>> +{
>> +  for (int i = record_full_list.size () - 1; i > index; i--)
>>       {
>> -      record_full_insn_num = 0;
>> -      record_full_first.next = NULL;
>> +      for (auto &entry : record_full_list[i].effects)
>> +	record_full_entry_cleanup (entry);
>> +      record_full_list.pop_back ();
>>       }
>> -  else
>> -    record_full_entry_release (rec);
>> +  /* Set the next instruction to be past the end of the log so we
>> +     start recording if the user moves forward again.  */
>> +  record_full_next_insn = index;
>>   }
>>
>> -/* Free all record entries forward of the given list position.  */
>> +/* Save the incomplete instruction in the log.  */
>>
>>   static void
>> -record_full_list_release_following (struct record_full_entry *rec)
>> +record_full_save_instruction ()
>>   {
>> -  struct record_full_entry *tmp = rec->next;
>> +  ++record_full_insn_count;
>> +  record_full_incomplete_instruction.insn_num = record_full_insn_count;
>> +  record_full_incomplete_instruction.effects.shrink_to_fit ();
>> +  record_full_list.push_back (std::move
>> (record_full_incomplete_instruction));
>> +  record_full_next_insn++;
>>
>> -  rec->next = NULL;
>> -  while (tmp)
>> -    {
>> -      rec = tmp->next;
>> -      if (record_full_entry_release (tmp) == record_full_end)
>> -	{
>> -	  record_full_insn_num--;
>> -	  record_full_insn_count--;
>> -	}
>> -      tmp = rec;
>> -    }
>> +  record_full_reset_incomplete ();
>>   }
>>
>>   /* Delete the first instruction from the beginning of the log, to make
>> @@ -550,52 +511,22 @@ record_full_list_release_following (struct
>> record_full_entry *rec)
>>   static void
>>   record_full_list_release_first (void)
>>   {
>> -  struct record_full_entry *tmp;
>> -
>> -  if (!record_full_first.next)
>> +  if (record_full_list.empty ())
>>       return;
>>
>> -  /* Loop until a record_full_end.  */
>> -  while (1)
>> -    {
>> -      /* Cut record_full_first.next out of the linked list.  */
>> -      tmp = record_full_first.next;
>> -      record_full_first.next = tmp->next;
>> -      tmp->next->prev = &record_full_first;
>> -
>> -      /* tmp is now isolated, and can be deleted.  */
>> -      if (record_full_entry_release (tmp) == record_full_end)
>> -	break;	/* End loop at first record_full_end.  */
>> +  for (auto &entry : record_full_list[0].effects)
>> +    record_full_entry_cleanup (entry);
>>
>> -      if (!record_full_first.next)
>> -	{
>> -	  gdb_assert (record_full_insn_num == 1);
>> -	  break;	/* End loop when list is empty.  */
>> -	}
>> -    }
>> +  record_full_list.pop_front ();
>> +  --record_full_next_insn;
>>   }
>>
>>   /* Add a struct record_full_entry to record_full_arch_list.  */
>>
>>   static void
>> -record_full_arch_list_add (struct record_full_entry *rec)
>> +record_full_arch_list_add (record_full_entry &rec)
>>   {
>> -  if (record_debug > 1)
>> -    gdb_printf (gdb_stdlog,
>> -		"Process record: record_full_arch_list_add %s.\n",
>> -		host_address_to_string (rec));
>> -
>> -  if (record_full_arch_list_tail)
>> -    {
>> -      record_full_arch_list_tail->next = rec;
>> -      rec->prev = record_full_arch_list_tail;
>> -      record_full_arch_list_tail = rec;
>> -    }
>> -  else
>> -    {
>> -      record_full_arch_list_head = rec;
>> -      record_full_arch_list_tail = rec;
>> -    }
>> +  record_full_incomplete_instruction.effects.push_back (rec);
> Now since this is one line only, do we really need a separate function for this?
I guess not, but I think at the same time it would make the patch that 
much harder to review because the fact that this being how you add to 
the incomplete list is not obvious...
>
>>   }
>>
>>   /* Return the value storage location of a record entry.  */
>> @@ -613,7 +544,6 @@ record_full_get_loc (struct record_full_entry *rec)
>>         return rec->u.reg.u.ptr;
>>       else
>>         return rec->u.reg.u.buf;
>> -  case record_full_end:
>>     default:
>>       gdb_assert_not_reached ("unexpected record_full_entry type");
>>       return NULL;
>> @@ -625,7 +555,7 @@ record_full_get_loc (struct record_full_entry *rec)
>>   int
>>   record_full_arch_list_add_reg (struct regcache *regcache, int regnum)
>>   {
>> -  struct record_full_entry *rec;
>> +  record_full_entry rec;
>>
>>     if (record_debug > 1)
>>       gdb_printf (gdb_stdlog,
>> @@ -633,9 +563,9 @@ record_full_arch_list_add_reg (struct regcache
>> *regcache, int regnum)
>>   		"record list.\n",
>>   		regnum);
>>
>> -  rec = record_full_reg_alloc (regcache, regnum);
>> +  rec = record_full_reg_init (regcache, regnum);
>>
>> -  regcache->cooked_read (regnum, record_full_get_loc (rec));
>> +  regcache->cooked_read (regnum, record_full_get_loc (&rec));
>>
>>     record_full_arch_list_add (rec);
>>
>> @@ -648,7 +578,7 @@ record_full_arch_list_add_reg (struct regcache
>> *regcache, int regnum)
>>   int
>>   record_full_arch_list_add_mem (CORE_ADDR addr, int len)
>>   {
>> -  struct record_full_entry *rec;
>> +  record_full_entry rec;
>>
>>     if (record_debug > 1)
>>       gdb_printf (gdb_stdlog,
>> @@ -659,12 +589,12 @@ record_full_arch_list_add_mem (CORE_ADDR
>> addr, int len)
>>     if (!addr)	/* FIXME: Why?  Some arch must permit it...  */
>>       return 0;
>>
>> -  rec = record_full_mem_alloc (addr, len);
>> +  rec = record_full_mem_init (addr, len);
>>
>>     if (record_read_memory (current_inferior ()->arch (), addr,
>> -			  record_full_get_loc (rec), len))
>> +			  record_full_get_loc (&rec), len))
>>       {
>> -      record_full_mem_release (rec);
>> +      record_full_mem_cleanup (rec);
>>         return -1;
>>       }
>>
>> @@ -673,27 +603,6 @@ record_full_arch_list_add_mem (CORE_ADDR addr,
>> int len)
>>     return 0;
>>   }
>>
>> -/* Add a record_full_end type struct record_full_entry to
>> -   record_full_arch_list.  */
>> -
>> -int
>> -record_full_arch_list_add_end (void)
>> -{
>> -  struct record_full_entry *rec;
>> -
>> -  if (record_debug > 1)
>> -    gdb_printf (gdb_stdlog,
>> -		"Process record: add end to arch list.\n");
>> -
>> -  rec = record_full_end_alloc ();
>> -  rec->u.end.sigval = GDB_SIGNAL_0;
>> -  rec->u.end.insn_num = ++record_full_insn_count;
>> -
>> -  record_full_arch_list_add (rec);
>> -
>> -  return 0;
>> -}
>> -
>>   static void
>>   record_full_check_insn_num (void)
>>   {
>> @@ -725,8 +634,7 @@ record_full_message (struct regcache *regcache,
>> enum gdb_signal signal)
>>
>>     try
>>       {
>> -      record_full_arch_list_head = NULL;
>> -      record_full_arch_list_tail = NULL;
>> +      record_full_reset_incomplete ();
>>
>>         /* Check record_full_insn_num.  */
>>         record_full_check_insn_num ();
>> @@ -746,20 +654,16 @@ record_full_message (struct regcache *regcache,
>> enum gdb_signal signal)
>>   	 the user says something different, like "deliver this signal"
>>   	 during the replay mode).
>>
>> -	 User should understand that nothing he does during the replay
>> -	 mode will change the behavior of the child.  If he tries,
>> +	 User should understand that nothing they do during the replay
>> +	 mode will change the behavior of the child.  If they try,
>>   	 then that is a user error.
>>
>>   	 But we should still deliver the signal to gdb during the replay,
>>   	 if we delivered it during the recording.  Therefore we should
>>   	 record the signal during record_full_wait, not
>>   	 record_full_resume.  */
>> -      if (record_full_list != &record_full_first)  /* FIXME better way
>> -						      to check */
>> -	{
>> -	  gdb_assert (record_full_list->type == record_full_end);
>> -	  record_full_list->u.end.sigval = signal;
>> -	}
>> +      if (signal != GDB_SIGNAL_0 && !record_full_list.empty ())
>> +	record_full_list[record_full_next_insn - 1].sigval = signal;
>>
>>         if (signal == GDB_SIGNAL_0
>>   	  || !gdbarch_process_record_signal_p (gdbarch))
>> @@ -778,13 +682,11 @@ record_full_message (struct regcache *regcache,
>> enum gdb_signal signal)
>>       }
>>     catch (const gdb_exception &ex)
>>       {
>> -      record_full_list_release (record_full_arch_list_tail);
>> +      record_full_reset_incomplete ();
>>         throw;
>>       }
>>
>> -  record_full_list->next = record_full_arch_list_head;
>> -  record_full_arch_list_head->prev = record_full_list;
>> -  record_full_list = record_full_arch_list_tail;
>> +  record_full_save_instruction ();
>>
>>     if (record_full_insn_num == record_full_insn_max_num)
>>       record_full_list_release_first ();
>> @@ -829,9 +731,9 @@ static enum target_stop_reason
>> record_full_stop_reason
>>      entries and memory entries, followed by an 'end' entry.  */
>>
>>   static inline void
>> -record_full_exec_insn (struct regcache *regcache,
>> -		       struct gdbarch *gdbarch,
>> -		       struct record_full_entry *entry)
>> +record_full_exec_entry (regcache *regcache,
>> +			gdbarch *gdbarch,
>> +			record_full_entry *entry)
>>   {
>>     switch (entry->type)
>>       {
>> @@ -909,6 +811,15 @@ record_full_exec_insn (struct regcache *regcache,
>>       }
>>   }
> Again function comment.. 😉

Darn I'm really bad at these lol

Fixed :)

>
>> +static inline void
>> +record_full_exec_insn (regcache *regcache,
>> +		       gdbarch *gdbarch,
>> +		       record_full_instruction &insn)
>> +{
>> +  for (auto &entry : insn.effects)
>> +    record_full_exec_entry (regcache, gdbarch, &entry);
>> +}
>> +
>>   static void record_full_restore (struct bfd &cbfd);
>>
>>   /* Asynchronous signal handle registered as event loop source for when
>> @@ -983,10 +894,7 @@ record_full_open (const char *args, int from_tty)
>>     record_preopen ();
>>
>>     /* Reset */
>> -  record_full_insn_num = 0;
>> -  record_full_insn_count = 0;
>> -  record_full_list = &record_full_first;
>> -  record_full_list->next = NULL;
>> +  record_full_reset_history ();
>>
>>     bfd *cbfd = get_inferior_core_bfd (current_inferior ());
>>     if (cbfd != nullptr)
>> @@ -1014,7 +922,7 @@ record_full_base_target::close ()
>>     if (record_debug)
>>       gdb_printf (gdb_stdlog, "Process record: record_full_close\n");
>>
>> -  record_full_list_release (record_full_list);
>> +  record_full_reset_history ();
>>
>>     /* Release record_full_core_regbuf.  */
>>     if (record_full_core_regbuf)
>> @@ -1313,7 +1221,6 @@ record_full_wait_1 (struct target_ops *ops,
>>         struct gdbarch *gdbarch = regcache->arch ();
>>         const address_space *aspace = current_inferior ()->aspace.get ();
>>         int continue_flag = 1;
>> -      int first_record_full_end = 1;
>>
>>         try
>>   	{
>> @@ -1322,7 +1229,6 @@ record_full_wait_1 (struct target_ops *ops,
>>   	  record_full_stop_reason = TARGET_STOPPED_BY_NO_REASON;
>>   	  status->set_stopped (GDB_SIGNAL_0);
>>
>> -	  /* Check breakpoint when forward execute.  */
>>   	  if (execution_direction == EXEC_FORWARD)
>>   	    {
>>   	      tmp_pc = regcache_read_pc (regcache);
>> @@ -1330,8 +1236,7 @@ record_full_wait_1 (struct target_ops *ops,
>>
>> &record_full_stop_reason))
>>   		{
>>   		  if (record_debug)
>> -		    gdb_printf (gdb_stdlog,
>> -				"Process record: break at %s.\n",
>> +		    gdb_printf (gdb_stdlog, "Process record: break at %s.\n",
>>   				paddress (gdbarch, tmp_pc));
>>   		  goto replay_out;
>>   		}
>> @@ -1344,10 +1249,10 @@ record_full_wait_1 (struct target_ops *ops,
>>   	     the signal.  */
>>   	  target_terminal::ours ();
>>
>> -	  /* In EXEC_FORWARD mode, record_full_list points to the tail of
>> prev
>> -	     instruction.  */
>> -	  if (execution_direction == EXEC_FORWARD && record_full_list-
>>> next)
>> -	    record_full_list = record_full_list->next;
>> +	  /* In EXEC_FORWARD mode, record_full_next_insn is the next
>> +	     instruction to be executed.  */
>> +	  if (execution_direction == EXEC_REVERSE)
>> +	    record_full_next_insn--;
>>
>>   	  /* Loop over the record_full_list, looking for the next place to
>>   	     stop.  */
>> @@ -1355,108 +1260,92 @@ record_full_wait_1 (struct target_ops *ops,
>>   	    {
>>   	      /* Check for beginning and end of log.  */
>>   	      if (execution_direction == EXEC_REVERSE
>> -		  && record_full_list == &record_full_first)
>> +		  && record_full_next_insn < 0)
>>   		{
>>   		  /* Hit beginning of record log in reverse.  */
>>   		  status->set_no_history ();
>> +		  record_full_next_insn = 0;
>>   		  break;
>>   		}
>>   	      if (execution_direction != EXEC_REVERSE
>> -		  && !record_full_list->next)
>> +		  && record_full_next_insn == record_full_list.size ())
>>   		{
>>   		  /* Hit end of record log going forward.  */
>>   		  status->set_no_history ();
>>   		  break;
>>   		}
>>
>> -	      record_full_exec_insn (regcache, gdbarch, record_full_list);
>> +	      record_full_exec_insn
>> +		(regcache, gdbarch,
>> +		 record_full_list[record_full_next_insn]);
>>
>> -	      if (record_full_list->type == record_full_end)
>> +	      /* step */
>> +	      if (record_full_resume_step)
>>   		{
>>   		  if (record_debug > 1)
>> -		    gdb_printf
>> -		      (gdb_stdlog,
>> -		       "Process record: record_full_end %s to "
>> -		       "inferior.\n",
>> -		       host_address_to_string (record_full_list));
>> -
>> -		  if (first_record_full_end
>> -		      && execution_direction == EXEC_REVERSE)
>> -		    {
>> -		      /* When reverse execute, the first
>> -			 record_full_end is the part of current
>> -			 instruction.  */
>> -		      first_record_full_end = 0;
>> -		    }
>> -		  else
>> -		    {
>> -		      /* In EXEC_REVERSE mode, this is the
>> -			 record_full_end of prev instruction.  In
>> -			 EXEC_FORWARD mode, this is the
>> -			 record_full_end of current instruction.  */
>> -		      /* step */
>> -		      if (record_full_resume_step)
>> -			{
>> -			  if (record_debug > 1)
>> -			    gdb_printf (gdb_stdlog,
>> -					"Process record: step.\n");
>> -			  continue_flag = 0;
>> -			}
>> -
>> -		      /* check breakpoint */
>> -		      tmp_pc = regcache_read_pc (regcache);
>> -		      if (record_check_stopped_by_breakpoint
>> -			  (aspace, tmp_pc, &record_full_stop_reason))
>> -			{
>> -			  if (record_debug)
>> -			    gdb_printf (gdb_stdlog,
>> -					"Process record: break "
>> -					"at %s.\n",
>> -					paddress (gdbarch, tmp_pc));
>> +		    gdb_printf (gdb_stdlog,
>> +				"Process record: step.\n");
>> +		  continue_flag = 0;
>> +		}
>>
>> -			  continue_flag = 0;
>> -			}
>> +	      /* check breakpoint */
>> +	      tmp_pc = regcache_read_pc (regcache);
>> +	      if (record_check_stopped_by_breakpoint
>> +		  (aspace, tmp_pc, &record_full_stop_reason))
>> +		{
>> +		  if (record_debug)
>> +		    gdb_printf (gdb_stdlog,
>> +				"Process record: break "
>> +				"at %s.\n",
>> +				paddress (gdbarch, tmp_pc));
>>
>> -		      if (record_full_stop_reason
>> -			  == TARGET_STOPPED_BY_WATCHPOINT)
>> -			{
>> -			  if (record_debug)
>> -			    gdb_printf (gdb_stdlog,
>> -					"Process record: hit hw "
>> -					"watchpoint.\n");
>> -			  continue_flag = 0;
>> -			}
>> -		      /* Check target signal */
>> -		      if (record_full_list->u.end.sigval != GDB_SIGNAL_0)
>> -			/* FIXME: better way to check */
>> -			continue_flag = 0;
>> -		    }
>> +		  continue_flag = 0;
>>   		}
>>
>> -	      if (continue_flag)
>> +	      if (record_full_stop_reason
>> +		  == TARGET_STOPPED_BY_WATCHPOINT)
>>   		{
>> -		  if (execution_direction == EXEC_REVERSE)
>> -		    {
>> -		      if (record_full_list->prev)
>> -			record_full_list = record_full_list->prev;
>> -		    }
>> -		  else
>> -		    {
>> -		      if (record_full_list->next)
>> -			record_full_list = record_full_list->next;
>> -		    }
>> +		  if (record_debug)
>> +		    gdb_printf (gdb_stdlog,
>> +				"Process record: hit hw "
>> +				"watchpoint.\n");
>> +		  continue_flag = 0;
>>   		}
>> +	      if (record_full_list[record_full_next_insn].sigval.has_value ())
>> +		continue_flag = 0;
>> +
>> +	      if (execution_direction == EXEC_REVERSE)
>> +		record_full_next_insn--;
>> +	      else
>> +		record_full_next_insn++;
>>   	    }
>>   	  while (continue_flag);
>>
>> +	  if (record_full_next_insn < 0)
>> +	    {
>> +	      gdb_assert (execution_direction == EXEC_REVERSE);
>> +	      record_full_next_insn = 0;
>> +	    }
>> +	  else if (record_full_next_insn > record_full_list.size ())
>> +	    {
>> +	      gdb_assert (execution_direction == EXEC_FORWARD);
>> +	      record_full_next_insn = record_full_list.size ();
>> +	    }
>> +	  /* Reset the current instruction to point to the one to be replayed
>> +	     moving forward.  */
>> +	  else if (execution_direction == EXEC_REVERSE)
>> +	    record_full_next_insn++;
>> +
>>   	replay_out:
>>   	  if (status->kind () == TARGET_WAITKIND_STOPPED)
>>   	    {
>> +	      int insn = (execution_direction == EXEC_FORWARD)
>> +			 ? record_full_next_insn - 1 : record_full_next_insn;
>>   	      if (record_full_get_sig)
>>   		status->set_stopped (GDB_SIGNAL_INT);
>> -	      else if (record_full_list->u.end.sigval != GDB_SIGNAL_0)
>> -		/* FIXME: better way to check */
>> -		status->set_stopped (record_full_list->u.end.sigval);
>> +	      else if (record_full_list[insn].sigval.has_value ())
>> +		status->set_stopped
>> +		  (record_full_list[insn].sigval.value ());
>>   	      else
>>   		status->set_stopped (GDB_SIGNAL_TRAP);
>>   	    }
>> @@ -1464,12 +1353,9 @@ record_full_wait_1 (struct target_ops *ops,
>>         catch (const gdb_exception &ex)
>>   	{
>>   	  if (execution_direction == EXEC_REVERSE)
>> -	    {
>> -	      if (record_full_list->next)
>> -		record_full_list = record_full_list->next;
>> -	    }
>> +	    record_full_next_insn++;
>>   	  else
>> -	    record_full_list = record_full_list->prev;
>> +	    record_full_next_insn--;
>>
>>   	  throw;
>>   	}
>> @@ -1557,8 +1443,7 @@ record_full_registers_change (struct regcache
>> *regcache, int regnum)
>>     /* Check record_full_insn_num.  */
>>     record_full_check_insn_num ();
>>
>> -  record_full_arch_list_head = NULL;
>> -  record_full_arch_list_tail = NULL;
>> +  record_full_reset_incomplete ();
>>
>>     if (regnum < 0)
>>       {
>> @@ -1568,7 +1453,7 @@ record_full_registers_change (struct regcache
>> *regcache, int regnum)
>>   	{
>>   	  if (record_full_arch_list_add_reg (regcache, i))
>>   	    {
>> -	      record_full_list_release (record_full_arch_list_tail);
>> +	      record_full_reset_incomplete ();
>>   	      error (_("Process record: failed to record execution log."));
>>   	    }
>>   	}
>> @@ -1577,18 +1462,11 @@ record_full_registers_change (struct regcache
>> *regcache, int regnum)
>>       {
>>         if (record_full_arch_list_add_reg (regcache, regnum))
>>   	{
>> -	  record_full_list_release (record_full_arch_list_tail);
>> +	  record_full_reset_incomplete ();
>>   	  error (_("Process record: failed to record execution log."));
>>   	}
>>       }
>> -  if (record_full_arch_list_add_end ())
>> -    {
>> -      record_full_list_release (record_full_arch_list_tail);
>> -      error (_("Process record: failed to record execution log."));
>> -    }
>> -  record_full_list->next = record_full_arch_list_head;
>> -  record_full_arch_list_head->prev = record_full_list;
>> -  record_full_list = record_full_arch_list_tail;
>> +  record_full_save_instruction ();
>>
>>     if (record_full_insn_num == record_full_insn_max_num)
>>       record_full_list_release_first ();
>> @@ -1607,7 +1485,7 @@ record_full_target::store_registers (struct
>> regcache *regcache, int regno)
>>   	{
>>   	  int n;
>>
>> -	  /* Let user choose if he wants to write register or not.  */
>> +	  /* Let user choose if they want to write register or not.  */
> It might be cleaner to move such changes in a separate commit,
> since they just make this big patch even bigger.
Considering it is just a tiny change to a comment, I don't  think it 
makes that much of a difference, really.
>
>>   	  if (regno < 0)
>>   	    n =
>>   	      query (_("Because GDB is in replay mode, changing the "
>> @@ -1642,7 +1520,7 @@ record_full_target::store_registers (struct
>> regcache *regcache, int regno)
>>   	    }
>>
>>   	  /* Destroy the record from here forward.  */
>> -	  record_full_list_release_following (record_full_list);
>> +	  record_full_list_release_following (record_full_next_insn);
>>   	}
>>
>>         record_full_registers_change (regcache, regno);
>> @@ -1675,36 +1553,24 @@ record_full_target::xfer_partial (enum
>> target_object object,
>>   	    error (_("Process record canceled the operation."));
>>
>>   	  /* Destroy the record from here forward.  */
>> -	  record_full_list_release_following (record_full_list);
>> +	  record_full_list_release_following (record_full_next_insn);
>>   	}
>>
>>         /* Check record_full_insn_num */
>>         record_full_check_insn_num ();
>>
>>         /* Record registers change to list as an instruction.  */
>> -      record_full_arch_list_head = NULL;
>> -      record_full_arch_list_tail = NULL;
>> +      record_full_reset_incomplete ();
>>         if (record_full_arch_list_add_mem (offset, len))
>>   	{
>> -	  record_full_list_release (record_full_arch_list_tail);
>> -	  if (record_debug)
>> -	    gdb_printf (gdb_stdlog,
>> -			"Process record: failed to record "
>> -			"execution log.");
>> -	  return TARGET_XFER_E_IO;
>> -	}
>> -      if (record_full_arch_list_add_end ())
>> -	{
>> -	  record_full_list_release (record_full_arch_list_tail);
>> +	  record_full_reset_incomplete ();
>>   	  if (record_debug)
>>   	    gdb_printf (gdb_stdlog,
>>   			"Process record: failed to record "
>>   			"execution log.");
>>   	  return TARGET_XFER_E_IO;
>>   	}
>> -      record_full_list->next = record_full_arch_list_head;
>> -      record_full_arch_list_head->prev = record_full_list;
>> -      record_full_list = record_full_arch_list_tail;
>> +      record_full_save_instruction ();
>>
>>         if (record_full_insn_num == record_full_insn_max_num)
>>   	record_full_list_release_first ();
>> @@ -1865,9 +1731,11 @@ record_full_base_target::get_bookmark (const
>> char *args, int from_tty)
>>   {
>>     char *ret = NULL;
>>
>> +  if (record_full_list.empty ())
>> +    return (gdb_byte *) ret;
>> +
>>     /* Return stringified form of instruction count.  */
>> -  if (record_full_list && record_full_list->type == record_full_end)
>> -    ret = xstrdup (pulongest (record_full_list->u.end.insn_num));
>> +  ret = xstrdup (pulongest
>> (record_full_list[record_full_next_insn].insn_num));
>>
>>     if (record_debug)
>>       {
>> @@ -1923,30 +1791,22 @@ record_full_base_target::record_method
>> (ptid_t ptid)
>>   void
>>   record_full_base_target::info_record ()
>>   {
>> -  struct record_full_entry *p;
>> -
>>     if (RECORD_FULL_IS_REPLAY)
>>       gdb_printf (_("Replay mode:\n"));
>>     else
>>       gdb_printf (_("Record mode:\n"));
>>
>> -  /* Find entry for first actual instruction in the log.  */
>> -  for (p = record_full_first.next;
>> -       p != NULL && p->type != record_full_end;
>> -       p = p->next)
>> -    ;
>> -
>>     /* Do we have a log at all?  */
>> -  if (p != NULL && p->type == record_full_end)
>> +  if (!record_full_list.empty ())
>>       {
>>         /* Display instruction number for first instruction in the log.  */
>> -      gdb_printf (_("Lowest recorded instruction number is %s.\n"),
>> -		  pulongest (p->u.end.insn_num));
>> +      gdb_printf (_("Lowest recorded instruction number is %u.\n"),
>> +		  record_full_list[0].insn_num);
>>
>>         /* If in replay mode, display where we are in the log.  */
>>         if (RECORD_FULL_IS_REPLAY)
>> -	gdb_printf (_("Current instruction number is %s.\n"),
>> -		    pulongest (record_full_list->u.end.insn_num));
>> +	gdb_printf (_("Current instruction number is %u.\n"),
>> +		    record_full_list[record_full_next_insn].insn_num);
>>
>>         /* Display instruction number for last instruction in the log.  */
>>         gdb_printf (_("Highest recorded instruction number is %s.\n"),
>> @@ -1975,7 +1835,7 @@ record_full_base_target::supports_delete_record
>> ()
>>   void
>>   record_full_base_target::delete_record ()
>>   {
>> -  record_full_list_release_following (record_full_list);
>> +  record_full_reset_history ();
>>   }
>>
>>   /* The "record_is_replaying" target method.  */
>> @@ -2001,23 +1861,23 @@ record_full_base_target::record_will_replay
>> (ptid_t ptid, int dir)
>>   /* Go to a specific entry.  */
>>
>>   static void
>> -record_full_goto_entry (struct record_full_entry *p)
>> +record_full_goto_entry (size_t target_insn)
>>   {
>> -  if (p == NULL)
>> +  if (target_insn >= record_full_list.size ())
>>       error (_("Target insn not found."));
>> -  else if (p == record_full_list)
>> +  else if (target_insn == record_full_next_insn)
>>       error (_("Already at target insn."));
>> -  else if (p->u.end.insn_num > record_full_list->u.end.insn_num)
>> +  else if (target_insn > record_full_next_insn)
>>       {
>>         gdb_printf (_("Go forward to insn number %s\n"),
>> -		  pulongest (p->u.end.insn_num));
>> -      record_full_goto_insn (p, EXEC_FORWARD);
>> +		  pulongest (record_full_list[target_insn].insn_num));
>> +      record_full_goto_insn (target_insn, EXEC_FORWARD);
>>       }
>>     else
>>       {
>>         gdb_printf (_("Go backward to insn number %s\n"),
>> -		  pulongest (p->u.end.insn_num));
>> -      record_full_goto_insn (p, EXEC_REVERSE);
>> +		  pulongest (target_insn));
>> +      record_full_goto_insn (target_insn, EXEC_REVERSE);
>>       }
>>
>>     registers_changed ();
>> @@ -2033,13 +1893,7 @@ record_full_goto_entry (struct record_full_entry
>> *p)
>>   void
>>   record_full_base_target::goto_record_begin ()
>>   {
>> -  struct record_full_entry *p = NULL;
>> -
>> -  for (p = &record_full_first; p != NULL; p = p->next)
>> -    if (p->type == record_full_end)
>> -      break;
>> -
>> -  record_full_goto_entry (p);
>> +  record_full_goto_entry (0);
>>   }
>>
>>   /* The "goto_record_end" target method.  */
>> @@ -2047,15 +1901,7 @@ record_full_base_target::goto_record_begin ()
>>   void
>>   record_full_base_target::goto_record_end ()
>>   {
>> -  struct record_full_entry *p = NULL;
>> -
>> -  for (p = record_full_list; p->next != NULL; p = p->next)
>> -    ;
>> -  for (; p!= NULL; p = p->prev)
>> -    if (p->type == record_full_end)
>> -      break;
>> -
>> -  record_full_goto_entry (p);
>> +  record_full_goto_entry (record_full_list.size () - 1);
>>   }
>>
>>   /* The "goto_record" target method.  */
>> @@ -2063,13 +1909,7 @@ record_full_base_target::goto_record_end ()
>>   void
>>   record_full_base_target::goto_record (ULONGEST target_insn)
>>   {
>> -  struct record_full_entry *p = NULL;
>> -
>> -  for (p = &record_full_first; p != NULL; p = p->next)
>> -    if (p->type == record_full_end && p->u.end.insn_num == target_insn)
>> -      break;
>> -
>> -  record_full_goto_entry (p);
>> +  record_full_goto_entry (target_insn);
>>   }
>>
>>   /* The "record_stop_replaying" target method.  */
>> @@ -2339,19 +2179,96 @@ netorder32 (uint32_t input)
>>     return ret;
>>   }
> Function comment. And I think it would be cleaner to move this refactoring in
> a separate patch, too. Or is this separate function only possible due to the change
> from using a list to std::deque?
This function could be in a patch of its own, yeah. Will do that for v4
>
>> +static void
>> +record_full_read_entry_from_bfd (bfd *cbfd, asection *osec, int *bfd_offset)
>> +{
>> +  uint8_t rectype;
>> +  uint32_t regnum, len;
>> +  uint64_t addr;
>> +  regcache *cache = get_thread_regcache (inferior_thread ());
>> +
>> +  bfdcore_read (cbfd, osec, &rectype, sizeof (rectype), bfd_offset);
>> +  switch (rectype)
>> +    {
>> +    case record_full_reg: /* reg */
>> +      {
>> +	/* Get register number to regnum.  */
>> +	bfdcore_read (cbfd, osec, &regnum, sizeof (regnum), bfd_offset);
>> +	regnum = netorder32 (regnum);
>> +
>> +	record_full_entry rec;
>> +
>> +	rec = record_full_reg_init (cache, regnum);
>> +
>> +	/* Get val.  */
>> +	bfdcore_read (cbfd, osec, record_full_get_loc (&rec),
>> +		      rec.u.reg.len, bfd_offset);
>> +
>> +	if (record_debug)
>> +	  gdb_printf (gdb_stdlog,
>> +		      "  Reading register %d (1 "
>> +		      "plus %lu plus %d bytes)\n",
>> +		      rec.u.reg.num,
>> +		      (unsigned long) sizeof (regnum),
>> +		      rec.u.reg.len);
>> +
>> +	record_full_arch_list_add (rec);
>> +	break;
>> +      }
>> +
>> +    case record_full_mem: /* mem */
>> +      {
>> +	/* Get len.  */
>> +	bfdcore_read (cbfd, osec, &len, sizeof (len), bfd_offset);
>> +	len = netorder32 (len);
>> +
>> +	/* Get addr.  */
>> +	bfdcore_read (cbfd, osec, &addr, sizeof (addr),
>> +		      bfd_offset);
>> +	addr = netorder64 (addr);
>> +
>> +	record_full_entry rec;
>> +	rec = record_full_mem_init (addr, len);
>> +
>> +	/* Get val.  */
>> +	bfdcore_read (cbfd, osec, record_full_get_loc (&rec), len,
>> +		      bfd_offset);
>> +
>> +	if (record_debug)
>> +	  gdb_printf (gdb_stdlog,
>> +		      "  Reading memory %s (1 plus "
>> +		      "%lu plus %lu plus %d bytes)\n",
>> +		      paddress (get_current_arch (),
>> +				rec.u.mem.addr),
>> +		      (unsigned long) sizeof (addr),
>> +		      (unsigned long) sizeof (len),
>> +		      len);
>> +
>> +	record_full_arch_list_add (rec);
>> +	break;
>> +      }
>> +
>> +    default:
>> +      error (_("Bad entry type %d, offset %d of %lu in core file %ps."),
>> +	     rectype, *bfd_offset, bfd_section_size (osec),
>> +	     styled_string (file_name_style.style (),
>> +			    bfd_get_filename (cbfd)));
>> +      break;
>> +    }
>> +}
>> +
>>   /* Restore the execution log from core file CBFD.  */
>>
>>   static void
>>   record_full_restore (struct bfd &cbfd)
>>   {
>>     uint32_t magic;
>> -  struct record_full_entry *rec;
>>     asection *osec;
>>     uint32_t osec_size;
>>     int bfd_offset = 0;
>>
>>     /* "record_full_restore" can only be called when record list is empty.  */
>> -  gdb_assert (record_full_first.next == NULL);
>> +  gdb_assert (record_full_list.empty ());
>>
>>     if (record_debug)
>>       gdb_printf (gdb_stdlog, "Restoring recording from core file.\n");
>> @@ -2379,124 +2296,47 @@ record_full_restore (struct bfd &cbfd)
>>   		"RECORD_FULL_FILE_MAGIC (0x%s)\n",
>>   		phex_nz (netorder32 (magic), 4));
>>
>> -  /* Restore the entries in recfd into record_full_arch_list_head and
>> -     record_full_arch_list_tail.  */
>> -  record_full_arch_list_head = NULL;
>> -  record_full_arch_list_tail = NULL;
>>     record_full_insn_num = 0;
>>
>>     try
>>       {
>> -      regcache *regcache = get_thread_regcache (inferior_thread ());
>> -
>> -      while (1)
>> +      while (bfd_offset < osec_size)
>>   	{
>> -	  uint8_t rectype;
>> -	  uint32_t regnum, len, signal, count;
>> -	  uint64_t addr;
>> +	  record_full_reset_incomplete ();
>> +	  uint32_t eff_count = 0;
>> +	  uint8_t sigval;
>> +	  uint32_t insn_num;
>>
>> -	  /* We are finished when offset reaches osec_size.  */
>> -	  if (bfd_offset >= osec_size)
>> -	    break;
>> -	  bfdcore_read (&cbfd, osec, &rectype, sizeof (rectype), &bfd_offset);
>> +	  /* First read the generic information for an instruction.  */
>> +	  bfdcore_read (&cbfd, osec, &sigval, sizeof (uint8_t), &bfd_offset);
>> +	  bfdcore_read (&cbfd, osec, &eff_count, sizeof (uint32_t),
>> +			&bfd_offset);
>> +	  bfdcore_read (&cbfd, osec, &insn_num, sizeof (uint32_t),
>> +			&bfd_offset);
>>
>> -	  switch (rectype)
>> -	    {
>> -	    case record_full_reg: /* reg */
>> -	      /* Get register number to regnum.  */
>> -	      bfdcore_read (&cbfd, osec, &regnum, sizeof (regnum),
>> -			    &bfd_offset);
>> -	      regnum = netorder32 (regnum);
>> -
>> -	      rec = record_full_reg_alloc (regcache, regnum);
>> -
>> -	      /* Get val.  */
>> -	      bfdcore_read (&cbfd, osec, record_full_get_loc (rec),
>> -			    rec->u.reg.len, &bfd_offset);
>> -
>> -	      if (record_debug)
>> -		gdb_printf (gdb_stdlog,
>> -			    "  Reading register %d (1 "
>> -			    "plus %lu plus %d bytes)\n",
>> -			    rec->u.reg.num,
>> -			    (unsigned long) sizeof (regnum),
>> -			    rec->u.reg.len);
>> -	      break;
>> +	  record_full_incomplete_instruction.insn_num = netorder32
>> (insn_num);
>> +	  if (sigval != GDB_SIGNAL_0)
>> +	    record_full_incomplete_instruction.sigval = (gdb_signal) sigval;
>>
>> -	    case record_full_mem: /* mem */
>> -	      /* Get len.  */
>> -	      bfdcore_read (&cbfd, osec, &len, sizeof (len), &bfd_offset);
>> -	      len = netorder32 (len);
>> -
>> -	      /* Get addr.  */
>> -	      bfdcore_read (&cbfd, osec, &addr, sizeof (addr), &bfd_offset);
>> -	      addr = netorder64 (addr);
>> -
>> -	      rec = record_full_mem_alloc (addr, len);
>> -
>> -	      /* Get val.  */
>> -	      bfdcore_read (&cbfd, osec, record_full_get_loc (rec),
>> -			    rec->u.mem.len, &bfd_offset);
>> -
>> -	      if (record_debug)
>> -		gdb_printf (gdb_stdlog,
>> -			    "  Reading memory %s (1 plus "
>> -			    "%lu plus %lu plus %d bytes)\n",
>> -			    paddress (get_current_arch (),
>> -				      rec->u.mem.addr),
>> -			    (unsigned long) sizeof (addr),
>> -			    (unsigned long) sizeof (len),
>> -			    rec->u.mem.len);
>> -	      break;
>> +	  eff_count = netorder32 (eff_count);
>>
>> -	    case record_full_end: /* end */
>> -	      rec = record_full_end_alloc ();
>> -	      record_full_insn_num ++;
>> -
>> -	      /* Get signal value.  */
>> -	      bfdcore_read (&cbfd, osec, &signal, sizeof (signal),
>> -			    &bfd_offset);
>> -	      signal = netorder32 (signal);
>> -	      rec->u.end.sigval = (enum gdb_signal) signal;
>> -
>> -	      /* Get insn count.  */
>> -	      bfdcore_read (&cbfd, osec, &count, sizeof (count), &bfd_offset);
>> -	      count = netorder32 (count);
>> -	      rec->u.end.insn_num = count;
>> -	      record_full_insn_count = count + 1;
>> -	      if (record_debug)
>> -		gdb_printf (gdb_stdlog,
>> -			    "  Reading record_full_end (1 + "
>> -			    "%lu + %lu bytes), offset == %s\n",
>> -			    (unsigned long) sizeof (signal),
>> -			    (unsigned long) sizeof (count),
>> -			    paddress (get_current_arch (),
>> -				      bfd_offset));
>> -	      break;
>> +	  /* This deals with all the side effects.  */
>> +	 while (eff_count > 0)
>> +	   {
>> +	     eff_count--;
>>
>> -	    default:
>> -	      error (_("Bad entry type in core file %ps."),
>> -		     styled_string (file_name_style.style (),
>> -				    bfd_get_filename (&cbfd)));
>> -	      break;
>> -	    }
>> +	     record_full_read_entry_from_bfd (&cbfd, osec, &bfd_offset);
>> +	   }
>>
>> -	  /* Add rec to record arch list.  */
>> -	  record_full_arch_list_add (rec);
>> +	  record_full_save_instruction ();
>>   	}
>>       }
>>     catch (const gdb_exception &ex)
>>       {
>> -      record_full_list_release (record_full_arch_list_tail);
>> +      record_full_reset_incomplete ();
>>         throw;
>>       }
>>
>> -  /* Add record_full_arch_list_head to the end of record list.  */
>> -  record_full_first.next = record_full_arch_list_head;
>> -  record_full_arch_list_head->prev = &record_full_first;
>> -  record_full_arch_list_tail->next = NULL;
>> -  record_full_list = &record_full_first;
>> -
>>     /* Update record_full_insn_max_num.  */
>>     if (record_full_insn_num > record_full_insn_max_num)
>>       {
>> @@ -2505,6 +2345,10 @@ record_full_restore (struct bfd &cbfd)
>>   	       record_full_insn_max_num);
>>       }
>>
>> +  /* When loading a recording, we'll always start at the oldest possible
>> +     instruction, no matter where the original recording was stopped.  */
>> +  record_full_next_insn = 0;
>> +
>>     /* Succeeded.  */
>>     gdb_printf (_("Restored records from core file %s.\n"),
>>   	      bfd_get_filename (&cbfd));
>> @@ -2538,13 +2382,73 @@ cmd_record_full_restore (const char *args, int
>> from_tty)
>>     record_full_open (nullptr, from_tty);
>>   }
> Function comment. And similar comment for separate patch.
>
> I believe sth. like a follow-up commit that introduces the functions
> record_full_read_entry_from_bfd and record_full_write_entry_to_bfd
> would make this much easier to review.
yeah same as above, will make this patch 2 of the series.
>
>> +static void
>> +record_full_write_entry_to_bfd (record_full_entry &entry,
>> +				gdb_bfd_ref_ptr obfd,
>> +				asection *osec, int *bfd_offset,
>> +				gdbarch *gdbarch)
>> +{
>> +  /* Save entry.  */
>> +  uint8_t type;
>> +  uint32_t regnum, len;
>> +  uint64_t addr;
>> +
>> +  type = entry.type;
>> +  bfdcore_write (obfd.get (), osec, &type, sizeof (type), bfd_offset);
>> +
>> +  switch (entry.type)
>> +    {
>> +    case record_full_reg: /* reg */
>> +      if (record_debug)
>> +	gdb_printf (gdb_stdlog,
>> +		    "  Writing register %d (1 "
>> +		    "plus %lu plus %d bytes)\n",
>> +		    entry.u.reg.num,
>> +		    (unsigned long) sizeof (regnum),
>> +		    entry.u.reg.len);
>> +
>> +      /* Write regnum.  */
>> +      regnum = netorder32 (entry.u.reg.num);
>> +      bfdcore_write (obfd.get (), osec, &regnum, sizeof (regnum), bfd_offset);
>> +
>> +      /* Write regval.  */
>> +      bfdcore_write (obfd.get (), osec, record_full_get_loc (&entry),
>> +		     entry.u.reg.len, bfd_offset);
>> +      break;
>> +
>> +    case record_full_mem: /* mem */
>> +      if (record_debug)
>> +	gdb_printf (gdb_stdlog,
>> +		    "  Writing memory %s (1 plus "
>> +		    "%lu plus %lu plus %d bytes)\n",
>> +		    paddress (gdbarch,
>> +			      entry.u.mem.addr),
>> +		    (unsigned long) sizeof (addr),
>> +		    (unsigned long) sizeof (len),
>> +		    entry.u.mem.len);
>> +
>> +      /* Write memlen.  */
>> +      len = netorder32 (entry.u.mem.len);
>> +      bfdcore_write (obfd.get (), osec, &len, sizeof (len), bfd_offset);
>> +
>> +      /* Write memaddr.  */
>> +      addr = netorder64 (entry.u.mem.addr);
>> +      bfdcore_write (obfd.get (), osec, &addr, sizeof (addr), bfd_offset);
>> +
>> +      /* Write memval.  */
>> +      bfdcore_write (obfd.get (), osec, record_full_get_loc (&entry),
>> +		     entry.u.mem.len, bfd_offset);
>> +      break;
>> +    }
>> +
>> +}
>> +
>>   /* Save the execution log to a file.  We use a modified elf corefile
>>      format, with an extra section for our data.  */
>>
>>   void
>>   record_full_base_target::save_record (const char *recfilename)
>>   {
>> -  struct record_full_entry *cur_record_full_list;
>>     uint32_t magic;
>>     struct gdbarch *gdbarch;
>>     int save_size = 0;
>> @@ -2562,9 +2466,6 @@ record_full_base_target::save_record (const char
>> *recfilename)
>>     /* Arrange to remove the output file on failure.  */
>>     gdb::unlinker unlink_file (recfilename);
>>
>> -  /* Save the current record entry to "cur_record_full_list".  */
>> -  cur_record_full_list = record_full_list;
>> -
>>     /* Get the values of regcache and gdbarch.  */
>>     regcache *regcache = get_thread_regcache (inferior_thread ());
>>     gdbarch = regcache->arch ();
>> @@ -2574,34 +2475,27 @@ record_full_base_target::save_record (const
>> char *recfilename)
>>       = record_full_gdb_operation_disable_set ();
>>
>>     /* Reverse execute to the begin of record list.  */
>> -  while (1)
>> -    {
>> -      /* Check for beginning and end of log.  */
>> -      if (record_full_list == &record_full_first)
>> -	break;
>> -
>> -      record_full_exec_insn (regcache, gdbarch, record_full_list);
>> -
>> -      if (record_full_list->prev)
>> -	record_full_list = record_full_list->prev;
>> -    }
>> +  for (int i = record_full_next_insn - 1; i >= 0; i--)
>> +    record_full_exec_insn (regcache, gdbarch,
>> +			   record_full_list[i]);
>>
>>     /* Compute the size needed for the extra bfd section.  */
>>     save_size = 4;	/* magic cookie */
>> -  for (record_full_list = record_full_first.next; record_full_list;
>> -       record_full_list = record_full_list->next)
>> -    switch (record_full_list->type)
>> -      {
>> -      case record_full_end:
>> -	save_size += 1 + 4 + 4;
>> -	break;
>> -      case record_full_reg:
>> -	save_size += 1 + 4 + record_full_list->u.reg.len;
>> -	break;
>> -      case record_full_mem:
>> -	save_size += 1 + 4 + 8 + record_full_list->u.mem.len;
>> -	break;
>> -      }
>> +  for (int i = record_full_list.size () - 1; i >= 0; i--)
>> +    {
>> +      /* Number of effects of an instruction.  */
>> +      save_size += sizeof (uint32_t) + sizeof (uint8_t) + sizeof (uint32_t);
>> +      for (auto &entry : record_full_list[i].effects)
>> +	switch (entry.type)
>> +	  {
>> +	  case record_full_reg:
>> +	    save_size += 1 + 4 + entry.u.reg.len;
>> +	    break;
>> +	  case record_full_mem:
>> +	    save_size += 1 + 4 + 8 + entry.u.mem.len;
>> +	    break;
>> +	  }
>> +    }
>>
>>     /* Make the new bfd section.  */
>>     osec = bfd_make_section_anyway_with_flags (obfd.get (), "precord",
>> @@ -2630,108 +2524,32 @@ record_full_base_target::save_record (const
>> char *recfilename)
>>
>>     /* Save the entries to recfd and forward execute to the end of
>>        record list.  */
>> -  record_full_list = &record_full_first;
>> -  while (1)
>> +  for (int i = 0; i < record_full_list.size (); i++)
>>       {
>> -      /* Save entry.  */
>> -      if (record_full_list != &record_full_first)
>> +      uint32_t eff_count = (uint32_t) record_full_list[i].effects.size ();
>> +      uint32_t insn_num = record_full_list[i].insn_num;
>> +      uint8_t sigval = (record_full_list[i].sigval.has_value ())
>> +			 ? record_full_list[i].sigval.value ()
>> +			 : GDB_SIGNAL_0;
>> +
>> +      /* Signal.  */
>> +      bfdcore_write (obfd.get (), osec, &sigval, sizeof (sigval), &bfd_offset);
>> +      eff_count = netorder32 (eff_count);
>> +      /* Number of effects.  */
>> +      bfdcore_write (obfd.get (), osec, &eff_count, sizeof (eff_count),
>> +		     &bfd_offset);
>> +      /* Instruction number.  */
>> +      bfdcore_write (obfd.get (), osec, &insn_num, sizeof (uint32_t),
>> +		     &bfd_offset);
>> +
>> +      for (auto &entry : record_full_list[i].effects)
>>   	{
>> -	  uint8_t type;
>> -	  uint32_t regnum, len, signal, count;
>> -	  uint64_t addr;
>> -
>> -	  type = record_full_list->type;
>> -	  bfdcore_write (obfd.get (), osec, &type, sizeof (type), &bfd_offset);
>> -
>> -	  switch (record_full_list->type)
>> -	    {
>> -	    case record_full_reg: /* reg */
>> -	      if (record_debug)
>> -		gdb_printf (gdb_stdlog,
>> -			    "  Writing register %d (1 "
>> -			    "plus %lu plus %d bytes)\n",
>> -			    record_full_list->u.reg.num,
>> -			    (unsigned long) sizeof (regnum),
>> -			    record_full_list->u.reg.len);
>> -
>> -	      /* Write regnum.  */
>> -	      regnum = netorder32 (record_full_list->u.reg.num);
>> -	      bfdcore_write (obfd.get (), osec, &regnum,
>> -			     sizeof (regnum), &bfd_offset);
>> -
>> -	      /* Write regval.  */
>> -	      bfdcore_write (obfd.get (), osec,
>> -			     record_full_get_loc (record_full_list),
>> -			     record_full_list->u.reg.len, &bfd_offset);
>> -	      break;
>> -
>> -	    case record_full_mem: /* mem */
>> -	      if (record_debug)
>> -		gdb_printf (gdb_stdlog,
>> -			    "  Writing memory %s (1 plus "
>> -			    "%lu plus %lu plus %d bytes)\n",
>> -			    paddress (gdbarch,
>> -				      record_full_list->u.mem.addr),
>> -			    (unsigned long) sizeof (addr),
>> -			    (unsigned long) sizeof (len),
>> -			    record_full_list->u.mem.len);
>> -
>> -	      /* Write memlen.  */
>> -	      len = netorder32 (record_full_list->u.mem.len);
>> -	      bfdcore_write (obfd.get (), osec, &len, sizeof (len),
>> -			     &bfd_offset);
>> -
>> -	      /* Write memaddr.  */
>> -	      addr = netorder64 (record_full_list->u.mem.addr);
>> -	      bfdcore_write (obfd.get (), osec, &addr,
>> -			     sizeof (addr), &bfd_offset);
>> -
>> -	      /* Write memval.  */
>> -	      bfdcore_write (obfd.get (), osec,
>> -			     record_full_get_loc (record_full_list),
>> -			     record_full_list->u.mem.len, &bfd_offset);
>> -	      break;
>> -
>> -	      case record_full_end:
>> -		if (record_debug)
>> -		  gdb_printf (gdb_stdlog,
>> -			      "  Writing record_full_end (1 + "
>> -			      "%lu + %lu bytes)\n",
>> -			      (unsigned long) sizeof (signal),
>> -			      (unsigned long) sizeof (count));
>> -		/* Write signal value.  */
>> -		signal = netorder32 (record_full_list->u.end.sigval);
>> -		bfdcore_write (obfd.get (), osec, &signal,
>> -			       sizeof (signal), &bfd_offset);
>> -
>> -		/* Write insn count.  */
>> -		count = netorder32 (record_full_list->u.end.insn_num);
>> -		bfdcore_write (obfd.get (), osec, &count,
>> -			       sizeof (count), &bfd_offset);
>> -		break;
>> -	    }
>> +	  record_full_write_entry_to_bfd (entry, obfd, osec, &bfd_offset,
>> +					  gdbarch);
>>   	}
>> -
>>         /* Execute entry.  */
>> -      record_full_exec_insn (regcache, gdbarch, record_full_list);
>> -
>> -      if (record_full_list->next)
>> -	record_full_list = record_full_list->next;
>> -      else
>> -	break;
>> -    }
>> -
>> -  /* Reverse execute to cur_record_full_list.  */
>> -  while (1)
>> -    {
>> -      /* Check for beginning and end of log.  */
>> -      if (record_full_list == cur_record_full_list)
>> -	break;
>> -
>> -      record_full_exec_insn (regcache, gdbarch, record_full_list);
>> -
>> -      if (record_full_list->prev)
>> -	record_full_list = record_full_list->prev;
>> +      if (i < record_full_next_insn)
>> +	record_full_exec_insn (regcache, gdbarch, record_full_list[i]);
>>       }
>>
>>     unlink_file.keep ();
>> @@ -2742,11 +2560,11 @@ record_full_base_target::save_record (const
>> char *recfilename)
>>   }
>>
>>   /* record_full_goto_insn -- rewind the record log (forward or backward,
>> -   depending on DIR) to the given entry, changing the program state
>> -   correspondingly.  */
>> +   depending on DIR) to the entry in position TARGET_INSN in the history,
>> +   changing the program state correspondingly.  */
>>
>>   static void
>> -record_full_goto_insn (struct record_full_entry *entry,
>> +record_full_goto_insn (size_t target_insn,
>>   		       enum exec_direction_kind dir)
>>   {
>>     scoped_restore restore_operation_disable
>> @@ -2757,17 +2575,12 @@ record_full_goto_insn (struct record_full_entry
>> *entry,
>>     /* Assume everything is valid: we will hit the entry,
>>        and we will not hit the end of the recording.  */
>>
>> -  if (dir == EXEC_FORWARD)
>> -    record_full_list = record_full_list->next;
>> -
>> -  do
>> -    {
>> -      record_full_exec_insn (regcache, gdbarch, record_full_list);
>> -      if (dir == EXEC_REVERSE)
>> -	record_full_list = record_full_list->prev;
>> -      else
>> -	record_full_list = record_full_list->next;
>> -    } while (record_full_list != entry);
>> +  if (dir == EXEC_REVERSE)
>> +    for (int i = record_full_next_insn; i > target_insn; i--)
>> +      record_full_exec_insn (regcache, gdbarch, record_full_list[i - 1]);
>> +  else
>> +    for (int i = record_full_next_insn; i < target_insn; i++)
>> +      record_full_exec_insn (regcache, gdbarch, record_full_list[i]);
>>   }
>>
>>   /* Alias for "target record-full".  */
>> @@ -2798,61 +2611,36 @@ set_record_full_insn_max_num (const char
>> *args, int from_tty,
>>   static void
>>   maintenance_print_record_instruction (const char *args, int from_tty)
>>   {
>> -  struct record_full_entry *to_print = record_full_list;
>> +  if (record_full_list.empty ())
>> +    error (_("Not enough recorded history"));
>>
>> +  int offset = record_full_next_insn - 1;
>> +  /* Reduce the offset by 1 if the record_full_next_insn is after the end
>> +     so that we show the last recorded instruction instead of crashing.  */
>> +  if (offset == record_full_list.size ())
>> +    offset--;
>>     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"));
>> -	}
>> +      offset += value_as_long (parse_and_eval (args));
>> +      if (offset >= record_full_list.size () || offset < 0)
>> +	error (_("Not enough recorded history"));
>>       }
>> -  gdb_assert (to_print != nullptr);
>> +  auto to_print = record_full_list.begin () + offset;
>>
>>     gdbarch *arch = current_inferior ()->arch ();
>>
>> -  /* 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)
>> +  for (auto entry : to_print->effects)
>>       {
>> -      switch (to_print->type)
>> +      switch (entry.type)
>>   	{
>>   	  case record_full_reg:
>>   	    {
>> -	      type *regtype = gdbarch_register_type (arch, to_print-
>>> u.reg.num);
>> +	      type *regtype = gdbarch_register_type (arch, entry.u.reg.num);
>>   	      value *val
>>   		  = value_from_contents (regtype,
>> -					 record_full_get_loc (to_print));
>> +					 record_full_get_loc (&entry));
>>   	      gdb_printf ("Register %s changed: ",
>> -			  gdbarch_register_name (arch, to_print-
>>> u.reg.num));
>> +			  gdbarch_register_name (arch, entry.u.reg.num));
>>   	      struct value_print_options opts;
>>   	      get_user_print_options (&opts);
>>   	      opts.raw = true;
>> @@ -2862,17 +2650,16 @@ maintenance_print_record_instruction (const
>> char *args, int from_tty)
>>   	    }
>>   	  case record_full_mem:
>>   	    {
>> -	      gdb_byte *b = record_full_get_loc (to_print);
>> +	      gdb_byte *b = record_full_get_loc (&entry);
>>   	      gdb_printf ("%d bytes of memory at address %s changed from:",
>> -			  to_print->u.mem.len,
>> -			  print_core_address (arch, to_print->u.mem.addr));
>> -	      for (int i = 0; i < to_print->u.mem.len; i++)
>> +			  entry.u.mem.len,
>> +			  print_core_address (arch, entry.u.mem.addr));
>> +	      for (int i = 0; i < entry.u.mem.len; i++)
>>   		gdb_printf (" %02x", b[i]);
>>   	      gdb_printf ("\n");
>>   	      break;
>>   	    }
>>   	}
>> -      to_print = to_print->next;
>>       }
>>   }
>>
>> @@ -2880,11 +2667,6 @@ INIT_GDB_FILE (record_full)
>>   {
>>     struct cmd_list_element *c;
>>
>> -  /* Init record_full_first.  */
>> -  record_full_first.prev = NULL;
>> -  record_full_first.next = NULL;
>> -  record_full_first.type = record_full_end;
>> -
>>     add_target (record_full_target_info, record_full_open);
>>     add_deprecated_target_alias (record_full_target_info, "record");
>>     add_target (record_full_core_target_info, record_full_open);
>> diff --git a/gdb/record-full.h b/gdb/record-full.h
>> index 51effe74560..c327d879a8a 100644
>> --- a/gdb/record-full.h
>> +++ b/gdb/record-full.h
>> @@ -41,7 +41,6 @@ enum record_result
>>
>>   extern int record_full_arch_list_add_reg (struct regcache *regcache, int
>> num);
>>   extern int record_full_arch_list_add_mem (CORE_ADDR addr, int len);
>> -extern int record_full_arch_list_add_end (void);
>>
>>   /* Returns true if the process record target is open.  */
>>   extern int record_full_is_used (void);
>> diff --git a/gdb/riscv-tdep.c b/gdb/riscv-tdep.c
>> index 6b5c9c02d46..f5b1f66844c 100644
>> --- a/gdb/riscv-tdep.c
>> +++ b/gdb/riscv-tdep.c
>> @@ -5466,8 +5466,5 @@ riscv_process_record (struct gdbarch *gdbarch,
>> struct regcache *regcache,
>>     if (res != RECORD_SUCCESS)
>>       return res;
>>
>> -  if (record_full_arch_list_add_end ())
>> -    return -1;
>> -
>>     return 0;
>>   }
>> diff --git a/gdb/rs6000-tdep.c b/gdb/rs6000-tdep.c
>> index e135df94a40..f964889a49f 100644
>> --- a/gdb/rs6000-tdep.c
>> +++ b/gdb/rs6000-tdep.c
>> @@ -7138,8 +7138,6 @@ ppc_process_prefix_instruction (int insn_prefix,
>> int insn_suffix,
>>     if (record_full_arch_list_add_reg (regcache, PPC_PC_REGNUM))
>>       return -1;
>>
>> -  if (record_full_arch_list_add_end ())
>> -    return -1;
>>     return 0;
>>   }
>>
>> @@ -7447,8 +7445,6 @@ ppc_process_record (struct gdbarch *gdbarch,
>> struct regcache *regcache,
>>
>>     if (record_full_arch_list_add_reg (regcache, PPC_PC_REGNUM))
>>       return -1;
>> -  if (record_full_arch_list_add_end ())
>> -    return -1;
>>     return 0;
>>   }
>>
>> diff --git a/gdb/s390-linux-tdep.c b/gdb/s390-linux-tdep.c
>> index 3cd6b359d13..6bea30f08dc 100644
>> --- a/gdb/s390-linux-tdep.c
>> +++ b/gdb/s390-linux-tdep.c
>> @@ -910,9 +910,6 @@ s390_linux_record_signal (struct gdbarch *gdbarch,
>> struct regcache *regcache,
>>     if (record_full_arch_list_add_mem (sp, sizeof_rt_sigframe))
>>       return -1;
>>
>> -  if (record_full_arch_list_add_end ())
>> -    return -1;
>> -
>>     return 0;
>>   }
>>
>> diff --git a/gdb/s390-tdep.c b/gdb/s390-tdep.c
>> index f9d7bdd04e4..36ce659ea15 100644
>> --- a/gdb/s390-tdep.c
>> +++ b/gdb/s390-tdep.c
>> @@ -7019,8 +7019,6 @@ s390_process_record (struct gdbarch *gdbarch,
>> struct regcache *regcache,
>>
>>     if (record_full_arch_list_add_reg (regcache, S390_PSWA_REGNUM))
>>       return -1;
>> -  if (record_full_arch_list_add_end ())
>> -    return -1;
>>     return 0;
>>   }
>>
>> --
>> 2.54.0
>>
> Christina
>
> Intel Deutschland GmbH
> Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
> Tel: +49 89 991 430, www.intel.de
> Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
> Chairperson of the Supervisory Board: Nicole Lau
> Registered Seat: Munich
> Commercial Register: Amtsgericht Muenchen HRB 186928
>

-- 
Cheers,
Guinevere Larsen
It/she


^ permalink raw reply	[flat|nested] 27+ messages in thread

* RE: [PATCH v3 6/6] gdb/record: rename record_full_list to record_full_log
  2026-05-15 16:37 ` [PATCH v3 6/6] gdb/record: rename record_full_list to record_full_log Guinevere Larsen
@ 2026-05-29 12:05   ` Schimpe, Christina
  0 siblings, 0 replies; 27+ messages in thread
From: Schimpe, Christina @ 2026-05-29 12:05 UTC (permalink / raw)
  To: Guinevere Larsen, gdb-patches



> -----Original Message-----
> From: Guinevere Larsen <guinevere@redhat.com>
> Sent: Freitag, 15. Mai 2026 18:37
> To: gdb-patches@sourceware.org
> Cc: Guinevere Larsen <guinevere@redhat.com>
> Subject: [PATCH v3 6/6] gdb/record: rename record_full_list to
> record_full_log
> 
> Now that we no longer use a linked list to manage the history, the name
> "record_full_list" is no longer meaningful. This commit changes the name to
> record_full_log since it is the execution log. I decided to not say
> "record_full_execution_log" to avoid overly long lines when accessing it.
> ---
>  gdb/record-full.c | 120 +++++++++++++++++++++++-----------------------
>  1 file changed, 60 insertions(+), 60 deletions(-)
> 
> diff --git a/gdb/record-full.c b/gdb/record-full.c index
> 055d2c66346..3c63753d24e 100644
> --- a/gdb/record-full.c
> +++ b/gdb/record-full.c
> @@ -76,7 +76,7 @@
>  #define DEFAULT_RECORD_FULL_INSN_MAX_NUM	200000
> 
>  #define RECORD_FULL_IS_REPLAY \
> -  ((record_full_next_insn != record_full_list.size ()) \
> +  ((record_full_next_insn != record_full_log.size ()) \
>      || ::execution_direction == EXEC_REVERSE)
> 
>  #define RECORD_FULL_FILE_MAGIC_OLD	netorder32(0x20091016)
> @@ -446,7 +446,7 @@ static struct record_full_core_buf_entry
> *record_full_core_buf_list = NULL;
>  /* The following variables are used for managing the history of executed
>     instructions from the inferior.
> 
> -   record_full_list contains all instructions that were fully executed and
> +   record_full_log contains all instructions that were fully executed
> + and
>     saved to the log, so that we can replay the execution.
> 
>     record_full_next_insn always points to the next instruction that would @@
> -459,7 +459,7 @@ static struct record_full_core_buf_entry
> *record_full_core_buf_list = NULL;
>     happening.  It is manipulated by the "arch list" functions for historical
>     reasons.  */
> 
> -static std::deque<record_full_instruction> record_full_list;
> +static std::deque<record_full_instruction> record_full_log;
>  static record_full_instruction record_full_incomplete_instruction;
>  static int record_full_next_insn;
> 
> @@ -665,14 +665,14 @@ record_full_reset_history ()
>    record_full_insn_count = 0;
>    record_full_next_insn = 0;
> 
> -  record_full_list.clear ();
> +  record_full_log.clear ();
>  }
> 
>  static void
> -record_full_list_release_following (int index)
> +record_full_log_release_following (int index)
>  {
> -  for (int i = record_full_list.size () - 1; i > index; i--)
> -    record_full_list.pop_back ();
> +  for (int i = record_full_log.size () - 1; i > index; i--)
> +    record_full_log.pop_back ();
>    /* Set the next instruction to be past the end of the log so we
>       start recording if the user moves forward again.  */
>    record_full_next_insn = index;
> @@ -686,7 +686,7 @@ record_full_save_instruction ()
>    ++record_full_insn_count;
>    record_full_incomplete_instruction.insn_num = record_full_insn_count;
>    record_full_incomplete_instruction.effects.shrink_to_fit ();
> -  record_full_list.push_back (std::move
> (record_full_incomplete_instruction));
> +  record_full_log.push_back (std::move
> + (record_full_incomplete_instruction));
>    record_full_next_insn++;
> 
>    record_full_reset_incomplete ();
> @@ -696,12 +696,12 @@ record_full_save_instruction ()
>     room for adding a new instruction at the end of the log.  */
> 
>  static void
> -record_full_list_release_first (void)
> +record_full_log_release_first (void)
>  {
> -  if (record_full_list.empty ())
> +  if (record_full_log.empty ())
>      return;
> 
> -  record_full_list.pop_front ();
> +  record_full_log.pop_front ();
>    --record_full_next_insn;
>  }
> 
> @@ -765,7 +765,7 @@ record_full_arch_list_add_mem (CORE_ADDR addr,
> int len)  static void  record_full_check_insn_num (void)  {
> -  if (record_full_list.size () == record_full_insn_max_num)
> +  if (record_full_log.size () == record_full_insn_max_num)
>      {
>        /* Ask user what to do.  */
>        if (record_full_stop_at_limit)
> @@ -781,9 +781,9 @@ record_full_check_insn_num (void)
> 
>  /* Before inferior step (when GDB record the running message, inferior
>     only can step), GDB will call this function to record the values to
> -   record_full_list.  This function will call gdbarch_process_record to
> +   record_full_log.  This function will call gdbarch_process_record to
>     record the running message of inferior and set them to
> -   record_full_arch_list, and add it to record_full_list.  */
> +   record_full_arch_list, and add it to record_full_log.  */
> 
>  static void
>  record_full_message (struct regcache *regcache, enum gdb_signal signal)
> @@ -821,8 +821,8 @@ record_full_message (struct regcache *regcache,
> enum gdb_signal signal)
>  	 if we delivered it during the recording.  Therefore we should
>  	 record the signal during record_full_wait, not
>  	 record_full_resume.  */
> -      if (signal != GDB_SIGNAL_0 && !record_full_list.empty ())
> -	record_full_list[record_full_next_insn - 1].sigval = signal;
> +      if (signal != GDB_SIGNAL_0 && !record_full_log.empty ())
> +	record_full_log[record_full_next_insn - 1].sigval = signal;
> 
>        if (signal == GDB_SIGNAL_0
>  	  || !gdbarch_process_record_signal_p (gdbarch)) @@ -847,8 +847,8
> @@ record_full_message (struct regcache *regcache, enum gdb_signal
> signal)
> 
>    record_full_save_instruction ();
> 
> -  if (record_full_list.size () == record_full_insn_max_num)
> -    record_full_list_release_first ();
> +  if (record_full_log.size () == record_full_insn_max_num)
> +    record_full_log_release_first ();
>  }
> 
>  static bool
> @@ -1325,7 +1325,7 @@ record_full_wait_1 (struct target_ops *ops,
>  	  if (execution_direction == EXEC_REVERSE)
>  	    record_full_next_insn--;
> 
> -	  /* Loop over the record_full_list, looking for the next place to
> +	  /* Loop over the record_full_log, looking for the next place to
>  	     stop.  */
>  	  do
>  	    {
> @@ -1339,14 +1339,14 @@ record_full_wait_1 (struct target_ops *ops,
>  		  break;
>  		}
>  	      if (execution_direction != EXEC_REVERSE
> -		  && record_full_next_insn == record_full_list.size ())
> +		  && record_full_next_insn == record_full_log.size ())
>  		{
>  		  /* Hit end of record log going forward.  */
>  		  status->set_no_history ();
>  		  break;
>  		}
> 
> -	      record_full_list[record_full_next_insn].exec_insn (regcache);
> +	      record_full_log[record_full_next_insn].exec_insn (regcache);
> 
>  	      /* step */
>  	      if (record_full_resume_step)
> @@ -1380,7 +1380,7 @@ record_full_wait_1 (struct target_ops *ops,
>  				"watchpoint.\n");
>  		  continue_flag = 0;
>  		}
> -	      if (record_full_list[record_full_next_insn].sigval.has_value ())
> +	      if (record_full_log[record_full_next_insn].sigval.has_value ())
>  		continue_flag = 0;
> 
>  	      if (execution_direction == EXEC_REVERSE) @@ -1395,10
> +1395,10 @@ record_full_wait_1 (struct target_ops *ops,
>  	      gdb_assert (execution_direction == EXEC_REVERSE);
>  	      record_full_next_insn = 0;
>  	    }
> -	  else if (record_full_next_insn > record_full_list.size ())
> +	  else if (record_full_next_insn > record_full_log.size ())
>  	    {
>  	      gdb_assert (execution_direction == EXEC_FORWARD);
> -	      record_full_next_insn = record_full_list.size ();
> +	      record_full_next_insn = record_full_log.size ();
>  	    }
>  	  /* Reset the current instruction to point to the one to be replayed
>  	     moving forward.  */
> @@ -1412,9 +1412,9 @@ record_full_wait_1 (struct target_ops *ops,
>  			 ? record_full_next_insn - 1 : record_full_next_insn;
>  	      if (record_full_get_sig)
>  		status->set_stopped (GDB_SIGNAL_INT);
> -	      else if (record_full_list[insn].sigval.has_value ())
> +	      else if (record_full_log[insn].sigval.has_value ())
>  		status->set_stopped
> -		  (record_full_list[insn].sigval.value ());
> +		  (record_full_log[insn].sigval.value ());
>  	      else
>  		status->set_stopped (GDB_SIGNAL_TRAP);
>  	    }
> @@ -1537,8 +1537,8 @@ record_full_registers_change (struct regcache
> *regcache, int regnum)
>      }
>    record_full_save_instruction ();
> 
> -  if (record_full_list.size () == record_full_insn_max_num)
> -    record_full_list_release_first ();
> +  if (record_full_log.size () == record_full_insn_max_num)
> +    record_full_log_release_first ();
>  }
> 
>  /* "store_registers" method for process record target.  */ @@ -1587,7
> +1587,7 @@ record_full_target::store_registers (struct regcache *regcache,
> int regno)
>  	    }
> 
>  	  /* Destroy the record from here forward.  */
> -	  record_full_list_release_following (record_full_next_insn);
> +	  record_full_log_release_following (record_full_next_insn);
>  	}
> 
>        record_full_registers_change (regcache, regno); @@ -1620,7 +1620,7
> @@ record_full_target::xfer_partial (enum target_object object,
>  	    error (_("Process record canceled the operation."));
> 
>  	  /* Destroy the record from here forward.  */
> -	  record_full_list_release_following (record_full_next_insn);
> +	  record_full_log_release_following (record_full_next_insn);
>  	}
> 
>        /* Check record_full_insn_num */
> @@ -1639,8 +1639,8 @@ record_full_target::xfer_partial (enum
> target_object object,
>  	}
>        record_full_save_instruction ();
> 
> -      if (record_full_list.size () == record_full_insn_max_num)
> -	record_full_list_release_first ();
> +      if (record_full_log.size () == record_full_insn_max_num)
> +	record_full_log_release_first ();
>      }
> 
>    return this->beneath ()->xfer_partial (object, annex, readbuf, writebuf, @@ -
> 1796,11 +1796,11 @@ record_full_base_target::get_bookmark (const char
> *args, int from_tty)  {
>    char *ret = NULL;
> 
> -  if (record_full_list.empty ())
> +  if (record_full_log.empty ())
>      return (gdb_byte *) ret;
> 
>    /* Return stringified form of instruction count.  */
> -  ret = xstrdup (pulongest (record_full_list[record_full_next_insn].insn_num));
> +  ret = xstrdup (pulongest
> + (record_full_log[record_full_next_insn].insn_num));
> 
>    if (record_debug)
>      {
> @@ -1862,16 +1862,16 @@ record_full_base_target::info_record ()
>      gdb_printf (_("Record mode:\n"));
> 
>    /* Do we have a log at all?  */
> -  if (!record_full_list.empty ())
> +  if (!record_full_log.empty ())
>      {
>        /* Display instruction number for first instruction in the log.  */
>        gdb_printf (_("Lowest recorded instruction number is %u.\n"),
> -		  record_full_list[0].insn_num);
> +		  record_full_log[0].insn_num);
> 
>        /* If in replay mode, display where we are in the log.  */
>        if (RECORD_FULL_IS_REPLAY)
>  	gdb_printf (_("Current instruction number is %u.\n"),
> -		    record_full_list[record_full_next_insn].insn_num);
> +		    record_full_log[record_full_next_insn].insn_num);
> 
>        /* Display instruction number for last instruction in the log.  */
>        gdb_printf (_("Highest recorded instruction number is %s.\n"), @@ -
> 1879,7 +1879,7 @@ record_full_base_target::info_record ()
> 
>        /* Display log count.  */
>        gdb_printf (_("Log contains %lu instructions.\n"),
> -		  (unsigned long int) record_full_list.size ());
> +		  (unsigned long int) record_full_log.size ());
>      }
>    else
>      gdb_printf (_("No instructions have been logged.\n")); @@ -1928,14
> +1928,14 @@ record_full_base_target::record_will_replay (ptid_t ptid, int
> dir)  static void  record_full_goto_entry (size_t target_insn)  {
> -  if (target_insn >= record_full_list.size ())
> +  if (target_insn >= record_full_log.size ())
>      error (_("Target insn not found."));
>    else if (target_insn == record_full_next_insn)
>      error (_("Already at target insn."));
>    else if (target_insn > record_full_next_insn)
>      {
>        gdb_printf (_("Go forward to insn number %s\n"),
> -		  pulongest (record_full_list[target_insn].insn_num));
> +		  pulongest (record_full_log[target_insn].insn_num));
>        record_full_goto_insn (target_insn, EXEC_FORWARD);
>      }
>    else
> @@ -1966,7 +1966,7 @@ record_full_base_target::goto_record_begin ()
> void  record_full_base_target::goto_record_end ()  {
> -  record_full_goto_entry (record_full_list.size () - 1);
> +  record_full_goto_entry (record_full_log.size () - 1);
>  }
> 
>  /* The "goto_record" target method.  */ @@ -2403,7 +2403,7 @@
> record_full_restore (struct bfd &cbfd)
>    int bfd_offset = 0;
> 
>    /* "record_full_restore" can only be called when record list is empty.  */
> -  gdb_assert (record_full_list.empty ());
> +  gdb_assert (record_full_log.empty ());
> 
>    if (record_debug)
>      gdb_printf (gdb_stdlog, "Restoring recording from core file.\n"); @@ -
> 2454,9 +2454,9 @@ record_full_restore (struct bfd &cbfd)
>      }
> 
>    /* Update record_full_insn_max_num.  */
> -  if (record_full_list.size () > record_full_insn_max_num)
> +  if (record_full_log.size () > record_full_insn_max_num)
>      {
> -      record_full_insn_max_num = record_full_list.size ();
> +      record_full_insn_max_num = record_full_log.size ();
>        warning (_("Auto increase record/replay buffer limit to %u."),
>  	       record_full_insn_max_num);
>      }
> @@ -2634,16 +2634,16 @@ record_full_base_target::save_record (const
> char *recfilename)
> 
>    /* Reverse execute to the begin of record list.  */
>    for (int i = record_full_next_insn - 1; i >= 0; i--)
> -    record_full_list[i].exec_insn (regcache);
> +    record_full_log[i].exec_insn (regcache);
> 
>    /* Compute the size needed for the extra bfd section.  */
>    save_size = 4;	/* magic cookie */
> -  for (int i = record_full_list.size () - 1; i >= 0; i--)
> +  for (int i = record_full_log.size () - 1; i >= 0; i--)
>      {
>        /* Number of effects of an instruction.  */
>        save_size += sizeof (uint32_t) + sizeof (uint8_t) + sizeof (uint32_t);
> -      save_size += 4 + record_full_list[i].pc.len;
> -      for (auto &entry : record_full_list[i].effects)
> +      save_size += 4 + record_full_log[i].pc.len;
> +      for (auto &entry : record_full_log[i].effects)
>  	switch (entry.type ())
>  	  {
>  	  case record_full_reg:
> @@ -2682,12 +2682,12 @@ record_full_base_target::save_record (const
> char *recfilename)
> 
>    /* Save the entries to recfd and forward execute to the end of
>       record list.  */
> -  for (int i = 0; i < record_full_list.size (); i++)
> +  for (int i = 0; i < record_full_log.size (); i++)
>      {
> -      record_full_list[i].to_bfd (obfd, osec, &bfd_offset, gdbarch);
> +      record_full_log[i].to_bfd (obfd, osec, &bfd_offset, gdbarch);
>        /* Execute entry.  */
>        if (i < record_full_next_insn)
> -	record_full_list[i].exec_insn (regcache);
> +	record_full_log[i].exec_insn (regcache);
>      }
> 
>    unlink_file.keep ();
> @@ -2714,10 +2714,10 @@ record_full_goto_insn (size_t target_insn,
> 
>    if (dir == EXEC_REVERSE)
>      for (int i = record_full_next_insn; i > target_insn; i--)
> -      record_full_list[i-1].exec_insn (regcache);
> +      record_full_log[i-1].exec_insn (regcache);
>    else
>      for (int i = record_full_next_insn; i < target_insn; i++)
> -      record_full_list[i].exec_insn (regcache);
> +      record_full_log[i].exec_insn (regcache);
>  }
> 
>  /* Alias for "target record-full".  */
> @@ -2732,10 +2732,10 @@ static void
>  set_record_full_insn_max_num (const char *args, int from_tty,
>  			      struct cmd_list_element *c)
>  {
> -  if (record_full_list.size () > record_full_insn_max_num)
> +  if (record_full_log.size () > record_full_insn_max_num)
>      {
> -      while (record_full_list.size () > record_full_insn_max_num)
> -	record_full_list_release_first ();
> +      while (record_full_log.size () > record_full_insn_max_num)
> +	record_full_log_release_first ();
>      }
>  }
> 
> @@ -2744,21 +2744,21 @@ set_record_full_insn_max_num (const char
> *args, int from_tty,  static void  maintenance_print_record_instruction (const
> char *args, int from_tty)  {
> -  if (record_full_list.empty ())
> +  if (record_full_log.empty ())
>      error (_("Not enough recorded history"));
> 
>    int offset = record_full_next_insn - 1;
>    /* Reduce the offset by 1 if the record_full_next_insn is after the end
>       so that we show the last recorded instruction instead of crashing.  */
> -  if (offset == record_full_list.size ())
> +  if (offset == record_full_log.size ())
>      offset--;
>    if (args != nullptr)
>      {
>        offset += value_as_long (parse_and_eval (args));
> -      if (offset >= record_full_list.size () || offset < 0)
> +      if (offset >= record_full_log.size () || offset < 0)
>  	error (_("Not enough recorded history"));
>      }
> -  auto to_print = record_full_list.begin () + offset;
> +  auto to_print = record_full_log.begin () + offset;
> 
>    gdbarch *arch = current_inferior ()->arch ();
>    struct value_print_options opts;
> --
> 2.54.0
> 

LGTM.

Reviewed-By: Christina Schimpe <christina.schimpe@intel.com>

Christina
Intel Deutschland GmbH
Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
Tel: +49 89 991 430, www.intel.de
Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
Chairperson of the Supervisory Board: Nicole Lau
Registered Seat: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928

^ permalink raw reply	[flat|nested] 27+ messages in thread

* RE: [PATCH v3 3/7] gdb/record: c++ify internal structures of record-full.c
  2026-05-27 20:13     ` Guinevere Larsen
@ 2026-05-29 12:09       ` Schimpe, Christina
  2026-05-29 16:38         ` Guinevere Larsen
  0 siblings, 1 reply; 27+ messages in thread
From: Schimpe, Christina @ 2026-05-29 12:09 UTC (permalink / raw)
  To: Guinevere Larsen, gdb-patches; +Cc: Thiago Jung Bauermann

> -----Original Message-----
> From: Guinevere Larsen <guinevere@redhat.com>
> Sent: Mittwoch, 27. Mai 2026 22:13
> To: Schimpe, Christina <christina.schimpe@intel.com>; gdb-
> patches@sourceware.org
> Cc: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Subject: Re: [PATCH v3 3/7] gdb/record: c++ify internal structures of record-
> full.c
> 
> On 5/26/26 6:00 AM, Schimpe, Christina wrote:
> >> -----Original Message-----
> >> From: Guinevere Larsen <guinevere@redhat.com>
> >> Sent: Freitag, 15. Mai 2026 18:37
> >> To: gdb-patches@sourceware.org
> >> Cc: Guinevere Larsen <guinevere@redhat.com>; Thiago Jung Bauermann
> >> <thiago.bauermann@linaro.org>
> >> Subject: [PATCH v3 3/7] gdb/record: c++ify internal structures of
> >> record-full.c
> >>
> >> This commit adds a constructor, destructor, and some methods to the
> >> structures record_full_entry, record_full_reg_entry and
> >> record_full_mem_entry. This is a move to disentangle the internal
> >> representation of the data and how record-full manipulates it for
> replaying.
> >>
> >> Along with this change, record_full_entry is changed to use an
> >> std::variant, since it was basically doing that already, but now we
> >> have the stdlibc++ error checking to make sure we're only accessing
> elements we're allowed to.
> >>
> >> Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> >> ---
> >>   gdb/record-full.c | 601 +++++++++++++++++++++++++---------------------
> >>   1 file changed, 321 insertions(+), 280 deletions(-)
> >>
> >> diff --git a/gdb/record-full.c b/gdb/record-full.c index
> >> e4dd07dc300..24e94eaf5da 100644
> >> --- a/gdb/record-full.c
> >> +++ b/gdb/record-full.c
> >> @@ -51,6 +51,7 @@
> >>   #include <optional>
> >>   #include <deque>
> >>   #include <signal.h>
> >> +#include <variant>
> >>
> >>   /* This module implements "target record-full", also known as "process
> >>      record and replay".  This target sits on top of a "normal"
> >> target @@ -93,12
> >> +94,113 @@ struct record_full_mem_entry
> >>     int len;
> >>     /* Set this flag if target memory for this entry
> >>        can no longer be accessed.  */
> >> -  int mem_entry_not_accessible;
> >> +  bool mem_entry_accessible;
> > Shouldn't we update the comment then, too ?
> Yes, good point!
> > Nit: you could consider moving this into a separate patch. I would make the
> review a bit easier IMO.
> >
> > The same applies for each structure/class that you refactor in this patch.
> > But this is rather an optional suggestion, too. 😊
> Thiago suggested that, since I'm already messing with the class, I may as well
> invert the name and logic of the variable. I guess my thinking was that, since
> he already seen my previous version this wouldn't be much of a change to
> review, but you make a good point, will keep it in mind for the future
> >
> >>     union
> >>     {
> >>       gdb_byte *ptr;
> >>       gdb_byte buf[sizeof (gdb_byte *)];
> >>     } u;
> >> +
> >> +  record_full_mem_entry () : addr (0), len (0) { }
> > Any specific reason why you don't init mem_entry_accessible and u here ?
> Mainly because I wanted to treat "len == 0" as effectively uninitialized, so it
> didn't seem important, but I see that I didn't actually follow through with it
> that much so I suppose I should at least initialize mem_entry_accessible. u
> seems unnecessary to setup, since we'll do 0-sized reads (which will return
> the local buffer and not a random pointer)....
> >
> >> +
> >> +  record_full_mem_entry (CORE_ADDR mem_addr, int mem_len)  {
> >> +    addr = mem_addr;
> >> +    len = mem_len;
> >> +    if (len > sizeof (u.buf))
> >> +      u.ptr = new gdb_byte[len];
> >> +    mem_entry_accessible = true;
> >> +  }
> >> +
> >> +  record_full_mem_entry (record_full_mem_entry &&other)  {
> >> +    addr = other.addr;
> >> +    len = other.len;
> >> +    memcpy (u.buf, other.u.buf, sizeof (u.buf));
> >> +    mem_entry_accessible = other.mem_entry_accessible;
> >> +    /* With len == 0, OTHER is guaranteed to not try to free the
> >> +       memory when it is destructed.  */
> >> +    other.len = 0;
> >> +  }
> >> +
> >> +  ~record_full_mem_entry ()
> >> +  {
> >> +    if (len > sizeof (u.buf))
> >> +      delete[] u.ptr;
> >> +  }
> >> +
> >> +  record_full_mem_entry &operator= (record_full_mem_entry &&other)
> {
> >> +    addr = other.addr;
> >> +    len = other.len;
> >> +    memcpy (u.buf, other.u.buf, sizeof (u.buf));
> > Shouldn't we check for self-assignment here ?
> 
> I don't think it would be possible to self-assign here, since this is a
> move operator. We shouldn't be able to create a copy anywhere, so I
> think it shouldn't be a large thing.
> I can add an assert, since if it ever happens this is a large bug.

Yeah, I just noticed it since I've been looking at a sample implementation
for the move assignment operator which does check for self-assignment:

https://learn.microsoft.com/en-us/cpp/cpp/move-constructors-and-move-assignment-operators-cpp?view=msvc-170#example-complete-move-constructor-and-assignment-operator

and then compared a bit with other classes which have DISABLE_COPY_AND_ASSIGN
and the move assignment operator.

> >
> > Also, like the destructor, I believe we must delete u.ptr here:
> > ~~~
> >    if (len > sizeof (u.buf))
> >        delete[] u.ptr;
> > ~~~
> >
> > Same feedback for record_full_mem_entry &operator=
> 
> The idea of a move operation (constructor or assignment) is that we
> won't need to allocate new memory, we'll just pass the pointer to the
> new holder and that's it. If the pointer is deleted here, since it was
> copied over in the memcpy call, when the next object calls its
> destructor, we'll get a double free.

Arg, this might have been confusing.  My feedback on this is not exactly at the right line.
I'd add this delete before those lines:

> >> +    addr = other.addr;
> >> +    len = other.len;
> >> +    memcpy (u.buf, other.u.buf, sizeof (u.buf));

When the object is finally destructed, I don't expect a double free, since we only free
the data of the moved object. In the move operator we delete the data of the original
object.

In the example link I shared above, also the existing data are deleted.
But it might be that I'm missing something here still.

> >
> >> +    mem_entry_accessible = other.mem_entry_accessible;
> >> +    /* With len == 0, OTHER is guaranteed to not try to free the
> >> +       memory when it is destructed.  */
> >> +    other.len = 0;
> >> +    return *this;
> >> +  }
> >> +
> >> +  DISABLE_COPY_AND_ASSIGN (record_full_mem_entry);
> >> +
> >> +  gdb_byte *get_loc ()
> > I don't think this is too important, but since I saw it:
> > This should be const I think.
> >
> > IIUC we should then also use a const reference in loops which call get_loc (),
> > like this one:
> >
> >    for (auto &entry : to_print->effects)
> >
> > And the same applies for type, reg and mem.
> 
> There's a good amount of places that would need changing for adding
> const to get_loc, I think, including bfdcore_read and bfdcore_write, so
> it feels like its too much for this patch. reg and mem throw errors
> about the get discarding qualifiers.
> 
> I did add const to "type" though, thanks for pointing that out.
> 
> >
> >> +  {
> >> +    if (len > sizeof (u.buf))
> >> +      return u.ptr;
> >> +    else
> >> +      return u.buf;
> >> +  }
> >> +
> >> +  bool execute (gdbarch *gdbarch)
> >> +  {
> >> +    /* Nothing to do if the memory is flagged not_accessible.  */
> >> +    if (!mem_entry_accessible)
> >> +      return false;
> >> +
> >> +    gdb::byte_vector buf (len);
> >> +
> >> +    if (record_debug > 1)
> >> +      gdb_printf (gdb_stdlog,
> >> +		  "Process record: record_full_mem %s to "
> >> +		  "inferior addr = %s len = %d.\n",
> >> +		  host_address_to_string (this),
> >> +		  paddress (gdbarch, addr),
> >> +		  len);
> >> +
> >> +    if (record_read_memory (gdbarch, addr, buf.data (), len))
> >> +      mem_entry_accessible = false;
> >> +    else
> >> +      {
> >> +	if (target_write_memory (addr,
> >> +				 get_loc (),
> >> +				 len))
> >> +	  {
> >> +	    mem_entry_accessible = false;
> >> +	    if (record_debug)
> >> +	      warning (_("Process record: error writing memory at "
> >> +			 "addr = %s len = %d."),
> >> +		       paddress (gdbarch, addr),
> >> +		       len);
> >> +	  }
> >> +	else
> >> +	  {
> >> +	    memcpy (get_loc (), buf.data (), len);
> >> +
> >> +	    /* We've changed memory --- check if a hardware
> >> +	       watchpoint should trap.  Note that this
> >> +	       presently assumes the target beneath supports
> >> +	       continuable watchpoints.  On non-continuable
> >> +	       watchpoints target, we'll want to check this
> >> +	       _before_ actually doing the memory change, and
> >> +	       not doing the change at all if the watchpoint
> >> +	       traps.  */
> >> +	    if (hardware_watchpoint_inserted_in_range
> >> +		(current_inferior ()->aspace.get (), addr, len))
> >> +	      return true;
> >> +	  }
> >> +      }
> >> +    return false;
> >> +  }
> >>   };
> >>
> >>   struct record_full_reg_entry
> >> @@ -110,6 +212,70 @@ struct record_full_reg_entry
> >>       gdb_byte *ptr;
> >>       gdb_byte buf[2 * sizeof (gdb_byte *)];
> >>     } u;
> >> +
> >> +  record_full_reg_entry () : num (0), len (0) { }
> >> +
> >> +  record_full_reg_entry (gdbarch *gdbarch, int regnum)  {
> >> +    num = regnum;
> >> +    len = register_size (gdbarch, regnum);
> >> +    if (len > sizeof (u.buf))
> >> +      u.ptr = new gdb_byte[len];
> >> +  }
> >> +
> >> +  record_full_reg_entry (record_full_reg_entry &&other)  {
> >> +    num = other.num;
> >> +    len = other.len;
> >> +    memcpy (u.buf, other.u.buf, sizeof (u.buf));
> >> +    /* With len == 0, OTHER is guaranteed to not try to free the
> >> +       memory when it is destructed.  */
> >> +    other.len = 0;
> >> +  }
> >> +
> >> +  ~record_full_reg_entry ()
> >> +  {
> >> +    if (len > sizeof (u.buf))
> >> +      delete[] u.ptr;
> >> +  }
> >> +
> >> +  record_full_reg_entry &operator=(record_full_reg_entry &&other)  {
> >> +    num = other.num;
> >> +    len = other.len;
> >> +    memcpy (u.buf, other.u.buf, sizeof (u.buf));
> >> +    /* With len == 0, OTHER is guaranteed to not try to free the
> >> +       memory when it is destructed.  */
> >> +    other.len = 0;
> >> +    return *this;
> >> +  }
> >> +
> >> +  DISABLE_COPY_AND_ASSIGN (record_full_reg_entry);
> >> +
> >> +  gdb_byte *get_loc ()
> >> +  {
> >> +    if (len > sizeof (u.buf))
> >> +      return u.ptr;
> >> +    else
> >> +      return u.buf;
> >> +  }
> >> +
> >> +  bool execute (regcache *regcache)
> >> +  {
> >> +    gdb::byte_vector buf (len);
> >> +
> >> +    if (record_debug > 1)
> >> +      gdb_printf (gdb_stdlog,
> >> +		  "Process record: record_full_reg %s to "
> >> +		  "inferior num = %d.\n",
> >> +		  host_address_to_string (this),
> >> +		  num);
> >> +
> >> +    regcache->cooked_read (num, buf);
> >> +    regcache->cooked_write (num, get_loc ());
> >> +    memcpy (get_loc (), buf.data (), len);
> >> +    return false;
> >> +  }
> >>   };
> >>
> >>   enum record_full_type
> >> @@ -118,16 +284,88 @@ enum record_full_type
> >>     record_full_mem
> >>   };
> >>
> >> -struct record_full_entry
> >> +class record_full_entry
> >>   {
> >> -  enum record_full_type type;
> >> -  union
> >> +  std::variant<record_full_reg_entry, record_full_mem_entry> entry;
> >> +
> >> +public:
> >> +  record_full_entry () : entry (record_full_reg_entry ()) {}
> >> +
> >> +  /* Constructor for a register entry.  Type is here to make it
> >> +     easier to recognize it in the constructor calls, it isn't
> >> +     actually important.  */
> >> +  record_full_entry (record_full_type reg_type, gdbarch *gdbarch,
> >> +		     int regnum)
> >> +  : entry(record_full_reg_entry (gdbarch, regnum))
> >>     {
> >> -    /* reg */
> >> -    struct record_full_reg_entry reg;
> >> -    /* mem */
> >> -    struct record_full_mem_entry mem;
> >> -  } u;
> >> +    gdb_assert (reg_type == record_full_reg);  }
> >> +
> >> +  record_full_entry (record_full_type mem_type, CORE_ADDR addr, int
> >> + len)
> >> +  : entry(record_full_mem_entry (addr, len))  {
> >> +    gdb_assert (mem_type == record_full_mem);  }
> >> +
> >> +  record_full_entry (record_full_entry &&other)
> >> +  : entry (std::move (other.entry))
> >> +  {
> >> +  }
> >> +
> >> +  DISABLE_COPY_AND_ASSIGN (record_full_entry);
> >> +
> >> +  record_full_reg_entry& reg ()
> >> +  {
> >> +    gdb_assert (type () == record_full_reg);
> >> +    return std::get<record_full_reg_entry> (entry);  }
> >> +
> >> +  record_full_mem_entry& mem ()
> >> +  {
> >> +    gdb_assert (type () == record_full_mem);
> >> +    return std::get<record_full_mem_entry> (entry);  }
> >> +
> >> +  record_full_type type ()
> >> +  {
> >> +    switch (entry.index ())
> >> +    {
> >> +    case 0:
> >> +      return record_full_reg;
> >> +    case 1:
> >> +      return record_full_mem;
> >> +    }
> >> +    gdb_assert_not_reached ("Impossible variant index");  }
> >> +
> >> +  /* Get the pointer to the data stored by this entry.  */  gdb_byte
> >> + *get_loc ()  {
> >> +    switch (type ())
> >> +    {
> >> +    case record_full_reg:
> >> +      return reg ().get_loc ();
> >> +    case record_full_mem:
> >> +      return mem ().get_loc ();
> >> +    }
> >> +    gdb_assert_not_reached ("Impossible entry type");  }
> >> +
> >> +  /* Execute this entry, swapping the appropriate values from memory or
> >> +     register and the recorded ones.  Returns TRUE if the execution was
> >> +     stopped by a watchpoint.  */
> >> +
> >> +  bool execute (regcache *regcache)
> >> +  {
> >> +    switch (type ())
> >> +      {
> >> +      case record_full_reg:
> >> +	return reg ().execute (regcache);
> >> +      case record_full_mem:
> >> +	return mem ().execute (regcache->arch ());
> >> +      }
> >> +    return false;
> >> +  }
> >>   };
> >>
> >>   /* This is the main structure that comprises the execution log.
> >> @@ -384,91 +622,12 @@ static struct cmd_list_element
> >> *record_full_cmdlist;  static void record_full_goto_insn (size_t target_insn,
> >>   				   enum exec_direction_kind dir);
> >>
> >> -/* Initialization and cleanup functions for record_full_reg and
> >> -   record_full_mem entries.  */
> >> -
> >> -/* Init a record_full_reg record entry.  */
> >> -
> >> -static inline record_full_entry
> >> -record_full_reg_init (struct regcache *regcache, int regnum) -{
> >> -  record_full_entry rec;
> >> -  struct gdbarch *gdbarch = regcache->arch ();
> >> -
> >> -  rec.type = record_full_reg;
> >> -  rec.u.reg.num = regnum;
> >> -  rec.u.reg.len = register_size (gdbarch, regnum);
> >> -  if (rec.u.reg.len > sizeof (rec.u.reg.u.buf))
> >> -    rec.u.reg.u.ptr = (gdb_byte *) xmalloc (rec.u.reg.len);
> >> -
> >> -  return rec;
> >> -}
> >> -
> >> -/* Cleanup a record_full_reg record entry.  */
> >> -
> >> -static inline void
> >> -record_full_reg_cleanup (record_full_entry rec) -{
> >> -  gdb_assert (rec.type == record_full_reg);
> >> -  if (rec.u.reg.len > sizeof (rec.u.reg.u.buf))
> >> -    xfree (rec.u.reg.u.ptr);
> >> -}
> >> -
> >> -/* Init a record_full_mem record entry.  */
> >> -
> >> -static inline record_full_entry
> >> -record_full_mem_init (CORE_ADDR addr, int len) -{
> >> -  record_full_entry rec;
> >> -
> >> -  rec.type = record_full_mem;
> >> -  rec.u.mem.addr = addr;
> >> -  rec.u.mem.len = len;
> >> -  if (rec.u.mem.len > sizeof (rec.u.mem.u.buf))
> >> -    rec.u.mem.u.ptr = (gdb_byte *) xmalloc (len);
> >> -  rec.u.mem.mem_entry_not_accessible = 0;
> >> -
> >> -  return rec;
> >> -}
> >> -
> >> -/* Cleanup a record_full_mem record entry.  */
> >> -
> >> -static inline void
> >> -record_full_mem_cleanup (record_full_entry rec) -{
> >> -  gdb_assert (rec.type == record_full_mem);
> >> -  if (rec.u.mem.len > sizeof (rec.u.mem.u.buf))
> >> -    xfree (rec.u.mem.u.ptr);
> >> -}
> >> -
> >> -/* Free one record entry, any type.  */
> >> -
> >> -static inline void
> >> -record_full_entry_cleanup (record_full_entry rec) -{
> >> -
> >> -  switch (rec.type) {
> >> -  case record_full_reg:
> >> -    record_full_reg_cleanup (rec);
> >> -    break;
> >> -  case record_full_mem:
> >> -    record_full_mem_cleanup (rec);
> >> -    break;
> >> -  }
> >> -}
> >> -
> >>   static void
> >>   record_full_reset_history ()
> >>   {
> >>     record_full_insn_count = 0;
> >>     record_full_next_insn = 0;
> >>
> >> -  for (auto &insn : record_full_list)
> >> -    {
> >> -      for (auto &entry : insn.effects)
> >> -	record_full_entry_cleanup (entry);
> >> -    }
> >> -
> >>     record_full_list.clear ();
> >>   }
> >>
> >> @@ -476,11 +635,7 @@ static void
> >>   record_full_list_release_following (int index)  {
> >>     for (int i = record_full_list.size () - 1; i > index; i--)
> >> -    {
> >> -      for (auto &entry : record_full_list[i].effects)
> >> -	record_full_entry_cleanup (entry);
> >> -      record_full_list.pop_back ();
> >> -    }
> >> +    record_full_list.pop_back ();
> >>     /* Set the next instruction to be past the end of the log so we
> >>        start recording if the user moves forward again.  */
> >>     record_full_next_insn = index;
> >> @@ -509,9 +664,6 @@ record_full_list_release_first (void)
> >>     if (record_full_list.empty ())
> >>       return;
> >>
> >> -  for (auto &entry : record_full_list[0].effects)
> >> -    record_full_entry_cleanup (entry);
> >> -
> >>     record_full_list.pop_front ();
> >>     --record_full_next_insn;
> >>   }
> >> @@ -521,28 +673,7 @@ record_full_list_release_first (void)  static void
> >> record_full_arch_list_add (record_full_entry &rec)  {
> >> -  record_full_incomplete_instruction.effects.push_back (rec); -}
> >> -
> >> -/* Return the value storage location of a record entry.  */ -static inline
> >> gdb_byte * -record_full_get_loc (struct record_full_entry *rec) -{
> >> -  switch (rec->type) {
> >> -  case record_full_mem:
> >> -    if (rec->u.mem.len > sizeof (rec->u.mem.u.buf))
> >> -      return rec->u.mem.u.ptr;
> >> -    else
> >> -      return rec->u.mem.u.buf;
> >> -  case record_full_reg:
> >> -    if (rec->u.reg.len > sizeof (rec->u.reg.u.buf))
> >> -      return rec->u.reg.u.ptr;
> >> -    else
> >> -      return rec->u.reg.u.buf;
> >> -  default:
> >> -    gdb_assert_not_reached ("unexpected record_full_entry type");
> >> -    return NULL;
> >> -  }
> >> +  record_full_incomplete_instruction.effects.push_back (std::move
> >> + (rec));
> >>   }
> >>
> >>   /* Record the value of a register NUM to record_full_arch_list.  */ @@ -
> 550,7
> >> +681,7 @@ record_full_get_loc (struct record_full_entry *rec)  int
> >> record_full_arch_list_add_reg (struct regcache *regcache, int regnum)  {
> >> -  record_full_entry rec;
> >> +  record_full_entry rec (record_full_reg, regcache->arch (), regnum);
> >>
> >>     if (record_debug > 1)
> >>       gdb_printf (gdb_stdlog,
> >> @@ -558,9 +689,7 @@ record_full_arch_list_add_reg (struct regcache
> >> *regcache, int regnum)
> >>   		"record list.\n",
> >>   		regnum);
> >>
> >> -  rec = record_full_reg_init (regcache, regnum);
> >> -
> >> -  regcache->cooked_read (regnum, record_full_get_loc (&rec));
> >> +  regcache->cooked_read (regnum, rec.get_loc ());
> >>
> >>     record_full_arch_list_add (rec);
> >>
> >> @@ -573,7 +702,7 @@ record_full_arch_list_add_reg (struct regcache
> >> *regcache, int regnum)  int  record_full_arch_list_add_mem (CORE_ADDR
> >> addr, int len)  {
> >> -  record_full_entry rec;
> >> +  record_full_entry rec (record_full_mem, addr, len);
> >>
> >>     if (record_debug > 1)
> >>       gdb_printf (gdb_stdlog,
> >> @@ -584,14 +713,9 @@ record_full_arch_list_add_mem (CORE_ADDR
> addr,
> >> int len)
> >>     if (!addr)	/* FIXME: Why?  Some arch must permit it...  */
> >>       return 0;
> >>
> >> -  rec = record_full_mem_init (addr, len);
> >> -
> >>     if (record_read_memory (current_inferior ()->arch (), addr,
> >> -			  record_full_get_loc (&rec), len))
> >> -    {
> >> -      record_full_mem_cleanup (rec);
> >> -      return -1;
> >> -    }
> >> +			  rec.get_loc (), len))
> >> +    return -1;
> >>
> >>     record_full_arch_list_add (rec);
> >>
> >> @@ -719,98 +843,14 @@ record_full_gdb_operation_disable_set (void)
> >> static enum target_stop_reason record_full_stop_reason
> >>     = TARGET_STOPPED_BY_NO_REASON;
> >>
> >> -/* Execute one instruction from the record log.  Each instruction in
> >> -   the log will be represented by an arbitrary sequence of register
> >> -   entries and memory entries, followed by an 'end' entry.  */
> >> -
> >> -static inline void
> >> -record_full_exec_entry (regcache *regcache,
> >> -			gdbarch *gdbarch,
> >> -			record_full_entry *entry)
> >> -{
> >> -  switch (entry->type)
> >> -    {
> >> -    case record_full_reg: /* reg */
> >> -      {
> >> -	gdb::byte_vector reg (entry->u.reg.len);
> >> -
> >> -	if (record_debug > 1)
> >> -	  gdb_printf (gdb_stdlog,
> >> -		      "Process record: record_full_reg %s to "
> >> -		      "inferior num = %d.\n",
> >> -		      host_address_to_string (entry),
> >> -		      entry->u.reg.num);
> >> -
> >> -	regcache->cooked_read (entry->u.reg.num, reg.data ());
> >> -	regcache->cooked_write (entry->u.reg.num, record_full_get_loc
> >> (entry));
> >> -	memcpy (record_full_get_loc (entry), reg.data (), entry->u.reg.len);
> >> -      }
> >> -      break;
> >> -
> >> -    case record_full_mem: /* mem */
> >> -      {
> >> -	/* Nothing to do if the entry is flagged not_accessible.  */
> >> -	if (!entry->u.mem.mem_entry_not_accessible)
> >> -	  {
> >> -	    gdb::byte_vector mem (entry->u.mem.len);
> >> -
> >> -	    if (record_debug > 1)
> >> -	      gdb_printf (gdb_stdlog,
> >> -			  "Process record: record_full_mem %s to "
> >> -			  "inferior addr = %s len = %d.\n",
> >> -			  host_address_to_string (entry),
> >> -			  paddress (gdbarch, entry->u.mem.addr),
> >> -			  entry->u.mem.len);
> >> -
> >> -	    if (record_read_memory (gdbarch,
> >> -				    entry->u.mem.addr, mem.data (),
> >> -				    entry->u.mem.len))
> >> -	      entry->u.mem.mem_entry_not_accessible = 1;
> >> -	    else
> >> -	      {
> >> -		if (target_write_memory (entry->u.mem.addr,
> >> -					 record_full_get_loc (entry),
> >> -					 entry->u.mem.len))
> >> -		  {
> >> -		    entry->u.mem.mem_entry_not_accessible = 1;
> >> -		    if (record_debug)
> >> -		      warning (_("Process record: error writing memory at "
> >> -				 "addr = %s len = %d."),
> >> -			       paddress (gdbarch, entry->u.mem.addr),
> >> -			       entry->u.mem.len);
> >> -		  }
> >> -		else
> >> -		  {
> >> -		    memcpy (record_full_get_loc (entry), mem.data (),
> >> -			    entry->u.mem.len);
> >> -
> >> -		    /* We've changed memory --- check if a hardware
> >> -		       watchpoint should trap.  Note that this
> >> -		       presently assumes the target beneath supports
> >> -		       continuable watchpoints.  On non-continuable
> >> -		       watchpoints target, we'll want to check this
> >> -		       _before_ actually doing the memory change, and
> >> -		       not doing the change at all if the watchpoint
> >> -		       traps.  */
> >> -		    if (hardware_watchpoint_inserted_in_range
> >> -			(current_inferior ()->aspace.get (),
> >> -			 entry->u.mem.addr, entry->u.mem.len))
> >> -		      record_full_stop_reason =
> >> TARGET_STOPPED_BY_WATCHPOINT;
> >> -		  }
> >> -	      }
> >> -	  }
> >> -      }
> >> -      break;
> >> -    }
> >> -}
> >> -
> >>   static inline void
> >>   record_full_exec_insn (regcache *regcache,
> >>   		       gdbarch *gdbarch,
> >>   		       record_full_instruction &insn)  {
> >>     for (auto &entry : insn.effects)
> >> -    record_full_exec_entry (regcache, gdbarch, &entry);
> >> +    if (entry.execute (regcache))
> >> +      record_full_stop_reason = TARGET_STOPPED_BY_WATCHPOINT;
> >>   }
> >>
> >>   static void record_full_restore (struct bfd &cbfd); @@ -2185,21 +2225,19
> >> @@ record_full_read_entry_from_bfd (bfd *cbfd, asection *osec, int
> >> *bfd_offset)
> >>   	bfdcore_read (cbfd, osec, &regnum, sizeof (regnum), bfd_offset);
> >>   	regnum = netorder32 (regnum);
> >>
> >> -	record_full_entry rec;
> >> -
> >> -	rec = record_full_reg_init (cache, regnum);
> >> +	record_full_entry rec (record_full_reg, cache->arch (), regnum);
> >>
> >>   	/* Get val.  */
> >> -	bfdcore_read (cbfd, osec, record_full_get_loc (&rec),
> >> -		      rec.u.reg.len, bfd_offset);
> >> +	bfdcore_read (cbfd, osec, rec.get_loc (),
> >> +		      rec.reg ().len, bfd_offset);
> >>
> >>   	if (record_debug)
> >>   	  gdb_printf (gdb_stdlog,
> >>   		      "  Reading register %d (1 "
> >>   		      "plus %lu plus %d bytes)\n",
> >> -		      rec.u.reg.num,
> >> +		      rec.reg ().num,
> >>   		      (unsigned long) sizeof (regnum),
> >> -		      rec.u.reg.len);
> >> +		      rec.reg ().len);
> >>
> >>   	record_full_arch_list_add (rec);
> >>   	break;
> >> @@ -2216,19 +2254,17 @@ record_full_read_entry_from_bfd (bfd *cbfd,
> >> asection *osec, int *bfd_offset)
> >>   		      bfd_offset);
> >>   	addr = netorder64 (addr);
> >>
> >> -	record_full_entry rec;
> >> -	rec = record_full_mem_init (addr, len);
> >> +	record_full_entry rec (record_full_mem, addr, len);
> >>
> >>   	/* Get val.  */
> >> -	bfdcore_read (cbfd, osec, record_full_get_loc (&rec), len,
> >> -		      bfd_offset);
> >> +	bfdcore_read (cbfd, osec, rec.get_loc (), len, bfd_offset);
> >>
> >>   	if (record_debug)
> >>   	  gdb_printf (gdb_stdlog,
> >>   		      "  Reading memory %s (1 plus "
> >>   		      "%lu plus %lu plus %d bytes)\n",
> >>   		      paddress (get_current_arch (),
> >> -				rec.u.mem.addr),
> >> +				rec.mem ().addr),
> >>   		      (unsigned long) sizeof (addr),
> >>   		      (unsigned long) sizeof (len),
> >>   		      len);
> >> @@ -2380,52 +2416,56 @@ record_full_write_entry_to_bfd
> >> (record_full_entry &entry,
> >>     uint32_t regnum, len;
> >>     uint64_t addr;
> >>
> >> -  type = entry.type;
> >> +  type = entry.type ();
> >>     bfdcore_write (obfd.get (), osec, &type, sizeof (type), bfd_offset);
> >>
> >> -  switch (entry.type)
> >> +  switch (type)
> >>       {
> >>       case record_full_reg: /* reg */
> >> -      if (record_debug)
> >> -	gdb_printf (gdb_stdlog,
> >> -		    "  Writing register %d (1 "
> >> -		    "plus %lu plus %d bytes)\n",
> >> -		    entry.u.reg.num,
> >> -		    (unsigned long) sizeof (regnum),
> >> -		    entry.u.reg.len);
> >> -
> >> -      /* Write regnum.  */
> >> -      regnum = netorder32 (entry.u.reg.num);
> >> -      bfdcore_write (obfd.get (), osec, &regnum, sizeof (regnum),
> bfd_offset);
> >> -
> >> -      /* Write regval.  */
> >> -      bfdcore_write (obfd.get (), osec, record_full_get_loc (&entry),
> >> -		     entry.u.reg.len, bfd_offset);
> >> -      break;
> >> +      {
> >> +	auto &reg = entry.reg ();
> >> +	if (record_debug)
> >> +	  gdb_printf (gdb_stdlog,
> >> +		      "  Writing register %d (1 "
> >> +		      "plus %lu plus %d bytes)\n",
> >> +		      reg.num,
> >> +		      (unsigned long) sizeof (regnum),
> >> +		      reg.len);
> >> +
> >> +	/* Write regnum.  */
> >> +	regnum = netorder32 (reg.num);
> >> +	bfdcore_write (obfd.get (), osec, &regnum, sizeof (regnum),
> >> +bfd_offset);
> >> +
> >> +	/* Write regval.  */
> >> +	bfdcore_write (obfd.get (), osec, entry.get_loc (), reg.len, bfd_offset);
> >> +	break;
> >> +      }
> >>
> >>       case record_full_mem: /* mem */
> >> -      if (record_debug)
> >> -	gdb_printf (gdb_stdlog,
> >> -		    "  Writing memory %s (1 plus "
> >> -		    "%lu plus %lu plus %d bytes)\n",
> >> -		    paddress (gdbarch,
> >> -			      entry.u.mem.addr),
> >> -		    (unsigned long) sizeof (addr),
> >> -		    (unsigned long) sizeof (len),
> >> -		    entry.u.mem.len);
> >> -
> >> -      /* Write memlen.  */
> >> -      len = netorder32 (entry.u.mem.len);
> >> -      bfdcore_write (obfd.get (), osec, &len, sizeof (len), bfd_offset);
> >> -
> >> -      /* Write memaddr.  */
> >> -      addr = netorder64 (entry.u.mem.addr);
> >> -      bfdcore_write (obfd.get (), osec, &addr, sizeof (addr), bfd_offset);
> >> -
> >> -      /* Write memval.  */
> >> -      bfdcore_write (obfd.get (), osec, record_full_get_loc (&entry),
> >> -		     entry.u.mem.len, bfd_offset);
> >> -      break;
> >> +      {
> >> +	auto &mem = entry.mem ();
> >> +	if (record_debug)
> >> +	  gdb_printf (gdb_stdlog,
> >> +		      "  Writing memory %s (1 plus "
> >> +		      "%lu plus %lu plus %d bytes)\n",
> >> +		      paddress (gdbarch, mem.addr),
> >> +		      (unsigned long) sizeof (addr),
> >> +		      (unsigned long) sizeof (len),
> >> +		      mem.len);
> >> +
> >> +	/* Write memlen.  */
> >> +	len = netorder32 (mem.len);
> >> +	bfdcore_write (obfd.get (), osec, &len, sizeof (len), bfd_offset);
> >> +
> >> +	/* Write memaddr.  */
> >> +	addr = netorder64 (mem.addr);
> >> +	bfdcore_write (obfd.get (), osec, &addr, sizeof (addr), bfd_offset);
> >> +
> >> +	/* Write memval.  */
> >> +	bfdcore_write (obfd.get (), osec, entry.get_loc (), mem.len,
> >> +		       bfd_offset);
> >> +	break;
> >> +      }
> >>       }
> >>
> >>   }
> >> @@ -2473,13 +2513,13 @@ record_full_base_target::save_record (const
> >> char *recfilename)
> >>         /* Number of effects of an instruction.  */
> >>         save_size += sizeof (uint32_t) + sizeof (uint8_t) + sizeof (uint32_t);
> >>         for (auto &entry : record_full_list[i].effects)
> >> -	switch (entry.type)
> >> +	switch (entry.type ())
> >>   	  {
> >>   	  case record_full_reg:
> >> -	    save_size += 1 + 4 + entry.u.reg.len;
> >> +	    save_size += 1 + 4 + entry.reg ().len;
> >>   	    break;
> >>   	  case record_full_mem:
> >> -	    save_size += 1 + 4 + 8 + entry.u.mem.len;
> >> +	    save_size += 1 + 4 + 8 + entry.mem ().len;
> >>   	    break;
> >>   	  }
> >>       }
> >> @@ -2612,18 +2652,18 @@ maintenance_print_record_instruction
> (const
> >> char *args, int from_tty)
> >>
> >>     gdbarch *arch = current_inferior ()->arch ();
> >>
> >> -  for (auto entry : to_print->effects)
> >> +  for (auto &entry : to_print->effects)
> >>       {
> >> -      switch (entry.type)
> >> +      switch (entry.type ())
> >>   	{
> >>   	  case record_full_reg:
> >>   	    {
> >> -	      type *regtype = gdbarch_register_type (arch, entry.u.reg.num);
> >> +	      type *regtype = gdbarch_register_type (arch, entry.reg ().num);
> >>   	      value *val
> >>   		  = value_from_contents (regtype,
> >> -					 record_full_get_loc (&entry));
> >> +					 entry.get_loc ());
> >>   	      gdb_printf ("Register %s changed: ",
> >> -			  gdbarch_register_name (arch, entry.u.reg.num));
> >> +			  gdbarch_register_name (arch, entry.reg ().num));
> >>   	      struct value_print_options opts;
> >>   	      get_user_print_options (&opts);
> >>   	      opts.raw = true;
> >> @@ -2633,11 +2673,12 @@ maintenance_print_record_instruction
> (const
> >> char *args, int from_tty)
> >>   	    }
> >>   	  case record_full_mem:
> >>   	    {
> >> -	      gdb_byte *b = record_full_get_loc (&entry);
> >> +	      record_full_mem_entry& mem = entry.mem ();
> >> +	      gdb_byte *b = entry.get_loc ();
> >>   	      gdb_printf ("%d bytes of memory at address %s changed from:",
> >> -			  entry.u.mem.len,
> >> -			  print_core_address (arch, entry.u.mem.addr));
> >> -	      for (int i = 0; i < entry.u.mem.len; i++)
> >> +			  mem.len,
> >> +			  print_core_address (arch, mem.addr));
> >> +	      for (int i = 0; i < mem.len; i++)
> >>   		gdb_printf (" %02x", b[i]);
> >>   	      gdb_printf ("\n");
> >>   	      break;
> >> --
> >> 2.54.0
> >>
> > Christina
> > Intel Deutschland GmbH
> > Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
> > Tel: +49 89 991 430, www.intel.de
> > Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
> > Chairperson of the Supervisory Board: Nicole Lau
> > Registered Seat: Munich
> > Commercial Register: Amtsgericht Muenchen HRB 186928
> >
> 
> --
> Cheers,
> Guinevere Larsen
> It/she

Christina
Intel Deutschland GmbH
Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
Tel: +49 89 991 430, www.intel.de
Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
Chairperson of the Supervisory Board: Nicole Lau
Registered Seat: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928

^ permalink raw reply	[flat|nested] 27+ messages in thread

* RE: [PATCH v3 5/7] gdb/record: extract the PC to record_full_instruction
  2026-05-15 16:37 ` [PATCH v3 5/7] gdb/record: extract the PC to record_full_instruction Guinevere Larsen
  2026-05-16  1:04   ` Thiago Jung Bauermann
@ 2026-05-29 12:40   ` Schimpe, Christina
  2026-06-01 20:24     ` Guinevere Larsen
  1 sibling, 1 reply; 27+ messages in thread
From: Schimpe, Christina @ 2026-05-29 12:40 UTC (permalink / raw)
  To: Guinevere Larsen, gdb-patches

> -----Original Message-----
> From: Guinevere Larsen <guinevere@redhat.com>
> Sent: Freitag, 15. Mai 2026 18:37
> To: gdb-patches@sourceware.org
> Cc: Guinevere Larsen <guinevere@redhat.com>
> Subject: [PATCH v3 5/7] gdb/record: extract the PC to record_full_instruction
> 
> This commit makes it so the PC is not saved as part of the
> record_full_instruction effects, but rather gets a special location.
> That is because a couple of commands would really benefit from it being easy
> to find the PC (especially ones from record-btrace that's haven't been
> implemented to record-full yet, such as the ones in PR record/18059), while
> also possibly allowing for one fewer resizing of the effect vector (and saving
> an entire byte in the process).
> 
> This commit also refactored record_full_read_entry_from_bfd and
> record_full_write_entry_to_bfd, to make them methods of
> record_full_reg_entry and record_full_mem_entry, and also creates similar
> methods for record_full_entry and record_full_instruction.
> These could be turned into constructors in a future step of
> c++ification, but it felt like too much change for a single commit.
> ---
>  gdb/record-full.c | 386 ++++++++++++++++++++++++++++------------------
>  1 file changed, 239 insertions(+), 147 deletions(-)
> 
> diff --git a/gdb/record-full.c b/gdb/record-full.c index
> 8cabd6e9438..17696e07c0d 100644
> --- a/gdb/record-full.c
> +++ b/gdb/record-full.c
> @@ -143,6 +143,14 @@ struct record_full_mem_entry
> 
>    DISABLE_COPY_AND_ASSIGN (record_full_mem_entry);
> 
> +  /* Create a mem_entry from a bfd file, when restoring a recording.
> +*/
> +  static record_full_mem_entry from_bfd (bfd *cbfd, asection* osec,
> +					 int *bfd_offset);
> +
> +  /* Save this mem entry to a bfd file.  */
> +  void to_bfd (gdb_bfd_ref_ptr obfd, asection *osec, int *bfd_offset,
> +	       gdbarch *gdbarch);
> +

I believe only in one of those to_bfd functions the gdbarch parameter is used.
Did you intentionally keep it the function which don't have use the parameter in to_bfd ?

And it might be a good idea to describe the parameters a bit, too. 😊

>    gdb_byte *get_loc ()
>    {
>      if (len > sizeof (u.buf))
> @@ -252,6 +260,14 @@ struct record_full_reg_entry
> 
>    DISABLE_COPY_AND_ASSIGN (record_full_reg_entry);
> 
> +  /* Create a reg_entry from a bfd file, when restoring a recording.
> +*/
> +  static record_full_reg_entry from_bfd (bfd *cbfd, asection* osec,
> +					 int *bfd_offset);
> +
> +  /* Save this reg entry to a bfd file.  */
> +  void to_bfd (gdb_bfd_ref_ptr obfd, asection *osec, int *bfd_offset,
> +	       gdbarch *gdbarch);
> +
>    gdb_byte *get_loc ()
>    {
>      if (len > sizeof (u.buf))
> @@ -314,6 +330,13 @@ class record_full_entry
> 
>    DISABLE_COPY_AND_ASSIGN (record_full_entry);
> 
> +  /* Create a generic entry from a bfd file, when restoring a
> + recording.  */  static void from_bfd (bfd *cbfd, asection* osec, int
> + *bfd_offset);
> +
> +  /* Save this entry to a bfd file.  */
> +  void to_bfd (gdb_bfd_ref_ptr obfd, asection *osec, int *bfd_offset,
> +	       gdbarch *gdbarch);
> +
>    record_full_reg_entry& reg ()
>    {
>      gdb_assert (type () == record_full_reg); @@ -374,6 +397,7 @@ class
> record_full_entry
>       this one;
>     * sigval: Whether the inferior received a signal while the following
>       instruction was being recorded;
> +   * pc: The Program Counter for where the instruction starts;

"for where" sounds a bit odd, what do you think about:

The Program Counter at the start of the instruction.

>     * effects: A list of record_full_entry structures, each of which
>       describing one effect that the instruction has on the inferior.
> 
> @@ -388,10 +412,18 @@ struct record_full_instruction
>    uint32_t insn_num;
>    std::optional<gdb_signal> sigval;
>    std::vector<record_full_entry> effects;
> +  record_full_reg_entry pc;
> 
>    /* Execute the full instruction.  As a side effect, set
>       record_full_stop_reason.  */
>    void exec_insn (regcache *regcache);
> +
> +  /* Create a full recorded instruction from a bfd.  */  static void
> + from_bfd (bfd *cbfd, asection* osec, int *bfd_offset);
> +
> +  /* Save this instruction to a bfd file.  */
> +  void to_bfd (gdb_bfd_ref_ptr obfd, asection *osec, int *bfd_offset,
> +	       gdbarch *gdbarch);
>  };
> 
>  /* If true, query if PREC cannot record memory @@ -695,7 +727,10 @@
> record_full_arch_list_add_reg (struct regcache *regcache, int regnum)
> 
>    regcache->cooked_read (regnum, rec.get_loc ());
> 
> -  record_full_arch_list_add (rec);
> +  if (regnum == gdbarch_pc_regnum (regcache->arch ()))
> +      record_full_incomplete_instruction.pc = std::move (rec.reg ());
> + else
> +    record_full_arch_list_add (rec);

Nit: indent is only 2 spaces.

>    return 0;
>  }
> @@ -849,6 +884,7 @@ static enum target_stop_reason
> record_full_stop_reason
> 
>  void record_full_instruction::exec_insn (regcache *regcache)  {
> +  pc.execute (regcache);
>    for (auto &entry : effects)
>      if (entry.execute (regcache))
>        record_full_stop_reason = TARGET_STOPPED_BY_WATCHPOINT; @@ -
> 2207,68 +2243,88 @@ netorder32 (uint32_t input)
>    return ret;
>  }
> 
> -static void
> -record_full_read_entry_from_bfd (bfd *cbfd, asection *osec, int *bfd_offset)
> +record_full_reg_entry
> +record_full_reg_entry::from_bfd (bfd *cbfd, asection *osec, int
> +*bfd_offset)
>  {
> -  uint8_t rectype;
> -  uint32_t regnum, len;
> -  uint64_t addr;
> +  uint32_t regnum;
>    regcache *cache = get_thread_regcache (inferior_thread ());
> 
> +  /* Get register number to regnum.  */
> +  bfdcore_read (cbfd, osec, &regnum, sizeof (regnum),
> +		bfd_offset);
> +  regnum = netorder32 (regnum);
> +
> +  record_full_reg_entry reg (cache->arch (), regnum);
> +
> +  /* Get val.  */
> +  bfdcore_read (cbfd, osec, reg.get_loc (),
> +		reg.len, bfd_offset);
> +
> +  if (record_debug)
> +    gdb_printf (gdb_stdlog,
> +		"  Reading register %d (1 "
> +		"plus %lu plus %d bytes)\n",
> +		reg.num,
> +		(unsigned long) sizeof (regnum),
> +		reg.len);
> +  return reg;
> +
> +}
> +
> +record_full_mem_entry
> +record_full_mem_entry::from_bfd (bfd *cbfd, asection *osec, int
> +*bfd_offset) {
> +  uint32_t len;
> +  uint64_t addr;
> +
> +  /* Get len.  */
> +  bfdcore_read (cbfd, osec, &len, sizeof (len), bfd_offset);  len =
> + netorder32 (len);
> +
> +  /* Get addr.  */
> +  bfdcore_read (cbfd, osec, &addr, sizeof (addr),
> +		bfd_offset);
> +  addr = netorder64 (addr);
> +
> +  record_full_mem_entry mem (addr, len);
> +
> +  /* Get val.  */
> +  bfdcore_read (cbfd, osec, mem.get_loc (),
> +		len, bfd_offset);
> +
> +  if (record_debug)
> +    gdb_printf (gdb_stdlog,
> +		"  Reading memory %s (1 plus "
> +		"%lu plus %lu plus %d bytes)\n",
> +		paddress (get_current_arch (),
> +			  mem.addr),
> +		(unsigned long) sizeof (addr),
> +		(unsigned long) sizeof (len),
> +		len);
> +
> +  return mem;
> +}
> +
> +void
> +record_full_entry::from_bfd (bfd *cbfd, asection *osec, int
> +*bfd_offset) {
> +  uint8_t rectype;
> +  record_full_entry rec;
> +
>    bfdcore_read (cbfd, osec, &rectype, sizeof (rectype), bfd_offset);
>    switch (rectype)
>      {
>      case record_full_reg: /* reg */
>        {
> -	/* Get register number to regnum.  */
> -	bfdcore_read (cbfd, osec, &regnum, sizeof (regnum), bfd_offset);
> -	regnum = netorder32 (regnum);
> -
> -	record_full_entry rec (record_full_reg, cache->arch (), regnum);
> -
> -	/* Get val.  */
> -	bfdcore_read (cbfd, osec, rec.get_loc (),
> -		      rec.reg ().len, bfd_offset);
> -
> -	if (record_debug)
> -	  gdb_printf (gdb_stdlog,
> -		      "  Reading register %d (1 "
> -		      "plus %lu plus %d bytes)\n",
> -		      rec.reg ().num,
> -		      (unsigned long) sizeof (regnum),
> -		      rec.reg ().len);
> -
> -	record_full_arch_list_add (rec);
> +	rec.entry = std::move (record_full_reg_entry::from_bfd
> +				(cbfd, osec, bfd_offset));
>  	break;
>        }
> 
>      case record_full_mem: /* mem */
>        {
> -	/* Get len.  */
> -	bfdcore_read (cbfd, osec, &len, sizeof (len), bfd_offset);
> -	len = netorder32 (len);
> -
> -	/* Get addr.  */
> -	bfdcore_read (cbfd, osec, &addr, sizeof (addr),
> -		      bfd_offset);
> -	addr = netorder64 (addr);
> -
> -	record_full_entry rec (record_full_mem, addr, len);
> -
> -	/* Get val.  */
> -	bfdcore_read (cbfd, osec, rec.get_loc (), len, bfd_offset);
> -
> -	if (record_debug)
> -	  gdb_printf (gdb_stdlog,
> -		      "  Reading memory %s (1 plus "
> -		      "%lu plus %lu plus %d bytes)\n",
> -		      paddress (get_current_arch (),
> -				rec.mem ().addr),
> -		      (unsigned long) sizeof (addr),
> -		      (unsigned long) sizeof (len),
> -		      len);
> -
> -	record_full_arch_list_add (rec);
> +	rec.entry = std::move (record_full_mem_entry::from_bfd
> +				(cbfd, osec, bfd_offset));
>  	break;
>        }
> 
> @@ -2279,6 +2335,39 @@ record_full_read_entry_from_bfd (bfd *cbfd,
> asection *osec, int *bfd_offset)
>  			    bfd_get_filename (cbfd)));
>        break;
>      }
> +  record_full_arch_list_add (rec);
> +}
> +
> +void
> +record_full_instruction::from_bfd (bfd *cbfd, asection *osec, int
> +*bfd_offset) {
> +  uint32_t eff_count = 0;
> +  uint8_t sigval;
> +  uint32_t insn_num;
> +
> +  /* First read the generic information for an instruction.  */
> +  bfdcore_read (cbfd, osec, &sigval, sizeof (uint8_t), bfd_offset);
> +  bfdcore_read (cbfd, osec, &eff_count, sizeof (uint32_t),
> +		bfd_offset);
> +  bfdcore_read (cbfd, osec, &insn_num, sizeof (uint32_t),
> +		bfd_offset);
> +
> +  record_full_incomplete_instruction.insn_num = netorder32 (insn_num);
> + if (sigval != GDB_SIGNAL_0)
> +    record_full_incomplete_instruction.sigval = (gdb_signal) sigval;
> +
> +  record_full_incomplete_instruction.pc
> +    = record_full_reg_entry::from_bfd (cbfd, osec, bfd_offset);
> +
> +  eff_count = netorder32 (eff_count);
> +
> +  /* This deals with all the side effects.  */ while (eff_count > 0)
> +    {
> +      eff_count--;
> +
> +      record_full_entry::from_bfd (cbfd, osec, bfd_offset);
> +    }
>  }
> 
>  /* Restore the execution log from core file CBFD.  */ @@ -2325,30 +2414,8
> @@ record_full_restore (struct bfd &cbfd)
>        while (bfd_offset < osec_size)
>  	{
>  	  record_full_reset_incomplete ();
> -	  uint32_t eff_count = 0;
> -	  uint8_t sigval;
> -	  uint32_t insn_num;
> -
> -	  /* First read the generic information for an instruction.  */
> -	  bfdcore_read (&cbfd, osec, &sigval, sizeof (uint8_t), &bfd_offset);
> -	  bfdcore_read (&cbfd, osec, &eff_count, sizeof (uint32_t),
> -			&bfd_offset);
> -	  bfdcore_read (&cbfd, osec, &insn_num, sizeof (uint32_t),
> -			&bfd_offset);
> -
> -	  record_full_incomplete_instruction.insn_num = netorder32
> (insn_num);
> -	  if (sigval != GDB_SIGNAL_0)
> -	    record_full_incomplete_instruction.sigval = (gdb_signal) sigval;
> 
> -	  eff_count = netorder32 (eff_count);
> -
> -	  /* This deals with all the side effects.  */
> -	 while (eff_count > 0)
> -	   {
> -	     eff_count--;
> -
> -	     record_full_read_entry_from_bfd (&cbfd, osec, &bfd_offset);
> -	   }
> +	  record_full_instruction::from_bfd (&cbfd, osec, &bfd_offset);
> 
>  	  record_full_save_instruction ();
>  	}
> @@ -2404,71 +2471,109 @@ cmd_record_full_restore (const char *args, int
> from_tty)
>    record_full_open (nullptr, from_tty);  }
> 
> -static void
> -record_full_write_entry_to_bfd (record_full_entry &entry,
> -				gdb_bfd_ref_ptr obfd,
> -				asection *osec, int *bfd_offset,
> -				gdbarch *gdbarch)
> +void
> +record_full_reg_entry::to_bfd (gdb_bfd_ref_ptr obfd,
> +			       asection *osec, int *bfd_offset,
> +			       gdbarch *gdbarch)
> +{
> +  uint32_t regnum;
> +
> +  if (record_debug)
> +    gdb_printf (gdb_stdlog,
> +		"  Writing register %d (1 "
> +		"plus %lu plus %d bytes)\n",
> +		this->num,
> +		(unsigned long) sizeof (regnum),
> +		this->len);
> +
> +  /* Write regnum.  */
> +  regnum = netorder32 (this->num);
> +  bfdcore_write (obfd.get (), osec, &regnum, sizeof (regnum),
> + bfd_offset);
> +
> +  /* Write regval.  */
> +  bfdcore_write (obfd.get (), osec, this->get_loc (), this->len,
> +bfd_offset); }
> +
> +void
> +record_full_mem_entry::to_bfd (gdb_bfd_ref_ptr obfd,
> +			       asection *osec, int *bfd_offset,
> +			       gdbarch *gdbarch)
> +{
> +  uint32_t len;
> +  uint64_t addr;
> +
> +  if (record_debug)
> +    gdb_printf (gdb_stdlog,
> +		"  Writing memory %s (1 plus "
> +		"%lu plus %lu plus %d bytes)\n",
> +		paddress (gdbarch, this->addr),
> +		(unsigned long) sizeof (addr),
> +		(unsigned long) sizeof (len),
> +		this->len);
> +
> +  /* Write memlen.  */
> +  len = netorder32 (this->len);
> +  bfdcore_write (obfd.get (), osec, &len, sizeof (len), bfd_offset);
> +
> +  /* Write memaddr.  */
> +  addr = netorder64 (this->addr);
> +  bfdcore_write (obfd.get (), osec, &addr, sizeof (addr), bfd_offset);
> +
> +  /* Write memval.  */
> +  bfdcore_write (obfd.get (), osec, this->get_loc (), this->len,
> +bfd_offset); }
> +
> +void
> +record_full_entry::to_bfd (gdb_bfd_ref_ptr obfd, asection *osec,
> +			   int *bfd_offset, gdbarch *gdbarch)
>  {
>    /* Save entry.  */
>    uint8_t type;
> -  uint32_t regnum, len;
> -  uint64_t addr;
> 
> -  type = entry.type ();
> +  type = this->type ();
>    bfdcore_write (obfd.get (), osec, &type, sizeof (type), bfd_offset);
> 
>    switch (type)
>      {
>      case record_full_reg: /* reg */
> -      {
> -	auto &reg = entry.reg ();
> -	if (record_debug)
> -	  gdb_printf (gdb_stdlog,
> -		      "  Writing register %d (1 "
> -		      "plus %lu plus %d bytes)\n",
> -		      reg.num,
> -		      (unsigned long) sizeof (regnum),
> -		      reg.len);
> -
> -	/* Write regnum.  */
> -	regnum = netorder32 (reg.num);
> -	bfdcore_write (obfd.get (), osec, &regnum, sizeof (regnum),
> bfd_offset);
> -
> -	/* Write regval.  */
> -	bfdcore_write (obfd.get (), osec, entry.get_loc (), reg.len, bfd_offset);
> -	break;
> -      }
> +      reg ().to_bfd (obfd, osec, bfd_offset, gdbarch);
> +      break;
> 
>      case record_full_mem: /* mem */
> -      {
> -	auto &mem = entry.mem ();
> -	if (record_debug)
> -	  gdb_printf (gdb_stdlog,
> -		      "  Writing memory %s (1 plus "
> -		      "%lu plus %lu plus %d bytes)\n",
> -		      paddress (gdbarch, mem.addr),
> -		      (unsigned long) sizeof (addr),
> -		      (unsigned long) sizeof (len),
> -		      mem.len);
> -
> -	/* Write memlen.  */
> -	len = netorder32 (mem.len);
> -	bfdcore_write (obfd.get (), osec, &len, sizeof (len), bfd_offset);
> -
> -	/* Write memaddr.  */
> -	addr = netorder64 (mem.addr);
> -	bfdcore_write (obfd.get (), osec, &addr, sizeof (addr), bfd_offset);
> -
> -	/* Write memval.  */
> -	bfdcore_write (obfd.get (), osec, entry.get_loc (), mem.len,
> -		       bfd_offset);
> -	break;
> -      }
> +      mem ().to_bfd (obfd, osec, bfd_offset, gdbarch);
> +      break;
>      }
> 
>  }
> 
> +void
> +record_full_instruction::to_bfd (gdb_bfd_ref_ptr obfd, asection *osec,
> +				 int *bfd_offset, gdbarch *gdbarch) {
> +  uint32_t eff_count = (uint32_t) this->effects.size ();
> +  uint32_t insn_num = this->insn_num;
> +  uint8_t sigval = (this->sigval.has_value ())
> +		     ? this->sigval.value ()
> +		     : GDB_SIGNAL_0;
> +
> +  /* Signal.  */
> +  bfdcore_write (obfd.get (), osec, &sigval, sizeof (sigval),
> +bfd_offset);
> +  eff_count = netorder32 (eff_count);
> +  /* Number of effects.  */
> +  bfdcore_write (obfd.get (), osec, &eff_count, sizeof (eff_count),
> +		 bfd_offset);
> +  /* Instruction number.  */
> +  bfdcore_write (obfd.get (), osec, &insn_num, sizeof (uint32_t),
> +		 bfd_offset);
> +
> +  this->pc.to_bfd (obfd, osec, bfd_offset, gdbarch);
> +
> +  for (auto &entry : this->effects)
> +    {
> +      entry.to_bfd (obfd, osec, bfd_offset, gdbarch);
> +    }
> +}
> +
>  /* Save the execution log to a file.  We use a modified elf corefile
>     format, with an extra section for our data.  */
> 
> @@ -2510,6 +2615,7 @@ record_full_base_target::save_record (const char
> *recfilename)
>      {
>        /* Number of effects of an instruction.  */
>        save_size += sizeof (uint32_t) + sizeof (uint8_t) + sizeof (uint32_t);
> +      save_size += 4 + record_full_list[i].pc.len;
>        for (auto &entry : record_full_list[i].effects)
>  	switch (entry.type ())
>  	  {
> @@ -2551,27 +2657,7 @@ record_full_base_target::save_record (const char
> *recfilename)
>       record list.  */
>    for (int i = 0; i < record_full_list.size (); i++)
>      {
> -      uint32_t eff_count = (uint32_t) record_full_list[i].effects.size ();
> -      uint32_t insn_num = record_full_list[i].insn_num;
> -      uint8_t sigval = (record_full_list[i].sigval.has_value ())
> -			 ? record_full_list[i].sigval.value ()
> -			 : GDB_SIGNAL_0;
> -
> -      /* Signal.  */
> -      bfdcore_write (obfd.get (), osec, &sigval, sizeof (sigval), &bfd_offset);
> -      eff_count = netorder32 (eff_count);
> -      /* Number of effects.  */
> -      bfdcore_write (obfd.get (), osec, &eff_count, sizeof (eff_count),
> -		     &bfd_offset);
> -      /* Instruction number.  */
> -      bfdcore_write (obfd.get (), osec, &insn_num, sizeof (uint32_t),
> -		     &bfd_offset);
> -
> -      for (auto &entry : record_full_list[i].effects)
> -	{
> -	  record_full_write_entry_to_bfd (entry, obfd, osec, &bfd_offset,
> -					  gdbarch);
> -	}
> +      record_full_list[i].to_bfd (obfd, osec, &bfd_offset, gdbarch);
>        /* Execute entry.  */
>        if (i < record_full_next_insn)
>  	record_full_list[i].exec_insn (regcache); @@ -2648,6 +2734,9 @@
> maintenance_print_record_instruction (const char *args, int from_tty)
>    auto to_print = record_full_list.begin () + offset;
> 
>    gdbarch *arch = current_inferior ()->arch ();
> +  struct value_print_options opts;
> +  get_user_print_options (&opts);
> +  opts.raw = true;
> 
>    for (auto &entry : to_print->effects)
>      {
> @@ -2661,9 +2750,6 @@ maintenance_print_record_instruction (const char
> *args, int from_tty)
>  					 entry.get_loc ());
>  	      gdb_printf ("Register %s changed: ",
>  			  gdbarch_register_name (arch, entry.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;
> @@ -2682,6 +2768,12 @@ maintenance_print_record_instruction (const
> char *args, int from_tty)
>  	    }
>  	}
>      }
> +  type *regtype = gdbarch_register_type (arch, to_print->pc.num);
> +  value *val = value_from_contents (regtype, to_print->pc.get_loc ());
> +  gdb_printf ("Register %s changed: ",
> +	      gdbarch_register_name (arch, to_print->pc.num));
> +  value_print (val, gdb_stdout, &opts);
> +  gdb_printf ("\n");
>  }
> 
>  INIT_GDB_FILE (record_full)
> --
> 2.54.0
> 

Besides those nits, LGTM:

Reviewed-By: Christina Schimpe <christina.schimpe@intel.com>

Christina
Intel Deutschland GmbH
Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
Tel: +49 89 991 430, www.intel.de
Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
Chairperson of the Supervisory Board: Nicole Lau
Registered Seat: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928

^ permalink raw reply	[flat|nested] 27+ messages in thread

* RE: [PATCH v3 1/7] gdb/record: Refactor record history
  2026-05-28 13:20     ` Guinevere Larsen
@ 2026-05-29 13:47       ` Schimpe, Christina
  0 siblings, 0 replies; 27+ messages in thread
From: Schimpe, Christina @ 2026-05-29 13:47 UTC (permalink / raw)
  To: Guinevere Larsen, gdb-patches; +Cc: Thiago Jung Bauermann

> -----Original Message-----
> From: Guinevere Larsen <guinevere@redhat.com>
> Sent: Donnerstag, 28. Mai 2026 15:20
> To: Schimpe, Christina <christina.schimpe@intel.com>; gdb-
> patches@sourceware.org
> Cc: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> Subject: Re: [PATCH v3 1/7] gdb/record: Refactor record history
> 
> On 5/26/26 5:33 AM, Schimpe, Christina wrote:
> > Hi Guinevere,
> >
> > Please find some further comments below.
> >
> >> -----Original Message-----
> >> From: Guinevere Larsen <guinevere@redhat.com>
> >> Sent: Freitag, 15. Mai 2026 18:37
> >> To: gdb-patches@sourceware.org
> >> Cc: Guinevere Larsen <guinevere@redhat.com>; Thiago Jung Bauermann
> >> <thiago.bauermann@linaro.org>
> >> Subject: [PATCH v3 1/7] gdb/record: Refactor record history
> >>
> >> This is the first step in a large refactor in how GDB keeps execution
> >> history.  Rather than using a linked list where multiple entries can
> >> describe a single instruction, the history will now be stored in an
> >> std::deque, each instruction being one entry in the deque.
> >>
> >> The choice was initially to use an std::vector, but it would become
> >> unwieldy because it needs all the memory to be consecutive, which is
> >> hard for 200 thousand entries. Deque was picked because it was a nice
> >> midpoint between vector (maximum cache cohesion) and linked list
> >> (maximum ease of finding space to store more).
> >>
> >> Each instruction in memory will be now one record_full_instruction
> >> entry, which for this commit just contains a vector of
> >> record_full_entry for the effects of the instruction, and the data that
> >> was stored in the record_full_end entry (that is, the instruction number
> >> and the signal, if any).
> >>
> >> This change introduced a minimal performance improvement (what's
> >> important is that it isn't a degradation) and a  reduction in the total
> >> memory footprint of roughly 20% if the entire history is used.
> >>
> >> Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
> >> ---
> >>   gdb/aarch64-tdep.c     |    2 -
> >>   gdb/amd64-linux-tdep.c |    3 -
> >>   gdb/arm-tdep.c         |    2 -
> >>   gdb/i386-linux-tdep.c  |    3 -
> >>   gdb/i386-tdep.c        |    4 -
> >>   gdb/loongarch-tdep.c   |    2 -
> >>   gdb/moxie-tdep.c       |    2 -
> >>   gdb/ppc-linux-tdep.c   |    3 -
> >>   gdb/record-full.c      | 1186 ++++++++++++++++------------------------
> >>   gdb/record-full.h      |    1 -
> >>   gdb/riscv-tdep.c       |    3 -
> >>   gdb/rs6000-tdep.c      |    4 -
> >>   gdb/s390-linux-tdep.c  |    3 -
> >>   gdb/s390-tdep.c        |    2 -
> >>   14 files changed, 484 insertions(+), 736 deletions(-)
> >>
> >> diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c
> >> index 4befaa2720d..a532992b1d8 100644
> >> --- a/gdb/aarch64-tdep.c
> >> +++ b/gdb/aarch64-tdep.c
> >> @@ -6210,8 +6210,6 @@ aarch64_process_record (struct gdbarch
> >> *gdbarch, struct regcache *regcache,
> >>   	       aarch64_record.aarch64_mems[rec_no].len))
> >>   	    ret = -1;
> >>
> >> -      if (record_full_arch_list_add_end ())
> >> -	ret = -1;
> >>       }
> >>
> >>     deallocate_reg_mem (&aarch64_record);
> >> diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c
> >> index a5ac26654cf..9b23db72bbe 100644
> >> --- a/gdb/amd64-linux-tdep.c
> >> +++ b/gdb/amd64-linux-tdep.c
> >> @@ -1591,9 +1591,6 @@ amd64_linux_record_signal (struct gdbarch
> >> *gdbarch,
> >>   				     + AMD64_LINUX_frame_size))
> >>       return -1;
> >>
> >> -  if (record_full_arch_list_add_end ())
> >> -    return -1;
> >> -
> >>     return 0;
> >>   }
> >>
> >> diff --git a/gdb/arm-tdep.c b/gdb/arm-tdep.c
> >> index 08cce9dbad8..1ce0927f26b 100644
> >> --- a/gdb/arm-tdep.c
> >> +++ b/gdb/arm-tdep.c
> >> @@ -14911,8 +14911,6 @@ arm_process_record (struct gdbarch
> *gdbarch,
> >> struct regcache *regcache,
> >>   	    }
> >>   	}
> >>
> >> -      if (record_full_arch_list_add_end ())
> >> -	ret = -1;
> >>       }
> >>
> >>
> >> diff --git a/gdb/i386-linux-tdep.c b/gdb/i386-linux-tdep.c
> >> index 23aeccac9fc..4f33766fcdd 100644
> >> --- a/gdb/i386-linux-tdep.c
> >> +++ b/gdb/i386-linux-tdep.c
> >> @@ -949,9 +949,6 @@ i386_linux_record_signal (struct gdbarch
> *gdbarch,
> >>   				     I386_LINUX_xstate +
> >> I386_LINUX_frame_size))
> >>       return -1;
> >>
> >> -  if (record_full_arch_list_add_end ())
> >> -    return -1;
> >> -
> >>     return 0;
> >>   }
> >>
> >> diff --git a/gdb/i386-tdep.c b/gdb/i386-tdep.c
> >> index fa935b5fcdb..fee36b46a73 100644
> >> --- a/gdb/i386-tdep.c
> >> +++ b/gdb/i386-tdep.c
> >> @@ -5201,8 +5201,6 @@ i386_record_vex (struct i386_record_s *ir,
> uint8_t
> >> vex_w, uint8_t vex_r,
> >>       }
> >>
> >>     record_full_arch_list_add_reg (ir->regcache, ir-
> >>> regmap[X86_RECORD_REIP_REGNUM]);
> >> -  if (record_full_arch_list_add_end ())
> >> -    return -1;
> >>
> >>     return 0;
> >>   }
> >> @@ -8359,8 +8357,6 @@ Do you want to stop the program?"),
> >>
> >>     /* In the future, maybe still need to deal with need_dasm.  */
> >>     I386_RECORD_FULL_ARCH_LIST_ADD_REG
> (X86_RECORD_REIP_REGNUM);
> >> -  if (record_full_arch_list_add_end ())
> >> -    return -1;
> >>
> >>     return 0;
> >>
> >> diff --git a/gdb/loongarch-tdep.c b/gdb/loongarch-tdep.c
> >> index 225b1abb703..5ec6b2a48d0 100644
> >> --- a/gdb/loongarch-tdep.c
> >> +++ b/gdb/loongarch-tdep.c
> >> @@ -2789,8 +2789,6 @@ loongarch_process_record (struct gdbarch
> >> *gdbarch, struct regcache *regcache,
> >>   					 LOONGARCH_PC_REGNUM))
> >>   	return -1;
> >>
> >> -      if (record_full_arch_list_add_end ())
> >> -	return -1;
> >>       }
> >>
> >>     return ret;
> >> diff --git a/gdb/moxie-tdep.c b/gdb/moxie-tdep.c
> >> index c8a04d21f59..2f8f0186488 100644
> >> --- a/gdb/moxie-tdep.c
> >> +++ b/gdb/moxie-tdep.c
> >> @@ -1040,8 +1040,6 @@ moxie_process_record (struct gdbarch
> *gdbarch,
> >> struct regcache *regcache,
> >>
> >>     if (record_full_arch_list_add_reg (regcache, MOXIE_PC_REGNUM))
> >>       return -1;
> >> -  if (record_full_arch_list_add_end ())
> >> -    return -1;
> >>     return 0;
> >>   }
> >>
> >> diff --git a/gdb/ppc-linux-tdep.c b/gdb/ppc-linux-tdep.c
> >> index 19698eaacbe..e2f53551709 100644
> >> --- a/gdb/ppc-linux-tdep.c
> >> +++ b/gdb/ppc-linux-tdep.c
> >> @@ -1565,9 +1565,6 @@ ppc_linux_record_signal (struct gdbarch
> *gdbarch,
> >> struct regcache *regcache,
> >>     if (record_full_arch_list_add_mem (sp, SIGNAL_FRAMESIZE +
> >> sizeof_rt_sigframe))
> >>       return -1;
> >>
> >> -  if (record_full_arch_list_add_end ())
> >> -    return -1;
> >> -
> >>     return 0;
> >>   }
> >>
> >> diff --git a/gdb/record-full.c b/gdb/record-full.c
> >> index 69d2c100e57..02a57726a95 100644
> >> --- a/gdb/record-full.c
> >> +++ b/gdb/record-full.c
> >> @@ -47,6 +47,9 @@
> >>   #include "interps.h"
> >>   #include "cli/cli-style.h"
> >>
> >> +#include <vector>
> >> +#include <optional>
> >> +#include <deque>
> >>   #include <signal.h>
> >>
> >>   /* This module implements "target record-full", also known as "process
> >> @@ -72,20 +75,17 @@
> >>   #define DEFAULT_RECORD_FULL_INSN_MAX_NUM	200000
> >>
> >>   #define RECORD_FULL_IS_REPLAY \
> >> -  (record_full_list->next || ::execution_direction == EXEC_REVERSE)
> >> +  ((record_full_next_insn != record_full_list.size ()) \
> >> +    || ::execution_direction == EXEC_REVERSE)
> >>
> >>   #define RECORD_FULL_FILE_MAGIC	netorder32(0x20091016)
> >>
> >>   /* These are the core structs of the process record functionality.
> >>
> >>      A record_full_entry is a record of the value change of a register
> >> -   ("record_full_reg") or a part of memory ("record_full_mem").  And each
> >> -   instruction must have a struct record_full_entry ("record_full_end")
> >> -   that indicates that this is the last struct record_full_entry of this
> >> -   instruction.
> >> -
> >> -   Each struct record_full_entry is linked to "record_full_list" by "prev"
> >> -   and "next" pointers.  */
> >> +   ("record_full_reg") or a part of memory ("record_full_mem").
> >> +   These are saved on the record_full_instruction struct, which also
> >> +   contains some extra information, such as delivered signals.  */
> > Optional suggestion (since it has been like this before already):
> >
> > The description for record_full_entry is not above the actual struct and the
> > further structs miss a comment. In the description above the enum values
> > are described '("record_full_reg") or a part of memory
> ("record_full_mem")',
> > but not the actual structs.  I think it would be helpful to write this out a bit
> more,
> > add comments to each struct and move the one for record_full_entry
> > above its actual struct.
> > Given that this commit is already big enough, a separate commit might be
> better.
> Yeah, these can probably be better documented. I'll try to think of
> something and either add a commit at the end of this series, or send a
> follow up patch later (depends on how hard it is for me to think of
> something :) )
> >
> >>   struct record_full_mem_entry
> >>   {
> >> @@ -112,47 +112,14 @@ struct record_full_reg_entry
> >>     } u;
> >>   };
> >>
> >> -struct record_full_end_entry
> >> -{
> >> -  enum gdb_signal sigval;
> >> -  ULONGEST insn_num;
> >> -};
> >> -
> >>   enum record_full_type
> >>   {
> >> -  record_full_end = 0,
> >>     record_full_reg,
> >>     record_full_mem
> >>   };
> >>
> >> -/* This is the data structure that makes up the execution log.
> >> -
> >> -   The execution log consists of a single linked list of entries
> >> -   of type "struct record_full_entry".  It is doubly linked so that it
> >> -   can be traversed in either direction.
> >> -
> >> -   The start of the list is anchored by a struct called
> >> -   "record_full_first".  The pointer "record_full_list" either points
> >> -   to the last entry that was added to the list (in record mode), or to
> >> -   the next entry in the list that will be executed (in replay mode).
> >> -
> >> -   Each list element (struct record_full_entry), in addition to next
> >> -   and prev pointers, consists of a union of three entry types: mem,
> >> -   reg, and end.  A field called "type" determines which entry type is
> >> -   represented by a given list element.
> >> -
> >> -   Each instruction that is added to the execution log is represented
> >> -   by a variable number of list elements ('entries').  The instruction
> >> -   will have one "reg" entry for each register that is changed by
> >> -   executing the instruction (including the PC in every case).  It
> >> -   will also have one "mem" entry for each memory change.  Finally,
> >> -   each instruction will have an "end" entry that separates it from
> >> -   the changes associated with the next instruction.  */
> >> -
> >>   struct record_full_entry
> >>   {
> >> -  struct record_full_entry *prev;
> >> -  struct record_full_entry *next;
> >>     enum record_full_type type;
> >>     union
> >>     {
> >> @@ -160,11 +127,31 @@ struct record_full_entry
> >>       struct record_full_reg_entry reg;
> >>       /* mem */
> >>       struct record_full_mem_entry mem;
> >> -    /* end */
> >> -    struct record_full_end_entry end;
> >>     } u;
> >>   };
> >>
> >> +/* This is the main structure that comprises the execution log.
> >> +   Each instruction is comprised of:
> >> +   * The instruction number: How many instructions were recorded before
> >> +     this one;
> >> +   * sigval: Whether the inferior received a signal while the following
> >> +     instruction was being recorded;
> >> +   * effects: A list of record_full_entry structures, each of which
> >> +     describing one effect that the instruction has on the inferior.
> >> +
> >> +   Note, the signal is stored in the previous instruction for historical
> >> +   reasons.  This is how it was first implemented, and no one has gotten
> >> +   around to changing it yet.  */
> >> +
> >> +struct record_full_instruction
> >> +{
> >> +  /* This might be different from the index if
> >> +     we had to remove the first few instructions.  */
> >> +  uint32_t insn_num;
> >> +  std::optional<gdb_signal> sigval;
> >> +  std::vector<record_full_entry> effects;
> >> +};
> >> +
> >>   /* If true, query if PREC cannot record memory
> >>      change of next instruction.  */
> >>   bool record_full_memory_query = false;
> >> @@ -181,27 +168,25 @@ static detached_regcache
> >> *record_full_core_regbuf = NULL;
> >>   static std::vector<target_section> record_full_core_sections;
> >>   static struct record_full_core_buf_entry *record_full_core_buf_list =
> NULL;
> >>
> >> -/* The following variables are used for managing the linked list that
> >> -   represents the execution log.
> >> +/* The following variables are used for managing the history of executed
> >> +   instructions from the inferior.
> >>
> >> -   record_full_first is the anchor that holds down the beginning of
> >> -   the list.
> >> +   record_full_list contains all instructions that were fully executed and
> >> +   saved to the log, so that we can replay the execution.
> >>
> >> -   record_full_list serves two functions:
> >> -     1) In record mode, it anchors the end of the list.
> >> -     2) In replay mode, it traverses the list and points to
> >> -	the next instruction that must be emulated.
> >> +   record_full_next_insn always points to the next instruction that would
> >> +   be executed if the inferior executes forward.  In the special case when
> >> +   the inferior is not replaying, record_full_next_insn points past the
> >> +   end of the history.
> >>
> >> -   record_full_arch_list_head and record_full_arch_list_tail are used
> >> -   to manage a separate list, which is used to build up the change
> >> -   elements of the currently executing instruction during record mode.
> >> -   When this instruction has been completely annotated in the "arch
> >> -   list", it will be appended to the main execution log.  */
> >> +   record_full_incomplete_instruction holds a partial instruction, while
> >> +   the lower target is disassembling the instruction, or as partial xfers are
> >> +   happening.  It is manipulated by the "arch list" functions for historical
> >> +   reasons.  */
> >>
> >> -static struct record_full_entry record_full_first;
> >> -static struct record_full_entry *record_full_list = &record_full_first;
> >> -static struct record_full_entry *record_full_arch_list_head = NULL;
> >> -static struct record_full_entry *record_full_arch_list_tail = NULL;
> >> +static std::deque<record_full_instruction> record_full_list;
> >> +static record_full_instruction record_full_incomplete_instruction;
> >> +static int record_full_next_insn;
> >>
> >>   /* true ask user. false auto delete the last struct record_full_entry.  */
> >>   static bool record_full_stop_at_limit = true;
> >> @@ -361,6 +346,14 @@ record_full_target::kill ()
> >>     record_kill (this);
> >>   }
> > This  function is still missing a comment.
> oops, fixed.
> >> +static void
> >> +record_full_reset_incomplete ()
> >> +{
> >> +  record_full_incomplete_instruction.effects.clear ();
> >> +  record_full_incomplete_instruction.sigval.reset ();
> >> +  record_full_incomplete_instruction.insn_num = 0;
> >> +}
> >> +
> >>   /* See record-full.h.  */
> >>
> >>   int
> >> @@ -390,156 +383,124 @@ static struct cmd_list_element
> >> *show_record_full_cmdlist;
> >>   /* Command list for "record full".  */
> >>   static struct cmd_list_element *record_full_cmdlist;
> >>
> >> -static void record_full_goto_insn (struct record_full_entry *entry,
> >> +static void record_full_goto_insn (size_t target_insn,
> >>   				   enum exec_direction_kind dir);
> > Is this some general guideline to call this cleanup/init instead of
> release/alloc ?
> > Since this patch is already so big, I just wonder why you changed it. 😊
> 
> There isn't any guideline, I just figured that alloc/release was no
> longer correct, since we were no longer allocating or releasing dynamic
> memory... This was before I realized how far I could take the
> C++ification and make them into proper constructors.
> 
> I can undo the change if you'd like, but since they are going to become
> the constructor I feel like it is not really necessary either way...

No, I really don't insist on that... 

> >> -/* Alloc and free functions for record_full_reg, record_full_mem, and
> >> -   record_full_end entries.  */
> >> +/* Initialization and cleanup functions for record_full_reg and
> >> +   record_full_mem entries.  */
> >>
> >> -/* Alloc a record_full_reg record entry.  */
> >> +/* Init a record_full_reg record entry.  */
> >>
> >> -static inline struct record_full_entry *
> >> -record_full_reg_alloc (struct regcache *regcache, int regnum)
> >> +static inline record_full_entry
> >> +record_full_reg_init (struct regcache *regcache, int regnum)
> >>   {
> >> -  struct record_full_entry *rec;
> >> +  record_full_entry rec;
> >>     struct gdbarch *gdbarch = regcache->arch ();
> >>
> >> -  rec = XCNEW (struct record_full_entry);
> >> -  rec->type = record_full_reg;
> >> -  rec->u.reg.num = regnum;
> >> -  rec->u.reg.len = register_size (gdbarch, regnum);
> >> -  if (rec->u.reg.len > sizeof (rec->u.reg.u.buf))
> >> -    rec->u.reg.u.ptr = (gdb_byte *) xmalloc (rec->u.reg.len);
> >> +  rec.type = record_full_reg;
> >> +  rec.u.reg.num = regnum;
> >> +  rec.u.reg.len = register_size (gdbarch, regnum);
> >> +  if (rec.u.reg.len > sizeof (rec.u.reg.u.buf))
> >> +    rec.u.reg.u.ptr = (gdb_byte *) xmalloc (rec.u.reg.len);
> >>
> >>     return rec;
> >>   }
> >>
> >> -/* Free a record_full_reg record entry.  */
> >> +/* Cleanup a record_full_reg record entry.  */
> >>
> >>   static inline void
> >> -record_full_reg_release (struct record_full_entry *rec)
> >> +record_full_reg_cleanup (record_full_entry rec)
> >>   {
> >> -  gdb_assert (rec->type == record_full_reg);
> >> -  if (rec->u.reg.len > sizeof (rec->u.reg.u.buf))
> >> -    xfree (rec->u.reg.u.ptr);
> >> -  xfree (rec);
> >> +  gdb_assert (rec.type == record_full_reg);
> >> +  if (rec.u.reg.len > sizeof (rec.u.reg.u.buf))
> >> +    xfree (rec.u.reg.u.ptr);
> >>   }
> >>
> >> -/* Alloc a record_full_mem record entry.  */
> >> +/* Init a record_full_mem record entry.  */
> >>
> >> -static inline struct record_full_entry *
> >> -record_full_mem_alloc (CORE_ADDR addr, int len)
> >> +static inline record_full_entry
> >> +record_full_mem_init (CORE_ADDR addr, int len)
> >>   {
> >> -  struct record_full_entry *rec;
> >> +  record_full_entry rec;
> >>
> >> -  rec = XCNEW (struct record_full_entry);
> >> -  rec->type = record_full_mem;
> >> -  rec->u.mem.addr = addr;
> >> -  rec->u.mem.len = len;
> >> -  if (rec->u.mem.len > sizeof (rec->u.mem.u.buf))
> >> -    rec->u.mem.u.ptr = (gdb_byte *) xmalloc (len);
> >> +  rec.type = record_full_mem;
> >> +  rec.u.mem.addr = addr;
> >> +  rec.u.mem.len = len;
> >> +  if (rec.u.mem.len > sizeof (rec.u.mem.u.buf))
> >> +    rec.u.mem.u.ptr = (gdb_byte *) xmalloc (len);
> >> +  rec.u.mem.mem_entry_not_accessible = 0;
> >>
> >>     return rec;
> >>   }
> >>
> >> -/* Free a record_full_mem record entry.  */
> >> +/* Cleanup a record_full_mem record entry.  */
> >>
> >>   static inline void
> >> -record_full_mem_release (struct record_full_entry *rec)
> >> +record_full_mem_cleanup (record_full_entry rec)
> >>   {
> >> -  gdb_assert (rec->type == record_full_mem);
> >> -  if (rec->u.mem.len > sizeof (rec->u.mem.u.buf))
> >> -    xfree (rec->u.mem.u.ptr);
> >> -  xfree (rec);
> >> +  gdb_assert (rec.type == record_full_mem);
> >> +  if (rec.u.mem.len > sizeof (rec.u.mem.u.buf))
> >> +    xfree (rec.u.mem.u.ptr);
> >>   }
> >>
> >> -/* Alloc a record_full_end record entry.  */
> >> -
> >> -static inline struct record_full_entry *
> >> -record_full_end_alloc (void)
> >> -{
> >> -  struct record_full_entry *rec;
> >> -
> >> -  rec = XCNEW (struct record_full_entry);
> >> -  rec->type = record_full_end;
> >> -
> >> -  return rec;
> >> -}
> >> -
> >> -/* Free a record_full_end record entry.  */
> >> +/* Free one record entry, any type.  */
> >>
> >>   static inline void
> >> -record_full_end_release (struct record_full_entry *rec)
> >> +record_full_entry_cleanup (record_full_entry rec)
> >>   {
> >> -  xfree (rec);
> >> -}
> >> -
> >> -/* Free one record entry, any type.
> >> -   Return entry->type, in case caller wants to know.  */
> >>
> >> -static inline enum record_full_type
> >> -record_full_entry_release (struct record_full_entry *rec)
> >> -{
> >> -  enum record_full_type type = rec->type;
> >> -
> >> -  switch (type) {
> >> +  switch (rec.type) {
> >>     case record_full_reg:
> >> -    record_full_reg_release (rec);
> >> +    record_full_reg_cleanup (rec);
> >>       break;
> >>     case record_full_mem:
> >> -    record_full_mem_release (rec);
> >> -    break;
> >> -  case record_full_end:
> >> -    record_full_end_release (rec);
> >> +    record_full_mem_cleanup (rec);
> >>       break;
> >>     }
> >> -  return type;
> >>   }
> >>
> >> -/* Free all record entries in list pointed to by REC.  */
> >> -
> >>   static void
> >> -record_full_list_release (struct record_full_entry *rec)
> >> +record_full_reset_history ()
> >>   {
> >> -  if (!rec)
> >> -    return;
> >> -
> >> -  while (rec->next)
> >> -    rec = rec->next;
> >> +  record_full_insn_num = 0;
> >> +  record_full_insn_count = 0;
> >> +  record_full_next_insn = 0;
> >>
> >> -  while (rec->prev)
> >> +  for (auto &insn : record_full_list)
> >>       {
> >> -      rec = rec->prev;
> >> -      record_full_entry_release (rec->next);
> >> +      for (auto &entry : insn.effects)
> >> +	record_full_entry_cleanup (entry);
> >>       }
> >>
> >> -  if (rec == &record_full_first)
> >> +  record_full_list.clear ();
> >> +}
> > This function is missing a comment.
> oops, fixed
> >
> >> +static void
> >> +record_full_list_release_following (int index)
> >> +{
> >> +  for (int i = record_full_list.size () - 1; i > index; i--)
> >>       {
> >> -      record_full_insn_num = 0;
> >> -      record_full_first.next = NULL;
> >> +      for (auto &entry : record_full_list[i].effects)
> >> +	record_full_entry_cleanup (entry);
> >> +      record_full_list.pop_back ();
> >>       }
> >> -  else
> >> -    record_full_entry_release (rec);
> >> +  /* Set the next instruction to be past the end of the log so we
> >> +     start recording if the user moves forward again.  */
> >> +  record_full_next_insn = index;
> >>   }
> >>
> >> -/* Free all record entries forward of the given list position.  */
> >> +/* Save the incomplete instruction in the log.  */
> >>
> >>   static void
> >> -record_full_list_release_following (struct record_full_entry *rec)
> >> +record_full_save_instruction ()
> >>   {
> >> -  struct record_full_entry *tmp = rec->next;
> >> +  ++record_full_insn_count;
> >> +  record_full_incomplete_instruction.insn_num = record_full_insn_count;
> >> +  record_full_incomplete_instruction.effects.shrink_to_fit ();
> >> +  record_full_list.push_back (std::move
> >> (record_full_incomplete_instruction));
> >> +  record_full_next_insn++;
> >>
> >> -  rec->next = NULL;
> >> -  while (tmp)
> >> -    {
> >> -      rec = tmp->next;
> >> -      if (record_full_entry_release (tmp) == record_full_end)
> >> -	{
> >> -	  record_full_insn_num--;
> >> -	  record_full_insn_count--;
> >> -	}
> >> -      tmp = rec;
> >> -    }
> >> +  record_full_reset_incomplete ();
> >>   }
> >>
> >>   /* Delete the first instruction from the beginning of the log, to make
> >> @@ -550,52 +511,22 @@ record_full_list_release_following (struct
> >> record_full_entry *rec)
> >>   static void
> >>   record_full_list_release_first (void)
> >>   {
> >> -  struct record_full_entry *tmp;
> >> -
> >> -  if (!record_full_first.next)
> >> +  if (record_full_list.empty ())
> >>       return;
> >>
> >> -  /* Loop until a record_full_end.  */
> >> -  while (1)
> >> -    {
> >> -      /* Cut record_full_first.next out of the linked list.  */
> >> -      tmp = record_full_first.next;
> >> -      record_full_first.next = tmp->next;
> >> -      tmp->next->prev = &record_full_first;
> >> -
> >> -      /* tmp is now isolated, and can be deleted.  */
> >> -      if (record_full_entry_release (tmp) == record_full_end)
> >> -	break;	/* End loop at first record_full_end.  */
> >> +  for (auto &entry : record_full_list[0].effects)
> >> +    record_full_entry_cleanup (entry);
> >>
> >> -      if (!record_full_first.next)
> >> -	{
> >> -	  gdb_assert (record_full_insn_num == 1);
> >> -	  break;	/* End loop when list is empty.  */
> >> -	}
> >> -    }
> >> +  record_full_list.pop_front ();
> >> +  --record_full_next_insn;
> >>   }
> >>
> >>   /* Add a struct record_full_entry to record_full_arch_list.  */
> >>
> >>   static void
> >> -record_full_arch_list_add (struct record_full_entry *rec)
> >> +record_full_arch_list_add (record_full_entry &rec)
> >>   {
> >> -  if (record_debug > 1)
> >> -    gdb_printf (gdb_stdlog,
> >> -		"Process record: record_full_arch_list_add %s.\n",
> >> -		host_address_to_string (rec));
> >> -
> >> -  if (record_full_arch_list_tail)
> >> -    {
> >> -      record_full_arch_list_tail->next = rec;
> >> -      rec->prev = record_full_arch_list_tail;
> >> -      record_full_arch_list_tail = rec;
> >> -    }
> >> -  else
> >> -    {
> >> -      record_full_arch_list_head = rec;
> >> -      record_full_arch_list_tail = rec;
> >> -    }
> >> +  record_full_incomplete_instruction.effects.push_back (rec);
> > Now since this is one line only, do we really need a separate function for
> this?
> I guess not, but I think at the same time it would make the patch that
> much harder to review because the fact that this being how you add to
> the incomplete list is not obvious...

I still may not have looked deeply enough into this patch yet, but it’s even
more confusing to me with this additional function.
In any case, this if of course up to you. 😊

> >
> >>   }
> >>
> >>   /* Return the value storage location of a record entry.  */
> >> @@ -613,7 +544,6 @@ record_full_get_loc (struct record_full_entry *rec)
> >>         return rec->u.reg.u.ptr;
> >>       else
> >>         return rec->u.reg.u.buf;
> >> -  case record_full_end:
> >>     default:
> >>       gdb_assert_not_reached ("unexpected record_full_entry type");
> >>       return NULL;
> >> @@ -625,7 +555,7 @@ record_full_get_loc (struct record_full_entry *rec)
> >>   int
> >>   record_full_arch_list_add_reg (struct regcache *regcache, int regnum)
> >>   {
> >> -  struct record_full_entry *rec;
> >> +  record_full_entry rec;
> >>
> >>     if (record_debug > 1)
> >>       gdb_printf (gdb_stdlog,
> >> @@ -633,9 +563,9 @@ record_full_arch_list_add_reg (struct regcache
> >> *regcache, int regnum)
> >>   		"record list.\n",
> >>   		regnum);
> >>
> >> -  rec = record_full_reg_alloc (regcache, regnum);
> >> +  rec = record_full_reg_init (regcache, regnum);
> >>
> >> -  regcache->cooked_read (regnum, record_full_get_loc (rec));
> >> +  regcache->cooked_read (regnum, record_full_get_loc (&rec));
> >>
> >>     record_full_arch_list_add (rec);
> >>
> >> @@ -648,7 +578,7 @@ record_full_arch_list_add_reg (struct regcache
> >> *regcache, int regnum)
> >>   int
> >>   record_full_arch_list_add_mem (CORE_ADDR addr, int len)
> >>   {
> >> -  struct record_full_entry *rec;
> >> +  record_full_entry rec;
> >>
> >>     if (record_debug > 1)
> >>       gdb_printf (gdb_stdlog,
> >> @@ -659,12 +589,12 @@ record_full_arch_list_add_mem (CORE_ADDR
> >> addr, int len)
> >>     if (!addr)	/* FIXME: Why?  Some arch must permit it...  */
> >>       return 0;
> >>
> >> -  rec = record_full_mem_alloc (addr, len);
> >> +  rec = record_full_mem_init (addr, len);
> >>
> >>     if (record_read_memory (current_inferior ()->arch (), addr,
> >> -			  record_full_get_loc (rec), len))
> >> +			  record_full_get_loc (&rec), len))
> >>       {
> >> -      record_full_mem_release (rec);
> >> +      record_full_mem_cleanup (rec);
> >>         return -1;
> >>       }
> >>
> >> @@ -673,27 +603,6 @@ record_full_arch_list_add_mem (CORE_ADDR
> addr,
> >> int len)
> >>     return 0;
> >>   }
> >>
> >> -/* Add a record_full_end type struct record_full_entry to
> >> -   record_full_arch_list.  */
> >> -
> >> -int
> >> -record_full_arch_list_add_end (void)
> >> -{
> >> -  struct record_full_entry *rec;
> >> -
> >> -  if (record_debug > 1)
> >> -    gdb_printf (gdb_stdlog,
> >> -		"Process record: add end to arch list.\n");
> >> -
> >> -  rec = record_full_end_alloc ();
> >> -  rec->u.end.sigval = GDB_SIGNAL_0;
> >> -  rec->u.end.insn_num = ++record_full_insn_count;
> >> -
> >> -  record_full_arch_list_add (rec);
> >> -
> >> -  return 0;
> >> -}
> >> -
> >>   static void
> >>   record_full_check_insn_num (void)
> >>   {
> >> @@ -725,8 +634,7 @@ record_full_message (struct regcache *regcache,
> >> enum gdb_signal signal)
> >>
> >>     try
> >>       {
> >> -      record_full_arch_list_head = NULL;
> >> -      record_full_arch_list_tail = NULL;
> >> +      record_full_reset_incomplete ();
> >>
> >>         /* Check record_full_insn_num.  */
> >>         record_full_check_insn_num ();
> >> @@ -746,20 +654,16 @@ record_full_message (struct regcache
> *regcache,
> >> enum gdb_signal signal)
> >>   	 the user says something different, like "deliver this signal"
> >>   	 during the replay mode).
> >>
> >> -	 User should understand that nothing he does during the replay
> >> -	 mode will change the behavior of the child.  If he tries,
> >> +	 User should understand that nothing they do during the replay
> >> +	 mode will change the behavior of the child.  If they try,
> >>   	 then that is a user error.
> >>
> >>   	 But we should still deliver the signal to gdb during the replay,
> >>   	 if we delivered it during the recording.  Therefore we should
> >>   	 record the signal during record_full_wait, not
> >>   	 record_full_resume.  */
> >> -      if (record_full_list != &record_full_first)  /* FIXME better way
> >> -						      to check */
> >> -	{
> >> -	  gdb_assert (record_full_list->type == record_full_end);
> >> -	  record_full_list->u.end.sigval = signal;
> >> -	}
> >> +      if (signal != GDB_SIGNAL_0 && !record_full_list.empty ())
> >> +	record_full_list[record_full_next_insn - 1].sigval = signal;
> >>
> >>         if (signal == GDB_SIGNAL_0
> >>   	  || !gdbarch_process_record_signal_p (gdbarch))
> >> @@ -778,13 +682,11 @@ record_full_message (struct regcache
> *regcache,
> >> enum gdb_signal signal)
> >>       }
> >>     catch (const gdb_exception &ex)
> >>       {
> >> -      record_full_list_release (record_full_arch_list_tail);
> >> +      record_full_reset_incomplete ();
> >>         throw;
> >>       }
> >>
> >> -  record_full_list->next = record_full_arch_list_head;
> >> -  record_full_arch_list_head->prev = record_full_list;
> >> -  record_full_list = record_full_arch_list_tail;
> >> +  record_full_save_instruction ();
> >>
> >>     if (record_full_insn_num == record_full_insn_max_num)
> >>       record_full_list_release_first ();
> >> @@ -829,9 +731,9 @@ static enum target_stop_reason
> >> record_full_stop_reason
> >>      entries and memory entries, followed by an 'end' entry.  */
> >>
> >>   static inline void
> >> -record_full_exec_insn (struct regcache *regcache,
> >> -		       struct gdbarch *gdbarch,
> >> -		       struct record_full_entry *entry)
> >> +record_full_exec_entry (regcache *regcache,
> >> +			gdbarch *gdbarch,
> >> +			record_full_entry *entry)
> >>   {
> >>     switch (entry->type)
> >>       {
> >> @@ -909,6 +811,15 @@ record_full_exec_insn (struct regcache
> *regcache,
> >>       }
> >>   }
> > Again function comment.. 😉
> 
> Darn I'm really bad at these lol
> 
> Fixed :)
> 
> >
> >> +static inline void
> >> +record_full_exec_insn (regcache *regcache,
> >> +		       gdbarch *gdbarch,
> >> +		       record_full_instruction &insn)
> >> +{
> >> +  for (auto &entry : insn.effects)
> >> +    record_full_exec_entry (regcache, gdbarch, &entry);
> >> +}
> >> +
> >>   static void record_full_restore (struct bfd &cbfd);
> >>
> >>   /* Asynchronous signal handle registered as event loop source for when
> >> @@ -983,10 +894,7 @@ record_full_open (const char *args, int from_tty)
> >>     record_preopen ();
> >>
> >>     /* Reset */
> >> -  record_full_insn_num = 0;
> >> -  record_full_insn_count = 0;
> >> -  record_full_list = &record_full_first;
> >> -  record_full_list->next = NULL;
> >> +  record_full_reset_history ();
> >>
> >>     bfd *cbfd = get_inferior_core_bfd (current_inferior ());
> >>     if (cbfd != nullptr)
> >> @@ -1014,7 +922,7 @@ record_full_base_target::close ()
> >>     if (record_debug)
> >>       gdb_printf (gdb_stdlog, "Process record: record_full_close\n");
> >>
> >> -  record_full_list_release (record_full_list);
> >> +  record_full_reset_history ();
> >>
> >>     /* Release record_full_core_regbuf.  */
> >>     if (record_full_core_regbuf)
> >> @@ -1313,7 +1221,6 @@ record_full_wait_1 (struct target_ops *ops,
> >>         struct gdbarch *gdbarch = regcache->arch ();
> >>         const address_space *aspace = current_inferior ()->aspace.get ();
> >>         int continue_flag = 1;
> >> -      int first_record_full_end = 1;
> >>
> >>         try
> >>   	{
> >> @@ -1322,7 +1229,6 @@ record_full_wait_1 (struct target_ops *ops,
> >>   	  record_full_stop_reason = TARGET_STOPPED_BY_NO_REASON;
> >>   	  status->set_stopped (GDB_SIGNAL_0);
> >>
> >> -	  /* Check breakpoint when forward execute.  */
> >>   	  if (execution_direction == EXEC_FORWARD)
> >>   	    {
> >>   	      tmp_pc = regcache_read_pc (regcache);
> >> @@ -1330,8 +1236,7 @@ record_full_wait_1 (struct target_ops *ops,
> >>
> >> &record_full_stop_reason))
> >>   		{
> >>   		  if (record_debug)
> >> -		    gdb_printf (gdb_stdlog,
> >> -				"Process record: break at %s.\n",
> >> +		    gdb_printf (gdb_stdlog, "Process record: break at %s.\n",
> >>   				paddress (gdbarch, tmp_pc));
> >>   		  goto replay_out;
> >>   		}
> >> @@ -1344,10 +1249,10 @@ record_full_wait_1 (struct target_ops *ops,
> >>   	     the signal.  */
> >>   	  target_terminal::ours ();
> >>
> >> -	  /* In EXEC_FORWARD mode, record_full_list points to the tail of
> >> prev
> >> -	     instruction.  */
> >> -	  if (execution_direction == EXEC_FORWARD && record_full_list-
> >>> next)
> >> -	    record_full_list = record_full_list->next;
> >> +	  /* In EXEC_FORWARD mode, record_full_next_insn is the next
> >> +	     instruction to be executed.  */
> >> +	  if (execution_direction == EXEC_REVERSE)
> >> +	    record_full_next_insn--;
> >>
> >>   	  /* Loop over the record_full_list, looking for the next place to
> >>   	     stop.  */
> >> @@ -1355,108 +1260,92 @@ record_full_wait_1 (struct target_ops *ops,
> >>   	    {
> >>   	      /* Check for beginning and end of log.  */
> >>   	      if (execution_direction == EXEC_REVERSE
> >> -		  && record_full_list == &record_full_first)
> >> +		  && record_full_next_insn < 0)
> >>   		{
> >>   		  /* Hit beginning of record log in reverse.  */
> >>   		  status->set_no_history ();
> >> +		  record_full_next_insn = 0;
> >>   		  break;
> >>   		}
> >>   	      if (execution_direction != EXEC_REVERSE
> >> -		  && !record_full_list->next)
> >> +		  && record_full_next_insn == record_full_list.size ())
> >>   		{
> >>   		  /* Hit end of record log going forward.  */
> >>   		  status->set_no_history ();
> >>   		  break;
> >>   		}
> >>
> >> -	      record_full_exec_insn (regcache, gdbarch, record_full_list);
> >> +	      record_full_exec_insn
> >> +		(regcache, gdbarch,
> >> +		 record_full_list[record_full_next_insn]);
> >>
> >> -	      if (record_full_list->type == record_full_end)
> >> +	      /* step */
> >> +	      if (record_full_resume_step)
> >>   		{
> >>   		  if (record_debug > 1)
> >> -		    gdb_printf
> >> -		      (gdb_stdlog,
> >> -		       "Process record: record_full_end %s to "
> >> -		       "inferior.\n",
> >> -		       host_address_to_string (record_full_list));
> >> -
> >> -		  if (first_record_full_end
> >> -		      && execution_direction == EXEC_REVERSE)
> >> -		    {
> >> -		      /* When reverse execute, the first
> >> -			 record_full_end is the part of current
> >> -			 instruction.  */
> >> -		      first_record_full_end = 0;
> >> -		    }
> >> -		  else
> >> -		    {
> >> -		      /* In EXEC_REVERSE mode, this is the
> >> -			 record_full_end of prev instruction.  In
> >> -			 EXEC_FORWARD mode, this is the
> >> -			 record_full_end of current instruction.  */
> >> -		      /* step */
> >> -		      if (record_full_resume_step)
> >> -			{
> >> -			  if (record_debug > 1)
> >> -			    gdb_printf (gdb_stdlog,
> >> -					"Process record: step.\n");
> >> -			  continue_flag = 0;
> >> -			}
> >> -
> >> -		      /* check breakpoint */
> >> -		      tmp_pc = regcache_read_pc (regcache);
> >> -		      if (record_check_stopped_by_breakpoint
> >> -			  (aspace, tmp_pc, &record_full_stop_reason))
> >> -			{
> >> -			  if (record_debug)
> >> -			    gdb_printf (gdb_stdlog,
> >> -					"Process record: break "
> >> -					"at %s.\n",
> >> -					paddress (gdbarch, tmp_pc));
> >> +		    gdb_printf (gdb_stdlog,
> >> +				"Process record: step.\n");
> >> +		  continue_flag = 0;
> >> +		}
> >>
> >> -			  continue_flag = 0;
> >> -			}
> >> +	      /* check breakpoint */
> >> +	      tmp_pc = regcache_read_pc (regcache);
> >> +	      if (record_check_stopped_by_breakpoint
> >> +		  (aspace, tmp_pc, &record_full_stop_reason))
> >> +		{
> >> +		  if (record_debug)
> >> +		    gdb_printf (gdb_stdlog,
> >> +				"Process record: break "
> >> +				"at %s.\n",
> >> +				paddress (gdbarch, tmp_pc));
> >>
> >> -		      if (record_full_stop_reason
> >> -			  == TARGET_STOPPED_BY_WATCHPOINT)
> >> -			{
> >> -			  if (record_debug)
> >> -			    gdb_printf (gdb_stdlog,
> >> -					"Process record: hit hw "
> >> -					"watchpoint.\n");
> >> -			  continue_flag = 0;
> >> -			}
> >> -		      /* Check target signal */
> >> -		      if (record_full_list->u.end.sigval != GDB_SIGNAL_0)
> >> -			/* FIXME: better way to check */
> >> -			continue_flag = 0;
> >> -		    }
> >> +		  continue_flag = 0;
> >>   		}
> >>
> >> -	      if (continue_flag)
> >> +	      if (record_full_stop_reason
> >> +		  == TARGET_STOPPED_BY_WATCHPOINT)
> >>   		{
> >> -		  if (execution_direction == EXEC_REVERSE)
> >> -		    {
> >> -		      if (record_full_list->prev)
> >> -			record_full_list = record_full_list->prev;
> >> -		    }
> >> -		  else
> >> -		    {
> >> -		      if (record_full_list->next)
> >> -			record_full_list = record_full_list->next;
> >> -		    }
> >> +		  if (record_debug)
> >> +		    gdb_printf (gdb_stdlog,
> >> +				"Process record: hit hw "
> >> +				"watchpoint.\n");
> >> +		  continue_flag = 0;
> >>   		}
> >> +	      if (record_full_list[record_full_next_insn].sigval.has_value ())
> >> +		continue_flag = 0;
> >> +
> >> +	      if (execution_direction == EXEC_REVERSE)
> >> +		record_full_next_insn--;
> >> +	      else
> >> +		record_full_next_insn++;
> >>   	    }
> >>   	  while (continue_flag);
> >>
> >> +	  if (record_full_next_insn < 0)
> >> +	    {
> >> +	      gdb_assert (execution_direction == EXEC_REVERSE);
> >> +	      record_full_next_insn = 0;
> >> +	    }
> >> +	  else if (record_full_next_insn > record_full_list.size ())
> >> +	    {
> >> +	      gdb_assert (execution_direction == EXEC_FORWARD);
> >> +	      record_full_next_insn = record_full_list.size ();
> >> +	    }
> >> +	  /* Reset the current instruction to point to the one to be replayed
> >> +	     moving forward.  */
> >> +	  else if (execution_direction == EXEC_REVERSE)
> >> +	    record_full_next_insn++;
> >> +
> >>   	replay_out:
> >>   	  if (status->kind () == TARGET_WAITKIND_STOPPED)
> >>   	    {
> >> +	      int insn = (execution_direction == EXEC_FORWARD)
> >> +			 ? record_full_next_insn - 1 : record_full_next_insn;
> >>   	      if (record_full_get_sig)
> >>   		status->set_stopped (GDB_SIGNAL_INT);
> >> -	      else if (record_full_list->u.end.sigval != GDB_SIGNAL_0)
> >> -		/* FIXME: better way to check */
> >> -		status->set_stopped (record_full_list->u.end.sigval);
> >> +	      else if (record_full_list[insn].sigval.has_value ())
> >> +		status->set_stopped
> >> +		  (record_full_list[insn].sigval.value ());
> >>   	      else
> >>   		status->set_stopped (GDB_SIGNAL_TRAP);
> >>   	    }
> >> @@ -1464,12 +1353,9 @@ record_full_wait_1 (struct target_ops *ops,
> >>         catch (const gdb_exception &ex)
> >>   	{
> >>   	  if (execution_direction == EXEC_REVERSE)
> >> -	    {
> >> -	      if (record_full_list->next)
> >> -		record_full_list = record_full_list->next;
> >> -	    }
> >> +	    record_full_next_insn++;
> >>   	  else
> >> -	    record_full_list = record_full_list->prev;
> >> +	    record_full_next_insn--;
> >>
> >>   	  throw;
> >>   	}
> >> @@ -1557,8 +1443,7 @@ record_full_registers_change (struct regcache
> >> *regcache, int regnum)
> >>     /* Check record_full_insn_num.  */
> >>     record_full_check_insn_num ();
> >>
> >> -  record_full_arch_list_head = NULL;
> >> -  record_full_arch_list_tail = NULL;
> >> +  record_full_reset_incomplete ();
> >>
> >>     if (regnum < 0)
> >>       {
> >> @@ -1568,7 +1453,7 @@ record_full_registers_change (struct regcache
> >> *regcache, int regnum)
> >>   	{
> >>   	  if (record_full_arch_list_add_reg (regcache, i))
> >>   	    {
> >> -	      record_full_list_release (record_full_arch_list_tail);
> >> +	      record_full_reset_incomplete ();
> >>   	      error (_("Process record: failed to record execution log."));
> >>   	    }
> >>   	}
> >> @@ -1577,18 +1462,11 @@ record_full_registers_change (struct
> regcache
> >> *regcache, int regnum)
> >>       {
> >>         if (record_full_arch_list_add_reg (regcache, regnum))
> >>   	{
> >> -	  record_full_list_release (record_full_arch_list_tail);
> >> +	  record_full_reset_incomplete ();
> >>   	  error (_("Process record: failed to record execution log."));
> >>   	}
> >>       }
> >> -  if (record_full_arch_list_add_end ())
> >> -    {
> >> -      record_full_list_release (record_full_arch_list_tail);
> >> -      error (_("Process record: failed to record execution log."));
> >> -    }
> >> -  record_full_list->next = record_full_arch_list_head;
> >> -  record_full_arch_list_head->prev = record_full_list;
> >> -  record_full_list = record_full_arch_list_tail;
> >> +  record_full_save_instruction ();
> >>
> >>     if (record_full_insn_num == record_full_insn_max_num)
> >>       record_full_list_release_first ();
> >> @@ -1607,7 +1485,7 @@ record_full_target::store_registers (struct
> >> regcache *regcache, int regno)
> >>   	{
> >>   	  int n;
> >>
> >> -	  /* Let user choose if he wants to write register or not.  */
> >> +	  /* Let user choose if they want to write register or not.  */
> > It might be cleaner to move such changes in a separate commit,
> > since they just make this big patch even bigger.
> Considering it is just a tiny change to a comment, I don't  think it
> makes that much of a difference, really.
> >
> >>   	  if (regno < 0)
> >>   	    n =
> >>   	      query (_("Because GDB is in replay mode, changing the "
> >> @@ -1642,7 +1520,7 @@ record_full_target::store_registers (struct
> >> regcache *regcache, int regno)
> >>   	    }
> >>
> >>   	  /* Destroy the record from here forward.  */
> >> -	  record_full_list_release_following (record_full_list);
> >> +	  record_full_list_release_following (record_full_next_insn);
> >>   	}
> >>
> >>         record_full_registers_change (regcache, regno);
> >> @@ -1675,36 +1553,24 @@ record_full_target::xfer_partial (enum
> >> target_object object,
> >>   	    error (_("Process record canceled the operation."));
> >>
> >>   	  /* Destroy the record from here forward.  */
> >> -	  record_full_list_release_following (record_full_list);
> >> +	  record_full_list_release_following (record_full_next_insn);
> >>   	}
> >>
> >>         /* Check record_full_insn_num */
> >>         record_full_check_insn_num ();
> >>
> >>         /* Record registers change to list as an instruction.  */
> >> -      record_full_arch_list_head = NULL;
> >> -      record_full_arch_list_tail = NULL;
> >> +      record_full_reset_incomplete ();
> >>         if (record_full_arch_list_add_mem (offset, len))
> >>   	{
> >> -	  record_full_list_release (record_full_arch_list_tail);
> >> -	  if (record_debug)
> >> -	    gdb_printf (gdb_stdlog,
> >> -			"Process record: failed to record "
> >> -			"execution log.");
> >> -	  return TARGET_XFER_E_IO;
> >> -	}
> >> -      if (record_full_arch_list_add_end ())
> >> -	{
> >> -	  record_full_list_release (record_full_arch_list_tail);
> >> +	  record_full_reset_incomplete ();
> >>   	  if (record_debug)
> >>   	    gdb_printf (gdb_stdlog,
> >>   			"Process record: failed to record "
> >>   			"execution log.");
> >>   	  return TARGET_XFER_E_IO;
> >>   	}
> >> -      record_full_list->next = record_full_arch_list_head;
> >> -      record_full_arch_list_head->prev = record_full_list;
> >> -      record_full_list = record_full_arch_list_tail;
> >> +      record_full_save_instruction ();
> >>
> >>         if (record_full_insn_num == record_full_insn_max_num)
> >>   	record_full_list_release_first ();
> >> @@ -1865,9 +1731,11 @@ record_full_base_target::get_bookmark
> (const
> >> char *args, int from_tty)
> >>   {
> >>     char *ret = NULL;
> >>
> >> +  if (record_full_list.empty ())
> >> +    return (gdb_byte *) ret;
> >> +
> >>     /* Return stringified form of instruction count.  */
> >> -  if (record_full_list && record_full_list->type == record_full_end)
> >> -    ret = xstrdup (pulongest (record_full_list->u.end.insn_num));
> >> +  ret = xstrdup (pulongest
> >> (record_full_list[record_full_next_insn].insn_num));
> >>
> >>     if (record_debug)
> >>       {
> >> @@ -1923,30 +1791,22 @@ record_full_base_target::record_method
> >> (ptid_t ptid)
> >>   void
> >>   record_full_base_target::info_record ()
> >>   {
> >> -  struct record_full_entry *p;
> >> -
> >>     if (RECORD_FULL_IS_REPLAY)
> >>       gdb_printf (_("Replay mode:\n"));
> >>     else
> >>       gdb_printf (_("Record mode:\n"));
> >>
> >> -  /* Find entry for first actual instruction in the log.  */
> >> -  for (p = record_full_first.next;
> >> -       p != NULL && p->type != record_full_end;
> >> -       p = p->next)
> >> -    ;
> >> -
> >>     /* Do we have a log at all?  */
> >> -  if (p != NULL && p->type == record_full_end)
> >> +  if (!record_full_list.empty ())
> >>       {
> >>         /* Display instruction number for first instruction in the log.  */
> >> -      gdb_printf (_("Lowest recorded instruction number is %s.\n"),
> >> -		  pulongest (p->u.end.insn_num));
> >> +      gdb_printf (_("Lowest recorded instruction number is %u.\n"),
> >> +		  record_full_list[0].insn_num);
> >>
> >>         /* If in replay mode, display where we are in the log.  */
> >>         if (RECORD_FULL_IS_REPLAY)
> >> -	gdb_printf (_("Current instruction number is %s.\n"),
> >> -		    pulongest (record_full_list->u.end.insn_num));
> >> +	gdb_printf (_("Current instruction number is %u.\n"),
> >> +		    record_full_list[record_full_next_insn].insn_num);
> >>
> >>         /* Display instruction number for last instruction in the log.  */
> >>         gdb_printf (_("Highest recorded instruction number is %s.\n"),
> >> @@ -1975,7 +1835,7 @@
> record_full_base_target::supports_delete_record
> >> ()
> >>   void
> >>   record_full_base_target::delete_record ()
> >>   {
> >> -  record_full_list_release_following (record_full_list);
> >> +  record_full_reset_history ();
> >>   }
> >>
> >>   /* The "record_is_replaying" target method.  */
> >> @@ -2001,23 +1861,23 @@ record_full_base_target::record_will_replay
> >> (ptid_t ptid, int dir)
> >>   /* Go to a specific entry.  */
> >>
> >>   static void
> >> -record_full_goto_entry (struct record_full_entry *p)
> >> +record_full_goto_entry (size_t target_insn)
> >>   {
> >> -  if (p == NULL)
> >> +  if (target_insn >= record_full_list.size ())
> >>       error (_("Target insn not found."));
> >> -  else if (p == record_full_list)
> >> +  else if (target_insn == record_full_next_insn)
> >>       error (_("Already at target insn."));
> >> -  else if (p->u.end.insn_num > record_full_list->u.end.insn_num)
> >> +  else if (target_insn > record_full_next_insn)
> >>       {
> >>         gdb_printf (_("Go forward to insn number %s\n"),
> >> -		  pulongest (p->u.end.insn_num));
> >> -      record_full_goto_insn (p, EXEC_FORWARD);
> >> +		  pulongest (record_full_list[target_insn].insn_num));
> >> +      record_full_goto_insn (target_insn, EXEC_FORWARD);
> >>       }
> >>     else
> >>       {
> >>         gdb_printf (_("Go backward to insn number %s\n"),
> >> -		  pulongest (p->u.end.insn_num));
> >> -      record_full_goto_insn (p, EXEC_REVERSE);
> >> +		  pulongest (target_insn));
> >> +      record_full_goto_insn (target_insn, EXEC_REVERSE);
> >>       }
> >>
> >>     registers_changed ();
> >> @@ -2033,13 +1893,7 @@ record_full_goto_entry (struct
> record_full_entry
> >> *p)
> >>   void
> >>   record_full_base_target::goto_record_begin ()
> >>   {
> >> -  struct record_full_entry *p = NULL;
> >> -
> >> -  for (p = &record_full_first; p != NULL; p = p->next)
> >> -    if (p->type == record_full_end)
> >> -      break;
> >> -
> >> -  record_full_goto_entry (p);
> >> +  record_full_goto_entry (0);
> >>   }
> >>
> >>   /* The "goto_record_end" target method.  */
> >> @@ -2047,15 +1901,7 @@ record_full_base_target::goto_record_begin ()
> >>   void
> >>   record_full_base_target::goto_record_end ()
> >>   {
> >> -  struct record_full_entry *p = NULL;
> >> -
> >> -  for (p = record_full_list; p->next != NULL; p = p->next)
> >> -    ;
> >> -  for (; p!= NULL; p = p->prev)
> >> -    if (p->type == record_full_end)
> >> -      break;
> >> -
> >> -  record_full_goto_entry (p);
> >> +  record_full_goto_entry (record_full_list.size () - 1);
> >>   }
> >>
> >>   /* The "goto_record" target method.  */
> >> @@ -2063,13 +1909,7 @@ record_full_base_target::goto_record_end ()
> >>   void
> >>   record_full_base_target::goto_record (ULONGEST target_insn)
> >>   {
> >> -  struct record_full_entry *p = NULL;
> >> -
> >> -  for (p = &record_full_first; p != NULL; p = p->next)
> >> -    if (p->type == record_full_end && p->u.end.insn_num == target_insn)
> >> -      break;
> >> -
> >> -  record_full_goto_entry (p);
> >> +  record_full_goto_entry (target_insn);
> >>   }
> >>
> >>   /* The "record_stop_replaying" target method.  */
> >> @@ -2339,19 +2179,96 @@ netorder32 (uint32_t input)
> >>     return ret;
> >>   }
> > Function comment. And I think it would be cleaner to move this refactoring
> in
> > a separate patch, too. Or is this separate function only possible due to the
> change
> > from using a list to std::deque?
> This function could be in a patch of its own, yeah. Will do that for v4
> >
> >> +static void
> >> +record_full_read_entry_from_bfd (bfd *cbfd, asection *osec, int
> *bfd_offset)
> >> +{
> >> +  uint8_t rectype;
> >> +  uint32_t regnum, len;
> >> +  uint64_t addr;
> >> +  regcache *cache = get_thread_regcache (inferior_thread ());
> >> +
> >> +  bfdcore_read (cbfd, osec, &rectype, sizeof (rectype), bfd_offset);
> >> +  switch (rectype)
> >> +    {
> >> +    case record_full_reg: /* reg */
> >> +      {
> >> +	/* Get register number to regnum.  */
> >> +	bfdcore_read (cbfd, osec, &regnum, sizeof (regnum), bfd_offset);
> >> +	regnum = netorder32 (regnum);
> >> +
> >> +	record_full_entry rec;
> >> +
> >> +	rec = record_full_reg_init (cache, regnum);
> >> +
> >> +	/* Get val.  */
> >> +	bfdcore_read (cbfd, osec, record_full_get_loc (&rec),
> >> +		      rec.u.reg.len, bfd_offset);
> >> +
> >> +	if (record_debug)
> >> +	  gdb_printf (gdb_stdlog,
> >> +		      "  Reading register %d (1 "
> >> +		      "plus %lu plus %d bytes)\n",
> >> +		      rec.u.reg.num,
> >> +		      (unsigned long) sizeof (regnum),
> >> +		      rec.u.reg.len);
> >> +
> >> +	record_full_arch_list_add (rec);
> >> +	break;
> >> +      }
> >> +
> >> +    case record_full_mem: /* mem */
> >> +      {
> >> +	/* Get len.  */
> >> +	bfdcore_read (cbfd, osec, &len, sizeof (len), bfd_offset);
> >> +	len = netorder32 (len);
> >> +
> >> +	/* Get addr.  */
> >> +	bfdcore_read (cbfd, osec, &addr, sizeof (addr),
> >> +		      bfd_offset);
> >> +	addr = netorder64 (addr);
> >> +
> >> +	record_full_entry rec;
> >> +	rec = record_full_mem_init (addr, len);
> >> +
> >> +	/* Get val.  */
> >> +	bfdcore_read (cbfd, osec, record_full_get_loc (&rec), len,
> >> +		      bfd_offset);
> >> +
> >> +	if (record_debug)
> >> +	  gdb_printf (gdb_stdlog,
> >> +		      "  Reading memory %s (1 plus "
> >> +		      "%lu plus %lu plus %d bytes)\n",
> >> +		      paddress (get_current_arch (),
> >> +				rec.u.mem.addr),
> >> +		      (unsigned long) sizeof (addr),
> >> +		      (unsigned long) sizeof (len),
> >> +		      len);
> >> +
> >> +	record_full_arch_list_add (rec);
> >> +	break;
> >> +      }
> >> +
> >> +    default:
> >> +      error (_("Bad entry type %d, offset %d of %lu in core file %ps."),
> >> +	     rectype, *bfd_offset, bfd_section_size (osec),
> >> +	     styled_string (file_name_style.style (),
> >> +			    bfd_get_filename (cbfd)));
> >> +      break;
> >> +    }
> >> +}
> >> +
> >>   /* Restore the execution log from core file CBFD.  */
> >>
> >>   static void
> >>   record_full_restore (struct bfd &cbfd)
> >>   {
> >>     uint32_t magic;
> >> -  struct record_full_entry *rec;
> >>     asection *osec;
> >>     uint32_t osec_size;
> >>     int bfd_offset = 0;
> >>
> >>     /* "record_full_restore" can only be called when record list is empty.  */
> >> -  gdb_assert (record_full_first.next == NULL);
> >> +  gdb_assert (record_full_list.empty ());
> >>
> >>     if (record_debug)
> >>       gdb_printf (gdb_stdlog, "Restoring recording from core file.\n");
> >> @@ -2379,124 +2296,47 @@ record_full_restore (struct bfd &cbfd)
> >>   		"RECORD_FULL_FILE_MAGIC (0x%s)\n",
> >>   		phex_nz (netorder32 (magic), 4));
> >>
> >> -  /* Restore the entries in recfd into record_full_arch_list_head and
> >> -     record_full_arch_list_tail.  */
> >> -  record_full_arch_list_head = NULL;
> >> -  record_full_arch_list_tail = NULL;
> >>     record_full_insn_num = 0;
> >>
> >>     try
> >>       {
> >> -      regcache *regcache = get_thread_regcache (inferior_thread ());
> >> -
> >> -      while (1)
> >> +      while (bfd_offset < osec_size)
> >>   	{
> >> -	  uint8_t rectype;
> >> -	  uint32_t regnum, len, signal, count;
> >> -	  uint64_t addr;
> >> +	  record_full_reset_incomplete ();
> >> +	  uint32_t eff_count = 0;
> >> +	  uint8_t sigval;
> >> +	  uint32_t insn_num;
> >>
> >> -	  /* We are finished when offset reaches osec_size.  */
> >> -	  if (bfd_offset >= osec_size)
> >> -	    break;
> >> -	  bfdcore_read (&cbfd, osec, &rectype, sizeof (rectype), &bfd_offset);
> >> +	  /* First read the generic information for an instruction.  */
> >> +	  bfdcore_read (&cbfd, osec, &sigval, sizeof (uint8_t), &bfd_offset);
> >> +	  bfdcore_read (&cbfd, osec, &eff_count, sizeof (uint32_t),
> >> +			&bfd_offset);
> >> +	  bfdcore_read (&cbfd, osec, &insn_num, sizeof (uint32_t),
> >> +			&bfd_offset);
> >>
> >> -	  switch (rectype)
> >> -	    {
> >> -	    case record_full_reg: /* reg */
> >> -	      /* Get register number to regnum.  */
> >> -	      bfdcore_read (&cbfd, osec, &regnum, sizeof (regnum),
> >> -			    &bfd_offset);
> >> -	      regnum = netorder32 (regnum);
> >> -
> >> -	      rec = record_full_reg_alloc (regcache, regnum);
> >> -
> >> -	      /* Get val.  */
> >> -	      bfdcore_read (&cbfd, osec, record_full_get_loc (rec),
> >> -			    rec->u.reg.len, &bfd_offset);
> >> -
> >> -	      if (record_debug)
> >> -		gdb_printf (gdb_stdlog,
> >> -			    "  Reading register %d (1 "
> >> -			    "plus %lu plus %d bytes)\n",
> >> -			    rec->u.reg.num,
> >> -			    (unsigned long) sizeof (regnum),
> >> -			    rec->u.reg.len);
> >> -	      break;
> >> +	  record_full_incomplete_instruction.insn_num = netorder32
> >> (insn_num);
> >> +	  if (sigval != GDB_SIGNAL_0)
> >> +	    record_full_incomplete_instruction.sigval = (gdb_signal) sigval;
> >>
> >> -	    case record_full_mem: /* mem */
> >> -	      /* Get len.  */
> >> -	      bfdcore_read (&cbfd, osec, &len, sizeof (len), &bfd_offset);
> >> -	      len = netorder32 (len);
> >> -
> >> -	      /* Get addr.  */
> >> -	      bfdcore_read (&cbfd, osec, &addr, sizeof (addr), &bfd_offset);
> >> -	      addr = netorder64 (addr);
> >> -
> >> -	      rec = record_full_mem_alloc (addr, len);
> >> -
> >> -	      /* Get val.  */
> >> -	      bfdcore_read (&cbfd, osec, record_full_get_loc (rec),
> >> -			    rec->u.mem.len, &bfd_offset);
> >> -
> >> -	      if (record_debug)
> >> -		gdb_printf (gdb_stdlog,
> >> -			    "  Reading memory %s (1 plus "
> >> -			    "%lu plus %lu plus %d bytes)\n",
> >> -			    paddress (get_current_arch (),
> >> -				      rec->u.mem.addr),
> >> -			    (unsigned long) sizeof (addr),
> >> -			    (unsigned long) sizeof (len),
> >> -			    rec->u.mem.len);
> >> -	      break;
> >> +	  eff_count = netorder32 (eff_count);
> >>
> >> -	    case record_full_end: /* end */
> >> -	      rec = record_full_end_alloc ();
> >> -	      record_full_insn_num ++;
> >> -
> >> -	      /* Get signal value.  */
> >> -	      bfdcore_read (&cbfd, osec, &signal, sizeof (signal),
> >> -			    &bfd_offset);
> >> -	      signal = netorder32 (signal);
> >> -	      rec->u.end.sigval = (enum gdb_signal) signal;
> >> -
> >> -	      /* Get insn count.  */
> >> -	      bfdcore_read (&cbfd, osec, &count, sizeof (count), &bfd_offset);
> >> -	      count = netorder32 (count);
> >> -	      rec->u.end.insn_num = count;
> >> -	      record_full_insn_count = count + 1;
> >> -	      if (record_debug)
> >> -		gdb_printf (gdb_stdlog,
> >> -			    "  Reading record_full_end (1 + "
> >> -			    "%lu + %lu bytes), offset == %s\n",
> >> -			    (unsigned long) sizeof (signal),
> >> -			    (unsigned long) sizeof (count),
> >> -			    paddress (get_current_arch (),
> >> -				      bfd_offset));
> >> -	      break;
> >> +	  /* This deals with all the side effects.  */
> >> +	 while (eff_count > 0)
> >> +	   {
> >> +	     eff_count--;
> >>
> >> -	    default:
> >> -	      error (_("Bad entry type in core file %ps."),
> >> -		     styled_string (file_name_style.style (),
> >> -				    bfd_get_filename (&cbfd)));
> >> -	      break;
> >> -	    }
> >> +	     record_full_read_entry_from_bfd (&cbfd, osec, &bfd_offset);
> >> +	   }
> >>
> >> -	  /* Add rec to record arch list.  */
> >> -	  record_full_arch_list_add (rec);
> >> +	  record_full_save_instruction ();
> >>   	}
> >>       }
> >>     catch (const gdb_exception &ex)
> >>       {
> >> -      record_full_list_release (record_full_arch_list_tail);
> >> +      record_full_reset_incomplete ();
> >>         throw;
> >>       }
> >>
> >> -  /* Add record_full_arch_list_head to the end of record list.  */
> >> -  record_full_first.next = record_full_arch_list_head;
> >> -  record_full_arch_list_head->prev = &record_full_first;
> >> -  record_full_arch_list_tail->next = NULL;
> >> -  record_full_list = &record_full_first;
> >> -
> >>     /* Update record_full_insn_max_num.  */
> >>     if (record_full_insn_num > record_full_insn_max_num)
> >>       {
> >> @@ -2505,6 +2345,10 @@ record_full_restore (struct bfd &cbfd)
> >>   	       record_full_insn_max_num);
> >>       }
> >>
> >> +  /* When loading a recording, we'll always start at the oldest possible
> >> +     instruction, no matter where the original recording was stopped.  */
> >> +  record_full_next_insn = 0;
> >> +
> >>     /* Succeeded.  */
> >>     gdb_printf (_("Restored records from core file %s.\n"),
> >>   	      bfd_get_filename (&cbfd));
> >> @@ -2538,13 +2382,73 @@ cmd_record_full_restore (const char *args,
> int
> >> from_tty)
> >>     record_full_open (nullptr, from_tty);
> >>   }
> > Function comment. And similar comment for separate patch.
> >
> > I believe sth. like a follow-up commit that introduces the functions
> > record_full_read_entry_from_bfd and record_full_write_entry_to_bfd
> > would make this much easier to review.
> yeah same as above, will make this patch 2 of the series.

Thanks!

Christina
Intel Deutschland GmbH
Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
Tel: +49 89 991 430, www.intel.de
Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
Chairperson of the Supervisory Board: Nicole Lau
Registered Seat: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v3 3/7] gdb/record: c++ify internal structures of record-full.c
  2026-05-29 12:09       ` Schimpe, Christina
@ 2026-05-29 16:38         ` Guinevere Larsen
  0 siblings, 0 replies; 27+ messages in thread
From: Guinevere Larsen @ 2026-05-29 16:38 UTC (permalink / raw)
  To: Schimpe, Christina, gdb-patches; +Cc: Thiago Jung Bauermann

[-- Attachment #1: Type: text/plain, Size: 2667 bytes --]

On 5/29/26 9:09 AM, Schimpe, Christina wrote:
>>>> +    addr = other.addr;
>>>> +    len = other.len;
>>>> +    memcpy (u.buf, other.u.buf, sizeof (u.buf));
>>> Shouldn't we check for self-assignment here ?
>> I don't think it would be possible to self-assign here, since this is a
>> move operator. We shouldn't be able to create a copy anywhere, so I
>> think it shouldn't be a large thing.
>> I can add an assert, since if it ever happens this is a large bug.
> Yeah, I just noticed it since I've been looking at a sample implementation
> for the move assignment operator which does check for self-assignment:
>
> https://learn.microsoft.com/en-us/cpp/cpp/move-constructors-and-move-assignment-operators-cpp?view=msvc-170#example-complete-move-constructor-and-assignment-operator
>
> and then compared a bit with other classes which have DISABLE_COPY_AND_ASSIGN
> and the move assignment operator.
Right, makes sense. Since in this case I would see self-assignment as a 
bug, I added an assert, but if we ever have a different opinion as a 
project, it's easy enough to make it an if condition.
>
>>> Also, like the destructor, I believe we must delete u.ptr here:
>>> ~~~
>>>     if (len > sizeof (u.buf))
>>>         delete[] u.ptr;
>>> ~~~
>>>
>>> Same feedback for record_full_mem_entry &operator=
>> The idea of a move operation (constructor or assignment) is that we
>> won't need to allocate new memory, we'll just pass the pointer to the
>> new holder and that's it. If the pointer is deleted here, since it was
>> copied over in the memcpy call, when the next object calls its
>> destructor, we'll get a double free.
> Arg, this might have been confusing.  My feedback on this is not exactly at the right line.
> I'd add this delete before those lines:
>
>>>> +    addr = other.addr;
>>>> +    len = other.len;
>>>> +    memcpy (u.buf, other.u.buf, sizeof (u.buf));
> When the object is finally destructed, I don't expect a double free, since we only free
> the data of the moved object. In the move operator we delete the data of the original
> object.
>
> In the example link I shared above, also the existing data are deleted.
> But it might be that I'm missing something here still.

Oh right, that makes a lot more sense, I thought the suggestion was to 
free the other.u.ptr lol. Sorry for the misunderstanding!

In the case of this code, I would expect that we're always assigning 
this to the "essentially uninitialized" variable, so again I think an 
assert that len == 0 is a better check. I'll run stuff locally to ensure 
that my understanding is correct, and if it isn't I'll add the free you 
suggested.

-- 
Cheers,
Guinevere Larsen
It/she

[-- Attachment #2: Type: text/html, Size: 4411 bytes --]

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v3 5/7] gdb/record: extract the PC to record_full_instruction
  2026-05-29 12:40   ` Schimpe, Christina
@ 2026-06-01 20:24     ` Guinevere Larsen
  0 siblings, 0 replies; 27+ messages in thread
From: Guinevere Larsen @ 2026-06-01 20:24 UTC (permalink / raw)
  To: Schimpe, Christina, gdb-patches

[-- Attachment #1: Type: text/plain, Size: 2499 bytes --]

On 5/29/26 9:40 AM, Schimpe, Christina wrote:
>> -----Original Message-----
>> From: Guinevere Larsen<guinevere@redhat.com>
>> Sent: Freitag, 15. Mai 2026 18:37
>> To:gdb-patches@sourceware.org
>> Cc: Guinevere Larsen<guinevere@redhat.com>
>> Subject: [PATCH v3 5/7] gdb/record: extract the PC to record_full_instruction
>>
>> This commit makes it so the PC is not saved as part of the
>> record_full_instruction effects, but rather gets a special location.
>> That is because a couple of commands would really benefit from it being easy
>> to find the PC (especially ones from record-btrace that's haven't been
>> implemented to record-full yet, such as the ones in PR record/18059), while
>> also possibly allowing for one fewer resizing of the effect vector (and saving
>> an entire byte in the process).
>>
>> This commit also refactored record_full_read_entry_from_bfd and
>> record_full_write_entry_to_bfd, to make them methods of
>> record_full_reg_entry and record_full_mem_entry, and also creates similar
>> methods for record_full_entry and record_full_instruction.
>> These could be turned into constructors in a future step of
>> c++ification, but it felt like too much change for a single commit.
>> ---
>>   gdb/record-full.c | 386 ++++++++++++++++++++++++++++------------------
>>   1 file changed, 239 insertions(+), 147 deletions(-)
>>
>> diff --git a/gdb/record-full.c b/gdb/record-full.c index
>> 8cabd6e9438..17696e07c0d 100644
>> --- a/gdb/record-full.c
>> +++ b/gdb/record-full.c
>> @@ -143,6 +143,14 @@ struct record_full_mem_entry
>>
>>     DISABLE_COPY_AND_ASSIGN (record_full_mem_entry);
>>
>> +  /* Create a mem_entry from a bfd file, when restoring a recording.
>> +*/
>> +  static record_full_mem_entry from_bfd (bfd*cbfd, asection* osec,
>> +					 int *bfd_offset);
>> +
>> +  /* Save this mem entry to a bfd file.  */
>> +  void to_bfd (gdb_bfd_ref_ptr obfd, asection *osec, int *bfd_offset,
>> +	       gdbarch *gdbarch);
>> +
> I believe only in one of those to_bfd functions the gdbarch parameter is used.
> Did you intentionally keep it the function which don't have use the parameter in to_bfd ?
Fixed. I didn't keep it intentionally.
>
> And it might be a good idea to describe the parameters a bit, too. 😊

I would love to describe the parameters, but to be fair, I only know 
what the self-explanatory ones do lol. No idea what the section is used 
for, and the bfd_ref_ptr is the ref-counted pointer to the BFD file...

-- 
Cheers,
Guinevere Larsen
It/she

[-- Attachment #2: Type: text/html, Size: 3660 bytes --]

^ permalink raw reply	[flat|nested] 27+ messages in thread

end of thread, other threads:[~2026-06-01 20:25 UTC | newest]

Thread overview: 27+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-05-15 16:36 [PATCH v3 0/7] refactor the internals of record-full Guinevere Larsen
2026-05-15 16:36 ` [PATCH v3 1/7] gdb/record: Refactor record history Guinevere Larsen
2026-05-26  8:33   ` Schimpe, Christina
2026-05-28 13:20     ` Guinevere Larsen
2026-05-29 13:47       ` Schimpe, Christina
2026-05-15 16:36 ` [PATCH v3 2/7] gdb/record: remove record_full_insn_num Guinevere Larsen
2026-05-26  8:35   ` Schimpe, Christina
2026-05-15 16:37 ` [PATCH v3 3/7] gdb/record: c++ify internal structures of record-full.c Guinevere Larsen
2026-05-26  9:00   ` Schimpe, Christina
2026-05-27 20:13     ` Guinevere Larsen
2026-05-29 12:09       ` Schimpe, Christina
2026-05-29 16:38         ` Guinevere Larsen
2026-05-15 16:37 ` [PATCH v3 4/7] gdb/record: make record_full_history more c++-like Guinevere Larsen
2026-05-16  1:03   ` Thiago Jung Bauermann
2026-05-26  9:04   ` Schimpe, Christina
2026-05-26 20:07     ` Guinevere Larsen
2026-05-15 16:37 ` [PATCH v3 5/6] gdb/record: Define new version of the record-save section Guinevere Larsen
2026-05-15 16:37 ` [PATCH v3 5/7] gdb/record: extract the PC to record_full_instruction Guinevere Larsen
2026-05-16  1:04   ` Thiago Jung Bauermann
2026-05-29 12:40   ` Schimpe, Christina
2026-06-01 20:24     ` Guinevere Larsen
2026-05-15 16:37 ` [PATCH v3 6/7] gdb/record: Define new version of the record-save section Guinevere Larsen
2026-05-15 16:37 ` [PATCH v3 6/6] gdb/record: rename record_full_list to record_full_log Guinevere Larsen
2026-05-29 12:05   ` Schimpe, Christina
2026-05-15 16:37 ` [PATCH v3 7/7] " Guinevere Larsen
2026-05-16  1:07   ` Thiago Jung Bauermann
2026-05-15 17:45 ` [PATCH v3 0/7] refactor the internals of record-full Guinevere Larsen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox