| /* Native-dependent code for GNU/Linux x86-64. | 
 |  | 
 |    Copyright (C) 2001-2022 Free Software Foundation, Inc. | 
 |    Contributed by Jiri Smid, SuSE Labs. | 
 |  | 
 |    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 <http://www.gnu.org/licenses/>.  */ | 
 |  | 
 | #include "defs.h" | 
 | #include "inferior.h" | 
 | #include "regcache.h" | 
 | #include "elf/common.h" | 
 | #include <sys/uio.h> | 
 | #include "nat/gdb_ptrace.h" | 
 | #include <asm/prctl.h> | 
 | #include <sys/reg.h> | 
 | #include "gregset.h" | 
 | #include "gdb_proc_service.h" | 
 |  | 
 | #include "amd64-nat.h" | 
 | #include "amd64-tdep.h" | 
 | #include "amd64-linux-tdep.h" | 
 | #include "i386-linux-tdep.h" | 
 | #include "gdbsupport/x86-xstate.h" | 
 |  | 
 | #include "x86-linux-nat.h" | 
 | #include "nat/linux-ptrace.h" | 
 | #include "nat/amd64-linux-siginfo.h" | 
 |  | 
 | /* This definition comes from prctl.h.  Kernels older than 2.5.64 | 
 |    do not have it.  */ | 
 | #ifndef PTRACE_ARCH_PRCTL | 
 | #define PTRACE_ARCH_PRCTL      30 | 
 | #endif | 
 |  | 
 | struct amd64_linux_nat_target final : public x86_linux_nat_target | 
 | { | 
 |   /* Add our register access methods.  */ | 
 |   void fetch_registers (struct regcache *, int) override; | 
 |   void store_registers (struct regcache *, int) override; | 
 |  | 
 |   bool low_siginfo_fixup (siginfo_t *ptrace, gdb_byte *inf, int direction) | 
 |     override; | 
 | }; | 
 |  | 
 | static amd64_linux_nat_target the_amd64_linux_nat_target; | 
 |  | 
 | /* Mapping between the general-purpose registers in GNU/Linux x86-64 | 
 |    `struct user' format and GDB's register cache layout for GNU/Linux | 
 |    i386. | 
 |  | 
 |    Note that most GNU/Linux x86-64 registers are 64-bit, while the | 
 |    GNU/Linux i386 registers are all 32-bit, but since we're | 
 |    little-endian we get away with that.  */ | 
 |  | 
 | /* From <sys/reg.h> on GNU/Linux i386.  */ | 
 | static int amd64_linux_gregset32_reg_offset[] = | 
 | { | 
 |   RAX * 8, RCX * 8,		/* %eax, %ecx */ | 
 |   RDX * 8, RBX * 8,		/* %edx, %ebx */ | 
 |   RSP * 8, RBP * 8,		/* %esp, %ebp */ | 
 |   RSI * 8, RDI * 8,		/* %esi, %edi */ | 
 |   RIP * 8, EFLAGS * 8,		/* %eip, %eflags */ | 
 |   CS * 8, SS * 8,		/* %cs, %ss */ | 
 |   DS * 8, ES * 8,		/* %ds, %es */ | 
 |   FS * 8, GS * 8,		/* %fs, %gs */ | 
 |   -1, -1, -1, -1, -1, -1, -1, -1, | 
 |   -1, -1, -1, -1, -1, -1, -1, -1, | 
 |   -1, -1, -1, -1, -1, -1, -1, -1, -1, | 
 |   -1, -1, -1, -1, -1, -1, -1, -1, | 
 |   -1, -1, -1, -1,		  /* MPX registers BND0 ... BND3.  */ | 
 |   -1, -1,			  /* MPX registers BNDCFGU, BNDSTATUS.  */ | 
 |   -1, -1, -1, -1, -1, -1, -1, -1, /* k0 ... k7 (AVX512)  */ | 
 |   -1, -1, -1, -1, -1, -1, -1, -1, /* zmm0 ... zmm7 (AVX512)  */ | 
 |   -1,				  /* PKEYS register PKRU  */ | 
 |   ORIG_RAX * 8			  /* "orig_eax"  */ | 
 | }; | 
 |  | 
 |  | 
 | /* Transfering the general-purpose registers between GDB, inferiors | 
 |    and core files.  */ | 
 |  | 
 | /* See amd64_collect_native_gregset.  This linux specific version handles | 
 |    issues with negative EAX values not being restored correctly upon syscall | 
 |    return when debugging 32-bit targets.  It has no effect on 64-bit | 
 |    targets.  */ | 
 |  | 
 | static void | 
 | amd64_linux_collect_native_gregset (const struct regcache *regcache, | 
 | 				    void *gregs, int regnum) | 
 | { | 
 |   amd64_collect_native_gregset (regcache, gregs, regnum); | 
 |  | 
 |   struct gdbarch *gdbarch = regcache->arch (); | 
 |   if (gdbarch_bfd_arch_info (gdbarch)->bits_per_word == 32) | 
 |     { | 
 |       /* Sign extend EAX value to avoid potential syscall restart | 
 | 	 problems.   | 
 |  | 
 | 	 On Linux, when a syscall is interrupted by a signal, the | 
 | 	 (kernel function implementing the) syscall may return | 
 | 	 -ERESTARTSYS when a signal occurs.  Doing so indicates that | 
 | 	 the syscall is restartable.  Then, depending on settings | 
 | 	 associated with the signal handler, and after the signal | 
 | 	 handler is called, the kernel can then either return -EINTR | 
 | 	 or it can cause the syscall to be restarted.  We are | 
 | 	 concerned with the latter case here. | 
 | 	  | 
 | 	 On (32-bit) i386, the status (-ERESTARTSYS) is placed in the | 
 | 	 EAX register.  When debugging a 32-bit process from a 64-bit | 
 | 	 (amd64) GDB, the debugger fetches 64-bit registers even | 
 | 	 though the process being debugged is only 32-bit.  The | 
 | 	 register cache is only 32 bits wide though; GDB discards the | 
 | 	 high 32 bits when placing 64-bit values in the 32-bit | 
 | 	 regcache.  Normally, this is not a problem since the 32-bit | 
 | 	 process should only care about the lower 32-bit portions of | 
 | 	 these registers.  That said, it can happen that the 64-bit | 
 | 	 value being restored will be different from the 64-bit value | 
 | 	 that was originally retrieved from the kernel.  The one place | 
 | 	 (that we know of) where it does matter is in the kernel's | 
 | 	 syscall restart code.  The kernel's code for restarting a | 
 | 	 syscall after a signal expects to see a negative value | 
 | 	 (specifically -ERESTARTSYS) in the 64-bit RAX register in | 
 | 	 order to correctly cause a syscall to be restarted. | 
 | 	  | 
 | 	 The call to amd64_collect_native_gregset, above, is setting | 
 | 	 the high 32 bits of RAX (and other registers too) to 0.  For | 
 | 	 syscall restart, we need to sign extend EAX so that RAX will | 
 | 	 appear as a negative value when EAX is set to -ERESTARTSYS.  | 
 | 	 This in turn will cause the signal handling code in the | 
 | 	 kernel to recognize -ERESTARTSYS which will in turn cause the | 
 | 	 syscall to be restarted. | 
 |  | 
 | 	 The test case gdb.base/interrupt.exp tests for this problem. | 
 | 	 Without this sign extension code in place, it'll show | 
 | 	 a number of failures when testing against unix/-m32.  */ | 
 |  | 
 |       if (regnum == -1 || regnum == I386_EAX_REGNUM) | 
 | 	{ | 
 | 	  void *ptr = ((gdb_byte *) gregs  | 
 | 		       + amd64_linux_gregset32_reg_offset[I386_EAX_REGNUM]); | 
 |  | 
 | 	  *(int64_t *) ptr = *(int32_t *) ptr; | 
 | 	} | 
 |     } | 
 | } | 
 |  | 
 | /* Fill GDB's register cache with the general-purpose register values | 
 |    in *GREGSETP.  */ | 
 |  | 
 | void | 
 | supply_gregset (struct regcache *regcache, const elf_gregset_t *gregsetp) | 
 | { | 
 |   amd64_supply_native_gregset (regcache, gregsetp, -1); | 
 | } | 
 |  | 
 | /* Fill register REGNUM (if it is a general-purpose register) in | 
 |    *GREGSETP with the value in GDB's register cache.  If REGNUM is -1, | 
 |    do this for all registers.  */ | 
 |  | 
 | void | 
 | fill_gregset (const struct regcache *regcache, | 
 | 	      elf_gregset_t *gregsetp, int regnum) | 
 | { | 
 |   amd64_linux_collect_native_gregset (regcache, gregsetp, regnum); | 
 | } | 
 |  | 
 | /* Transfering floating-point registers between GDB, inferiors and cores.  */ | 
 |  | 
 | /* Fill GDB's register cache with the floating-point and SSE register | 
 |    values in *FPREGSETP.  */ | 
 |  | 
 | void | 
 | supply_fpregset (struct regcache *regcache, const elf_fpregset_t *fpregsetp) | 
 | { | 
 |   amd64_supply_fxsave (regcache, -1, fpregsetp); | 
 | } | 
 |  | 
 | /* Fill register REGNUM (if it is a floating-point or SSE register) in | 
 |    *FPREGSETP with the value in GDB's register cache.  If REGNUM is | 
 |    -1, do this for all registers.  */ | 
 |  | 
 | void | 
 | fill_fpregset (const struct regcache *regcache, | 
 | 	       elf_fpregset_t *fpregsetp, int regnum) | 
 | { | 
 |   amd64_collect_fxsave (regcache, regnum, fpregsetp); | 
 | } | 
 |  | 
 |  | 
 | /* Transferring arbitrary registers between GDB and inferior.  */ | 
 |  | 
 | /* Fetch register REGNUM from the child process.  If REGNUM is -1, do | 
 |    this for all registers (including the floating point and SSE | 
 |    registers).  */ | 
 |  | 
 | void | 
 | amd64_linux_nat_target::fetch_registers (struct regcache *regcache, int regnum) | 
 | { | 
 |   struct gdbarch *gdbarch = regcache->arch (); | 
 |   int tid; | 
 |  | 
 |   /* GNU/Linux LWP ID's are process ID's.  */ | 
 |   tid = regcache->ptid ().lwp (); | 
 |   if (tid == 0) | 
 |     tid = regcache->ptid ().pid (); /* Not a threaded program.  */ | 
 |  | 
 |   if (regnum == -1 || amd64_native_gregset_supplies_p (gdbarch, regnum)) | 
 |     { | 
 |       elf_gregset_t regs; | 
 |  | 
 |       if (ptrace (PTRACE_GETREGS, tid, 0, (long) ®s) < 0) | 
 | 	perror_with_name (_("Couldn't get registers")); | 
 |  | 
 |       amd64_supply_native_gregset (regcache, ®s, -1); | 
 |       if (regnum != -1) | 
 | 	return; | 
 |     } | 
 |  | 
 |   if (regnum == -1 || !amd64_native_gregset_supplies_p (gdbarch, regnum)) | 
 |     { | 
 |       elf_fpregset_t fpregs; | 
 |  | 
 |       if (have_ptrace_getregset == TRIBOOL_TRUE) | 
 | 	{ | 
 | 	  char xstateregs[X86_XSTATE_MAX_SIZE]; | 
 | 	  struct iovec iov; | 
 |  | 
 | 	  /* Pre-4.14 kernels have a bug (fixed by commit 0852b374173b | 
 | 	     "x86/fpu: Add FPU state copying quirk to handle XRSTOR failure on | 
 | 	     Intel Skylake CPUs") that sometimes causes the mxcsr location in | 
 | 	     xstateregs not to be copied by PTRACE_GETREGSET.  Make sure that | 
 | 	     the location is at least initialized with a defined value.  */ | 
 | 	  memset (xstateregs, 0, sizeof (xstateregs)); | 
 | 	  iov.iov_base = xstateregs; | 
 | 	  iov.iov_len = sizeof (xstateregs); | 
 | 	  if (ptrace (PTRACE_GETREGSET, tid, | 
 | 		      (unsigned int) NT_X86_XSTATE, (long) &iov) < 0) | 
 | 	    perror_with_name (_("Couldn't get extended state status")); | 
 |  | 
 | 	  amd64_supply_xsave (regcache, -1, xstateregs); | 
 | 	} | 
 |       else | 
 | 	{ | 
 | 	  if (ptrace (PTRACE_GETFPREGS, tid, 0, (long) &fpregs) < 0) | 
 | 	    perror_with_name (_("Couldn't get floating point status")); | 
 |  | 
 | 	  amd64_supply_fxsave (regcache, -1, &fpregs); | 
 | 	} | 
 |     } | 
 | } | 
 |  | 
 | /* Store register REGNUM back into the child process.  If REGNUM is | 
 |    -1, do this for all registers (including the floating-point and SSE | 
 |    registers).  */ | 
 |  | 
 | void | 
 | amd64_linux_nat_target::store_registers (struct regcache *regcache, int regnum) | 
 | { | 
 |   struct gdbarch *gdbarch = regcache->arch (); | 
 |   int tid; | 
 |  | 
 |   /* GNU/Linux LWP ID's are process ID's.  */ | 
 |   tid = regcache->ptid ().lwp (); | 
 |   if (tid == 0) | 
 |     tid = regcache->ptid ().pid (); /* Not a threaded program.  */ | 
 |  | 
 |   if (regnum == -1 || amd64_native_gregset_supplies_p (gdbarch, regnum)) | 
 |     { | 
 |       elf_gregset_t regs; | 
 |  | 
 |       if (ptrace (PTRACE_GETREGS, tid, 0, (long) ®s) < 0) | 
 | 	perror_with_name (_("Couldn't get registers")); | 
 |  | 
 |       amd64_linux_collect_native_gregset (regcache, ®s, regnum); | 
 |  | 
 |       if (ptrace (PTRACE_SETREGS, tid, 0, (long) ®s) < 0) | 
 | 	perror_with_name (_("Couldn't write registers")); | 
 |  | 
 |       if (regnum != -1) | 
 | 	return; | 
 |     } | 
 |  | 
 |   if (regnum == -1 || !amd64_native_gregset_supplies_p (gdbarch, regnum)) | 
 |     { | 
 |       elf_fpregset_t fpregs; | 
 |  | 
 |       if (have_ptrace_getregset == TRIBOOL_TRUE) | 
 | 	{ | 
 | 	  char xstateregs[X86_XSTATE_MAX_SIZE]; | 
 | 	  struct iovec iov; | 
 |  | 
 | 	  iov.iov_base = xstateregs; | 
 | 	  iov.iov_len = sizeof (xstateregs); | 
 | 	  if (ptrace (PTRACE_GETREGSET, tid, | 
 | 		      (unsigned int) NT_X86_XSTATE, (long) &iov) < 0) | 
 | 	    perror_with_name (_("Couldn't get extended state status")); | 
 |  | 
 | 	  amd64_collect_xsave (regcache, regnum, xstateregs, 0); | 
 |  | 
 | 	  if (ptrace (PTRACE_SETREGSET, tid, | 
 | 		      (unsigned int) NT_X86_XSTATE, (long) &iov) < 0) | 
 | 	    perror_with_name (_("Couldn't write extended state status")); | 
 | 	} | 
 |       else | 
 | 	{ | 
 | 	  if (ptrace (PTRACE_GETFPREGS, tid, 0, (long) &fpregs) < 0) | 
 | 	    perror_with_name (_("Couldn't get floating point status")); | 
 |  | 
 | 	  amd64_collect_fxsave (regcache, regnum, &fpregs); | 
 |  | 
 | 	  if (ptrace (PTRACE_SETFPREGS, tid, 0, (long) &fpregs) < 0) | 
 | 	    perror_with_name (_("Couldn't write floating point status")); | 
 | 	} | 
 |     } | 
 | } | 
 |  | 
 |  | 
 | /* This function is called by libthread_db as part of its handling of | 
 |    a request for a thread's local storage address.  */ | 
 |  | 
 | ps_err_e | 
 | ps_get_thread_area (struct ps_prochandle *ph, | 
 | 		    lwpid_t lwpid, int idx, void **base) | 
 | { | 
 |   if (gdbarch_bfd_arch_info (ph->thread->inf->gdbarch)->bits_per_word == 32) | 
 |     { | 
 |       unsigned int base_addr; | 
 |       ps_err_e result; | 
 |  | 
 |       result = x86_linux_get_thread_area (lwpid, (void *) (long) idx, | 
 | 					  &base_addr); | 
 |       if (result == PS_OK) | 
 | 	{ | 
 | 	  /* Extend the value to 64 bits.  Here it's assumed that | 
 | 	     a "long" and a "void *" are the same.  */ | 
 | 	  (*base) = (void *) (long) base_addr; | 
 | 	} | 
 |       return result; | 
 |     } | 
 |   else | 
 |     { | 
 |  | 
 |       /* FIXME: ezannoni-2003-07-09 see comment above about include | 
 | 	 file order.  We could be getting bogus values for these two.  */ | 
 |       gdb_assert (FS < ELF_NGREG); | 
 |       gdb_assert (GS < ELF_NGREG); | 
 |       switch (idx) | 
 | 	{ | 
 | 	case FS: | 
 | 	    { | 
 | 	      unsigned long fs; | 
 | 	      errno = 0; | 
 | 	      fs = ptrace (PTRACE_PEEKUSER, lwpid, | 
 | 			   offsetof (struct user_regs_struct, fs_base), 0); | 
 | 	      if (errno == 0) | 
 | 		{ | 
 | 		  *base = (void *) fs; | 
 | 		  return PS_OK; | 
 | 		} | 
 | 	    } | 
 |  | 
 | 	  break; | 
 |  | 
 | 	case GS: | 
 | 	    { | 
 | 	      unsigned long gs; | 
 | 	      errno = 0; | 
 | 	      gs = ptrace (PTRACE_PEEKUSER, lwpid, | 
 | 			   offsetof (struct user_regs_struct, gs_base), 0); | 
 | 	      if (errno == 0) | 
 | 		{ | 
 | 		  *base = (void *) gs; | 
 | 		  return PS_OK; | 
 | 		} | 
 | 	    } | 
 | 	  break; | 
 |  | 
 | 	default:                   /* Should not happen.  */ | 
 | 	  return PS_BADADDR; | 
 | 	} | 
 |     } | 
 |   return PS_ERR;               /* ptrace failed.  */ | 
 | } | 
 |  | 
 |  | 
 | /* Convert a ptrace/host siginfo object, into/from the siginfo in the | 
 |    layout of the inferiors' architecture.  Returns true if any | 
 |    conversion was done; false otherwise.  If DIRECTION is 1, then copy | 
 |    from INF to PTRACE.  If DIRECTION is 0, copy from PTRACE to | 
 |    INF.  */ | 
 |  | 
 | bool | 
 | amd64_linux_nat_target::low_siginfo_fixup (siginfo_t *ptrace, | 
 | 					   gdb_byte *inf, | 
 | 					   int direction) | 
 | { | 
 |   struct gdbarch *gdbarch = get_frame_arch (get_current_frame ()); | 
 |  | 
 |   /* Is the inferior 32-bit?  If so, then do fixup the siginfo | 
 |      object.  */ | 
 |   if (gdbarch_bfd_arch_info (gdbarch)->bits_per_word == 32) | 
 |     return amd64_linux_siginfo_fixup_common (ptrace, inf, direction, | 
 | 					     FIXUP_32); | 
 |   /* No fixup for native x32 GDB.  */ | 
 |   else if (gdbarch_addr_bit (gdbarch) == 32 && sizeof (void *) == 8) | 
 |     return amd64_linux_siginfo_fixup_common (ptrace, inf, direction, | 
 | 					     FIXUP_X32); | 
 |   else | 
 |     return false; | 
 | } | 
 |  | 
 | void _initialize_amd64_linux_nat (); | 
 | void | 
 | _initialize_amd64_linux_nat () | 
 | { | 
 |   amd64_native_gregset32_reg_offset = amd64_linux_gregset32_reg_offset; | 
 |   amd64_native_gregset32_num_regs = I386_LINUX_NUM_REGS; | 
 |   amd64_native_gregset64_reg_offset = amd64_linux_gregset_reg_offset; | 
 |   amd64_native_gregset64_num_regs = AMD64_LINUX_NUM_REGS; | 
 |  | 
 |   gdb_assert (ARRAY_SIZE (amd64_linux_gregset32_reg_offset) | 
 | 	      == amd64_native_gregset32_num_regs); | 
 |  | 
 |   linux_target = &the_amd64_linux_nat_target; | 
 |  | 
 |   /* Add the target.  */ | 
 |   add_inf_child_target (linux_target); | 
 | } |