This is the mail archive of the gdb-patches@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: PATCH: 6/6 [3rd try]: Add AVX support (gdbserver changes)


On Tue, Mar 30, 2010 at 09:48:33AM -0700, H.J. Lu wrote:
> On Sun, Mar 28, 2010 at 06:09:35PM -0700, H.J. Lu wrote:
> > Hi,
> > 
> > Here are gdbserver changes to support AVX.  OK to install?
> > 
> > Thanks.
> > 
> 
> Here is the updated gdbserver change.  I tested it with
> 
> # gdbserver --multi host:10000
> 
> connecting from
> 
> 1. gdb without AVX support.
> 2. gdb with AVX support,
> 3. gdb without XML support.
> 
> to debug 32bit and 64bit binaries.  Everything works correctly.
> OK to install?
> 
> Thanks.
> 
> 

Here is the updated patch. I will check it in together with i386/amd64
changes.

Thanks.

H.J.
---
2010-04-06  H.J. Lu  <hongjiu.lu@intel.com>

	* Makefile.in (clean): Updated.
	(i386-avx.o): New.
	(i386-avx.c): Likewise.
	(i386-avx-linux.o): Likewise.
	(i386-avx-linux.c): Likewise.
	(amd64-avx.o): Likewise.
	(amd64-avx.c): Likewise.
	(amd64-avx-linux.o): Likewise.
	(amd64-avx-linux.c): Likewise.

	* configure.srv (srv_i386_regobj): Add i386-avx.o.
	(srv_i386_linux_regobj): Add i386-avx-linux.o.
	(srv_amd64_regobj): Add amd64-avx.o.
	(srv_amd64_linux_regobj): Add amd64-avx-linux.o.
	(srv_i386_32bit_xmlfiles): Add i386/32bit-avx.xml.
	(srv_i386_64bit_xmlfiles): Add i386/64bit-avx.xml.
	(srv_i386_xmlfiles): Add i386/i386-avx.xml.
	(srv_amd64_xmlfiles): Add i386/amd64-avx.xml.
	(srv_i386_linux_xmlfiles): Add i386/i386-avx-linux.xml.
	(srv_amd64_linux_xmlfiles): Add i386/amd64-avx-linux.xml.

	* i387-fp.c: Include "i386-xstate.h".
	(i387_xsave): New.
	(i387_cache_to_xsave): Likewise.
	(i387_xsave_to_cache): Likewise.
	(x86_xcr0): Likewise.

	* i387-fp.h (i387_cache_to_xsave): Likewise.
	(i387_xsave_to_cache): Likewise.
	(x86_xcr0): Likewise.

	* linux-arm-low.c (target_regsets): Initialize nt_type to 0.
	* linux-crisv32-low.c (target_regsets): Likewise.
	* linux-m68k-low.c (target_regsets): Likewise.
	* linux-mips-low.c (target_regsets): Likewise.
	* linux-ppc-low.c (target_regsets): Likewise.
	* linux-s390-low.c (target_regsets): Likewise.
	* linux-sh-low.c (target_regsets): Likewise.
	* linux-sparc-low.c (target_regsets): Likewise.
	* linux-xtensa-low.c (target_regsets): Likewise.

	* linux-low.c: Include <sys/uio.h>.
	(regsets_fetch_inferior_registers): Support nt_type.
	(regsets_store_inferior_registers): Likewise.
	(linux_process_qsupported): New.
	(linux_target_ops): Add linux_process_qsupported.

	* linux-low.h (regset_info): Add nt_type.
	(linux_target_ops): Add process_qsupported.

	* linux-x86-low.c: Include "i386-xstate.h", "elf/common.h"
	and <sys/uio.h>.
	(init_registers_i386_avx_linux): New.
	(init_registers_amd64_avx_linux): Likewise.
	(xmltarget_i386_linux_no_xml): Likewise.
	(xmltarget_amd64_linux_no_xml): Likewise.
	(PTRACE_GETREGSET): Likewise.
	(PTRACE_SETREGSET): Likewise.
	(x86_fill_xstateregset): Likewise.
	(x86_store_xstateregset): Likewise.
	(use_xml): Likewise.
	(x86_linux_update_xmltarget): Likewise.
	(x86_linux_process_qsupported): Likewise.
	(target_regsets): Add NT_X86_XSTATE entry and Initialize nt_type.
	(x86_arch_setup): Don't call init_registers_amd64_linux nor
	init_registers_i386_linux here.  Call
	x86_linux_update_xmltarget.
	(the_low_target): Add x86_linux_process_qsupported.

	* server.c (handle_query): Call target_process_qsupported.

	* target.h (target_ops): Add process_qsupported.
	(target_process_qsupported): New.

diff --git a/gdb/gdbserver/Makefile.in b/gdb/gdbserver/Makefile.in
index 7fecced..2ec9784 100644
--- a/gdb/gdbserver/Makefile.in
+++ b/gdb/gdbserver/Makefile.in
@@ -217,6 +217,8 @@ clean:
 	rm -f powerpc-isa205-vsx64l.c
 	rm -f s390-linux32.c s390-linux64.c s390x-linux64.c
 	rm -f xml-builtin.c stamp-xml
+	rm -f i386-avx.c i386-avx-linux.c
+	rm -f amd64-avx.c amd64-avx-linux.c
 
 maintainer-clean realclean distclean: clean
 	rm -f nm.h tm.h xm.h config.status config.h stamp-h config.log
@@ -351,6 +353,12 @@ i386.c : $(srcdir)/../regformats/i386/i386.dat $(regdat_sh)
 i386-linux.o : i386-linux.c $(regdef_h)
 i386-linux.c : $(srcdir)/../regformats/i386/i386-linux.dat $(regdat_sh)
 	$(SHELL) $(regdat_sh) $(srcdir)/../regformats/i386/i386-linux.dat i386-linux.c
+i386-avx.o : i386-avx.c $(regdef_h)
+i386-avx.c : $(srcdir)/../regformats/i386/i386-avx.dat $(regdat_sh)
+	$(SHELL) $(regdat_sh) $(srcdir)/../regformats/i386/i386-avx.dat i386-avx.c
+i386-avx-linux.o : i386-avx-linux.c $(regdef_h)
+i386-avx-linux.c : $(srcdir)/../regformats/i386/i386-avx-linux.dat $(regdat_sh)
+	$(SHELL) $(regdat_sh) $(srcdir)/../regformats/i386/i386-avx-linux.dat i386-avx-linux.c
 reg-ia64.o : reg-ia64.c $(regdef_h)
 reg-ia64.c : $(srcdir)/../regformats/reg-ia64.dat $(regdat_sh)
 	$(SHELL) $(regdat_sh) $(srcdir)/../regformats/reg-ia64.dat reg-ia64.c
