]> granicus.if.org Git - neomutt/commitdiff
show backtrace on segfault 1731/head
authorRichard Russon <rich@flatcap.org>
Mon, 3 Jun 2019 12:00:17 +0000 (13:00 +0100)
committerRichard Russon <rich@flatcap.org>
Mon, 3 Jun 2019 23:56:54 +0000 (00:56 +0100)
Add a configure option `--backtrace` that uses libunwind to print a
basic backtrace if NeoMutt segfaults.

Co-authored-by: Pietro Cerutti <gahr@gahr.ch>
Makefile.autosetup
auto.def
backtrace.c [new file with mode: 0644]
mutt.h
mutt/signal.c
mutt/signal2.h
mutt_signal.c
test/signal/mutt_sig_init.c

index 3db9275d7292a77b4624e2f463a142f228f60a7d..ade09bf5a9a8eaf5091e2b84b4664a4909f5b990 100644 (file)
@@ -73,6 +73,9 @@ NEOMUTTOBJS=  account.o addrbook.o alias.o bcache.o browser.o color.o commands.o
                recvcmd.o resize.o rfc1524.o rfc3676.o \
                score.o send.o sendlib.o sidebar.o smtp.o sort.o state.o \
                status.o system.o terminal.o version.o icommands.o
+@if HAVE_LIBUNWIND
+NEOMUTTOBJS+=  backtrace.o
+@endif
 
 @if !HAVE_WCSCASECMP
 NEOMUTTOBJS+=  wcscasecmp.o
