[libcxxabi] [libunwind] [libunwind][AIX] Handle VAPI-based return addresses in stack unwinding for LLU (PR #209280)
Xing Xue via cfe-commits
cfe-commits at lists.llvm.org
Mon Jul 13 12:49:27 PDT 2026
https://github.com/xingxue-ibm updated https://github.com/llvm/llvm-project/pull/209280
>From 6b69974ca040d85a14a898c242f60c425b190c49 Mon Sep 17 00:00:00 2001
From: Xing Xue <xingxue at outlook.com>
Date: Mon, 13 Jul 2026 15:04:04 -0400
Subject: [PATCH 1/2] Update the unwinder to accommodate AIX changes required
for LLU (Live Library Update).
---
libcxxabi/test/aix_unwind_vapi.pass.cpp | 66 +++++++++++++++++
libunwind/src/UnwindCursor.hpp | 98 ++++++++++++++++++++++++-
2 files changed, 160 insertions(+), 4 deletions(-)
create mode 100644 libcxxabi/test/aix_unwind_vapi.pass.cpp
diff --git a/libcxxabi/test/aix_unwind_vapi.pass.cpp b/libcxxabi/test/aix_unwind_vapi.pass.cpp
new file mode 100644
index 0000000000000..bafb02f366ab4
--- /dev/null
+++ b/libcxxabi/test/aix_unwind_vapi.pass.cpp
@@ -0,0 +1,66 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// Test unwinding through a frame whose return address points to the VAPI
+// glue for the Live Library Update feature.
+
+// REQUIRES: target={{.+}}-aix{{.*}}
+
+#include <stdlib.h>
+#include <stdio.h>
+
+// VAPI glue addresses
+constexpr uintptr_t vapi_glue_addr_ext_32 = 0x8B80;
+constexpr uintptr_t vapi_addr_64 = 0x8e00;
+constexpr uintptr_t vapi_size_64 = 0x0200;
+constexpr uintptr_t vapi_glue_addr_begin = vapi_glue_addr_ext_32; // Start address in 32-bit
+constexpr uintptr_t vapi_glue_addr_end = vapi_addr_64 + vapi_size_64; // End address in 64-bit
+
+struct FunctionDescriptor {
+ uintptr_t entry;
+ uintptr_t toc;
+ uintptr_t env;
+};
+
+struct Tracker {
+ const char* name;
+ Tracker(const char* n) : name(n) { printf("Construct %s\n", name); }
+ ~Tracker() { printf("Destruct %s\n", name); }
+};
+
+int comp(const void*, const void*) {
+ printf("comp()\n");
+ throw 42;
+}
+
+static void do_search() {
+ Tracker t("do_search-local");
+ char buf[1];
+ char key = 0;
+ printf("calling bsearch()\n");
+ // bsearch uses VAPI and sets the return address to the VAPI glue when LLU
+ // is on.
+ bsearch(&key, buf, 1, 1, comp);
+ printf("after bsearch\n");
+}
+
+int main() {
+ FunctionDescriptor* fd = reinterpret_cast<FunctionDescriptor*>(bsearch);
+
+ if (vapi_glue_addr_begin <= fd->entry && fd->entry < vapi_glue_addr_end) {
+ printf("!!! Live Library Update is enabled !!!\n");
+ } else {
+ printf("!!! Live Library Update is NOT enabled !!!\n");
+ }
+
+ try {
+ do_search();
+ } catch (...) {
+ printf("caught ...\n");
+ }
+}
diff --git a/libunwind/src/UnwindCursor.hpp b/libunwind/src/UnwindCursor.hpp
index ff92c9e0a844a..f59384af81b5e 100644
--- a/libunwind/src/UnwindCursor.hpp
+++ b/libunwind/src/UnwindCursor.hpp
@@ -1363,6 +1363,10 @@ class UnwindCursor : public AbstractUnwindCursor {
reinterpret_cast<tbtable *>(_info.unwind_info),
_registers, _isSignalFrame);
}
+ bool isKnownVapiNotActive() const { return _isKnownVapiNotActive; }
+ void setIsKnownVapiNotActive(bool val) { _isKnownVapiNotActive = val; }
+ static pint_t getVAPILR();
+
#endif // defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND)
A &_addressSpace;
@@ -1377,13 +1381,15 @@ class UnwindCursor : public AbstractUnwindCursor {
#ifdef _LIBUNWIND_TRACE_RET_INJECT
uint32_t _walkedFrames;
#endif
+#if defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND)
+ bool _isKnownVapiNotActive;
+#endif
};
-
template <typename A, typename R>
UnwindCursor<A, R>::UnwindCursor(unw_context_t *context, A &as)
: _addressSpace(as), _registers(context), _unwindInfoMissing(false),
- _isSignalFrame(false) {
+ _isSignalFrame(false), _isKnownVapiNotActive(false) {
static_assert((check_fit<UnwindCursor<A, R>, unw_cursor_t>::does_fit),
"UnwindCursor<> does not fit in unw_cursor_t");
static_assert((alignof(UnwindCursor<A, R>) <= alignof(unw_cursor_t)),
@@ -1393,13 +1399,13 @@ UnwindCursor<A, R>::UnwindCursor(unw_context_t *context, A &as)
template <typename A, typename R>
UnwindCursor<A, R>::UnwindCursor(A &as, void *)
- : _addressSpace(as), _unwindInfoMissing(false), _isSignalFrame(false) {
+ : _addressSpace(as), _unwindInfoMissing(false), _isSignalFrame(false),
+ _isKnownVapiNotActive(false) {
memset(static_cast<void *>(&_info), 0, sizeof(_info));
// FIXME
// fill in _registers from thread arg
}
-
template <typename A, typename R>
bool UnwindCursor<A, R>::validReg(int regNum) {
return _registers.validRegister(regNum);
@@ -1469,6 +1475,30 @@ template <typename A, typename R> void UnwindCursor<A, R>::jumpto() {
static constexpr size_t _EXTRA_LIBUNWIND_FRAMES_WALKED = 5 - 1;
_registers.returnto(_walkedFrames + _EXTRA_LIBUNWIND_FRAMES_WALKED);
#else
+#if defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND)
+ if (isKnownVapiNotActive()) {
+ // If the current frame is known VAPI not active , execute the VAPI return
+ // glue to clear the VAPI control block. The VAPI return glue is used by
+ // AIX longjmp based on the VAPI active status recorded by setjmp in the
+ // jmp_buf, which means that the VAPI return glue can be called solely on
+ // the basis of the VAPI active status of the target context.
+
+ // VAPI return glue address is the VAPI glue address - 4.
+#ifdef __64BIT__
+ constexpr pint_t VAPIReturnGlue = 0x8e40 - 4;
+#else
+ constexpr pint_t VAPIReturnGlue = 0x8c40 - 4;
+#endif
+
+ _LIBUNWIND_TRACE_UNWINDING("VAPI: executing return glue %p\n",
+ reinterpret_cast<void *>(VAPIReturnGlue));
+ register auto *registers __asm__("r30") = &_registers;
+ __asm__ __volatile__("bla %[retglue]"
+ : "+r"(registers)
+ : [retglue] "i"(VAPIReturnGlue));
+ registers->jumpto();
+ }
+#endif // defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND)
_registers.jumpto();
#endif
}
@@ -2459,6 +2489,45 @@ bool UnwindCursor<A, R>::getInfoFromTBTable(pint_t pc, R ®isters) {
return true;
}
+// VAPI glue addresses
+constexpr uintptr_t vapi_glue_addr_ext_32 = 0x8B80;
+constexpr uintptr_t vapi_addr_64 = 0x8e00;
+constexpr uintptr_t vapi_size_64 = 0x0200;
+constexpr uintptr_t vapi_glue_addr_begin =
+ vapi_glue_addr_ext_32; // Start address in 32-bit
+constexpr uintptr_t vapi_glue_addr_end =
+ vapi_addr_64 + vapi_size_64; // End address in 64-bit
+
+#ifdef __64BIT__
+constexpr uintptr_t VAPI_CB_SIZE = 256;
+constexpr uintptr_t TLS_POINTER_OFFSET = 30 * 1024;
+constexpr uintptr_t TLSCB_BASE_SIZE = 256;
+
+static __inline__ __attribute__((__always_inline__)) char *tptr(void) {
+ char *result;
+ __asm__("mr %0, 13" : "=r"(result));
+ return result;
+}
+#else // 32-bit
+constexpr uintptr_t VAPI_CB_SIZE = 128;
+constexpr uintptr_t TLS_POINTER_OFFSET = 31 * 1024;
+constexpr uintptr_t TLSCB_BASE_SIZE = 128;
+
+static __inline__ __attribute__((__always_inline__)) char *tptr(void) {
+ char *result;
+ __asm__("mfspr %0, 259" : "=r"(result));
+ return result;
+}
+#endif
+
+constexpr int VAPI_CB_OFFSET =
+ TLS_POINTER_OFFSET + VAPI_CB_SIZE + TLSCB_BASE_SIZE;
+
+template <typename A, typename R>
+typename UnwindCursor<A, R>::pint_t UnwindCursor<A, R>::getVAPILR() {
+ return *reinterpret_cast<pint_t *>(tptr() - VAPI_CB_OFFSET + 8);
+}
+
// Step back up the stack following the frame back link.
template <typename A, typename R>
int UnwindCursor<A, R>::stepWithTBTable(pint_t pc, tbtable *TBTable,
@@ -2579,6 +2648,17 @@ int UnwindCursor<A, R>::stepWithTBTable(pint_t pc, tbtable *TBTable,
} else {
// Otherwise, use the LR value in the stack link area.
returnAddress = reinterpret_cast<pint_t *>(lastStack)[2];
+
+ if (vapi_glue_addr_begin <= returnAddress &&
+ returnAddress < vapi_glue_addr_end) {
+ _LIBUNWIND_TRACE_UNWINDING(
+ "The return address=%p is within the range of VAPI address, ",
+ reinterpret_cast<void *>(returnAddress));
+ setIsKnownVapiNotActive(true);
+ returnAddress = getVAPILR();
+ _LIBUNWIND_TRACE_UNWINDING("return address=%p from VAPI\n",
+ reinterpret_cast<void *>(returnAddress));
+ }
}
// Reset LR in the current context.
@@ -2709,6 +2789,16 @@ int UnwindCursor<A, R>::stepWithTBTable(pint_t pc, tbtable *TBTable,
// Return address is the address after call site instruction.
pint_t nextReturnAddress = reinterpret_cast<pint_t *>(nextStack)[2];
+ if (vapi_glue_addr_begin <= nextReturnAddress &&
+ nextReturnAddress < vapi_glue_addr_end) {
+ _LIBUNWIND_TRACE_UNWINDING(
+ "The next return address=%p is within the range of VAPI address, ",
+ reinterpret_cast<void *>(nextReturnAddress));
+ nextReturnAddress = getVAPILR();
+ _LIBUNWIND_TRACE_UNWINDING("the next return address=%p from VAPI\n",
+ reinterpret_cast<void *>(nextReturnAddress));
+ }
+
if (nextReturnAddress > 0x01 && nextReturnAddress < 0x10000) {
_LIBUNWIND_TRACE_UNWINDING("The next is a signal handler frame: "
"nextStack=%p, next return address=%p\n",
>From 578ab2193990e1954f302654d3f17fa40e639f0e Mon Sep 17 00:00:00 2001
From: Xing Xue <xingxue at outlook.com>
Date: Mon, 13 Jul 2026 15:47:23 -0400
Subject: [PATCH 2/2] Guard _isKnownVapiNotActive(false) initializers with #if
defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND).
---
libunwind/src/UnwindCursor.hpp | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/libunwind/src/UnwindCursor.hpp b/libunwind/src/UnwindCursor.hpp
index f59384af81b5e..7b400964defc8 100644
--- a/libunwind/src/UnwindCursor.hpp
+++ b/libunwind/src/UnwindCursor.hpp
@@ -1389,7 +1389,11 @@ class UnwindCursor : public AbstractUnwindCursor {
template <typename A, typename R>
UnwindCursor<A, R>::UnwindCursor(unw_context_t *context, A &as)
: _addressSpace(as), _registers(context), _unwindInfoMissing(false),
- _isSignalFrame(false), _isKnownVapiNotActive(false) {
+ _isSignalFrame(false)
+#if defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND)
+ , _isKnownVapiNotActive(false)
+#endif
+ {
static_assert((check_fit<UnwindCursor<A, R>, unw_cursor_t>::does_fit),
"UnwindCursor<> does not fit in unw_cursor_t");
static_assert((alignof(UnwindCursor<A, R>) <= alignof(unw_cursor_t)),
@@ -1399,8 +1403,11 @@ UnwindCursor<A, R>::UnwindCursor(unw_context_t *context, A &as)
template <typename A, typename R>
UnwindCursor<A, R>::UnwindCursor(A &as, void *)
- : _addressSpace(as), _unwindInfoMissing(false), _isSignalFrame(false),
- _isKnownVapiNotActive(false) {
+ : _addressSpace(as), _unwindInfoMissing(false), _isSignalFrame(false)
+#if defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND)
+ , _isKnownVapiNotActive(false)
+#endif
+ {
memset(static_cast<void *>(&_info), 0, sizeof(_info));
// FIXME
// fill in _registers from thread arg
More information about the cfe-commits
mailing list