All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Lluís Vilanova" <vilanova@ac.upc.edu>
To: qemu-devel@nongnu.org
Cc: "Eric Blake" <eblake@redhat.com>,
	"Emilio G. Cota" <cota@braap.org>,
	"Stefan Hajnoczi" <stefanha@redhat.com>,
	"Lluís Vilanova" <vilanova@ac.upc.edu>,
	"Paolo Bonzini" <pbonzini@redhat.com>,
	"Richard Henderson" <rth@twiddle.net>
Subject: [Qemu-devel] [PATCH v4 16/20] instrument: Add event 'guest_mem_before_trans'
Date: Wed,  6 Sep 2017 21:27:18 +0300	[thread overview]
Message-ID: <150472243868.24907.11184861173318139147.stgit@frigg.lan> (raw)
In-Reply-To: <150471856141.24907.274176769201097378.stgit@frigg.lan>

Signed-off-by: Lluís Vilanova <vilanova@ac.upc.edu>
---
 Makefile.target                 |    1 +
 instrument/control.c            |   13 +++++++-
 instrument/control.h            |   36 +++++++++++++++++++++-
 instrument/control.inc.h        |   16 +++++++---
 instrument/events.h             |   21 +++++++++++++
 instrument/events.inc.h         |   19 ++++++++++++
 instrument/load.c               |    1 +
 instrument/qemu-instr/control.h |   15 +++++++++
 instrument/qemu-instr/types.h   |   64 +++++++++++++++++++++++++++++++++++++++
 stubs/instrument.c              |    4 ++
 tcg/tcg-op.c                    |    5 +++
 trace/control.h                 |   23 ++++++++++++++
 trace/mem.h                     |   23 --------------
 13 files changed, 210 insertions(+), 31 deletions(-)

diff --git a/Makefile.target b/Makefile.target
index 7f42c45db8..6997b921c9 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -196,6 +196,7 @@ $(QEMU_PROG_BUILD): config-devices.mak
 COMMON_LDADDS = ../libqemuutil.a ../libqemustub.a
 
 # build either PROG or PROGW
+$(QEMU_PROG_BUILD): CFLAGS += -DQEMU_TARGET_BUILD=1
 $(QEMU_PROG_BUILD): $(all-obj-y) $(COMMON_LDADDS)
 	$(call LINK, $(filter-out %.mak, $^))
 ifdef CONFIG_DARWIN
diff --git a/instrument/control.c b/instrument/control.c
index 3cec1028e5..3c3875dc99 100644
--- a/instrument/control.c
+++ b/instrument/control.c
@@ -16,7 +16,7 @@
 #include "qom/cpu.h"
 
 
-__thread InstrState instr_cur_state;
+__thread InstrInfo instr_cur_info;
 
 
 unsigned int instr_cpus_count;
@@ -75,3 +75,14 @@ QI_VPUBLIC void qi_event_set_guest_cpu_reset(void (*fn)(QICPU vcpu))
     ERROR_IF(!instr_get_state(), "called outside instrumentation");
     instr_set_event(guest_cpu_reset, fn);
 }
+
+
+void (*instr_event__guest_mem_before_trans)(
+    QICPU vcpu_trans, QITCGv_cpu vcpu_exec, QITCGv vaddr, QIMemInfo info);
+
+QI_VPUBLIC void qi_event_set_guest_mem_before_trans(
+    void (*fn)(QICPU vcpu_trans, QITCGv_cpu vcpu_exec, QITCGv vaddr, QIMemInfo info))
+{
+    ERROR_IF(!instr_get_state(), "called outside instrumentation");
+    instr_set_event(guest_mem_before_trans, fn);
+}
diff --git a/instrument/control.h b/instrument/control.h
index 0c37692465..d9e3dd3da6 100644
--- a/instrument/control.h
+++ b/instrument/control.h
@@ -56,12 +56,21 @@ typedef enum {
     INSTR_STATE_ENABLE,
 } InstrState;
 
