]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
gdb: LoongArch: Add basic process record/replay support
authorHui Li <lihui@loongson.cn>
Mon, 25 Nov 2024 07:40:03 +0000 (15:40 +0800)
committerTiezhu Yang <yangtiezhu@loongson.cn>
Mon, 25 Nov 2024 11:15:02 +0000 (19:15 +0800)
GDB provides a special process record and replay target that can
record a log of the process execution, and replay it later with
both forward and reverse execution commands. This patch adds the
basic support of process record and replay on LoongArch, it allows
users to debug basic LoongArch instructions and provides reverse
debugging support.

Here is a simple example on LoongArch:

$ cat test.c
int a = 0;
int main()
  {
    a = 1;
    a = 2;
    return 0;
  }
$ gdb test
...
(gdb) start
...
Temporary breakpoint 1, main () at test.c:4
4     a = 1;
(gdb) record
(gdb) p a
$1 = 0
(gdb) n
5     a = 2;
(gdb) n
6     return 0;
(gdb) p a
$2 = 2
(gdb) rn
5     a = 2;
(gdb) rn

Reached end of recorded history; stopping.
Backward execution from here not possible.
main () at test.c:4
4     a = 1;
(gdb) p a
$3 = 0
(gdb) record stop
Process record is stopped and all execution logs are deleted.
(gdb) c
Continuing.
[Inferior 1 (process 129178) exited normally]

Signed-off-by: Hui Li <lihui@loongson.cn>
Approved-By: Guinevere Larsen <guinevere@redhat.com> (record-full)
Approved-By: Tom Tromey <tom@tromey.com>
Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn>
gdb/configure.tgt
gdb/loongarch-linux-tdep.c
gdb/loongarch-tdep.c
gdb/loongarch-tdep.h
gdb/testsuite/lib/gdb.exp

index 62df71b13fa142b9f775c4970ee71a31573cb19b..2d78c1182faba669dcf2766574bc9c116493798a 100644 (file)
@@ -363,7 +363,7 @@ lm32-*-*)
 loongarch*-*-linux*)
        # Target: LoongArch running Linux
        gdb_target_obs="loongarch-linux-tdep.o glibc-tdep.o \
-                       linux-tdep.o solib-svr4.o"
+                       linux-tdep.o solib-svr4.o linux-record.o"
        ;;
 
 m32c-*-*)
index 715780603034c310f52071760741fc3245ad94ef..86e7ed8a0bc4645c8e73c5e9da8dde61cdf894a3 100644 (file)
@@ -602,6 +602,9 @@ loongarch_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
 
   /* Get the syscall number from the arch's register.  */
   set_gdbarch_get_syscall_number (gdbarch, loongarch_linux_get_syscall_number);
+
+  /* Reversible debugging, process record.  */
+  set_gdbarch_process_record (gdbarch, loongarch_process_record);
 }
 
 /* Initialize LoongArch Linux target support.  */
index c50dd7f4b78338bee18812dcc7b105b201c32f77..c1b3a1f239c64f843a87f9d081ab92cc17e11c88 100644 (file)
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 #include "arch-utils.h"
+#include "arch/loongarch-insn.h"
 #include "dwarf2/frame.h"
 #include "elf-bfd.h"
 #include "extract-store-integer.h"
 #include "frame-unwind.h"
 #include "gdbcore.h"
+#include "linux-record.h"
 #include "loongarch-tdep.h"
+#include "record.h"
+#include "record-full.h"
 #include "reggroups.h"
 #include "target.h"
 #include "target-descriptions.h"
@@ -1889,6 +1893,497 @@ loongarch_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
   return gdbarch;
 }
 
