#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<LockToken, Error>
{
unsigned maxRetries = (timeout < Seconds::infinity()) ? timeout.value() : UINT_MAX;
bool locked = m_lock.tryLock();
unsigned tryCount = 0;
while (!locked && tryCount < maxRetries) {
#if !OS(WINDOWS)
(static_cast<unsigned (*)(unsigned)>(sleep))(1);
#endif
locked = m_lock.tryLock();
}
if (!locked)
return makeUnexpected(Error::TimedOut);
return LockToken::LockedValue;
}
#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(VMInspector::LockToken, 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; }
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(VMInspector::LockToken, void* machinePC) -> Expected<CodeBlock*, Error>
{
#if ENABLE(JIT)
CodeBlock* codeBlock = nullptr;
bool hasTimeout = false;
iterate([&] (VM& vm) {
if (!vm.apiLock().currentThreadIsHoldingLock())
return FunctorStatus::Continue;
auto& lock = vm.heap.codeBlockSet().getLock();
bool isSafeToLock = ensureIsSafeToLock(lock);
if (!isSafeToLock) {
hasTimeout = true;
return FunctorStatus::Continue; }
vm.heap.forEachCodeBlockIgnoringJITPlans([&] (CodeBlock* cb) {
JITCode* jitCode = cb->jitCode().get();
if (!jitCode) {
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
}
}