dart-sdk/runtime/vm/signal_handler_android.cc
Vyacheslav Egorov a013de84e0 [vm] Reserve larger alternative signal stack if needed
Commit 18bdb28ef6 have configured `SIGPROF` handler on Android
to use alternative signal stack to workaround a [bug][1] in Bionic
implementation of `setjmp`. However when implementing the
workaround we have misinterpreted `sigaltstack` documentation
and assumed that `sigaltstack` configures alternative stack
*globally*, similar to how `sigaction` configures the signal handler.
This is not correct: `sigaltstack` configures alternative stack
for the current thread only.

Nevertheless our workaround kinda worked as intended because Bionic's
`pthread_create` actually assigns an individual alternative
stack to each new thread since [Android L][2].

However older version of Bionic (pre [Android 6.0.1][3]) configured
alternative signal stack which was too small for ARM64. This meant
that non-trivial signal handler could easily overflow the stack
and cause a SIGSEGV when hitting a guard page.

This is what we observe in the flutter/flutter#130003: launching
Flutter application with profiler enabled in a ARM64 emulator
running Android 6.0 causes an immediate segfault in the
signal handler.

This CL changes our code to make sure that alternative signal
stack associated with the current thread is present and is large
enough and allocate a new one if it is not.

[1]: b/152210274
[2]: https://android-review.git.corp.google.com/c/platform/bionic/+/62238
[3]: https://android-review.git.corp.google.com/c/platform/bionic/+/172213
[4]: https://github.com/flutter/flutter/issues/130003

TEST=manually on an Android 6.0.0 ARM64 emulator

Cq-Include-Trybots: luci.dart.try:vm-aot-android-release-arm64c-try,vm-ffi-android-release-arm64c-try,vm-ffi-android-release-arm-try
Change-Id: Ib87df25e72ad3f486e90cf2fc6d7f980a8e0b1dc
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/327866
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Slava Egorov <vegorov@google.com>
2023-09-27 15:11:05 +00:00

191 lines
6.2 KiB
C++

// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "vm/globals.h"
#include "vm/instructions.h"
#include "vm/signal_handler.h"
#include "vm/simulator.h"
#if defined(DART_HOST_OS_ANDROID)
namespace dart {
uintptr_t SignalHandler::GetProgramCounter(const mcontext_t& mcontext) {
uintptr_t pc = 0;
#if defined(HOST_ARCH_IA32)
pc = static_cast<uintptr_t>(mcontext.gregs[REG_EIP]);
#elif defined(HOST_ARCH_X64)
pc = static_cast<uintptr_t>(mcontext.gregs[REG_RIP]);
#elif defined(HOST_ARCH_ARM)
pc = static_cast<uintptr_t>(mcontext.arm_pc);
#elif defined(HOST_ARCH_ARM64)
pc = static_cast<uintptr_t>(mcontext.pc);
#elif defined(HOST_ARCH_RISCV64)
pc = static_cast<uintptr_t>(mcontext.__gregs[REG_PC]);
#else
#error Unsupported architecture.
#endif // HOST_ARCH_...
return pc;
}
uintptr_t SignalHandler::GetFramePointer(const mcontext_t& mcontext) {
uintptr_t fp = 0;
#if defined(HOST_ARCH_IA32)
fp = static_cast<uintptr_t>(mcontext.gregs[REG_EBP]);
#elif defined(HOST_ARCH_X64)
fp = static_cast<uintptr_t>(mcontext.gregs[REG_RBP]);
#elif defined(HOST_ARCH_ARM)
// B1.3.3 Program Status Registers (PSRs)
if ((mcontext.arm_cpsr & (1 << 5)) != 0) {
// Thumb mode.
fp = static_cast<uintptr_t>(mcontext.arm_r7);
} else {
// ARM mode.
fp = static_cast<uintptr_t>(mcontext.arm_fp);
}
#elif defined(HOST_ARCH_ARM64)
fp = static_cast<uintptr_t>(mcontext.regs[29]);
#elif defined(HOST_ARCH_RISCV64)
fp = static_cast<uintptr_t>(mcontext.__gregs[REG_S0]);
#else
#error Unsupported architecture.
#endif // HOST_ARCH_...
return fp;
}
uintptr_t SignalHandler::GetCStackPointer(const mcontext_t& mcontext) {
uintptr_t sp = 0;
#if defined(HOST_ARCH_IA32)
sp = static_cast<uintptr_t>(mcontext.gregs[REG_ESP]);
#elif defined(HOST_ARCH_X64)
sp = static_cast<uintptr_t>(mcontext.gregs[REG_RSP]);
#elif defined(HOST_ARCH_ARM)
sp = static_cast<uintptr_t>(mcontext.arm_sp);
#elif defined(HOST_ARCH_ARM64)
sp = static_cast<uintptr_t>(mcontext.sp);
#elif defined(HOST_ARCH_RISCV64)
sp = static_cast<uintptr_t>(mcontext.__gregs[REG_SP]);
#else
#error Unsupported architecture.
#endif // HOST_ARCH_...
return sp;
}
uintptr_t SignalHandler::GetDartStackPointer(const mcontext_t& mcontext) {
#if defined(TARGET_ARCH_ARM64) && !defined(USING_SIMULATOR)
return static_cast<uintptr_t>(mcontext.regs[SPREG]);
#else
return GetCStackPointer(mcontext);
#endif
}
uintptr_t SignalHandler::GetLinkRegister(const mcontext_t& mcontext) {
uintptr_t lr = 0;
#if defined(HOST_ARCH_IA32)
lr = 0;
#elif defined(HOST_ARCH_X64)
lr = 0;
#elif defined(HOST_ARCH_ARM)
lr = static_cast<uintptr_t>(mcontext.arm_lr);
#elif defined(HOST_ARCH_ARM64)
lr = static_cast<uintptr_t>(mcontext.regs[30]);
#elif defined(HOST_ARCH_RISCV64)
lr = static_cast<uintptr_t>(mcontext.__gregs[REG_RA]);
#else
#error Unsupported architecture.
#endif // HOST_ARCH_...
return lr;
}
void SignalHandler::Install(SignalAction action) {
// Bionic implementation of setjmp temporary mangles SP register
// in place which breaks signal delivery on the thread stack - when
// kernel tries to deliver SIGPROF and we are in the middle of
// setjmp SP value is invalid - might be pointing to random memory
// or outside of writable space at all. In the first case we
// get memory corruption and in the second case kernel would send
// SIGSEGV to the process. See b/152210274 for details.
// To work around this issue we request SIGPROF signals to be delivered
// on the alternative signal stack by setting SA_ONSTACK. The stack itself
// is configured when interrupts are enabled for a particular thread.
// In reality Bionic's |pthread_create| eagerly creates and assigns an
// alternative signal stack for each thread. However older versions of Bionic
// (L and below) make the size of alternative stack too small which causes
// stack overflows and crashes.
struct sigaction act = {};
act.sa_sigaction = action;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGPROF); // Prevent nested signals.
act.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK;
int r = sigaction(SIGPROF, &act, nullptr);
ASSERT(r == 0);
}
void SignalHandler::Remove() {
// Ignore future SIGPROF signals because by default SIGPROF will terminate
// the process and we may have some signals in flight.
struct sigaction act = {};
act.sa_handler = SIG_IGN;
sigemptyset(&act.sa_mask);
int r = sigaction(SIGPROF, &act, nullptr);
RELEASE_ASSERT(r == 0);
}
void* SignalHandler::PrepareCurrentThread() {
// These constants are selected to prevent allocating alternative signal
// stack if Bionic has already allocated large enough one for us. They
// match current values used in Bionic[1].
//
// [1]: https://cs.android.com/android/platform/superproject/main/+/main:bionic/libc/bionic/pthread_internal.h;drc=3649db34a154cedb8ef53a5adbaa349970159b58;l=243
const intptr_t kGuardPageSize = 4 * KB;
#if defined(TARGET_ARCH_IS_64_BIT)
const intptr_t kSigAltStackSize = 32 * KB;
#else
const intptr_t kSigAltStackSize = 16 * KB;
#endif
// First check if the alternative signal stack is already installed and
// large enough.
int r;
stack_t ss;
memset(&ss, 0, sizeof(ss));
r = sigaltstack(nullptr, &ss);
ASSERT(r == 0);
if (ss.ss_flags == 0 && ss.ss_size >= (kSigAltStackSize - kGuardPageSize)) {
// Bionic has created a large enough stack already.
return nullptr;
}
// We are running on an older version of Android, where Bionic creates
// stacks which are too small.
ss.ss_sp = malloc(kSigAltStackSize);
ss.ss_size = kSigAltStackSize;
ss.ss_flags = 0;
r = sigaltstack(&ss, nullptr);
ASSERT(r == 0);
return ss.ss_sp;
}
void SignalHandler::CleanupCurrentThreadState(void* stack) {
if (stack != nullptr) {
// Disable alternative stack then free allocated memory.
stack_t ss, old_ss;
memset(&ss, 0, sizeof(ss));
ss.ss_flags = SS_DISABLE;
int r = sigaltstack(&ss, &old_ss);
ASSERT(r == 0);
ASSERT(old_ss.ss_sp == stack);
free(stack);
}
}
} // namespace dart
#endif // defined(DART_HOST_OS_ANDROID)