diff options
author | Dave Allison <dallison@google.com> | 2014-07-25 16:15:27 -0700 |
---|---|---|
committer | Dave Allison <dallison@google.com> | 2014-08-13 09:01:41 -0700 |
commit | 648d7112609dd19c38131b3e71c37bcbbd19d11e (patch) | |
tree | 54062831327c660acb309e877e8d8df9ba0c2d5d /runtime | |
parent | 99c251bbd225dd97d0deece29559a430b12a0b66 (diff) | |
download | art-648d7112609dd19c38131b3e71c37bcbbd19d11e.zip art-648d7112609dd19c38131b3e71c37bcbbd19d11e.tar.gz art-648d7112609dd19c38131b3e71c37bcbbd19d11e.tar.bz2 |
Reduce stack usage for overflow checks
This reduces the stack space reserved for overflow checks to 12K, split
into an 8K gap and a 4K protected region. GC needs over 8K when running
in a stack overflow situation.
Also prevents signal runaway by detecting a signal inside code that
resulted from a signal handler invokation. And adds a max signal count to
the SignalTest to prevent it running forever.
Also reduces the number of iterations for the InterfaceTest as this was
taking (almost) forever with the --trace option on run-test.
Bug: 15435566
Change-Id: Id4fd46f22d52d42a9eb431ca07948673e8fda694
Diffstat (limited to 'runtime')
-rw-r--r-- | runtime/arch/arm/asm_support_arm.h | 4 | ||||
-rw-r--r-- | runtime/arch/arm/fault_handler_arm.cc | 31 | ||||
-rw-r--r-- | runtime/arch/arm/quick_entrypoints_arm.S | 25 | ||||
-rw-r--r-- | runtime/arch/arm64/asm_support_arm64.h | 4 | ||||
-rw-r--r-- | runtime/arch/arm64/fault_handler_arm64.cc | 31 | ||||
-rw-r--r-- | runtime/arch/arm64/quick_entrypoints_arm64.S | 25 | ||||
-rw-r--r-- | runtime/arch/x86/asm_support_x86.h | 6 | ||||
-rw-r--r-- | runtime/arch/x86/fault_handler_x86.cc | 20 | ||||
-rw-r--r-- | runtime/arch/x86/quick_entrypoints_x86.S | 15 | ||||
-rw-r--r-- | runtime/arch/x86_64/asm_support_x86_64.h | 6 | ||||
-rw-r--r-- | runtime/arch/x86_64/quick_entrypoints_x86_64.S | 12 | ||||
-rw-r--r-- | runtime/entrypoints/entrypoint_utils.cc | 5 | ||||
-rw-r--r-- | runtime/entrypoints/quick/quick_throw_entrypoints.cc | 4 | ||||
-rw-r--r-- | runtime/entrypoints_order_test.cc | 4 | ||||
-rw-r--r-- | runtime/instruction_set.cc | 9 | ||||
-rw-r--r-- | runtime/mirror/art_method.cc | 5 | ||||
-rw-r--r-- | runtime/oat.cc | 2 | ||||
-rw-r--r-- | runtime/reflection.cc | 49 | ||||
-rw-r--r-- | runtime/thread.cc | 151 | ||||
-rw-r--r-- | runtime/thread.h | 58 |
20 files changed, 217 insertions, 249 deletions
diff --git a/runtime/arch/arm/asm_support_arm.h b/runtime/arch/arm/asm_support_arm.h index ae30aee..330924e 100644 --- a/runtime/arch/arm/asm_support_arm.h +++ b/runtime/arch/arm/asm_support_arm.h @@ -24,9 +24,9 @@ // Offset of field Thread::tls32_.thin_lock_thread_id verified in InitCpu #define THREAD_ID_OFFSET 12 // Offset of field Thread::tlsPtr_.card_table verified in InitCpu -#define THREAD_CARD_TABLE_OFFSET 112 +#define THREAD_CARD_TABLE_OFFSET 120 // Offset of field Thread::tlsPtr_.exception verified in InitCpu -#define THREAD_EXCEPTION_OFFSET 116 +#define THREAD_EXCEPTION_OFFSET 124 #define FRAME_SIZE_SAVE_ALL_CALLEE_SAVE 176 #define FRAME_SIZE_REFS_ONLY_CALLEE_SAVE 32 diff --git a/runtime/arch/arm/fault_handler_arm.cc b/runtime/arch/arm/fault_handler_arm.cc index be28544..28b69ec 100644 --- a/runtime/arch/arm/fault_handler_arm.cc +++ b/runtime/arch/arm/fault_handler_arm.cc @@ -35,7 +35,7 @@ namespace art { extern "C" void art_quick_throw_null_pointer_exception(); -extern "C" void art_quick_throw_stack_overflow_from_signal(); +extern "C" void art_quick_throw_stack_overflow(); extern "C" void art_quick_implicit_suspend(); // Get the size of a thumb2 instruction in bytes. @@ -194,40 +194,19 @@ bool StackOverflowHandler::Action(int sig, siginfo_t* info, void* context) { uintptr_t overflow_addr = sp - GetStackOverflowReservedBytes(kArm); - Thread* self = reinterpret_cast<Thread*>(sc->arm_r9); - CHECK_EQ(self, Thread::Current()); - uintptr_t pregion = reinterpret_cast<uintptr_t>(self->GetStackEnd()) - - Thread::kStackOverflowProtectedSize; - // Check that the fault address is the value expected for a stack overflow. if (fault_addr != overflow_addr) { VLOG(signals) << "Not a stack overflow"; return false; } - // We know this is a stack overflow. We need to move the sp to the overflow region - // that exists below the protected region. Determine the address of the next - // available valid address below the protected region. - uintptr_t prevsp = sp; - sp = pregion; - VLOG(signals) << "setting sp to overflow region at " << std::hex << sp; - - // Since the compiler puts the implicit overflow - // check before the callee save instructions, the SP is already pointing to - // the previous frame. - VLOG(signals) << "previous frame: " << std::hex << prevsp; - - // Now establish the stack pointer for the signal return. - sc->arm_sp = prevsp; - - // Tell the stack overflow code where the new stack pointer should be. - sc->arm_ip = sp; // aka r12 + VLOG(signals) << "Stack overflow found"; - // Now arrange for the signal handler to return to art_quick_throw_stack_overflow_from_signal. + // Now arrange for the signal handler to return to art_quick_throw_stack_overflow_from. // The value of LR must be the same as it was when we entered the code that // caused this fault. This will be inserted into a callee save frame by - // the function to which this handler returns (art_quick_throw_stack_overflow_from_signal). - sc->arm_pc = reinterpret_cast<uintptr_t>(art_quick_throw_stack_overflow_from_signal); + // the function to which this handler returns (art_quick_throw_stack_overflow). + sc->arm_pc = reinterpret_cast<uintptr_t>(art_quick_throw_stack_overflow); // The kernel will now return to the address in sc->arm_pc. return true; diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S index 6c63a1a..dd1f04a 100644 --- a/runtime/arch/arm/quick_entrypoints_arm.S +++ b/runtime/arch/arm/quick_entrypoints_arm.S @@ -235,31 +235,6 @@ NO_ARG_RUNTIME_EXCEPTION art_quick_throw_stack_overflow, artThrowStackOverflowFr */ ONE_ARG_RUNTIME_EXCEPTION art_quick_throw_no_such_method, artThrowNoSuchMethodFromCode - /* - * Invoke stack overflow exception from signal handler. - * On entry: - * r9: thread - * sp: address of last known frame - * r12: address of next valid SP below protected region in stack - * - * This is deceptively simple but hides some complexity. It is called in the case of - * a stack overflow condition during implicit checks. The signal handler has been - * called by the kernel due to a load from the protected stack region. The handler - * works out the address of the previous frame and passes this in SP. However there - * is a piece of memory somewhere below the current SP that is not accessible (the - * memory that caused the signal). The signal handler works out the next - * accessible value of SP and passes this in r12. This code then sets up the SP - * to be this new value and calls the code to create and throw the stack overflow - * exception. - */ -ENTRY art_quick_throw_stack_overflow_from_signal - SETUP_SAVE_ALL_CALLEE_SAVE_FRAME // save all registers as basis for long jump context - mov r0, r9 @ pass Thread::Current - mov r1, sp @ pass SP - mov sp, r12 @ move SP down to below protected region. - b artThrowStackOverflowFromCode @ artThrowStackOverflowFromCode(Thread*, SP) -END art_quick_throw_stack_overflow_from_signal - /* * All generated callsites for interface invokes and invocation slow paths will load arguments * as usual - except instead of loading arg0/r0 with the target Method*, arg0/r0 will contain diff --git a/runtime/arch/arm64/asm_support_arm64.h b/runtime/arch/arm64/asm_support_arm64.h index 7f0f56f..a926449 100644 --- a/runtime/arch/arm64/asm_support_arm64.h +++ b/runtime/arch/arm64/asm_support_arm64.h @@ -30,9 +30,9 @@ // Offset of field Thread::suspend_count_ #define THREAD_FLAGS_OFFSET 0 // Offset of field Thread::card_table_ -#define THREAD_CARD_TABLE_OFFSET 112 +#define THREAD_CARD_TABLE_OFFSET 120 // Offset of field Thread::exception_ -#define THREAD_EXCEPTION_OFFSET 120 +#define THREAD_EXCEPTION_OFFSET 128 // Offset of field Thread::thin_lock_thread_id_ #define THREAD_ID_OFFSET 12 diff --git a/runtime/arch/arm64/fault_handler_arm64.cc b/runtime/arch/arm64/fault_handler_arm64.cc index 3a7e689..b5948cb 100644 --- a/runtime/arch/arm64/fault_handler_arm64.cc +++ b/runtime/arch/arm64/fault_handler_arm64.cc @@ -27,7 +27,7 @@ #include "thread.h" #include "thread-inl.h" -extern "C" void art_quick_throw_stack_overflow_from_signal(); +extern "C" void art_quick_throw_stack_overflow(); extern "C" void art_quick_throw_null_pointer_exception(); extern "C" void art_quick_implicit_suspend(); @@ -157,40 +157,19 @@ bool StackOverflowHandler::Action(int sig, siginfo_t* info, void* context) { uintptr_t overflow_addr = sp - GetStackOverflowReservedBytes(kArm64); - Thread* self = reinterpret_cast<Thread*>(sc->regs[art::arm64::TR]); - CHECK_EQ(self, Thread::Current()); - uintptr_t pregion = reinterpret_cast<uintptr_t>(self->GetStackEnd()) - - Thread::kStackOverflowProtectedSize; - // Check that the fault address is the value expected for a stack overflow. if (fault_addr != overflow_addr) { VLOG(signals) << "Not a stack overflow"; return false; } - // We know this is a stack overflow. We need to move the sp to the overflow region - // that exists below the protected region. Determine the address of the next - // available valid address below the protected region. - uintptr_t prevsp = sp; - sp = pregion; - VLOG(signals) << "setting sp to overflow region at " << std::hex << sp; - - // Since the compiler puts the implicit overflow - // check before the callee save instructions, the SP is already pointing to - // the previous frame. - VLOG(signals) << "previous frame: " << std::hex << prevsp; - - // Now establish the stack pointer for the signal return. - sc->sp = prevsp; - - // Tell the stack overflow code where the new stack pointer should be. - sc->regs[art::arm64::IP0] = sp; // aka x16 + VLOG(signals) << "Stack overflow found"; - // Now arrange for the signal handler to return to art_quick_throw_stack_overflow_from_signal. + // Now arrange for the signal handler to return to art_quick_throw_stack_overflow. // The value of LR must be the same as it was when we entered the code that // caused this fault. This will be inserted into a callee save frame by - // the function to which this handler returns (art_quick_throw_stack_overflow_from_signal). - sc->pc = reinterpret_cast<uintptr_t>(art_quick_throw_stack_overflow_from_signal); + // the function to which this handler returns (art_quick_throw_stack_overflow). + sc->pc = reinterpret_cast<uintptr_t>(art_quick_throw_stack_overflow); // The kernel will now return to the address in sc->pc. return true; diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S index 62ae099..ab9035a 100644 --- a/runtime/arch/arm64/quick_entrypoints_arm64.S +++ b/runtime/arch/arm64/quick_entrypoints_arm64.S @@ -435,31 +435,6 @@ NO_ARG_RUNTIME_EXCEPTION art_quick_throw_stack_overflow, artThrowStackOverflowFr */ ONE_ARG_RUNTIME_EXCEPTION art_quick_throw_no_such_method, artThrowNoSuchMethodFromCode - /* - * Invoke stack overflow exception from signal handler. - * On entry: - * xSELF: thread - * SP: address of last known frame - * IP0: address of next valid SP below protected region in stack - * - * This is deceptively simple but hides some complexity. It is called in the case of - * a stack overflow condition during implicit checks. The signal handler has been - * called by the kernel due to a load from the protected stack region. The handler - * works out the address of the previous frame and passes this in SP. However there - * is a piece of memory somewhere below the current SP that is not accessible (the - * memory that caused the signal). The signal handler works out the next - * accessible value of SP and passes this in x16/IP0. This code then sets up the SP - * to be this new value and calls the code to create and throw the stack overflow - * exception. - */ -ENTRY art_quick_throw_stack_overflow_from_signal - SETUP_SAVE_ALL_CALLEE_SAVE_FRAME // save all registers as basis for long jump context - mov x0, xSELF // pass Thread::Current - mov x1, sp // pass SP - mov sp, xIP0 // move SP down to below protected region. - b artThrowStackOverflowFromCode // artThrowStackOverflowFromCode(Thread*, SP) -END art_quick_throw_stack_overflow_from_signal - /* * All generated callsites for interface invokes and invocation slow paths will load arguments * as usual - except instead of loading arg0/x0 with the target Method*, arg0/x0 will contain diff --git a/runtime/arch/x86/asm_support_x86.h b/runtime/arch/x86/asm_support_x86.h index 531ed77..c9f5a25 100644 --- a/runtime/arch/x86/asm_support_x86.h +++ b/runtime/arch/x86/asm_support_x86.h @@ -20,11 +20,11 @@ #include "asm_support.h" // Offset of field Thread::self_ verified in InitCpu -#define THREAD_SELF_OFFSET 148 +#define THREAD_SELF_OFFSET 156 // Offset of field Thread::card_table_ verified in InitCpu -#define THREAD_CARD_TABLE_OFFSET 112 +#define THREAD_CARD_TABLE_OFFSET 120 // Offset of field Thread::exception_ verified in InitCpu -#define THREAD_EXCEPTION_OFFSET 116 +#define THREAD_EXCEPTION_OFFSET 124 // Offset of field Thread::thin_lock_thread_id_ verified in InitCpu #define THREAD_ID_OFFSET 12 diff --git a/runtime/arch/x86/fault_handler_x86.cc b/runtime/arch/x86/fault_handler_x86.cc index 89baded..c143c5d 100644 --- a/runtime/arch/x86/fault_handler_x86.cc +++ b/runtime/arch/x86/fault_handler_x86.cc @@ -34,7 +34,7 @@ #define CTX_ESP uc_mcontext->__ss.__rsp #define CTX_EIP uc_mcontext->__ss.__rip #define CTX_EAX uc_mcontext->__ss.__rax -#define CTX_METHOD uc_mcontext->__ss.__rax +#define CTX_METHOD uc_mcontext->__ss.__rdi #else // 32 bit mac build. #define CTX_ESP uc_mcontext->__ss.__esp @@ -71,7 +71,7 @@ extern "C" void _art_quick_test_suspend(); #define EXT_SYM(sym) _ ## sym #else extern "C" void art_quick_throw_null_pointer_exception(); -extern "C" void art_quick_throw_stack_overflow_from_signal(); +extern "C" void art_quick_throw_stack_overflow(); extern "C" void art_quick_test_suspend(); #define EXT_SYM(sym) sym #endif @@ -382,30 +382,20 @@ bool StackOverflowHandler::Action(int sig, siginfo_t* info, void* context) { uintptr_t overflow_addr = sp - GetStackOverflowReservedBytes(kX86); #endif - Thread* self = Thread::Current(); - uintptr_t pregion = reinterpret_cast<uintptr_t>(self->GetStackEnd()) - - Thread::kStackOverflowProtectedSize; - // Check that the fault address is the value expected for a stack overflow. if (fault_addr != overflow_addr) { VLOG(signals) << "Not a stack overflow"; return false; } - // We know this is a stack overflow. We need to move the sp to the overflow region - // that exists below the protected region. Determine the address of the next - // available valid address below the protected region. - VLOG(signals) << "setting sp to overflow region at " << std::hex << pregion; + VLOG(signals) << "Stack overflow found"; // Since the compiler puts the implicit overflow // check before the callee save instructions, the SP is already pointing to // the previous frame. - // Tell the stack overflow code where the new stack pointer should be. - uc->CTX_EAX = pregion; - - // Now arrange for the signal handler to return to art_quick_throw_stack_overflow_from_signal. - uc->CTX_EIP = reinterpret_cast<uintptr_t>(EXT_SYM(art_quick_throw_stack_overflow_from_signal)); + // Now arrange for the signal handler to return to art_quick_throw_stack_overflow. + uc->CTX_EIP = reinterpret_cast<uintptr_t>(art_quick_throw_stack_overflow); return true; } diff --git a/runtime/arch/x86/quick_entrypoints_x86.S b/runtime/arch/x86/quick_entrypoints_x86.S index dc4019d..75ec49d 100644 --- a/runtime/arch/x86/quick_entrypoints_x86.S +++ b/runtime/arch/x86/quick_entrypoints_x86.S @@ -174,21 +174,6 @@ NO_ARG_RUNTIME_EXCEPTION art_quick_throw_div_zero, artThrowDivZeroFromCode */ NO_ARG_RUNTIME_EXCEPTION art_quick_throw_stack_overflow, artThrowStackOverflowFromCode -// On entry to this function, EAX contains the ESP value for the overflow region. -DEFINE_FUNCTION art_quick_throw_stack_overflow_from_signal - // Here, the ESP is above the protected region. We need to create a - // callee save frame and then move ESP down to the overflow region. - SETUP_SAVE_ALL_CALLEE_SAVE_FRAME // save all registers as basis for long jump context - mov %esp, %ecx // get current stack pointer - mov %eax, %esp // move ESP to the overflow region. - PUSH ecx // pass SP - pushl %fs:THREAD_SELF_OFFSET // pass Thread::Current() - CFI_ADJUST_CFA_OFFSET(4) - SETUP_GOT_NOSAVE // clobbers ebx (harmless here) - call PLT_SYMBOL(artThrowStackOverflowFromCode) // artThrowStackOverflowFromCode(Thread*, SP) - int3 // unreached -END_FUNCTION art_quick_throw_stack_overflow_from_signal - /* * Called by managed code, saves callee saves and then calls artThrowException * that will place a mock Method* at the bottom of the stack. Arg1 holds the exception. diff --git a/runtime/arch/x86_64/asm_support_x86_64.h b/runtime/arch/x86_64/asm_support_x86_64.h index c3637ef..40958dc 100644 --- a/runtime/arch/x86_64/asm_support_x86_64.h +++ b/runtime/arch/x86_64/asm_support_x86_64.h @@ -28,11 +28,11 @@ #define RUNTIME_REF_AND_ARGS_CALLEE_SAVE_FRAME_OFFSET 16 // Offset of field Thread::self_ verified in InitCpu -#define THREAD_SELF_OFFSET 184 +#define THREAD_SELF_OFFSET 192 // Offset of field Thread::card_table_ verified in InitCpu -#define THREAD_CARD_TABLE_OFFSET 112 +#define THREAD_CARD_TABLE_OFFSET 120 // Offset of field Thread::exception_ verified in InitCpu -#define THREAD_EXCEPTION_OFFSET 120 +#define THREAD_EXCEPTION_OFFSET 128 // Offset of field Thread::thin_lock_thread_id_ verified in InitCpu #define THREAD_ID_OFFSET 12 diff --git a/runtime/arch/x86_64/quick_entrypoints_x86_64.S b/runtime/arch/x86_64/quick_entrypoints_x86_64.S index cbf0f38..48bc240 100644 --- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S +++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S @@ -284,18 +284,6 @@ NO_ARG_RUNTIME_EXCEPTION art_quick_throw_div_zero, artThrowDivZeroFromCode */ NO_ARG_RUNTIME_EXCEPTION art_quick_throw_stack_overflow, artThrowStackOverflowFromCode -// On entry to this function, RAX contains the ESP value for the overflow region. -DEFINE_FUNCTION SYMBOL(art_quick_throw_stack_overflow_from_signal) - // Here, the RSP is above the protected region. We need to create a - // callee save frame and then move RSP down to the overflow region. - SETUP_SAVE_ALL_CALLEE_SAVE_FRAME // save all registers as basis for long jump context - mov %rsp, %rsi // get current stack pointer, pass SP as second arg - mov %rax, %rsp // move RSP to the overflow region. - mov %gs:THREAD_SELF_OFFSET, %rdi // pass Thread::Current() as first arg - call PLT_SYMBOL(artThrowStackOverflowFromCode) // artThrowStackOverflowFromCode(Thread*, SP) - int3 // unreached -END_FUNCTION SYMBOL(art_quick_throw_stack_overflow_from_signal) - /* * Called by managed code, saves callee saves and then calls artThrowException * that will place a mock Method* at the bottom of the stack. Arg1 holds the exception. diff --git a/runtime/entrypoints/entrypoint_utils.cc b/runtime/entrypoints/entrypoint_utils.cc index 4755b9e..a0e35f8 100644 --- a/runtime/entrypoints/entrypoint_utils.cc +++ b/runtime/entrypoints/entrypoint_utils.cc @@ -212,6 +212,11 @@ void ThrowStackOverflowError(Thread* self) { bool explicit_overflow_check = Runtime::Current()->ExplicitStackOverflowChecks(); self->ResetDefaultStackEnd(!explicit_overflow_check); // Return to default stack size. + + // And restore protection if implicit checks are on. + if (!explicit_overflow_check) { + self->ProtectStack(); + } } void CheckReferenceResult(mirror::Object* o, Thread* self) { diff --git a/runtime/entrypoints/quick/quick_throw_entrypoints.cc b/runtime/entrypoints/quick/quick_throw_entrypoints.cc index 879010e..13decc8 100644 --- a/runtime/entrypoints/quick/quick_throw_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_throw_entrypoints.cc @@ -58,8 +58,10 @@ extern "C" void artThrowNullPointerExceptionFromCode(Thread* self, StackReference<mirror::ArtMethod>* sp) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { FinishCalleeSaveFrameSetup(self, sp, Runtime::kSaveAll); + self->NoteSignalBeingHandled(); ThrowLocation throw_location = self->GetCurrentLocationForThrow(); ThrowNullPointerExceptionFromDexPC(throw_location); + self->NoteSignalHandlerDone(); self->QuickDeliverException(); } @@ -83,7 +85,9 @@ extern "C" void artThrowArrayBoundsFromCode(int index, int length, Thread* self, extern "C" void artThrowStackOverflowFromCode(Thread* self, StackReference<mirror::ArtMethod>* sp) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { FinishCalleeSaveFrameSetup(self, sp, Runtime::kSaveAll); + self->NoteSignalBeingHandled(); ThrowStackOverflowError(self); + self->NoteSignalHandlerDone(); self->QuickDeliverException(); } diff --git a/runtime/entrypoints_order_test.cc b/runtime/entrypoints_order_test.cc index ae1b94f..f572d27 100644 --- a/runtime/entrypoints_order_test.cc +++ b/runtime/entrypoints_order_test.cc @@ -70,6 +70,10 @@ class EntrypointsOrderTest : public CommonRuntimeTest { EXPECT_OFFSET_DIFFP(Thread, tls32_, daemon, throwing_OutOfMemoryError, 4); EXPECT_OFFSET_DIFFP(Thread, tls32_, throwing_OutOfMemoryError, no_thread_suspension, 4); EXPECT_OFFSET_DIFFP(Thread, tls32_, no_thread_suspension, thread_exit_check_count, 4); + EXPECT_OFFSET_DIFFP(Thread, tls32_, thread_exit_check_count, + is_exception_reported_to_instrumentation_, 4); + EXPECT_OFFSET_DIFFP(Thread, tls32_, is_exception_reported_to_instrumentation_, + handling_signal_, 4); // TODO: Better connection. Take alignment into account. EXPECT_OFFSET_DIFF_GT3(Thread, tls32_.thread_exit_check_count, tls64_.trace_clock_base, 4, diff --git a/runtime/instruction_set.cc b/runtime/instruction_set.cc index d7e358c..d8a38f4 100644 --- a/runtime/instruction_set.cc +++ b/runtime/instruction_set.cc @@ -87,11 +87,10 @@ 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; +static constexpr size_t kArmStackOverflowReservedBytes = 8 * KB; +static constexpr size_t kArm64StackOverflowReservedBytes = 8 * KB; +static constexpr size_t kX86StackOverflowReservedBytes = 8 * KB; +static constexpr size_t kX86_64StackOverflowReservedBytes = 8 * KB; size_t GetStackOverflowReservedBytes(InstructionSet isa) { switch (isa) { diff --git a/runtime/mirror/art_method.cc b/runtime/mirror/art_method.cc index 8eacb1c..e168fb2 100644 --- a/runtime/mirror/art_method.cc +++ b/runtime/mirror/art_method.cc @@ -283,6 +283,11 @@ uint32_t ArtMethod::FindCatchBlock(Handle<ArtMethod> h_this, Handle<Class> excep void ArtMethod::Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* result, const char* shorty) { + if (UNLIKELY(__builtin_frame_address(0) < self->GetStackEnd())) { + ThrowStackOverflowError(self); + return; + } + if (kIsDebugBuild) { self->AssertThreadSuspensionIsAllowable(); CHECK_EQ(kRunnable, self->GetState()); diff --git a/runtime/oat.cc b/runtime/oat.cc index 0a8c35b..ede108c 100644 --- a/runtime/oat.cc +++ b/runtime/oat.cc @@ -23,7 +23,7 @@ namespace art { const uint8_t OatHeader::kOatMagic[] = { 'o', 'a', 't', '\n' }; -const uint8_t OatHeader::kOatVersion[] = { '0', '3', '8', '\0' }; +const uint8_t OatHeader::kOatVersion[] = { '0', '3', '9', '\0' }; static size_t ComputeOatHeaderSize(const SafeMap<std::string, std::string>* variable_data) { size_t estimate = 0U; diff --git a/runtime/reflection.cc b/runtime/reflection.cc index 0169ccc..a2209e3 100644 --- a/runtime/reflection.cc +++ b/runtime/reflection.cc @@ -446,6 +446,14 @@ static void InvokeWithArgArray(const ScopedObjectAccessAlreadyRunnable& soa, JValue InvokeWithVarArgs(const ScopedObjectAccessAlreadyRunnable& soa, jobject obj, jmethodID mid, va_list args) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + // We want to make sure that the stack is not within a small distance from the + // protected region in case we are calling into a leaf function whose stack + // check has been elided. + if (UNLIKELY(__builtin_frame_address(0) < soa.Self()->GetStackEnd())) { + ThrowStackOverflowError(soa.Self()); + return JValue(); + } + mirror::ArtMethod* method = soa.DecodeMethod(mid); mirror::Object* receiver = method->IsStatic() ? nullptr : soa.Decode<mirror::Object*>(obj); uint32_t shorty_len = 0; @@ -459,6 +467,14 @@ JValue InvokeWithVarArgs(const ScopedObjectAccessAlreadyRunnable& soa, jobject o JValue InvokeWithJValues(const ScopedObjectAccessAlreadyRunnable& soa, mirror::Object* receiver, jmethodID mid, jvalue* args) { + // We want to make sure that the stack is not within a small distance from the + // protected region in case we are calling into a leaf function whose stack + // check has been elided. + if (UNLIKELY(__builtin_frame_address(0) < soa.Self()->GetStackEnd())) { + ThrowStackOverflowError(soa.Self()); + return JValue(); + } + mirror::ArtMethod* method = soa.DecodeMethod(mid); uint32_t shorty_len = 0; const char* shorty = method->GetShorty(&shorty_len); @@ -471,6 +487,14 @@ JValue InvokeWithJValues(const ScopedObjectAccessAlreadyRunnable& soa, mirror::O JValue InvokeVirtualOrInterfaceWithJValues(const ScopedObjectAccessAlreadyRunnable& soa, mirror::Object* receiver, jmethodID mid, jvalue* args) { + // We want to make sure that the stack is not within a small distance from the + // protected region in case we are calling into a leaf function whose stack + // check has been elided. + if (UNLIKELY(__builtin_frame_address(0) < soa.Self()->GetStackEnd())) { + ThrowStackOverflowError(soa.Self()); + return JValue(); + } + mirror::ArtMethod* method = FindVirtualMethod(receiver, soa.DecodeMethod(mid)); uint32_t shorty_len = 0; const char* shorty = method->GetShorty(&shorty_len); @@ -483,6 +507,14 @@ JValue InvokeVirtualOrInterfaceWithJValues(const ScopedObjectAccessAlreadyRunnab JValue InvokeVirtualOrInterfaceWithVarArgs(const ScopedObjectAccessAlreadyRunnable& soa, jobject obj, jmethodID mid, va_list args) { + // We want to make sure that the stack is not within a small distance from the + // protected region in case we are calling into a leaf function whose stack + // check has been elided. + if (UNLIKELY(__builtin_frame_address(0) < soa.Self()->GetStackEnd())) { + ThrowStackOverflowError(soa.Self()); + return JValue(); + } + mirror::Object* receiver = soa.Decode<mirror::Object*>(obj); mirror::ArtMethod* method = FindVirtualMethod(receiver, soa.DecodeMethod(mid)); uint32_t shorty_len = 0; @@ -496,6 +528,14 @@ JValue InvokeVirtualOrInterfaceWithVarArgs(const ScopedObjectAccessAlreadyRunnab void InvokeWithShadowFrame(Thread* self, ShadowFrame* shadow_frame, uint16_t arg_offset, MethodHelper& mh, JValue* result) { + // We want to make sure that the stack is not within a small distance from the + // protected region in case we are calling into a leaf function whose stack + // check has been elided. + if (UNLIKELY(__builtin_frame_address(0) < self->GetStackEnd())) { + ThrowStackOverflowError(self); + return; + } + ArgArray arg_array(mh.GetShorty(), mh.GetShortyLength()); arg_array.BuildArgArrayFromFrame(shadow_frame, arg_offset); shadow_frame->GetMethod()->Invoke(self, arg_array.GetArray(), arg_array.GetNumBytes(), result, @@ -504,6 +544,15 @@ void InvokeWithShadowFrame(Thread* self, ShadowFrame* shadow_frame, uint16_t arg jobject InvokeMethod(const ScopedObjectAccessAlreadyRunnable& soa, jobject javaMethod, jobject javaReceiver, jobject javaArgs, bool accessible) { + // We want to make sure that the stack is not within a small distance from the + // protected region in case we are calling into a leaf function whose stack + // check has been elided. + if (UNLIKELY(__builtin_frame_address(0) < + soa.Self()->GetStackEndForInterpreter(true))) { + ThrowStackOverflowError(soa.Self()); + return nullptr; + } + mirror::ArtMethod* m = mirror::ArtMethod::FromReflectedMethod(soa, javaMethod); mirror::Class* declaring_class = m->GetDeclaringClass(); diff --git a/runtime/thread.cc b/runtime/thread.cc index 8e6da74..7aabfce 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -76,8 +76,7 @@ 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); +const size_t Thread::kStackOverflowImplicitCheckSize = GetStackOverflowReservedBytes(kRuntimeISA); static const char* kThreadNameDuringStartup = "<native thread without managed peer>"; @@ -238,92 +237,48 @@ static size_t FixStackSize(size_t stack_size) { byte dont_optimize_this; // Install a protected region in the stack. This is used to trigger a SIGSEGV if a stack -// overflow is detected. It is located right below the stack_end_. Just below that -// is the StackOverflow reserved region used when creating the StackOverflow -// exception. +// overflow is detected. It is located right below the stack_begin_. // -// There is a little complexity here that deserves a special mention. When running on the -// host (glibc), the process's main thread's stack is allocated with a special flag +// There is a little complexity here that deserves a special mention. On some +// architectures, the stack created using a VM_GROWSDOWN flag // to prevent memory being allocated when it's not needed. This flag makes the // kernel only allocate memory for the stack by growing down in memory. Because we // want to put an mprotected region far away from that at the stack top, we need // to make sure the pages for the stack are mapped in before we call mprotect. We do // this by reading every page from the stack bottom (highest address) to the stack top. // We then madvise this away. -void Thread::InstallImplicitProtection(bool is_main_stack) { - byte* pregion = tlsPtr_.stack_end; - byte* stack_lowmem = tlsPtr_.stack_begin; - byte* stack_top = reinterpret_cast<byte*>(reinterpret_cast<uintptr_t>(&pregion) & +void Thread::InstallImplicitProtection() { + byte* pregion = tlsPtr_.stack_begin - kStackOverflowProtectedSize; + byte* stack_himem = tlsPtr_.stack_end; + byte* stack_top = reinterpret_cast<byte*>(reinterpret_cast<uintptr_t>(&stack_himem) & ~(kPageSize - 1)); // Page containing current top of stack. - const bool running_on_intel = (kRuntimeISA == kX86) || (kRuntimeISA == kX86_64); - - if (running_on_intel) { - // On Intel, we need to map in the main stack. This must be done by reading from the - // current stack pointer downwards as the stack is mapped using VM_GROWSDOWN - // in the kernel. Any access more than a page below the current SP will cause - // a segv. - if (is_main_stack) { - // First we need to unprotect the protected region because this may - // be called more than once for a particular stack and we will crash - // if we try to read the protected page. - mprotect(pregion - kStackOverflowProtectedSize, kStackOverflowProtectedSize, PROT_READ); - - // Read every page from the high address to the low. - for (byte* p = stack_top; p > stack_lowmem; p -= kPageSize) { - dont_optimize_this = *p; - } - } - } + // First remove the protection on the protected region as will want to read and + // write it. This may fail (on the first attempt when the stack is not mapped) + // but we ignore that. + UnprotectStack(); - // Check and place a marker word at the lowest usable address in the stack. This - // is used to prevent a double protection. - constexpr uint32_t kMarker = 0xdadadada; - uintptr_t *marker = reinterpret_cast<uintptr_t*>(pregion); - if (*marker == kMarker) { - // The region has already been set up. But on the main stack on the host we have - // removed the protected region in order to read the stack memory. We need to put - // this back again. - if (is_main_stack && running_on_intel) { - mprotect(pregion - kStackOverflowProtectedSize, kStackOverflowProtectedSize, PROT_NONE); - madvise(stack_lowmem, stack_top - stack_lowmem, MADV_DONTNEED); - } - return; - } - // Add marker so that we can detect a second attempt to do this. - *marker = kMarker; + // Map in the stack. This must be done by reading from the + // current stack pointer downwards as the stack may be mapped using VM_GROWSDOWN + // in the kernel. Any access more than a page below the current SP might cause + // a segv. - if (!running_on_intel) { - // Running on !Intel, stacks are mapped cleanly. The protected region for the - // main stack just needs to be mapped in. We do this by writing one byte per page. - for (byte* p = pregion - kStackOverflowProtectedSize; p < pregion; p += kPageSize) { - *p = 0; - } + // Read every page from the high address to the low. + for (byte* p = stack_top; p > pregion; p -= kPageSize) { + dont_optimize_this = *p; } - pregion -= kStackOverflowProtectedSize; - VLOG(threads) << "installing stack protected region at " << std::hex << static_cast<void*>(pregion) << " to " << static_cast<void*>(pregion + kStackOverflowProtectedSize - 1); - - if (mprotect(pregion, kStackOverflowProtectedSize, PROT_NONE) == -1) { - LOG(FATAL) << "Unable to create protected region in stack for implicit overflow check. Reason:" - << strerror(errno) << kStackOverflowProtectedSize; - } + // Protect the bottom of the stack to prevent read/write to it. + ProtectStack(); // Tell the kernel that we won't be needing these pages any more. // NB. madvise will probably write zeroes into the memory (on linux it does). - if (is_main_stack) { - if (running_on_intel) { - // On the host, it's the whole stack (minus a page to prevent overwrite of stack top). - madvise(stack_lowmem, stack_top - stack_lowmem - kPageSize, MADV_DONTNEED); - } else { - // On Android, just the protected region. - madvise(pregion, kStackOverflowProtectedSize, MADV_DONTNEED); - } - } + uint32_t unwanted_size = stack_top - pregion - kPageSize; + madvise(pregion, unwanted_size, MADV_DONTNEED); } void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) { @@ -538,7 +493,13 @@ void Thread::InitStackHwm() { tlsPtr_.stack_begin = reinterpret_cast<byte*>(read_stack_base); tlsPtr_.stack_size = read_stack_size; - if (read_stack_size <= GetStackOverflowReservedBytes(kRuntimeISA)) { + // The minimum stack size we can cope with is the overflow reserved bytes (typically + // 8K) + the protected region size (4K) + another page (4K). Typically this will + // be 8+4+4 = 16K. The thread won't be able to do much with this stack even the GC takes + // between 8K and 12K. + uint32_t min_stack = GetStackOverflowReservedBytes(kRuntimeISA) + kStackOverflowProtectedSize + + 4 * KB; + if (read_stack_size <= min_stack) { LOG(FATAL) << "Attempt to attach a thread with a too-small stack (" << read_stack_size << " bytes)"; } @@ -582,20 +543,19 @@ void Thread::InitStackHwm() { // Install the protected region if we are doing implicit overflow checks. if (implicit_stack_check) { - if (is_main_thread) { - size_t guardsize; - pthread_attr_t attributes; - CHECK_PTHREAD_CALL(pthread_attr_init, (&attributes), "guard size query"); - CHECK_PTHREAD_CALL(pthread_attr_getguardsize, (&attributes, &guardsize), "guard size query"); - CHECK_PTHREAD_CALL(pthread_attr_destroy, (&attributes), "guard size query"); - // The main thread might have protected region at the bottom. We need - // to install our own region so we need to move the limits - // of the stack to make room for it. - tlsPtr_.stack_begin += guardsize; - tlsPtr_.stack_end += guardsize; - tlsPtr_.stack_size -= guardsize; - } - InstallImplicitProtection(is_main_thread); + size_t guardsize; + pthread_attr_t attributes; + CHECK_PTHREAD_CALL(pthread_attr_init, (&attributes), "guard size query"); + CHECK_PTHREAD_CALL(pthread_attr_getguardsize, (&attributes, &guardsize), "guard size query"); + CHECK_PTHREAD_CALL(pthread_attr_destroy, (&attributes), "guard size query"); + // The thread might have protected region at the bottom. We need + // to install our own region so we need to move the limits + // of the stack to make room for it. + tlsPtr_.stack_begin += guardsize; + tlsPtr_.stack_end += guardsize; + tlsPtr_.stack_size -= guardsize; + + InstallImplicitProtection(); } // Sanity check. @@ -2266,6 +2226,14 @@ void Thread::SetStackEndForStackOverflow() { } tlsPtr_.stack_end = tlsPtr_.stack_begin; + + // Remove the stack overflow protection if is it set up. + bool implicit_stack_check = !Runtime::Current()->ExplicitStackOverflowChecks(); + if (implicit_stack_check) { + if (!UnprotectStack()) { + LOG(ERROR) << "Unable to remove stack protection for stack overflow"; + } + } } void Thread::SetTlab(byte* start, byte* end) { @@ -2291,4 +2259,21 @@ std::ostream& operator<<(std::ostream& os, const Thread& thread) { return os; } +void Thread::ProtectStack() { + void* pregion = tlsPtr_.stack_begin - kStackOverflowProtectedSize; + VLOG(threads) << "Protecting stack at " << pregion; + if (mprotect(pregion, kStackOverflowProtectedSize, PROT_NONE) == -1) { + LOG(FATAL) << "Unable to create protected region in stack for implicit overflow check. " + "Reason: " + << strerror(errno) << " size: " << kStackOverflowProtectedSize; + } +} + +bool Thread::UnprotectStack() { + void* pregion = tlsPtr_.stack_begin - kStackOverflowProtectedSize; + VLOG(threads) << "Unprotecting stack at " << pregion; + return mprotect(pregion, kStackOverflowProtectedSize, PROT_READ|PROT_WRITE) == 0; +} + + } // namespace art diff --git a/runtime/thread.h b/runtime/thread.h index c2b200b..120ff6f 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -94,16 +94,41 @@ enum ThreadFlag { static constexpr size_t kNumRosAllocThreadLocalSizeBrackets = 34; +// Thread's stack layout for implicit stack overflow checks: +// +// +---------------------+ <- highest address of stack memory +// | | +// . . <- SP +// | | +// | | +// +---------------------+ <- stack_end +// | | +// | Gap | +// | | +// +---------------------+ <- stack_begin +// | | +// | Protected region | +// | | +// +---------------------+ <- lowest address of stack memory +// +// The stack always grows down in memory. At the lowest address is a region of memory +// that is set mprotect(PROT_NONE). Any attempt to read/write to this region will +// result in a segmentation fault signal. At any point, the thread's SP will be somewhere +// between the stack_end and the highest address in stack memory. An implicit stack +// overflow check is a read of memory at a certain offset below the current SP (4K typically). +// If the thread's SP is below the stack_end address this will be a read into the protected +// region. If the SP is above the stack_end address, the thread is guaranteed to have +// at least 4K of space. Because stack overflow checks are only performed in generated code, +// if the thread makes a call out to a native function (through JNI), that native function +// might only have 4K of memory (if the SP is adjacent to stack_end). + class Thread { public: - // How much of the reserved bytes is reserved for incoming signals. - static constexpr size_t kStackOverflowSignalReservedBytes = 2 * KB; - // For implicit overflow checks we reserve an extra piece of memory at the bottom // 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 = 16 * KB; + static constexpr size_t kStackOverflowProtectedSize = 4 * KB; static const size_t kStackOverflowImplicitCheckSize; // Creates a new native thread corresponding to the given managed peer. @@ -582,7 +607,7 @@ class Thread { } // Install the protected region for implicit stack checks. - void InstallImplicitProtection(bool is_main_stack); + void InstallImplicitProtection(); bool IsHandlingStackOverflow() const { return tlsPtr_.stack_end == tlsPtr_.stack_begin; @@ -814,6 +839,20 @@ class Thread { tls32_.is_exception_reported_to_instrumentation_ = reported; } + void ProtectStack(); + bool UnprotectStack(); + + void NoteSignalBeingHandled() { + if (tls32_.handling_signal_) { + LOG(FATAL) << "Detected signal while processing a signal"; + } + tls32_.handling_signal_ = true; + } + + void NoteSignalHandlerDone() { + tls32_.handling_signal_ = false; + } + private: explicit Thread(bool daemon); ~Thread() LOCKS_EXCLUDED(Locks::mutator_lock_, @@ -918,7 +957,8 @@ class Thread { explicit tls_32bit_sized_values(bool is_daemon) : suspend_count(0), debug_suspend_count(0), thin_lock_thread_id(0), tid(0), daemon(is_daemon), throwing_OutOfMemoryError(false), no_thread_suspension(0), - thread_exit_check_count(0), is_exception_reported_to_instrumentation_(false) { + thread_exit_check_count(0), is_exception_reported_to_instrumentation_(false), + handling_signal_(false), padding_(0) { } union StateAndFlags state_and_flags; @@ -958,6 +998,12 @@ class Thread { // When true this field indicates that the exception associated with this thread has already // been reported to instrumentation. bool32_t is_exception_reported_to_instrumentation_; + + // True if signal is being handled by this thread. + bool32_t handling_signal_; + + // Padding to make the size aligned to 8. Remove this if we add another 32 bit field. + int32_t padding_; } tls32_; struct PACKED(8) tls_64bit_sized_values { |