@@ -438,6 +446,12 @@ amd64.c : $(srcdir)/../regformats/i386/amd64.dat $(regdat_sh)
 amd64-linux.o : amd64-linux.c $(regdef_h)
 amd64-linux.c : $(srcdir)/../regformats/i386/amd64-linux.dat $(regdat_sh)
 	$(SHELL) $(regdat_sh) $(srcdir)/../regformats/i386/amd64-linux.dat amd64-linux.c
+amd64-avx.o : amd64-avx.c $(regdef_h)
+amd64-avx.c : $(srcdir)/../regformats/i386/amd64-avx.dat $(regdat_sh)
+	$(SHELL) $(regdat_sh) $(srcdir)/../regformats/i386/amd64-avx.dat amd64-avx.c
+amd64-avx-linux.o : amd64-avx-linux.c $(regdef_h)
+amd64-avx-linux.c : $(srcdir)/../regformats/i386/amd64-avx-linux.dat $(regdat_sh)
+	$(SHELL) $(regdat_sh) $(srcdir)/../regformats/i386/amd64-avx-linux.dat amd64-avx-linux.c
 reg-xtensa.o : reg-xtensa.c $(regdef_h)
 reg-xtensa.c : $(srcdir)/../regformats/reg-xtensa.dat $(regdat_sh)
 	$(SHELL) $(regdat_sh) $(srcdir)/../regformats/reg-xtensa.dat reg-xtensa.c
diff --git a/gdb/gdbserver/configure.srv b/gdb/gdbserver/configure.srv
index f7c80bd..8bc9aeb 100644
--- a/gdb/gdbserver/configure.srv
+++ b/gdb/gdbserver/configure.srv
@@ -22,17 +22,17 @@
 # Default hostio_last_error implementation
 srv_hostio_err_objs="hostio-errno.o"
 
-srv_i386_regobj=i386.o
-srv_i386_linux_regobj=i386-linux.o
-srv_amd64_regobj=amd64.o
-srv_amd64_linux_regobj=amd64-linux.o
+srv_i386_regobj="i386.o i386-avx.o"
+srv_i386_linux_regobj="i386-linux.o i386-avx-linux.o"
+srv_amd64_regobj="amd64.o x86-64-avx.o"
+srv_amd64_linux_regobj="amd64-linux.o amd64-avx-linux.o"
 
-srv_i386_32bit_xmlfiles="i386/32bit-core.xml i386/32bit-sse.xml"
-srv_i386_64bit_xmlfiles="i386/64bit-core.xml i386/64bit-sse.xml"
-srv_i386_xmlfiles="i386/i386.xml $srv_i386_32bit_xmlfiles"
-srv_amd64_xmlfiles="i386/amd64.xml $srv_i386_64bit_xmlfiles"
-srv_i386_linux_xmlfiles="i386/i386-linux.xml i386/32bit-linux.xml $srv_i386_32bit_xmlfiles"
-srv_amd64_linux_xmlfiles="i386/amd64-linux.xml i386/64bit-linux.xml $srv_i386_64bit_xmlfiles"
+srv_i386_32bit_xmlfiles="i386/32bit-core.xml i386/32bit-sse.xml i386/32bit-avx.xml"
+srv_i386_64bit_xmlfiles="i386/64bit-core.xml i386/64bit-sse.xml i386/64bit-avx.xml"
+srv_i386_xmlfiles="i386/i386.xml i386/i386-avx.xml $srv_i386_32bit_xmlfiles"
+srv_amd64_xmlfiles="i386/amd64.xml i386/amd64-avx.xml $srv_i386_64bit_xmlfiles"
+srv_i386_linux_xmlfiles="i386/i386-linux.xml i386/i386-avx-linux.xml i386/32bit-linux.xml $srv_i386_32bit_xmlfiles"
+srv_amd64_linux_xmlfiles="i386/amd64-linux.xml i386/amd64-avx-linux.xml i386/64bit-linux.xml $srv_i386_64bit_xmlfiles"
 
 # Input is taken from the "${target}" variable.
 
diff --git a/gdb/gdbserver/i387-fp.c b/gdb/gdbserver/i387-fp.c
index 7ef4ba3..5461022 100644
--- a/gdb/gdbserver/i387-fp.c
+++ b/gdb/gdbserver/i387-fp.c
@@ -19,6 +19,7 @@
 
 #include "server.h"
 #include "i387-fp.h"
+#include "i386-xstate.h"
 
 int num_xmm_registers = 8;
 
@@ -72,6 +73,46 @@ struct i387_fxsave {
   unsigned char xmm_space[256];
 };
 
+struct i387_xsave {
+  /* All these are only sixteen bits, plus padding, except for fop (which
+     is only eleven bits), and fooff / fioff (which are 32 bits each).  */
+  unsigned short fctrl;
+  unsigned short fstat;
+  unsigned short ftag;
+  unsigned short fop;
+  unsigned int fioff;
+  unsigned short fiseg;
+  unsigned short pad1;
+  unsigned int fooff;
+  unsigned short foseg;
+  unsigned short pad12;
+
+  unsigned int mxcsr;
+  unsigned int mxcsr_mask;
+
+  /* Space for eight 80-bit FP values in 128-bit spaces.  */
+  unsigned char st_space[128];
+
+  /* Space for eight 128-bit XMM values, or 16 on x86-64.  */
+  unsigned char xmm_space[256];
+
+  unsigned char reserved1[48];
+
+  /* The extended control register 0 (the XFEATURE_ENABLED_MASK
+     register).  */
+  unsigned long long xcr0;
+
+  unsigned char reserved2[40];
+
+  /* The XSTATE_BV bit vector.  */
+  unsigned long long xstate_bv;
+
+  unsigned char reserved3[56];
+
+  /* Space for eight upper 128-bit YMM values, or 16 on x86-64.  */
+  unsigned char ymmh_space[256];
+};
+
 void
 i387_cache_to_fsave (struct regcache *regcache, void *buf)
 {
@@ -199,6 +240,128 @@ i387_cache_to_fxsave (struct regcache *regcache, void *buf)
   fp->foseg = val;
 }
 
