VMInspector.cpp   [plain text]


/*
 * Copyright (C) 2017-2018 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 "HeapIterationScope.h"
#include "MachineContext.h"
#include "MarkedSpaceInlines.h"
#include "StackVisitor.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&) -> FunctorStatus {
        auto& allocator = ExecutableAllocator::singleton();
        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;
            }

            if (!JITCode::isJIT(jitCode->jitType()))
                return;

            if (jitCode->contains(machinePC)) {
                codeBlock = cb;
                return;
            }
        });
        if (codeBlock)
            return FunctorStatus::Done;
        return FunctorStatus::Continue;
    });

    if (!codeBlock && hasTimeout)
        return makeUnexpected(Error::TimedOut);
    return codeBlock;
#else
    UNUSED_PARAM(machinePC);
    return nullptr;
#endif
}

bool VMInspector::currentThreadOwnsJSLock(ExecState* exec)
{
    return exec->vm().currentThreadIsHoldingAPILock();
}

static bool ensureCurrentThreadOwnsJSLock(ExecState* exec)
{
    if (VMInspector::currentThreadOwnsJSLock(exec))
        return true;
    dataLog("ERROR: current thread does not own the JSLock\n");
    return false;
}

void VMInspector::gc(ExecState* exec)
{
    VM& vm = exec->vm();
    if (!ensureCurrentThreadOwnsJSLock(exec))
        return;
    vm.heap.collectNow(Sync, CollectionScope::Full);
}

void VMInspector::edenGC(ExecState* exec)
{
    VM& vm = exec->vm();
    if (!ensureCurrentThreadOwnsJSLock(exec))
        return;
    vm.heap.collectSync(CollectionScope::Eden);
}

bool VMInspector::isInHeap(Heap* heap, void* ptr)
{
    MarkedBlock* candidate = MarkedBlock::blockFor(ptr);
    if (heap->objectSpace().blocks().set().contains(candidate))
        return true;
    for (LargeAllocation* allocation : heap->objectSpace().largeAllocations()) {
        if (allocation->contains(ptr))
            return true;
    }
    return false;
}

struct CellAddressCheckFunctor : MarkedBlock::CountFunctor {
    CellAddressCheckFunctor(JSCell* candidate)
        : candidate(candidate)
    {
    }

    IterationStatus operator()(HeapCell* cell, HeapCell::Kind) const
    {
        if (cell == candidate) {
            found = true;
            return IterationStatus::Done;
        }
        return IterationStatus::Continue;
    }

    JSCell* candidate;
    mutable bool found { false };
};

bool VMInspector::isValidCell(Heap* heap, JSCell* candidate)
{
    HeapIterationScope iterationScope(*heap);
    CellAddressCheckFunctor functor(candidate);
    heap->objectSpace().forEachLiveCell(iterationScope, functor);
    return functor.found;
}

bool VMInspector::isValidCodeBlock(ExecState* exec, CodeBlock* candidate)
{
    if (!ensureCurrentThreadOwnsJSLock(exec))
        return false;

    struct CodeBlockValidationFunctor {
        CodeBlockValidationFunctor(CodeBlock* candidate)
            : candidate(candidate)
        {
        }

        void operator()(CodeBlock* codeBlock) const
        {
            if (codeBlock == candidate)
                found = true;
        }

        CodeBlock* candidate;
        mutable bool found { false };
    };

    VM& vm = exec->vm();
    CodeBlockValidationFunctor functor(candidate);
    vm.heap.forEachCodeBlock(functor);
    return functor.found;
}

CodeBlock* VMInspector::codeBlockForFrame(CallFrame* topCallFrame, unsigned frameNumber)
{
    if (!ensureCurrentThreadOwnsJSLock(topCallFrame))
        return nullptr;

    if (!topCallFrame)
        return nullptr;

    struct FetchCodeBlockFunctor {
    public:
        FetchCodeBlockFunctor(unsigned targetFrameNumber)
            : targetFrame(targetFrameNumber)
        {
        }

        StackVisitor::Status operator()(StackVisitor& visitor) const
        {
            auto currentFrame = nextFrame++;
            if (currentFrame == targetFrame) {
                codeBlock = visitor->codeBlock();
                return StackVisitor::Done;
            }
            return StackVisitor::Continue;
        }

        unsigned targetFrame;
        mutable unsigned nextFrame { 0 };
        mutable CodeBlock* codeBlock { nullptr };
    };

    FetchCodeBlockFunctor functor(frameNumber);
    topCallFrame->iterate(functor);
    return functor.codeBlock;
}

class DumpFrameFunctor {
public:
    enum Action {
        DumpOne,
        DumpAll
    };

    DumpFrameFunctor(Action action, unsigned framesToSkip)
        : m_action(action)
        , m_framesToSkip(framesToSkip)
    {
    }

    StackVisitor::Status operator()(StackVisitor& visitor) const
    {
        m_currentFrame++;
        if (m_currentFrame > m_framesToSkip) {
            visitor->dump(WTF::dataFile(), Indenter(2), [&] (PrintStream& out) {
                out.print("[", (m_currentFrame - m_framesToSkip - 1), "] ");
            });
        }
        if (m_action == DumpOne && m_currentFrame > m_framesToSkip)
            return StackVisitor::Done;
        return StackVisitor::Continue;
    }

private:
    Action m_action;
    unsigned m_framesToSkip;
    mutable unsigned m_currentFrame { 0 };
};

void VMInspector::dumpCallFrame(CallFrame* callFrame, unsigned framesToSkip)
{
    if (!ensureCurrentThreadOwnsJSLock(callFrame))
        return;
    DumpFrameFunctor functor(DumpFrameFunctor::DumpOne, framesToSkip);
    callFrame->iterate(functor);
}

void VMInspector::dumpStack(CallFrame* topCallFrame, unsigned framesToSkip)
{
    if (!ensureCurrentThreadOwnsJSLock(topCallFrame))
        return;
    if (!topCallFrame)
        return;
    DumpFrameFunctor functor(DumpFrameFunctor::DumpAll, framesToSkip);
    topCallFrame->iterate(functor);
}

void VMInspector::dumpValue(JSValue value)
{
    dataLog(value);
}

} // namespace JSC