public inbox for gdb-patches@sourceware.org
 help / color / mirror / Atom feed
From: Don Breazeal <donb@codesourcery.com>
To: <gdb-patches@sourceware.org>
Subject: [PATCH 08/10] Extended-remote follow vfork
Date: Thu, 07 Aug 2014 18:01:00 -0000	[thread overview]
Message-ID: <1407434395-19089-9-git-send-email-donb@codesourcery.com> (raw)
In-Reply-To: <1407434395-19089-1-git-send-email-donb@codesourcery.com>

This patch implements follow-fork for vfork on extended-remote linux targets.

The implementation follows the native implementation as much as possible.  Most of the work is done on the GDB side in the existing code now in infrun.c.  GDBserver just has to report the events and do a little bookkeeping.

Implementation was almost entirely in gdbserver, excepting changes to gdb/remote.c, and included:

 * enabling VFORK events
   - by adding ptrace options for VFORK and VFORK_DONE as 'additional options' in linux-low.c:initialize_low, so that they are available for extended-mode.

   - by adding ptrace options for VFORK and VFORK_DONE as 'base options' in linux-low.c:linux_enable_extended_features.  In this function we know we are in extended-mode, so we enable these options if they are supported.
 
 * handling VFORK and VFORK_DONE events in linux-low.c:handle_extended_wait by saving the event information for event reporting and for the follow_fork request expected to come from GDB.
 
 * handling VFORK in linux-low.c:linux_follow_fork, for the case where VFORK_DONE is supported and the case where it is not.  This code is very similar to the code in linux-nat.c:linux_child_follow_fork, but slight differences in the data structures between the two implementations led me to keep them separate.

 * include VFORK and VFORK_DONE events in the predicate linux-low.c:extended_event_reported.

 * add support for VFORK and VFORK_DONE events in RSP by adding stop reasons "vfork" and "vforkdone" to the 'T' Stop Reply Packet in both gdbserver/remote-utils.c and gdb/remote.c.

Tested on x64 Ubuntu Lucid, native, remote, extended-remote.  Also tested (on the same system) support for systems lacking VFORK_DONE events by modifying gdbserver to simulate that case.

Thanks,
--Don

gdb/
2014-08-06  Don Breazeal  <donb@codesourcery.com>
	* remote.c: New stop reasons "vfork"
	and "vforkdone" for RSP 'T' Stop Reply Packet.

gdb/gdbserver/
2014-08-06  Don Breazeal  <donb@codesourcery.com>
	* linux-low.c (handle_extended_wait): Handle PTRACE_EVENT_VFORK and
	PTRACE_EVENT_VFORK_DONE.
	(linux_enable_extended_features): Enable PTRACE_EVENT_VFORK and
	PTRACE_EVENT_VFORK_DONE.
	(linux_follow_fork): Handle following a vfork.
	(extended_event_reported): Add vfork and vfork-done events to
	the list of extended events.
	(initialize_low): Enable PTRACE_EVENT_VFORK and
	PTRACE_EVENT_VFORK_DONE.
	* remote-utils.c (prepare_resume_reply): New stop reasons "vfork"
	and "vforkdone" for RSP 'T' Stop Reply Packet.

---
 gdb/gdbserver/linux-low.c    |  107 ++++++++++++++++++++++++++++++++++++------
 gdb/gdbserver/remote-utils.c |   16 ++++++-
 gdb/remote.c                 |   15 ++++++
 3 files changed, 121 insertions(+), 17 deletions(-)

diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c
index 4958fdb..69cad15 100644
--- a/gdb/gdbserver/linux-low.c
+++ b/gdb/gdbserver/linux-low.c
@@ -210,6 +210,7 @@ int using_threads = 1;
    jump pads).  */
 static int stabilizing_threads;
 
+static void async_file_mark (void);
 static void linux_resume_one_lwp (struct lwp_info *lwp,
 				  int step, int signal, siginfo_t *info);
 static void linux_resume (struct thread_resume *resume_info, size_t n);