+void
+i387_cache_to_xsave (struct regcache *regcache, void *buf)
+{
+  struct i387_xsave *fp = (struct i387_xsave *) buf;
+  int i;
+  unsigned long val, val2;
+  unsigned int clear_bv;
+  unsigned long long xstate_bv = 0;
+  char raw[16];
+  char *p;
+
+  /* The supported bits in `xstat_bv' are 1 byte.  Clear part in
+     vector registers if its bit in xstat_bv is zero.  */
+  clear_bv = (~fp->xstate_bv) & x86_xcr0;
+
+  /* Clear part in x87 and vector registers if its bit in xstat_bv is
+     zero.  */
+  if (clear_bv)
+    {
+      if ((clear_bv & I386_XSTATE_X87))
+	for (i = 0; i < 8; i++)
+	  memset (((char *) &fp->st_space[0]) + i * 16, 0, 10);
+
+      if ((clear_bv & I386_XSTATE_SSE))
+	for (i = 0; i < num_xmm_registers; i++) 
+	  memset (((char *) &fp->xmm_space[0]) + i * 16, 0, 16);
+
+      if ((clear_bv & I386_XSTATE_AVX))
+	for (i = 0; i < num_xmm_registers; i++) 
+	  memset (((char *) &fp->ymmh_space[0]) + i * 16, 0, 16);
+    }
+
+  /* Check if any x87 registers are changed.  */
+  if ((x86_xcr0 & I386_XSTATE_X87))
+    {
+      int st0_regnum = find_regno ("st0");
+
+      for (i = 0; i < 8; i++)
+	{
+	  collect_register (regcache, i + st0_regnum, raw);
+	  p = ((char *) &fp->st_space[0]) + i * 16;
+	  if (memcmp (raw, p, 10))
+	    {
+	      xstate_bv |= I386_XSTATE_X87;
+	      memcpy (p, raw, 10);
+	    }
+	}
+    }
+
+  /* Check if any SSE registers are changed.  */
+  if ((x86_xcr0 & I386_XSTATE_SSE))
+    {
+      int xmm0_regnum = find_regno ("xmm0");
+
+      for (i = 0; i < num_xmm_registers; i++) 
+	{
+	  collect_register (regcache, i + xmm0_regnum, raw);
+	  p = ((char *) &fp->xmm_space[0]) + i * 16;
+	  if (memcmp (raw, p, 16))
+	    {
+	      xstate_bv |= I386_XSTATE_SSE;
+	      memcpy (p, raw, 16);
+	    }
+	}
+    }
+
+  /* Check if any AVX registers are changed.  */
+  if ((x86_xcr0 & I386_XSTATE_AVX))
+    {
+      int ymm0h_regnum = find_regno ("ymm0h");
+
+      for (i = 0; i < num_xmm_registers; i++) 
+	{
+	  collect_register (regcache, i + ymm0h_regnum, raw);
+	  p = ((char *) &fp->ymmh_space[0]) + i * 16;
+	  if (memcmp (raw, p, 16))
+	    {
+	      xstate_bv |= I386_XSTATE_AVX;
+	      memcpy (p, raw, 16);
+	    }
+	}
+    }
+
+  /* Update the corresponding bits in xstate_bv if any SSE/AVX
+     registers are changed.  */
+  fp->xstate_bv |= xstate_bv;
+
+  collect_register_by_name (regcache, "fioff", &fp->fioff);
+  collect_register_by_name (regcache, "fooff", &fp->fooff);
+  collect_register_by_name (regcache, "mxcsr", &fp->mxcsr);
+
+  /* This one's 11 bits... */
+  collect_register_by_name (regcache, "fop", &val2);
+  fp->fop = (val2 & 0x7FF) | (fp->fop & 0xF800);
+
+  /* Some registers are 16-bit.  */
+  collect_register_by_name (regcache, "fctrl", &val);
+  fp->fctrl = val;
+
+  collect_register_by_name (regcache, "fstat", &val);
+  fp->fstat = val;
+
+  /* Convert to the simplifed tag form stored in fxsave data.  */
+  collect_register_by_name (regcache, "ftag", &val);
+  val &= 0xFFFF;
+  val2 = 0;
+  for (i = 7; i >= 0; i--)
+    {
+      int tag = (val >> (i * 2)) & 3;
+
+      if (tag != 3)
+	val2 |= (1 << i);
+    }
+  fp->ftag = val2;
+
+  collect_register_by_name (regcache, "fiseg", &val);
+  fp->fiseg = val;
+
+  collect_register_by_name (regcache, "foseg", &val);
+  fp->foseg = val;
+}
+
 static int
 i387_ftag (struct i387_fxsave *fp, int regno)
 {
@@ -296,3 +459,107 @@ i387_fxsave_to_cache (struct regcache *regcache, const void *buf)
   val = (fp->fop) & 0x7FF;
   supply_register_by_name (regcache, "fop", &val);
 }
