OSDN Git Service

2000-01-29 Mark Kettenis <kettenis@gnu.org>
[pf3gnuchains/pf3gnuchains3x.git] / gdb / i386-linux-nat.c
index 8c63a94..88b6ba2 100644 (file)
@@ -1,26 +1,27 @@
 /* Native-dependent code for Linux running on i386's, for GDB.
 
-This file is part of GDB.
+   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 2 of the License, or
-(at your option) any later version.
+   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 2 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.
+   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, write to the Free Software
-Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.  */
 
 #include "defs.h"
 #include "inferior.h"
 #include "gdbcore.h"
 
-/* For i386_linux_skip_solib_resolver */
+/* For i386_linux_skip_solib_resolver */
 #include "symtab.h"
 #include "frame.h"
 #include "symfile.h"
@@ -34,262 +35,701 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
 #include <sys/reg.h>
 #endif
 
-/* This is a duplicate of the table in i386-xdep.c. */
+/* On Linux, threads are implemented as pseudo-processes, in which
+   case we may be tracing more than one process at a time.  In that
+   case, inferior_pid will contain the main process ID and the
+   individual thread (process) ID mashed together.  These macros are
+   used to separate them out.  These definitions should be overridden
+   if thread support is included.  */
 
+#if !defined (PIDGET)  /* Default definition for PIDGET/TIDGET.  */
+#define PIDGET(PID)    PID
+#define TIDGET(PID)    0
+#endif
+
+
+/* The register sets used in Linux ELF core-dumps are identical to the
+   register sets in `struct user' that is used for a.out core-dumps,
+   and is also used by `ptrace'.  The corresponding types are
+   `elf_gregset_t' for the general-purpose registers (with
+   `elf_greg_t' the type of a single GP register) and `elf_fpregset_t'
+   for the floating-point registers.
+
+   Those types used to be available under the names `gregset_t' and
+   `fpregset_t' too, and this file used those names in the past.  But
+   those names are now used for the register sets used in the
+   `mcontext_t' type, and have a different size and layout.  */
+
+/* Mapping between the general-purpose registers in `struct user'
+   format and GDB's register array layout.  */
 static int regmap[] = 
 {
   EAX, ECX, EDX, EBX,
   UESP, EBP, ESI, EDI,
   EIP, EFL, CS, SS,
-  DS, ES, FS, GS,
+  DS, ES, FS, GS
 };
 
+/* Which ptrace request retrieves which registers?
+   These apply to the corresponding SET requests as well.  */
+#define GETREGS_SUPPLIES(regno) \
+  (0 <= (regno) && (regno) <= 15)
+#define GETFPREGS_SUPPLIES(regno) \
+  (FP0_REGNUM <= (regno) && (regno) <= LAST_FPU_CTRL_REGNUM)
+#define GETXFPREGS_SUPPLIES(regno) \
+  (FP0_REGNUM <= (regno) && (regno) <= MXCSR_REGNUM)
+
+/* Does the current host support the GETXFPREGS request?  The header
+   file may or may not define it, and even if it is defined, the
+   kernel will return EIO if it's running on a pre-SSE processor.
+
+   PTRACE_GETXFPREGS is a Cygnus invention, since we wrote our own
+   Linux kernel patch for SSE support.  That patch may or may not
+   actually make it into the official distribution.  If you find that
+   years have gone by since this stuff was added, and Linux isn't
+   using PTRACE_GETXFPREGS, that means that our patch didn't make it,
+   and you can delete this, and the related code.
+
+   My instinct is to attach this to some architecture- or
+   target-specific data structure, but really, a particular GDB
+   process can only run on top of one kernel at a time.  So it's okay
+   for this to be a simple variable.  */
+int have_ptrace_getxfpregs =
+#ifdef HAVE_PTRACE_GETXFPREGS
+  1
+#else
+  0
+#endif
+;
+
+\f
+/* Transfering the general-purpose registers between GDB, inferiors
+   and core files.  */
+
+/* Fill GDB's register array with the genereal-purpose register values
+   in *GREGSETP.  */
+
+void
+supply_gregset (elf_gregset_t *gregsetp)
+{
+  elf_greg_t *regp = (elf_greg_t *) gregsetp;
+  int regi;
+
+  for (regi = 0; regi < NUM_GREGS; regi++)
+    supply_register (regi, (char *) (regp + regmap[regi]));
+}
+
+/* Convert the valid general-purpose register values in GDB's register
+   array to `struct user' format and store them in *GREGSETP.  The
+   array VALID indicates which register values are valid.  If VALID is
+   NULL, all registers are assumed to be valid.  */
 