+#define INSTR_MAX_TCG_REGS 16
+
+typedef struct InstrInfo {
+    InstrState state;
+    unsigned int max;
+    void *tcg_regs[INSTR_MAX_TCG_REGS];
+} InstrInfo;
+
 /**
  * instr_set_state:
  *
- * Set the instrumentation state of the current host thread.
+ * Set the instrumentation state of the current host thread, and return its
+ * #InstrInfo.
  */
-static inline void instr_set_state(InstrState state);
+static inline InstrInfo *instr_set_state(InstrState state);
 
 /**
  * instr_get_state:
@@ -70,6 +79,29 @@ static inline void instr_set_state(InstrState state);
  */
 static inline InstrState instr_get_state(void);
 
+/**
+ * instr_tcg_set:
+ * @info: Pointer to #InstrInfo.
+ * @num: Number of TCG register used by instrumentation.
+ * @arg: TCG register.
+ *
+ * Get a suitable QITCGv* from a TCGv* value.
+ */
+#define instr_tcg_set(info, num, arg) \
+    ({                                \
+        info->tcg_regs[num] = arg;    \
+        (void *)num;                  \
+    })
+
+/**
+ * instr_tcg_count:
+ * @info: Pointer to #InstrInfo.
+ * @count: Number of TCG registers used by instrumentation.
+ *
+ * Set the number of TCG registers used by instrumentation.
+ */
+static inline void instr_tcg_count(InstrInfo *info, unsigned int count);
+
 
 #include "instrument/control.inc.h"
 
diff --git a/instrument/control.inc.h b/instrument/control.inc.h
index 18ae6a34cc..e8224319e0 100644
--- a/instrument/control.inc.h
+++ b/instrument/control.inc.h
@@ -15,16 +15,18 @@
 #include <stdint.h>
 
 
-extern __thread InstrState instr_cur_state;
+extern __thread InstrInfo instr_cur_info;
 
-static inline void instr_set_state(InstrState state)
+static inline InstrInfo *instr_set_state(InstrState state)
 {
-    atomic_store_release(&instr_cur_state, state);
+    InstrInfo *info = &instr_cur_info;
+    atomic_store_release(&info->state, state);
+    return info;
 }
 
 static inline InstrState instr_get_state(void)
 {
-    return atomic_load_acquire(&instr_cur_state);
+    return atomic_load_acquire(&instr_cur_info.state);
 }
 
 
@@ -46,3 +48,9 @@ static inline QICPU instr_cpu_set(CPUState *vcpu)
     uintptr_t idx = vcpu->cpu_index;
     return (QICPU )idx;
 }
+
+
+static inline void instr_tcg_count(InstrInfo *info, unsigned int count)
+{
+    info->max = count;
+}
diff --git a/instrument/events.h b/instrument/events.h
index 4a0560490a..1cc4dbb052 100644
--- a/instrument/events.h
+++ b/instrument/events.h
@@ -12,6 +12,8 @@
 
 #include "instrument/qemu-instr/control.h"
 #include "instrument/qemu-instr/types.h"
+#include "trace/control.h"
+
 
 /**
  * instr_get_event:
@@ -30,6 +32,20 @@
     atomic_store_release(&instr_event__ ## name, fn)
 
 
+/*
+ * Re-define types used by some instrumentation events. We need some arbitrary
+ * definition for non-target objects.
+ */
+#if defined(QEMU_TARGET_BUILD)
+#include "tcg/tcg.h"
+#else
+typedef struct TCGv_d *TCGv;
+typedef struct TCGv_env_d *TCGv_env;
+typedef struct TCGv_i32_d *TCGv_i32;
+typedef struct TCGv_i64_d *TCGv_i64;
+#endif
+
+
 extern qi_fini_fn instr_event__fini_fn;
 extern void *instr_event__fini_data;
 
@@ -42,6 +58,11 @@ static inline void instr_guest_cpu_exit(CPUState *vcpu);
 extern void (*instr_event__guest_cpu_reset)(QICPU vcpu);
 static inline void instr_guest_cpu_reset(CPUState *vcpu);
 
+extern void (*instr_event__guest_mem_before_trans)(
+    QICPU vcpu_trans, QITCGv_cpu vcpu_exec, QITCGv vaddr, QIMemInfo info);
+static inline void instr_guest_mem_before_trans(
+    CPUState *vcpu_trans, TCGv_env vcpu_exec, TCGv vaddr, TraceMemInfo info);
+
 
 #include "instrument/events.inc.h"
 