+
+void
+i387_xsave_to_cache (struct regcache *regcache, const void *buf)
+{
+  struct i387_xsave *fp = (struct i387_xsave *) buf;
+  struct i387_fxsave *fxp = (struct i387_fxsave *) buf;
+  int i, top;
+  unsigned long val;
+  unsigned int clear_bv;
+  char *p;
+
+  /* The supported bits in `xstat_bv' are 1 byte.  Clear part in
+     vector registers if its bit in xstat_bv is zero.  */
+  clear_bv = (~fp->xstate_bv) & x86_xcr0;
+
+  /* Check if any x87 registers are changed.  */
+  if ((x86_xcr0 & I386_XSTATE_X87))
+    {
+      int st0_regnum = find_regno ("st0");
+
+      if ((clear_bv & I386_XSTATE_X87))
+	p = NULL;
+      else
+	p = (char *) buf;
+
+      for (i = 0; i < 8; i++)
+	{
+	  if (p)
+	    p = ((char *) &fp->st_space[0]) + i * 16;
+	  supply_register (regcache, i + st0_regnum, p);
+	}
+    }
+
+  if ((x86_xcr0 & I386_XSTATE_SSE))
+    {
+      int xmm0_regnum = find_regno ("xmm0");
+
+      if ((clear_bv & I386_XSTATE_SSE))
+	p = NULL;
+      else
+	p = (char *) buf;
+
+      for (i = 0; i < num_xmm_registers; i++)
+	{
+	  if (p)
+	    p = ((char *) &fp->xmm_space[0]) + i * 16;
+	  supply_register (regcache, i + xmm0_regnum, p);
+	}
+    }
+
+  if ((x86_xcr0 & I386_XSTATE_AVX))
+    {
+      int ymm0h_regnum = find_regno ("ymm0h");
+
+      if ((clear_bv & I386_XSTATE_AVX))
+	p = NULL;
+      else
+	p = (char *) buf;
+
+      for (i = 0; i < num_xmm_registers; i++)
+	{
+	  if (p)
+	    p = ((char *) &fp->ymmh_space[0]) + i * 16;
+	  supply_register (regcache, i + ymm0h_regnum, p);
+	}
+    }
+
+  supply_register_by_name (regcache, "fioff", &fp->fioff);
+  supply_register_by_name (regcache, "fooff", &fp->fooff);
+  supply_register_by_name (regcache, "mxcsr", &fp->mxcsr);
+
+  /* Some registers are 16-bit.  */
+  val = fp->fctrl & 0xFFFF;
+  supply_register_by_name (regcache, "fctrl", &val);
+
+  val = fp->fstat & 0xFFFF;
+  supply_register_by_name (regcache, "fstat", &val);
+
+  /* Generate the form of ftag data that GDB expects.  */
+  top = (fp->fstat >> 11) & 0x7;
+  val = 0;
+  for (i = 7; i >= 0; i--)
+    {
+      int tag;
+      if (fp->ftag & (1 << i))
+	tag = i387_ftag (fxp, (i + 8 - top) % 8);
+      else
+	tag = 3;
+      val |= tag << (2 * i);
+    }
+  supply_register_by_name (regcache, "ftag", &val);
+
+  val = fp->fiseg & 0xFFFF;
+  supply_register_by_name (regcache, "fiseg", &val);
+
+  val = fp->foseg & 0xFFFF;
+  supply_register_by_name (regcache, "foseg", &val);
+
+  val = (fp->fop) & 0x7FF;
+  supply_register_by_name (regcache, "fop", &val);
+}
+
+/* Default to SSE.  */
+unsigned long long x86_xcr0 = I386_XSTATE_SSE_MASK;
diff --git a/gdb/gdbserver/i387-fp.h b/gdb/gdbserver/i387-fp.h
index d1e0681..ed1a322 100644
--- a/gdb/gdbserver/i387-fp.h
+++ b/gdb/gdbserver/i387-fp.h
@@ -26,6 +26,11 @@ void i387_fsave_to_cache (struct regcache *regcache, const void *buf);
 void i387_cache_to_fxsave (struct regcache *regcache, void *buf);
 void i387_fxsave_to_cache (struct regcache *regcache, const void *buf);
 
+void i387_cache_to_xsave (struct regcache *regcache, void *buf);
+void i387_xsave_to_cache (struct regcache *regcache, const void *buf);
+
+extern unsigned long long x86_xcr0;
+
 extern int num_xmm_registers;
 
 #endif /* I387_FP_H */
