Test detection, during stepping, of the backchain mutation introduced by a VAPI call (see https://github.com/llvm/llvm-project/pull/209280). Further, test resumption of contexts using cursors obtained while a VAPI is active on the thread. Testing is done via FileCheck inspection of trace output enabled by `LIBUNWIND_PRINT_UNWINDING=1`. When Live Library Update is not enabled, synthetic trace output is generated by the test program itself. --------- Assisted-by: IBM Bob (cherry picked from commit 861efe03d96d93cd7e475d15bbc676f6d521f4fd)
168 lines
5.9 KiB
C++
168 lines
5.9 KiB
C++
//===----------------------------------------------------------------------===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// REQUIRES: target={{.+}}-aix{{.*}}
|
|
// REQUIRES: has-filecheck
|
|
|
|
// 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
|
|
|
|
// Tests use of the libunwind C API to step up from a context where the VAPI is
|
|
// active and to resume contexts where
|
|
// - the VAPI is active (and thus VAPI return glue is not called) and
|
|
// - where the VAPI is not active (and thus VAPI return glue _is_ called).
|
|
//
|
|
// In the latter case, which applies not just to the caller of the Virtual API
|
|
// but also to its ancestors, the return glue should always be called (i.e.,
|
|
// each time, without regard for whether the VAPI is currently active).
|
|
|
|
#include <assert.h>
|
|
#include <libunwind.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#ifdef __64BIT__
|
|
#define R3 UNW_PPC64_R3
|
|
#else
|
|
#define R3 UNW_PPC_R3
|
|
#endif
|
|
|
|
extern "C" void *returns_twice_bsearch
|
|
[[gnu::returns_twice]] (const void *, const void *, size_t, size_t,
|
|
int (*)(const void *,
|
|
const void *)) __asm__("bsearch");
|
|
|
|
static bool llu_enabled;
|
|
static unw_cursor_t bsearch_cursor;
|
|
static unw_cursor_t bsearch_caller_cursor;
|
|
static unw_cursor_t main_cursor;
|
|
|
|
extern "C" int cmp(const void *pa, const void *pb) {
|
|
(void)pa;
|
|
(void)pb;
|
|
|
|
fprintf(
|
|
stderr,
|
|
"Populate global cursors for `bsearch`, `bsearch_caller`, and `main`.\n");
|
|
// CHECK-LABEL: Populate global cursors
|
|
unw_context_t context;
|
|
unw_cursor_t cursor;
|
|
unw_getcontext(&context);
|
|
unw_init_local(&cursor, &context);
|
|
// Step from `cmp` up to `bsearch`.
|
|
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=cmp
|
|
// DEBUG: libunwind: the next return address=[[VAPI_RA:[^ ]*]] from VAPI
|
|
bsearch_cursor = cursor;
|
|
// Step from `bsearch` up to `bsearch_caller`.
|
|
unw_step(&cursor);
|
|
if (!llu_enabled)
|
|
fprintf(stderr, "libunwind: return address=VAPI_NOT_ENABLED from VAPI\n");
|
|
// DEBUG-LABEL: libunwind: stepWithTBTable: Look up traceback table of func=bsearch
|
|
// DEBUG: libunwind: return address=[[VAPI_RA]] from VAPI
|
|
bsearch_caller_cursor = cursor;
|
|
// Step from `bsearch_caller` up to `main`.
|
|
unw_step(&cursor);
|
|
// DEBUG-LABEL: libunwind: stepWithTBTable: Look up traceback table of func=_Z14bsearch_callerv
|
|
// DEBUG-NOT: VAPI
|
|
main_cursor = cursor;
|
|
|
|
// Test resuming context where VAPI is active.
|
|
fprintf(stderr,
|
|
"Return to `bsearch` with r3 set to 0 using the global cursor.\n");
|
|
unw_set_reg(&bsearch_cursor, R3, (unw_word_t)0);
|
|
unw_resume(&bsearch_cursor);
|
|
__builtin_unreachable();
|
|
// CHECK-LABEL: Return to `bsearch`
|
|
// DEBUG-NOT: VAPI: executing return glue
|
|
}
|
|
|
|
char bsearch_caller_ret;
|
|
void *bsearch_caller(void) {
|
|
volatile int state = 0;
|
|
|
|
char c;
|
|
char buf[3];
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wuninitialized-const-pointer"
|
|
void *result = returns_twice_bsearch(&c, buf, 1, 1, cmp);
|
|
#pragma clang diagnostic pop
|
|
assert(result == &buf[state]);
|
|
|
|
// Test resuming context where VAPI is not active. The VAPI return glue should
|
|
// be used each time without regard for whether the VAPI is currently active.
|
|
|
|
// `state` is volatile to observe changes in the value between the multiple
|
|
// returns in `setjmp`/`longjmp`-like usage. We need to spell out the
|
|
// `volatile` accesses very explicitly thanks to changes in the C++ standard.
|
|
if ((state = state + 1, state) < 3) {
|
|
fprintf(stderr,
|
|
"Return to `bsearch_caller` at the invocation of "
|
|
"`returns_twice_bsearch` (really `bsearch`) with r3 set to "
|
|
"&buf[%d] using the global cursor.\n",
|
|
state);
|
|
if (!llu_enabled)
|
|
fprintf(stderr,
|
|
"libunwind: VAPI: executing return glue VAPI_NOT_ENABLED\n");
|
|
unw_set_reg(&bsearch_caller_cursor, R3, (unw_word_t)&buf[state]);
|
|
unw_resume(&bsearch_caller_cursor);
|
|
__builtin_unreachable();
|
|
// CHECK-LABEL: Return to `bsearch_caller` {{.*}}&buf[1]
|
|
// DEBUG: libunwind: VAPI: executing return glue
|
|
// CHECK-LABEL: Return to `bsearch_caller` {{.*}}&buf[2]
|
|
// DEBUG: libunwind: VAPI: executing return glue
|
|
}
|
|
|
|
// Test resuming context where VAPI is not active, one frame up from the VAPI
|
|
// caller.
|
|
fprintf(stderr, "Return to `main` at the invocation of `bsearch_caller` with "
|
|
"r3 set to `&bsearch_caller_ret` using the global cursor.\n");
|
|
if (!llu_enabled)
|
|
fprintf(stderr,
|
|
"libunwind: VAPI: executing return glue VAPI_NOT_ENABLED\n");
|
|
unw_set_reg(&main_cursor, R3, (unw_word_t)&bsearch_caller_ret);
|
|
unw_resume(&main_cursor);
|
|
__builtin_unreachable();
|
|
// CHECK-LABEL: Return to `main`
|
|
// DEBUG: libunwind: VAPI: executing return glue
|
|
}
|
|
|
|
// 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;
|
|
};
|
|
|
|
int main(void) {
|
|
if (setenv("LIBUNWIND_PRINT_UNWINDING", "1", true) != 0) {
|
|
perror("setenv");
|
|
abort();
|
|
}
|
|
|
|
FunctionDescriptor *fd = reinterpret_cast<FunctionDescriptor *>(bsearch);
|
|
llu_enabled =
|
|
vapi_glue_addr_begin <= fd->entry && fd->entry < vapi_glue_addr_end;
|
|
|
|
assert(bsearch_caller() == &bsearch_caller_ret);
|
|
}
|