@@ -218,6 +219,9 @@ static void unstop_all_lwps (int unsuspend, struct lwp_info *except);
 static int linux_wait_for_event_filtered (ptid_t wait_ptid, ptid_t filter_ptid,
 					  int *wstat, int options);
 static int linux_wait_for_event (ptid_t ptid, int *wstat, int options);
+static ptid_t linux_wait_1 (ptid_t ptid,
+			    struct target_waitstatus *ourstatus,
+			    int target_options);
 static struct lwp_info *add_lwp (ptid_t ptid);
 static int linux_stopped_by_watchpoint (void);
 static void mark_lwp_dead (struct lwp_info *lwp, int wstat);
@@ -379,7 +383,8 @@ handle_extended_wait (struct lwp_info *event_child, int wstat)
   struct thread_info *event_thr = get_lwp_thread (event_child);
   struct lwp_info *new_lwp;
 
-  if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_CLONE)
+  if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK
+      || event == PTRACE_EVENT_CLONE)
     {
       ptid_t ptid;
       unsigned long new_pid;
@@ -404,7 +409,7 @@ handle_extended_wait (struct lwp_info *event_child, int wstat)
 	    warning ("wait returned unexpected status 0x%x", status);
 	}
 
-      if (event == PTRACE_EVENT_FORK)
+      if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK)
        {
          struct process_info *parent_proc;
          struct process_info *child_proc;
@@ -444,12 +449,18 @@ handle_extended_wait (struct lwp_info *event_child, int wstat)
 	 if (the_low_target.new_fork != NULL)
 	   the_low_target.new_fork (parent_proc, child_proc);
 
-         /* Save fork info for target processing.  */
-         current_inferior->pending_follow.kind = TARGET_WAITKIND_FORKED;
+         /* Save fork info for target processing and reporting to GDB.  */
+	 if (event == PTRACE_EVENT_FORK)
+	   {
+             current_inferior->pending_follow.kind = TARGET_WAITKIND_FORKED;
+             event_child->waitstatus.kind = TARGET_WAITKIND_FORKED;
+	   }
+	 else if (event == PTRACE_EVENT_VFORK)
+	   {
+             current_inferior->pending_follow.kind = TARGET_WAITKIND_VFORKED;
+             event_child->waitstatus.kind = TARGET_WAITKIND_VFORKED;
+	   }
          current_inferior->pending_follow.value.related_pid = ptid;
-
-         /* Save fork info for reporting to GDB.  */
-         event_child->waitstatus.kind = TARGET_WAITKIND_FORKED;
          event_child->waitstatus.value.related_pid = ptid;
 
          /* Report the event.  */
@@ -509,6 +520,11 @@ handle_extended_wait (struct lwp_info *event_child, int wstat)
       /* Don't report the event.  */
       return 1;
     }
+  else if (event == PTRACE_EVENT_VFORK_DONE)
+    {
+      event_child->waitstatus.kind = TARGET_WAITKIND_VFORK_DONE;
+      return 0;
+    }
 
   internal_error (__FILE__, __LINE__, _("unknown ptrace event %d"), event);
 }
