void DebuggerProxy::processInterrupt(CmdInterrupt &cmd) { TRACE_RB(2, "DebuggerProxy::processInterrupt\n"); // Do the server-side work for this interrupt, which just notifies the client. if (!cmd.onServer(*this)) { TRACE_RB(1, "Failed to send CmdInterrupt to client\n"); Debugger::UsageLog("server", getSandboxId(), "ProxyError", "Send interrupt"); stopAndThrow(); } Debugger::UsageLogInterrupt("server", getSandboxId(), cmd); // Wait for commands from the debugger client and process them. We'll stay // here until we get a command that should cause the thread to continue. while (true) { DebuggerCommandPtr res; while (!DebuggerCommand::Receive(m_thrift, res, "DebuggerProxy::processInterrupt()")) { // we will wait forever until DebuggerClient sends us something checkStop(); } checkStop(); if (res) { TRACE_RB(2, "Proxy got cmd type %d\n", res->getType()); Debugger::UsageLog("server", getSandboxId(), folly::to<std::string>(res->getType())); // Any control flow command gets installed here and we continue execution. m_flow = std::dynamic_pointer_cast<CmdFlowControl>(res); if (m_flow) { m_flow->onSetup(*this, cmd); if (!m_flow->complete()) { TRACE_RB(2, "Incomplete flow command %d remaining on proxy for " "further processing\n", m_flow->getType()); if (m_threadMode == Normal) { // We want the flow command to complete on the thread that // starts it. switchThreadMode(Sticky); } } else { // The flow cmd has determined that it is done with its work and // doesn't need to remain for later processing. TRACE_RB(2, "Flow command %d completed\n", m_flow->getType()); m_flow.reset(); } return; } if (res->is(DebuggerCommand::KindOfQuit)) { TRACE_RB(2, "Received quit command\n"); res->onServer(*this); // acknowledge receipt so that client can quit. stopAndThrow(); } } bool cmdFailure = false; try { // Perform the server-side work for this command. if (res) { if (!res->onServer(*this)) { TRACE_RB(1, "Failed to execute cmd %d from client\n", res->getType()); Debugger::UsageLog("server", getSandboxId(), "ProxyError", "Command failed"); cmdFailure = true; } } else { TRACE_RB(1, "Failed to receive cmd from client\n"); Debugger::UsageLog("server", getSandboxId(), "ProxyError", "Command receive failed"); cmdFailure = true; } } catch (const DebuggerException &e) { throw; } catch (const std::exception& e) { Logger::Warning(DEBUGGER_LOG_TAG "Cmd type %d onServer() threw exception %s", res->getType(), e.what()); Debugger::UsageLog("server", getSandboxId(), "ProxyError", "Command exception"); cmdFailure = true; } catch (...) { Logger::Warning(DEBUGGER_LOG_TAG "Cmd type %d onServer() threw non standard exception", res->getType()); Debugger::UsageLog("server", getSandboxId(), "ProxyError", "Command exception"); cmdFailure = true; } if (cmdFailure) stopAndThrow(); if (res->shouldExitInterrupt()) return; } }
// Handle an interrupt from the VM. void DebuggerProxy::interrupt(CmdInterrupt &cmd) { TRACE_RB(2, "DebuggerProxy::interrupt\n"); // Make any breakpoints that have passed breakable again. setBreakableForBreakpointsNotMatching(cmd); // At this point we have an interrupt, but we don't know if we're on the // thread the proxy considers "current". // NB: BreakPointReached really means we've got control of a VM thread from // the opcode hook. This could be for a breakpoint, stepping, etc. // Wait until this thread is the one this proxy wants to debug. if (!blockUntilOwn(cmd, true)) { return; } // We know we're on the "current" thread, so we can process any active flow // command, stop if we're at a breakpoint, handle other interrupts, etc. if (checkFlowBreak(cmd)) { // We've hit a breakpoint and now need to make sure that breakpoints // wont be hit again for this site until control leaves this site. // (Breakpoints can still get hit if control reaches this site during // a call that is part of this site because the flags are stacked.) unsetBreakableForBreakpointsMatching(cmd); while (true) { try { // We're about to send the client an interrupt and start // waiting for commands back from it. Disable signal polling // during this time, since our protocol requires that only one // thread talk to the client at a time. disableSignalPolling(); SCOPE_EXIT { enableSignalPolling(); }; processInterrupt(cmd); } catch (const DebuggerException &e) { switchThreadMode(Normal); throw; } catch (...) { assert(false); // no other exceptions should be seen here switchThreadMode(Normal); throw; } if (cmd.getInterruptType() == PSPEnded) { switchThreadMode(Normal); return; // we are done with this thread } if (!m_newThread) { break; // we're not switching threads } switchThreadMode(Normal, m_newThread->m_id); blockUntilOwn(cmd, false); } } if (m_threadMode == Normal) { Lock lock(this); m_thread = 0; notify(); } else if (cmd.getInterruptType() == PSPEnded) { switchThreadMode(Normal); // we are done with this thread } }
// Checks whether the cmd has any breakpoints that match the current Site. // Also returns true for cmds that should always break, like SessionStarted, // and returns true when we have special modes setup for, say, breaking on // RequestEnded, PSPEnded, etc. bool DebuggerProxy::checkBreakPoints(CmdInterrupt &cmd) { TRACE(2, "DebuggerProxy::checkBreakPoints\n"); ReadLock lock(m_breakMutex); return cmd.shouldBreak(*this, m_breakpoints, getRealStackDepth()); }
void CmdNext::onBeginInterrupt(DebuggerProxy& proxy, CmdInterrupt& interrupt) { TRACE(2, "CmdNext::onBeginInterrupt\n"); assert(!m_complete); // Complete cmds should not be asked to do work. ActRec *fp = g_context->getFP(); if (!fp) { // If we have no frame just wait for the next instruction to be interpreted. m_needsVMInterrupt = true; return; } PC pc = g_context->getPC(); Unit* unit = fp->m_func->unit(); Offset offset = unit->offsetOf(pc); TRACE(2, "CmdNext: pc %p, opcode %s at '%s' offset %d\n", pc, opcodeToName(*reinterpret_cast<const Op*>(pc)), fp->m_func->fullName()->data(), offset); int currentVMDepth = g_context->m_nesting; int currentStackDepth = proxy.getStackDepth(); TRACE(2, "CmdNext: original depth %d:%d, current depth %d:%d\n", m_vmDepth, m_stackDepth, currentVMDepth, currentStackDepth); // Where are we on the stack now vs. when we started? Breaking the answer down // into distinct variables helps the clarity of the algorithm below. bool deeper = false; bool originalDepth = false; if ((currentVMDepth == m_vmDepth) && (currentStackDepth == m_stackDepth)) { originalDepth = true; } else if ((currentVMDepth > m_vmDepth) || ((currentVMDepth == m_vmDepth) && (currentStackDepth > m_stackDepth))) { deeper = true; } m_needsVMInterrupt = false; // Will be set again below if still needed. // First consider if we've got internal breakpoints setup. These are used when // we can make an accurate prediction of where execution should flow, // eventually, and when we want to let the program run normally until we get // there. if (hasStepOuts() || hasStepCont()) { TRACE(2, "CmdNext: checking internal breakpoint(s)\n"); if (atStepOutOffset(unit, offset)) { if (deeper) return; // Recursion TRACE(2, "CmdNext: hit step-out\n"); } else if (atStepContOffset(unit, offset)) { if (m_stepContTag != getResumableTag(fp)) return; TRACE(2, "CmdNext: hit step-cont\n"); // We're in the continuation we expect. This may be at a // different stack depth, though, especially if we've moved from // the original function to the continuation. Update the depth // accordingly. if (!originalDepth) { m_vmDepth = currentVMDepth; m_stackDepth = currentStackDepth; deeper = false; originalDepth = true; } } else if (interrupt.getInterruptType() == ExceptionHandler) { // Entering an exception handler may take us someplace we weren't // expecting. Adjust internal breakpoints accordingly. First case is easy. if (deeper) { TRACE(2, "CmdNext: exception handler, deeper\n"); return; } // For step-conts, we ignore handlers at the original level if we're not // in the original continuation. We don't care about exception handlers // in continuations being driven at the same level. if (hasStepCont() && originalDepth && (m_stepContTag != getResumableTag(fp))) { TRACE(2, "CmdNext: exception handler, original depth, wrong cont\n"); return; } // Sometimes we have handlers in generated code, i.e., Continuation::next. // These just help propagate exceptions so ignore those. if (fp->m_func->line1() == 0) { TRACE(2, "CmdNext: exception handler, ignoring func with no source\n"); return; } TRACE(2, "CmdNext: exception handler altering expected flow\n"); } else { // We have internal breakpoints setup, but we haven't hit one yet. Keep // running until we reach one. TRACE(2, "CmdNext: waiting to hit internal breakpoint...\n"); return; } // We've hit one internal breakpoint at a useful place, or decided we don't, // need them, so we can remove them all now. cleanupStepOuts(); cleanupStepCont(); } 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, "CmdNext: exception handler\n"); removeLocationFilter(); m_needsVMInterrupt = true; return; } if (m_skippingAsyncSuspend) { m_skippingAsyncSuspend = false; stepAfterAsyncSuspend(); return; } if (deeper) { TRACE(2, "CmdNext: deeper, setup step out to get back to original line\n"); setupStepOuts(); // 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(); return; } if (originalDepth && (m_loc == interrupt.getFileLine())) { TRACE(2, "CmdNext: not complete, still on same line\n"); stepCurrentLine(interrupt, fp, pc); return; } TRACE(2, "CmdNext: operation complete.\n"); m_complete = (decCount() == 0); if (!m_complete) { TRACE(2, "CmdNext: repeat count > 0, start fresh.\n"); onSetup(proxy, interrupt); } }
void CmdStep::onSetup(DebuggerProxy &proxy, CmdInterrupt &interrupt) { assert(!m_complete); // Complete cmds should not be asked to do work. installLocationFilterForLine(interrupt.getSite()); m_needsVMInterrupt = true; }
// Checks whether the cmd has any breakpoints that match the current Site. // Also returns true for cmds that have should always break. bool DebuggerProxy::checkBreakPoints(CmdInterrupt &cmd) { TRACE(2, "DebuggerProxy::checkBreakPoints\n"); ReadLock lock(m_breakMutex); return cmd.shouldBreak(m_breakpoints); }
// Handle an interrupt from the VM. void DebuggerProxy::interrupt(CmdInterrupt &cmd) { TRACE(2, "DebuggerProxy::interrupt\n"); changeBreakPointDepth(cmd); if (cmd.getInterruptType() == BreakPointReached) { if (!needInterrupt()) return; // NB: stepping is represented as a BreakPointReached interrupt. // Modify m_lastLocFilter to save current location. This will short-circuit // the work done up in phpDebuggerOpcodeHook() and ensure we don't break on // this line until we're completely off of it. InterruptSite *site = cmd.getSite(); if (g_vmContext->m_lastLocFilter) { g_vmContext->m_lastLocFilter->clear(); } else { g_vmContext->m_lastLocFilter = new VM::PCFilter(); } if (debug && Trace::moduleEnabled(Trace::bcinterp, 5)) { Trace::trace("prepare source loc filter\n"); const VM::OffsetRangeVec& offsets = site->getCurOffsetRange(); for (VM::OffsetRangeVec::const_iterator it = offsets.begin(); it != offsets.end(); ++it) { Trace::trace("block source loc in %s:%d: unit %p offset [%d, %d)\n", site->getFile(), site->getLine0(), site->getUnit(), it->m_base, it->m_past); } } g_vmContext->m_lastLocFilter->addRanges(site->getUnit(), site->getCurOffsetRange()); // If the breakpoint is not to be processed, we should continue execution. BreakPointInfoPtr bp = getBreakPointAtCmd(cmd); if (bp) { if (!bp->breakable(getRealStackDepth())) { return; } else { bp->unsetBreakable(getRealStackDepth()); } } } // Wait until this thread is the thread this proxy wants to debug. // NB: breakpoints and control flow checks happen here, too, and return false // if we're not done with the flow, or not at a breakpoint, etc. if (!blockUntilOwn(cmd, true)) { return; } if (processFlowBreak(cmd)) { while (true) { try { Lock lock(m_signalMutex); m_signum = CmdSignal::SignalNone; processInterrupt(cmd); } catch (const DebuggerException &e) { switchThreadMode(Normal); throw; } catch (...) { assert(false); // no other exceptions should be seen here switchThreadMode(Normal); throw; } if (cmd.getInterruptType() == PSPEnded) { switchThreadMode(Normal); return; // we are done with this thread } if (!m_newThread) { break; // we're not switching threads } switchThreadMode(Normal, m_newThread->m_id); blockUntilOwn(cmd, false); } } if (m_threadMode == Normal) { Lock lock(this); m_thread = 0; notify(); } else if (cmd.getInterruptType() == PSPEnded) { switchThreadMode(Normal); // we are done with this thread } }