public inbox for frysk-cvs@sourceware.org
help / color / mirror / Atom feed
From: mark@sourceware.org
To: frysk-cvs@sourceware.org
Subject: [SCM]  master: Add new libunwind get_unwind_table interface.
Date: Tue, 11 Dec 2007 14:35:00 -0000	[thread overview]
Message-ID: <20071211143548.25846.qmail@sourceware.org> (raw)

The branch, master has been updated
       via  2579d604e2b8ce0bc583e872642ecdfefb957cfa (commit)
      from  5f078697788883be959202a9643d29430587e182 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email.

- Log -----------------------------------------------------------------
commit 2579d604e2b8ce0bc583e872642ecdfefb957cfa
Author: Mark Wielaard <mwielaard@redhat.com>
Date:   Tue Dec 11 15:35:01 2007 +0100

    Add new libunwind get_unwind_table interface.
    
    frysk-imports/libunwind/ChangeLog
    2007-12-11  Mark Wielaard  <mwielaard@redhat.com>
    
            * include/libunwind-common.h.in (unw_get_unwind_table): Add
            unw_accessor_t, remove elf image arguments.
            * src/mi/Gget_unwind_table.c (unw_get_unwind_table): Likewise.
            Rewrite to use unw_accessor_t for reading all data.
    
    frysk-sys/lib/unwind/ChangeLog
    2007-12-11  Mark Wielaard  <mwielaard@redhat.com>
    
            * ElfImage.java (name): New field.
            (ElfImage): Set name.
            (toString): Add name.
            * cni/ElfImage.cxx (mapElfImage): Pass in name.
            * cni/UnwindH.hxx (access_mem): Handle memory holes.
            (get_eh_frame_hdr_addr): New static function.
            (local_access_mem): Likewise.
            (createProcInfoFromElfImage): Use get_eh_frame_hdr_addr and
            local_access_mem.
            (createElfImageFromVDSO): Set name to [vdso].

-----------------------------------------------------------------------

Summary of changes:
 frysk-imports/libunwind/ChangeLog                  |    7 +
 .../libunwind/include/libunwind-common.h.in        |    8 +-
 frysk-imports/libunwind/src/mi/Gget_unwind_table.c |  229 ++++++--------------
 frysk-sys/lib/unwind/ChangeLog                     |   13 ++
 frysk-sys/lib/unwind/ElfImage.java                 |    8 +-
 frysk-sys/lib/unwind/cni/ElfImage.cxx              |    2 +-
 frysk-sys/lib/unwind/cni/UnwindH.hxx               |  194 +++++++++++++++--
 7 files changed, 273 insertions(+), 188 deletions(-)

First 500 lines of diff:
diff --git a/frysk-imports/libunwind/ChangeLog b/frysk-imports/libunwind/ChangeLog
index f7af421..3e02b0d 100644
--- a/frysk-imports/libunwind/ChangeLog
+++ b/frysk-imports/libunwind/ChangeLog
@@ -1,3 +1,10 @@
+2007-12-11  Mark Wielaard  <mwielaard@redhat.com>
+
+	* include/libunwind-common.h.in (unw_get_unwind_table): Add
+	unw_accessor_t, remove elf image arguments.
+	* src/mi/Gget_unwind_table.c (unw_get_unwind_table): Likewise.
+	Rewrite to use unw_accessor_t for reading all data.
+	
 2007-11-28  Mark Wielaard  <mwielaard@redhat.com>
 
 	* src/mi/Gget_unwind_table.c (unw_get_unwind_table): Make addr a
diff --git a/frysk-imports/libunwind/include/libunwind-common.h.in b/frysk-imports/libunwind/include/libunwind-common.h.in
index 497a7b4..a837055 100644
--- a/frysk-imports/libunwind/include/libunwind-common.h.in
+++ b/frysk-imports/libunwind/include/libunwind-common.h.in
@@ -252,8 +252,10 @@ extern int unw_get_proc_name (unw_cursor_t *, char *, size_t, unw_word_t *);
 extern const char *unw_strerror (int);
 extern int unw_get_unwind_table(unw_addr_space_t as, unw_word_t ip, 
 				unw_proc_info_t *pi, int need_unwind_info,
-				void *image, size_t size, 
-				unsigned long segbase, unsigned long mapoff,
-				void *arg);
+				void *arg,
+				unw_accessors_t *eh_frame_accessors,
+				unw_word_t eh_frame_hdr_address,
+				void *eh_frame_arg,
+				unw_word_t peh_vaddr);
 
 extern unw_addr_space_t unw_local_addr_space;
