diff options
author | Andreas Gampe <agampe@google.com> | 2014-07-14 16:21:44 -0700 |
---|---|---|
committer | Andreas Gampe <agampe@google.com> | 2014-07-21 23:36:31 -0700 |
commit | 7ea6f79bbddd69d5db86a8656a31aaaf64ae2582 (patch) | |
tree | c64f89b15ca71e87317f6dd405ef4a5560b73e01 /runtime | |
parent | e72ff8022968b23efedc56c0afdc1d24e8a928c2 (diff) | |
download | android_art-7ea6f79bbddd69d5db86a8656a31aaaf64ae2582.tar.gz android_art-7ea6f79bbddd69d5db86a8656a31aaaf64ae2582.tar.bz2 android_art-7ea6f79bbddd69d5db86a8656a31aaaf64ae2582.zip |
ART: Throw StackOverflowError in native code
Initialize stack-overflow errors in native code to be able to reduce
the preserved area size of the stack.
Includes a refactoring away from constexpr in instruction_set.h to allow
for easy changing of the values.
Change-Id: I117cc8485f43da5f0a470f0f5e5b3dc3b5a06246
Diffstat (limited to 'runtime')
-rw-r--r-- | runtime/arch/arm/fault_handler_arm.cc | 4 | ||||
-rw-r--r-- | runtime/entrypoints/entrypoint_utils.cc | 97 | ||||
-rw-r--r-- | runtime/instruction_set.cc | 38 | ||||
-rw-r--r-- | runtime/instruction_set.h | 28 | ||||
-rw-r--r-- | runtime/thread.cc | 10 | ||||
-rw-r--r-- | runtime/thread.h | 5 | ||||
-rw-r--r-- | runtime/well_known_classes.cc | 4 | ||||
-rw-r--r-- | runtime/well_known_classes.h | 2 |
8 files changed, 141 insertions, 47 deletions
diff --git a/runtime/arch/arm/fault_handler_arm.cc b/runtime/arch/arm/fault_handler_arm.cc index e22c56ec69..48582f464e 100644 --- a/runtime/arch/arm/fault_handler_arm.cc +++ b/runtime/arch/arm/fault_handler_arm.cc @@ -61,7 +61,7 @@ void FaultManager::GetMethodAndReturnPCAndSP(siginfo_t* siginfo, void* context, // get the method from the top of the stack. However it's in r0. uintptr_t* fault_addr = reinterpret_cast<uintptr_t*>(sc->fault_address); uintptr_t* overflow_addr = reinterpret_cast<uintptr_t*>( - reinterpret_cast<uint8_t*>(*out_sp) - kArmStackOverflowReservedBytes); + reinterpret_cast<uint8_t*>(*out_sp) - GetStackOverflowReservedBytes(kArm)); if (overflow_addr == fault_addr) { *out_method = reinterpret_cast<mirror::ArtMethod*>(sc->arm_r0); } else { @@ -192,7 +192,7 @@ bool StackOverflowHandler::Action(int sig, siginfo_t* info, void* context) { VLOG(signals) << "checking for stack overflow, sp: " << std::hex << sp << ", fault_addr: " << fault_addr; - uintptr_t overflow_addr = sp - kArmStackOverflowReservedBytes; + uintptr_t overflow_addr = sp - GetStackOverflowReservedBytes(kArm); Thread* self = reinterpret_cast<Thread*>(sc->arm_r9); CHECK_EQ(self, Thread::Current()); diff --git a/runtime/entrypoints/entrypoint_utils.cc b/runtime/entrypoints/entrypoint_utils.cc index 0fa0e410bf..c1c7631b35 100644 --- a/runtime/entrypoints/entrypoint_utils.cc +++ b/runtime/entrypoints/entrypoint_utils.cc @@ -110,8 +110,8 @@ mirror::Array* CheckAndAllocArrayFromCodeInstrumented(uint32_t type_idx, mirror: void ThrowStackOverflowError(Thread* self) { if (self->IsHandlingStackOverflow()) { - LOG(ERROR) << "Recursive stack overflow."; - // We don't fail here because SetStackEndForStackOverflow will print better diagnostics. + LOG(ERROR) << "Recursive stack overflow."; + // We don't fail here because SetStackEndForStackOverflow will print better diagnostics. } if (Runtime::Current()->GetInstrumentation()->AreExitStubsInstalled()) { @@ -123,15 +123,90 @@ void ThrowStackOverflowError(Thread* self) { JNIEnvExt* env = self->GetJniEnv(); std::string msg("stack size "); msg += PrettySize(self->GetStackSize()); - // Use low-level JNI routine and pre-baked error class to avoid class linking operations that - // would consume more stack. - int rc = ::art::ThrowNewException(env, WellKnownClasses::java_lang_StackOverflowError, - msg.c_str(), NULL); - if (rc != JNI_OK) { - // TODO: ThrowNewException failed presumably because of an OOME, we continue to throw the OOME - // or die in the CHECK below. We may want to throw a pre-baked StackOverflowError - // instead. - LOG(ERROR) << "Couldn't throw new StackOverflowError because JNI ThrowNew failed."; + + // Avoid running Java code for exception initialization. + // TODO: Checks to make this a bit less brittle. + + std::string error_msg; + + // Allocate an uninitialized object. + ScopedLocalRef<jobject> exc(env, + env->AllocObject(WellKnownClasses::java_lang_StackOverflowError)); + if (exc.get() != nullptr) { + // "Initialize". + // StackOverflowError -> VirtualMachineError -> Error -> Throwable -> Object. + // Only Throwable has "custom" fields: + // String detailMessage. + // Throwable cause (= this). + // List<Throwable> suppressedExceptions (= Collections.emptyList()). + // Object stackState; + // StackTraceElement[] stackTrace; + // Only Throwable has a non-empty constructor: + // this.stackTrace = EmptyArray.STACK_TRACE_ELEMENT; + // fillInStackTrace(); + + // detailMessage. + // TODO: Use String::FromModifiedUTF...? + ScopedLocalRef<jstring> s(env, env->NewStringUTF(msg.c_str())); + if (s.get() != nullptr) { + jfieldID detail_message_id = env->GetFieldID(WellKnownClasses::java_lang_Throwable, + "detailMessage", "Ljava/lang/String;"); + env->SetObjectField(exc.get(), detail_message_id, s.get()); + + // cause. + jfieldID cause_id = env->GetFieldID(WellKnownClasses::java_lang_Throwable, + "cause", "Ljava/lang/Throwable;"); + env->SetObjectField(exc.get(), cause_id, exc.get()); + + // suppressedExceptions. + jfieldID emptylist_id = env->GetStaticFieldID(WellKnownClasses::java_util_Collections, + "EMPTY_LIST", "Ljava/util/List;"); + ScopedLocalRef<jobject> emptylist(env, env->GetStaticObjectField( + WellKnownClasses::java_util_Collections, emptylist_id)); + CHECK(emptylist.get() != nullptr); + jfieldID suppressed_id = env->GetFieldID(WellKnownClasses::java_lang_Throwable, + "suppressedExceptions", "Ljava/util/List;"); + env->SetObjectField(exc.get(), suppressed_id, emptylist.get()); + + // stackState is set as result of fillInStackTrace. fillInStackTrace calls + // nativeFillInStackTrace. + ScopedLocalRef<jobject> stack_state_val(env, nullptr); + { + ScopedObjectAccessUnchecked soa(env); + stack_state_val.reset(soa.Self()->CreateInternalStackTrace<false>(soa)); + } + if (stack_state_val.get() != nullptr) { + jfieldID stackstateID = env->GetFieldID(WellKnownClasses::java_lang_Throwable, + "stackState", "Ljava/lang/Object;"); + env->SetObjectField(exc.get(), stackstateID, stack_state_val.get()); + + // stackTrace. + jfieldID stack_trace_elem_id = env->GetStaticFieldID( + WellKnownClasses::libcore_util_EmptyArray, "STACK_TRACE_ELEMENT", + "[Ljava/lang/StackTraceElement;"); + ScopedLocalRef<jobject> stack_trace_elem(env, env->GetStaticObjectField( + WellKnownClasses::libcore_util_EmptyArray, stack_trace_elem_id)); + jfieldID stacktrace_id = env->GetFieldID( + WellKnownClasses::java_lang_Throwable, "stackTrace", "[Ljava/lang/StackTraceElement;"); + env->SetObjectField(exc.get(), stacktrace_id, stack_trace_elem.get()); + + // Throw the exception. + ThrowLocation throw_location = self->GetCurrentLocationForThrow(); + self->SetException(throw_location, + reinterpret_cast<mirror::Throwable*>(self->DecodeJObject(exc.get()))); + } else { + error_msg = "Could not create stack trace."; + } + } else { + // Could not allocate a string object. + error_msg = "Couldn't throw new StackOverflowError because JNI NewStringUTF failed."; + } + } else { + error_msg = "Could not allocate StackOverflowError object."; + } + + if (!error_msg.empty()) { + LOG(ERROR) << error_msg; CHECK(self->IsExceptionPending()); } diff --git a/runtime/instruction_set.cc b/runtime/instruction_set.cc index 5b6039647c..d7e358ce96 100644 --- a/runtime/instruction_set.cc +++ b/runtime/instruction_set.cc @@ -83,6 +83,44 @@ size_t GetInstructionSetAlignment(InstructionSet isa) { } } + +static constexpr size_t kDefaultStackOverflowReservedBytes = 16 * KB; +static constexpr size_t kMipsStackOverflowReservedBytes = kDefaultStackOverflowReservedBytes; + +// TODO: Lower once implicit stack-overflow checks can work with less than 16K. +static constexpr size_t kArmStackOverflowReservedBytes = (kIsDebugBuild ? 16 : 16) * KB; +static constexpr size_t kArm64StackOverflowReservedBytes = (kIsDebugBuild ? 16 : 16) * KB; +static constexpr size_t kX86StackOverflowReservedBytes = (kIsDebugBuild ? 16 : 16) * KB; +static constexpr size_t kX86_64StackOverflowReservedBytes = (kIsDebugBuild ? 16 : 16) * KB; + +size_t GetStackOverflowReservedBytes(InstructionSet isa) { + switch (isa) { + case kArm: // Intentional fall-through. + case kThumb2: + return kArmStackOverflowReservedBytes; + + case kArm64: + return kArm64StackOverflowReservedBytes; + + case kMips: + return kMipsStackOverflowReservedBytes; + + case kX86: + return kX86StackOverflowReservedBytes; + + case kX86_64: + return kX86_64StackOverflowReservedBytes; + + case kNone: + LOG(FATAL) << "kNone has no stack overflow size"; + return 0; + + default: + LOG(FATAL) << "Unknown instruction set" << isa; + return 0; + } +} + std::string InstructionSetFeatures::GetFeatureString() const { std::string result; if ((mask_ & kHwDiv) != 0) { diff --git a/runtime/instruction_set.h b/runtime/instruction_set.h index dce1c150ba..f212811e32 100644 --- a/runtime/instruction_set.h +++ b/runtime/instruction_set.h @@ -169,33 +169,7 @@ static inline size_t GetBytesPerFprSpillLocation(InstructionSet isa) { } } -static constexpr size_t kDefaultStackOverflowReservedBytes = 16 * KB; -static constexpr size_t kArmStackOverflowReservedBytes = kDefaultStackOverflowReservedBytes; -static constexpr size_t kMipsStackOverflowReservedBytes = kDefaultStackOverflowReservedBytes; - -// TODO: shrink reserved space, in particular for 64bit. - -// Worst-case, we would need about 2.6x the amount of x86_64 for many more registers. -// But this one works rather well. -static constexpr size_t kArm64StackOverflowReservedBytes = 32 * KB; -// TODO: Bumped to workaround regression (http://b/14982147) Specifically to fix: -// test-art-host-run-test-interpreter-018-stack-overflow -// test-art-host-run-test-interpreter-107-int-math2 -static constexpr size_t kX86StackOverflowReservedBytes = (kIsDebugBuild ? 32 : 24) * KB; -static constexpr size_t kX86_64StackOverflowReservedBytes = 32 * KB; - -static constexpr size_t GetStackOverflowReservedBytes(InstructionSet isa) { - return (isa == kArm || isa == kThumb2) ? kArmStackOverflowReservedBytes : - isa == kArm64 ? kArm64StackOverflowReservedBytes : - isa == kMips ? kMipsStackOverflowReservedBytes : - isa == kX86 ? kX86StackOverflowReservedBytes : - isa == kX86_64 ? kX86_64StackOverflowReservedBytes : - isa == kNone ? (LOG(FATAL) << "kNone has no stack overflow size", 0) : - (LOG(FATAL) << "Unknown instruction set" << isa, 0); -} - -static constexpr size_t kRuntimeStackOverflowReservedBytes = - GetStackOverflowReservedBytes(kRuntimeISA); +size_t GetStackOverflowReservedBytes(InstructionSet isa); enum InstructionFeatures { kHwDiv = 0x1, // Supports hardware divide. diff --git a/runtime/thread.cc b/runtime/thread.cc index f888029af1..c58f56b49c 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -76,6 +76,8 @@ namespace art { bool Thread::is_started_ = false; pthread_key_t Thread::pthread_key_self_; ConditionVariable* Thread::resume_cond_ = nullptr; +const size_t Thread::kStackOverflowImplicitCheckSize = kStackOverflowProtectedSize + + GetStackOverflowReservedBytes(kRuntimeISA); static const char* kThreadNameDuringStartup = "<native thread without managed peer>"; @@ -219,7 +221,7 @@ static size_t FixStackSize(size_t stack_size) { // It's likely that callers are trying to ensure they have at least a certain amount of // stack space, so we should add our reserved space on top of what they requested, rather // than implicitly take it away from them. - stack_size += kRuntimeStackOverflowReservedBytes; + stack_size += GetStackOverflowReservedBytes(kRuntimeISA); } else { // If we are going to use implicit stack checks, allocate space for the protected // region at the bottom of the stack. @@ -308,7 +310,7 @@ void Thread::InstallImplicitProtection(bool is_main_stack) { if (mprotect(pregion, kStackOverflowProtectedSize, PROT_NONE) == -1) { LOG(FATAL) << "Unable to create protected region in stack for implicit overflow check. Reason:" - << strerror(errno); + << strerror(errno) << kStackOverflowProtectedSize; } // Tell the kernel that we won't be needing these pages any more. @@ -536,7 +538,7 @@ void Thread::InitStackHwm() { tlsPtr_.stack_begin = reinterpret_cast<byte*>(read_stack_base); tlsPtr_.stack_size = read_stack_size; - if (read_stack_size <= kRuntimeStackOverflowReservedBytes) { + if (read_stack_size <= GetStackOverflowReservedBytes(kRuntimeISA)) { LOG(FATAL) << "Attempt to attach a thread with a too-small stack (" << read_stack_size << " bytes)"; } @@ -2247,7 +2249,7 @@ void Thread::SetStackEndForStackOverflow() { if (tlsPtr_.stack_end == tlsPtr_.stack_begin) { // However, we seem to have already extended to use the full stack. LOG(ERROR) << "Need to increase kStackOverflowReservedBytes (currently " - << kRuntimeStackOverflowReservedBytes << ")?"; + << GetStackOverflowReservedBytes(kRuntimeISA) << ")?"; DumpStack(LOG(ERROR)); LOG(FATAL) << "Recursive stack overflow."; } diff --git a/runtime/thread.h b/runtime/thread.h index d08c2fce82..c555034e08 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -104,8 +104,7 @@ class Thread { // is protected against reads and the lower is available for use while // throwing the StackOverflow exception. static constexpr size_t kStackOverflowProtectedSize = 16 * KB; - static constexpr size_t kStackOverflowImplicitCheckSize = kStackOverflowProtectedSize + - kRuntimeStackOverflowReservedBytes; + static const size_t kStackOverflowImplicitCheckSize; // Creates a new native thread corresponding to the given managed peer. // Used to implement Thread.start. @@ -567,7 +566,7 @@ class Thread { // overflow region. tlsPtr_.stack_end = tlsPtr_.stack_begin + kStackOverflowImplicitCheckSize; } else { - tlsPtr_.stack_end = tlsPtr_.stack_begin + kRuntimeStackOverflowReservedBytes; + tlsPtr_.stack_end = tlsPtr_.stack_begin + GetStackOverflowReservedBytes(kRuntimeISA); } } diff --git a/runtime/well_known_classes.cc b/runtime/well_known_classes.cc index fdc6e3f487..3a6a72b841 100644 --- a/runtime/well_known_classes.cc +++ b/runtime/well_known_classes.cc @@ -47,6 +47,8 @@ jclass WellKnownClasses::java_lang_Thread$UncaughtExceptionHandler; jclass WellKnownClasses::java_lang_ThreadGroup; jclass WellKnownClasses::java_lang_Throwable; jclass WellKnownClasses::java_nio_DirectByteBuffer; +jclass WellKnownClasses::java_util_Collections; +jclass WellKnownClasses::libcore_util_EmptyArray; jclass WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk; jclass WellKnownClasses::org_apache_harmony_dalvik_ddmc_DdmServer; @@ -150,6 +152,8 @@ void WellKnownClasses::Init(JNIEnv* env) { java_lang_ThreadGroup = CacheClass(env, "java/lang/ThreadGroup"); java_lang_Throwable = CacheClass(env, "java/lang/Throwable"); java_nio_DirectByteBuffer = CacheClass(env, "java/nio/DirectByteBuffer"); + java_util_Collections = CacheClass(env, "java/util/Collections"); + libcore_util_EmptyArray = CacheClass(env, "libcore/util/EmptyArray"); org_apache_harmony_dalvik_ddmc_Chunk = CacheClass(env, "org/apache/harmony/dalvik/ddmc/Chunk"); org_apache_harmony_dalvik_ddmc_DdmServer = CacheClass(env, "org/apache/harmony/dalvik/ddmc/DdmServer"); diff --git a/runtime/well_known_classes.h b/runtime/well_known_classes.h index f6c2930909..7639f50c69 100644 --- a/runtime/well_known_classes.h +++ b/runtime/well_known_classes.h @@ -60,7 +60,9 @@ struct WellKnownClasses { static jclass java_lang_ThreadGroup; static jclass java_lang_Thread$UncaughtExceptionHandler; static jclass java_lang_Throwable; + static jclass java_util_Collections; static jclass java_nio_DirectByteBuffer; + static jclass libcore_util_EmptyArray; static jclass org_apache_harmony_dalvik_ddmc_Chunk; static jclass org_apache_harmony_dalvik_ddmc_DdmServer; |