Only register fr_fault signal handlers if we're not running under a debugger
[freeradius.git] / src / lib / debug.c
index 22ae9ad..efa7fd7 100644 (file)
@@ -24,6 +24,7 @@
 #include <assert.h>
 #include <freeradius-devel/libradius.h>
 #include <sys/stat.h>
+#include <sys/wait.h>
 
 #if defined(HAVE_MALLOPT) && defined(HAVE_MALLOC_H)
 #  include <malloc.h>
 #  include <sys/prctl.h>
 #endif
 
+#ifdef HAVE_SYS_PTRACE_H
+#  include <sys/ptrace.h>
+#  if !defined(PTRACE_ATTACH) && defined(PT_ATTACH)
+#    define PTRACE_ATTACH PT_ATTACH
+#  endif
+#  if !defined(PTRACE_CONT) && defined(PT_CONTINUE)
+#    define PTRACE_CONT PT_CONTINUE
+#  endif
+#  if !defined(PTRACE_DETACH) && defined(PT_DETACH)
+#    define PTRACE_DETACH PT_DETACH
+#  endif
+#endif
+
 #ifdef HAVE_SYS_RESOURCE_H
 #  include <sys/resource.h>
 #endif
 #endif
 
 #ifdef HAVE_EXECINFO
-#  define MAX_BT_FRAMES 128
-#  define MAX_BT_CBUFF  65536                          //!< Should be a power of 2
+#  ifndef MAX_BT_FRAMES
+#    define MAX_BT_FRAMES 128
+#  endif
+#  ifndef MAX_BT_CBUFF
+#    define MAX_BT_CBUFF  1048576                      //!< Should be a power of 2
+#  endif
 
 #  ifdef HAVE_PTHREAD_H
 static pthread_mutex_t fr_debug_init = PTHREAD_MUTEX_INITIALIZER;
@@ -80,7 +98,7 @@ static fr_fault_cb_t panic_cb = NULL;                 //!< Callback to execute whilst panickin
 static fr_fault_log_t fr_fault_log = NULL;             //!< Function to use to process logging output.
 static int fr_fault_log_fd = STDERR_FILENO;            //!< Where to write debug output.
 
-static int fr_debugger_present = -1;                   //!< Whether were attached to by a debugger.
+static int debugger_attached = -1;                     //!< Whether were attached to by a debugger.
 
 #ifdef HAVE_SYS_RESOURCE_H
 static struct rlimit core_limits;
@@ -91,15 +109,96 @@ static TALLOC_CTX *talloc_autofree_ctx;
 
 #define FR_FAULT_LOG(fmt, ...) fr_fault_log(fmt "\n", ## __VA_ARGS__)
 
-/** Stub callback to see if the SIGTRAP handler is overriden
+#ifdef HAVE_SYS_PTRACE_H
+#  ifdef __linux__
+#    define _PTRACE(_x, _y) ptrace(_x, _y, NULL, NULL)
+#  else
+#    define _PTRACE(_x, _y) ptrace(_x, _y, NULL, 0)
+#  endif
+
+/** Determine if we're running under a debugger by attempting to attach using pattach
  *
- * @param signum signal raised.
+ * @return 0 if we're not, 1 if we are, -1 if we can't tell.
  */
