Skip to content

Support libspl build using llvm-libunwind #17230

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions config/user-libunwind.m4
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,22 @@ AC_DEFUN([ZFS_AC_CONFIG_USER_LIBUNWIND], [
], [
AC_MSG_RESULT([no])
])
dnl LLVM includes it's own libunwind library, which
dnl defines the highest numbered register in a different
dnl way, and has an incompatible unw_resname function.
AC_MSG_CHECKING([whether libunwind is llvm libunwind])
AC_COMPILE_IFELSE([
AC_LANG_PROGRAM([
#include <libunwind.h>
#if !defined(_LIBUNWIND_HIGHEST_DWARF_REGISTER)
#error "_LIBUNWIND_HIGHEST_DWARF_REGISTER is not defined"
#endif
], [])], [
AC_MSG_RESULT([yes])
AC_DEFINE(IS_LIBUNWIND_LLVM, 1, [libunwind is llvm libunwind])
], [
AC_MSG_RESULT([no])
])
AX_RESTORE_FLAGS
], [
AS_IF([test "x$with_libunwind" = "xyes"], [
Expand Down
66 changes: 49 additions & 17 deletions lib/libspl/backtrace.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,26 @@
do { ssize_t r __maybe_unused = write(fd, s, n); } while (0)
#define spl_bt_write(fd, s) spl_bt_write_n(fd, s, sizeof (s)-1)

#if defined(HAVE_LIBUNWIND)
#ifdef HAVE_LIBUNWIND
/*
* libunwind-gcc and libunwind-llvm both list registers using an enum,
* unw_regnum_t, however they indicate the highest numbered register for
* a given architecture in different ways. We can check which one is defined
* and mark which libunwind is in use
*/
#ifdef IS_LIBUNWIND_LLVM
#include <libunwind.h>
#define LAST_REG_INDEX _LIBUNWIND_HIGHEST_DWARF_REGISTER
#else
/*
* Need to define UNW_LOCAL_ONLY before importing libunwind.h
* if using libgcc libunwind.
*/
#define UNW_LOCAL_ONLY
#include <libunwind.h>
#define LAST_REG_INDEX UNW_TDEP_LAST_REG
#endif


/*
* Convert `v` to ASCII hex characters. The bottom `n` nybbles (4-bits ie one
Expand Down Expand Up @@ -102,14 +119,13 @@ libspl_backtrace(int fd)
unw_init_local(&cp, &uc);

/*
* libunwind's list of possible registers for this architecture is an
* enum, unw_regnum_t. UNW_TDEP_LAST_REG is the highest-numbered
* register in that list, however, not all register numbers in this
* range are defined by the architecture, and not all defined registers
* will be present on every implementation of that architecture.
* Moreover, libunwind provides nice names for most, but not all
* registers, but these are hardcoded; a name being available does not
* mean that register is available.
* Iterate over all registers for the architecture. We've figured
* out the highest number above, however, not all register numbers in
* this range are defined by the architecture, and not all defined
* registers will be present on every implementation of that
* architecture. Moreover, libunwind provides nice names for most, but
* not all registers, but these are hardcoded; a name being available
* does not mean that register is available.
*
* So, we have to pull this all together here. We try to get the value
* of every possible register. If we get a value for it, then the
Expand All @@ -120,26 +136,42 @@ libspl_backtrace(int fd)
* thing.
*/
uint_t cols = 0;
for (uint_t regnum = 0; regnum <= UNW_TDEP_LAST_REG; regnum++) {
for (uint_t regnum = 0; regnum <= LAST_REG_INDEX; regnum++) {
/*
* Get the value. Any error probably means the register
* doesn't exist, and we skip it.
* doesn't exist, and we skip it. LLVM libunwind iterates over
* fp registers in the same list, however they have to be
* accessed using unw_get_fpreg instead. Here, we just ignore
* them.
*/
#ifdef IS_LIBUNWIND_LLVM
if (unw_is_fpreg(&cp, regnum) ||
unw_get_reg(&cp, regnum, &v) < 0)
continue;
#else
if (unw_get_reg(&cp, regnum, &v) < 0)
continue;
#endif

/*
* Register name. If libunwind doesn't have a name for it,
* Register name. If GCC libunwind doesn't have a name for it,
* it will return "???". As a shortcut, we just treat '?'
* is an alternate end-of-string character.
* is an alternate end-of-string character. LLVM libunwind will
* return the string 'unknown register', which we detect by
* checking if the register name is longer than 5 characters.
*/
#ifdef IS_LIBUNWIND_LLVM
const char *name = unw_regname(&cp, regnum);
#else
const char *name = unw_regname(regnum);
#endif
for (n = 0; name[n] != '\0' && name[n] != '?'; n++) {}
if (n == 0) {
if (n == 0 || n > 5) {
/*
* No valid name, so make one of the form "?xx", where
* "xx" is the two-char hex of libunwind's register
* number.
* No valid name, or likely llvm_libunwind returned
* unknown_register, so make one of the form "?xx",
* where "xx" is the two-char hex of libunwind's
* register number.
*/
buf[0] = '?';
n = spl_bt_u64_to_hex_str(regnum, 2,
Expand Down
Loading