@@ -1250,7 +1266,9 @@ linux_enable_extended_features (void)
       /* There is no process yet, so include extended options in the
 	 base options for subsequent ptrace configuration.  */
       linux_ptrace_set_desired_options (PTRACE_O_TRACECLONE
-					| PTRACE_O_TRACEFORK, 0);
+					| PTRACE_O_TRACEFORK
+					| PTRACE_O_TRACEVFORK
+					| PTRACE_O_TRACEVFORKDONE, 0);
     }
   else
     {
@@ -1298,6 +1316,7 @@ linux_follow_fork (int follow_child, int detach_fork)
 {
   struct inferior_list_entry *parent_inf;
   struct thread_info *parent_thread;
+  int has_vforked;
   
   parent_inf = find_inferior (&all_threads, is_parent_callback, NULL);
 
@@ -1307,7 +1326,8 @@ linux_follow_fork (int follow_child, int detach_fork)
     return 0;
 
   parent_thread = (struct thread_info *)parent_inf;
-  parent_thread->pending_follow.kind = TARGET_WAITKIND_IGNORE;
+  has_vforked = (parent_thread->pending_follow.kind
+		 == TARGET_WAITKIND_VFORKED);
 
   if (!follow_child)
     {
@@ -1350,7 +1370,7 @@ linux_follow_fork (int follow_child, int detach_fork)
 		signo = 0;
 
 	      ptrace (PTRACE_DETACH, child_pid, 0, signo);
-	      
+
 	      /* Deallocate all process-related storage.  */
 	      child_proc = find_process_pid (child_pid);
 	      if (child_proc != NULL)
@@ -1359,8 +1379,63 @@ linux_follow_fork (int follow_child, int detach_fork)
 	      current_inferior = NULL;
 	    }
 	}
+
+      if (has_vforked)
+	{
+	  struct lwp_info *parent_lp;
+
+	  parent_lp = get_thread_lwp (parent_thread);
+	  gdb_assert (linux_supports_tracefork () >= 0);
+
+	  if (linux_supports_tracevforkdone ())
+	    {
+  	      if (debug_threads)
+		{
+		  debug_printf ("LFF: waiting for VFORK_DONE on %d\n",
+				ptid_get_pid (parent_thread->entry.id));
+		}
+	      parent_lp->stopped = 1;
+
+	      /* We'll handle the VFORK_DONE event like any other
+		 event, in target_wait.  */
+	    }
+	  else
+	    {
+	      /* We can't insert breakpoints until the child has
+		 finished with the shared memory region.  Without
+		 VFORK_DONE events, the best we can do is to make sure it
+		 runs for a little while.  Hopefully it will be out of
+		 range of any breakpoints we reinsert.  Usually this
+		 is only the single-step breakpoint at vfork's return
+		 point.
+		 
+		 See linux-nat.c:linux_child_follow_vfork for a more
+		 detailed discussion of this.  */
+
+  	      if (debug_threads)
+  		debug_printf ("LFF: no VFORK_DONE support, sleeping a bit\n");
+
+	      usleep (10000);
+
+	      /* Pretend we've seen a PTRACE_EVENT_VFORK_DONE event,
+		 and leave it pending.  The next linux_resume_one_lwp call
+		 will notice a pending event, and bypasses actually
+		 resuming the inferior.  */
+	      parent_lp->status_pending_p = 1;
+	      parent_lp->waitstatus.kind = TARGET_WAITKIND_VFORK_DONE;
+	      parent_lp->stopped = 1;
+
+	      /* If we're in async mode, need to tell the event loop
+		 there's something here to process.  */
+	      if (target_is_async_p ())
+		async_file_mark ();
+	    }
+	}
     }
 
+  parent_thread->pending_follow.kind = TARGET_WAITKIND_IGNORE;
+  parent_thread->pending_follow.value.related_pid = null_ptid;
+
   return 0;
 }
 
@@ -2560,9 +2635,6 @@ static void move_out_of_jump_pad_callback (struct inferior_list_entry *entry);
 static int stuck_in_jump_pad_callback (struct inferior_list_entry *entry,
 				       void *data);
 static int lwp_running (struct inferior_list_entry *entry, void *data);
-static ptid_t linux_wait_1 (ptid_t ptid,
-			    struct target_waitstatus *ourstatus,
-			    int target_options);
 
 /* Stabilize threads (move out of jump pads).
 
@@ -2675,7 +2747,9 @@ extended_event_reported (const struct target_waitstatus *waitstatus)
   if (waitstatus == NULL)
     return 0;
 
-  return (waitstatus->kind == TARGET_WAITKIND_FORKED);
+  return (waitstatus->kind == TARGET_WAITKIND_FORKED
+          || waitstatus->kind == TARGET_WAITKIND_VFORKED
+	  || waitstatus->kind == TARGET_WAITKIND_VFORK_DONE);
 }
 
 /* Wait for process, returns status.  */
@@ -6358,5 +6432,8 @@ initialize_low (void)
 
   initialize_low_arch ();
 
-  linux_ptrace_set_desired_options (PTRACE_O_TRACECLONE, PTRACE_O_TRACEFORK);
+  linux_ptrace_set_desired_options (PTRACE_O_TRACECLONE,
+				    PTRACE_O_TRACEFORK
+				    | PTRACE_O_TRACEVFORK
+				    | PTRACE_O_TRACEVFORKDONE);
 }