-static void _sigtrap_handler(UNUSED int signum)
+static int fr_debugger_attached(void)
 {
-       fr_debugger_present = 0;
-       signal(SIGTRAP, SIG_DFL);
+       int pid;
+
+       int from_child[2] = {-1, -1};
+
+       if (pipe(from_child) < 0) {
+               fr_strerror_printf("Debugger check failed: Error opening internal pipe: %s", fr_syserror(errno));
+               return -1;
+       }
+
+       pid = fork();
+       if (pid == -1) {
+               fr_strerror_printf("Debugger check failed: Error forking: %s", fr_syserror(errno));
+               return -1;
+       }
+
+       /* Child */
+       if (pid == 0) {
+               int8_t ret = 0;
+               int ppid = getppid();
+
+               /* Close parent's side */
+               close(from_child[0]);
+
+               if (_PTRACE(PTRACE_ATTACH, ppid) == 0) {
+                       /* If we attached then we're not running under a debugger */
+                       write(from_child[1], &ret, sizeof(ret));
+
+                       /* Wait for the parent to stop and continue it */
+                       waitpid(ppid, NULL, 0);
+                       _PTRACE(PTRACE_CONT, ppid);
+
+                       /* Detach */
+                       _PTRACE(PTRACE_DETACH, ppid);
+                       exit(0);
+               }
+
+               ret = 1;
+               /* Something is already attached */
+               write(from_child[1], &ret, 1);
+
+               exit(0);
+       /* Parent */
+       } else {
+               int8_t ret = -1;
+
+               /*
+                *      The child writes a 1 if pattach failed else 0.
+                *
+                *      This read may be interrupted by pattach,
+                *      which is why we need the loop.
+                */
+               while ((read(from_child[0], &ret, 1) < 0) && (errno == EINTR));
+
+               /* Ret not updated */
+               if (ret < 0) {
+                       fr_strerror_printf("Debugger check failed: Error getting status from child: %s",
+                                          fr_syserror(errno));
+               }
+
+               /* Close the pipes here (if we did it above, it might race with pattach) */
+               close(from_child[1]);
+               close(from_child[0]);
+
+               /* Collect the status of the child */
+               waitpid(pid, NULL, 0);
+
+               return ret;
+       }
+}
+#else
+static int fr_debugger_attached(void)
+{
+       fr_strerror_printf("Debugger check failed: PTRACE not available");
+
+       return -1;
 }
+#endif
 
 /** Break in debugger (if were running under a debugger)
  *
@@ -110,11 +209,14 @@ static void _sigtrap_handler(UNUSED int signum)
  */
 void fr_debug_break(void)
 {
-       if (fr_debugger_present == -1) {
-               fr_debugger_present = 0;
-               signal(SIGTRAP, _sigtrap_handler);
-               raise(SIGTRAP);
-       } else if (fr_debugger_present == 1) {
+       if (debugger_attached == -1) {
+               debugger_attached = fr_debugger_attached();
+       }
+
+       if (debugger_attached == 1) {
+               fprintf(stderr, "Debugger detected, raising SIGTRAP\n");
+               fflush(stderr);
+
                raise(SIGTRAP);
        }
 }
@@ -134,7 +236,7 @@ void backtrace_print(fr_cbuff_t *cbuff, void *obj)
                if ((p->obj == obj) || !obj) {
                        found = true;
 
-                       fprintf(stderr, "Stacktrace for: %p\n", p->obj, p);
+                       fprintf(stderr, "Stacktrace for: %p\n", p->obj);
                        backtrace_symbols_fd(p->frames, p->count, STDERR_FILENO);
                }
        };