-/*  FIXME:  These routine absolutely depends upon (NUM_REGS - NUM_FREGS)
-    being less than or equal to the number of registers that can be stored
-    in a gregset_t.  Note that with the current scheme there will typically
-    be more registers actually stored in a gregset_t that what we know
-    about.  This is bogus and should be fixed. */
+static void
+convert_to_gregset (elf_gregset_t *gregsetp, signed char *valid)
+{
+  elf_greg_t *regp = (elf_greg_t *) gregsetp;
+  int regi;
 
-/*  Given a pointer to a general register set in /proc format (gregset_t *),
-    unpack the register contents and supply them as gdb's idea of the current
-    register values. */
+  for (regi = 0; regi < NUM_GREGS; regi++)
+    if (! valid || valid[regi])
+      *(regp + regmap[regi]) = * (int *) &registers[REGISTER_BYTE (regi)];
+}
 
+/* Fill register REGNO (if it is a general-purpose register) in
+   *GREGSETPS with the value in GDB's register array.  If REGNO is -1,
+   do this for all registers.  */
 void
-supply_gregset (gregsetp)
-     gregset_t *gregsetp;
+fill_gregset (elf_gregset_t *gregsetp, int regno)
 {
-  register int regi;
-  register greg_t *regp = (greg_t *) gregsetp;
+  if (regno == -1)
+    {
+      convert_to_gregset (gregsetp, NULL);
+      return;
+    }
 
-  for (regi = 0 ; regi < (NUM_REGS - NUM_FREGS) ; regi++)
+  if (GETREGS_SUPPLIES (regno))
     {
-      supply_register (regi, (char *) (regp + regmap[regi]));
+      signed char valid[NUM_GREGS];
+
+      memset (valid, 0, sizeof (valid));
+      valid[regno] = 1;
+
+      convert_to_gregset (gregsetp, valid);
     }
 }
 
-void
-fill_gregset (gregsetp, regno)
-     gregset_t *gregsetp;
-     int regno;
+/* Fetch all general-purpose registers from process/thread TID and
+   store their values in GDB's register array.  */
+
+static void
+fetch_regs (int tid)
 {
-  int regi;
-  register greg_t *regp = (greg_t *) gregsetp;
+  elf_gregset_t regs;
+  int ret;
 
-  for (regi = 0 ; regi < (NUM_REGS - NUM_FREGS) ; regi++)
+  ret = ptrace (PTRACE_GETREGS, tid, 0, (int) &regs);
+  if (ret < 0)
     {
-      if ((regno == -1) || (regno == regi))
-       {
-         *(regp + regmap[regi]) = *(int *) &registers[REGISTER_BYTE (regi)];
-       }
+      warning ("Couldn't get registers.");
+      return;
     }
+
+  supply_gregset (&regs);
 }
 
+/* Store all valid general-purpose registers in GDB's register array
+   into the process/thread specified by TID.  */
+
+static void
+store_regs (int tid)
+{
+  elf_gregset_t regs;
+  int ret;
+
+  ret = ptrace (PTRACE_GETREGS, tid, 0, (int) &regs);
+  if (ret < 0)
+    {
+      warning ("Couldn't get registers.");
+      return;
+    }
 
-/*  Given a pointer to a floating point register set in (fpregset_t *)
-    format, unpack the register contents and supply them as gdb's
-    idea of the current floating point register values. */
+  convert_to_gregset (&regs, register_valid);
+
+  ret = ptrace (PTRACE_SETREGS, tid, 0, (int) &regs);
+  if (ret < 0)
+    {
+      warning ("Couldn't write registers.");
+      return;
+    }
+}
+
+\f
+/* Transfering floating-point registers between GDB, inferiors and cores.  */
+
+/* What is the address of st(N) within the floating-point register set F?  */
+#define FPREG_ADDR(f, n) ((char *) &(f)->st_space + (n) * 10)
+
+/* Fill GDB's register array with the floating-point register values in
+   *FPREGSETP.  */
 
 void 
