diff --git a/config/user-libunwind.m4 b/config/user-libunwind.m4 new file mode 100644 index 000000000000..99ba3dcf452d --- /dev/null +++ b/config/user-libunwind.m4 @@ -0,0 +1,44 @@ +dnl +dnl Checks for libunwind, which usually does a better job than backtrace() when +dnl resolving symbols in the stack backtrace. Newer versions have support for +dnl getting info about the object file the function came from, so we look for +dnl that too and use it if found. +dnl +AC_DEFUN([ZFS_AC_CONFIG_USER_LIBUNWIND], [ + AC_ARG_WITH([libunwind], + AS_HELP_STRING([--with-libunwind], + [use libunwind for backtraces in userspace assertions]), + [], + [with_libunwind=auto]) + + AS_IF([test "x$with_libunwind" != "xno"], [ + ZFS_AC_FIND_SYSTEM_LIBRARY(LIBUNWIND, [libunwind], [libunwind.h], [], [unwind], [], [ + dnl unw_get_elf_filename() is sometimes a macro, other + dnl times a proper symbol, so we can't just do a link + dnl check; we need to include the header properly. + AX_SAVE_FLAGS + CFLAGS="$CFLAGS $LIBUNWIND_CFLAGS" + LIBS="$LIBS $LIBUNWIND_LIBS" + AC_MSG_CHECKING([for unw_get_elf_filename in libunwind]) + AC_LINK_IFELSE([ + AC_LANG_PROGRAM([ + #define UNW_LOCAL_ONLY + #include + ], [ + unw_get_elf_filename(0, 0, 0, 0); + ]) + ], [ + AC_MSG_RESULT([yes]) + AC_DEFINE(HAVE_LIBUNWIND_ELF, 1, + [libunwind has unw_get_elf_filename]) + ], [ + AC_MSG_RESULT([no]) + ]) + AX_RESTORE_FLAGS + ], [ + AS_IF([test "x$with_libunwind" = "xyes"], [ + AC_MSG_FAILURE([--with-libunwind was given, but libunwind is not available, try installing libunwind-devel]) + ]) + ]) + ]) +]) diff --git a/config/user.m4 b/config/user.m4 index 8d11e031ba2e..badd920d2b8a 100644 --- a/config/user.m4 +++ b/config/user.m4 @@ -1,38 +1,39 @@ dnl # dnl # Default ZFS user configuration dnl # AC_DEFUN([ZFS_AC_CONFIG_USER], [ ZFS_AC_CONFIG_USER_GETTEXT ZFS_AC_CONFIG_USER_MOUNT_HELPER ZFS_AC_CONFIG_USER_SYSVINIT ZFS_AC_CONFIG_USER_DRACUT AM_COND_IF([BUILD_FREEBSD], [ PKG_INSTALLDIR(['${prefix}/libdata/pkgconfig'])], [ PKG_INSTALLDIR ]) ZFS_AC_CONFIG_USER_ZLIB AM_COND_IF([BUILD_LINUX], [ ZFS_AC_CONFIG_USER_UDEV ZFS_AC_CONFIG_USER_SYSTEMD ZFS_AC_CONFIG_USER_LIBUDEV ZFS_AC_CONFIG_USER_LIBUUID ZFS_AC_CONFIG_USER_LIBBLKID ]) ZFS_AC_CONFIG_USER_LIBTIRPC ZFS_AC_CONFIG_USER_LIBCRYPTO ZFS_AC_CONFIG_USER_LIBAIO ZFS_AC_CONFIG_USER_LIBATOMIC ZFS_AC_CONFIG_USER_LIBFETCH ZFS_AC_CONFIG_USER_AIO_H ZFS_AC_CONFIG_USER_CLOCK_GETTIME ZFS_AC_CONFIG_USER_PAM ZFS_AC_CONFIG_USER_BACKTRACE + ZFS_AC_CONFIG_USER_LIBUNWIND ZFS_AC_CONFIG_USER_RUNSTATEDIR ZFS_AC_CONFIG_USER_MAKEDEV_IN_SYSMACROS ZFS_AC_CONFIG_USER_MAKEDEV_IN_MKDEV ZFS_AC_CONFIG_USER_ZFSEXEC AC_CHECK_FUNCS([execvpe issetugid mlockall strlcat strlcpy gettid]) AC_SUBST(RM) ]) diff --git a/lib/libspl/Makefile.am b/lib/libspl/Makefile.am index 9f413b08c16f..eb2377305aca 100644 --- a/lib/libspl/Makefile.am +++ b/lib/libspl/Makefile.am @@ -1,47 +1,47 @@ include $(srcdir)/%D%/include/Makefile.am -libspl_assert_la_CFLAGS = $(AM_CFLAGS) $(LIBRARY_CFLAGS) +libspl_assert_la_CFLAGS = $(AM_CFLAGS) $(LIBRARY_CFLAGS) $(LIBUNWIND_CFLAGS) libspl_la_CFLAGS = $(libspl_assert_la_CFLAGS) noinst_LTLIBRARIES += libspl_assert.la libspl.la CPPCHECKTARGETS += libspl_assert.la libspl.la libspl_assert_la_SOURCES = \ %D%/assert.c libspl_la_SOURCES = \ %D%/libspl_impl.h \ %D%/atomic.c \ %D%/getexecname.c \ %D%/list.c \ %D%/mkdirp.c \ %D%/page.c \ %D%/strlcat.c \ %D%/strlcpy.c \ %D%/timestamp.c \ %D%/include/sys/list.h \ %D%/include/sys/list_impl.h if BUILD_LINUX libspl_la_SOURCES += \ %D%/os/linux/getexecname.c \ %D%/os/linux/gethostid.c \ %D%/os/linux/getmntany.c \ %D%/os/linux/zone.c endif if BUILD_FREEBSD libspl_la_SOURCES += \ %D%/os/freebsd/getexecname.c \ %D%/os/freebsd/gethostid.c \ %D%/os/freebsd/getmntany.c \ %D%/os/freebsd/mnttab.c \ %D%/os/freebsd/zone.c endif libspl_la_LIBADD = \ libspl_assert.la libspl_la_LIBADD += $(LIBATOMIC_LIBS) $(LIBCLOCK_GETTIME) -libspl_assert_la_LIBADD = $(BACKTRACE_LIBS) +libspl_assert_la_LIBADD = $(BACKTRACE_LIBS) $(LIBUNWIND_LIBS) diff --git a/lib/libspl/assert.c b/lib/libspl/assert.c index 4acf687f4b23..e6e3008f0aa6 100644 --- a/lib/libspl/assert.c +++ b/lib/libspl/assert.c @@ -1,113 +1,144 @@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or https://opensource.org/licenses/CDDL-1.0. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright (c) 2024, Rob Norris */ #include #include #if defined(__linux__) #include #include #ifdef HAVE_GETTID #define libspl_gettid() gettid() #else #include #define libspl_gettid() ((pid_t)syscall(__NR_gettid)) #endif #define libspl_getprogname() (program_invocation_short_name) #define libspl_getthreadname(buf, len) \ prctl(PR_GET_NAME, (unsigned long)(buf), 0, 0, 0) #elif defined(__FreeBSD__) #include #define libspl_gettid() pthread_getthreadid_np() #define libspl_getprogname() getprogname() #define libspl_getthreadname(buf, len) \ pthread_getname_np(pthread_self(), buf, len); #endif -#if defined(HAVE_BACKTRACE) +#if defined(HAVE_LIBUNWIND) +#define UNW_LOCAL_ONLY +#include + +static inline void +libspl_dump_backtrace(void) +{ + unw_context_t uc; + unw_cursor_t cp; + unw_word_t ip, off; + char funcname[128]; +#ifdef HAVE_LIBUNWIND_ELF + char objname[128]; + unw_word_t objoff; +#endif + + fprintf(stderr, "Call trace:\n"); + unw_getcontext(&uc); + unw_init_local(&cp, &uc); + while (unw_step(&cp) > 0) { + unw_get_reg(&cp, UNW_REG_IP, &ip); + unw_get_proc_name(&cp, funcname, sizeof (funcname), &off); +#ifdef HAVE_LIBUNWIND_ELF + unw_get_elf_filename(&cp, objname, sizeof (objname), &objoff); + fprintf(stderr, " [0x%08lx] %s+0x%2lx (in %s +0x%2lx)\n", + ip, funcname, off, objname, objoff); +#else + fprintf(stderr, " [0x%08lx] %s+0x%2lx\n", ip, funcname, off); +#endif + } +} +#elif defined(HAVE_BACKTRACE) #include static inline void libspl_dump_backtrace(void) { void *btptrs[100]; size_t nptrs = backtrace(btptrs, 100); char **bt = backtrace_symbols(btptrs, nptrs); fprintf(stderr, "Call trace:\n"); for (size_t i = 0; i < nptrs; i++) fprintf(stderr, " %s\n", bt[i]); free(bt); } #else #define libspl_dump_backtrace() #endif static boolean_t libspl_assert_ok = B_FALSE; void libspl_set_assert_ok(boolean_t val) { libspl_assert_ok = val; } static pthread_mutex_t assert_lock = PTHREAD_MUTEX_INITIALIZER; /* printf version of libspl_assert */ void libspl_assertf(const char *file, const char *func, int line, const char *format, ...) { pthread_mutex_lock(&assert_lock); va_list args; char tname[64]; libspl_getthreadname(tname, sizeof (tname)); fprintf(stderr, "ASSERT at %s:%d:%s()\n", file, line, func); va_start(args, format); vfprintf(stderr, format, args); va_end(args); fprintf(stderr, "\n" " PID: %-8u COMM: %s\n" " TID: %-8u NAME: %s\n", getpid(), libspl_getprogname(), libspl_gettid(), tname); libspl_dump_backtrace(); #if !__has_feature(attribute_analyzer_noreturn) && !defined(__COVERITY__) if (libspl_assert_ok) { pthread_mutex_unlock(&assert_lock); return; } #endif abort(); }