diff --git a/frysk-imports/libunwind/src/mi/Gget_unwind_table.c b/frysk-imports/libunwind/src/mi/Gget_unwind_table.c
index 2acb947..1d905b6 100644
--- a/frysk-imports/libunwind/src/mi/Gget_unwind_table.c
+++ b/frysk-imports/libunwind/src/mi/Gget_unwind_table.c
@@ -22,177 +22,72 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.  */
 
 #include "libunwind_i.h"
-//#include "remote.h"
+#include "dwarf_i.h"
 #include "dwarf-eh.h"
 
 int
-unw_get_unwind_table(unw_addr_space_t as, unw_word_t ip, unw_proc_info_t *pi,
-			int need_unwind_info, void *image, size_t size, 
-			unsigned long segbase, unsigned long mapoff, void *arg)
+unw_get_unwind_table(unw_addr_space_t as, unw_word_t ip,
+		     unw_proc_info_t *pi, int need_unwind_info, void *arg,
+		     unw_accessors_t *eh_frame_accessors,
+		     unw_word_t eh_frame_hdr_address,
+		     void *eh_frame_arg,
+		     unw_word_t peh_vaddr)
 {
-	Debug(99, "Entering get_unwind_table\n");
-	Elf_W(Phdr) *phdr, *ptxt = NULL, *peh_hdr = NULL, *pdyn = NULL;
-	unw_word_t eh_frame_start, fde_count, load_base;
-	char *addr;
-	struct dwarf_eh_frame_hdr *hdr;	
-	Elf_W(Ehdr) *ehdr;
-	int i, ret;
-	unw_dyn_info_t di_cache;
-	
-	 if (size <= EI_CLASS)
-    		return -1;
-
-	Debug(99, "Checking elf size\n");
- 	if (!(memcmp (image, ELFMAG, SELFMAG) == 0
-	  && ((uint8_t *) image)[EI_CLASS] == ELF_CLASS))
-	  	return -1;
-    		
-    	Debug(99, "Checked elf class\n");
-    	ehdr = image;
-	phdr = (Elf_W(Phdr) *) ((char *) image + ehdr->e_phoff);
-	for (i = 0; i < ehdr->e_phnum; ++i)
-    {
-      switch (phdr[i].p_type)
-	{
-	case PT_LOAD:
-	  if (phdr[i].p_offset == mapoff)
-	    ptxt = phdr + i;
-	  break;
-
-	case PT_GNU_EH_FRAME:
-	  peh_hdr = phdr + i;
-	  break;
-
-	case PT_DYNAMIC:
-	  pdyn = phdr + i;
-	  break;
-
-	default:
-	  break;
-	}
-    }
-    
-    	Debug(99, "Traversed headers\n");
-	if (!ptxt || !peh_hdr)
-    		return -UNW_ENOINFO;
-    		    
-    	if (pdyn)
-    {
-    	Debug(99, "Got dynamic header\n");
-      /* For dynamicly linked executables and shared libraries,
-	 DT_PLTGOT is the value that data-relative addresses are
-	 relative to for that object.  We call this the "gp".  */
-	    Elf_W(Dyn) *dyn = (Elf_W(Dyn) *)(pdyn->p_offset
-					     + (char *) image);
-      for (; dyn->d_tag != DT_NULL; ++dyn)
-	if (dyn->d_tag == DT_PLTGOT)
-	  {
-	    /* Assume that _DYNAMIC is writable and GLIBC has
-	       relocated it (true for x86 at least).  */
-	    di_cache.gp = dyn->d_un.d_ptr;
-	    break;
-	  }
-    }
-     else
-    /* Otherwise this is a static executable with no _DYNAMIC.  Assume
-       that data-relative addresses are relative to 0, i.e.,
-       absolute.  */
-    	di_cache.gp = 0;
-    	
-    	Debug(99, "Got eh_frame_hdr\n");
-    	 hdr = (struct dwarf_eh_frame_hdr *) (peh_hdr->p_offset
-				       + (char *) image);
-  if (hdr->version != DW_EH_VERSION)
-    {
-      Debug (1, "table has unexpected version %d\n",
-	    hdr->version);
-      return 0;
-    }
-
-	Debug(99, "EH_VERSION is correct\n");
-
-  addr = hdr + 1;
-  Debug (99, "Got addr\n");
-  /* Fill in a dummy proc_info structure.  We just need to fill in
-     enough to ensure that dwarf_read_encoded_pointer() can do it's
-     job.  Since we don't have a procedure-context at this point, all
-     we have to do is fill in the global-pointer.  */
-  memset (pi, 0, sizeof (*pi));  
-  Debug(99, "cleared pi\n");
-  pi->gp = di_cache.gp;
-
-  Debug(99, "set pi gp\n");
-
-// The following is a local address space memory accessor used by
-// dwarf_read_encoded_pointer.  The arg pointer is the base address,
-// addr is the offset from the base address.
-  int 
-  local_access_mem (unw_addr_space_t as, unw_word_t addr,
-		    unw_word_t *val, int write, void *arg) 
-  {
-    Debug(99, "entering local_access_mem, reading addr: 0x%lx into: %p\n", 
-	  (long) addr, val);
-    if (write)
-      {
-	// Writing is not supported
-	return -UNW_EINVAL;
-      }
-    else
-      {
-	*val = *(unw_word_t *) (addr + (char *) arg);
-	Debug (16, "mem[%x] -> %x\n", (addr + (char *) arg), *val);
-      }
-    Debug(99, "leaving local_access_mem\n");
-    return 0;
-  }
-  
-  unw_accessors_t local_accessors = {NULL, NULL, NULL, local_access_mem, 
-				     NULL, NULL, NULL, NULL, NULL};
-  unw_addr_space_t local_addr_space
-    = unw_create_addr_space(&local_accessors, 0);
-
-  unw_word_t start = 0;
+  int ret;
+  unw_word_t start = eh_frame_hdr_address;
+
+  // Version
+  unsigned char version;
+  if ((ret = dwarf_readu8 (as, eh_frame_accessors, &start,
+			   &version, eh_frame_arg)) < 0)
+    return -UNW_ENOINFO;
+
+  if (version != DW_EH_VERSION)
+    return -UNW_ENOINFO;
+
+  unsigned char eh_frame_ptr_enc;
+  if ((ret = dwarf_readu8 (as, eh_frame_accessors, &start,
+			   &eh_frame_ptr_enc, eh_frame_arg)) < 0)
+    return -UNW_ENOINFO;
+
+  unsigned char fde_count_enc;
+  if ((ret = dwarf_readu8 (as, eh_frame_accessors, &start,
+			   &fde_count_enc, eh_frame_arg)) < 0)
+    return -UNW_ENOINFO;
+
+  unsigned char table_enc;
+  if ((ret = dwarf_readu8 (as, eh_frame_accessors, &start,
+			   &table_enc, eh_frame_arg)) < 0)
+    return -UNW_ENOINFO;
   	
-   /* (Optionally) read eh_frame_ptr: */
-  if ((ret = dwarf_read_encoded_pointer (local_addr_space, &local_accessors,
-					 &start, hdr->eh_frame_ptr_enc, pi,
-					 &eh_frame_start, addr)) < 0)
-    return -1;
-    
-  Debug(99, "read eh_frame_start: 0x%lx\n", (long) eh_frame_start);
-
-  /* (Optionally) read fde_count: */
-  if ((ret = dwarf_read_encoded_pointer (local_addr_space, &local_accessors,
-					 &start, hdr->fde_count_enc, pi,
-					 &fde_count, addr)) < 0)
-    return -1;
-
-  Debug(99, "read fde_count: 0x%lx\n", (long) fde_count);
-  if (hdr->table_enc != (DW_EH_PE_datarel | DW_EH_PE_sdata4))
-    {
-      return -1;
-    }
+  if (table_enc != (DW_EH_PE_datarel | DW_EH_PE_sdata4))
+    return -UNW_ENOINFO;
+
+  unw_word_t eh_frame_start;
+  if ((ret = dwarf_read_encoded_pointer (as, eh_frame_accessors,
+					 &start, eh_frame_ptr_enc, pi,
+					 &eh_frame_start, eh_frame_arg)) < 0)
+    return -UNW_ENOINFO;
     
-  addr += start;
-
-  load_base = segbase - ptxt->p_vaddr;
-
-  di_cache.start_ip = segbase;
-  di_cache.end_ip = di_cache.start_ip + ptxt->p_memsz;
-  di_cache.format = UNW_INFO_FORMAT_REMOTE_TABLE;
-  di_cache.u.rti.name_ptr = 0;
-  /* two 32-bit values (ip_offset/fde_offset) per table-entry: */
-  di_cache.u.rti.table_len = (fde_count * 8) / sizeof (unw_word_t);
-  di_cache.u.rti.table_data = ((load_base + peh_hdr->p_vaddr)
-				   + (addr - (unw_word_t) image
-				      - peh_hdr->p_offset));
-
-  /* For the binary-search table in the eh_frame_hdr, data-relative
-     means relative to the start of that section... */
-  di_cache.u.rti.segbase = ((load_base + peh_hdr->p_vaddr)
-				+ ((unw_word_t) hdr - (unw_word_t) image
-				   - peh_hdr->p_offset));
-
-	Debug(99, "Leaving get_unwind_table\n");
-  return tdep_search_unwind_table (as, ip, &di_cache, pi, need_unwind_info, arg);
+  unw_word_t fde_count;
+  if ((ret = dwarf_read_encoded_pointer (as, eh_frame_accessors,
+					 &start, fde_count_enc, pi,
+					 &fde_count, eh_frame_arg)) < 0)
+    return -UNW_ENOINFO;
+
+  unw_dyn_info_t di;
+  di.start_ip = pi->start_ip;
+  di.end_ip = pi->end_ip;
+  di.format = UNW_INFO_FORMAT_REMOTE_TABLE;
+  di.gp = pi->gp;
+  di.u.rti.name_ptr = 0;
+  /* two 32-bit values (ip_offset/fde_offset) per table-entry:
+     For the binary-search table in the eh_frame_hdr, data-relative
+     means relative to the start of that section...
+     So for now we pass in peh_vaddr and use the main as for access. */
+  di.u.rti.table_len = (fde_count * 8) / sizeof (unw_word_t);
+  di.u.rti.table_data = peh_vaddr + 12;
+  di.u.rti.segbase = peh_vaddr;
+
+  return tdep_search_unwind_table (as, ip, &di, pi, need_unwind_info, arg);
 }