-supply_fpregset (fpregsetp)
-     fpregset_t *fpregsetp;
+supply_fpregset (elf_fpregset_t *fpregsetp)
+{
+  int reg;
+
+  /* Supply the floating-point registers.  */
+  for (reg = 0; reg < 8; reg++)
+    supply_register (FP0_REGNUM + reg, FPREG_ADDR (fpregsetp, reg));
+
+  supply_register (FCTRL_REGNUM, (char *) &fpregsetp->cwd);
+  supply_register (FSTAT_REGNUM, (char *) &fpregsetp->swd);
+  supply_register (FTAG_REGNUM,  (char *) &fpregsetp->twd);
+  supply_register (FCOFF_REGNUM, (char *) &fpregsetp->fip);
+  supply_register (FDS_REGNUM,   (char *) &fpregsetp->fos);
+  supply_register (FDOFF_REGNUM, (char *) &fpregsetp->foo);
+  
+  /* Extract the code segment and opcode from the  "fcs" member.  */
+  {
+    long l;
+
+    l = fpregsetp->fcs & 0xffff;
+    supply_register (FCS_REGNUM, (char *) &l);
+
+    l = (fpregsetp->fcs >> 16) & ((1 << 11) - 1);
+    supply_register (FOP_REGNUM, (char *) &l);
+  }
+}
+
+/* Convert the valid floating-point register values in GDB's register
+   array to `struct user' format and store them in *FPREGSETP.  The
+   array VALID indicates which register values are valid.  If VALID is
+   NULL, all registers are assumed to be valid.  */
+
+static void
+convert_to_fpregset (elf_fpregset_t *fpregsetp, signed char *valid)
+{
+  int reg;
+
+  /* Fill in the floating-point registers.  */
+  for (reg = 0; reg < 8; reg++)
+    if (!valid || valid[reg])
+      memcpy (FPREG_ADDR (fpregsetp, reg),
+             &registers[REGISTER_BYTE (FP0_REGNUM + reg)],
+             REGISTER_RAW_SIZE(FP0_REGNUM + reg));
+
+#define fill(MEMBER, REGNO)                                            \
+  if (! valid || valid[(REGNO)])                                       \
+    memcpy (&fpregsetp->MEMBER, &registers[REGISTER_BYTE (REGNO)],     \
+           sizeof (fpregsetp->MEMBER))
+
+  fill (cwd, FCTRL_REGNUM);
+  fill (swd, FSTAT_REGNUM);
+  fill (twd, FTAG_REGNUM);
+  fill (fip, FCOFF_REGNUM);
+  fill (foo, FDOFF_REGNUM);
+  fill (fos, FDS_REGNUM);
+
+#undef fill
+
+  if (! valid || valid[FCS_REGNUM])
+    fpregsetp->fcs
+      = ((fpregsetp->fcs & ~0xffff)
+        | (* (int *) &registers[REGISTER_BYTE (FCS_REGNUM)] & 0xffff));
+
+  if (! valid || valid[FOP_REGNUM])
+    fpregsetp->fcs
+      = ((fpregsetp->fcs & 0xffff)
+        | ((*(int *) &registers[REGISTER_BYTE (FOP_REGNUM)] & ((1 << 11) - 1))
+           << 16));
+}
+
+/* Fill register REGNO (if it is a floating-point register) in
+   *FPREGSETP with the value in GDB's register array.  If REGNO is -1,
+   do this for all registers.  */
+
+void
+fill_fpregset (elf_fpregset_t *fpregsetp, int regno)
 {
-  register int regi;
-  char *from;
-  from = (char *) &(fpregsetp->st_space[0]);
-  for (regi = FPSTART_REGNUM ; regi <= FPEND_REGNUM ; regi++)
+  if (regno == -1)
     {
-      supply_register(regi, from);
-      from += REGISTER_RAW_SIZE(regi);
+      convert_to_fpregset (fpregsetp, NULL);
+      return;
+    }
+
+  if (GETFPREGS_SUPPLIES(regno))
+    {
+      signed char valid[MAX_NUM_REGS];
+      
+      memset (valid, 0, sizeof (valid));
+      valid[regno] = 1;
+             
+      convert_to_fpregset (fpregsetp, valid);
     }
 }
 
