public inbox for gcc-cvs@sourceware.org
help / color / mirror / Atom feed
* [gcc(refs/vendors/ARM/heads/morello)] Handle removing LSB from code pointer in unwinder
@ 2021-12-10 16:49 Matthew Malcomson
  0 siblings, 0 replies; only message in thread
From: Matthew Malcomson @ 2021-12-10 16:49 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:2a0e79e8bba64117b2e9b13bd84b2413834c5741

commit 2a0e79e8bba64117b2e9b13bd84b2413834c5741
Author: Matthew Malcomson <matthew.malcomson@arm.com>
Date:   Fri Dec 10 16:31:21 2021 +0000

    Handle removing LSB from code pointer in unwinder
    
    The unwinder already has two mechanisms for converting what is found in
    the LR to something useful.  There is `__builtin_extract_return_addr`
    which takes the value from the LR (which can be found with
    `__builtin_return_address`) and converts it into a relevant code
    pointer, and there is `__builtin_frob_return_addr` which takes an
    address and converts it into the value that will need to be in the LR or
    stack slot for the epilogue to do its work.
    
    These don't quite fit the need in Morello.  These act to adjust the
    return address that the unwinder thinks about.  Hence
    `__builtin_extract_return_addr` has been applied on the return value of
    _Unwind_GetIP, and `__builtin_frob_return_addr` gets applied on the
    value that we would give in _Unwind_GetIP in order to convert that value
    back into what the epilogue needs.
    
    In Morello the address found in the link register (or stack slot) is
    sealed.  Hence if we were to use this same mechanism to adjust this
    value, _Unwind_GetIP would now return invalid pointers, and
    `__builtin_frob_return_addr` would not be able to make them valid again.
    Hence we need a different mechanism.  The mechanism we choose is to
    return the valid pointer including its LSB set in _Unwind_GetIP, and
    then require any users of that pointer to remove the LSB if needed.  One
    case where this may be needed is in the personality function of a
    frame that may need the IP address (and not necessarily the full
    pointer) in order to determine what action to take -- e.g. with C++
    which landing pad if any is relevant for the current location.
    
    In order to make this more convenient and to remove the architectural
    knowledge required for its implementation, we introduce a new builtin
    `__builtin_code_address_from_pointer`.  We use this in libgcc where it
    is needed.  These places are:
     1. When finding the FDE associated with a location.
     2. When determining which CFA instructions to execute based on a
        location.
    
    Future work using this new builtin in places like libbacktrace and the
    personality functions will be done in future patches.
    
    Small note on implementation:
     We expand the pointer into a Pmode value in the generic part of this
     builtin since the idea of the builtin is to return a code address
     instead of a pointer.   This is not relevant for Morello, but seems
     like it makes the operation of the function consistent across different
     architectures.
    
    This patch also accounts for the Morello LSB in AArch64 unwind headers.
    In aarch64-unwind.h we don't need to account for the Morello LSB, but
    there is a function which looks like it might need it on the surface.
    Here we add a comment to explain why it's not necessary in that case.
    
    In freebsd-unwind.h our implementation of the fallback method for
    finding the frame state (i.e. when there is no associated FDE with this
    frame) tells whether this frame is a signal frame or not based on the
    location of the PC.  This needs adjustment to account for the Morello
    LSB and we adjust accordingly.
    (This change has not been tested since we are not focussing on FreeBSD
    for the moment).
    
    In linux-unwind.h our fallback implementation works by dereferencing the
    PC to tell if the code it points to matches the toolchains
    implementation of __default_sa_restorer.  This method is no longer valid
    for Morello since the PC is sealed and can not be dereferenced to read
    in this way.  Hence we disable this fallback implementation for PureCap
    Morello.
    N.b. this does not disable unwinding past signal frames, only unwinding
    past signal frames that do not have unwind information.  If the signal
    frame has unwind information then it should include the 'S' character in
    the augmentation string and we can find the frame state from the FDE
    as usual.

