#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;
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;
#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)
*
*/
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);
}
}
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);
}
};
/*
* Here we temporarily enable the dumpable flag so if GBD or LLDB
- * is called in the panic_action, they can pattach tot he running
+ * is called in the panic_action, they can pattach to the running
* process.
*/
if (fr_get_dumpable_flag() == 0) {
*/
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");
}
/* 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
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);
}
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);
}
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);
}
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);
}
}
#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);
+}