-/*  Given a pointer to a floating point register set in (fpregset_t *)
-    format, update all of the registers from gdb's idea
-    of the current floating point register set. */
+/* Fetch all floating-point registers from process/thread TID and store
+   thier values in GDB's register array.  */
 
-void
-fill_fpregset (fpregsetp, regno)
-     fpregset_t *fpregsetp;
-     int regno;
+static void
+fetch_fpregs (int tid)
 {
-  int regi;
-  char *to;
-  char *from;
+  elf_fpregset_t fpregs;
+  int ret;
 
-  to = (char *) &(fpregsetp->st_space[0]);
-  for (regi = FPSTART_REGNUM ; regi <= FPEND_REGNUM ; regi++)
+  ret = ptrace (PTRACE_GETFPREGS, tid, 0, (int) &fpregs);
+  if (ret < 0)
     {
-      from = (char *) &registers[REGISTER_BYTE (regi)];
-      memcpy (to, from, REGISTER_RAW_SIZE (regi));
-      to += REGISTER_RAW_SIZE(regi);
+      warning ("Couldn't get floating point status.");
+      return;
     }
+
+  supply_fpregset (&fpregs);
 }
 
-/*
-  Get the whole floating point state of the process and
-  store the floating point stack into registers[].
-  */
+/* Store all valid floating-point registers in GDB's register array
+   into the process/thread specified by TID.  */
+
 static void
-fetch_fpregs(void)
+store_fpregs (int tid)
 {
-  int ret, regno;
-  char buf[FPREG_BYTES];
+  elf_fpregset_t fpregs;
+  int ret;
 
-  ret = ptrace (PTRACE_GETFPREGS, inferior_pid,        0, (int)buf);
-  if ( ret < 0 )
+  ret = ptrace (PTRACE_GETFPREGS, tid, 0, (int) &fpregs);
+  if (ret < 0)
     {
-      warning ("Couldn't get floating point status");
+      warning ("Couldn't get floating point status.");
       return;
     }
 
-  for ( regno = 0; regno < NUM_FREGS; regno++ )
+  convert_to_fpregset (&fpregs, register_valid);
+
+  ret = ptrace (PTRACE_SETFPREGS, tid, 0, (int) &fpregs);
+  if (ret < 0)
     {
-      if ( regno < 7 )
-       supply_register (NUM_REGS-NUM_FREGS+regno, buf + regno*4);
-      else
-       supply_register (NUM_REGS-NUM_FREGS+regno,
-                        buf + FPENV_BYTES + (regno-7)*FPREG_RAW_SIZE);
+      warning ("Couldn't write floating point status.");
+      return;
     }
+}
+
+\f
+/* Transfering floating-point and SSE registers to and from GDB.  */
 