Diff:
---
 gcc/builtin-types.def                  |  1 +
 gcc/builtins.c                         |  3 +++
 gcc/builtins.def                       |  1 +
 gcc/config/aarch64/aarch64.c           | 16 ++++++++++++++++
 gcc/doc/tm.texi                        | 18 ++++++++++++++++++
 gcc/doc/tm.texi.in                     |  2 ++
 gcc/except.c                           | 28 ++++++++++++++++++++++++++++
 gcc/except.h                           |  1 +
 gcc/target.def                         | 20 ++++++++++++++++++++
 libgcc/config/aarch64/aarch64-unwind.h |  3 +++
 libgcc/config/aarch64/freebsd-unwind.h |  3 ++-
 libgcc/config/aarch64/linux-unwind.h   |  6 ++++++
 libgcc/unwind-dw2-fde.c                | 12 ++++++------
 libgcc/unwind-dw2-fde.h                |  3 ++-
 libgcc/unwind-dw2.c                    |  9 ++++++---
 15 files changed, 115 insertions(+), 11 deletions(-)

diff --git a/gcc/builtin-types.def b/gcc/builtin-types.def
index 0ec19556c18..fb3fa964e87 100644
--- a/gcc/builtin-types.def
+++ b/gcc/builtin-types.def
@@ -262,6 +262,7 @@ DEF_FUNCTION_TYPE_1 (BT_FN_VOID_PTR, BT_VOID, BT_PTR)
 DEF_FUNCTION_TYPE_1 (BT_FN_SIZE_CONST_STRING, BT_SIZE, BT_CONST_STRING)
 DEF_FUNCTION_TYPE_1 (BT_FN_INT_CONST_STRING, BT_INT, BT_CONST_STRING)
 DEF_FUNCTION_TYPE_1 (BT_FN_PTR_PTR, BT_PTR, BT_PTR)
+DEF_FUNCTION_TYPE_1 (BT_FN_PTRMODE_PTR, BT_PTRMODE, BT_PTR)
 DEF_FUNCTION_TYPE_1 (BT_FN_VOID_VALIST_REF, BT_VOID, BT_VALIST_REF)
 DEF_FUNCTION_TYPE_1 (BT_FN_VOID_INT, BT_VOID, BT_INT)
 DEF_FUNCTION_TYPE_1 (BT_FN_VOID_BOOL, BT_VOID, BT_BOOL)
diff --git a/gcc/builtins.c b/gcc/builtins.c
index 181d34bf918..a5858503178 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -8550,6 +8550,8 @@ expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode,
       return expand_builtin_frob_return_addr (CALL_EXPR_ARG (exp, 0));
     case BUILT_IN_EXTRACT_RETURN_ADDR:
       return expand_builtin_extract_return_addr (CALL_EXPR_ARG (exp, 0));
+    case BUILT_IN_CODE_ADDRESS_FROM_POINTER:
+      return expand_builtin_code_address_from_pointer (CALL_EXPR_ARG (exp, 0));
     case BUILT_IN_EH_RETURN:
       expand_builtin_eh_return (CALL_EXPR_ARG (exp, 0),
 				CALL_EXPR_ARG (exp, 1));
@@ -11917,6 +11919,7 @@ is_simple_builtin (tree decl)
       case BUILT_IN_ASSUME_ALIGNED:
       case BUILT_IN_RETURN_ADDRESS:
       case BUILT_IN_EXTRACT_RETURN_ADDR:
+      case BUILT_IN_CODE_ADDRESS_FROM_POINTER:
       case BUILT_IN_FROB_RETURN_ADDR:
       case BUILT_IN_RETURN:
       case BUILT_IN_AGGREGATE_INCOMING_ADDRESS:
diff --git a/gcc/builtins.def b/gcc/builtins.def
index 102322b7912..9b0a2235f7d 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -840,6 +840,7 @@ DEF_GCC_BUILTIN        (BUILT_IN_CLRSB, "clrsb", BT_FN_INT_INT, ATTR_CONST_NOTHR
 DEF_GCC_BUILTIN        (BUILT_IN_CLRSBIMAX, "clrsbimax", BT_FN_INT_INTMAX, ATTR_CONST_NOTHROW_LEAF_LIST)
 DEF_GCC_BUILTIN        (BUILT_IN_CLRSBL, "clrsbl", BT_FN_INT_LONG, ATTR_CONST_NOTHROW_LEAF_LIST)
 DEF_GCC_BUILTIN        (BUILT_IN_CLRSBLL, "clrsbll", BT_FN_INT_LONGLONG, ATTR_CONST_NOTHROW_LEAF_LIST)
+DEF_GCC_BUILTIN        (BUILT_IN_CODE_ADDRESS_FROM_POINTER, "code_address_from_pointer", BT_FN_PTRMODE_PTR, ATTR_LEAF_LIST)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_DCGETTEXT, "dcgettext", BT_FN_STRING_CONST_STRING_CONST_STRING_INT, ATTR_FORMAT_ARG_2)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_DGETTEXT, "dgettext", BT_FN_STRING_CONST_STRING_CONST_STRING, ATTR_FORMAT_ARG_2)
 DEF_GCC_BUILTIN        (BUILT_IN_DWARF_CFA, "dwarf_cfa", BT_FN_PTR, ATTR_NULL)
diff --git a/gcc/config/aarch64/aarch64.c b/gcc/config/aarch64/aarch64.c
index b77d05db2da..570f634af1f 100644
--- a/gcc/config/aarch64/aarch64.c
+++ b/gcc/config/aarch64/aarch64.c
@@ -24631,6 +24631,19 @@ aarch64_target_capability_mode ()
   return opt_scalar_addr_mode ();
 }
 
+/* Implement TARGET_CODE_ADDRESS_FROM_POINTER hook.  */
+rtx
+aarch64_code_address_from_pointer (rtx x)
+{
+  if (!TARGET_CAPABILITY_PURE)
+    return x;
+  gcc_assert (GET_MODE (x) == Pmode);
+  gcc_assert (HWI_COMPUTABLE_MODE_P (POmode));
+  return expand_and (POmode, drop_capability (x),
+		     gen_int_mode (~((uint64_t)1), POmode),
+		     NULL);
+}
+
 /* Implement TARGET_ADJUST_LABEL_EXPANSION hook.  */
 rtx
 aarch64_adjust_label_expansion (rtx x)
@@ -25256,6 +25269,9 @@ aarch64_libgcc_floating_mode_supported_p
 #undef TARGET_CAPABILITY_MODE
 #define TARGET_CAPABILITY_MODE aarch64_target_capability_mode
 
+#undef TARGET_CODE_ADDRESS_FROM_POINTER
+#define TARGET_CODE_ADDRESS_FROM_POINTER aarch64_code_address_from_pointer
+
 #undef TARGET_ADJUST_LABEL_EXPANSION
 #define TARGET_ADJUST_LABEL_EXPANSION aarch64_adjust_label_expansion
 
diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi
index 0ce67733b49..e42f913c468 100644
--- a/gcc/doc/tm.texi
+++ b/gcc/doc/tm.texi
@@ -1389,6 +1389,24 @@ You would most commonly define this macro if the @code{allocate_stack}
 pattern needs to support both a 32- and a 64-bit mode.
 @end defmac
 
+@deftypefn {Target Hook} rtx TARGET_CODE_ADDRESS_FROM_POINTER (rtx @var{addr})
+This hook returns an RTX describing the address of the instruction that
+would be next executed if code were to jump to the valid pointer argument.
+
+Most architectures will not need to implement this, and would benefit from
+specifying @code{MASK_RETURN_ADDR} and @code{RETURN_ADDR_OFFSET} for the
+implementation of @code{__builtin_extract_return_addr} instead.
+
+@code{__builtin_extract_return_addr} is for converting the value that would
+be in the return address register into a correct address that can be used as
+a pointer.
+
+This hook is for converting the value that would be found in the return
+address register into a correct address that *can not* be used as a pointer.
+The difference is significant for capability architectures where pointers may
+be sealed from modification without invalidating the pointer.
+@end deftypefn
+
 @deftypefn {Target Hook} scalar_int_mode TARGET_LIBGCC_CMP_RETURN_MODE (void)
 This target hook should return the mode to be used for the return value
 of compare instructions expanded to libgcc calls.  If not defined
diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in
index 98fde7445c6..a95fe9ed2ac 100644
--- a/gcc/doc/tm.texi.in
+++ b/gcc/doc/tm.texi.in
@@ -1266,6 +1266,8 @@ You would most commonly define this macro if the @code{allocate_stack}
 pattern needs to support both a 32- and a 64-bit mode.
 @end defmac
 
+@hook TARGET_CODE_ADDRESS_FROM_POINTER
+
 @hook TARGET_LIBGCC_CMP_RETURN_MODE
 
 @hook TARGET_LIBGCC_SHIFT_COUNT_MODE
diff --git a/gcc/except.c b/gcc/except.c
index 72fbb45570b..20ef7a6c178 100644
--- a/gcc/except.c
+++ b/gcc/except.c
@@ -2210,6 +2210,34 @@ expand_builtin_extract_return_addr (tree addr_tree)
   return addr;
 }
 
+/* Given a valid code pointer, return the address of the instruction that
+   jumping to this code pointer would execute next.  For most architectures
+   this would be the same as the original pointer.  One architecture where this
+   would not be the case is the Morello AArch64 architecture where a code
+   pointer may have its LSB set to indicate the execution mode which the
+   instruction should be ran in.
+
+   N.b. this must be separate to __builtin_extract_return_addr since that
+   function is used to extract a real pointer while this builtin is used when
+   the modification can not produce a valid pointer (in the Morello
+   architecture valid addresses may not be valid pointers).  */
+rtx
+expand_builtin_code_address_from_pointer (tree pointer_tree)
+{
+  rtx addr = expand_expr (pointer_tree, NULL_RTX, Pmode, EXPAND_NORMAL);
+  if (GET_MODE (addr) != Pmode
+      && GET_MODE (addr) != VOIDmode)
+    {
+#ifdef POINTERS_EXTEND_UNSIGNED
+      addr = convert_memory_address (Pmode, addr);
+#else
+      addr = convert_to_mode (Pmode, addr, 0);
+#endif
+    }
+
+  return targetm.code_address_from_pointer (addr);
+}
+
 /* Given an actual address in addr_tree, do any necessary encoding
    and return the value to be stored in the return address register or
    stack slot so the epilogue will return to that address.  */
diff --git a/gcc/except.h b/gcc/except.h
index b7ce39dbb0b..ff8f59ab1e8 100644
--- a/gcc/except.h
+++ b/gcc/except.h
@@ -237,6 +237,7 @@ extern rtx expand_builtin_eh_copy_values (tree);
 extern void expand_builtin_unwind_init (void);
 extern rtx expand_builtin_eh_return_data_regno (tree);
 extern rtx expand_builtin_extract_return_addr (tree);
+extern rtx expand_builtin_code_address_from_pointer (tree);
 extern void expand_builtin_init_dwarf_reg_sizes (tree);
 extern rtx expand_builtin_frob_return_addr (tree);
 extern rtx expand_builtin_dwarf_sp_column (void);
diff --git a/gcc/target.def b/gcc/target.def
index 14eea320995..040ceb0af43 100644
--- a/gcc/target.def
+++ b/gcc/target.def
@@ -2136,6 +2136,26 @@ to express such options.  It should return a string containing these options,\n\
 separated by spaces, which the caller will free.\n",
 char *, (void), hook_charptr_void_null)
 
