diff --git a/config.lib b/config.lib index 379b2eaf1c..4ff37a8e20 100644 --- a/config.lib +++ b/config.lib @@ -97,6 +97,7 @@ set_default() { with_sse="1" with_libbfd="1" with_bfd_extra_debug="1" + with_dbg_gdb="1" save_params_array=" build @@ -176,6 +177,7 @@ set_default() { with_sse with_libbfd with_bfd_extra_debug + with_dbg_gdb CC CXX CFLAGS CXXFLAGS LDFLAGS CFLAGS_BUILD CXXFLAGS_BUILD LDFLAGS_BUILD" } @@ -477,6 +479,10 @@ detect_params() { --with-bfd-extra-debug) with_bfd_extra_debug="1";; --with-bfd-extra-debug=*) with_bfd_extra_debug="$optarg";; + --without-self-gdb-debug) with_dbg_gdb="0";; + --with-self-gdb-debug) with_dbg_gdb="1";; + --with-self-gdb-debug=*) with_dbg_gdb="$optarg";; + CC=* | --CC=*) CC="$optarg";; CXX=* | --CXX=*) CXX="$optarg";; CFLAGS=* | --CFLAGS=*) CFLAGS="$optarg";; @@ -1614,6 +1620,7 @@ EOL CFLAGS="$CFLAGS -DWITH_DL" fi + LIBBFD_LIBS= if [ "$with_libbfd" = "1" ]; then if test_compile_libbfd "-lbfd -lz"; then LIBBFD_LIBS="-lbfd -lz" @@ -1621,20 +1628,67 @@ EOL LIBBFD_LIBS="-lbfd -liberty -lz" elif test_compile_libbfd "-lbfd -liberty -lintl -lz"; then LIBBFD_LIBS="-lbfd -liberty -lintl -lz" - else - LIBBFD_LIBS= fi if [ -n "$LIBBFD_LIBS" ]; then log 1 "checking libbfd... found" LIBS="$LIBS $LIBBFD_LIBS" CFLAGS="$CFLAGS -DWITH_BFD" - if [ $enable_debug -lt 1 ] && [ "$with_bfd_extra_debug" = "1" ]; then - CFLAGS="$CFLAGS -g1" - fi else log 1 "checking libbfd... no" fi fi + + HAVE_GDB_DBG= + if [ "$with_dbg_gdb" = "1" ]; then + log 2 "executing $cc_host $CFLAGS $LDFLAGS $STATIC_FLAGS -o tmp.config.dbggdb -x c++ -" + "$cc_host" $CFLAGS $LDFLAGS $STATIC_FLAGS -o tmp.config.dbggdb -x c++ - 2> /dev/null << EOL + #include + #include + #include + #include + #include + #include + int main() { + pid_t tid = syscall(SYS_gettid); + int status; + waitpid((pid_t) 0, &status, 0); + return WIFEXITED(status) && WEXITSTATUS(status); + } +EOL + ret=$? + rm -f tmp.config.dbggdb + log 2 " exit code $ret" + if [ $ret -ne 0 ]; then + log 1 "checking dbg gdb... no" + else + log 1 "checking dbg gdb... found" + CFLAGS="$CFLAGS -DWITH_DBG_GDB" + HAVE_GDB_DBG=1 + + log 2 "executing $cc_host $CFLAGS $LDFLAGS $STATIC_FLAGS -o tmp.config.dbggdbprctl -x c++ -" + "$cc_host" $CFLAGS $LDFLAGS $STATIC_FLAGS -o tmp.config.dbggdbprctl -x c++ - 2> /dev/null << EOL + #include + int main() { + return prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0); + } +EOL + ret=$? + rm -f tmp.config.dbggdbprctl + log 2 " exit code $ret" + if [ $ret -ne 0 ]; then + log 1 "checking dbg gdb (prctl)... no" + else + log 1 "checking dbg gdb (prctl)... found" + CFLAGS="$CFLAGS -DWITH_PRCTL_PT" + fi + fi + fi + + if [ -n "$LIBBFD_LIBS" -o -n "$HAVE_GDB_DBG" ]; then + if [ $enable_debug -lt 1 ] && [ "$with_bfd_extra_debug" = "1" ]; then + CFLAGS="$CFLAGS -g1" + fi + fi fi if [ "$os" = "MINGW" ]; then @@ -3694,6 +3748,7 @@ showhelp() { echo " --without-sse disable SSE support (x86/x86_64 only)" echo " --without-libbfd disable libbfd support, used for improved crash logs (MinGW and Unix/glibc only)" echo " --without-bfd-extra-debug disable extra debugging information when using libbfd (MinGW and Unix/glibc only)" + echo " --without-self-gdb-debug disable improved crash logs using gdb (Linux only)" echo "" echo "Some influential environment variables:" echo " CC C compiler command" diff --git a/src/os/unix/crashlog_unix.cpp b/src/os/unix/crashlog_unix.cpp index 60c8208785..175eb7c534 100644 --- a/src/os/unix/crashlog_unix.cpp +++ b/src/os/unix/crashlog_unix.cpp @@ -21,6 +21,19 @@ #include #include +#if defined(WITH_DBG_GDB) +#include +#include +#include +#include +#include +#include +#endif /* WITH_DBG_GDB */ + +#if defined(WITH_PRCTL_PT) +#include +#endif /* WITH_PRCTL_PT */ + #if defined(__GLIBC__) /* Execinfo (and thus making stacktraces) is a GNU extension */ # include @@ -131,6 +144,79 @@ class CrashLogUnix : public CrashLog { } #endif + /** + * Get a stack backtrace of the current thread's stack using the gdb debugger, if available. + * + * Using GDB is useful as it knows about inlined functions and locals, and generally can + * do a more thorough job than in LogStacktrace. + * This is done in addition to LogStacktrace as gdb cannot be assumed to be present + * and there is some potentially useful information in the output from LogStacktrace + * which is not in gdb's output. + */ + char *LogStacktraceGdb(char *buffer, const char *last) const + { +#if defined(WITH_DBG_GDB) + +#if defined(WITH_PRCTL_PT) + prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0); +#endif /* WITH_PRCTL_PT */ + + pid_t tid = syscall(SYS_gettid); + + int pipefd[2]; + if (pipe(pipefd) == -1) return buffer; + + int pid = fork(); + if (pid < 0) return buffer; + + if (pid == 0) { + /* child */ + + close(pipefd[0]); /* Close unused read end */ + dup2(pipefd[1], STDOUT_FILENO); + close(pipefd[1]); + int null_fd = open("/dev/null", O_RDWR); + if (null_fd != -1) { + dup2(null_fd, STDERR_FILENO); + dup2(null_fd, STDIN_FILENO); + } + char buffer[16]; + seprintf(buffer, lastof(buffer), "%d", tid); + execlp("gdb", "gdb", "-n", "-p", buffer, "-batch", "-ex", "bt full", NULL); + exit(42); + } + + /* parent */ + + close(pipefd[1]); /* Close unused write end */ + + char *buffer_orig = buffer; + + buffer += seprintf(buffer, last, "Stacktrace (GDB):\n"); + while (buffer < last) { + ssize_t res = read(pipefd[0], buffer, last - buffer); + if (res < 0) { + if (errno == EINTR) continue; + break; + } else if (res == 0) { + break; + } else { + buffer += res; + } + } + buffer += seprintf(buffer, last, "\n"); + + int status; + int wait_ret = waitpid(pid, &status, 0); + if (wait_ret == -1 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) { + /* gdb did not appear to run successfully */ + buffer = buffer_orig; + } +#endif /* WITH_DBG_GDB */ + + return buffer; + } + /** * Get a stack backtrace of the current thread's stack. * @@ -156,6 +242,8 @@ class CrashLogUnix : public CrashLog { */ /* virtual */ char *LogStacktrace(char *buffer, const char *last) const { + buffer = LogStacktraceGdb(buffer, last); + buffer += seprintf(buffer, last, "Stacktrace:\n"); #if defined(__GLIBC__)