/* * Copyright (C) 2017 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "VMInspector.h" #include "CodeBlock.h" #include "CodeBlockSet.h" #include "HeapInlines.h" #include #include #if !OS(WINDOWS) #include #endif namespace JSC { VMInspector& VMInspector::instance() { static VMInspector* manager; static std::once_flag once; std::call_once(once, [] { manager = new VMInspector(); }); return *manager; } void VMInspector::add(VM* vm) { auto locker = holdLock(m_lock); m_list.append(vm); } void VMInspector::remove(VM* vm) { auto locker = holdLock(m_lock); m_list.remove(vm); } auto VMInspector::lock(Seconds timeout) -> Expected { // This function may be called from a signal handler (e.g. via visit()). Hence, // it should only use APIs that are safe to call from signal handlers. This is // why we use unistd.h's sleep() instead of its alternatives. // We'll be doing sleep(1) between tries below. Hence, sleepPerRetry is 1. unsigned maxRetries = (timeout < Seconds::infinity()) ? timeout.value() : UINT_MAX; Expected locker = Locker::tryLock(m_lock); unsigned tryCount = 0; while (!locker && tryCount < maxRetries) { // We want the version of sleep from unistd.h. Cast to disambiguate. #if !OS(WINDOWS) (static_cast(sleep))(1); #endif locker = Locker::tryLock(m_lock); } if (!locker) return makeUnexpected(Error::TimedOut); return locker; } #if ENABLE(JIT) static bool ensureIsSafeToLock(Lock& lock) { unsigned maxRetries = 2; unsigned tryCount = 0; while (tryCount <= maxRetries) { bool success = lock.tryLock(); if (success) { lock.unlock(); return true; } tryCount++; } return false; }; #endif // ENABLE(JIT) auto VMInspector::isValidExecutableMemory(const VMInspector::Locker&, void* machinePC) -> Expected { #if ENABLE(JIT) bool found = false; bool hasTimeout = false; iterate([&] (VM& vm) -> FunctorStatus { auto allocator = vm.executableAllocator; auto& lock = allocator.getLock(); bool isSafeToLock = ensureIsSafeToLock(lock); if (!isSafeToLock) { hasTimeout = true; return FunctorStatus::Continue; // Skip this VM. } LockHolder executableAllocatorLocker(lock); if (allocator.isValidExecutableMemory(executableAllocatorLocker, machinePC)) { found = true; return FunctorStatus::Done; } return FunctorStatus::Continue; }); if (!found && hasTimeout) return makeUnexpected(Error::TimedOut); return found; #else UNUSED_PARAM(machinePC); return false; #endif } auto VMInspector::codeBlockForMachinePC(const VMInspector::Locker&, void* machinePC) -> Expected { #if ENABLE(JIT) CodeBlock* codeBlock = nullptr; bool hasTimeout = false; iterate([&] (VM& vm) { if (!vm.apiLock().currentThreadIsHoldingLock()) return FunctorStatus::Continue; // It is safe to call Heap::forEachCodeBlockIgnoringJITPlans here because: // 1. CodeBlocks are added to the CodeBlockSet from the main thread before // they are handed to the JIT plans. Those codeBlocks will have a null jitCode, // but we check for that in our lambda functor. // 2. CodeBlockSet::iterate() will acquire the CodeBlockSet lock before iterating. // This ensures that a CodeBlock won't be GCed while we're iterating. // 3. We do a tryLock on the CodeBlockSet's lock first to ensure that it is // safe for the current thread to lock it before calling // Heap::forEachCodeBlockIgnoringJITPlans(). Hence, there's no risk of // re-entering the lock and deadlocking on it. auto& lock = vm.heap.codeBlockSet().getLock(); bool isSafeToLock = ensureIsSafeToLock(lock); if (!isSafeToLock) { hasTimeout = true; return FunctorStatus::Continue; // Skip this VM. } vm.heap.forEachCodeBlockIgnoringJITPlans([&] (CodeBlock* cb) { JITCode* jitCode = cb->jitCode().get(); if (!jitCode) { // If the codeBlock is a replacement codeBlock which is in the process of being // compiled, its jitCode will be null, and we can disregard it as a match for // the machinePC we're searching for. return false; } if (!JITCode::isJIT(jitCode->jitType())) return false; if (jitCode->contains(machinePC)) { codeBlock = cb; return true; } return false; }); if (codeBlock) return FunctorStatus::Done; return FunctorStatus::Continue; }); if (!codeBlock && hasTimeout) return makeUnexpected(Error::TimedOut); return codeBlock; #else UNUSED_PARAM(machinePC); return nullptr; #endif } } // namespace JSC