namespace art HIDDEN { // Static fault manger object accessed by signal handler.
FaultManager fault_manager;
// These need to be NO_INLINE since some debuggers do not read the inline-info to set a breakpoint // if they aren't. extern"C" NO_INLINE __attribute__((visibility("default"))) 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.";
} extern"C" NO_INLINE __attribute__((visibility("default"))) void art_sigbus_fault() { // Set a breakpoint here to be informed when a SIGBUS is unhandled by ART.
VLOG(signals) << "Caught unknown SIGBUS in ART fault handler - chaining to next handler.";
} extern"C" NO_INLINE __attribute__((visibility("default"))) void art_sigsys_fault() { // Set a breakpoint here to be informed when a SIGSYS is unhandled by ART.
VLOG(signals) << "Caught unknown SIGSYS in ART fault handler - chaining to next handler.";
}
// Signal handler called on SIGSEGV. staticbool art_sigsegv_handler(int sig, siginfo_t* info, void* context) { return fault_manager.HandleSigsegvFault(sig, info, context);
}
// Signal handler called on SIGBUS. staticbool art_sigbus_handler(int sig, siginfo_t* info, void* context) { return fault_manager.HandleSigbusFault(sig, info, context);
}
// Signal handler called on SIGSYS. staticbool art_sigsys_handler(int sig, siginfo_t* info, void* context) { return fault_manager.HandleSigsysFault(sig, info, context);
}
FaultManager::~FaultManager() { // ~Runtime() calls Shutdown(), but the FaultManager's lifetime can be shorter than the runtime. // Make sure to call Shutdown in the destructor to not leak memory through the handlers.
Shutdown();
}
staticconstchar* SignalCodeName(int sig, int code) { if (sig == SIGSEGV) { switch (code) { case SEGV_MAPERR: return"SEGV_MAPERR"; case SEGV_ACCERR: return"SEGV_ACCERR"; case8: return"SEGV_MTEAERR"; case9: return"SEGV_MTESERR"; default: return"SEGV_UNKNOWN";
}
} elseif (sig == SIGBUS) { switch (code) { case BUS_ADRALN: return"BUS_ADRALN"; case BUS_ADRERR: return"BUS_ADRERR"; case BUS_OBJERR: return"BUS_OBJERR"; default: return"BUS_UNKNOWN";
}
} elseif (sig == SIGSYS) { switch (code) { case SYS_SECCOMP: return"SYS_SECCOMP"; default: return"SYS_UNKNOWN";
}
} else { return"UNKNOWN";
}
}
// 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);
}
{
MutexLock lock(Thread::Current(), generated_code_ranges_lock_); for (size_t i = 0; i != kNumLocalGeneratedCodeRanges; ++i) {
GeneratedCodeRange* next = (i + 1u != kNumLocalGeneratedCodeRanges)
? &generated_code_ranges_storage_[i + 1u]
: nullptr;
generated_code_ranges_storage_[i].next.store(next, std::memory_order_relaxed);
generated_code_ranges_storage_[i].start = nullptr;
generated_code_ranges_storage_[i].size = 0u;
}
free_generated_code_ranges_ = generated_code_ranges_storage_;
}
void FaultManager::Shutdown() { if (initialized_) {
Release();
// 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).
MutexLock lock(Thread::Current(), generated_code_ranges_lock_);
GeneratedCodeRange* 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);
std::less<GeneratedCodeRange*> less; if (!less(range, generated_code_ranges_storage_) &&
less(range, generated_code_ranges_storage_ + kNumLocalGeneratedCodeRanges)) { // Nothing to do - not adding `range` to the `free_generated_code_ranges_` anymore.
} else { // Range is not in the `generated_code_ranges_storage_`. delete range;
}
range = next_range;
}
}
}
#ifdef TEST_NESTED_SIGNAL // Simulate a crash in a handler.
raise(SIGBUS); #endif if (mark_compact_->SigbusHandler(info)) {
MaybeSuspendFaster(this, info, context); returntrue;
} // Set a breakpoint in this function to catch unhandled signals.
art_sigbus_fault(); returnfalse;
}
#ifdef TEST_NESTED_SIGNAL // Simulate a crash in a handler.
raise(SIGSEGV); #endif
if (IsInGeneratedCode(info, context)) {
VLOG(signals) << "in generated code, looking for handler"; for (constauto& handler : generated_code_handlers_) {
VLOG(signals) << "invoking Action on handler " << handler; if (handler->Action(sig, info, context)) { // We have handled a signal so it's time to return from the // signal handler to the appropriate place. returntrue;
}
}
} elseif (kRuntimeQuickCodeISA == InstructionSet::kArm64) {
CheckForUnrecognizedImplicitSuspendCheckInBootImage(info, context);
}
// We hit a signal we didn't handle. This might be something for which // we can give more information about so call all registered handlers to // see if it is. if (HandleFaultByOtherHandlers(sig, info, context)) { returntrue;
}
// Set a breakpoint in this function to catch unhandled signals.
art_sigsegv_fault(); returnfalse;
}
// 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);
}
void FaultManager::RemoveGeneratedCodeRange(constvoid* 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(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 performs the cleanup during its `MarkingPhase()` while holding // the mutator exclusively, so we do not need a checkpoint. All other GCs perform // the cleanup in their `ReclaimPhase()` while holding the mutator lock as shared // and it's safe to release and re-acquire the mutator lock. Despite holding the // mutator lock as shared, the thread is not always 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 {
DCHECK(Locks::mutator_lock_->IsSharedHeld(self)); // Use explicit state transitions or unlock/lock. bool runnable = (self->GetState() == ThreadState::kRunnable); if (runnable) {
self->TransitionFromRunnableToSuspended(ThreadState::kNative);
} else {
Locks::mutator_lock_->SharedUnlock(self);
}
DCHECK(!Locks::mutator_lock_->IsSharedHeld(self));
runtime->GetThreadList()->RunEmptyCheckpoint(); if (runnable) {
self->TransitionFromSuspendedToRunnable();
} else {
Locks::mutator_lock_->SharedLock(self);
}
}
}
FreeGeneratedCodeRange(range);
}
// 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([[maybe_unused]] siginfo_t* siginfo,
[[maybe_unused]] void* context) { // We can only be running Java code in the current thread if it // is in Runnable state.
VLOG(signals) << "Checking for generated code";
Thread* thread = Thread::Current(); if (thread == nullptr) {
VLOG(signals) << "no current thread"; returnfalse;
}
ThreadState state = thread->GetState(); if (state != ThreadState::kRunnable) {
VLOG(signals) << "not runnable"; returnfalse;
}
// Current thread is runnable. // Make sure it has the mutator lock. if (!Locks::mutator_lock_->IsSharedHeld(thread)) {
VLOG(signals) << "no lock"; returnfalse;
}
#ifdef ART_USE_SIMULATOR // Get the PC from the simulator. As the fault handler runs natively, the simulated PC won't have // changed from the faulting instruction.
uintptr_t fault_pc = Thread::Current()->GetSimExecutor()->GetPc(); #else
uintptr_t fault_pc = GetFaultPc(siginfo, context); #endif if (fault_pc == 0u) {
VLOG(signals) << "no fault PC"; returnfalse;
}
// 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) { returntrue;
} // 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);
} returnfalse;
}
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;
// 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.)
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."; returnfalse;
}
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);
VLOG(signals) << "dexpc: " << dexpc; return dexpc != dex::kDexNoIndex;
}
bool JavaStackTraceHandler::Action([[maybe_unused]] int sig, 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); if (in_generated_code) {
LOG(ERROR) << "Dumping java stack trace for crash in generated code";
Thread* self = Thread::Current();
uintptr_t sp = FaultManager::GetFaultSp(context);
CHECK_NE(sp, 0u); // Otherwise we should not have reached this handler. // 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));
}
returnfalse; // Return false since we want to propagate the fault to the main signal handler.
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.