[libunwind] [libunwind][test][AIX] Add C API test for unwinding from AIX VAPI (as signal handler) (PR #209662)
Hubert Tong via cfe-commits
cfe-commits at lists.llvm.org
Tue Jul 14 19:12:32 PDT 2026
https://github.com/hubert-reinterpretcast created https://github.com/llvm/llvm-project/pull/209662
Further to https://github.com/llvm/llvm-project/pull/209306, test a case where a signal handler is a Virtual API function triggered synchronously while the VAPI is not active. Resuming an ancestor context of the signal frame should call the VAPI return glue.
---------
Assisted-by: IBM Bob
>From 9c59e1ba7e329d81c3c5025123eb5c2a24646ab2 Mon Sep 17 00:00:00 2001
From: Hubert Tong <hubert.reinterpretcast at gmail.com>
Date: Tue, 14 Jul 2026 18:08:18 -0400
Subject: [PATCH 1/7] WIP: Skeleton for libunwind C API test for signal handler
that is a Virtual API
---
.../test/aix_vapi_signal_unwind.pass.cpp | 52 +++++++++++++++++++
1 file changed, 52 insertions(+)
create mode 100644 libunwind/test/aix_vapi_signal_unwind.pass.cpp
diff --git a/libunwind/test/aix_vapi_signal_unwind.pass.cpp b/libunwind/test/aix_vapi_signal_unwind.pass.cpp
new file mode 100644
index 0000000000000..0b79f74b23076
--- /dev/null
+++ b/libunwind/test/aix_vapi_signal_unwind.pass.cpp
@@ -0,0 +1,52 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// Tests unwinding where the signal handler is a VAPI function.
+//
+// `exit` is used as the signal handler and the unwinding is done in an atexit handler.
+// `__builtin_debugtrap` is used because `raise` is also a VAPI function.
+
+// REQUIRES: target={{.+}}-aix{{.*}}
+// REQUIRES: has-filecheck
+
+// ADDITIONAL_COMPILE_FLAGS: -fno-inline -fno-exceptions
+
+// RUN: %{build}
+// RUN: %{exec} %t.exe 2>&1 | FileCheck %s
+
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+void my_atexit_handler(void) {
+ fprintf(stderr, "Retrieve a cursor to `main` by stepping up.\n");
+ // CHECK: Retrieve a cursor to `main`
+ // TODO
+ fprintf(stderr, "Resume `main` at the call to `trapper.\n");
+ // CHECK: Resume `main`
+ // TODO
+}
+
+void trapper(void) {
+ __builtin_debugtrap();
+ _Exit(EXIT_FAILURE);
+}
+
+int main(void) {
+ if (atexit(my_atexit_handler) != 0) {
+ perror("atexit");
+ abort();
+ }
+ if (signal(SIGTRAP, exit) == SIG_ERR) {
+ perror("signal");
+ abort();
+ }
+ trapper();
+ _Exit(0);
+}
>From a25110566e1ca525b520ced8706f17ea735cabfc Mon Sep 17 00:00:00 2001
From: Hubert Tong <hubert.reinterpretcast at gmail.com>
Date: Tue, 14 Jul 2026 18:31:22 -0400
Subject: [PATCH 2/7] WIP: IBM Bob: Add API calls; enable tracing, add
DEBUG-LABEL checks
---
.../test/aix_vapi_signal_unwind.pass.cpp | 32 +++++++++++++++----
1 file changed, 26 insertions(+), 6 deletions(-)
diff --git a/libunwind/test/aix_vapi_signal_unwind.pass.cpp b/libunwind/test/aix_vapi_signal_unwind.pass.cpp
index 0b79f74b23076..ec288f4434375 100644
--- a/libunwind/test/aix_vapi_signal_unwind.pass.cpp
+++ b/libunwind/test/aix_vapi_signal_unwind.pass.cpp
@@ -17,20 +17,36 @@
// ADDITIONAL_COMPILE_FLAGS: -fno-inline -fno-exceptions
// RUN: %{build}
-// RUN: %{exec} %t.exe 2>&1 | FileCheck %s
+// RUN: %{exec} %t.exe 2>&1 \
+// RUN: | FileCheck --check-prefix=CHECK \
+// RUN: %if libunwind-assertions-enabled %{ --check-prefix=DEBUG %} \
+// RUN: %s
#include <errno.h>
+#include <libunwind.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
void my_atexit_handler(void) {
fprintf(stderr, "Retrieve a cursor to `main` by stepping up.\n");
- // CHECK: Retrieve a cursor to `main`
- // TODO
- fprintf(stderr, "Resume `main` at the call to `trapper.\n");
- // CHECK: Resume `main`
- // TODO
+ // CHECK-LABEL: Retrieve a cursor to `main`
+ unw_context_t context;
+ unw_cursor_t cursor;
+ unw_getcontext(&context);
+ unw_init_local(&cursor, &context);
+ // Step from `my_atexit_handler` up to `exit`.
+ unw_step(&cursor);
+ // DEBUG-LABEL: libunwind: stepWithTBTable: Look up traceback table of func=my_atexit_handler
+ // Step from `exit` (signal handler, VAPI) up to `main`.
+ unw_step(&cursor);
+ // DEBUG-LABEL: libunwind: stepWithTBTable: Look up traceback table of func=exit
+
+ fprintf(stderr, "Resume `main` at the call to `trapper`.\n");
+ // CHECK-LABEL: Resume `main`
+ unw_resume(&cursor);
+ __builtin_unreachable();
+ // DEBUG: libunwind: VAPI: executing return glue
}
void trapper(void) {
@@ -39,6 +55,10 @@ void trapper(void) {
}
int main(void) {
+ if (setenv("LIBUNWIND_PRINT_UNWINDING", "1", true) != 0) {
+ perror("setenv");
+ abort();
+ }
if (atexit(my_atexit_handler) != 0) {
perror("atexit");
abort();
>From a3dbb34d6099239eefdafbf77a5c3237326a6868 Mon Sep 17 00:00:00 2001
From: Hubert Tong <hubert.reinterpretcast at gmail.com>
Date: Tue, 14 Jul 2026 19:11:54 -0400
Subject: [PATCH 3/7] Fixup! IBM Bob: Account for `trapper` frame and C++ name
mangling
---
libunwind/test/aix_vapi_signal_unwind.pass.cpp | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/libunwind/test/aix_vapi_signal_unwind.pass.cpp b/libunwind/test/aix_vapi_signal_unwind.pass.cpp
index ec288f4434375..76b3c4839e15e 100644
--- a/libunwind/test/aix_vapi_signal_unwind.pass.cpp
+++ b/libunwind/test/aix_vapi_signal_unwind.pass.cpp
@@ -37,10 +37,13 @@ void my_atexit_handler(void) {
unw_init_local(&cursor, &context);
// Step from `my_atexit_handler` up to `exit`.
unw_step(&cursor);
- // DEBUG-LABEL: libunwind: stepWithTBTable: Look up traceback table of func=my_atexit_handler
- // Step from `exit` (signal handler, VAPI) up to `main`.
+ // DEBUG-LABEL: libunwind: stepWithTBTable: Look up traceback table of func=_Z17my_atexit_handlerv
+ // Step from `exit` (signal handler, VAPI) up to `trapper`.
unw_step(&cursor);
// DEBUG-LABEL: libunwind: stepWithTBTable: Look up traceback table of func=exit
+ // Step from `trapper` up to `main`.
+ unw_step(&cursor);
+ // DEBUG-LABEL: libunwind: stepWithTBTable: Look up traceback table of func=_Z7trapperv
fprintf(stderr, "Resume `main` at the call to `trapper`.\n");
// CHECK-LABEL: Resume `main`
>From 9fb71682099774569e72eb1deebd78eeccbaaa5d Mon Sep 17 00:00:00 2001
From: Hubert Tong <hubert.reinterpretcast at gmail.com>
Date: Tue, 14 Jul 2026 19:36:48 -0400
Subject: [PATCH 4/7] Add DEBUG check lines for unw_step calls
---
libunwind/test/aix_vapi_signal_unwind.pass.cpp | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/libunwind/test/aix_vapi_signal_unwind.pass.cpp b/libunwind/test/aix_vapi_signal_unwind.pass.cpp
index 76b3c4839e15e..1a8465848da97 100644
--- a/libunwind/test/aix_vapi_signal_unwind.pass.cpp
+++ b/libunwind/test/aix_vapi_signal_unwind.pass.cpp
@@ -38,12 +38,16 @@ void my_atexit_handler(void) {
// Step from `my_atexit_handler` up to `exit`.
unw_step(&cursor);
// DEBUG-LABEL: libunwind: stepWithTBTable: Look up traceback table of func=_Z17my_atexit_handlerv
+ // DEBUG: libunwind: the next return address={{[^ ]*}} from VAPI
+ // DEBUG: libunwind: The next is a signal handler frame
// Step from `exit` (signal handler, VAPI) up to `trapper`.
unw_step(&cursor);
// DEBUG-LABEL: libunwind: stepWithTBTable: Look up traceback table of func=exit
+ // DEBUG-NEXT: libunwind: Possible signal handler frame
// Step from `trapper` up to `main`.
unw_step(&cursor);
// DEBUG-LABEL: libunwind: stepWithTBTable: Look up traceback table of func=_Z7trapperv
+ // DEBUG-NOT: VAPI
fprintf(stderr, "Resume `main` at the call to `trapper`.\n");
// CHECK-LABEL: Resume `main`
>From 8fb0bd8e220ae270c9f3914c01d3b68fd3fa64be Mon Sep 17 00:00:00 2001
From: Hubert Tong <hubert.reinterpretcast at gmail.com>
Date: Tue, 14 Jul 2026 19:47:42 -0400
Subject: [PATCH 5/7] IBM Bob: Add synthetic trace output when LLU is not
enabled
---
.../test/aix_vapi_signal_unwind.pass.cpp | 21 +++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/libunwind/test/aix_vapi_signal_unwind.pass.cpp b/libunwind/test/aix_vapi_signal_unwind.pass.cpp
index 1a8465848da97..c40ffc15de39f 100644
--- a/libunwind/test/aix_vapi_signal_unwind.pass.cpp
+++ b/libunwind/test/aix_vapi_signal_unwind.pass.cpp
@@ -28,7 +28,24 @@
#include <stdio.h>
#include <stdlib.h>
+// VAPI glue addresses
+constexpr uintptr_t vapi_glue_addr_ext_32 = 0x8b80;
+constexpr uintptr_t vapi_addr_64 = 0x8e00;
+constexpr size_t vapi_size_64 = 0x0200;
+constexpr uintptr_t vapi_glue_addr_begin = vapi_glue_addr_ext_32;
+constexpr uintptr_t vapi_glue_addr_end = vapi_addr_64 + vapi_size_64;
+
+struct FunctionDescriptor {
+ uintptr_t entry;
+ uintptr_t toc;
+ uintptr_t env;
+};
+
void my_atexit_handler(void) {
+ FunctionDescriptor *fd = reinterpret_cast<FunctionDescriptor *>(exit);
+ bool llu_enabled =
+ vapi_glue_addr_begin <= fd->entry && fd->entry < vapi_glue_addr_end;
+
fprintf(stderr, "Retrieve a cursor to `main` by stepping up.\n");
// CHECK-LABEL: Retrieve a cursor to `main`
unw_context_t context;
@@ -37,6 +54,8 @@ void my_atexit_handler(void) {
unw_init_local(&cursor, &context);
// Step from `my_atexit_handler` up to `exit`.
unw_step(&cursor);
+ if (!llu_enabled)
+ fprintf(stderr, "libunwind: the next return address=VAPI_NOT_ENABLED from VAPI\n");
// DEBUG-LABEL: libunwind: stepWithTBTable: Look up traceback table of func=_Z17my_atexit_handlerv
// DEBUG: libunwind: the next return address={{[^ ]*}} from VAPI
// DEBUG: libunwind: The next is a signal handler frame
@@ -51,6 +70,8 @@ void my_atexit_handler(void) {
fprintf(stderr, "Resume `main` at the call to `trapper`.\n");
// CHECK-LABEL: Resume `main`
+ if (!llu_enabled)
+ fprintf(stderr, "libunwind: VAPI: executing return glue VAPI_NOT_ENABLED\n");
unw_resume(&cursor);
__builtin_unreachable();
// DEBUG: libunwind: VAPI: executing return glue
>From d39d71bb53c22d1897e4f6935ba5613e685f1ef5 Mon Sep 17 00:00:00 2001
From: Hubert Tong <hubert.reinterpretcast at gmail.com>
Date: Tue, 14 Jul 2026 21:43:46 -0400
Subject: [PATCH 6/7] Use DEBUG-DAG to allow for synthetic trace ordering
difference
---
.../test/aix_vapi_signal_unwind.pass.cpp | 44 ++++++++++---------
1 file changed, 24 insertions(+), 20 deletions(-)
diff --git a/libunwind/test/aix_vapi_signal_unwind.pass.cpp b/libunwind/test/aix_vapi_signal_unwind.pass.cpp
index c40ffc15de39f..f9a2aa47c7d16 100644
--- a/libunwind/test/aix_vapi_signal_unwind.pass.cpp
+++ b/libunwind/test/aix_vapi_signal_unwind.pass.cpp
@@ -1,26 +1,26 @@
//===----------------------------------------------------------------------===//
//
-// 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
+/// 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
//
//===----------------------------------------------------------------------===//
-// Tests unwinding where the signal handler is a VAPI function.
+/// Tests unwinding where the signal handler is a VAPI function.
//
-// `exit` is used as the signal handler and the unwinding is done in an atexit handler.
-// `__builtin_debugtrap` is used because `raise` is also a VAPI function.
+/// `exit` is used as the signal handler and the unwinding is done in an atexit handler.
+/// `__builtin_debugtrap` is used because `raise` is also a VAPI function.
-// REQUIRES: target={{.+}}-aix{{.*}}
-// REQUIRES: has-filecheck
+/// REQUIRES: target={{.+}}-aix{{.*}}
+/// REQUIRES: has-filecheck
-// ADDITIONAL_COMPILE_FLAGS: -fno-inline -fno-exceptions
+/// ADDITIONAL_COMPILE_FLAGS: -fno-inline -fno-exceptions
-// RUN: %{build}
-// RUN: %{exec} %t.exe 2>&1 \
-// RUN: | FileCheck --check-prefix=CHECK \
-// RUN: %if libunwind-assertions-enabled %{ --check-prefix=DEBUG %} \
-// RUN: %s
+/// RUN: %{build}
+/// RUN: %{exec} %t.exe 2>&1 \
+/// RUN: | FileCheck --check-prefix=CHECK \
+/// RUN: %if libunwind-assertions-enabled %{ --check-prefix=DEBUG %} \
+/// RUN: %s
#include <errno.h>
#include <libunwind.h>
@@ -28,7 +28,7 @@
#include <stdio.h>
#include <stdlib.h>
-// VAPI glue addresses
+/// VAPI glue addresses
constexpr uintptr_t vapi_glue_addr_ext_32 = 0x8b80;
constexpr uintptr_t vapi_addr_64 = 0x8e00;
constexpr size_t vapi_size_64 = 0x0200;
@@ -52,18 +52,22 @@ void my_atexit_handler(void) {
unw_cursor_t cursor;
unw_getcontext(&context);
unw_init_local(&cursor, &context);
- // Step from `my_atexit_handler` up to `exit`.
+ /// Step from `my_atexit_handler` up to `exit`.
unw_step(&cursor);
if (!llu_enabled)
fprintf(stderr, "libunwind: the next return address=VAPI_NOT_ENABLED from VAPI\n");
+ /// Note:
+ /// The synthetic VAPI_NOT_ENABLED output would appear _after_ the trace
+ /// output indicating that the "next is a signal handler frame". Use DEBUG-DAG
+ /// to allow for that.
// DEBUG-LABEL: libunwind: stepWithTBTable: Look up traceback table of func=_Z17my_atexit_handlerv
- // DEBUG: libunwind: the next return address={{[^ ]*}} from VAPI
- // DEBUG: libunwind: The next is a signal handler frame
- // Step from `exit` (signal handler, VAPI) up to `trapper`.
+ // DEBUG-DAG: libunwind: the next return address={{[^ ]*}} from VAPI
+ // DEBUG-DAG: libunwind: The next is a signal handler frame
+ /// Step from `exit` (signal handler, VAPI) up to `trapper`.
unw_step(&cursor);
// DEBUG-LABEL: libunwind: stepWithTBTable: Look up traceback table of func=exit
// DEBUG-NEXT: libunwind: Possible signal handler frame
- // Step from `trapper` up to `main`.
+ /// Step from `trapper` up to `main`.
unw_step(&cursor);
// DEBUG-LABEL: libunwind: stepWithTBTable: Look up traceback table of func=_Z7trapperv
// DEBUG-NOT: VAPI
>From 33d833c727b197fcd6378baa153701d86974edaa Mon Sep 17 00:00:00 2001
From: Hubert Tong <hubert.reinterpretcast at gmail.com>
Date: Tue, 14 Jul 2026 21:58:54 -0400
Subject: [PATCH 7/7] clang-format
---
libunwind/test/aix_vapi_signal_unwind.pass.cpp | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/libunwind/test/aix_vapi_signal_unwind.pass.cpp b/libunwind/test/aix_vapi_signal_unwind.pass.cpp
index f9a2aa47c7d16..038a0d5d4359d 100644
--- a/libunwind/test/aix_vapi_signal_unwind.pass.cpp
+++ b/libunwind/test/aix_vapi_signal_unwind.pass.cpp
@@ -1,14 +1,15 @@
//===----------------------------------------------------------------------===//
//
-/// 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
+// 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
//
//===----------------------------------------------------------------------===//
/// Tests unwinding where the signal handler is a VAPI function.
//
-/// `exit` is used as the signal handler and the unwinding is done in an atexit handler.
+/// `exit` is used as the signal handler and the unwinding is done in an atexit
+/// handler.
/// `__builtin_debugtrap` is used because `raise` is also a VAPI function.
/// REQUIRES: target={{.+}}-aix{{.*}}
@@ -55,7 +56,8 @@ void my_atexit_handler(void) {
/// Step from `my_atexit_handler` up to `exit`.
unw_step(&cursor);
if (!llu_enabled)
- fprintf(stderr, "libunwind: the next return address=VAPI_NOT_ENABLED from VAPI\n");
+ fprintf(stderr,
+ "libunwind: the next return address=VAPI_NOT_ENABLED from VAPI\n");
/// Note:
/// The synthetic VAPI_NOT_ENABLED output would appear _after_ the trace
/// output indicating that the "next is a signal handler frame". Use DEBUG-DAG
@@ -75,7 +77,8 @@ void my_atexit_handler(void) {
fprintf(stderr, "Resume `main` at the call to `trapper`.\n");
// CHECK-LABEL: Resume `main`
if (!llu_enabled)
- fprintf(stderr, "libunwind: VAPI: executing return glue VAPI_NOT_ENABLED\n");
+ fprintf(stderr,
+ "libunwind: VAPI: executing return glue VAPI_NOT_ENABLED\n");
unw_resume(&cursor);
__builtin_unreachable();
// DEBUG: libunwind: VAPI: executing return glue
More information about the cfe-commits
mailing list