diff options
Diffstat (limited to 'Source/JavaScriptCore/tools/VMInspector.cpp')
-rw-r--r-- | Source/JavaScriptCore/tools/VMInspector.cpp | 195 |
1 files changed, 195 insertions, 0 deletions
diff --git a/Source/JavaScriptCore/tools/VMInspector.cpp b/Source/JavaScriptCore/tools/VMInspector.cpp new file mode 100644 index 000000000..ef44ecca7 --- /dev/null +++ b/Source/JavaScriptCore/tools/VMInspector.cpp @@ -0,0 +1,195 @@ +/* + * 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.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 |