diff options
| -rw-r--r-- | Android.mk | 2 | ||||
| -rw-r--r-- | build/Android.libarttest.mk | 1 | ||||
| -rw-r--r-- | runtime/Android.mk | 5 | ||||
| -rw-r--r-- | runtime/fault_handler.cc | 27 | ||||
| -rw-r--r-- | runtime/runtime.cc | 17 | ||||
| -rw-r--r-- | runtime/thread.h | 2 | ||||
| -rw-r--r-- | sigchainlib/Android.mk | 29 | ||||
| -rw-r--r-- | sigchainlib/sigchain.cc | 183 | ||||
| -rw-r--r-- | sigchainlib/sigchain.h | 29 | ||||
| -rw-r--r-- | test/Android.mk | 1 | ||||
| -rw-r--r-- | test/SignalTest/SignalTest.java | 60 | ||||
| -rw-r--r-- | test/SignalTest/signaltest.cc | 74 |
12 files changed, 424 insertions, 6 deletions
diff --git a/Android.mk b/Android.mk index b329a5a4ef..a30c090027 100644 --- a/Android.mk +++ b/Android.mk @@ -90,6 +90,8 @@ include $(art_path)/oatdump/Android.mk include $(art_path)/dalvikvm/Android.mk include $(art_path)/tools/Android.mk include $(art_build_path)/Android.oat.mk +include $(art_path)/sigchainlib/Android.mk + diff --git a/build/Android.libarttest.mk b/build/Android.libarttest.mk index 9e5f3d6e4e..c08092895a 100644 --- a/build/Android.libarttest.mk +++ b/build/Android.libarttest.mk @@ -16,6 +16,7 @@ LIBARTTEST_COMMON_SRC_FILES := \ test/JniTest/jni_test.cc \ + test/SignalTest/signaltest.cc \ test/ReferenceMap/stack_walk_refmap_jni.cc \ test/StackWalk/stack_walk_jni.cc \ test/UnsafeTest/unsafe_test.cc diff --git a/runtime/Android.mk b/runtime/Android.mk index c2507b1457..a0648b0ff4 100644 --- a/runtime/Android.mk +++ b/runtime/Android.mk @@ -402,11 +402,13 @@ $$(ENUM_OPERATOR_OUT_GEN): $$(GENERATED_SRC_DIR)/%_operator_out.cc : $(LOCAL_PAT endif endif LOCAL_C_INCLUDES += $(ART_C_INCLUDES) + LOCAL_C_INCLUDES += art/sigchainlib + LOCAL_SHARED_LIBRARIES += liblog libnativehelper include external/libcxx/libcxx.mk LOCAL_SHARED_LIBRARIES += libbacktrace_libc++ ifeq ($$(art_target_or_host),target) - LOCAL_SHARED_LIBRARIES += libcutils libdl libselinux libutils + LOCAL_SHARED_LIBRARIES += libcutils libdl libselinux libutils libsigchain LOCAL_STATIC_LIBRARIES := libziparchive libz else # host LOCAL_STATIC_LIBRARIES += libcutils libziparchive-host libz libutils @@ -459,3 +461,4 @@ endif ifeq ($(ART_BUILD_TARGET_DEBUG),true) $(eval $(call build-libart,target,debug,$(ART_TARGET_CLANG))) endif + diff --git a/runtime/fault_handler.cc b/runtime/fault_handler.cc index 8d750c5bdd..15c38c1f4b 100644 --- a/runtime/fault_handler.cc +++ b/runtime/fault_handler.cc @@ -29,12 +29,22 @@ #include "mirror/object-inl.h" #include "object_utils.h" #include "scoped_thread_state_change.h" +#ifdef HAVE_ANDROID_OS +#include "sigchain.h" +#endif #include "verify_object-inl.h" namespace art { // Static fault manger object accessed by signal handler. FaultManager fault_manager; +extern "C" { +void art_sigsegv_fault() { + // Set a breakpoint here to be informed when a SIGSEGV is unhandled by ART. + VLOG(signals)<< "Caught unknown SIGSEGV in ART fault handler - chaining to next handler."; +} +} + // Signal handler called on SIGSEGV. static void art_fault_handler(int sig, siginfo_t* info, void* context) { fault_manager.HandleFault(sig, info, context); @@ -45,9 +55,13 @@ FaultManager::FaultManager() { } FaultManager::~FaultManager() { +#ifdef HAVE_ANDROID_OS + UnclaimSignalChain(SIGSEGV); +#endif sigaction(SIGSEGV, &oldaction_, nullptr); // Restore old handler. } + void FaultManager::Init() { struct sigaction action; action.sa_sigaction = art_fault_handler; @@ -56,7 +70,13 @@ void FaultManager::Init() { #if !defined(__mips__) action.sa_restorer = nullptr; #endif + + // Set our signal handler now. sigaction(SIGSEGV, &action, &oldaction_); +#ifdef HAVE_ANDROID_OS + // Make sure our signal handler is called before any user handlers. + ClaimSignalChain(SIGSEGV, &oldaction_); +#endif } void FaultManager::HandleFault(int sig, siginfo_t* info, void* context) { @@ -79,8 +99,13 @@ void FaultManager::HandleFault(int sig, siginfo_t* info, void* context) { return; } } - VLOG(signals)<< "Caught unknown SIGSEGV in ART fault handler"; + art_sigsegv_fault(); + +#ifdef HAVE_ANDROID_OS + InvokeUserSignalHandler(sig, info, context); +#else oldaction_.sa_sigaction(sig, info, context); +#endif } void FaultManager::AddHandler(FaultHandler* handler, bool generated_code) { diff --git a/runtime/runtime.cc b/runtime/runtime.cc index 23a49cb33a..361070c028 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -562,9 +562,20 @@ bool Runtime::Init(const Options& raw_options, bool ignore_unrecognized) { GetInstrumentation()->ForceInterpretOnly(); } - if (options->explicit_checks_ != (ParsedOptions::kExplicitSuspendCheck | - ParsedOptions::kExplicitNullCheck | - ParsedOptions::kExplicitStackOverflowCheck) || kEnableJavaStackTraceHandler) { + bool implicit_checks_supported = false; + switch (kRuntimeISA) { + case kArm: + case kThumb2: + implicit_checks_supported = true; + break; + default: + break; + } + + if (implicit_checks_supported && + (options->explicit_checks_ != (ParsedOptions::kExplicitSuspendCheck | + ParsedOptions::kExplicitNullCheck | + ParsedOptions::kExplicitStackOverflowCheck) || kEnableJavaStackTraceHandler)) { fault_manager.Init(); // These need to be in a specific order. The null point check handler must be diff --git a/runtime/thread.h b/runtime/thread.h index 9a7cb486d8..08bbcaec49 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -121,7 +121,7 @@ class Thread { // of the stack (lowest memory). The higher portion of the memory // is protected against reads and the lower is available for use while // throwing the StackOverflow exception. - static constexpr size_t kStackOverflowProtectedSize = 32 * KB; + static constexpr size_t kStackOverflowProtectedSize = 16 * KB; static constexpr size_t kStackOverflowImplicitCheckSize = kStackOverflowProtectedSize + kStackOverflowReservedBytes; diff --git a/sigchainlib/Android.mk b/sigchainlib/Android.mk new file mode 100644 index 0000000000..cb1778dc8e --- /dev/null +++ b/sigchainlib/Android.mk @@ -0,0 +1,29 @@ +# +# Copyright (C) 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH:= $(call my-dir) + +include art/build/Android.common.mk + +include $(CLEAR_VARS) +LOCAL_CPP_EXTENSION := $(ART_CPP_EXTENSION) +LOCAL_MODULE_TAGS := optional +LOCAL_CFLAGS += $(ART_TARGET_CFLAGS) +LOCAL_SRC_FILES := sigchain.cc +LOCAL_MODULE:= libsigchain +LOCAL_SHARED_LIBRARIES += liblog libdl +LOCAL_ADDITIONAL_DEPENDENCIES += $(LOCAL_PATH)/Android.mk +include $(BUILD_SHARED_LIBRARY) diff --git a/sigchainlib/sigchain.cc b/sigchainlib/sigchain.cc new file mode 100644 index 0000000000..26e7d319cd --- /dev/null +++ b/sigchainlib/sigchain.cc @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <android/log.h> +#include <dlfcn.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> + +namespace art { + +class SignalAction { + public: + SignalAction() : claimed_(false) { + } + + // Claim the signal and keep the action specified. + void Claim(const struct sigaction& action) { + action_ = action; + claimed_ = true; + } + + // Unclaim the signal and restore the old action. + void Unclaim(int signal) { + claimed_ = false; + sigaction(signal, &action_, NULL); // Restore old action. + } + + // Get the action associated with this signal. + const struct sigaction& GetAction() const { + return action_; + } + + // Is the signal claimed? + bool IsClaimed() const { + return claimed_; + } + + // Change the recorded action to that specified. + void SetAction(const struct sigaction& action) { + action_ = action; + } + + private: + struct sigaction action_; // Action to be performed. + bool claimed_; // Whether signal is claimed or not. +}; + +// User's signal handlers +static SignalAction user_sigactions[_NSIG]; + +static void log(const char* format, ...) { + char buf[256]; + va_list ap; + va_start(ap, format); + vsnprintf(buf, sizeof(buf), format, ap); + __android_log_write(ANDROID_LOG_ERROR, "libsigchain", buf); + va_end(ap); +} + +static void CheckSignalValid(int signal) { + if (signal <= 0 || signal >= _NSIG) { + log("Invalid signal %d", signal); + abort(); + } +} + +// Claim a signal chain for a particular signal. +void ClaimSignalChain(int signal, struct sigaction* oldaction) { + CheckSignalValid(signal); + user_sigactions[signal].Claim(*oldaction); +} + +void UnclaimSignalChain(int signal) { + CheckSignalValid(signal); + + user_sigactions[signal].Unclaim(signal); +} + +// Invoke the user's signal handler. +void InvokeUserSignalHandler(int sig, siginfo_t* info, void* context) { + // Check the arguments. + CheckSignalValid(sig); + + // The signal must have been claimed in order to get here. Check it. + if (!user_sigactions[sig].IsClaimed()) { + abort(); + } + + const struct sigaction& action = user_sigactions[sig].GetAction(); + + // Only deliver the signal if the signal was not masked out. + if (sigismember(&action.sa_mask, sig)) { + return; + } + if ((action.sa_flags & SA_SIGINFO) == 0) { + if (action.sa_handler != NULL) { + action.sa_handler(sig); + } + } else { + if (action.sa_sigaction != NULL) { + action.sa_sigaction(sig, info, context); + } + } +} + +extern "C" { +// These functions are C linkage since they replace the functions in libc. + +int sigaction(int signal, const struct sigaction* new_action, struct sigaction* old_action) { + // If this signal has been claimed as a signal chain, record the user's + // action but don't pass it on to the kernel. + // Note that we check that the signal number is in range here. An out of range signal + // number should behave exactly as the libc sigaction. + if (signal > 0 && signal < _NSIG && user_sigactions[signal].IsClaimed()) { + if (old_action != NULL) { + *old_action = user_sigactions[signal].GetAction(); + } + if (new_action != NULL) { + user_sigactions[signal].SetAction(*new_action); + } + return 0; + } + + // Will only get here if the signal chain has not been claimed. We want + // to pass the sigaction on to the kernel via the real sigaction in libc. + + void* linked_sigaction_sym = dlsym(RTLD_NEXT, "sigaction"); + if (linked_sigaction_sym == nullptr) { + log("Unable to find next sigaction in signal chain"); + abort(); + } + + typedef int (*SigAction)(int, const struct sigaction*, struct sigaction*); + SigAction linked_sigaction = reinterpret_cast<SigAction>(linked_sigaction_sym); + return linked_sigaction(signal, new_action, old_action); +} + + +int sigprocmask(int how, const sigset_t* bionic_new_set, sigset_t* bionic_old_set) { + const sigset_t* new_set_ptr = bionic_new_set; + sigset_t tmpset; + if (bionic_new_set != NULL) { + tmpset = *bionic_new_set; + + if (how == SIG_BLOCK) { + // Don't allow claimed signals in the mask. If a signal chain has been claimed + // we can't allow the user to block that signal. + for (int i = 0 ; i < _NSIG; ++i) { + if (user_sigactions[i].IsClaimed() && sigismember(&tmpset, i)) { + sigdelset(&tmpset, i); + } + } + } + new_set_ptr = &tmpset; + } + + void* linked_sigprocmask_sym = dlsym(RTLD_NEXT, "sigprocmask"); + if (linked_sigprocmask_sym == nullptr) { + log("Unable to find next sigprocmask in signal chain"); + abort(); + } + + typedef int (*SigProcMask)(int how, const sigset_t*, sigset_t*); + SigProcMask linked_sigprocmask= reinterpret_cast<SigProcMask>(linked_sigprocmask_sym); + return linked_sigprocmask(how, new_set_ptr, bionic_old_set); +} +} // extern "C" +} // namespace art + diff --git a/sigchainlib/sigchain.h b/sigchainlib/sigchain.h new file mode 100644 index 0000000000..f6f2253d72 --- /dev/null +++ b/sigchainlib/sigchain.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_SIGCHAINLIB_SIGCHAIN_H_ +#define ART_SIGCHAINLIB_SIGCHAIN_H_ + +#include <signal.h> +namespace art { + +void ClaimSignalChain(int signal, struct sigaction* oldaction); +void UnclaimSignalChain(int signal); +void InvokeUserSignalHandler(int sig, siginfo_t* info, void* context); + +} // namespace art + +#endif // ART_SIGCHAINLIB_SIGCHAIN_H_ diff --git a/test/Android.mk b/test/Android.mk index 8caa033b98..c15259c4b1 100644 --- a/test/Android.mk +++ b/test/Android.mk @@ -46,6 +46,7 @@ TEST_OAT_DIRECTORIES := \ HelloWorld \ InterfaceTest \ JniTest \ + SignalTest \ NativeAllocations \ ParallelGC \ ReferenceMap \ diff --git a/test/SignalTest/SignalTest.java b/test/SignalTest/SignalTest.java new file mode 100644 index 0000000000..7f15aeae76 --- /dev/null +++ b/test/SignalTest/SignalTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class SignalTest { + private static native void initSignalTest(); + private static native void terminateSignalTest(); + private static native int testSignal(); + + private static void stackOverflow() { + stackOverflow(); + } + + public static void main(String[] args) { + System.loadLibrary("arttest"); + + System.out.println("init signal test"); + initSignalTest(); + try { + Object o = null; + int hash = o.hashCode(); + + // Should never get here. + System.out.println("hash: " + hash); + throw new AssertionError(); + } catch (NullPointerException e) { + System.out.println("Caught NullPointerException"); + } + try { + stackOverflow(); + + // Should never get here. + throw new AssertionError(); + } catch (StackOverflowError e) { + System.out.println("Caught StackOverflowError"); + } + + // Test that a signal in native code works. This will return + // the value 1234 if the signal is caught. + int x = testSignal(); + if (x != 1234) { + throw new AssertionError(); + } + + terminateSignalTest(); + System.out.println("Signal test OK"); + } +} diff --git a/test/SignalTest/signaltest.cc b/test/SignalTest/signaltest.cc new file mode 100644 index 0000000000..b84e3957ce --- /dev/null +++ b/test/SignalTest/signaltest.cc @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <signal.h> +#include <stdio.h> +#include <unistd.h> + +#include "jni.h" + +#ifdef __arm__ +#include <sys/ucontext.h> +#endif + +static void signalhandler(int sig, siginfo_t* info, void* context) { + printf("signal caught\n"); +#ifdef __arm__ + // On ARM we do a more exhaustive test to make sure the signal + // context is OK. + // We can do this because we know that the instruction causing + // the signal is 2 bytes long (thumb mov instruction). On + // other architectures this is more difficult. + // TODO: we could do this on other architectures too if necessary, it's just harder. + struct ucontext *uc = reinterpret_cast<struct ucontext*>(context); + struct sigcontext *sc = reinterpret_cast<struct sigcontext*>(&uc->uc_mcontext); + sc->arm_pc += 2; // Skip instruction causing segv. +#endif +} + +static struct sigaction oldaction; + +extern "C" JNIEXPORT void JNICALL Java_SignalTest_initSignalTest(JNIEnv*, jclass) { + struct sigaction action; + action.sa_sigaction = signalhandler; + sigemptyset(&action.sa_mask); + action.sa_flags = SA_SIGINFO | SA_ONSTACK; +#if !defined(__mips__) + action.sa_restorer = nullptr; +#endif + + sigaction(SIGSEGV, &action, &oldaction); +} + +extern "C" JNIEXPORT void JNICALL Java_SignalTest_terminateSignalTest(JNIEnv*, jclass) { + sigaction(SIGSEGV, &oldaction, nullptr); +} + +// Prevent the compiler being a smart-alec and optimizing out the assignment +// to nullptr. +char *p = nullptr; + +extern "C" JNIEXPORT jint JNICALL Java_SignalTest_testSignal(JNIEnv*, jclass) { +#ifdef __arm__ + // On ARM we cause a real SEGV. + *p = 'a'; +#else + // On other architectures we simulate SEGV. + kill(getpid(), SIGSEGV); +#endif + return 1234; +} + |