+/* PTRACE_GETXFPREGS is a Cygnus invention, since we wrote our own
+   Linux kernel patch for SSE support.  That patch may or may not
+   actually make it into the official distribution.  If you find that
+   years have gone by since this code was added, and Linux isn't using
+   PTRACE_GETXFPREGS, that means that our patch didn't make it, and
+   you can delete this code.  */
+
+#ifdef HAVE_PTRACE_GETXFPREGS
+
+/* Fill GDB's register array with the floating-point and SSE register
+   values in *XFPREGS.  */
+
+static void
+supply_xfpregset (struct user_xfpregs_struct *xfpregs)
+{
+  int reg;
+
+  /* Supply the floating-point registers.  */
+  for (reg = 0; reg < 8; reg++)
+    supply_register (FP0_REGNUM + reg, (char *) &xfpregs->st_space[reg]);
+
+  {
+    supply_register (FCTRL_REGNUM, (char *) &xfpregs->cwd);
+    supply_register (FSTAT_REGNUM, (char *) &xfpregs->swd);
+    supply_register (FTAG_REGNUM,  (char *) &xfpregs->twd);
+    supply_register (FCOFF_REGNUM, (char *) &xfpregs->fip);
+    supply_register (FDS_REGNUM,   (char *) &xfpregs->fos);
+    supply_register (FDOFF_REGNUM, (char *) &xfpregs->foo);
+  
+    /* Extract the code segment and opcode from the  "fcs" member.  */
+    {
+      long l;
+      
+      l = xfpregs->fcs & 0xffff;
+      supply_register (FCS_REGNUM, (char *) &l);
+
+      l = (xfpregs->fcs >> 16) & ((1 << 11) - 1);
+      supply_register (FOP_REGNUM, (char *) &l);
+    }
+  }
+
+  /* Supply the SSE registers.  */
+  for (reg = 0; reg < 8; reg++)
+    supply_register (XMM0_REGNUM + reg, (char *) &xfpregs->xmm_space[reg]);
+  supply_register (MXCSR_REGNUM, (char *) &xfpregs->mxcsr);
 }
 
+/* Convert the valid floating-point and SSE registers in GDB's
+   register array to `struct user' format and store them in *XFPREGS.
+   The array VALID indicates which registers are valid.  If VALID is
+   NULL, all registers are assumed to be valid.  */
 
-/*
-  Get the whole floating point state of the process and
-  replace the contents from registers[].
-  */
 static void
-store_fpregs(void)
+convert_to_xfpregset (struct user_xfpregs_struct *xfpregs,
+                     signed char *valid)
+{
+  int reg;
+
+  /* Fill in the floating-point registers.  */
+  for (reg = 0; reg < 8; reg++)
+    if (!valid || valid[reg])
+      memcpy (&xfpregs->st_space[reg],
+             &registers[REGISTER_BYTE (FP0_REGNUM + reg)],
+             REGISTER_RAW_SIZE(FP0_REGNUM + reg));
+
+#define fill(MEMBER, REGNO)                                            \
+  if (! valid || valid[(REGNO)])                                       \
+    memcpy (&xfpregs->MEMBER, &registers[REGISTER_BYTE (REGNO)],       \
+           sizeof (xfpregs->MEMBER))
+
+  fill (cwd, FCTRL_REGNUM);
+  fill (swd, FSTAT_REGNUM);
+  fill (twd, FTAG_REGNUM);
+  fill (fip, FCOFF_REGNUM);
+  fill (foo, FDOFF_REGNUM);
+  fill (fos, FDS_REGNUM);
+
+#undef fill
+
+  if (! valid || valid[FCS_REGNUM])
+    xfpregs->fcs
+      = ((xfpregs->fcs & ~0xffff)
+        | (* (int *) &registers[REGISTER_BYTE (FCS_REGNUM)] & 0xffff));
+
+  if (! valid || valid[FOP_REGNUM])
+    xfpregs->fcs
+      = ((xfpregs->fcs & 0xffff)
+        | ((*(int *) &registers[REGISTER_BYTE (FOP_REGNUM)] & ((1 << 11) - 1))
+           << 16));
+
+  /* Fill in the XMM registers.  */
+  for (reg = 0; reg < 8; reg++)
+    if (! valid || valid[reg])
+      memcpy (&xfpregs->xmm_space[reg],
+             &registers[REGISTER_BYTE (XMM0_REGNUM + reg)],
+             REGISTER_RAW_SIZE (XMM0_REGNUM + reg));
+}
+
+/* Fetch all registers covered by the PTRACE_SETXFPREGS request from
+   process/thread TID and store their values in GDB's register array.
+   Return non-zero if successful, zero otherwise.  */
+
+static int
+fetch_xfpregs (int tid)
 {
-  int ret, regno;
-  char buf[FPREG_BYTES];
+  struct user_xfpregs_struct xfpregs;
+  int ret;
 
-  ret = ptrace (PTRACE_GETFPREGS, inferior_pid,        0, (int)buf);
-  if ( ret < 0 )
+  if (! have_ptrace_getxfpregs) 
+    return 0;
+
+  ret = ptrace (PTRACE_GETXFPREGS, tid, 0, &xfpregs);
+  if (ret == -1)
     {
-      warning ("Couldn't get floating point status");
-      return;
+      if (errno == EIO)
+       {
+         have_ptrace_getxfpregs = 0;
+         return 0;
+       }
+
+      warning ("Couldn't read floating-point and SSE registers.");
+      return 0;
     }
 
-  for ( regno = 0; regno < NUM_FREGS; regno++ )
+  supply_xfpregset (&xfpregs);
+  return 1;
+}
+
+/* Store all valid registers in GDB's register array covered by the
+   PTRACE_SETXFPREGS request into the process/thread specified by TID.
+   Return non-zero if successful, zero otherwise.  */
+
+static int
+store_xfpregs (int tid)
+{
+  struct user_xfpregs_struct xfpregs;
+  int ret;
+
+  if (! have_ptrace_getxfpregs)
+    return 0;
+
+  ret = ptrace (PTRACE_GETXFPREGS, tid, 0, &xfpregs);
+  if (ret == -1)
     {
-      if ( register_valid[regno] )
+      if (errno == EIO)
        {
-         if ( regno < 7 )
-           {
-             read_register_gen (NUM_REGS-NUM_FREGS+regno,
-                                buf + regno*4);
-           }
-         else
-           {
-             read_register_gen (NUM_REGS-NUM_FREGS+regno,
-                                buf + FPENV_BYTES + (regno-7)*FPREG_RAW_SIZE);
-           }
+         have_ptrace_getxfpregs = 0;
+         return 0;
        }
+
+      warning ("Couldn't read floating-point and SSE registers.");
+      return 0;
     }
 
-  ret = ptrace (PTRACE_SETFPREGS, inferior_pid, 0, (int)buf);
-  if ( ret < 0 )
+  convert_to_xfpregset (&xfpregs, register_valid);
+
+  if (ptrace (PTRACE_SETXFPREGS, tid, 0, &xfpregs) < 0)
     {
-      warning ("Couldn't write floating point status");
-      return;
+      warning ("Couldn't write floating-point and SSE registers.");
+      return 0;
     }
 
+  return 1;
 }
 
