Support using llvm-libunwind

This commit adds support for using llvm-libunwind for kernels built
using llvm and clang. The two differences are that the largest register
index is given by _LIBUNWIND_HIGHEST_DWARF_REGISTER, we need to check
whether the register is a floating point register and the prototype
for unw_regname takes the unwind cursor as the first argument.

Reviewed-by: Rob Norris <robn@despairlabs.com>
Reviewed-by: Tony Hutter <hutter2@llnl.gov>
Signed-off-by: Sebastian Pauka <me@spauka.se>
Closes #17230
This commit is contained in:
Sebastian Pauka 2025-04-25 03:58:48 +10:00 committed by GitHub
parent 7031a48c70
commit 1b4826b9a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 65 additions and 17 deletions

View File

@ -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"], [

View File

@ -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
@ -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
@ -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,