diff --git a/instrument/events.inc.h b/instrument/events.inc.h
index 2f2cd324aa..2cb17049f7 100644
--- a/instrument/events.inc.h
+++ b/instrument/events.inc.h
@@ -8,6 +8,7 @@
  */
 
 #include "instrument/control.h"
+#include "trace/control.h"
 
 
 static inline void instr_guest_cpu_enter(CPUState *vcpu)
@@ -42,3 +43,21 @@ static inline void instr_guest_cpu_reset(CPUState *vcpu)
         instr_set_state(INSTR_STATE_DISABLE);
     }
 }
+
+static inline void instr_guest_mem_before_trans(
+    CPUState *vcpu_trans, TCGv_env vcpu_exec, TCGv vaddr, TraceMemInfo info)
+{
+    void (*cb)(QICPU vcpu_trans, QITCGv_cpu vcpu_exec, QITCGv vaddr, QIMemInfo info)
+        = instr_get_event(guest_mem_before_trans);
+    if (cb) {
+        InstrInfo *iinfo = instr_set_state(INSTR_STATE_ENABLE);
+        QICPU vcpu_trans_ = instr_cpu_set(vcpu_trans);
+        QITCGv_cpu vcpu_exec_ = instr_tcg_set(iinfo, 0, vcpu_exec);
+        QITCGv vaddr_ = instr_tcg_set(iinfo, 1, vaddr);
+        QIMemInfo info_;
+        info_.raw = info.raw;
+        instr_tcg_count(iinfo, 2);
+        (*cb)(vcpu_trans_, vcpu_exec_, vaddr_, info_);
+        instr_set_state(INSTR_STATE_DISABLE);
+    }
+}
diff --git a/instrument/load.c b/instrument/load.c
index d9310d1979..d5612af452 100644
--- a/instrument/load.c
+++ b/instrument/load.c
@@ -151,6 +151,7 @@ InstrUnloadError instr_unload(int64_t handle_id)
     instr_set_event(guest_cpu_enter, NULL);
     instr_set_event(guest_cpu_exit, NULL);
     instr_set_event(guest_cpu_reset, NULL);