+/* LoongArch record/replay enumerations and structures.  */
+
+enum loongarch_record_result
+{
+  LOONGARCH_RECORD_SUCCESS,
+  LOONGARCH_RECORD_UNSUPPORTED,
+  LOONGARCH_RECORD_UNKNOWN
+};
+
+struct loongarch_record_s
+{
+  struct gdbarch *gdbarch;
+  struct regcache *regcache;
+  CORE_ADDR this_addr;                 /* Addr of insn to be recorded.   */
+  uint32_t insn;                       /* Insn to be recorded.           */
+};
+
+/* Record handler for data processing instructions.  */
+
+static int
+loongarch_record_data_proc_insn (loongarch_record_s *loongarch_record)
+{
+  int rd_num;
+  rd_num = loongarch_decode_imm ("0:5", loongarch_record->insn, 0);
+  if (record_full_arch_list_add_reg (loongarch_record->regcache, rd_num))
+    return -1;
+
+  return LOONGARCH_RECORD_SUCCESS;
+}
+
+/* Record handler for read time instructions.  */
+
+static int
+loongarch_record_read_time_insn (loongarch_record_s *loongarch_record)
+{
+  int rd_num, rj_num;
+  rd_num = loongarch_decode_imm ("0:5", loongarch_record->insn, 0);
+  rj_num = loongarch_decode_imm ("5:5", loongarch_record->insn, 0);
+  if (record_full_arch_list_add_reg (loongarch_record->regcache, rd_num))
+    return -1;
+  if (record_full_arch_list_add_reg (loongarch_record->regcache, rj_num))
+    return -1;
+
+  return LOONGARCH_RECORD_SUCCESS;
+}
+
+/* Record handler for branch instructions.  */
+
+static int
+loongarch_record_branch_insn (loongarch_record_s *loongarch_record)
+{
+  if (is_jirl_insn (loongarch_record->insn))
+    {
+      int rd_num;
+      rd_num = loongarch_decode_imm ("0:5", loongarch_record->insn, 0);
+      if (record_full_arch_list_add_reg (loongarch_record->regcache, rd_num))
+       return -1;
+    }
+  else if (is_bl_insn (loongarch_record->insn))
+    {
+      if (record_full_arch_list_add_reg (loongarch_record->regcache,
+                                        LOONGARCH_RA_REGNUM))
+       return -1;
+    }
+
+  return LOONGARCH_RECORD_SUCCESS;
+}
+
+/* Record handler for float data processing instructions.  */
+
+static int
+loongarch_record_float_data_proc_insn (loongarch_record_s *loongarch_record)
+{
+  int fd_num;
+  fd_num = loongarch_decode_imm ("0:5", loongarch_record->insn, 0)
+          + LOONGARCH_FIRST_FP_REGNUM;
+
+  if (record_full_arch_list_add_reg (loongarch_record->regcache, fd_num))
+    return -1;
+
+  return LOONGARCH_RECORD_SUCCESS;
+}
+
+/* Record handler for move gr to fcsr instructions.  */
+
+static int
+loongarch_record_movgr2fcsr_insn (loongarch_record_s *loongarch_record)
+{
+  if (record_full_arch_list_add_reg (loongarch_record->regcache,
+                                    LOONGARCH_FCSR_REGNUM))
+    return -1;
+
+  return LOONGARCH_RECORD_SUCCESS;
+}
+
+/* Record handler for move gr/fr to fcc instructions.  */
+
+static int
+loongarch_record_mov2cf_insn (loongarch_record_s *loongarch_record)
+{
+  int cd;
+  cd = loongarch_decode_imm ("0:3", loongarch_record->insn, 0);
+  if (record_full_arch_list_add_reg (loongarch_record->regcache, cd))
+    return -1;
+
+  return LOONGARCH_RECORD_SUCCESS;
+}
+
+/* Record handler for float instructions.  */
+
+static unsigned int
+loongarch_record_float_insn (loongarch_record_s *loongarch_record)
+{
+  if (is_movgr2fcsr_insn (loongarch_record->insn))
+    return loongarch_record_movgr2fcsr_insn (loongarch_record);
+  else if (is_mov2cf_insn (loongarch_record->insn))
+    return loongarch_record_mov2cf_insn (loongarch_record);
+  else
+    return loongarch_record_float_data_proc_insn (loongarch_record);
+}
+
+/* Record handler for store instructions.  */
+
+static int
+loongarch_record_store_insn (loongarch_record_s *loongarch_record)
+{
+  enum store_types
+    {
+      STB, STH, STW, STD, STXB, STXH, STXW, STXD, STPTRW, STPTRD,
+      SCW, SCD, FSTS, FSTD, FSTXS, FSTXD, VST, XVST, NOT_STORE
+    };
+  int store_type, data_size, rj_num;
+  uint64_t address, rj_val;
+
+  store_type = is_st_b_insn (loongarch_record->insn)      ? STB     :
+              is_st_h_insn (loongarch_record->insn)       ? STH     :
+              is_st_w_insn (loongarch_record->insn)       ? STW     :
+              is_st_d_insn (loongarch_record->insn)       ? STD     :
+              is_stx_b_insn (loongarch_record->insn)      ? STXB    :
+              is_stx_h_insn (loongarch_record->insn)      ? STXH    :
+              is_stx_w_insn (loongarch_record->insn)      ? STXW    :
+              is_stx_d_insn (loongarch_record->insn)      ? STXD    :
+              is_stptr_w_insn (loongarch_record->insn)    ? STPTRW  :
+              is_stptr_d_insn (loongarch_record->insn)    ? STPTRD  :
+              is_sc_w_insn (loongarch_record->insn)       ? SCW     :
+              is_sc_d_insn (loongarch_record->insn)       ? SCD     :
+              is_fst_s_insn (loongarch_record->insn)      ? FSTS    :
+              is_fst_d_insn (loongarch_record->insn)      ? FSTD    :
+              is_fstx_s_insn (loongarch_record->insn)     ? FSTXS   :
+              is_fstx_d_insn (loongarch_record->insn)     ? FSTXD   :
+              is_vst_insn (loongarch_record->insn)        ? VST     :
+              is_xvst_insn (loongarch_record->insn)       ? XVST    :
+              NOT_STORE;
+  rj_num = loongarch_decode_imm ("5:5", loongarch_record->insn, 0);
+  regcache_raw_read_unsigned (loongarch_record->regcache, rj_num, &rj_val);
+
+  if (store_type == STB || store_type == STH || store_type == STW
+      || store_type == STD || store_type == FSTS || store_type == FSTD
+      || store_type == VST || store_type == XVST)
+    {
+      int imm;
+      imm = loongarch_decode_imm ("10:12", loongarch_record->insn, 1);
+      address = rj_val + imm;
+      switch (store_type)
+       {
+       case STB:
+         data_size = 1;
+         break;
+       case STH:
+         data_size = 2;
+         break;
+       case STW:
+       case FSTS:
+         data_size = 4;
+         break;
+       case STD:
+       case FSTD:
+         data_size = 8;
+         break;
+       case VST:
+         data_size = 16;
+         break;
+       case XVST:
+         data_size = 32;
+         break;
+       default:
+         data_size = 0;
+         break;
+       }
+
+      if (record_full_arch_list_add_mem (address, data_size))
+       return -1;
+    }
+  else if (store_type == STXB || store_type == STXH || store_type == STXW
+          || store_type == STXD || store_type == FSTXS || store_type == FSTXD)
+    {
+      int rk_num;
+      uint64_t rk_val;
+      rk_num = loongarch_decode_imm ("10:5", loongarch_record->insn, 0);
+      regcache_raw_read_unsigned (loongarch_record->regcache, rk_num, &rk_val);
+      address = rj_val + rk_val;
+      switch (store_type)
+       {
+       case STXB:
+         data_size = 1;
+         break;
+       case STXH:
+         data_size = 2;
+         break;
+       case STXW:
+       case FSTXS:
+         data_size = 4;
+         break;
+       case STXD:
+       case FSTXD:
+         data_size = 8;
+         break;
+       default:
+         data_size = 0;
+         break;
+       }
+
+      if (record_full_arch_list_add_mem (address, data_size))
+       return -1;
+    }
+  else if (store_type == STPTRW || store_type == STPTRD || store_type == SCW
+          || store_type == SCD)
+    {
+      int imm;
+      imm = loongarch_decode_imm ("10:14<<2", loongarch_record->insn, 1);
+      address = rj_val + imm;
+      switch (store_type)
+       {
+       case STPTRW:
+       case SCW:
+         data_size = 4;
+         break;
+       case STPTRD:
+       case SCD:
+         data_size = 8;
+         break;
+       default:
+         data_size = 0;
+         break;
+       }
+
+      if (record_full_arch_list_add_mem (address, data_size))
+       return -1;
+    }
+
+  return LOONGARCH_RECORD_SUCCESS;
+}
+
+/* Record handler for atomic memory access instructions.  */
+
+static int
+loongarch_record_atomic_access_insn (loongarch_record_s *loongarch_record)
+{
+  int rj_num, rd_num, rk_num, length;
+  int data_size;
+  uint64_t address;
+  rd_num = loongarch_decode_imm ("0:5", loongarch_record->insn, 0);
+  rj_num = loongarch_decode_imm ("5:5", loongarch_record->insn, 0);
+  rk_num = loongarch_decode_imm ("10:5", loongarch_record->insn, 0);
+  regcache_raw_read_unsigned (loongarch_record->regcache, rj_num, &address);
+  if (is_basic_am_w_d_insn (loongarch_record->insn))
+    {
+      length = loongarch_decode_imm ("15:1", loongarch_record->insn, 0);
+      data_size = length == 1 ? 8 : 4;
+      if (record_full_arch_list_add_mem (address, data_size))
+       return -1;
+    }
+  if (is_am_b_h_insn (loongarch_record->insn))
+    {
+      length = loongarch_decode_imm ("15:1", loongarch_record->insn, 0);
+      data_size = length == 1 ? 2 : 1;
+      if (record_full_arch_list_add_mem (address, data_size))
+       return -1;
+    }
+  if (is_amcas_insn (loongarch_record->insn))
+    {
+      length = loongarch_decode_imm ("15:2", loongarch_record->insn, 0);
+      switch (length)
+       {
+       case 0x0:
+         data_size = 1;
+         break;
+       case 0x1:
+         data_size = 2;
+         break;
+       case 0x2:
+         data_size = 4;
+         break;
+       case 0x3:
+         data_size = 8;
+         break;
+       default:
+         data_size = 0;
+         break;
+       }
+      if (record_full_arch_list_add_mem (address, data_size))
+       return -1;
+    }
+
+  if (record_full_arch_list_add_reg (loongarch_record->regcache, rd_num))
+    return -1;
+
+  if (is_amswap_insn (loongarch_record->insn))
+    {
+      if (record_full_arch_list_add_reg (loongarch_record->regcache, rk_num))
+       return -1;
+    }
+
+  return LOONGARCH_RECORD_SUCCESS;
+}
+
+/* Record handler for bound check load instructions.  */
+
+static int
+loongarch_record_bound_check_load_insn (loongarch_record_s *loongarch_record)
+{
+  int rd_num, rj_num, rk_num, fd_num;
+  uint64_t rj_val, rk_val;
+  rd_num = loongarch_decode_imm ("0:5", loongarch_record->insn, 0);
+  fd_num = loongarch_decode_imm ("0:5", loongarch_record->insn, 0);
+  rj_num = loongarch_decode_imm ("5:5", loongarch_record->insn, 0);
+  rk_num = loongarch_decode_imm ("10:5", loongarch_record->insn, 0);
+  regcache_raw_read_unsigned (loongarch_record->regcache, rj_num, &rj_val);
+  regcache_raw_read_unsigned (loongarch_record->regcache, rk_num, &rk_val);
+
+  if ((is_ldgt_insn (loongarch_record->insn) && (rj_val > rk_val))
+      || (is_ldle_insn (loongarch_record->insn) && (rj_val <= rk_val)))
+    {
+      if (record_full_arch_list_add_reg (loongarch_record->regcache, rd_num))
+       return -1;
+    }
+  else if ((is_fldgt_insn (loongarch_record->insn) && (rj_val > rk_val))
+          || (is_fldle_insn (loongarch_record->insn) && (rj_val <= rk_val)))
+    {
+      if (record_full_arch_list_add_reg (loongarch_record->regcache, fd_num))
+       return -1;
+    }
+
+  return LOONGARCH_RECORD_SUCCESS;
+}
+
+/* Record handler for bound check store instructions.  */
+
+static int
+loongarch_record_bound_check_store_insn (loongarch_record_s *loongarch_record)
+{
+  int rj_num, rk_num;
+  int data_size;
+  uint64_t rj_val, rk_val;
+  uint32_t length_opcode;
+  rj_num = loongarch_decode_imm ("5:5", loongarch_record->insn, 0);
+  rk_num = loongarch_decode_imm ("10:5", loongarch_record->insn, 0);
+  regcache_raw_read_unsigned (loongarch_record->regcache, rj_num, &rj_val);
+  regcache_raw_read_unsigned (loongarch_record->regcache, rk_num, &rk_val);
+
+  if ((is_stgt_insn (loongarch_record->insn) && (rj_val > rk_val))
+      || (is_stle_insn (loongarch_record->insn) && (rj_val <= rk_val)))
+    {
+      length_opcode = loongarch_record->insn & 0x00018000;
+      switch (length_opcode)
+       {
+       case 0x00000000:
+         data_size = 1;
+         break;
+       case 0x00008000:
+         data_size = 2;
+         break;
+       case 0x00010000:
+         data_size = 4;
+         break;
+       case 0x00018000:
+         data_size = 8;
+         break;
+       default:
+         data_size = 0;
+         break;
+       }
+
+      if (record_full_arch_list_add_mem (rj_val, data_size))
+       return -1;
+    }
+  else if ((is_fstgt_insn (loongarch_record->insn) && (rj_val > rk_val))
+           || (is_fstle_insn (loongarch_record->insn) && (rj_val <= rk_val)))
+    {
+      length_opcode = loongarch_record->insn & 0x00008000;
+      switch (length_opcode)
+       {
+       case 0x00000000:
+         data_size = 4;
+         break;
+       case 0x00008000:
+         data_size = 8;
+         break;
+       default:
+         data_size = 0;
+         break;
+       }
+
+      if (record_full_arch_list_add_mem (rj_val, data_size))
+       return -1;
+    }
+
+  return LOONGARCH_RECORD_SUCCESS;
+}
+
+/* Record handler for special instructions like privilege instructions,
+   barrier instructions and cache related instructions etc.  */
+
+static int
+loongarch_record_special_insn (loongarch_record_s *loongarch_record)
+{
+  return LOONGARCH_RECORD_SUCCESS;
+}
+
+/* Decode insns type and invoke its record handler.  */
+
+static int
+loongarch_record_decode_insn_handler (loongarch_record_s *loongarch_record)
+{
+  if (is_data_process_insn (loongarch_record->insn))
+    return loongarch_record_data_proc_insn (loongarch_record);
+  else if (is_branch_insn (loongarch_record->insn))
+    return loongarch_record_branch_insn (loongarch_record);
+  else if (is_store_insn (loongarch_record->insn))
+    return loongarch_record_store_insn (loongarch_record);
+  else if (is_read_time_insn (loongarch_record->insn))
+    return loongarch_record_read_time_insn (loongarch_record);
+  else if (is_float_insn (loongarch_record->insn))
+    return loongarch_record_float_insn (loongarch_record);
+  else if (is_special_insn (loongarch_record->insn))
+    return loongarch_record_special_insn (loongarch_record);
+  else if (is_atomic_access_insn (loongarch_record->insn))
+    return loongarch_record_atomic_access_insn (loongarch_record);
+  else if (is_bound_check_load_insn (loongarch_record->insn))
+    return loongarch_record_bound_check_load_insn (loongarch_record);
+  else if (is_bound_check_store_insn (loongarch_record->insn))
+    return loongarch_record_bound_check_store_insn (loongarch_record);
+
+  return LOONGARCH_RECORD_UNSUPPORTED;
+}
+
+/* Parse the current instruction and record the values of the registers and
+   memory that will be changed in current instruction to record_arch_list
+   return -1 if something is wrong.  */
+
+int
+loongarch_process_record (struct gdbarch *gdbarch, struct regcache *regcache,
+                         CORE_ADDR insn_addr)
+{
+  int ret = 0;
+  loongarch_record_s loongarch_record;
+
+  /* reset the content of loongarch_record */
+  memset (&loongarch_record, 0, sizeof (loongarch_record_s));
+
+  /* write the loongarch_record */
+  loongarch_record.gdbarch = gdbarch;
+  loongarch_record.regcache = regcache;
+  loongarch_record.this_addr = insn_addr;
+
+  /* Get the current instruction */
+  loongarch_record.insn = (uint32_t) loongarch_fetch_instruction (insn_addr);
+  ret = loongarch_record_decode_insn_handler (&loongarch_record);
+  if (ret == LOONGARCH_RECORD_UNSUPPORTED)
+    {
+      gdb_printf (gdb_stderr,
+                 _("Process record does not support instruction "
+                   "0x%0x at address %s.\n"),
+                 loongarch_record.insn,
+                 paddress (gdbarch, insn_addr));
+      return -1;
+    }
+  if (ret == LOONGARCH_RECORD_SUCCESS)
+    {
+      /* Record PC registers.  */
+      if (record_full_arch_list_add_reg (loongarch_record.regcache,
+                                        LOONGARCH_PC_REGNUM))
+       return -1;
+
+      if (record_full_arch_list_add_end ())
+       return -1;
+    }
+
+  return ret;
+}
+
 void _initialize_loongarch_tdep ();
 void
 _initialize_loongarch_tdep ()