+DEFHOOK
+(code_address_from_pointer,
+ "This hook returns an RTX describing the address of the instruction that\n\
+would be next executed if code were to jump to the valid pointer argument.\n\
+\n\
+Most architectures will not need to implement this, and would benefit from\n\
+specifying @code{MASK_RETURN_ADDR} and @code{RETURN_ADDR_OFFSET} for the\n\
+implementation of @code{__builtin_extract_return_addr} instead.\n\
+\n\
+@code{__builtin_extract_return_addr} is for converting the value that would\n\
+be in the return address register into a correct address that can be used as\n\
+a pointer.\n\
+\n\
+This hook is for converting the value that would be found in the return\n\
+address register into a correct address that *can not* be used as a pointer.\n\
+The difference is significant for capability architectures where pointers may\n\
+be sealed from modification without invalidating the pointer.",
+ rtx, (rtx addr),
+ hook_rtx_rtx_identity)
+
 DEFHOOK_UNDOC
 (eh_return_filter_mode,
  "Return machine mode for filter value.",
diff --git a/libgcc/config/aarch64/aarch64-unwind.h b/libgcc/config/aarch64/aarch64-unwind.h
index 81379bc8129..8f30f22e594 100644
--- a/libgcc/config/aarch64/aarch64-unwind.h
+++ b/libgcc/config/aarch64/aarch64-unwind.h
@@ -35,6 +35,9 @@ see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 static inline int
 aarch64_cie_signed_with_b_key (struct _Unwind_Context *context)
 {
+  /* MORELLO  Do not want to adjust for the LSB here since context->bases.func
+     is initialised using the address read from the FDE, which does not have
+     this LSB set.  */
   const struct dwarf_fde *fde = _Unwind_Find_FDE (context->bases.func,
 						  &context->bases);
   if (fde != NULL)
diff --git a/libgcc/config/aarch64/freebsd-unwind.h b/libgcc/config/aarch64/freebsd-unwind.h
index 6768bc0b193..b0dae2634e8 100644
--- a/libgcc/config/aarch64/freebsd-unwind.h
+++ b/libgcc/config/aarch64/freebsd-unwind.h
@@ -66,7 +66,8 @@ aarch64_outside_sigtramp_range (unsigned char *pc)
   if (sigtramp_range_determined < 2)  /* sysctl failed if < 2 */
     return 1;
 
-  return (pc < sigtramp_start || pc >= sigtramp_end);
+  _Unwind_Address pcaddr = __builtin_code_address_from_pointer (pc);
+  return (pcaddr < sigtramp_start || pcaddr >= sigtramp_end);
 }
 
 static _Unwind_Reason_Code
diff --git a/libgcc/config/aarch64/linux-unwind.h b/libgcc/config/aarch64/linux-unwind.h
index 703b448669b..ac2303d4b3c 100644
--- a/libgcc/config/aarch64/linux-unwind.h
+++ b/libgcc/config/aarch64/linux-unwind.h
@@ -41,7 +41,13 @@
 #define SVC_0		0xd4000001
 #endif
 
+/* The mechanism of reading the instructions pointed to by our LR can not work
+   for Morello since the CLR provided by the kernel will be sealed.
+   That means we can't read instructions from it.  Which means our
+   determination mechanism can no longer work.  */
+#ifndef __CHERI_PURE_CAPABILITY__
 #define MD_FALLBACK_FRAME_STATE_FOR aarch64_fallback_frame_state
+#endif
 
 static _Unwind_Reason_Code
 aarch64_fallback_frame_state (struct _Unwind_Context *context,
diff --git a/libgcc/unwind-dw2-fde.c b/libgcc/unwind-dw2-fde.c
index d1bad1c6315..609507db638 100644
--- a/libgcc/unwind-dw2-fde.c
+++ b/libgcc/unwind-dw2-fde.c
@@ -827,7 +827,7 @@ init_object (struct object* ob)
    array.  */
 
 static const fde *
-linear_search_fdes (struct object *ob, const fde *this_fde, void *pc)
+linear_search_fdes (struct object *ob, const fde *this_fde, _Unwind_Address pc)
 {
   const struct dwarf_cie *last_cie = 0;
   int encoding = ob->s.b.encoding;
@@ -897,7 +897,7 @@ linear_search_fdes (struct object *ob, const fde *this_fde, void *pc)
    implementations of increasing complexity.  */
 
 static inline const fde *
-binary_search_unencoded_fdes (struct object *ob, void *pc)
+binary_search_unencoded_fdes (struct object *ob, _Unwind_Address pc)
 {
   struct fde_vector *vec = ob->u.sort;
   size_t lo, hi;
@@ -923,7 +923,7 @@ binary_search_unencoded_fdes (struct object *ob, void *pc)
 }
 
 static inline const fde *
-binary_search_single_encoding_fdes (struct object *ob, void *pc)
+binary_search_single_encoding_fdes (struct object *ob, _Unwind_Address pc)
 {
   struct fde_vector *vec = ob->u.sort;
   int encoding = ob->s.b.encoding;
@@ -953,7 +953,7 @@ binary_search_single_encoding_fdes (struct object *ob, void *pc)
 }
 
 static inline const fde *
-binary_search_mixed_encoding_fdes (struct object *ob, void *pc)
+binary_search_mixed_encoding_fdes (struct object *ob, _Unwind_Address pc)
 {
   struct fde_vector *vec = ob->u.sort;
   size_t lo, hi;
@@ -984,7 +984,7 @@ binary_search_mixed_encoding_fdes (struct object *ob, void *pc)
 }
 
 static const fde *
-search_object (struct object* ob, void *pc)
+search_object (struct object* ob, _Unwind_Address pc)
 {
   /* If the data hasn't been sorted, try to do this now.  We may have
      more memory available than last time we tried.  */
@@ -1028,7 +1028,7 @@ search_object (struct object* ob, void *pc)
 }
 
 const fde *
-_Unwind_Find_FDE (void *pc, struct dwarf_eh_bases *bases)
+_Unwind_Find_FDE (_Unwind_Address pc, struct dwarf_eh_bases *bases)
 {
   struct object *ob;
   const fde *f = NULL;
diff --git a/libgcc/unwind-dw2-fde.h b/libgcc/unwind-dw2-fde.h
index 4bf7716e37f..f8c3f5686a3 100644
--- a/libgcc/unwind-dw2-fde.h
+++ b/libgcc/unwind-dw2-fde.h
@@ -163,7 +163,8 @@ next_fde (const fde *f)
   return (const fde *) ((const char *) f + f->length + sizeof (f->length));
 }
 
-extern const fde * _Unwind_Find_FDE (void *, struct dwarf_eh_bases *);
+typedef unsigned _Unwind_Address __attribute__((__mode__(__address__)));
+extern const fde * _Unwind_Find_FDE (_Unwind_Address, struct dwarf_eh_bases *);
 
 static inline int
 last_fde (struct object *obj __attribute__ ((__unused__)), const fde *f)
diff --git a/libgcc/unwind-dw2.c b/libgcc/unwind-dw2.c
index 47c61424529..ddfb4e00879 100644
--- a/libgcc/unwind-dw2.c
+++ b/libgcc/unwind-dw2.c
@@ -388,7 +388,8 @@ void *
 _Unwind_FindEnclosingFunction (void *pc)
 {
   struct dwarf_eh_bases bases;
-  const struct dwarf_fde *fde = _Unwind_Find_FDE (pc-1, &bases);
+   _Unwind_Address pcaddr = __builtin_code_address_from_pointer (pc);
+  const struct dwarf_fde *fde = _Unwind_Find_FDE (pcaddr-1, &bases);
   if (fde)
     return bases.func;
   else
@@ -981,8 +982,9 @@ execute_cfa_program (const unsigned char *insn_ptr,
      reflected at the point immediately before the call insn.
      In signal frames, return address is after last completed instruction,
      so we add 1 to return address to make the comparison <=.  */
+  _Unwind_Address retaddr = __builtin_code_address_from_pointer (context->ra);
   while (insn_ptr < insn_end
-	 && fs->pc < context->ra + _Unwind_IsSignalFrame (context))
+	 && fs->pc < retaddr + _Unwind_IsSignalFrame (context))
     {
       unsigned char insn = *insn_ptr++;
       _uleb128_t reg, utmp;
@@ -1275,7 +1277,8 @@ uw_frame_state_for (struct _Unwind_Context *context, _Unwind_FrameState *fs)
   if (context->ra == 0)
     return _URC_END_OF_STACK;
 
-  fde = _Unwind_Find_FDE (context->ra + _Unwind_IsSignalFrame (context) - 1,
+  _Unwind_Address retaddr = __builtin_code_address_from_pointer (context->ra);
+  fde = _Unwind_Find_FDE (retaddr + _Unwind_IsSignalFrame (context) - 1,
 			  &context->bases);
   if (fde == NULL)
     {


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2021-12-10 16:49 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-12-10 16:49 [gcc(refs/vendors/ARM/heads/morello)] Handle removing LSB from code pointer in unwinder Matthew Malcomson

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).