+    instr_set_event(guest_mem_before_trans, NULL);
 
     /* this should never fail */
     if (dlclose(handle->dlhandle) < 0) {
diff --git a/instrument/qemu-instr/control.h b/instrument/qemu-instr/control.h
index 238ea63301..af4fda138e 100644
--- a/instrument/qemu-instr/control.h
+++ b/instrument/qemu-instr/control.h
@@ -98,6 +98,21 @@ void qi_event_set_guest_cpu_exit(void (*fn)(QICPU vcpu));
  */
 void qi_event_set_guest_cpu_reset(void (*fn)(QICPU vcpu));
 
+/*
+ * Start virtual memory access (before any potential access violation).
+ *
+ * @vaddr: Access' virtual address.
+ * @info : Access' information.
+ *
+ * Does not include memory accesses performed by devices.
+ *
+ * Mode: user, softmmu
+ * Targets: TCG(all)
+ * Time: trans
+ */
+void qi_event_set_guest_mem_before_trans(
+    void (*fn)(QICPU vcpu_trans, QITCGv_cpu vcpu_exec, QITCGv vaddr, QIMemInfo info));
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/instrument/qemu-instr/types.h b/instrument/qemu-instr/types.h
index ea3a032b4f..11cbe1ccaa 100644
--- a/instrument/qemu-instr/types.h
+++ b/instrument/qemu-instr/types.h
@@ -14,10 +14,18 @@
 extern "C" {
 #endif
 
+#include <stdbool.h>
+#include <stdint.h>
+
+
 /**
  * SECTION: types
  * @section_id: qi-types
  * @title: Common types
+ *
+ * Data of architecture-specific length is always passed as an #int64_t to
+ * provide binary compatibility between the instrumentation library and QEMU,
+ * regardless of the guest architecture being instrumented.
  */
 
 /**
@@ -41,6 +49,62 @@ typedef struct QITraceEventIter QITraceEventIter;
  */
 typedef struct QICPU_d *QICPU;
 
+/**
+ * QIMemInfo:
+ * @size_shift: Memoy access size, interpreted as "1 << size_shift" bytes.
+ * @sign_extend: Whether the access is sign-extended.
+ * @endianness: Endianness type (0: little, 1: big).
+ * @store: Whether it's a store operation.
+ *
+ * Memory access information.
+ */
+typedef struct QIMemInfo {
+    union {
+        struct {
+            uint8_t size_shift : 2;
+            bool    sign_extend: 1;
+            uint8_t endianness : 1;
+            bool    store      : 1;
+        };
+        uint8_t raw;
+    };
+} QIMemInfo;
+
+/**
+ * QITCGv_cpu:
+ *
+ * TCG register with QICPU.
+ */
+typedef struct QITCGv_cpu_d *QITCGv_cpu;
+
+/**
+ * QITCGv:
+ *
+ * TCG register with data of architecture-specific length.
+ */
+typedef struct QITCGv_d *QITCGv;
+
+/**
+ * QITCGv_i32:
+ *
+ * TCG register with 32-bit data.
+ */
+typedef struct QITCGv_i32_d *QITCGv_i32;
+
+/**
+ * QITCGv_i64:
+ *
+ * TCG register with 64-bit data.
+ */
+typedef struct QITCGv_i64_d *QITCGv_i64;
+
+/*
+ * QITCGv_ptr:
+ *
+ * TCG register with pointer of architecture-specific length.
+ */
+typedef struct QITCGv_ptr_d *QITCGv_ptr;
+
 
 #include <qemu-instr/types.inc.h>
 
diff --git a/stubs/instrument.c b/stubs/instrument.c
index 74935975da..5e0d5150b5 100644
--- a/stubs/instrument.c
+++ b/stubs/instrument.c
@@ -10,7 +10,9 @@
 #include "instrument/control.h"
 
 
-__thread InstrState instr_cur_state;
+__thread InstrInfo instr_cur_info;
 void (*instr_event__guest_cpu_enter)(QICPU *vcpu);
 void (*instr_event__guest_cpu_exit)(QICPU *vcpu);
 void (*instr_event__guest_cpu_reset)(QICPU *vcpu);
+void (*instr_event__guest_mem_before_trans)(
+    QICPU vcpu_trans, QITCGv_cpu vcpu_exec, QITCGv vaddr, QIMemInfo info);
diff --git a/tcg/tcg-op.c b/tcg/tcg-op.c
index 234e300ede..40de61872e 100644
--- a/tcg/tcg-op.c
+++ b/tcg/tcg-op.c
@@ -26,6 +26,7 @@
 #include "qemu-common.h"
 #include "cpu.h"
 #include "exec/exec-all.h"
+#include "instrument/events.h"
 #include "tcg.h"
 #include "tcg-op.h"
 #include "trace-tcg.h"
@@ -2667,6 +2668,7 @@ void tcg_gen_qemu_ld_i32(TCGv_i32 val, TCGv addr, TCGArg idx, TCGMemOp memop)
     TraceMemInfo meminfo;
     memop = tcg_canonicalize_memop(memop, 0, 0);
     meminfo = trace_mem_get_info(memop, 0);
+    instr_guest_mem_before_trans(tcg_ctx.cpu, tcg_ctx.tcg_env, addr, meminfo);
     trace_guest_mem_before_tcg(tcg_ctx.cpu, tcg_ctx.tcg_env, addr, meminfo.raw);
     gen_ldst_i32(INDEX_op_qemu_ld_i32, val, addr, memop, idx);
 }
@@ -2676,6 +2678,7 @@ void tcg_gen_qemu_st_i32(TCGv_i32 val, TCGv addr, TCGArg idx, TCGMemOp memop)
     TraceMemInfo meminfo;
     memop = tcg_canonicalize_memop(memop, 0, 1);
     meminfo = trace_mem_get_info(memop, 1);
+    instr_guest_mem_before_trans(tcg_ctx.cpu, tcg_ctx.tcg_env, addr, meminfo);
     trace_guest_mem_before_tcg(tcg_ctx.cpu, tcg_ctx.tcg_env, addr, meminfo.raw);
     gen_ldst_i32(INDEX_op_qemu_st_i32, val, addr, memop, idx);
 }