+/* Fill the XMM registers in the register array with dummy values.  For
+   cases where we don't have access to the XMM registers.  I think
+   this is cleaner than printing a warning.  For a cleaner solution,
+   we should gdbarchify the i386 family.  */
 
-/*
-  Get state of all non-fp registers of the process and
-  store into registers[].
-  */
 static void
-fetch_regs(void)
+dummy_sse_values (void)
 {
-  int ret, regno;
-  char buf[17*sizeof(unsigned int)];
+  /* C doesn't have a syntax for NaN's, so write it out as an array of
+     longs.  */
+  static long dummy[4] = { 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff };
+  static long mxcsr = 0x1f80;
+  int reg;
+
+  for (reg = 0; reg < 8; reg++)
+    supply_register (XMM0_REGNUM + reg, (char *) dummy);
+  supply_register (MXCSR_REGNUM, (char *) &mxcsr);
+}
+
+#else
+
+/* Stub versions of the above routines, for systems that don't have
+   PTRACE_GETXFPREGS.  */
+static int store_xfpregs (int tid) { return 0; }
+static int fetch_xfpregs (int tid) { return 0; }
+static void dummy_sse_values (void) {}
+
+#endif
+
+\f
+/* Transferring arbitrary registers between GDB and inferior.  */
+
+/* Fetch register REGNO from the child process.  If REGNO is -1, do
+   this for all registers (including the floating point and SSE
+   registers).  */
+
+void
+fetch_inferior_registers (int regno)
+{
+  int tid;
+
+  /* Linux LWP ID's are process ID's.  */
+  if ((tid = TIDGET (inferior_pid)) == 0)
+    tid = inferior_pid;                /* Not a threaded program.  */
 
-  ret = ptrace (PTRACE_GETREGS, inferior_pid, 0, (int)buf);
-  if ( ret < 0 )
+  /* Use the PTRACE_GETXFPREGS request whenever possible, since it
+     transfers more registers in one system call, and we'll cache the
+     results.  But remember that fetch_xfpregs can fail, and return
+     zero.  */
+  if (regno == -1)
     {
-      warning ("Couldn't get registers");
+      fetch_regs (tid);
+      if (fetch_xfpregs (tid))
+       return;
+      fetch_fpregs (tid);
       return;
     }
 
-  for ( regno = 0; regno < NUM_REGS-NUM_FREGS; regno++ )
-    supply_register (regno, buf + register_addr (regno, U_REGS_OFFSET));
+  if (GETREGS_SUPPLIES (regno))
+    {
+      fetch_regs (tid);
+      return;
+    }
 
-}
+  if (GETXFPREGS_SUPPLIES (regno))
+    {
+      if (fetch_xfpregs (tid))
+       return;
+
+      /* Either our processor or our kernel doesn't support the SSE
+        registers, so read the FP registers in the traditional way,
+        and fill the SSE registers with dummy values.  It would be
+        more graceful to handle differences in the register set using
+        gdbarch.  Until then, this will at least make things work
+        plausibly.  */
+      fetch_fpregs (tid);
+      dummy_sse_values ();
+      return;
+    }
 