diff --git a/frysk-sys/lib/unwind/ChangeLog b/frysk-sys/lib/unwind/ChangeLog
index 1078b4a..933bca8 100644
--- a/frysk-sys/lib/unwind/ChangeLog
+++ b/frysk-sys/lib/unwind/ChangeLog
@@ -1,3 +1,16 @@
+2007-12-11  Mark Wielaard  <mwielaard@redhat.com>
+
+	* ElfImage.java (name): New field.
+	(ElfImage): Set name.
+	(toString): Add name.
+	* cni/ElfImage.cxx (mapElfImage): Pass in name.
+	* cni/UnwindH.hxx (access_mem): Handle memory holes.
+	(get_eh_frame_hdr_addr): New static function.
+	(local_access_mem): Likewise.
+	(createProcInfoFromElfImage): Use get_eh_frame_hdr_addr and
+	local_access_mem.
+	(createElfImageFromVDSO): Set name to [vdso].
+	
 2007-12-10  Andrew Cagney  <cagney@redhat.com>
 
 	* cni/UnwindH.hxx (access_fpreg): Allocate a buffer big enough for
diff --git a/frysk-sys/lib/unwind/ElfImage.java b/frysk-sys/lib/unwind/ElfImage.java
index 7707f09..e938c96 100644
--- a/frysk-sys/lib/unwind/ElfImage.java
+++ b/frysk-sys/lib/unwind/ElfImage.java
@@ -43,6 +43,8 @@ package lib.unwind;
 public class ElfImage
 {
 
+  final String name;
+
   long elfImage;
 
   long size;
@@ -53,8 +55,9 @@ public class ElfImage
 
   int ret = 0;
   
-  public ElfImage (long elfImage, long size, long segbase, long mapoff)
+  public ElfImage (String name, long elfImage, long size, long segbase, long mapoff)
   {
+    this.name = name;
     this.elfImage = elfImage;
     this.size = size;
     this.segbase = segbase;
@@ -63,6 +66,7 @@ public class ElfImage
 
   public ElfImage (int ret)
   {
+    this.name = "ERROR: " + Integer.toString(ret);
     this.ret = ret;
   }
 
@@ -71,7 +75,7 @@ public class ElfImage
     if (ret != 0)
       return "Bad Elf Image, ret: " + ret;
 
-    return "Elf Image: 0x" + Long.toHexString(elfImage) + " size: " + size
+    return "Elf Image (" + name + "): 0x" + Long.toHexString(elfImage) + " size: " + size
 	   + " segbase: 0x" + Long.toHexString(segbase) + " mapoff: 0x"
 	   + Long.toHexString(mapoff);
   }
diff --git a/frysk-sys/lib/unwind/cni/ElfImage.cxx b/frysk-sys/lib/unwind/cni/ElfImage.cxx
index d521643..3f7fdd3 100644
--- a/frysk-sys/lib/unwind/cni/ElfImage.cxx
+++ b/frysk-sys/lib/unwind/cni/ElfImage.cxx
@@ -80,7 +80,7 @@ lib::unwind::ElfImage::mapElfImage(jstring elfImageName, jlong segbase, jlong hi
     return new lib::unwind::ElfImage((jint) -1);
 
   lib::unwind::ElfImage* elfImage
-  = new lib::unwind::ElfImage((jlong) image, (jlong) size,
+    = new lib::unwind::ElfImage(elfImageName, (jlong) image, (jlong) size,
                               (jlong) segbase, (jlong) mapoff);
 
   return elfImage;
diff --git a/frysk-sys/lib/unwind/cni/UnwindH.hxx b/frysk-sys/lib/unwind/cni/UnwindH.hxx
index 1d13156..e1cc430 100644
--- a/frysk-sys/lib/unwind/cni/UnwindH.hxx
+++ b/frysk-sys/lib/unwind/cni/UnwindH.hxx
@@ -47,7 +47,9 @@
 
 #include <libdwfl.h>
 #include LIBUNWIND_TARGET_H
-#include <dwarf.h>
+
+#include <libelf.h>
+#include <gelf.h>
 
 #include <gcj/cni.h>
 
@@ -135,11 +137,22 @@ static int
 access_mem (::unw_addr_space_t as, ::unw_word_t addr,
 	    ::unw_word_t *valp, int write, void *arg)
 {
-  jbyteArray tmp = JvNewByteArray (sizeof (unw_word_t));
-  memcpy (elements(tmp), valp, JvGetArrayLength(tmp));
-  int ret = addressSpace(arg)->accessMem((jlong) addr, tmp, (jboolean) write);
-  memcpy(valp, elements(tmp), JvGetArrayLength(tmp));
-  return ret;
+  try
+    {
+      jbyteArray tmp = JvNewByteArray (sizeof (unw_word_t));
+      memcpy (elements(tmp), valp, JvGetArrayLength(tmp));
+      int ret = addressSpace(arg)->accessMem((jlong) addr,
+					     tmp, (jboolean) write);
+      memcpy(valp, elements(tmp), JvGetArrayLength(tmp));
+      return ret;
+    }
+  catch (java::lang::RuntimeException *t)
+    {
+      // We have to catch all RuntimeExceptions here since there
+      // is no indicator for just "invalid memory location".
+      // Core files might have "holes" in their memory.
+      return -UNW_EINVAL;
+    }
 }
 
 /*
@@ -429,26 +442,177 @@ lib::unwind::TARGET::getProcInfo(gnu::gcj::RawDataManaged* cursor)
   return myInfo;
 }
 
+// Return NULL when eh_frame_hdr cannot be found.
+// Also fills in ip->start_ip, ip->end_ip and ip->gp.
+// peh_vaddr will point to the address of the eh_frame_hdr in the main
+// address space of the inferior.
+static void *
+get_eh_frame_hdr_addr(unw_proc_info_t *pi, char *image, size_t size,
+		      unsigned long segbase, unw_word_t *peh_vaddr)
+{
+  if (elf_version(EV_CURRENT) == EV_NONE)
+    return NULL;
+
+  Elf *elf = elf_memory(image, size);
+  if (elf == NULL)
+    return NULL;
+
+  GElf_Ehdr ehdr;
+  if (gelf_getehdr(elf, &ehdr) == NULL)
+    return NULL;
+  
+  GElf_Phdr phdr;
+  int ptxt_ndx = -1, peh_hdr_ndx = -1, pdyn_ndx = -1;
+  for (int i = 0; i < ehdr.e_phnum; i++)
+    {
+      if (gelf_getphdr (elf, i, &phdr) == NULL)
+	return NULL;
+      
+      switch (phdr.p_type)
+        {
+        case PT_LOAD:
+          if (phdr.p_vaddr == segbase)
+            ptxt_ndx = i;
+          break;
+	  
+        case PT_GNU_EH_FRAME:
+          peh_hdr_ndx = i;
+          break;
+	  
+        case PT_DYNAMIC:
+          pdyn_ndx = i;
+          break;
+
+        default:
+          break;
+        }
+    }
+
+  if (ptxt_ndx == -1 || peh_hdr_ndx == -1)
+    return NULL;
+
+  GElf_Phdr ptxt, peh_hdr;
+  if (gelf_getphdr (elf, ptxt_ndx, &ptxt) == NULL)
+    return NULL;
+
+  if (gelf_getphdr (elf, peh_hdr_ndx, &peh_hdr) == NULL)
+    return NULL;
+
+  if (pdyn_ndx != -1)
+    {
+      /* For dynamicly linked executables and shared libraries,
+	 DT_PLTGOT is the value that data-relative addresses are
+	 relative to for that object.  We call this the "gp". */
+      GElf_Phdr pdyn;
+      if (gelf_getphdr (elf, pdyn_ndx, &pdyn) == NULL)
+	return NULL;
+
+      Elf_Scn *pdyn_scn = gelf_offscn(elf, pdyn.p_offset);
+      if (pdyn_scn == NULL)
+	return NULL;
+
+      Elf_Data *pdyn_data;
+      pdyn_data = elf_getdata (pdyn_scn, NULL);
+      if (pdyn_data == NULL)
+	return NULL;
+
+      GElf_Shdr pdyn_shdr;
+      if (gelf_getshdr (pdyn_scn, &pdyn_shdr) == NULL)
+	return NULL;
+
+      for (unsigned int i = 0;
+	   i < pdyn_shdr.sh_size / pdyn_shdr.sh_entsize; i++)
+	{
+	  GElf_Dyn dyn;
+	  if (gelf_getdyn (pdyn_data, i, &dyn) == NULL)
+	    return NULL;
+
+	  if (dyn.d_tag == DT_PLTGOT)
+	    {
+	      /* Assume that _DYNAMIC is writable and GLIBC has
+		 relocated it (true for x86 at least). */
+	      pi->gp = dyn.d_un.d_ptr;
+	      break;
+	    }
+	}
+    }
+  else
+    /* Otherwise this is a static executable with no _DYNAMIC.  Assume
+       that data-relative addresses are relative to 0, i.e.,
+       absolute. */
+    pi->gp = 0;
+
+  pi->start_ip = segbase;


hooks/post-receive
--
frysk system monitor/debugger


                 reply	other threads:[~2007-12-11 14:35 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20071211143548.25846.qmail@sourceware.org \
    --to=mark@sourceware.org \
    --cc=frysk-cvs@sourceware.org \
    --cc=frysk@sourceware.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).