index 5c8108182ad6b44c520f5c14229c0348b2a49ad9..a148363c795f0dab33ce8b08dd2668b3dba090fc 100644 (file)
@@ -44,4 +44,7 @@ struct loongarch_gdbarch_tdep : gdbarch_tdep_base
   CORE_ADDR (*syscall_next_pc) (const frame_info_ptr &frame) = nullptr;
 };
 
+extern int loongarch_process_record (struct gdbarch *gdbarch,
+                                    struct regcache *regcache, CORE_ADDR addr);
+
 #endif /* LOONGARCH_TDEP_H  */
index 2b27d7fc9e786d5d337714c2d9ebe4c182f5525e..127ab27210076dfa1cc286e6e7e032ef8de7bbd1 100644 (file)
@@ -3700,6 +3700,7 @@ proc supports_process_record {} {
     if { [istarget "arm*-*-linux*"] || [istarget "x86_64-*-linux*"]
          || [istarget "i\[34567\]86-*-linux*"]
          || [istarget "aarch64*-*-linux*"]
+        || [istarget "loongarch*-*-linux*"]
          || [istarget "powerpc*-*-linux*"]
          || [istarget "s390*-*-linux*"] } {
        return 1
@@ -3719,6 +3720,7 @@ proc supports_reverse {} {
     if { [istarget "arm*-*-linux*"] || [istarget "x86_64-*-linux*"]
          || [istarget "i\[34567\]86-*-linux*"]
          || [istarget "aarch64*-*-linux*"]
+        || [istarget "loongarch*-*-linux*"]
          || [istarget "powerpc*-*-linux*"]
          || [istarget "s390*-*-linux*"] } {
        return 1