f3e19a5dfa
I broke the operation of CmdMachine when we attach with the force flag with https://phabricator.fb.com/D851880. I consolidated some error handling logic around broken connections, etc., and in particular pulled throwing of an exception into forceQuit(). I failed to realize there was a single call site of forceQuit() that did not in fact throw an exception afterwards… the logic underneath of force attach. Switching the forceQuit() call there to stop() replaces the logic that was there previously, and restores the behavior.
660 linhas
23 KiB
C++
660 linhas
23 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 3.01 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| http://www.php.net/license/3_01.txt |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#include "hphp/runtime/debugger/debugger.h"
|
|
#include "hphp/runtime/debugger/debugger_server.h"
|
|
#include "hphp/runtime/debugger/debugger_client.h"
|
|
#include "hphp/runtime/debugger/cmd/cmd_interrupt.h"
|
|
#include "hphp/runtime/base/hphp_system.h"
|
|
#include "hphp/runtime/vm/jit/translator.h"
|
|
#include "hphp/runtime/vm/jit/translator-inline.h"
|
|
#include "hphp/util/text_color.h"
|
|
#include "hphp/util/util.h"
|
|
#include "hphp/util/logger.h"
|
|
|
|
namespace HPHP { namespace Eval {
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
TRACE_SET_MOD(debugger);
|
|
|
|
Debugger Debugger::s_debugger;
|
|
bool Debugger::s_clientStarted = false;
|
|
|
|
bool Debugger::StartServer() {
|
|
TRACE(2, "Debugger::StartServer\n");
|
|
return DebuggerServer::Start();
|
|
}
|
|
|
|
DebuggerProxyPtr Debugger::StartClient(const DebuggerClientOptions &options) {
|
|
TRACE(2, "Debugger::StartClient\n");
|
|
SmartPtr<Socket> localProxy = DebuggerClient::Start(options);
|
|
if (localProxy.get()) {
|
|
s_clientStarted = true;
|
|
return CreateProxy(localProxy, true);
|
|
}
|
|
return DebuggerProxyPtr();
|
|
}
|
|
|
|
void Debugger::Stop() {
|
|
TRACE(2, "Debugger::Stop\n");
|
|
LogShutdown(ShutdownKind::Normal);
|
|
s_debugger.m_proxyMap.clear();
|
|
DebuggerServer::Stop();
|
|
if (s_clientStarted) {
|
|
DebuggerClient::Stop();
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void Debugger::RegisterSandbox(const DSandboxInfo &sandbox) {
|
|
TRACE(2, "Debugger::RegisterSandbox\n");
|
|
s_debugger.registerSandbox(sandbox);
|
|
}
|
|
|
|
void Debugger::UnregisterSandbox(CStrRef id) {
|
|
TRACE(2, "Debugger::UnregisterSandbox\n");
|
|
s_debugger.unregisterSandbox(id.get());
|
|
}
|
|
|
|
void Debugger::RegisterThread() {
|
|
TRACE(2, "Debugger::RegisterThread\n");
|
|
s_debugger.registerThread();
|
|
}
|
|
|
|
DebuggerProxyPtr Debugger::CreateProxy(SmartPtr<Socket> socket, bool local) {
|
|
TRACE(2, "Debugger::CreateProxy\n");
|
|
return s_debugger.createProxy(socket, local);
|
|
}
|
|
|
|
void Debugger::RemoveProxy(DebuggerProxyPtr proxy) {
|
|
TRACE(2, "Debugger::RemoveProxy\n");
|
|
s_debugger.removeProxy(proxy);
|
|
}
|
|
|
|
int Debugger::CountConnectedProxy() {
|
|
TRACE(7, "Debugger::CountConnectedProxy\n");
|
|
return s_debugger.countConnectedProxy();
|
|
}
|
|
|
|
DebuggerProxyPtr Debugger::GetProxy() {
|
|
TRACE(2, "Debugger::GetProxy\n");
|
|
CStrRef sandboxId = g_context->getSandboxId();
|
|
return s_debugger.findProxy(sandboxId.get());
|
|
}
|
|
|
|
bool Debugger::SwitchSandbox(DebuggerProxyPtr proxy,
|
|
const std::string &newId,
|
|
bool force) {
|
|
TRACE(2, "Debugger::SwitchSandbox\n");
|
|
return s_debugger.switchSandbox(proxy, newId, force);
|
|
}
|
|
|
|
void Debugger::GetRegisteredSandboxes(DSandboxInfoPtrVec &sandboxes) {
|
|
TRACE(2, "Debugger::GetRegisteredSandboxes\n");
|
|
s_debugger.getSandboxes(sandboxes);
|
|
}
|
|
|
|
bool Debugger::IsThreadDebugging(int64_t id) {
|
|
TRACE(2, "Debugger::IsThreadDebugging\n");
|
|
return s_debugger.isThreadDebugging(id);
|
|
}
|
|
|
|
void Debugger::RequestInterrupt(DebuggerProxyPtr proxy) {
|
|
TRACE(2, "Debugger::RequestInterrupt\n");
|
|
s_debugger.requestInterrupt(proxy);
|
|
}
|
|
|
|
void Debugger::RetireDummySandboxThread(DummySandbox* toRetire) {
|
|
TRACE(2, "Debugger::RetireDummySandboxThread\n");
|
|
s_debugger.retireDummySandboxThread(toRetire);
|
|
}
|
|
|
|
void Debugger::CleanupDummySandboxThreads() {
|
|
TRACE(7, "Debugger::CleanupDummySandboxThreads\n");
|
|
s_debugger.cleanupDummySandboxThreads();
|
|
}
|
|
|
|
void Debugger::DebuggerSession(const DebuggerClientOptions& options,
|
|
bool restart) {
|
|
TRACE(2, "Debugger::DebuggerSession: restart=%d\n", restart);
|
|
if (options.extension.empty()) {
|
|
// even if it's empty, still need to call for warmup
|
|
hphp_invoke_simple("", true); // not to run the 1st file if compiled
|
|
} else {
|
|
hphp_invoke_simple(options.extension);
|
|
}
|
|
ThreadInfo *ti = ThreadInfo::s_threadInfo.getNoCheck();
|
|
ti->m_reqInjectionData.setDebugger(true);
|
|
if (!restart) {
|
|
DebuggerDummyEnv dde;
|
|
Debugger::InterruptSessionStarted(options.fileName.c_str());
|
|
}
|
|
if (!options.fileName.empty()) {
|
|
hphp_invoke_simple(options.fileName);
|
|
}
|
|
{
|
|
DebuggerDummyEnv dde;
|
|
Debugger::InterruptSessionEnded(options.fileName.c_str());
|
|
}
|
|
}
|
|
|
|
void Debugger::LogShutdown(ShutdownKind shutdownKind) {
|
|
int proxyCount = s_debugger.countConnectedProxy();
|
|
if (proxyCount > 0) {
|
|
Logger::Warning(DEBUGGER_LOG_TAG "%s with connected debuggers!",
|
|
shutdownKind == ShutdownKind::Normal ?
|
|
"Normal shutdown" : "Unexpected crash");
|
|
|
|
for (const auto& proxyEntry: s_debugger.m_proxyMap) {
|
|
auto sid = proxyEntry.first;
|
|
auto proxy = proxyEntry.second;
|
|
auto dummySid = StringData::GetStaticString(proxy->getDummyInfo().id());
|
|
if (sid != dummySid) {
|
|
auto sandbox = proxy->getSandbox();
|
|
if (sandbox.valid()) {
|
|
Logger::Warning(DEBUGGER_LOG_TAG "Debugging %s\n",
|
|
sandbox.desc().c_str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void Debugger::InterruptSessionStarted(const char *file,
|
|
const char *error /* = NULL */) {
|
|
TRACE(2, "Debugger::InterruptSessionStarted\n");
|
|
ThreadInfo::s_threadInfo->m_reqInjectionData.setDebugger(true);
|
|
Interrupt(SessionStarted, file, nullptr, error);
|
|
}
|
|
|
|
void Debugger::InterruptSessionEnded(const char *file) {
|
|
TRACE(2, "Debugger::InterruptSessionEnded\n");
|
|
Interrupt(SessionEnded, file);
|
|
}
|
|
|
|
void Debugger::InterruptWithUrl(int type, const char *url) {
|
|
// Build a site to represent the URL. Note it won't have any source info
|
|
// in it, because this event is raised with no PHP on the stack.
|
|
InterruptSite site(false, null_variant);
|
|
site.url() = url ? url : "";
|
|
Interrupt(type, url, &site);
|
|
}
|
|
|
|
void Debugger::InterruptRequestStarted(const char *url) {
|
|
TRACE(2, "Debugger::InterruptRequestStarted\n");
|
|
if (ThreadInfo::s_threadInfo->m_reqInjectionData.getDebugger()) {
|
|
InterruptWithUrl(RequestStarted, url);
|
|
}
|
|
}
|
|
|
|
void Debugger::InterruptRequestEnded(const char *url) {
|
|
TRACE(2, "Debugger::InterruptRequestEnded: url=%s\n", url);
|
|
if (ThreadInfo::s_threadInfo->m_reqInjectionData.getDebugger()) {
|
|
InterruptWithUrl(RequestEnded, url);
|
|
}
|
|
CStrRef sandboxId = g_context->getSandboxId();
|
|
s_debugger.unregisterSandbox(sandboxId.get());
|
|
}
|
|
|
|
void Debugger::InterruptPSPEnded(const char *url) {
|
|
TRACE(2, "Debugger::InterruptPSPEnded\n");
|
|
if (ThreadInfo::s_threadInfo->m_reqInjectionData.getDebugger()) {
|
|
InterruptWithUrl(PSPEnded, url);
|
|
}
|
|
}
|
|
|
|
// Primary entrypoint for the debugger from the VM. Called in response to a host
|
|
// of VM events that the debugger is interested in. The debugger will execute
|
|
// any logic needed to handle the event, and will block below this to wait for
|
|
// and process more commands from the debugger client. This function will only
|
|
// return when the debugger is letting the thread continue execution, e.g., for
|
|
// flow control command like continue, next, etc.
|
|
void Debugger::Interrupt(int type, const char *program,
|
|
InterruptSite *site /* = NULL */,
|
|
const char *error /* = NULL */) {
|
|
assert(RuntimeOption::EnableDebugger);
|
|
TRACE_RB(2, "Debugger::Interrupt type %d\n", type);
|
|
|
|
DebuggerProxyPtr proxy = GetProxy();
|
|
if (proxy) {
|
|
TRACE(3, "proxy != null\n");
|
|
RequestInjectionData &rjdata = ThreadInfo::s_threadInfo->m_reqInjectionData;
|
|
// The proxy will only service an interrupt if we've previously setup some
|
|
// form of flow control command (steps, breakpoints, etc.) or if it's
|
|
// an interrupt related to something like the session or request.
|
|
if (proxy->needInterrupt() || type != BreakPointReached) {
|
|
// Interrupts may execute some PHP code, causing another interruption.
|
|
std::stack<void *> &interrupts = rjdata.interrupts;
|
|
|
|
CmdInterrupt cmd((InterruptType)type, program, site, error);
|
|
interrupts.push(&cmd);
|
|
proxy->interrupt(cmd);
|
|
interrupts.pop();
|
|
}
|
|
// Some cmds require us to interpret all instructions until the cmd
|
|
// completes. Setting this will ensure we stay out of JIT code and in the
|
|
// interpreter so phpDebuggerOpcodeHook has a chance to work.
|
|
rjdata.setDebuggerIntr(proxy->needVMInterrupts());
|
|
} else {
|
|
TRACE(3, "proxy == null\n");
|
|
// Debugger clients are disconnected abnormally, or this sandbox is not
|
|
// being debugged.
|
|
if (type == SessionStarted || type == SessionEnded) {
|
|
// For command line programs, we need this exception to exit from
|
|
// the infinite execution loop.
|
|
throw DebuggerClientExitException();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Primary entrypoint from the set of "debugger hooks", which the VM calls in
|
|
// response to various events. While this function is quite general wrt. the
|
|
// type of interrupt, practically the type will be one of the following:
|
|
// - ExceptionThrown
|
|
// - ExceptionHandler
|
|
// - BreakPointReached
|
|
// - HardBreakPoint
|
|
//
|
|
// Note: it is indeed a bit odd that interrupts due to single stepping come in
|
|
// as "BreakPointReached". Currently this results in spurious work in the
|
|
// debugger.
|
|
void Debugger::InterruptVMHook(int type /* = BreakPointReached */,
|
|
CVarRef e /* = null_variant */) {
|
|
TRACE(2, "Debugger::InterruptVMHook\n");
|
|
// Computing the interrupt site here pulls in more data from the Unit to
|
|
// describe the current execution point.
|
|
InterruptSite site(type == HardBreakPoint, e);
|
|
if (!site.valid()) {
|
|
// An invalid site is missing something like an ActRec, a func, or a
|
|
// Unit. Currently the debugger has no action to take at such sites.
|
|
return;
|
|
}
|
|
Interrupt(type, nullptr, &site);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void Debugger::SetTextColors() {
|
|
TRACE(2, "Debugger::SetTextColors\n");
|
|
s_stdout_color = ANSI_COLOR_CYAN;
|
|
s_stderr_color = ANSI_COLOR_RED;
|
|
}
|
|
|
|
String Debugger::ColorStdout(CStrRef s) {
|
|
TRACE(2, "Debugger::ColorStdout\n");
|
|
if (s_stdout_color) {
|
|
return String(s_stdout_color) + s + String(ANSI_COLOR_END);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
String Debugger::ColorStderr(CStrRef s) {
|
|
TRACE(2, "Debugger::ColorStderr\n");
|
|
if (s_stderr_color) {
|
|
return String(s_stderr_color) + s + String(ANSI_COLOR_END);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool Debugger::isThreadDebugging(int64_t tid) {
|
|
TRACE(2, "Debugger::isThreadDebugging\n");
|
|
ThreadInfoMap::const_accessor acc;
|
|
if (m_threadInfos.find(acc, tid)) {
|
|
ThreadInfo* ti = acc->second;
|
|
return (ti->m_reqInjectionData.getDebugger());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Debugger::registerThread() {
|
|
TRACE(2, "Debugger::registerThread\n");
|
|
ThreadInfo* ti = ThreadInfo::s_threadInfo.getNoCheck();
|
|
int64_t tid = (int64_t)Process::GetThreadId();
|
|
ThreadInfoMap::accessor acc;
|
|
m_threadInfos.insert(acc, tid);
|
|
acc->second = ti;
|
|
}
|
|
|
|
void Debugger::addOrUpdateSandbox(const DSandboxInfo &sandbox) {
|
|
TRACE(2, "Debugger::addOrUpdateSandbox\n");
|
|
const StringData* sd = StringData::GetStaticString(sandbox.id());
|
|
SandboxMap::accessor acc;
|
|
if (m_sandboxMap.insert(acc, sd)) {
|
|
DSandboxInfoPtr sb(new DSandboxInfo());
|
|
*sb = sandbox;
|
|
acc->second = sb;
|
|
} else {
|
|
acc->second->update(sandbox);
|
|
}
|
|
}
|
|
|
|
void Debugger::getSandboxes(DSandboxInfoPtrVec &sandboxes) {
|
|
TRACE(2, "Debugger::getSandboxes\n");
|
|
sandboxes.reserve(m_sandboxMap.size());
|
|
for (SandboxMap::const_iterator iter =
|
|
m_sandboxMap.begin(); iter != m_sandboxMap.end(); ++iter) {
|
|
if (!iter->second->m_user.empty()) {
|
|
DSandboxInfoPtr sandbox(new DSandboxInfo());
|
|
*sandbox = *iter->second;
|
|
sandboxes.push_back(sandbox);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Notify the debugger that this thread is executing a request in the given
|
|
// sandbox. This ensures that the debugger knows about the sandbox, and adds
|
|
// the thread to the set of threads currently active in the sandbox.
|
|
void Debugger::registerSandbox(const DSandboxInfo &sandbox) {
|
|
TRACE(2, "Debugger::registerSandbox\n");
|
|
// update sandbox first
|
|
addOrUpdateSandbox(sandbox);
|
|
|
|
// add thread to m_sandboxThreadInfoMap
|
|
const StringData* sid = StringData::GetStaticString(sandbox.id());
|
|
ThreadInfo* ti = ThreadInfo::s_threadInfo.getNoCheck();
|
|
{
|
|
SandboxThreadInfoMap::accessor acc;
|
|
m_sandboxThreadInfoMap.insert(acc, sid);
|
|
ThreadInfoSet& set = acc->second;
|
|
set.insert(ti);
|
|
}
|
|
|
|
// find out whether this sandbox is being debugged
|
|
DebuggerProxyPtr proxy = findProxy(sid);
|
|
if (proxy) {
|
|
ti->m_reqInjectionData.setDebugger(true);
|
|
}
|
|
}
|
|
|
|
void Debugger::unregisterSandbox(const StringData* sandboxId) {
|
|
TRACE(2, "Debugger::unregisterSandbox\n");
|
|
ThreadInfo *ti = ThreadInfo::s_threadInfo.getNoCheck();
|
|
SandboxThreadInfoMap::accessor acc;
|
|
if (m_sandboxThreadInfoMap.find(acc, sandboxId)) {
|
|
ThreadInfoSet& set = acc->second;
|
|
set.erase(ti);
|
|
}
|
|
}
|
|
|
|
#define FOREACH_SANDBOX_THREAD_BEGIN(sid, ti) { \
|
|
SandboxThreadInfoMap::const_accessor acc; \
|
|
if (m_sandboxThreadInfoMap.find(acc, sid)) { \
|
|
const ThreadInfoSet& set = acc->second; \
|
|
for (std::set<ThreadInfo*>::iterator iter = set.begin(); \
|
|
iter != set.end(); ++iter) { \
|
|
ThreadInfo* ti = (*iter); \
|
|
assert(ThreadInfo::valid(ti)); \
|
|
|
|
#define FOREACH_SANDBOX_THREAD_END() } } } \
|
|
|
|
// Ask every thread in this proxy's sandbox and the dummy sandbox to
|
|
// "stop". Gaining control of these threads is the intention... the
|
|
// mechanism is to force them all to start interpreting all of their
|
|
// code in an effort to gain control in phpDebuggerOpcodeHook(). We
|
|
// set the "debugger interrupt" flag to ensure we interpret code
|
|
// rather than entering translated code, and we set the "debugger
|
|
// signal" surprise flag to pop out of loops in translated code.
|
|
void Debugger::requestInterrupt(DebuggerProxyPtr proxy) {
|
|
TRACE(2, "Debugger::requestInterrupt\n");
|
|
const StringData* sid = StringData::GetStaticString(proxy->getSandboxId());
|
|
FOREACH_SANDBOX_THREAD_BEGIN(sid, ti)
|
|
ti->m_reqInjectionData.setDebuggerIntr(true);
|
|
ti->m_reqInjectionData.setDebuggerSignalFlag();
|
|
FOREACH_SANDBOX_THREAD_END()
|
|
|
|
sid = StringData::GetStaticString(proxy->getDummyInfo().id());
|
|
FOREACH_SANDBOX_THREAD_BEGIN(sid, ti)
|
|
ti->m_reqInjectionData.setDebuggerIntr(true);
|
|
ti->m_reqInjectionData.setDebuggerSignalFlag();
|
|
FOREACH_SANDBOX_THREAD_END()
|
|
}
|
|
|
|
void Debugger::setDebuggerFlag(const StringData* sandboxId, bool flag) {
|
|
TRACE(2, "Debugger::setDebuggerFlag\n");
|
|
FOREACH_SANDBOX_THREAD_BEGIN(sandboxId, ti)
|
|
ti->m_reqInjectionData.setDebugger(flag);
|
|
FOREACH_SANDBOX_THREAD_END()
|
|
}
|
|
|
|
#undef FOREACH_SANDBOX_THREAD_BEGIN
|
|
#undef FOREACH_SANDBOX_THREAD_END
|
|
|
|
DebuggerProxyPtr Debugger::createProxy(SmartPtr<Socket> socket, bool local) {
|
|
TRACE(2, "Debugger::createProxy\n");
|
|
// Creates a proxy and threads needed to handle it. At this point, there is
|
|
// not enough information to attach a sandbox.
|
|
DebuggerProxyPtr proxy(new DebuggerProxy(socket, local));
|
|
{
|
|
// Place this new proxy into the proxy map keyed on the dummy sandbox id.
|
|
// This keeps the proxy alive in the server case, which drops the result of
|
|
// this function on the floor. It also makes the proxy findable when we a
|
|
// dummy sandbox thread needs to interrupt.
|
|
const StringData* sid =
|
|
StringData::GetStaticString(proxy->getDummyInfo().id());
|
|
assert(sid);
|
|
ProxyMap::accessor acc;
|
|
m_proxyMap.insert(acc, sid);
|
|
acc->second = proxy;
|
|
}
|
|
if (!local) {
|
|
proxy->startDummySandbox();
|
|
}
|
|
proxy->startSignalThread();
|
|
return proxy;
|
|
}
|
|
|
|
void Debugger::retireDummySandboxThread(DummySandbox* toRetire) {
|
|
TRACE(2, "Debugger::retireDummySandboxThread\n");
|
|
m_cleanupDummySandboxQ.push(toRetire);
|
|
}
|
|
|
|
void Debugger::cleanupDummySandboxThreads() {
|
|
TRACE(7, "Debugger::cleanupDummySandboxThreads\n");
|
|
DummySandbox* ptr = nullptr;
|
|
while (m_cleanupDummySandboxQ.try_pop(ptr)) {
|
|
ptr->notify();
|
|
try {
|
|
// we can't block the server for waiting
|
|
if (ptr->waitForEnd(1)) {
|
|
delete ptr;
|
|
} else {
|
|
Logger::Error("Dummy sandbox %p refused to stop", ptr);
|
|
}
|
|
} catch (Exception &e) {
|
|
Logger::Error("Dummy sandbox exception: " + e.getMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
// NB: when this returns, the Debugger class no longer has any references to the
|
|
// given proxy. It will likely be destroyed when the caller's reference goes out
|
|
// of scope.
|
|
void Debugger::removeProxy(DebuggerProxyPtr proxy) {
|
|
TRACE(2, "Debugger::removeProxy\n");
|
|
if (proxy->getSandbox().valid()) {
|
|
const StringData* sid = StringData::GetStaticString(proxy->getSandboxId());
|
|
setDebuggerFlag(sid, false);
|
|
m_proxyMap.erase(sid);
|
|
}
|
|
const StringData* dummySid =
|
|
StringData::GetStaticString(proxy->getDummyInfo().id());
|
|
m_proxyMap.erase(dummySid);
|
|
// Clear the debugger blacklist PC upon last detach if JIT is used
|
|
if (RuntimeOption::EvalJit && countConnectedProxy() == 0) {
|
|
Transl::Translator::Get()->clearDbgBL();
|
|
}
|
|
}
|
|
|
|
DebuggerProxyPtr Debugger::findProxy(const StringData* sandboxId) {
|
|
TRACE(2, "Debugger::findProxy\n");
|
|
if (sandboxId) {
|
|
ProxyMap::const_accessor acc;
|
|
if (m_proxyMap.find(acc, sandboxId)) {
|
|
return acc->second;
|
|
}
|
|
}
|
|
return DebuggerProxyPtr();
|
|
}
|
|
|
|
bool Debugger::switchSandbox(DebuggerProxyPtr proxy,
|
|
const std::string &newId,
|
|
bool force) {
|
|
TRACE(2, "Debugger::switchSandbox\n");
|
|
const StringData* newSid = StringData::GetStaticString(newId);
|
|
// Lock the proxy during the switch
|
|
Lock l(proxy.get());
|
|
if (proxy->getSandboxId() != newId) {
|
|
if (!switchSandboxImpl(proxy, newSid, force)) {
|
|
// failed to switch
|
|
return false;
|
|
}
|
|
}
|
|
|
|
updateProxySandbox(proxy, newSid);
|
|
return true;
|
|
}
|
|
|
|
bool Debugger::switchSandboxImpl(DebuggerProxyPtr proxy,
|
|
const StringData* newSid,
|
|
bool force) {
|
|
TRACE(2, "Debugger::switchSandboxImpl\n");
|
|
// Take the new sandbox
|
|
DebuggerProxyPtr otherProxy;
|
|
{
|
|
ProxyMap::accessor acc;
|
|
if (m_proxyMap.insert(acc, newSid)) {
|
|
acc->second = proxy;
|
|
} else {
|
|
// the sandbox indicated by newId is already attached by another proxy
|
|
if (!force) {
|
|
return false;
|
|
}
|
|
// Delay the destruction of the proxy originally attached to the sandbox
|
|
// to avoid calling a DebuggerProxy destructor (which can sleep) while
|
|
// holding m_mutex.
|
|
otherProxy = acc->second;
|
|
acc->second = proxy;
|
|
}
|
|
}
|
|
|
|
if (proxy->getSandbox().valid()) {
|
|
// Detach from the old sandbox
|
|
const StringData* oldSid =
|
|
StringData::GetStaticString(proxy->getSandboxId());
|
|
setDebuggerFlag(oldSid, false);
|
|
m_proxyMap.erase(oldSid);
|
|
}
|
|
setDebuggerFlag(newSid, true);
|
|
|
|
if (otherProxy) {
|
|
otherProxy->stop();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Debugger::updateProxySandbox(DebuggerProxyPtr proxy,
|
|
const StringData* sandboxId) {
|
|
TRACE(2, "Debugger::updateProxySandbox\n");
|
|
// update proxy's sandbox info from what is on file
|
|
SandboxMap::const_accessor acc;
|
|
if (m_sandboxMap.find(acc, sandboxId)) {
|
|
proxy->updateSandbox(acc->second);
|
|
} else {
|
|
// don't have the sandbox on file yet. create a sandbox info with
|
|
// no path for now. Sandbox path will be updated upon first request
|
|
// with that sandbox arrives
|
|
DSandboxInfoPtr sb(new DSandboxInfo(sandboxId->toCPPString()));
|
|
proxy->updateSandbox(sb);
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Helpers for usage logging
|
|
|
|
// NB: the usage logger is not owned by the Debugger. The caller will call this
|
|
// again with nullptr before destroying the given usage logger.
|
|
void Debugger::SetUsageLogger(DebuggerUsageLogger *usageLogger) {
|
|
TRACE(1, "Debugger::SetUsageLogger\n");
|
|
s_debugger.m_usageLogger = usageLogger;
|
|
}
|
|
|
|
void Debugger::InitUsageLogging() {
|
|
TRACE(1, "Debugger::InitUsageLogging\n");
|
|
if (s_debugger.m_usageLogger) s_debugger.m_usageLogger->init();
|
|
}
|
|
|
|
void Debugger::UsageLog(const std::string &mode, const std::string &sandboxId,
|
|
const std::string &cmd, const std::string &data) {
|
|
if (s_debugger.m_usageLogger) s_debugger.m_usageLogger->log(mode, sandboxId,
|
|
cmd, data);
|
|
}
|
|
|
|
const char *Debugger::InterruptTypeName(CmdInterrupt &cmd) {
|
|
switch (cmd.getInterruptType()) {
|
|
case SessionStarted: return "SessionStarted";
|
|
case SessionEnded: return "SessionEnded";
|
|
case RequestStarted: return "RequestStarted";
|
|
case RequestEnded: return "RequestEnded";
|
|
case PSPEnded: return "PSPEnded";
|
|
case HardBreakPoint: return "HardBreakPoint";
|
|
case BreakPointReached: return "BreakPointReached";
|
|
case ExceptionThrown: return "ExceptionThrown";
|
|
case ExceptionHandler: return "ExceptionHandler";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
void Debugger::UsageLogInterrupt(const std::string &mode,
|
|
const std::string &sandboxId,
|
|
CmdInterrupt &cmd) {
|
|
UsageLog(mode, sandboxId, "interrupt", InterruptTypeName(cmd));
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
DebuggerDummyEnv::DebuggerDummyEnv() {
|
|
TRACE(2, "DebuggerDummyEnv::DebuggerDummyEnv\n");
|
|
g_vmContext->enterDebuggerDummyEnv();
|
|
}
|
|
|
|
DebuggerDummyEnv::~DebuggerDummyEnv() {
|
|
TRACE(2, "DebuggerDummyEnv::~DebuggerDummyEnv\n");
|
|
g_vmContext->exitDebuggerDummyEnv();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
EvalBreakControl::EvalBreakControl(bool noBreak) {
|
|
TRACE(2, "EvalBreakControl::EvalBreakControl\n");
|
|
m_noBreakSave = g_vmContext->m_dbgNoBreak;
|
|
g_vmContext->m_dbgNoBreak = noBreak;
|
|
}
|
|
|
|
EvalBreakControl::~EvalBreakControl() {
|
|
TRACE(2, "EvalBreakControl::~EvalBreakControl\n");
|
|
g_vmContext->m_dbgNoBreak = m_noBreakSave;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}}
|