@@ -2696,6 +2699,7 @@ void tcg_gen_qemu_ld_i64(TCGv_i64 val, TCGv addr, TCGArg idx, TCGMemOp memop)
 
     memop = tcg_canonicalize_memop(memop, 1, 0);
     meminfo = trace_mem_get_info(memop, 0);
+    instr_guest_mem_before_trans(tcg_ctx.cpu, tcg_ctx.tcg_env, addr, meminfo);
     trace_guest_mem_before_tcg(tcg_ctx.cpu, tcg_ctx.tcg_env, addr, meminfo.raw);
     gen_ldst_i64(INDEX_op_qemu_ld_i64, val, addr, memop, idx);
 }
@@ -2711,6 +2715,7 @@ void tcg_gen_qemu_st_i64(TCGv_i64 val, TCGv addr, TCGArg idx, TCGMemOp memop)
 
     memop = tcg_canonicalize_memop(memop, 1, 1);
     meminfo = trace_mem_get_info(memop, 1);
+    instr_guest_mem_before_trans(tcg_ctx.cpu, tcg_ctx.tcg_env, addr, meminfo);
     trace_guest_mem_before_tcg(tcg_ctx.cpu, tcg_ctx.tcg_env, addr, meminfo.raw);
     gen_ldst_i64(INDEX_op_qemu_st_i64, val, addr, memop, idx);
 }
diff --git a/trace/control.h b/trace/control.h
index 3e6da24c98..6b3fe9a28f 100644
--- a/trace/control.h
+++ b/trace/control.h
@@ -20,6 +20,29 @@ typedef struct TraceEventIter {
     const char *pattern;
 } TraceEventIter;
 
