diff options
Diffstat (limited to 'runtime/fault_handler.cc')
-rw-r--r-- | runtime/fault_handler.cc | 373 |
1 files changed, 169 insertions, 204 deletions
diff --git a/runtime/fault_handler.cc b/runtime/fault_handler.cc index 684e81a61f..f8bd213d53 100644 --- a/runtime/fault_handler.cc +++ b/runtime/fault_handler.cc @@ -16,14 +16,12 @@ #include "fault_handler.h" -#include <atomic> #include <string.h> #include <sys/mman.h> #include <sys/ucontext.h> #include "art_method-inl.h" #include "base/logging.h" // For VLOG -#include "base/membarrier.h" #include "base/safe_copy.h" #include "base/stl_util.h" #include "dex/dex_file_types.h" @@ -53,16 +51,82 @@ static bool art_fault_handler(int sig, siginfo_t* info, void* context) { return fault_manager.HandleFault(sig, info, context); } -struct FaultManager::GeneratedCodeRange { - std::atomic<GeneratedCodeRange*> next; - const void* start; - size_t size; -}; +#if defined(__linux__) -FaultManager::FaultManager() - : generated_code_ranges_lock_("FaultHandler generated code ranges lock", - LockLevel::kGenericBottomLock), - initialized_(false) { +// Change to verify the safe implementations against the original ones. +constexpr bool kVerifySafeImpls = false; + +// Provide implementations of ArtMethod::GetDeclaringClass and VerifyClassClass that use SafeCopy +// to safely dereference pointers which are potentially garbage. +// Only available on Linux due to availability of SafeCopy. + +static mirror::Class* SafeGetDeclaringClass(ArtMethod* method) + REQUIRES_SHARED(Locks::mutator_lock_) { + char* method_declaring_class = + reinterpret_cast<char*>(method) + ArtMethod::DeclaringClassOffset().SizeValue(); + + // ArtMethod::declaring_class_ is a GcRoot<mirror::Class>. + // Read it out into as a CompressedReference directly for simplicity's sake. + mirror::CompressedReference<mirror::Class> cls; + ssize_t rc = SafeCopy(&cls, method_declaring_class, sizeof(cls)); + CHECK_NE(-1, rc); + + if (kVerifySafeImpls) { + ObjPtr<mirror::Class> actual_class = method->GetDeclaringClassUnchecked<kWithoutReadBarrier>(); + CHECK_EQ(actual_class, cls.AsMirrorPtr()); + } + + if (rc != sizeof(cls)) { + return nullptr; + } + + return cls.AsMirrorPtr(); +} + +static mirror::Class* SafeGetClass(mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_) { + char* obj_cls = reinterpret_cast<char*>(obj) + mirror::Object::ClassOffset().SizeValue(); + + mirror::HeapReference<mirror::Class> cls; + ssize_t rc = SafeCopy(&cls, obj_cls, sizeof(cls)); + CHECK_NE(-1, rc); + + if (kVerifySafeImpls) { + mirror::Class* actual_class = obj->GetClass<kVerifyNone>(); + CHECK_EQ(actual_class, cls.AsMirrorPtr()); + } + + if (rc != sizeof(cls)) { + return nullptr; + } + + return cls.AsMirrorPtr(); +} + +static bool SafeVerifyClassClass(mirror::Class* cls) REQUIRES_SHARED(Locks::mutator_lock_) { + mirror::Class* c_c = SafeGetClass(cls); + bool result = c_c != nullptr && c_c == SafeGetClass(c_c); + + if (kVerifySafeImpls) { + CHECK_EQ(VerifyClassClass(cls), result); + } + + return result; +} + +#else + +static mirror::Class* SafeGetDeclaringClass(ArtMethod* method_obj) + REQUIRES_SHARED(Locks::mutator_lock_) { + return method_obj->GetDeclaringClassUnchecked<kWithoutReadBarrier>().Ptr(); +} + +static bool SafeVerifyClassClass(mirror::Class* cls) REQUIRES_SHARED(Locks::mutator_lock_) { + return VerifyClassClass(cls); +} +#endif + + +FaultManager::FaultManager() : initialized_(false) { sigaction(SIGSEGV, nullptr, &oldaction_); } @@ -86,14 +150,6 @@ void FaultManager::Init() { }; AddSpecialSignalHandlerFn(SIGSEGV, &sa); - - // Notify the kernel that we intend to use a specific `membarrier()` command. - int result = art::membarrier(MembarrierCommand::kRegisterPrivateExpedited); - if (result != 0) { - LOG(WARNING) << "FaultHandler: MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED failed: " - << errno << " " << strerror(errno); - } - initialized_ = true; } @@ -111,20 +167,6 @@ void FaultManager::Shutdown() { // Free all handlers. STLDeleteElements(&generated_code_handlers_); STLDeleteElements(&other_handlers_); - - // Delete remaining code ranges if any (such as nterp code or oat code from - // oat files that have not been unloaded, including boot image oat files). - GeneratedCodeRange* range; - { - MutexLock lock(Thread::Current(), generated_code_ranges_lock_); - range = generated_code_ranges_.load(std::memory_order_acquire); - generated_code_ranges_.store(nullptr, std::memory_order_release); - } - while (range != nullptr) { - GeneratedCodeRange* next_range = range->next.load(std::memory_order_relaxed); - delete range; - range = next_range; - } } } @@ -179,7 +221,7 @@ bool FaultManager::HandleFault(int sig, siginfo_t* info, void* context) { raise(SIGSEGV); #endif - if (IsInGeneratedCode(info, context)) { + if (IsInGeneratedCode(info, context, true)) { VLOG(signals) << "in generated code, looking for handler"; for (const auto& handler : generated_code_handlers_) { VLOG(signals) << "invoking Action on handler " << handler; @@ -226,95 +268,37 @@ void FaultManager::RemoveHandler(FaultHandler* handler) { LOG(FATAL) << "Attempted to remove non existent handler " << handler; } -void FaultManager::AddGeneratedCodeRange(const void* start, size_t size) { - GeneratedCodeRange* new_range = new GeneratedCodeRange{nullptr, start, size}; - { - MutexLock lock(Thread::Current(), generated_code_ranges_lock_); - GeneratedCodeRange* old_head = generated_code_ranges_.load(std::memory_order_relaxed); - new_range->next.store(old_head, std::memory_order_relaxed); - generated_code_ranges_.store(new_range, std::memory_order_release); - } - - // The above release operation on `generated_code_ranges_` with an acquire operation - // on the same atomic object in `IsInGeneratedCode()` ensures the correct memory - // visibility for the contents of `*new_range` for any thread that loads the value - // written above (or a value written by a release sequence headed by that write). - // - // However, we also need to ensure that any thread that encounters a segmentation - // fault in the provided range shall actually see the written value. For JIT code - // cache and nterp, the registration happens while the process is single-threaded - // but the synchronization is more complicated for code in oat files. - // - // Threads that load classes register dex files under the `Locks::dex_lock_` and - // the first one to register a dex file with a given oat file shall add the oat - // code range; the memory visibility for these threads is guaranteed by the lock. - // However a thread that did not try to load a class with oat code can execute the - // code if a direct or indirect reference to such class escapes from one of the - // threads that loaded it. Use `membarrier()` for memory visibility in this case. - art::membarrier(MembarrierCommand::kPrivateExpedited); -} +static bool IsKnownPc(uintptr_t pc, ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) { + // Check whether the pc is within nterp range. + if (OatQuickMethodHeader::IsNterpPc(pc)) { + return true; + } -void FaultManager::RemoveGeneratedCodeRange(const void* start, size_t size) { - Thread* self = Thread::Current(); - GeneratedCodeRange* range = nullptr; - { - MutexLock lock(self, generated_code_ranges_lock_); - std::atomic<GeneratedCodeRange*>* before = &generated_code_ranges_; - range = before->load(std::memory_order_relaxed); - while (range != nullptr && range->start != start) { - before = &range->next; - range = before->load(std::memory_order_relaxed); - } - if (range != nullptr) { - GeneratedCodeRange* next = range->next.load(std::memory_order_relaxed); - if (before == &generated_code_ranges_) { - // Relaxed store directly to `generated_code_ranges_` would not satisfy - // conditions for a release sequence, so we need to use store-release. - before->store(next, std::memory_order_release); - } else { - // In the middle of the list, we can use a relaxed store as we're not - // publishing any newly written memory to potential reader threads. - // Whether they see the removed node or not is unimportant as we should - // not execute that code anymore. We're keeping the `next` link of the - // removed node, so that concurrent walk can use it to reach remaining - // retained nodes, if any. - before->store(next, std::memory_order_relaxed); - } - } + // Check whether the pc is in the JIT code cache. + jit::Jit* jit = Runtime::Current()->GetJit(); + if (jit != nullptr && jit->GetCodeCache()->ContainsPc(reinterpret_cast<const void*>(pc))) { + return true; } - CHECK(range != nullptr); - DCHECK_EQ(range->start, start); - CHECK_EQ(range->size, size); - - Runtime* runtime = Runtime::Current(); - CHECK(runtime != nullptr); - if (runtime->IsStarted() && runtime->GetThreadList() != nullptr) { - // Run a checkpoint before deleting the range to ensure that no thread holds a - // pointer to the removed range while walking the list in `IsInGeneratedCode()`. - // That walk is guarded by checking that the thread is `Runnable`, so any walk - // started before the removal shall be done when running the checkpoint and the - // checkpoint also ensures the correct memory visibility of `next` links, - // so the thread shall not see the pointer during future walks. - - // This function is currently called in different mutex and thread states. - // Semi-space GC can hold the mutator lock exclusively or shared and any GC - // may hold the mutator lock without actually being marked as Runnable. - // TODO: Clean up state transitions in different GC implementations. b/259440389 - if (Locks::mutator_lock_->IsExclusiveHeld(self)) { - // We do not need a checkpoint because no other thread is Runnable. - } else { - // Run a checkpoint with mutator lock held (shared). - DCHECK(Locks::mutator_lock_->IsSharedHeld(self)); - runtime->GetThreadList()->RunEmptyCheckpoint(); - } + + if (method->IsObsolete()) { + // Obsolete methods never happen on AOT code. + return false; } - delete range; + + // Note: at this point, we trust it's truly an ArtMethod we found at the bottom of the stack, + // and we can find its oat file through it. + const OatDexFile* oat_dex_file = method->GetDeclaringClass()->GetDexFile().GetOatDexFile(); + if (oat_dex_file != nullptr && + oat_dex_file->GetOatFile()->Contains(reinterpret_cast<const void*>(pc))) { + return true; + } + + return false; } -// This function is called within the signal handler. It checks that the thread -// is `Runnable`, the `mutator_lock_` is held (shared) and the fault PC is in one -// of the registered generated code ranges. No annotalysis is done. -bool FaultManager::IsInGeneratedCode(siginfo_t* siginfo, void* context) { +// This function is called within the signal handler. It checks that +// the mutator_lock is held (shared). No annotalysis is done. +bool FaultManager::IsInGeneratedCode(siginfo_t* siginfo, void* context, bool check_dex_pc) { // We can only be running Java code in the current thread if it // is in Runnable state. VLOG(signals) << "Checking for generated code"; @@ -337,109 +321,86 @@ bool FaultManager::IsInGeneratedCode(siginfo_t* siginfo, void* context) { return false; } - uintptr_t fault_pc = GetFaultPc(siginfo, context); - if (fault_pc == 0u) { - VLOG(signals) << "no fault PC"; + ArtMethod* method_obj = nullptr; + uintptr_t return_pc = 0; + uintptr_t sp = 0; + bool is_stack_overflow = false; + + // Get the architecture specific method address and return address. These + // are in architecture specific files in arch/<arch>/fault_handler_<arch>. + GetMethodAndReturnPcAndSp(siginfo, context, &method_obj, &return_pc, &sp, &is_stack_overflow); + + // If we don't have a potential method, we're outta here. + VLOG(signals) << "potential method: " << method_obj; + // TODO: Check linear alloc and image. + DCHECK_ALIGNED(ArtMethod::Size(kRuntimePointerSize), sizeof(void*)) + << "ArtMethod is not pointer aligned"; + if (method_obj == nullptr || !IsAligned<sizeof(void*)>(method_obj)) { + VLOG(signals) << "no method"; return false; } - // Walk over the list of registered code ranges. - GeneratedCodeRange* range = generated_code_ranges_.load(std::memory_order_acquire); - while (range != nullptr) { - if (fault_pc - reinterpret_cast<uintptr_t>(range->start) < range->size) { - return true; - } - // We may or may not see ranges that were concurrently removed, depending - // on when the relaxed writes of the `next` links become visible. However, - // even if we're currently at a node that is being removed, we shall visit - // all remaining ranges that are not being removed as the removed nodes - // retain the `next` link at the time of removal (which may lead to other - // removed nodes before reaching remaining retained nodes, if any). Correct - // memory visibility of `start` and `size` fields of the visited ranges is - // ensured by the release and acquire operations on `generated_code_ranges_`. - range = range->next.load(std::memory_order_relaxed); - } - return false; -} - -FaultHandler::FaultHandler(FaultManager* manager) : manager_(manager) { -} - -// -// Null pointer fault handler -// -NullPointerHandler::NullPointerHandler(FaultManager* manager) : FaultHandler(manager) { - manager_->AddHandler(this, true); -} - -bool NullPointerHandler::IsValidMethod(ArtMethod* method) { - // At this point we know that the thread is `Runnable` and the PC is in one of - // the registered code ranges. The `method` was read from the top of the stack - // and should really point to an actual `ArtMethod`, unless we're crashing during - // prologue or epilogue, or somehow managed to jump to the compiled code by some - // unexpected path, other than method invoke or exception delivery. We do a few - // quick checks without guarding from another fault. - VLOG(signals) << "potential method: " << method; - - static_assert(IsAligned<sizeof(void*)>(ArtMethod::Size(kRuntimePointerSize))); - if (method == nullptr || !IsAligned<sizeof(void*)>(method)) { - VLOG(signals) << ((method == nullptr) ? "null method" : "unaligned method"); + // Verify that the potential method is indeed a method. + // TODO: check the GC maps to make sure it's an object. + // Check that the class pointer inside the object is not null and is aligned. + // No read barrier because method_obj may not be a real object. + mirror::Class* cls = SafeGetDeclaringClass(method_obj); + if (cls == nullptr) { + VLOG(signals) << "not a class"; return false; } - // Check that the presumed method actually points to a class. Read barriers - // are not needed (and would be undesirable in a signal handler) when reading - // a chain of constant references to get to a non-movable `Class.class` object. - - // Note: Allowing nested faults. Checking that the method is in one of the - // `LinearAlloc` spaces, or that objects we look at are in the `Heap` would be - // slow and require locking a mutex, which is undesirable in a signal handler. - // (Though we could register valid ranges similarly to the generated code ranges.) - - mirror::Object* klass = - method->GetDeclaringClassAddressWithoutBarrier()->AsMirrorPtr(); - if (klass == nullptr || !IsAligned<kObjectAlignment>(klass)) { - VLOG(signals) << ((klass == nullptr) ? "null class" : "unaligned class"); + if (!IsAligned<kObjectAlignment>(cls)) { + VLOG(signals) << "not aligned"; return false; } - mirror::Class* class_class = klass->GetClass<kVerifyNone, kWithoutReadBarrier>(); - if (class_class == nullptr || !IsAligned<kObjectAlignment>(class_class)) { - VLOG(signals) << ((klass == nullptr) ? "null class_class" : "unaligned class_class"); + if (!SafeVerifyClassClass(cls)) { + VLOG(signals) << "not a class class"; return false; } - if (class_class != class_class->GetClass<kVerifyNone, kWithoutReadBarrier>()) { - VLOG(signals) << "invalid class_class"; + if (!IsKnownPc(return_pc, method_obj)) { + VLOG(signals) << "PC not in Java code"; return false; } - return true; -} + const OatQuickMethodHeader* method_header = method_obj->GetOatQuickMethodHeader(return_pc); -bool NullPointerHandler::IsValidReturnPc(ArtMethod** sp, uintptr_t return_pc) { - // Check if we can associate a dex PC with the return PC, whether from Nterp, - // or with an existing stack map entry for a compiled method. - // Note: Allowing nested faults if `IsValidMethod()` returned a false positive. - // Note: The `ArtMethod::GetOatQuickMethodHeader()` can acquire locks (at least - // `Locks::jit_lock_`) and if the thread already held such a lock, the signal - // handler would deadlock. However, if a thread is holding one of the locks - // below the mutator lock, the PC should be somewhere in ART code and should - // not match any registered generated code range, so such as a deadlock is - // unlikely. If it happens anyway, the worst case is that an internal ART crash - // would be reported as ANR. - ArtMethod* method = *sp; - const OatQuickMethodHeader* method_header = method->GetOatQuickMethodHeader(return_pc); if (method_header == nullptr) { - VLOG(signals) << "No method header."; + VLOG(signals) << "no compiled code"; return false; } - VLOG(signals) << "looking for dex pc for return pc 0x" << std::hex << return_pc - << " pc offset: 0x" << std::hex - << (return_pc - reinterpret_cast<uintptr_t>(method_header->GetEntryPoint())); - uint32_t dexpc = method_header->ToDexPc(reinterpret_cast<ArtMethod**>(sp), return_pc, false); + + // We can be certain that this is a method now. Check if we have a GC map + // at the return PC address. + if (true || kIsDebugBuild) { + VLOG(signals) << "looking for dex pc for return pc " << std::hex << return_pc; + uint32_t sought_offset = return_pc - + reinterpret_cast<uintptr_t>(method_header->GetEntryPoint()); + VLOG(signals) << "pc offset: " << std::hex << sought_offset; + } + uint32_t dexpc = dex::kDexNoIndex; + if (is_stack_overflow) { + // If it's an implicit stack overflow check, the frame is not setup, so we + // just infer the dex PC as zero. + dexpc = 0; + } else { + CHECK_EQ(*reinterpret_cast<ArtMethod**>(sp), method_obj); + dexpc = method_header->ToDexPc(reinterpret_cast<ArtMethod**>(sp), return_pc, false); + } VLOG(signals) << "dexpc: " << dexpc; - return dexpc != dex::kDexNoIndex; + return !check_dex_pc || dexpc != dex::kDexNoIndex; +} + +FaultHandler::FaultHandler(FaultManager* manager) : manager_(manager) { +} + +// +// Null pointer fault handler +// +NullPointerHandler::NullPointerHandler(FaultManager* manager) : FaultHandler(manager) { + manager_->AddHandler(this, true); } // @@ -465,13 +426,17 @@ JavaStackTraceHandler::JavaStackTraceHandler(FaultManager* manager) : FaultHandl bool JavaStackTraceHandler::Action(int sig ATTRIBUTE_UNUSED, siginfo_t* siginfo, void* context) { // Make sure that we are in the generated code, but we may not have a dex pc. - bool in_generated_code = manager_->IsInGeneratedCode(siginfo, context); + bool in_generated_code = manager_->IsInGeneratedCode(siginfo, context, false); if (in_generated_code) { LOG(ERROR) << "Dumping java stack trace for crash in generated code"; + ArtMethod* method = nullptr; + uintptr_t return_pc = 0; + uintptr_t sp = 0; + bool is_stack_overflow = false; Thread* self = Thread::Current(); - uintptr_t sp = FaultManager::GetFaultSp(context); - CHECK_NE(sp, 0u); // Otherwise we should not have reached this handler. + manager_->GetMethodAndReturnPcAndSp( + siginfo, context, &method, &return_pc, &sp, &is_stack_overflow); // Inside of generated code, sp[0] is the method, so sp is the frame. self->SetTopOfStack(reinterpret_cast<ArtMethod**>(sp)); self->DumpJavaStack(LOG_STREAM(ERROR)); |