/* Native-dependent code for GNU/Linux on LoongArch processors. Copyright (C) 2022-2023 Free Software Foundation, Inc. Contributed by Loongson Ltd. This file is part of GDB. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "defs.h" #include "elf/common.h" #include "gregset.h" #include "inferior.h" #include "linux-nat-trad.h" #include "loongarch-tdep.h" #include "nat/gdb_ptrace.h" #include "target-descriptions.h" #include /* LoongArch Linux native additions to the default Linux support. */ class loongarch_linux_nat_target final : public linux_nat_trad_target { public: /* Add our register access methods. */ void fetch_registers (struct regcache *, int) override; void store_registers (struct regcache *, int) override; protected: /* Override linux_nat_trad_target methods. */ CORE_ADDR register_u_offset (struct gdbarch *gdbarch, int regnum, int store_p) override; }; /* Fill GDB's register array with the general-purpose, orig_a0, pc and badv register values from the current thread. */ static void fetch_gregs_from_thread (struct regcache *regcache, int regnum, pid_t tid) { elf_gregset_t regset; if (regnum == -1 || (regnum >= 0 && regnum < 32) || regnum == LOONGARCH_ORIG_A0_REGNUM || regnum == LOONGARCH_PC_REGNUM || regnum == LOONGARCH_BADV_REGNUM) { struct iovec iov; iov.iov_base = ®set; iov.iov_len = sizeof (regset); if (ptrace (PTRACE_GETREGSET, tid, NT_PRSTATUS, (long) &iov) < 0) perror_with_name (_("Couldn't get NT_PRSTATUS registers")); else loongarch_gregset.supply_regset (nullptr, regcache, regnum, ®set, sizeof (regset)); } } /* Store to the current thread the valid general-purpose, orig_a0, pc and badv register values in the GDB's register array. */ static void store_gregs_to_thread (struct regcache *regcache, int regnum, pid_t tid) { elf_gregset_t regset; if (regnum == -1 || (regnum >= 0 && regnum < 32) || regnum == LOONGARCH_ORIG_A0_REGNUM || regnum == LOONGARCH_PC_REGNUM || regnum == LOONGARCH_BADV_REGNUM) { struct iovec iov; iov.iov_base = ®set; iov.iov_len = sizeof (regset); if (ptrace (PTRACE_GETREGSET, tid, NT_PRSTATUS, (long) &iov) < 0) perror_with_name (_("Couldn't get NT_PRSTATUS registers")); else { loongarch_gregset.collect_regset (nullptr, regcache, regnum, ®set, sizeof (regset)); if (ptrace (PTRACE_SETREGSET, tid, NT_PRSTATUS, (long) &iov) < 0) perror_with_name (_("Couldn't set NT_PRSTATUS registers")); } } } /* Fill GDB's register array with the fp, fcc and fcsr register values from the current thread. */ static void fetch_fpregs_from_thread (struct regcache *regcache, int regnum, pid_t tid) { elf_fpregset_t regset; if ((regnum == -1) || (regnum >= LOONGARCH_FIRST_FP_REGNUM && regnum <= LOONGARCH_FCSR_REGNUM)) { struct iovec iovec = { .iov_base = ®set, .iov_len = sizeof (regset) }; if (ptrace (PTRACE_GETREGSET, tid, NT_FPREGSET, (long) &iovec) < 0) perror_with_name (_("Couldn't get NT_FPREGSET registers")); else loongarch_fpregset.supply_regset (nullptr, regcache, regnum, ®set, sizeof (regset)); } } /* Store to the current thread the valid fp, fcc and fcsr register values in the GDB's register array. */ static void store_fpregs_to_thread (struct regcache *regcache, int regnum, pid_t tid) { elf_fpregset_t regset; if ((regnum == -1) || (regnum >= LOONGARCH_FIRST_FP_REGNUM && regnum <= LOONGARCH_FCSR_REGNUM)) { struct iovec iovec = { .iov_base = ®set, .iov_len = sizeof (regset) }; if (ptrace (PTRACE_GETREGSET, tid, NT_FPREGSET, (long) &iovec) < 0) perror_with_name (_("Couldn't get NT_FPREGSET registers")); else { loongarch_fpregset.collect_regset (nullptr, regcache, regnum, ®set, sizeof (regset)); if (ptrace (PTRACE_SETREGSET, tid, NT_FPREGSET, (long) &iovec) < 0) perror_with_name (_("Couldn't set NT_FPREGSET registers")); } } } /* Implement the "fetch_registers" target_ops method. */ void loongarch_linux_nat_target::fetch_registers (struct regcache *regcache, int regnum) { pid_t tid = get_ptrace_pid (regcache->ptid ()); fetch_gregs_from_thread(regcache, regnum, tid); fetch_fpregs_from_thread(regcache, regnum, tid); } /* Implement the "store_registers" target_ops method. */ void loongarch_linux_nat_target::store_registers (struct regcache *regcache, int regnum) { pid_t tid = get_ptrace_pid (regcache->ptid ()); store_gregs_to_thread (regcache, regnum, tid); store_fpregs_to_thread(regcache, regnum, tid); } /* Return the address in the core dump or inferior of register REGNO. */ CORE_ADDR loongarch_linux_nat_target::register_u_offset (struct gdbarch *gdbarch, int regnum, int store_p) { if (regnum >= 0 && regnum < 32) return regnum; else if (regnum == LOONGARCH_PC_REGNUM) return LOONGARCH_PC_REGNUM; else return -1; } static loongarch_linux_nat_target the_loongarch_linux_nat_target; /* Wrapper functions. These are only used by libthread_db. */ void supply_gregset (struct regcache *regcache, const gdb_gregset_t *gregset) { loongarch_gregset.supply_regset (nullptr, regcache, -1, gregset, sizeof (gdb_gregset_t)); } void fill_gregset (const struct regcache *regcache, gdb_gregset_t *gregset, int regnum) { loongarch_gregset.collect_regset (nullptr, regcache, regnum, gregset, sizeof (gdb_gregset_t)); } void supply_fpregset (struct regcache *regcache, const gdb_fpregset_t *fpregset) { loongarch_fpregset.supply_regset (nullptr, regcache, -1, fpregset, sizeof (gdb_fpregset_t)); } void fill_fpregset (const struct regcache *regcache, gdb_fpregset_t *fpregset, int regnum) { loongarch_fpregset.collect_regset (nullptr, regcache, regnum, fpregset, sizeof (gdb_fpregset_t)); } /* Initialize LoongArch Linux native support. */ void _initialize_loongarch_linux_nat (); void _initialize_loongarch_linux_nat () { linux_target = &the_loongarch_linux_nat_target; add_inf_child_target (&the_loongarch_linux_nat_target); }