+  internal_error ("i386-linux-nat.c (fetch_inferior_registers): "
+                 "got request for bad register number %d", regno);
+}
 
-/*
-  Get the whole non-floating-point register state of the process and
-  replace them in the process from registers[].
-  */
-static void
-store_regs(void)
+/* Store register REGNO back into the child process.  If REGNO is -1,
+   do this for all registers (including the floating point and SSE
+   registers).  */
+void
+store_inferior_registers (int regno)
 {
-  int ret, regno;
-  char buf[17*sizeof(unsigned int)];
+  int tid;
 
-  ret = ptrace (PTRACE_GETREGS, inferior_pid, 0, (int)buf);
-  if ( ret < 0 )
+  /* Linux LWP ID's are process ID's.  */
+  if ((tid = TIDGET (inferior_pid)) == 0)
+    tid = inferior_pid;                /* Not a threaded program.  */
+
+  /* Use the PTRACE_SETXFPREGS requests whenever possibl, since it
+     transfers more registers in one system call.  But remember that
+     store_xfpregs can fail, and return zero.  */
+  if (regno == -1)
     {
-      warning ("Couldn't get registers");
+      store_regs (tid);
+      if (store_xfpregs (tid))
+       return;
+      store_fpregs (tid);
       return;
     }
 
-  for ( regno = 0; regno < NUM_REGS-NUM_FREGS; regno++ )
+  if (GETREGS_SUPPLIES (regno))
     {
-      if ( register_valid[regno] )
-       read_register_gen (regno, buf + register_addr (regno, U_REGS_OFFSET));
+      store_regs (tid);
+      return;
     }
 
-  ret = ptrace (PTRACE_SETREGS, inferior_pid, 0, (int)buf);
-
-  if ( ret < 0 )
+  if (GETXFPREGS_SUPPLIES (regno))
     {
-      warning ("Couldn't write floating point status");
+      if (store_xfpregs (tid))
+       return;
+
+      /* Either our processor or our kernel doesn't support the SSE
+        registers, so just write the FP registers in the traditional
+        way.  */
+      store_fpregs (tid);
       return;
     }
 
+  internal_error ("Got request to store bad register number %d.", regno);
 }
 
+\f
+/* Interpreting register set info found in core files.  */
 
