bool CmdMachine::onServer(DebuggerProxy &proxy) { if (m_body == "rpc") { String host = m_rpcConfig[s_host].toString(); if (host.empty()) { register_intercept("", false, uninit_null()); } else { int port = m_rpcConfig[s_port].toInt32(); LibEventHttpClient::SetCache(host.data(), port, 1); register_intercept("", "fb_rpc_intercept_handler", m_rpcConfig); } return proxy.sendToClient(this); } if (m_body == "list") { Debugger::GetRegisteredSandboxes(m_sandboxes); return proxy.sendToClient(this); } if (m_body == "attach" && !m_sandboxes.empty()) { m_succeed = proxy.switchSandbox(m_sandboxes[0]->id(), m_force); if (m_succeed) { proxy.notifyDummySandbox(); m_exitInterrupt = true; } return proxy.sendToClient(this); } return false; }
void HphpdHook::onDefClass(const Class* cls) { // Make sure we have a proxy DebuggerProxy* proxy = Debugger::GetProxy().get(); if (proxy == nullptr) return; // If the proxy has enabled breakpoints that match entry into methods of // the given class, arrange for the VM to stop execution and notify the // debugger whenever execution enters one of these matched method. // This function is called once, when a class is first loaded, so it is not // performance critical. size_t numFuncs = cls->numMethods(); if (numFuncs == 0) return; auto clsName = cls->name(); std::vector<BreakPointInfoPtr> bps; proxy->getBreakPoints(bps); for (unsigned int i = 0; i < bps.size(); i++) { BreakPointInfoPtr bp = bps[i]; if (bp->m_state == BreakPointInfo::Disabled) continue; // TODO: check name space separately if (bp->getClass() != clsName->data()) continue; bp->m_bindState = BreakPointInfo::KnownToBeInvalid; for (size_t i = 0; i < numFuncs; ++i) { auto f = cls->getMethod(i); if (!matchFunctionName(bp->getFunction(), f)) continue; bp->m_bindState = BreakPointInfo::KnownToBeValid; phpAddBreakPointFuncEntry(f); } } }
bool CmdEval::onServer(DebuggerProxy &proxy) { PCFilter* locSave = g_vmContext->m_lastLocFilter; g_vmContext->m_lastLocFilter = new PCFilter(); g_vmContext->setDebuggerBypassCheck(m_bypassAccessCheck); DebuggerProxy::ExecutePHP(m_body, m_output, !proxy.isLocal(), m_frame); g_vmContext->setDebuggerBypassCheck(false); delete g_vmContext->m_lastLocFilter; g_vmContext->m_lastLocFilter = locSave; return proxy.sendToClient(this); }
DWORD DebuggerProxy::DebugPollProc( void* param ) { _ASSERT( param != NULL ); DebuggerProxy* pThis = (DebuggerProxy*) param; CoInitializeEx( NULL, COINIT_MULTITHREADED ); HRESULT hr = pThis->PollLoop(); CoUninitialize(); return hr; }
// NB: unlike most other commands, the client expects that more interrupts // can occur while we're doing the server-side work for an eval. bool CmdEval::onServer(DebuggerProxy &proxy) { PCFilter* locSave = g_vmContext->m_lastLocFilter; g_vmContext->m_lastLocFilter = new PCFilter(); g_vmContext->setDebuggerBypassCheck(m_bypassAccessCheck); proxy.ExecutePHP(m_body, m_output, m_frame, m_failed, DebuggerProxy::ExecutePHPFlagsAtInterrupt | (!proxy.isLocal() ? DebuggerProxy::ExecutePHPFlagsLog : DebuggerProxy::ExecutePHPFlagsNone)); g_vmContext->setDebuggerBypassCheck(false); delete g_vmContext->m_lastLocFilter; g_vmContext->m_lastLocFilter = locSave; return proxy.sendToClient(this); }
// NB: unlike most other commands, the client expects that more interrupts // can occur while we're doing the server-side work for an eval. bool CmdEval::onServer(DebuggerProxy &proxy) { PCFilter locSave; RequestInjectionData &rid = ThreadInfo::s_threadInfo->m_reqInjectionData; locSave.swap(rid.m_flowFilter); g_context->debuggerSettings.bypassCheck = m_bypassAccessCheck; proxy.ExecutePHP(m_body, m_output, m_frame, m_failed, DebuggerProxy::ExecutePHPFlagsAtInterrupt | (!proxy.isLocal() ? DebuggerProxy::ExecutePHPFlagsLog : DebuggerProxy::ExecutePHPFlagsNone)); g_context->debuggerSettings.bypassCheck = false; locSave.swap(rid.m_flowFilter); return proxy.sendToClient(this); }
bool CmdPrint::onServer(DebuggerProxy &proxy) { PCFilter* locSave = g_vmContext->m_lastLocFilter; g_vmContext->m_lastLocFilter = new PCFilter(); g_vmContext->setDebuggerBypassCheck(m_bypassAccessCheck); { EvalBreakControl eval(m_noBreak); m_ret = DebuggerProxy::ExecutePHP(DebuggerProxy::MakePHPReturn(m_body), m_output, !proxy.isLocal(), m_frame); } g_vmContext->setDebuggerBypassCheck(false); delete g_vmContext->m_lastLocFilter; g_vmContext->m_lastLocFilter = locSave; return proxy.sendToClient(this); }
void CmdOut::onBeginInterrupt(DebuggerProxy &proxy, CmdInterrupt &interrupt) { TRACE(2, "CmdNext::onBeginInterrupt\n"); assert(!m_complete); // Complete cmds should not be asked to do work. if (interrupt.getInterruptType() == ExceptionThrown) { // If an exception is thrown we turn interrupts on to ensure we stop when // control reaches the first catch clause. removeLocationFilter(); m_needsVMInterrupt = true; return; } int currentVMDepth = g_vmContext->m_nesting; int currentStackDepth = proxy.getStackDepth(); // Deeper or same depth? Keep running. if ((currentVMDepth > m_vmDepth) || ((currentVMDepth == m_vmDepth) && (currentStackDepth >= m_stackDepth))) { return; } TRACE(2, "CmdOut: shallower stack depth, done.\n"); cleanupStepOuts(); m_complete = (decCount() == 0); if (!m_complete) { TRACE(2, "CmdOut: not complete, step out again.\n"); onSetup(proxy, interrupt); } m_needsVMInterrupt = false; }
bool CmdWhere::onServer(DebuggerProxy &proxy) { m_stacktrace = g_vmContext->debugBacktrace(false, true, false); if (!m_stackArgs) { processStackTrace(); } return proxy.sendToClient(this); }
void HphpdHook::onFileLoad(Unit* unit) { DebuggerProxy* proxy = Debugger::GetProxy().get(); if (proxy == nullptr) return; // Look up the proxy's breakpoints and add needed breakpoints to the passed // unit std::vector<BreakPointInfoPtr> bps; proxy->getBreakPoints(bps); for (unsigned int i = 0; i < bps.size(); i++) { BreakPointInfoPtr bp = bps[i]; if (BreakPointInfo::MatchFile(bp->m_file, unit->filepath()->toCppString())) { addBreakPointInUnit(bp, unit); } } }
void CmdOut::onBeginInterrupt(DebuggerProxy &proxy, CmdInterrupt &interrupt) { TRACE(2, "CmdNext::onBeginInterrupt\n"); assert(!m_complete); // Complete cmds should not be asked to do work. if (interrupt.getInterruptType() == ExceptionThrown) { // If an exception is thrown we turn interrupts on to ensure we stop when // control reaches the first catch clause. removeLocationFilter(); m_needsVMInterrupt = true; return; } int currentVMDepth = g_vmContext->m_nesting; int currentStackDepth = proxy.getStackDepth(); if (currentVMDepth < m_vmDepth) { // Cut corner here, just break when cross VM boundary no matter how // many levels we want to go out of TRACE(2, "CmdOut: shallower VM depth, done.\n"); cleanupStepOuts(); m_complete = true; } else if ((currentVMDepth == m_vmDepth) && (currentStackDepth < m_stackDepth)) { TRACE(2, "CmdOut: same VM depth, shallower stack depth, done.\n"); cleanupStepOuts(); m_complete = (decCount() == 0); if (!m_complete) { TRACE(2, "CmdOut: not complete, step out again.\n"); setupStepOuts(); } } m_needsVMInterrupt = false; }
bool CmdVariable::onServer(DebuggerProxy &proxy) { if (m_type == KindOfVariableAsync) { //we only do variable inspection on continuation wait handles auto frame = getWaitHandleAtAsyncStackPosition(m_frame); if (frame != nullptr) { auto fp = frame->actRec(); if (fp != nullptr) { m_variables = getVariables(fp); } } } else if (m_frame < 0) { m_variables = g_context->m_globalVarEnv->getDefinedVariables(); m_global = true; } else { m_variables = g_context->getLocalDefinedVariables(m_frame); m_global = g_context->getVarEnv(m_frame) == g_context->m_globalVarEnv; } if (m_global) { m_variables.remove(s_GLOBALS); } if (m_version == 1) { // Remove the values before sending to client. ArrayInit ret(m_variables->size(), ArrayInit::Map{}); Variant v; for (ArrayIter iter(m_variables); iter; ++iter) { ret.add(iter.first().toString(), v); } m_variables = ret.toArray(); m_version = 2; } else if (m_version == 2) { // Remove entries that do not match a non empty m_varName. if (!m_varName.empty()) { ArrayInit ret(1, ArrayInit::Map{}); ret.add(m_varName, m_variables[m_varName]); m_variables = ret.toArray(); } // Remove entries whose name or contents do not match a non empty m_filter if (!m_filter.empty()) { ArrayInit ret(m_variables.size(), ArrayInit::Map{}); for (ArrayIter iter(m_variables); iter; ++iter) { String name = iter.first().toString(); if (name.find(m_filter, 0, false) < 0) { String fullvalue = DebuggerClient::FormatVariable(iter.second(), -1); if (fullvalue.find(m_filter, 0, false) < 0) { continue; } } ret.add(name, iter.second()); } m_variables = ret.toArray(); } } return proxy.sendToClient(this); }
void CmdOut::onSetup(DebuggerProxy &proxy, CmdInterrupt &interrupt) { TRACE(2, "CmdOut::onSetup\n"); assert(!m_complete); // Complete cmds should not be asked to do work. m_stackDepth = proxy.getStackDepth(); m_vmDepth = g_context->m_nesting; // Simply setup a "step out breakpoint" and let the program run. setupStepOuts(); }
bool CmdInstrument::onServer(DebuggerProxy &proxy) { m_instPoints = &m_ips; m_enabled = true; if (m_type == ActionRead) { readFromTable(proxy); } else if (m_type == ActionWrite) { validateAndWriteToTable(proxy); } return proxy.sendToClient(this); }
bool CmdWhere::onServer(DebuggerProxy &proxy) { if (m_type == KindOfWhereAsync) { m_stacktrace = createAsyncStacktrace(); } else { m_stacktrace = createBacktrace(BacktraceArgs() .withSelf() .ignoreArgs(!m_stackArgs)); } return proxy.sendToClient(this); }
void CmdInstrument::validateAndWriteToTable(DebuggerProxy &proxy) { if (!proxy.getInjTables()) { proxy.setInjTables(new InjectionTables()); } InjectionTableInt64* tablePC = nullptr; InjectionTableSD* tableFEntry = nullptr; for (int i = 0; i < (int)m_ips.size(); i++) { InstPointInfoPtr ipi = m_ips[i]; const Injection* inj = InjectionCache::GetInjection(ipi->m_code, ipi->m_desc); if (!inj) { // error in the code continue; } if (ipi->m_locType == InstPointInfo::LocHere || ipi->m_locType == InstPointInfo::LocFileLine) { // bytecode address const uchar *pc = ipi->lookupPC(); if (pc == nullptr) { continue; } if (tablePC == nullptr) { tablePC = new InjectionTableInt64(); } ipi->m_valid = true; (*tablePC)[(int64_t)pc] = inj; } if (ipi->m_locType == InstPointInfo::LocFuncEntry) { StackStringData sd(ipi->m_func.c_str(), ipi->m_func.size(), AttachLiteral); const StringData* sdCache = InjectionCache::GetStringData(&sd); if (tableFEntry == nullptr) { tableFEntry = new InjectionTableSD(); } ipi->m_valid = true; (*tableFEntry)[sdCache] = inj; } } proxy.getInjTables()->setInt64Table(InstHookTypeBCPC, tablePC); proxy.getInjTables()->setSDTable(InstHookTypeFuncEntry, tableFEntry); proxy.writeInjTablesToThread(); }
void CmdInstrument::readFromTable(DebuggerProxy &proxy) { proxy.readInjTablesFromThread(); m_ips.clear(); if (!proxy.getInjTables()) { // nothing there return; } // Bytecode address InjectionTableInt64* tablePC = proxy.getInjTables()->getInt64Table(InstHookTypeBCPC); if (tablePC) { for (InjectionTableInt64::const_iterator it = tablePC->begin(); it != tablePC->end(); ++it) { const Injection* inj = it->second; InstPointInfoPtr ipi(new InstPointInfo()); ipi->m_valid = true; if (inj->m_desc) { ipi->m_desc = inj->m_desc->data(); } ipi->m_locType = InstPointInfo::LocFileLine; // TODO use pc to figure out m_file and m_line // uchar* pc = (uchar*)it->first; m_ips.push_back(ipi); } } InjectionTableSD* tableFEntry = proxy.getInjTables()->getSDTable(InstHookTypeFuncEntry); if (tableFEntry) { for (InjectionTableSD::const_iterator it = tableFEntry->begin(); it != tableFEntry->end(); ++it) { const Injection* inj = it->second; InstPointInfoPtr ipi(new InstPointInfo()); ipi->m_valid = true; if (inj->m_desc) { ipi->m_desc = inj->m_desc->data(); } ipi->m_func = it->first->data(); ipi->m_locType = InstPointInfo::LocFuncEntry; m_ips.push_back(ipi); } } }
void CmdNext::onSetup(DebuggerProxy& proxy, CmdInterrupt& interrupt) { TRACE(2, "CmdNext::onSetup\n"); assert(!m_complete); // Complete cmds should not be asked to do work. m_stackDepth = proxy.getStackDepth(); m_vmDepth = g_vmContext->m_nesting; m_loc = interrupt.getFileLine(); ActRec *fp = g_vmContext->getFP(); assert(fp); // All interrupts which reach a flow cmd have an AR. PC pc = g_vmContext->getPC(); stepCurrentLine(interrupt, fp, pc); }
// Tries to read the contents of the file whose path is specified in m_file. // If the path cannot be resolved and is relative, the path of the sandbox // is used to qualify the relative path. If the contents cannot be retrieved // m_code will be an empty string. // The function returns false if the reply to the client fails during the // sending process. bool CmdList::onServer(DebuggerProxy &proxy) { auto savedWarningFrequency = RuntimeOption::WarningFrequency; RuntimeOption::WarningFrequency = 0; m_code = f_file_get_contents(m_file.c_str()); if (!proxy.isLocal() && !m_code.toBoolean() && m_file[0] != '/') { DSandboxInfo info = proxy.getSandbox(); if (info.m_path.empty()) { raise_warning("path for sandbox %s is not setup, run a web request", info.desc().c_str()); } else { std::string full_path = info.m_path + m_file; m_code = f_file_get_contents(full_path.c_str()); } } RuntimeOption::WarningFrequency = savedWarningFrequency; if (!m_code.toBoolean() && m_file == "systemlib.php") { m_code = SystemLib::s_source; } return proxy.sendToClient((DebuggerCommand*)this); }
bool CmdWhere::onServer(DebuggerProxy &proxy) { if (m_type == KindOfWhereAsync) { m_stacktrace = createAsyncStacktrace(); } else { m_stacktrace = g_vmContext->debugBacktrace(false, true, false); if (!m_stackArgs) { removeArgs(); } } return proxy.sendToClient(this); }
bool CmdShell::onServer(DebuggerProxy &proxy) { const char **argv = (const char **)malloc((m_args.size() + 1) * sizeof(char*)); for (unsigned int i = 0; i < m_args.size(); i++) { argv[i] = (char*)m_args[i].c_str(); } argv[m_args.size()] = nullptr; Process::Exec(argv[0], argv, nullptr, m_out, &m_out, true); free(argv); return proxy.sendToClient(this); }
void CmdNext::onSetup(DebuggerProxy &proxy, CmdInterrupt &interrupt) { TRACE(2, "CmdNext::onSetup\n"); assert(!m_complete); // Complete cmds should not be asked to do work. CmdFlowControl::onSetup(proxy, interrupt); m_stackDepth = proxy.getStackDepth(); m_vmDepth = g_vmContext->m_nesting; m_loc = interrupt.getFileLine(); // Start by single-stepping the current line. installLocationFilterForLine(interrupt.getSite()); m_needsVMInterrupt = true; }
bool CmdInternalTesting::onServer(DebuggerProxy &proxy) { TRACE(2, "CmdInternalTesting::onServer\n"); if (m_arg == "badcmdtypereceive") { m_type = KindOfInternalTestingBad; // Send back a bad cmd. } else if (m_arg == "shortcmdreceive") { m_arg = "shortcmd"; // Force send to drop a field } else if (m_arg == "segfaultServer") { int *px = nullptr; *px = 42; } return proxy.sendToClient(this); }
bool CmdGlobal::onServer(DebuggerProxy &proxy) { m_globals = CmdVariable::GetGlobalVariables(); if (m_version == 2) { // Remove the values before sending to client. ArrayInit ret(m_globals->size(), ArrayInit::Map{}); Variant v; for (ArrayIter iter(m_globals); iter; ++iter) { ret.add(iter.first().toString(), v); } m_globals = ret.toArray(); } return proxy.sendToClient(this); }
bool CmdHeaptrace::onServer(DebuggerProxy &proxy) { // globals std::vector<TypedValue *> roots; CArrRef arr = g_vmContext->m_globalVarEnv->getDefinedVariables(); arr->getChildren(roots); // static properties for (AllClasses ac; !ac.empty();) { Class *c = ac.popFront(); c->getChildren(roots); } // locals int numFrames = proxy.getRealStackDepth(); std::vector<Array> locs; for (int i = 0; i < numFrames; ++i) { locs.push_back(g_vmContext->getLocalDefinedVariables(i)); } for (CArrRef locArr : locs) { locArr->getChildren(roots); } Tracer<Accum>::traceAll( roots, [](TypedValue *node, Accum &accum) { accum.typesMap[(int64_t)node] = (int8_t)node->m_type; accum.sizeMap[(int64_t)node] = (int64_t)MemoryProfile::getSizeOfTV(node); }, [](TypedValue *parent, TypedValue *child, Accum &accum) { accum.adjacencyList[(int64_t)parent].push_back((int64_t)child); }, m_accum ); return proxy.sendToClient(this); }
void CmdOut::onBeginInterrupt(DebuggerProxy &proxy, CmdInterrupt &interrupt) { TRACE(2, "CmdOut::onBeginInterrupt\n"); assert(!m_complete); // Complete cmds should not be asked to do work. m_needsVMInterrupt = false; if (m_skippingOverPopR) { m_complete = true; return; } int currentVMDepth = g_context->m_nesting; int currentStackDepth = proxy.getStackDepth(); // Deeper or same depth? Keep running. if ((currentVMDepth > m_vmDepth) || ((currentVMDepth == m_vmDepth) && (currentStackDepth >= m_stackDepth))) { TRACE(2, "CmdOut: deeper, keep running...\n"); return; } if (interrupt.getInterruptType() == ExceptionHandler) { // If we're about to enter an exception handler we turn interrupts on to // ensure we stop when control reaches the handler. The normal logic below // will decide if we're done at that point or not. TRACE(2, "CmdOut: exception thrown\n"); removeLocationFilter(); m_needsVMInterrupt = true; return; } TRACE(2, "CmdOut: shallower stack depth, done.\n"); cleanupStepOuts(); int depth = decCount(); if (depth == 0) { PC pc = g_context->getPC(); // Step over PopR following a call if (toOp(*pc) == OpPopR) { m_skippingOverPopR = true; m_needsVMInterrupt = true; } else { m_complete = true; } return; } else { TRACE(2, "CmdOut: not complete, step out again.\n"); onSetup(proxy, interrupt); } }
void CmdNext::onSetup(DebuggerProxy& proxy, CmdInterrupt& interrupt) { TRACE(2, "CmdNext::onSetup\n"); assertx(!m_complete); // Complete cmds should not be asked to do work. m_stackDepth = proxy.getStackDepth(); m_vmDepth = g_context->m_nesting; m_loc = interrupt.getFileLine(); ActRec *fp = vmfp(); if (!fp) { // If we have no frame just wait for the next instruction to be interpreted. m_needsVMInterrupt = true; return; } PC pc = vmpc(); stepCurrentLine(interrupt, fp, pc); }
bool CmdThread::onServer(DebuggerProxy &proxy) { if (m_body == "info") { // collect info InfoVec info; debuggerInfo(info); Transport *transport = g_context->getTransport(); if (transport) { transport->debuggerInfo(info); } else { Add(info, "Thread Type", proxy.getThreadType()); } g_context->debuggerInfo(info); m_out = DebuggerClient::FormatInfoVec(info); return proxy.sendToClient(this); } if (m_body == "list") { proxy.getThreads(m_threads); return proxy.sendToClient(this); } if (m_body == "switch") { if (!m_threads.empty()) { proxy.switchThread(m_threads[0]); m_exitInterrupt = true; return true; } } if (m_body == "normal") { proxy.switchThreadMode(DebuggerProxy::Normal); return true; } if (m_body == "sticky") { proxy.switchThreadMode(DebuggerProxy::Sticky); return true; } if (m_body == "exclusive") { proxy.switchThreadMode(DebuggerProxy::Exclusive); return true; } assert(false); return false; }
bool CmdVariable::onServer(DebuggerProxy &proxy) { if (m_frame < 0) { m_variables = g_vmContext->m_globalVarEnv->getDefinedVariables(); m_global = true; } else { m_variables = g_vmContext->getLocalDefinedVariables(m_frame); m_global = g_vmContext->getVarEnv(m_frame) == g_vmContext->m_globalVarEnv; } if (m_global) { m_variables.remove(s_GLOBALS); } if (m_version == 1) { // Remove the values before sending to client. ArrayInit ret(m_variables->size()); Variant v; for (ArrayIter iter(m_variables); iter; ++iter) { ret.add(iter.first().toString(), v); } m_variables = ret.toArray(); m_version = 2; } else if (m_version == 2) { // Remove entries that do not match a non empty m_varName. if (!m_varName.empty()) { ArrayInit ret(1); ret.add(m_varName, m_variables[m_varName]); m_variables = ret.toArray(); } // Remove entries whose name or contents do not match a non empty m_filter if (!m_filter.empty()) { ArrayInit ret(1); for (ArrayIter iter(m_variables); iter; ++iter) { String name = iter.first().toString(); if (name.find(m_filter, 0, false) < 0) { String fullvalue = DebuggerClient::FormatVariable(iter.second(), -1); if (fullvalue.find(m_filter, 0, false) < 0) { continue; } } ret.add(name, iter.second()); } m_variables = ret.toArray(); } } return proxy.sendToClient(this); }
void CmdNext::onBeginInterrupt(DebuggerProxy &proxy, CmdInterrupt &interrupt) { TRACE(2, "CmdNext::onBeginInterrupt\n"); assert(!m_complete); // Complete cmds should not be asked to do work. if (interrupt.getInterruptType() == ExceptionThrown) { // If an exception is thrown we turn interrupts on to ensure we stop when // control reaches the first catch clause. removeLocationFilter(); m_needsVMInterrupt = true; return; } int currentVMDepth = g_vmContext->m_nesting; int currentStackDepth = proxy.getStackDepth(); if ((currentVMDepth < m_vmDepth) || ((currentVMDepth == m_vmDepth) && (currentStackDepth <= m_stackDepth))) { // We're at the same depth as when we started, or perhaps even shallower, so // there's no need for any step out breakpoint anymore. cleanupStepOut(); if (m_loc != interrupt.getFileLine()) { TRACE(2, "CmdNext: same depth, off original line.\n"); m_complete = (decCount() == 0); } if (!m_complete) { TRACE(2, "CmdNext: not complete, filter new line and keep stepping.\n"); m_loc = interrupt.getFileLine(); installLocationFilterForLine(interrupt.getSite()); m_needsVMInterrupt = true; } } else { // Deeper, so let's setup a step out operation and turn interrupts off. if (!m_stepOutUnit) { // We can nuke the entire location filter here since we'll re-install it // when we get back to the old level. Keeping it installed may be more // efficient if we were on a large line, but there is a penalty for every // opcode executed while it's installed and that's bad if there's a lot of // code called from that line. removeLocationFilter(); setupStepOut(); } m_needsVMInterrupt = false; } }