diff --git a/gdb/gdbserver/linux-arm-low.c b/gdb/gdbserver/linux-arm-low.c
index 54668f8..32bd7bb 100644
--- a/gdb/gdbserver/linux-arm-low.c
+++ b/gdb/gdbserver/linux-arm-low.c
@@ -354,16 +354,16 @@ arm_arch_setup (void)
 }
 
 struct regset_info target_regsets[] = {
-  { PTRACE_GETREGS, PTRACE_SETREGS, 18 * 4,
+  { PTRACE_GETREGS, PTRACE_SETREGS, 0, 18 * 4,
     GENERAL_REGS,
     arm_fill_gregset, arm_store_gregset },
-  { PTRACE_GETWMMXREGS, PTRACE_SETWMMXREGS, 16 * 8 + 6 * 4,
+  { PTRACE_GETWMMXREGS, PTRACE_SETWMMXREGS, 0, 16 * 8 + 6 * 4,
     EXTENDED_REGS,
     arm_fill_wmmxregset, arm_store_wmmxregset },
-  { PTRACE_GETVFPREGS, PTRACE_SETVFPREGS, 32 * 8 + 4,
+  { PTRACE_GETVFPREGS, PTRACE_SETVFPREGS, 0, 32 * 8 + 4,
     EXTENDED_REGS,
     arm_fill_vfpregset, arm_store_vfpregset },
-  { 0, 0, -1, -1, NULL, NULL }
+  { 0, 0, 0, -1, -1, NULL, NULL }
 };
 
 struct linux_target_ops the_low_target = {
diff --git a/gdb/gdbserver/linux-crisv32-low.c b/gdb/gdbserver/linux-crisv32-low.c
index 6ba48b6..d426c32 100644
--- a/gdb/gdbserver/linux-crisv32-low.c
+++ b/gdb/gdbserver/linux-crisv32-low.c
@@ -365,9 +365,9 @@ cris_store_gregset (const void *buf)
 typedef unsigned long elf_gregset_t[cris_num_regs];
 
 struct regset_info target_regsets[] = {
-  { PTRACE_GETREGS, PTRACE_SETREGS, sizeof (elf_gregset_t),
+  { PTRACE_GETREGS, PTRACE_SETREGS, 0, sizeof (elf_gregset_t),
     GENERAL_REGS, cris_fill_gregset, cris_store_gregset },
-  { 0, 0, -1, -1, NULL, NULL }
+  { 0, 0, 0, -1, -1, NULL, NULL }
 };
 
 struct linux_target_ops the_low_target = {
diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c
index 38af9d0..f159244 100644
--- a/gdb/gdbserver/linux-low.c
+++ b/gdb/gdbserver/linux-low.c
@@ -39,6 +39,7 @@
 #include <dirent.h>
 #include <sys/stat.h>
 #include <sys/vfs.h>
+#include <sys/uio.h>
 #ifndef ELFMAG0
 /* Don't include <linux/elf.h> here.  If it got included by gdb_proc_service.h
    then ELFMAG0 will have been defined.  If it didn't get included by
@@ -2977,14 +2978,15 @@ regsets_fetch_inferior_registers (struct regcache *regcache)
   struct regset_info *regset;
   int saw_general_regs = 0;
   int pid;
+  struct iovec iov;
 
   regset = target_regsets;
 
   pid = lwpid_of (get_thread_lwp (current_inferior));
   while (regset->size >= 0)
     {
-      void *buf;
-      int res;
+      void *buf, *data;
+      int nt_type, res;
 
       if (regset->size == 0 || disabled_regsets[regset - target_regsets])
 	{
@@ -2993,10 +2995,21 @@ regsets_fetch_inferior_registers (struct regcache *regcache)
 	}
 
       buf = xmalloc (regset->size);
+
+      nt_type = regset->nt_type;
+      if (nt_type)
+	{
+	  iov.iov_base = buf;
+	  iov.iov_len = regset->size;
+	  data = (void *) &iov;
+	}
+      else
+	data = buf;
+
 #ifndef __sparc__
-      res = ptrace (regset->get_request, pid, 0, buf);
+      res = ptrace (regset->get_request, pid, nt_type, data);
 #else
-      res = ptrace (regset->get_request, pid, buf, 0);
+      res = ptrace (regset->get_request, pid, data, nt_type);
 #endif
       if (res < 0)
 	{
@@ -3034,14 +3047,15 @@ regsets_store_inferior_registers (struct regcache *regcache)
   struct regset_info *regset;
   int saw_general_regs = 0;
   int pid;
+  struct iovec iov;
 
   regset = target_regsets;
 
   pid = lwpid_of (get_thread_lwp (current_inferior));
   while (regset->size >= 0)
     {
-      void *buf;
-      int res;
+      void *buf, *data;
+      int nt_type, res;
 
       if (regset->size == 0 || disabled_regsets[regset - target_regsets])
 	{
@@ -3054,10 +3068,21 @@ regsets_store_inferior_registers (struct regcache *regcache)
       /* First fill the buffer with the current register set contents,
 	 in case there are any items in the kernel's regset that are
 	 not in gdbserver's regcache.  */
+
+      nt_type = regset->nt_type;
+      if (nt_type)
+	{
+	  iov.iov_base = buf;
+	  iov.iov_len = regset->size;
+	  data = (void *) &iov;
+	}
+      else
+	data = buf;
+
 #ifndef __sparc__
-      res = ptrace (regset->get_request, pid, 0, buf);
+      res = ptrace (regset->get_request, pid, nt_type, data);
 #else
-      res = ptrace (regset->get_request, pid, buf, 0);
+      res = ptrace (regset->get_request, pid, &iov, data);
 #endif
 
       if (res == 0)
@@ -3067,9 +3092,9 @@ regsets_store_inferior_registers (struct regcache *regcache)
 
 	  /* Only now do we write the register set.  */
 #ifndef __sparc__
-	  res = ptrace (regset->set_request, pid, 0, buf);
+	  res = ptrace (regset->set_request, pid, nt_type, data);
 #else
-	  res = ptrace (regset->set_request, pid, buf, 0);
+	  res = ptrace (regset->set_request, pid, data, nt_type);
 #endif
 	}
 
@@ -4133,6 +4158,13 @@ linux_core_of_thread (ptid_t ptid)
   return core;
 }
 
+static void
+linux_process_qsupported (const char *query)
+{
+  if (the_low_target.process_qsupported != NULL)
+    the_low_target.process_qsupported (query);
+}
+
 static struct target_ops linux_target_ops = {
   linux_create_inferior,
   linux_attach,
@@ -4176,7 +4208,8 @@ static struct target_ops linux_target_ops = {
 #else
   NULL,
 #endif
-  linux_core_of_thread
+  linux_core_of_thread,
+  linux_process_qsupported
 };
 
 static void
diff --git a/gdb/gdbserver/linux-low.h b/gdb/gdbserver/linux-low.h
index d7aa418..52623bf 100644
--- a/gdb/gdbserver/linux-low.h
+++ b/gdb/gdbserver/linux-low.h
@@ -35,6 +35,9 @@ enum regset_type {
 struct regset_info
 {
   int get_request, set_request;
+  /* If NT_TYPE isn't 0, it will be passed to ptrace as the 3rd
+     argument and the 4th argument should be "const struct iovec *".  */
+  int nt_type;
   int size;
   enum regset_type type;
   regset_fill_func fill_function;
@@ -111,6 +114,9 @@ struct linux_target_ops
 
   /* Hook to call prior to resuming a thread.  */
   void (*prepare_to_resume) (struct lwp_info *);
+
+  /* Hook to support target specific qSupported.  */
+  void (*process_qsupported) (const char *);
 };
 
 extern struct linux_target_ops the_low_target;
diff --git a/gdb/gdbserver/linux-m68k-low.c b/gdb/gdbserver/linux-m68k-low.c
index 14e3864..6c98bb1 100644
--- a/gdb/gdbserver/linux-m68k-low.c
+++ b/gdb/gdbserver/linux-m68k-low.c
@@ -112,14 +112,14 @@ m68k_store_fpregset (struct regcache *regcache, const void *buf)
 
 struct regset_info target_regsets[] = {
 #ifdef HAVE_PTRACE_GETREGS
-  { PTRACE_GETREGS, PTRACE_SETREGS, sizeof (elf_gregset_t),
+  { PTRACE_GETREGS, PTRACE_SETREGS, 0, sizeof (elf_gregset_t),
     GENERAL_REGS,
     m68k_fill_gregset, m68k_store_gregset },
-  { PTRACE_GETFPREGS, PTRACE_SETFPREGS, sizeof (elf_fpregset_t),
+  { PTRACE_GETFPREGS, PTRACE_SETFPREGS, 0, sizeof (elf_fpregset_t),
     FP_REGS,
     m68k_fill_fpregset, m68k_store_fpregset },
 #endif /* HAVE_PTRACE_GETREGS */
-  { 0, 0, -1, -1, NULL, NULL }
+  { 0, 0, 0, -1, -1, NULL, NULL }
 };
 
 static const unsigned char m68k_breakpoint[] = { 0x4E, 0x4F };
diff --git a/gdb/gdbserver/linux-mips-low.c b/gdb/gdbserver/linux-mips-low.c
index 70f6700..1c04b2e 100644
--- a/gdb/gdbserver/linux-mips-low.c
+++ b/gdb/gdbserver/linux-mips-low.c
@@ -343,12 +343,12 @@ mips_store_fpregset (struct regcache *regcache, const void *buf)
 
 struct regset_info target_regsets[] = {
 #ifdef HAVE_PTRACE_GETREGS
-  { PTRACE_GETREGS, PTRACE_SETREGS, 38 * 8, GENERAL_REGS,
+  { PTRACE_GETREGS, PTRACE_SETREGS, 0, 38 * 8, GENERAL_REGS,
     mips_fill_gregset, mips_store_gregset },
-  { PTRACE_GETFPREGS, PTRACE_SETFPREGS, 33 * 8, FP_REGS,
+  { PTRACE_GETFPREGS, PTRACE_SETFPREGS, 0, 33 * 8, FP_REGS,
     mips_fill_fpregset, mips_store_fpregset },
 #endif /* HAVE_PTRACE_GETREGS */
-  { 0, 0, -1, -1, NULL, NULL }
+  { 0, 0, 0, -1, -1, NULL, NULL }
 };
 
 struct linux_target_ops the_low_target = {
diff --git a/gdb/gdbserver/linux-ppc-low.c b/gdb/gdbserver/linux-ppc-low.c
index 10a1309..000b20f 100644
--- a/gdb/gdbserver/linux-ppc-low.c
+++ b/gdb/gdbserver/linux-ppc-low.c
@@ -593,14 +593,14 @@ struct regset_info target_regsets[] = {
      fetch them every time, but still fall back to PTRACE_PEEKUSER for the
      general registers.  Some kernels support these, but not the newer
      PPC_PTRACE_GETREGS.  */
-  { PTRACE_GETVSXREGS, PTRACE_SETVSXREGS, SIZEOF_VSXREGS, EXTENDED_REGS,
+  { PTRACE_GETVSXREGS, PTRACE_SETVSXREGS, 0, SIZEOF_VSXREGS, EXTENDED_REGS,
   ppc_fill_vsxregset, ppc_store_vsxregset },
-  { PTRACE_GETVRREGS, PTRACE_SETVRREGS, SIZEOF_VRREGS, EXTENDED_REGS,
+  { PTRACE_GETVRREGS, PTRACE_SETVRREGS, 0, SIZEOF_VRREGS, EXTENDED_REGS,
     ppc_fill_vrregset, ppc_store_vrregset },
-  { PTRACE_GETEVRREGS, PTRACE_SETEVRREGS, 32 * 4 + 8 + 4, EXTENDED_REGS,
+  { PTRACE_GETEVRREGS, PTRACE_SETEVRREGS, 0, 32 * 4 + 8 + 4, EXTENDED_REGS,
     ppc_fill_evrregset, ppc_store_evrregset },
-  { 0, 0, 0, GENERAL_REGS, ppc_fill_gregset, NULL },
-  { 0, 0, -1, -1, NULL, NULL }
+  { 0, 0, 0, 0, GENERAL_REGS, ppc_fill_gregset, NULL },
+  { 0, 0, 0, -1, -1, NULL, NULL }
 };
 
 struct linux_target_ops the_low_target = {
diff --git a/gdb/gdbserver/linux-s390-low.c b/gdb/gdbserver/linux-s390-low.c
index 5460f57..eb865dc 100644
--- a/gdb/gdbserver/linux-s390-low.c
+++ b/gdb/gdbserver/linux-s390-low.c
@@ -181,8 +181,8 @@ static void s390_fill_gregset (struct regcache *regcache, void *buf)
 }
 
 struct regset_info target_regsets[] = {
-  { 0, 0, 0, GENERAL_REGS, s390_fill_gregset, NULL },
-  { 0, 0, -1, -1, NULL, NULL }
+  { 0, 0, 0, 0, GENERAL_REGS, s390_fill_gregset, NULL },
+  { 0, 0, 0, -1, -1, NULL, NULL }
 };
 
 
diff --git a/gdb/gdbserver/linux-sh-low.c b/gdb/gdbserver/linux-sh-low.c
index 9d27e7f..87a0dd2 100644
--- a/gdb/gdbserver/linux-sh-low.c
+++ b/gdb/gdbserver/linux-sh-low.c
@@ -104,8 +104,8 @@ static void sh_fill_gregset (struct regcache *regcache, void *buf)
 }
 
 struct regset_info target_regsets[] = {
-  { 0, 0, 0, GENERAL_REGS, sh_fill_gregset, NULL },
-  { 0, 0, -1, -1, NULL, NULL }
+  { 0, 0, 0, 0, GENERAL_REGS, sh_fill_gregset, NULL },
+  { 0, 0, 0, -1, -1, NULL, NULL }
 };
 
 struct linux_target_ops the_low_target = {
diff --git a/gdb/gdbserver/linux-sparc-low.c b/gdb/gdbserver/linux-sparc-low.c
index 0bb5f2f..e0bfe81 100644
--- a/gdb/gdbserver/linux-sparc-low.c
+++ b/gdb/gdbserver/linux-sparc-low.c
@@ -260,13 +260,13 @@ sparc_reinsert_addr (void)
 
 
 struct regset_info target_regsets[] = {
-  { PTRACE_GETREGS, PTRACE_SETREGS, sizeof (elf_gregset_t),
+  { PTRACE_GETREGS, PTRACE_SETREGS, 0, sizeof (elf_gregset_t),
     GENERAL_REGS,
     sparc_fill_gregset, sparc_store_gregset },
-  { PTRACE_GETFPREGS, PTRACE_SETFPREGS, sizeof (fpregset_t),
+  { PTRACE_GETFPREGS, PTRACE_SETFPREGS, 0, sizeof (fpregset_t),
     FP_REGS,
     sparc_fill_fpregset, sparc_store_fpregset },
-  { 0, 0, -1, -1, NULL, NULL }
+  { 0, 0, 0, -1, -1, NULL, NULL }
 };
 
 struct linux_target_ops the_low_target = {
diff --git a/gdb/gdbserver/linux-x86-low.c b/gdb/gdbserver/linux-x86-low.c
index 37fe60f..3853b25 100644
--- a/gdb/gdbserver/linux-x86-low.c
+++ b/gdb/gdbserver/linux-x86-low.c
@@ -24,6 +24,8 @@
 #include "linux-low.h"
 #include "i387-fp.h"
 #include "i386-low.h"
+#include "i386-xstate.h"
+#include "elf/common.h"
 
 #include "gdb_proc_service.h"
 
@@ -31,10 +33,35 @@
 void init_registers_i386_linux (void);
 /* Defined in auto-generated file amd64-linux.c.  */
 void init_registers_amd64_linux (void);
+/* Defined in auto-generated file i386-avx-linux.c.  */
+void init_registers_i386_avx_linux (void);
+/* Defined in auto-generated file amd64-avx-linux.c.  */
+void init_registers_amd64_avx_linux (void);
+
+/* Backward compatibility for gdb without XML support.  */
+
+static const char *xmltarget_i386_linux_no_xml = "@<target>\
+<architecture>i386</architecture>\
+<osabi>GNU/Linux</osabi>\
+</target>";
+static const char *xmltarget_amd64_linux_no_xml = "@<target>\
+<architecture>i386:x86-64</architecture>\
+<osabi>GNU/Linux</osabi>\
+</target>";
 
 #include <sys/reg.h>
 #include <sys/procfs.h>
 #include <sys/ptrace.h>
+#include <sys/uio.h>
+
+#ifndef PTRACE_GETREGSET
+#define PTRACE_GETREGSET	0x4204
+#endif
+
+#ifndef PTRACE_SETREGSET
+#define PTRACE_SETREGSET	0x4205
+#endif
+
 
 #ifndef PTRACE_GET_THREAD_AREA
 #define PTRACE_GET_THREAD_AREA 25
@@ -252,6 +279,18 @@ x86_store_fpxregset (struct regcache *regcache, const void *buf)
 
 #endif
 
+static void
+x86_fill_xstateregset (struct regcache *regcache, void *buf)
+{
+  i387_cache_to_xsave (regcache, buf);
+}
+
+static void
+x86_store_xstateregset (struct regcache *regcache, const void *buf)
+{
+  i387_xsave_to_cache (regcache, buf);
+}
+
 /* ??? The non-biarch i386 case stores all the i387 regs twice.
    Once in i387_.*fsave.* and once in i387_.*fxsave.*.
    This is, presumably, to handle the case where PTRACE_[GS]ETFPXREGS
@@ -264,21 +303,23 @@ x86_store_fpxregset (struct regcache *regcache, const void *buf)
 struct regset_info target_regsets[] =
 {
 #ifdef HAVE_PTRACE_GETREGS
-  { PTRACE_GETREGS, PTRACE_SETREGS, sizeof (elf_gregset_t),
+  { PTRACE_GETREGS, PTRACE_SETREGS, 0, sizeof (elf_gregset_t),
     GENERAL_REGS,
     x86_fill_gregset, x86_store_gregset },
+  { PTRACE_GETREGSET, PTRACE_SETREGSET, NT_X86_XSTATE, 0,
+    EXTENDED_REGS, x86_fill_xstateregset, x86_store_xstateregset },
 # ifndef __x86_64__
 #  ifdef HAVE_PTRACE_GETFPXREGS
-  { PTRACE_GETFPXREGS, PTRACE_SETFPXREGS, sizeof (elf_fpxregset_t),
+  { PTRACE_GETFPXREGS, PTRACE_SETFPXREGS, 0, sizeof (elf_fpxregset_t),
     EXTENDED_REGS,
     x86_fill_fpxregset, x86_store_fpxregset },
 #  endif
 # endif
-  { PTRACE_GETFPREGS, PTRACE_SETFPREGS, sizeof (elf_fpregset_t),
+  { PTRACE_GETFPREGS, PTRACE_SETFPREGS, 0, sizeof (elf_fpregset_t),
     FP_REGS,
     x86_fill_fpregset, x86_store_fpregset },
 #endif /* HAVE_PTRACE_GETREGS */
-  { 0, 0, -1, -1, NULL, NULL }
+  { 0, 0, 0, -1, -1, NULL, NULL }
 };
 
 static CORE_ADDR
@@ -780,6 +821,128 @@ x86_siginfo_fixup (struct siginfo *native, void *inf, int direction)
   return 0;
 }
 
+static int use_xml;
+
+/* Update gdbserver_xmltarget.  */
+
+static void
+x86_linux_update_xmltarget (void)
+{
+  static unsigned long long xcr0;
+  static int have_ptrace_getregset = -1;
+
+  if (!current_inferior)
+    return;
+
+#ifdef __x86_64__
+  if (num_xmm_registers == 8)
+    init_registers_i386_linux ();
+  else
+    init_registers_amd64_linux ();
+#else
+  init_registers_i386_linux ();
+#endif
+
+  if (!use_xml)
+    {
+      /* Don't use XML.  */
+#ifdef __x86_64__
+      if (num_xmm_registers == 8)
+	gdbserver_xmltarget = xmltarget_i386_linux_no_xml;
+      else
+	gdbserver_xmltarget = xmltarget_amd64_linux_no_xml;
+#else
+      gdbserver_xmltarget = xmltarget_i386_linux_no_xml;
+#endif
+
+      x86_xcr0 = I386_XSTATE_SSE_MASK;
+
+      return;
+    }
+
+  /* Check if XSAVE extended state is supported.  */
+  if (have_ptrace_getregset == -1)
+    {
+      int pid = pid_of (get_thread_lwp (current_inferior));
+      unsigned long long xstateregs[I386_XSTATE_SSE_SIZE / sizeof (long long)];
+      struct iovec iov;
+      struct regset_info *regset;
+
+      iov.iov_base = xstateregs;
+      iov.iov_len = sizeof (xstateregs);
+
+      /* Check if PTRACE_GETREGSET works.  */
+      if (ptrace (PTRACE_GETREGSET, pid, (unsigned int) NT_X86_XSTATE,
+		  &iov) < 0)
+	{
+	  have_ptrace_getregset = 0;
+	  return;
+	}
+      else
+	have_ptrace_getregset = 1;
+
+      /* Get XCR0 from XSAVE extended state at byte 464.  */
+      xcr0 = xstateregs[464 / sizeof (long long)];
+
+      /* Use PTRACE_GETREGSET if it is available.  */
+      for (regset = target_regsets;
+	   regset->fill_function != NULL; regset++)
+	if (regset->get_request == PTRACE_GETREGSET)
+	  regset->size = I386_XSTATE_SIZE (xcr0);
+	else if (regset->type != GENERAL_REGS)
+	  regset->size = 0;
+    }
+
+  if (have_ptrace_getregset)
+    {
+      /* AVX is the highest feature we support.  */
+      if ((xcr0 & I386_XSTATE_AVX_MASK) == I386_XSTATE_AVX_MASK)
+	{
+	  x86_xcr0 = xcr0;
+
+#ifdef __x86_64__
+	  /* I386 has 8 xmm regs.  */
+	  if (num_xmm_registers == 8)
+	    init_registers_i386_avx_linux ();
+	  else
+	    init_registers_amd64_avx_linux ();
+#else
+	  init_registers_i386_avx_linux ();
+#endif
+	}
+    }
+}
+
+/* Process qSupported query, "xmlRegisters=".  Update the buffer size for
+   PTRACE_GETREGSET.  */
+
+static void
+x86_linux_process_qsupported (const char *query)
+{
+  /* Return if gdb doesn't support XML.  If gdb sends "xmlRegisters="
+     with "i386" in qSupported query, it supports x86 XML target
+     descriptions.  */
+  use_xml = 0;
+  if (query != NULL && strncmp (query, "xmlRegisters=", 13) == 0)
+    {
+      char *copy = xstrdup (query + 13);
+      char *p;
+
+      for (p = strtok (copy, ","); p != NULL; p = strtok (NULL, ","))
+	{
+	  if (strcmp (p, "i386") == 0)
+	    {
+	      use_xml = 1;
+	      break;
+	    }
+	} 
+
+      free (copy);
+    }
+
+  x86_linux_update_xmltarget ();
+}
+
 /* Initialize gdbserver for the architecture of the inferior.  */
 
 static void
@@ -800,8 +963,6 @@ x86_arch_setup (void)
     }
   else if (use_64bit)
     {
-      init_registers_amd64_linux ();
-
       /* Amd64 doesn't have HAVE_LINUX_USRREGS.  */
       the_low_target.num_regs = -1;
       the_low_target.regmap = NULL;
@@ -811,14 +972,13 @@ x86_arch_setup (void)
       /* Amd64 has 16 xmm regs.  */
       num_xmm_registers = 16;
 
+      x86_linux_update_xmltarget ();
       return;
     }
 #endif
 
   /* Ok we have a 32-bit inferior.  */
 
-  init_registers_i386_linux ();
-
   the_low_target.num_regs = I386_NUM_REGS;
   the_low_target.regmap = i386_regmap;
   the_low_target.cannot_fetch_register = i386_cannot_fetch_register;
@@ -826,6 +986,8 @@ x86_arch_setup (void)
 
   /* I386 has 8 xmm regs.  */
   num_xmm_registers = 8;
+
+  x86_linux_update_xmltarget ();
 }
 
 /* This is initialized assuming an amd64 target.
@@ -858,5 +1020,6 @@ struct linux_target_ops the_low_target =
   x86_siginfo_fixup,
   x86_linux_new_process,
   x86_linux_new_thread,
-  x86_linux_prepare_to_resume
+  x86_linux_prepare_to_resume,
+  x86_linux_process_qsupported 
 };
diff --git a/gdb/gdbserver/linux-xtensa-low.c b/gdb/gdbserver/linux-xtensa-low.c
index c5ed351..8d0e73a 100644
--- a/gdb/gdbserver/linux-xtensa-low.c
+++ b/gdb/gdbserver/linux-xtensa-low.c
@@ -131,13 +131,13 @@ xtensa_store_xtregset (struct regcache *regcache, const void *buf)
 }
 
 struct regset_info target_regsets[] = {
-  { PTRACE_GETREGS, PTRACE_SETREGS, sizeof (elf_gregset_t),
+  { PTRACE_GETREGS, PTRACE_SETREGS, 0, sizeof (elf_gregset_t),
     GENERAL_REGS,
     xtensa_fill_gregset, xtensa_store_gregset },
-  { PTRACE_GETXTREGS, PTRACE_SETXTREGS, XTENSA_ELF_XTREG_SIZE,
+  { PTRACE_GETXTREGS, PTRACE_SETXTREGS, 0, XTENSA_ELF_XTREG_SIZE,
     EXTENDED_REGS,
     xtensa_fill_xtregset, xtensa_store_xtregset },
-  { 0, 0, -1, -1, NULL, NULL }
+  { 0, 0, 0, -1, -1, NULL, NULL }
 };
 
 #if XCHAL_HAVE_BE
diff --git a/gdb/gdbserver/server.c b/gdb/gdbserver/server.c
index c6fc005..568640e 100644
--- a/gdb/gdbserver/server.c
+++ b/gdb/gdbserver/server.c
@@ -1289,6 +1289,9 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
     {
       char *p = &own_buf[10];
 
+      /* Start processing qSupported packet.  */
+      target_process_qsupported (NULL);
+
       /* Process each feature being provided by GDB.  The first
 	 feature will follow a ':', and latter features will follow
 	 ';'.  */
@@ -1304,6 +1307,8 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
 		if (target_supports_multi_process ())
 		  multi_process = 1;
 	      }
+	    else
+	      target_process_qsupported (p);
 	  }
 
       sprintf (own_buf, "PacketSize=%x;QPassSignals+", PBUFSIZ - 1);
diff --git a/gdb/gdbserver/target.h b/gdb/gdbserver/target.h
index ac68652..6109b1c 100644
--- a/gdb/gdbserver/target.h
+++ b/gdb/gdbserver/target.h
@@ -286,6 +286,9 @@ struct target_ops
 
   /* Returns the core given a thread, or -1 if not known.  */
   int (*core_of_thread) (ptid_t);
+
+  /* Target specific qSupported support.  */
+  void (*process_qsupported) (const char *);
 };
 
 extern struct target_ops *the_target;
@@ -326,6 +329,10 @@ void set_target_ops (struct target_ops *);
   (the_target->supports_multi_process ? \
    (*the_target->supports_multi_process) () : 0)
 
+#define target_process_qsupported(query) \
+  if (the_target->process_qsupported) \
+    the_target->process_qsupported (query)
+
 /* Start non-stop mode, returns 0 on success, -1 on failure.   */
 
 int start_non_stop (int nonstop);


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]