[libunwind][SystemZ] Unwind out of signal handlers

Unwinding out of signal handlers currently does not work since
the sigreturn trampoline is not annotated with CFI data.

Fix this by detecting the sigreturn trampoline during unwinding
and providing appropriate unwind data manually. This follows
closely the approach used by existing code for the AArch64 target.

Reviewed by: MaskRay

Differential Revision: https://reviews.llvm.org/D124765
This commit is contained in:
Ulrich Weigand 2022-05-04 10:43:11 +02:00
parent f1b9245199
commit 71672375fe
3 changed files with 111 additions and 7 deletions

View file

@ -953,7 +953,7 @@ private:
}
#endif
#if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64)
#if defined(_LIBUNWIND_TARGET_LINUX) && (defined(_LIBUNWIND_TARGET_AARCH64) || defined(_LIBUNWIND_TARGET_S390X))
bool setInfoForSigReturn() {
R dummy;
return setInfoForSigReturn(dummy);
@ -962,8 +962,14 @@ private:
R dummy;
return stepThroughSigReturn(dummy);
}
#if defined(_LIBUNWIND_TARGET_AARCH64)
bool setInfoForSigReturn(Registers_arm64 &);
int stepThroughSigReturn(Registers_arm64 &);
#endif
#if defined(_LIBUNWIND_TARGET_S390X)
bool setInfoForSigReturn(Registers_s390x &);
int stepThroughSigReturn(Registers_s390x &);
#endif
template <typename Registers> bool setInfoForSigReturn(Registers &) {
return false;
}
@ -1258,7 +1264,7 @@ private:
unw_proc_info_t _info;
bool _unwindInfoMissing;
bool _isSignalFrame;
#if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64)
#if defined(_LIBUNWIND_TARGET_LINUX) && (defined(_LIBUNWIND_TARGET_AARCH64) || defined(_LIBUNWIND_TARGET_S390X))
bool _isSigReturn = false;
#endif
};
@ -2465,7 +2471,7 @@ int UnwindCursor<A, R>::stepWithTBTable(pint_t pc, tbtable *TBTable,
template <typename A, typename R>
void UnwindCursor<A, R>::setInfoBasedOnIPRegister(bool isReturnAddress) {
#if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64)
#if defined(_LIBUNWIND_TARGET_LINUX) && (defined(_LIBUNWIND_TARGET_AARCH64) || defined(_LIBUNWIND_TARGET_S390X))
_isSigReturn = false;
#endif
@ -2580,7 +2586,7 @@ void UnwindCursor<A, R>::setInfoBasedOnIPRegister(bool isReturnAddress) {
}
#endif // #if defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND)
#if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64)
#if defined(_LIBUNWIND_TARGET_LINUX) && (defined(_LIBUNWIND_TARGET_AARCH64) || defined(_LIBUNWIND_TARGET_S390X))
if (setInfoForSigReturn())
return;
#endif
@ -2653,6 +2659,104 @@ int UnwindCursor<A, R>::stepThroughSigReturn(Registers_arm64 &) {
}
#endif // defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64)
#if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_S390X)
template <typename A, typename R>
bool UnwindCursor<A, R>::setInfoForSigReturn(Registers_s390x &) {
// Look for the sigreturn trampoline. The trampoline's body is a
// specific instruction (see below). Typically the trampoline comes from the
// vDSO (i.e. the __kernel_[rt_]sigreturn function). A libc might provide its
// own restorer function, though, or user-mode QEMU might write a trampoline
// onto the stack.
const pint_t pc = static_cast<pint_t>(this->getReg(UNW_REG_IP));
const uint16_t inst = _addressSpace.get16(pc);
if (inst == 0x0a77 || inst == 0x0aad) {
_info = {};
_info.start_ip = pc;
_info.end_ip = pc + 2;
_isSigReturn = true;
return true;
}
return false;
}
template <typename A, typename R>
int UnwindCursor<A, R>::stepThroughSigReturn(Registers_s390x &) {
// Determine current SP.
const pint_t sp = static_cast<pint_t>(this->getReg(UNW_REG_SP));
// According to the s390x ABI, the CFA is at (incoming) SP + 160.
const pint_t cfa = sp + 160;
// Determine current PC and instruction there (this must be either
// a "svc __NR_sigreturn" or "svc __NR_rt_sigreturn").
const pint_t pc = static_cast<pint_t>(this->getReg(UNW_REG_IP));
const uint16_t inst = _addressSpace.get16(pc);
// Find the addresses of the signo and sigcontext in the frame.
pint_t pSigctx = 0;
pint_t pSigno = 0;
// "svc __NR_sigreturn" uses a non-RT signal trampoline frame.
if (inst == 0x0a77) {
// Layout of a non-RT signal trampoline frame, starting at the CFA:
// - 8-byte signal mask
// - 8-byte pointer to sigcontext, followed by signo
// - 4-byte signo
pSigctx = _addressSpace.get64(cfa + 8);
pSigno = pSigctx + 344;
}
// "svc __NR_rt_sigreturn" uses a RT signal trampoline frame.
if (inst == 0x0aad) {
// Layout of a RT signal trampoline frame, starting at the CFA:
// - 8-byte retcode (+ alignment)
// - 128-byte siginfo struct (starts with signo)
// - ucontext struct:
// - 8-byte long (uc_flags)
// - 8-byte pointer (uc_link)
// - 24-byte stack_t
// - 8 bytes of padding because sigcontext has 16-byte alignment
// - sigcontext/mcontext_t
pSigctx = cfa + 8 + 128 + 8 + 8 + 24 + 8;
pSigno = cfa + 8;
}
assert(pSigctx != 0);
assert(pSigno != 0);
// Offsets from sigcontext to each register.
const pint_t kOffsetPc = 8;
const pint_t kOffsetGprs = 16;
const pint_t kOffsetFprs = 216;
// Restore all registers.
for (int i = 0; i < 16; ++i) {
uint64_t value = _addressSpace.get64(pSigctx + kOffsetGprs +
static_cast<pint_t>(i * 8));
_registers.setRegister(UNW_S390X_R0 + i, value);
}
for (int i = 0; i < 16; ++i) {
static const int fpr[16] = {
UNW_S390X_F0, UNW_S390X_F1, UNW_S390X_F2, UNW_S390X_F3,
UNW_S390X_F4, UNW_S390X_F5, UNW_S390X_F6, UNW_S390X_F7,
UNW_S390X_F8, UNW_S390X_F9, UNW_S390X_F10, UNW_S390X_F11,
UNW_S390X_F12, UNW_S390X_F13, UNW_S390X_F14, UNW_S390X_F15
};
double value = _addressSpace.getDouble(pSigctx + kOffsetFprs +
static_cast<pint_t>(i * 8));
_registers.setFloatRegister(fpr[i], value);
}
_registers.setIP(_addressSpace.get64(pSigctx + kOffsetPc));
// SIGILL, SIGFPE and SIGTRAP are delivered with psw_addr
// after the faulting instruction rather than before it.
// Do not set _isSignalFrame in that case.
uint32_t signo = _addressSpace.get32(pSigno);
_isSignalFrame = (signo != 4 && signo != 5 && signo != 8);
return UNW_STEP_SUCCESS;
}
#endif // defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_S390X)
template <typename A, typename R>
int UnwindCursor<A, R>::step() {
// Bottom of stack is defined is when unwind info cannot be found.
@ -2661,7 +2765,7 @@ int UnwindCursor<A, R>::step() {
// Use unwinding info to modify register set as if function returned.
int result;
#if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64)
#if defined(_LIBUNWIND_TARGET_LINUX) && (defined(_LIBUNWIND_TARGET_AARCH64) || defined(_LIBUNWIND_TARGET_S390X))
if (_isSigReturn) {
result = this->stepThroughSigReturn();
} else

View file

@ -8,7 +8,7 @@
//===----------------------------------------------------------------------===//
// Ensure that the unwinder can cope with the signal handler.
// REQUIRES: linux && (target={{aarch64-.+}} || target={{x86_64-.+}})
// REQUIRES: linux && (target={{aarch64-.+}} || target={{s390x-.+}} || target={{x86_64-.+}})
// TODO: Figure out why this fails with Memory Sanitizer.
// XFAIL: msan

View file

@ -8,7 +8,7 @@
//===----------------------------------------------------------------------===//
// Ensure that leaf function can be unwund.
// REQUIRES: linux && (target={{aarch64-.+}} || target={{x86_64-.+}})
// REQUIRES: linux && (target={{aarch64-.+}} || target={{s390x-.+}} || target={{x86_64-.+}})
// TODO: Figure out why this fails with Memory Sanitizer.
// XFAIL: msan