@@ -506,7 +608,7 @@ void fr_fault(int sig)
 
                /*
                 *      Here we temporarily enable the dumpable flag so if GBD or LLDB
-                *      is called in the panic_action, they can pattach tohe running
+                *      is called in the panic_action, they can pattach to the running
                 *      process.
                 */
                if (fr_get_dumpable_flag() == 0) {
@@ -616,7 +718,7 @@ int fr_log_talloc_report(TALLOC_CTX *ctx)
  */
 static void _fr_fault_mem_report(int sig)
 {
-       fr_fault_log("CAUGHT SIGNAL: %s\n", strsignal(sig));
+       FR_FAULT_LOG("CAUGHT SIGNAL: %s", strsignal(sig));
 
        if (fr_log_talloc_report(NULL) < 0) fr_perror("memreport");
 }
@@ -672,24 +774,33 @@ int fr_fault_setup(char const *cmd, char const *program)
 
        /* Unsure what the side effects of changing the signal handler mid execution might be */
        if (!setup) {
+               debugger_attached = fr_debugger_attached();
+
+               /*
+                *  These signals can't be properly dealt with in the debugger
+                *  if we set our own signal handlers
+                */
+               if (debugger_attached == 0) {
 #ifdef SIGSEGV
-               if (fr_set_signal(SIGSEGV, fr_fault) < 0) return -1;
+                       if (fr_set_signal(SIGSEGV, fr_fault) < 0) return -1;
 #endif
 #ifdef SIGBUS
-               if (fr_set_signal(SIGBUS, fr_fault) < 0) return -1;
-#endif
-#ifdef SIGABRT
-               if (fr_set_signal(SIGABRT, fr_fault) < 0) return -1;
-               /*
-                *  Use this instead of abort so we get a
-                *  full backtrace with broken versions of LLDB
-                */
-               talloc_set_abort_fn(_fr_talloc_fault);
+                       if (fr_set_signal(SIGBUS, fr_fault) < 0) return -1;
 #endif
 #ifdef SIGFPE
-               if (fr_set_signal(SIGFPE, fr_fault) < 0) return -1;
+                       if (fr_set_signal(SIGFPE, fr_fault) < 0) return -1;
 #endif
 
+#ifdef SIGABRT
+                       if (fr_set_signal(SIGABRT, fr_fault) < 0) return -1;
+
+                       /*
+                        *  Use this instead of abort so we get a
+                        *  full backtrace with broken versions of LLDB
+                        */
+                       talloc_set_abort_fn(_fr_talloc_fault);
+#endif
+               }
 #ifdef SIGUSR1
                if (fr_set_signal(SIGUSR1, fr_fault) < 0) return -1;
 #endif
@@ -805,7 +916,7 @@ void fr_fault_set_log_fd(int fd)
 inline void fr_verify_vp(char const *file, int line, VALUE_PAIR const *vp)
 {
        if (!vp) {
-               fprintf(stderr, "CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR pointer was NULL", file, line);
+               FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR pointer was NULL", file, line);
                fr_assert(0);
                fr_exit_now(0);
        }
@@ -820,25 +931,25 @@ inline void fr_verify_vp(char const *file, int line, VALUE_PAIR const *vp)
                TALLOC_CTX *parent;
 
                if (!talloc_get_type(vp->data.ptr, uint8_t)) {
-                       fprintf(stderr, "CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" data buffer type should be "
-                               "uint8_t but is %s\n", file, line, vp->da->name, talloc_get_name(vp->data.ptr));
+                       FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" data buffer type should be "
+                                    "uint8_t but is %s\n", file, line, vp->da->name, talloc_get_name(vp->data.ptr));
                        (void) talloc_get_type_abort(vp->data.ptr, uint8_t);
                }
 
                len = talloc_array_length(vp->vp_octets);
                if (vp->length > len) {
-                       fprintf(stderr, "CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" length %zu is greater than "
-                               "uint8_t data buffer length %zu\n", file, line, vp->da->name, vp->length, len);
+                       FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" length %zu is greater than "
+                                    "uint8_t data buffer length %zu\n", file, line, vp->da->name, vp->length, len);
                        fr_assert(0);
                        fr_exit_now(1);
                }
 
                parent = talloc_parent(vp->data.ptr);
                if (parent != vp) {
-                       fprintf(stderr, "CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" char buffer is not "
-                               "parented by VALUE_PAIR %p, instead parented by %p (%s)\n",
-                               file, line, vp->da->name,
-                               vp, parent, parent ? talloc_get_name(parent) : "NULL");
+                       FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" char buffer is not "
+                                    "parented by VALUE_PAIR %p, instead parented by %p (%s)\n",
+                                    file, line, vp->da->name,
+                                    vp, parent, parent ? talloc_get_name(parent) : "NULL");
                        fr_assert(0);
                        fr_exit_now(1);
                }
@@ -851,32 +962,32 @@ inline void fr_verify_vp(char const *file, int line, VALUE_PAIR const *vp)
                TALLOC_CTX *parent;
 
                if (!talloc_get_type(vp->data.ptr, char)) {
-                       fprintf(stderr, "CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" data buffer type should be "
-                               "char but is %s\n", file, line, vp->da->name, talloc_get_name(vp->data.ptr));
+                       FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" data buffer type should be "
+                                    "char but is %s\n", file, line, vp->da->name, talloc_get_name(vp->data.ptr));
                        (void) talloc_get_type_abort(vp->data.ptr, char);
                }
 
                len = (talloc_array_length(vp->vp_strvalue) - 1);
                if (vp->length > len) {
-                       fprintf(stderr, "CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" length %zu is greater than "
-                               "char buffer length %zu\n", file, line, vp->da->name, vp->length, len);
+                       FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" length %zu is greater than "
+                                    "char buffer length %zu\n", file, line, vp->da->name, vp->length, len);
                        fr_assert(0);
                        fr_exit_now(1);
                }
 
                if (vp->vp_strvalue[vp->length] != '\0') {
-                       fprintf(stderr, "CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" char buffer not \\0 "
-                               "terminated\n", file, line, vp->da->name);
+                       FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" char buffer not \\0 "
+                                    "terminated\n", file, line, vp->da->name);
                        fr_assert(0);
                        fr_exit_now(1);
                }
 
                parent = talloc_parent(vp->data.ptr);
                if (parent != vp) {
-                       fprintf(stderr, "CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" uint8_t buffer is not "
-                               "parented by VALUE_PAIR %p, instead parented by %p (%s)\n",
-                               file, line, vp->da->name,
-                               vp, parent, parent ? talloc_get_name(parent) : "NULL");
+                       FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" uint8_t buffer is not "
+                                    "parented by VALUE_PAIR %p, instead parented by %p (%s)\n",
+                                    file, line, vp->da->name,
+                                    vp, parent, parent ? talloc_get_name(parent) : "NULL");
                        fr_assert(0);
                        fr_exit_now(1);
                }