index 5988a95716bcec309a90ec67757487ab9fc19160..50778650f134e518480b6934720cc0c3c680f696 100644 (file)
--- a/auto.def
+++ b/auto.def
@@ -87,6 +87,9 @@ options {
   with-qdbm:path            => "Location of QDBM"
   tokyocabinet=0            => "Use TokyoCabinet for the header cache"
   with-tokyocabinet:path    => "Location of TokyoCabinet"
+# libunwind
+  backtrace=0               => "Enable backtrace support with libunwind"
+  with-backtrace:path       => "Location of libunwind"
 # System
   with-sysroot:path         => "Target system root"
 # Testing
@@ -104,9 +107,9 @@ options {
 if {1} {
   # Keep sorted, please.
   foreach opt {
-    bdb coverage doc everything fmemopen full-doc gdbm gnutls gpgme gss
-    homespool idn idn2 inotify kyotocabinet lmdb locales-fix lua mixmaster nls
-    notmuch pgp qdbm sasl smime ssl testing tokyocabinet
+    bdb backtrace coverage doc everything fmemopen full-doc gdbm gnutls gpgme
+    gss homespool idn idn2 inotify kyotocabinet lmdb locales-fix lua mixmaster
+    nls notmuch pgp qdbm sasl smime ssl testing tokyocabinet
   } {
     define want-$opt [opt-bool $opt]
   }
@@ -634,6 +637,16 @@ if {[get-define want-homespool]} {
   define MAILPATH [opt-val with-mailpath /var/mail]
 }
 
+###############################################################################
+# Backtrace support with libunwind
+if {[get-define want-backtrace]} {
+  if {![check-inc-and-lib libunwind [opt-val with-backtrace $prefix] \
+                          libunwind.h unw_backtrace unwind]} {
+    user-error "Unable to find libunwind"
+  }
+  define LIBS "-lunwind-generic [get-define LIBS]"
+}
+
 ###############################################################################
 # Mixmaster
 if {[get-define want-mixmaster]} {
diff --git a/backtrace.c b/backtrace.c
new file mode 100644 (file)
index 0000000..c950be3
--- /dev/null
@@ -0,0 +1,58 @@
+/**
+ * @file
+ * Code backtrace
+ *
+ * @authors
+ * Copyright (C) 2018-2019 Richard Russon <rich@flatcap.org>
+ *
+ * @copyright
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @page backtrace Code backtrace
+ *
+ * Code backtrace
+ */
+
+#include "config.h"
+#include <libunwind.h>
+#include "mutt/mutt.h"
+
+/**
+ * show_backtrace - Log the program's call stack
+ */
+void show_backtrace(void)
+{
+  unw_cursor_t cursor;
+  unw_context_t uc;
+  unw_word_t ip, sp;
+  char buf[256];
+
+  printf("\nBacktrace\n");
+  mutt_debug(LL_DEBUG1, "\nBacktrace\n");
+  unw_getcontext(&uc);
+  unw_init_local(&cursor, &uc);
+  while (unw_step(&cursor) > 0)
+  {
+    unw_get_reg(&cursor, UNW_REG_IP, &ip);
+    unw_get_reg(&cursor, UNW_REG_SP, &sp);
+    unw_get_proc_name(&cursor, buf, sizeof(buf), &ip);
+    if (buf[0] == '_')
+      break;
+    printf("\t%s() ip = %lx, sp = %lx\n", buf, (long) ip, (long) sp);
+    mutt_debug(LL_DEBUG1, "\t%s() ip = %lx, sp = %lx\n", buf, (long) ip, (long) sp);
+  }
+  printf("\n");
+}
diff --git a/mutt.h b/mutt.h
index 0e6b1c809a9daa0f5ba140e1a3c02e468a93c0b3..f2c833634cc4be63cc09a707c1a71c82cf771ff5 100644 (file)
--- a/mutt.h
+++ b/mutt.h
@@ -153,4 +153,8 @@ enum CommandResult mutt_parse_rc_line(/* const */ char *line, struct Buffer *tok
 int mutt_query_variables(struct ListHead *queries);
 void reset_value(const char *name);
 
+#ifdef HAVE_LIBUNWIND
+void show_backtrace(void);
+#endif
+
 #endif /* MUTT_MUTT_H */
index afad444848ec053b8b4b8f0d35cfcfd1e9ffc2fd..f7bdf38b3cb5e4e8858be59b3d1cb8ab794bb253 100644 (file)
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include "curs_lib.h"
 #include "message.h"
 #include "signal2.h"
+#ifdef HAVE_LIBUNWIND
+#include "mutt.h"
+#endif
 
 static sigset_t Sigset;
 static sigset_t SigsetSys;
@@ -45,6 +49,7 @@ static bool SysSignalsBlocked;
 
 static sig_handler_t sig_handler = mutt_sig_empty_handler;
 static sig_handler_t exit_handler = mutt_sig_exit_handler;
+static sig_handler_t segv_handler = mutt_sig_exit_handler;
 
 /**
  * mutt_sig_empty_handler - Dummy signal handler
@@ -79,11 +84,12 @@ void mutt_sig_exit_handler(int sig)
  * mutt_sig_init - Initialise the signal handling
  * @param sig_fn  Function to handle signals
  * @param exit_fn Function to call on uncaught signals
+ * @param segv_fn Function to call on a segfault (Segmentation Violation)
  *
  * Set up handlers to ignore or catch signals of interest.
  * We use three handlers for the signals we want to catch, ignore, or exit.
  */
-void mutt_sig_init(sig_handler_t sig_fn, sig_handler_t exit_fn)
+void mutt_sig_init(sig_handler_t sig_fn, sig_handler_t exit_fn, sig_handler_t segv_fn)
 {
   if (sig_fn)
     sig_handler = sig_fn;
@@ -91,6 +97,9 @@ void mutt_sig_init(sig_handler_t sig_fn, sig_handler_t exit_fn)
   if (exit_fn)
     exit_handler = exit_fn;
 
+  if (segv_fn)
+    segv_handler = segv_fn;
+
   struct sigaction act;
 
   sigemptyset(&act.sa_mask);
@@ -98,6 +107,9 @@ void mutt_sig_init(sig_handler_t sig_fn, sig_handler_t exit_fn)
   act.sa_handler = SIG_IGN;
   sigaction(SIGPIPE, &act, NULL);
 
+  act.sa_handler = segv_handler;
+  sigaction(SIGSEGV, &act, NULL);
+
   act.sa_handler = exit_handler;
   sigaction(SIGTERM, &act, NULL);
   sigaction(SIGHUP, &act, NULL);
index bb56deff65a15621e1beb0316443edb78182b59f..f6ddeec3aa7acfff1a7e5e3f3f9e0f96ea05b224 100644 (file)
@@ -36,7 +36,7 @@ void mutt_sig_block(void);
 void mutt_sig_block_system(void);
 void mutt_sig_empty_handler(int sig);
 void mutt_sig_exit_handler(int sig);
-void mutt_sig_init(sig_handler_t sig_fn, sig_handler_t exit_fn);
+void mutt_sig_init(sig_handler_t sig_fn, sig_handler_t exit_fn, sig_handler_t segv_fn);
 void mutt_sig_unblock(void);
 void mutt_sig_unblock_system(bool catch);
 
index 3f24dbc82de389a3d63b385e467f0a51920285a4..f5109ab71e88ba3f9e0b767ee718b278c910e174 100644 (file)
@@ -78,7 +78,7 @@ static void curses_signal_handler(int sig)
 
 /**
  * curses_exit_handler - Notify the user and shutdown gracefully
- * @param sig Signal number, e.g. SIGINT
+ * @param sig Signal number, e.g. SIGTERM
  */
 static void curses_exit_handler(int sig)
 {
@@ -88,6 +88,27 @@ static void curses_exit_handler(int sig)
   mutt_sig_exit_handler(sig); /* DOES NOT RETURN */
 }
 
+/**
+ * curses_segv_handler - Catch a segfault and print a backtrace
+ * @param sig Signal number, e.g. SIGSEGV
+ */
+static void curses_segv_handler(int sig)
+{
+  curs_set(1);
+  endwin(); /* just to be safe */
+#ifdef HAVE_LIBUNWIND
+  show_backtrace();
+#endif
+
+  struct sigaction act;
+  sigemptyset(&act.sa_mask);
+  act.sa_flags = 0;
+  act.sa_handler = SIG_DFL;
+  sigaction(sig, &act, NULL);
+  // Re-raise the signal to give outside handlers a chance to deal with it
+  raise(sig);
+}
+
 #ifdef USE_SLANG_CURSES
 /**
  * mutt_intr_hook - Workaround handler for slang
@@ -104,7 +125,7 @@ static int mutt_intr_hook(void)
  */
 void mutt_signal_init(void)
 {
-  mutt_sig_init(curses_signal_handler, curses_exit_handler);
+  mutt_sig_init(curses_signal_handler, curses_exit_handler, curses_segv_handler);
 
 #ifdef USE_SLANG_CURSES
   /* This bit of code is required because of the implementation of
index c8882eaaa244ade5d7a01068fd28f98774a8541a..4d290ffcc3ce1d512acdf4271ad0bdc58c6d1dbb 100644 (file)
 
 void test_mutt_sig_init(void)
 {
-  // void mutt_sig_init(sig_handler_t sig_fn, sig_handler_t exit_fn);
+  // void mutt_sig_init(sig_handler_t sig_fn, sig_handler_t exit_fn, sig_handler_t segv_fn)
 
   {
     sig_handler_t exit_fn = mutt_sig_empty_handler;
-    mutt_sig_init(NULL, exit_fn);
-    TEST_CHECK_(1, "mutt_sig_init(NULL, exit_fn)");
+    sig_handler_t segv_fn = mutt_sig_empty_handler;
+    mutt_sig_init(NULL, exit_fn, segv_fn);
+    TEST_CHECK_(1, "mutt_sig_init(NULL, exit_fn, segv_fn)");
   }
 
   {
     sig_handler_t sig_fn = mutt_sig_empty_handler;
-    mutt_sig_init(sig_fn, NULL);
-    TEST_CHECK_(1, "mutt_sig_init(sig_fn, NULL)");
+    sig_handler_t segv_fn = mutt_sig_empty_handler;
+    mutt_sig_init(sig_fn, NULL, segv_fn);
+    TEST_CHECK_(1, "mutt_sig_init(sig_fn, NULL, segv_fn)");
+  }
+
+  {
+    sig_handler_t sig_fn = mutt_sig_empty_handler;
+    sig_handler_t exit_fn = mutt_sig_empty_handler;
+    mutt_sig_init(sig_fn, exit_fn, NULL);
+    TEST_CHECK_(1, "mutt_sig_init(sig_fn, exit_fn, NULL)");
   }
 }