-/* Fetch registers from the child process.
-   Fetch all if regno == -1, otherwise fetch all ordinary
-   registers or all floating point registers depending
-   upon the value of regno. */
+/* Provide registers to GDB from a core file.
 
-void
-fetch_inferior_registers (regno)
-     int regno;
-{
-  if ( (regno < NUM_REGS - NUM_FREGS) || (regno == -1) )
-    fetch_regs();
+   (We can't use the generic version of this function in
+   core-regset.c, because Linux has *three* different kinds of
+   register set notes.  core-regset.c would have to call
+   supply_xfpregset, which most platforms don't have.)
 
-  if ( (regno >= NUM_REGS - NUM_FREGS) || (regno == -1) )
-    fetch_fpregs();
-}
+   CORE_REG_SECT points to an array of bytes, which are the contents
+   of a `note' from a core file which BFD thinks might contain
+   register contents.  CORE_REG_SIZE is its size.
 
+   WHICH says which register set corelow suspects this is:
+     0 --- the general-purpose register set, in elf_gregset_t format
+     2 --- the floating-point register set, in elf_fpregset_t format
+     3 --- the extended floating-point register set, in struct
+           user_xfpregs_struct format
 
-/* Store our register values back into the inferior.
-   If REGNO is -1, do this for all registers.
-   Otherwise, REGNO specifies which register, which
-   then determines whether we store all ordinary
-   registers or all of the floating point registers. */
+   REG_ADDR isn't used on Linux.  */
 
-void
-store_inferior_registers (regno)
-     int regno;
+static void
+fetch_core_registers (char *core_reg_sect, unsigned core_reg_size,
+                     int which, CORE_ADDR reg_addr)
 {
-  if ( (regno < NUM_REGS - NUM_FREGS) || (regno == -1) )
-    store_regs();
+  elf_gregset_t gregset;
+  elf_fpregset_t fpregset;
 
-  if ( (regno >= NUM_REGS - NUM_FREGS) || (regno == -1) )
-    store_fpregs();
+  switch (which)
+    {
+    case 0:
+      if (core_reg_size != sizeof (gregset))
+       warning ("Wrong size gregset in core file.");
+      else
+       {
+         memcpy (&gregset, core_reg_sect, sizeof (gregset));
+         supply_gregset (&gregset);
+       }
+      break;
+
+    case 2:
+      if (core_reg_size != sizeof (fpregset))
+       warning ("Wrong size fpregset in core file.");
+      else
+       {
+         memcpy (&fpregset, core_reg_sect, sizeof (fpregset));
+         supply_fpregset (&fpregset);
+       }
+      break;
+
+#ifdef HAVE_PTRACE_GETXFPREGS
+      {
+       struct user_xfpregs_struct xfpregset;
+
+      case 3:
+       if (core_reg_size != sizeof (xfpregset))
+         warning ("Wrong size user_xfpregs_struct in core file.");
+       else
+         {
+           memcpy (&xfpregset, core_reg_sect, sizeof (xfpregset));
+           supply_xfpregset (&xfpregset);
+         }
+       break;
+      }
+#endif
+
+    default:
+      /* We've covered all the kinds of registers we know about here,
+         so this must be something we wouldn't know what to do with
+         anyway.  Just ignore it.  */
+      break;
+    }
 }
 
+\f
+/* Calling functions in shared libraries.  */
+/* FIXME: kettenis/2000-03-05: Doesn't this belong in a
+   target-dependent file?  The function
+   `i386_linux_skip_solib_resolver' is mentioned in
+   `config/i386/tm-linux.h'.  */
 
 /* Find the minimal symbol named NAME, and return both the minsym
    struct and its objfile.  This probably ought to be in minsym.c, but
@@ -357,7 +797,6 @@ skip_hurd_resolver (CORE_ADDR pc)
   return 0;
 }      
 
-
 /* See the comments for SKIP_SOLIB_RESOLVER at the top of infrun.c.
    This function:
    1) decides whether a PLT has sent us into the linker to resolve
@@ -377,3 +816,21 @@ i386_linux_skip_solib_resolver (CORE_ADDR pc)
 
   return 0;
 }
+
+\f
+/* Register that we are able to handle Linux ELF core file formats.  */
+
+static struct core_fns linux_elf_core_fns =
+{
+  bfd_target_elf_flavour,              /* core_flavour */
+  default_check_format,                        /* check_format */
+  default_core_sniffer,                        /* core_sniffer */
+  fetch_core_registers,                        /* core_read_registers */
+  NULL                                 /* next */
+};
+
+void
+_initialize_i386_linux_nat ()
+{
+  add_core_fns (&linux_elf_core_fns);
+}