+/**
+ * TraceMemInfo:
+ * @size_shift: Memoy access size, interpreted as "1 << size_shift" bytes.
+ * @sign_extend: Whether the access is sign-extended.
+ * @endianness: Endinness type (0: little, 1: big).
+ * @store: Whether it's a store operation.
+ *
+ * Memory access information.
+ *
+ * NOTE: Keep in sync with QIMemInfo.
+ */
+typedef struct TraceMemInfo {
+    union {
+        struct {
+            uint8_t size_shift : 2;
+            bool    sign_extend: 1;
+            uint8_t endianness : 1;
+            bool    store      : 1;
+        };
+        uint8_t raw;
+    };
+} TraceMemInfo;
+
 
 /**
  * trace_event_iter_init:
diff --git a/trace/mem.h b/trace/mem.h
index 9866b41401..bc89673272 100644
--- a/trace/mem.h
+++ b/trace/mem.h
@@ -12,29 +12,6 @@
 
 #include "tcg/tcg.h"
 
-/**
- * TraceMemInfo:
- * @size_shift: Memoy access size, interpreted as "1 << size_shift" bytes.
- * @sign_extend: Whether the access is sign-extended.
- * @endianness: Endinness type (0: little, 1: big).
- * @store: Whether it's a store operation.
- *
- * Memory access information.
- *
- * NOTE: Keep in sync with QIMemInfo.
- */
-typedef struct TraceMemInfo {
-    union {
-        struct {
-            uint8_t size_shift : 2;
-            bool    sign_extend: 1;
-            uint8_t endianness : 1;
-            bool    store      : 1;
-        };
-        uint8_t raw;
-    };
-} TraceMemInfo;
-
 
 /**
  * trace_mem_get_info:

  parent reply	other threads:[~2017-09-06 18:27 UTC|newest]

Thread overview: 39+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-09-06 17:22 [Qemu-devel] [PATCH v4 00/20] instrument: Add basic event instrumentation Lluís Vilanova
2017-09-06 17:26 ` [Qemu-devel] [PATCH v4 01/20] instrument: Add documentation Lluís Vilanova
2017-09-06 17:30 ` [Qemu-devel] [PATCH v4 02/20] instrument: Add configure-time flag Lluís Vilanova
2017-09-06 17:34 ` [Qemu-devel] [PATCH v4 03/20] instrument: Add generic library loader Lluís Vilanova
2017-09-06 21:20   ` Emilio G. Cota
2017-09-10 17:41     ` Lluís Vilanova
2017-09-06 17:38 ` [Qemu-devel] [PATCH v4 04/20] instrument: [linux-user] Add command line " Lluís Vilanova
2017-09-06 17:42 ` [Qemu-devel] [PATCH v4 05/20] instrument: [bsd-user] " Lluís Vilanova
2017-09-06 17:46 ` [Qemu-devel] [PATCH v4 06/20] instrument: [softmmu] " Lluís Vilanova
2017-09-06 17:50 ` [Qemu-devel] [PATCH v4 07/20] instrument: [qapi] Add " Lluís Vilanova
2017-09-07  8:43   ` Markus Armbruster
2017-09-10 21:39     ` Lluís Vilanova
2017-09-06 17:55 ` [Qemu-devel] [PATCH v4 08/20] instrument: [hmp] " Lluís Vilanova
2017-09-07  8:51   ` Markus Armbruster
2017-09-10 22:07     ` Lluís Vilanova
2017-09-06 17:59 ` [Qemu-devel] [PATCH v4 09/20] instrument: Add basic control interface Lluís Vilanova
2017-09-06 21:23   ` Emilio G. Cota
2017-09-10 22:15     ` Lluís Vilanova
2017-09-06 21:57   ` Emilio G. Cota
2017-09-10 23:28     ` Lluís Vilanova
2017-09-06 18:03 ` [Qemu-devel] [PATCH v4 10/20] instrument: Add support for tracing events Lluís Vilanova
2017-09-06 18:07 ` [Qemu-devel] [PATCH v4 11/20] instrument: Track vCPUs Lluís Vilanova
2017-09-06 21:36   ` Emilio G. Cota
2017-09-06 18:11 ` [Qemu-devel] [PATCH v4 12/20] instrument: Add event 'guest_cpu_enter' Lluís Vilanova
2017-09-06 18:15 ` [Qemu-devel] [PATCH v4 13/20] instrument: Add event 'guest_cpu_exit' Lluís Vilanova
2017-09-06 18:19 ` [Qemu-devel] [PATCH v4 14/20] instrument: Add event 'guest_cpu_reset' Lluís Vilanova
2017-09-06 18:23 ` [Qemu-devel] [PATCH v4 15/20] trace: Introduce a proper structure to describe memory accesses Lluís Vilanova
2017-09-06 18:27 ` Lluís Vilanova [this message]
2017-09-06 18:31 ` [Qemu-devel] [PATCH v4 17/20] instrument: Add event 'guest_mem_before_exec' Lluís Vilanova
2017-09-06 18:35 ` [Qemu-devel] [PATCH v4 18/20] instrument: Add event 'guest_user_syscall' Lluís Vilanova
2017-09-06 18:39 ` [Qemu-devel] [PATCH v4 19/20] instrument: Add event 'guest_user_syscall_ret' Lluís Vilanova
2017-09-06 18:43 ` [Qemu-devel] [PATCH v4 20/20] instrument: Add API to manipulate guest memory Lluís Vilanova
2017-09-06 20:59 ` [Qemu-devel] [PATCH v4 00/20] instrument: Add basic event instrumentation Emilio G. Cota
2017-09-10 17:40   ` Lluís Vilanova
2017-09-10 23:31   ` Lluís Vilanova
2017-09-07  7:45 ` Markus Armbruster
2017-09-07 10:58 ` Markus Armbruster
2017-09-07 14:21   ` Emilio G. Cota
2017-09-10 17:34     ` Lluís Vilanova

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=150472243868.24907.11184861173318139147.stgit@frigg.lan \
    --to=vilanova@ac.upc.edu \
    --cc=cota@braap.org \
    --cc=eblake@redhat.com \
    --cc=pbonzini@redhat.com \
    --cc=qemu-devel@nongnu.org \
    --cc=rth@twiddle.net \
    --cc=stefanha@redhat.com \
    /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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.