[master 1/2] Save a core file when anaconda crashes.

dashea installerbot-noreply at redhat.com
Thu Sep 24 21:17:05 UTC 2015


From: David Shea <dshea at redhat.com>

Call gcore on the anaconda process to save a core file, which lets us
choose where to save it and bypasses any ulimit or core_pattern madness
we may be working within.

Rearrange the signal handler from most-safe to least-safe, and keep the
signal blocked until ready to cross the line into unsafe calls.
---
 pyanaconda/isys/isys.c | 110 ++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 87 insertions(+), 23 deletions(-)

diff --git a/pyanaconda/isys/isys.c b/pyanaconda/isys/isys.c
index 23b5e7e..6cbadf2 100644
--- a/pyanaconda/isys/isys.c
+++ b/pyanaconda/isys/isys.c
@@ -20,12 +20,16 @@
 #include "config.h"
 #include <Python.h>
 #include <stdio.h>
+#include <sys/stat.h>
 #include <sys/time.h>
 #include <sys/types.h>
+#include <sys/wait.h>
 #include <signal.h>
 #include <execinfo.h>
 #include <stdlib.h>
 #include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
 
 static PyObject * doSignalHandlers(PyObject *s, PyObject *args);
 static PyObject * doSetSystemTime(PyObject *s, PyObject *args);
@@ -53,17 +57,91 @@ PyInit__isys(void) {
 static void sync_signal_handler(int signum) {
     void *array[20];
     size_t size;
-    char **strings;
-    size_t i;
 
+    sigset_t sigset;
+
+    pid_t child_pid;
+    char *pid_str;
+    int pid_size;
+    int exit_status;
+
+    /* Doing stuff in signal handlers is tricky. The program has been
+     * interupted, probably in the middle of something going wrong with
+     * memory access, so we're in a weird state to begin with, and errors
+     * that happen during the signal handler can cause some gnarly things
+     * to happen.
+     *
+     * POSIX defines a set of functions (listed in man 7 signal) that are
+     * safe to call from within a signal handler, and glibc adds a few more.
+     * Do the safe things first, then reset the signal handler so that further
+     * receipts of the signal (probably SIGSEGV) will just crash the program
+     * instead of getting stuck in a loop, and then enter the danger zone.
+     */
+
+    /* First say that something went wrong. That's easy! (but we can't use printf or strlen) */
+    const char err_prefix[] = "Anaconda received signal ";
+    char sigstr[2];
+    write(STDOUT_FILENO, err_prefix, sizeof(err_prefix) - 1);
+
+    /* Convert signum to ascii without using anything that allocates memory */
+    /* Assume the signal is <= 99 */
+    sigstr[0] = (signum / 10 % 10) + '0';
+    sigstr[1] = (signum % 10) + '0';
+    write(STDOUT_FILENO, sigstr, sizeof(sigstr));
+    write(STDOUT_FILENO, "!.\n", 3);
+
+    /* And that's about all the safe things we can do. Time to reset the handler,
+     * unblock the signal and go wild */
+    signal(signum, SIG_DFL);
+    sigemptyset(&sigset);
+    sigaddset(&sigset, signum);
+    pthread_sigmask(SIG_UNBLOCK, &sigset, NULL);
+
+    /* Print the backtrace */
+    /* backtrace_symbols_fd is signal-safe, but backtrace is not. */
     size = backtrace (array, 20);
-    strings = backtrace_symbols (array, size);
-    
-    printf ("Anaconda received signal %d!.  Backtrace:\n", signum);
-    for (i = 0; i < size; i++)
-        printf ("%s\n", strings[i]);
-     
-    free (strings);
+    backtrace_symbols_fd(array, size, STDOUT_FILENO);
+
+    /* Try call gcore on ourself to write out a core file */
+    pid_size = snprintf(NULL, 0, "%d", getpid());
+    if (pid_size <= 0) {
+        perror("Unable to current PID");
+        exit(1);
+    }
+    pid_size++;
+    pid_str = malloc(pid_size);
+    snprintf(pid_str, pid_size, "%d", getpid());
+
+    child_pid = fork();
+    if (0 == child_pid) {
+        /* Disable stderr to suppress all the garbage about debuginfo packages */
+        int fd;
+        fd = open("/dev/null", O_WRONLY);
+        if (fd < 0) {
+            perror("Unable to open /dev/null");
+            exit(1);
+        }
+        dup2(fd, STDERR_FILENO);
+
+        execlp("gcore", "gcore", "-o", "/tmp/anaconda.core", pid_str, NULL);
+        perror("Unable to exec gcore");
+        exit(1);
+    }
+    else if (child_pid < 0) {
+        perror("Unable to fork");
+        exit(1);
+    }
+
+    if (waitpid(child_pid, &exit_status, 0) < 0) {
+        perror("Error waiting on gcore");
+        exit(1);
+    }
+
+    if (!WIFEXITED(exit_status) || WEXITSTATUS(exit_status) != 0) {
+        printf("gcore exited with status %d\n", exit_status);
+        exit(1);
+    }
+
     exit(1);
 }
 
@@ -74,20 +152,6 @@ static PyObject * doSignalHandlers(PyObject *s, PyObject *args) {
     memset(&sa, 0, sizeof(struct sigaction));
     sa.sa_handler = sync_signal_handler;
 
-    /* Use these flags to ensure that a crash within the signal handler will
-     * just crash anaconda and not get stuck in a loop. RESETHAND resets the
-     * handler to SIG_DFL when the handler is entered, so that further signals
-     * will exit the program, and NODEFER ensures that the signal is not blocked
-     * during the signal handler, so a SIGSEGV triggered by handling a SIGSEGV will
-     * be processed and will use the default handler. The Linux kernel forces
-     * both of these things during a signal handler crash, but this makes it
-     * explicit.
-     *
-     * These flags also mean that a SIGSEGV from a second thread could abort
-     * the processing of a SIGSEGV from a first, but too bad.
-     */
-    sa.sa_flags = SA_RESETHAND | SA_NODEFER;
-
     if (sigaction(SIGILL, &sa, NULL) != 0) {
         return PyErr_SetFromErrno(PyExc_SystemError);
     }


-- 
To view this commit on github, visit https://github.com/rhinstaller/anaconda/commit/7f51d2da062e09d7962f9d0571d87c227e465dbb


More information about the anaconda-patches mailing list