| /* |
| * 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 <mutex> |
| #include <wtf/Expected.h> |
| |
| #if !OS(WINDOWS) |
| #include <unistd.h> |
| #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<Locker, Error> |
| { |
| // 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, Error> 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<unsigned (*)(unsigned)>(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<bool, Error> |
| { |
| #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<CodeBlock*, Error> |
| { |
| #if ENABLE(JIT) |
| CodeBlock* codeBlock = nullptr; |
| bool hasTimeout = false; |
| iterate([&] (VM& vm) { |
| if (!vm.currentThreadIsHoldingAPILock()) |
| 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. We 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& codeBlockSetLock = vm.heap.codeBlockSet().getLock(); |
| bool isSafeToLock = ensureIsSafeToLock(codeBlockSetLock); |
| if (!isSafeToLock) { |
| hasTimeout = true; |
| return FunctorStatus::Continue; // Skip this VM. |
| } |
| |
| auto locker = holdLock(codeBlockSetLock); |
| vm.heap.forEachCodeBlockIgnoringJITPlans(locker, [&] (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 |