@@ -904,11 +1015,11 @@ void fr_verify_list(char const *file, int line, TALLOC_CTX *expected, VALUE_PAIR
 
                parent = talloc_parent(vp);
                if (expected && (parent != expected)) {
-                       fprintf(stderr, "CONSISTENCY CHECK FAILED %s[%u]: Expected VALUE_PAIR \"%s\" to be parented "
-                               "by %p (%s), instead parented by %p (%s)\n",
-                               file, line, vp->da->name,
-                               expected, talloc_get_name(expected),
-                               parent, parent ? talloc_get_name(parent) : "NULL");
+                       FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: Expected VALUE_PAIR \"%s\" to be parented "
+                                    "by %p (%s), instead parented by %p (%s)\n",
+                                    file, line, vp->da->name,
+                                    expected, talloc_get_name(expected),
+                                    parent, parent ? talloc_get_name(parent) : "NULL");
 
                        fr_log_talloc_report(expected);
                        if (parent) fr_log_talloc_report(parent);
@@ -919,3 +1030,66 @@ void fr_verify_list(char const *file, int line, TALLOC_CTX *expected, VALUE_PAIR
        }
 }
 #endif
+
+bool fr_assert_cond(char const *file, int line, char const *expr, bool cond)
+{
+       if (!cond) {
+               FR_FAULT_LOG("SOFT ASSERT FAILED %s[%u]: %s", file, line, expr);
+#if !defined(NDEBUG) && defined(SIGUSR1)
+               fr_fault(SIGUSR1);
+#endif
+               return false;
+       }
+
+       return cond;
+}
+
+/** Exit possibly printing a message about why we're exiting.
+ *
+ * Use the fr_exit(status) macro instead of calling this function
+ * directly.
+ *
+ * @param file where fr_exit() was called.
+ * @param line where fr_exit() was called.
+ * @param status we're exiting with.
+ */
+void NEVER_RETURNS _fr_exit(char const *file, int line, int status)
+{
+#ifndef NDEBUG
+       char const *error = fr_strerror();
+
+       if (error && (status != 0)) {
+               FR_FAULT_LOG("EXIT(%i) CALLED %s[%u].  Last error was: %s", status, file, line, error);
+       } else {
+               FR_FAULT_LOG("EXIT(%i) CALLED %s[%u]", status, file, line);
+       }
+#endif
+       fr_debug_break();       /* If running under GDB we'll break here */
+
+       exit(status);
+}
+
+/** Exit possibly printing a message about why we're exiting.
+ *
+ * Use the fr_exit_now(status) macro instead of calling this function
+ * directly.
+ *
+ * @param file where fr_exit_now() was called.
+ * @param line where fr_exit_now() was called.
+ * @param status we're exiting with.
+ */
+void NEVER_RETURNS _fr_exit_now(char const *file, int line, int status)
+{
+#ifndef NDEBUG
+       char const *error = fr_strerror();
+
+       if (error && (status != 0)) {
+               FR_FAULT_LOG("_EXIT(%i) CALLED %s[%u].  Last error was: %s", status, file, line, error);
+       } else {
+               FR_FAULT_LOG("_EXIT(%i) CALLED %s[%u]", status, file, line);
+       }
+#endif
+       fr_debug_break();       /* If running under GDB we'll break here */
+
+       _exit(status);
+}