diff --git a/gdb/gdbserver/remote-utils.c b/gdb/gdbserver/remote-utils.c
index a3b12dd..00df57c 100644
--- a/gdb/gdbserver/remote-utils.c
+++ b/gdb/gdbserver/remote-utils.c
@@ -1112,15 +1112,18 @@ prepare_resume_reply (char *buf, ptid_t ptid,
     {
     case TARGET_WAITKIND_STOPPED:
     case TARGET_WAITKIND_FORKED:
+    case TARGET_WAITKIND_VFORKED:
       {
 	struct thread_info *saved_inferior;
 	const char **regp;
 	struct regcache *regcache;
  
-	if (status->kind == TARGET_WAITKIND_FORKED && multi_process)
+	if ((status->kind == TARGET_WAITKIND_FORKED 
+	     || status->kind == TARGET_WAITKIND_VFORKED) && multi_process)
 	  {
 	    enum gdb_signal signal = GDB_SIGNAL_TRAP;
-	    const char *event = "fork";
+	    const char *event = (status->kind == TARGET_WAITKIND_FORKED
+				 ?"fork":"vfork");
 
 	    sprintf (buf, "T%02x%s:p%x.%lx;", signal, event,
 		      ptid_get_pid (status->value.related_pid),
@@ -1231,6 +1234,15 @@ prepare_resume_reply (char *buf, ptid_t ptid,
       else
 	sprintf (buf, "X%02x", status->value.sig);
       break;
+    case TARGET_WAITKIND_VFORK_DONE:
+      if (multi_process)
+	{
+	  enum gdb_signal signal = GDB_SIGNAL_TRAP;
+	  const char *event = "vforkdone";
+	  
+	  sprintf (buf, "T%02x%s:;", signal, event);
+	}
+      break;
     default:
       error ("unhandled waitkind");
       break;
diff --git a/gdb/remote.c b/gdb/remote.c
index d9989db..1304949 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -5510,6 +5510,21 @@ Packet: '%s'\n"),
 		  event->ws.value.related_pid = read_ptid (++p1, &p);
 		  event->ws.kind = TARGET_WAITKIND_FORKED;
 		}
+	      else if (strncmp (p, "vfork", p1 - p) == 0)
+		{
+		  event->ws.value.related_pid = read_ptid (++p1, &p);
+		  event->ws.kind = TARGET_WAITKIND_VFORKED;
+		}
+	      else if (strncmp (p, "vforkdone", p1 - p) == 0)
+		{
+		  p1++;
+		  p_temp = p1;
+		  while (*p_temp && *p_temp != ';')
+		    p_temp++;
+
+		  event->ws.kind = TARGET_WAITKIND_VFORK_DONE;
+		  p = p_temp;
+		}
 	      else
 		{
 		  /* Silently skip unknown optional info.  */
-- 
1.7.0.4

  parent reply	other threads:[~2014-08-07 18:01 UTC|newest]

Thread overview: 110+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2014-08-07 18:00 [PATCH 00/10] Linux extended-remote fork events Don Breazeal
2014-08-07 18:00 ` [PATCH 02/10] Refactor follow-fork message printing Don Breazeal
2014-08-07 18:00 ` [PATCH 06/10] Extended-remote follow fork Don Breazeal
2014-08-07 18:00 ` [PATCH 04/10] Enhance extended ptrace event setup Don Breazeal
2014-08-13 17:50   ` Breazeal, Don
2014-08-07 18:00 ` [PATCH 03/10] Refactor extended ptrace event status Don Breazeal
2014-08-07 18:00 ` [PATCH 01/10] Refactor native follow-fork Don Breazeal
2014-08-07 18:00 ` [PATCH 07/10] Extended-remote arch-specific follow fork Don Breazeal
2014-08-07 18:00 ` [PATCH 05/10] GDBserver clone breakpoint list Don Breazeal
2014-08-07 18:01 ` [PATCH 09/10] Extended-remote fork catchpoints Don Breazeal
2014-08-07 18:01 ` Don Breazeal [this message]
2014-08-07 18:01 ` [PATCH 10/10] Extended-remote fork event documentation Don Breazeal
2014-08-07 19:31   ` Eli Zaretskii
2014-08-08 15:35     ` Breazeal, Don
2014-08-21  0:29 ` [PATCH 01/16 v2] Refactor native follow-fork Don Breazeal
2014-09-05 14:20   ` Pedro Alves
2014-09-05 18:56     ` Breazeal, Don
2014-09-05 20:20       ` Breazeal, Don
2014-09-09 10:57       ` Pedro Alves
2014-09-08 23:54     ` Breazeal, Don
2014-09-09 11:09       ` Pedro Alves
2014-09-12 16:50         ` Breazeal, Don
2014-09-22 15:53           ` Breazeal, Don
2014-09-26 18:13           ` Pedro Alves
2014-09-29 18:08             ` Breazeal, Don
2014-09-30 10:56               ` Pedro Alves
2014-09-30 18:43                 ` Breazeal, Don
2014-08-21  0:29 ` [Patch 00/16 v2] Linux extended-remote fork and exec events Don Breazeal
2014-09-04 20:57   ` Breazeal, Don
2014-10-31 23:29   ` [PATCH 08/16 v3] Extended-remote follow vfork Don Breazeal
2014-10-31 23:29   ` [PATCH 07/16 v3] Extended-remote arch-specific follow fork Don Breazeal
2014-10-31 23:29   ` [PATCH 00/16 v3] Linux extended-remote fork and exec events Don Breazeal
2014-11-12 15:54     ` Pedro Alves
2014-11-13 13:41     ` Pedro Alves
2014-11-13 13:51       ` Pedro Alves
2014-11-13 14:58         ` Pedro Alves
2014-11-13 19:14     ` Pedro Alves
2014-10-31 23:29   ` [PATCH 06/16 v3] Extended-remote Linux follow fork Don Breazeal
2014-11-13 13:00     ` Pedro Alves
2014-11-13 18:53       ` Breazeal, Don
2014-11-13 18:59         ` Pedro Alves
2014-11-13 19:06           ` Breazeal, Don
2014-12-06  0:31             ` Breazeal, Don
2015-01-23 12:53               ` Pedro Alves
2015-01-23 17:18                 ` Breazeal, Don
     [not found]                 ` <1422222420-25421-1-git-send-email-donb@codesourcery.com>
2015-01-25 21:49                   ` [PATCH v4 5/7] Arch-specific remote " Don Breazeal
2015-02-10 16:37                     ` Pedro Alves
2015-01-25 21:49                   ` [PATCH v4 6/7] Remote follow vfork Don Breazeal
2015-02-10 16:39                     ` Pedro Alves
2015-01-25 21:50                   ` [PATCH v4 2/7] Clone remote breakpoints Don Breazeal
2015-01-25 21:50                   ` [PATCH v4 1/7] Identify remote fork event support Don Breazeal
2015-02-10 16:34                     ` Pedro Alves
2015-01-25 21:58                   ` [PATCH v4 7/7] Remote fork catch Don Breazeal
2015-01-26  0:07                   ` [PATCH v4 3/7 v3] Extended-remote Linux follow fork Don Breazeal
2015-02-10 16:36                     ` Pedro Alves
2015-01-26  0:20                   ` [PATCH v4 4/7] Target remote " Don Breazeal
2015-01-12 22:39             ` [PATCH 06/16 v3] Extended-remote Linux " Don Breazeal
2015-01-12 22:49               ` Breazeal, Don
2014-10-31 23:29   ` [PATCH 04/16 v3] Determine supported extended-remote features Don Breazeal
2014-11-13 12:59     ` Pedro Alves
2014-11-13 18:28       ` Breazeal, Don
2014-11-13 18:33         ` Pedro Alves
2014-11-13 19:08           ` Pedro Alves
2014-11-13 18:37         ` Breazeal, Don
2014-11-13 18:48           ` Pedro Alves
2014-12-06  0:30             ` Breazeal, Don
2015-01-12 22:36           ` Don Breazeal
2015-01-21 21:02             ` Breazeal, Don
2014-10-31 23:29   ` [PATCH 05/16 v3] GDBserver clone breakpoint list Don Breazeal
2014-10-31 23:30   ` [PATCH 12/16 v3] Extended-remote follow exec Don Breazeal
2014-10-31 23:30   ` [PATCH 13/16 v3] Extended-remote exec catchpoints Don Breazeal
2014-10-31 23:30   ` [PATCH 10/16 v3] Extended-remote fork event documentation Don Breazeal
2014-10-31 23:30   ` [PATCH 09/16 v3] Extended-remote fork catchpoints Don Breazeal
2014-10-31 23:30   ` [PATCH 11/16 v3] Extended-remote Linux exit events Don Breazeal
2014-11-13 19:18     ` Pedro Alves
2014-10-31 23:31   ` [PATCH 14/16 v3] Suppress spurious warnings with extended-remote follow exec Don Breazeal
2014-10-31 23:31   ` [PATCH 15/16 v3] Extended-remote exec event documentation Don Breazeal
2014-10-31 23:31   ` [PATCH 16/16 v3] Non-stop follow exec tests Don Breazeal
2014-08-21  0:30 ` [PATCH 04/16 v2] Determine supported extended-remote features Don Breazeal
2014-10-15 16:17   ` Pedro Alves
2014-10-21 23:23     ` Breazeal, Don
2014-10-22 21:48       ` Pedro Alves
2014-10-31 23:38         ` Breazeal, Don
2014-08-21  0:30 ` [PATCH 02/16 v2] Refactor follow-fork message printing Don Breazeal
2014-09-26 19:52   ` Pedro Alves
2014-09-26 20:14     ` Breazeal, Don
2014-10-03 23:51       ` Breazeal, Don
2014-10-15 16:08       ` Pedro Alves
2014-10-22 23:47         ` Breazeal, Don
2014-10-24 12:35           ` Pedro Alves
2014-10-24 18:45             ` Breazeal, Don
2014-08-21  0:30 ` [PATCH 03/16 v2] Refactor ptrace extended event status Don Breazeal
2014-09-09 11:31   ` Pedro Alves
2014-09-19 18:14     ` [pushed] " Breazeal, Don
2014-08-21  0:31 ` [PATCH 05/16 v2] GDBserver clone breakpoint list Don Breazeal
2014-10-15 17:40   ` Pedro Alves
2014-10-31 23:44     ` Breazeal, Don
2014-08-21  0:31 ` [PATCH 06/16 v2] Extended-remote Linux follow fork Don Breazeal
2014-09-19 20:57   ` Breazeal, Don
2014-08-21  0:31 ` [PATCH 07/16 v2] Extended-remote arch-specific " Don Breazeal
2014-09-19 21:26   ` Breazeal, Don
2014-08-21  0:32 ` [PATCH 08/16 v2] Extended-remote follow vfork Don Breazeal
2014-08-21  0:33 ` [PATCH 09/16 v2] Extended-remote fork catchpoints Don Breazeal
2014-08-21  0:33 ` [PATCH 10/16 v2] Extended-remote fork event documentation Don Breazeal
2014-08-21  0:33 ` [PATCH 11/16 v2] Extended-remote Linux exit events Don Breazeal
2014-08-21  0:34 ` [PATCH 12/16 v2] Extended-remote follow exec Don Breazeal
2014-08-21  0:34 ` [PATCH 13/16 v2] Extended-remote exec catchpoints Don Breazeal
2014-08-21  0:35 ` [PATCH 14/16 v2] Suppress spurious warnings with extended-remote follow exec Don Breazeal
2014-08-21  0:36 ` [PATCH 16/16 v2] Non-stop follow exec tests Don Breazeal
2014-08-21  0:36 ` [PATCH 15/16 v2] Extended-remote exec event documentation Don Breazeal

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=1407434395-19089-9-git-send-email-donb@codesourcery.com \
    --to=donb@codesourcery.com \
    --cc=gdb-patches@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).