void CmdStep::onBeginInterrupt(DebuggerProxy &proxy, CmdInterrupt &interrupt) { // Step doesn't care about this interrupt... we just stay the course and // keep stepping. if (interrupt.getInterruptType() == ExceptionHandler) return; // Don't step into generated functions, keep looking. if (interrupt.getSite()->getLine0() == 0) return; m_complete = (decCount() == 0); if (!m_complete) { installLocationFilterForLine(interrupt.getSite()); } }
void CmdNext::stepCurrentLine(CmdInterrupt& interrupt, ActRec* fp, PC pc) { // Special handling for yields from generators. The destination of these // instructions is somewhat counter intuitive so we take care to ensure that // we step to the most appropriate place. For yeilds, we want to land on the // next statement when driven from a C++ iterator like ASIO. If the generator // is driven directly from PHP (i.e., a loop calling send($foo)) then we'll // land back at the callsite of send(). For returns from generators, we follow // the execution stack for now, and end up at the caller of ASIO or send(). auto op = toOp(*pc); if (fp->m_func->isGenerator() && (op == OpContSuspend || op == OpContSuspendK || op == OpContRetC)) { TRACE(2, "CmdNext: encountered yield or return from generator\n"); // Patch the projected return point(s) in both cases, to catch if we exit // the the asio iterator or if we are being iterated directly by PHP. setupStepOuts(); op = toOp(*pc); if (op == OpContSuspend || op == OpContSuspendK) { // Patch the next normal execution point so we can pickup the stepping // from there if the caller is C++. setupStepCont(fp, pc); } removeLocationFilter(); return; } installLocationFilterForLine(interrupt.getSite()); m_needsVMInterrupt = true; }
// Do not allow further breaks on the site of cmd, except during // calls made from the current site. void DebuggerProxy::unsetBreakableForBreakpointsMatching(CmdInterrupt& cmd) { TRACE(2, "DebuggerProxy::unsetBreakableForBreakpointsMatching\n"); auto stackDepth = getRealStackDepth(); for (unsigned int i = 0; i < m_breakpoints.size(); ++i) { BreakPointInfoPtr bp = m_breakpoints[i]; if (bp != nullptr && bp->m_state != BreakPointInfo::Disabled && bp->match(*this, cmd.getInterruptType(), *cmd.getSite())) { bp->unsetBreakable(stackDepth); } } }
void DebuggerProxy::changeBreakPointDepth(CmdInterrupt& cmd) { TRACE(2, "DebuggerProxy::changeBreakPointDepth\n"); for (unsigned int i = 0; i < m_breakpoints.size(); ++i) { // if the site changes, then update the breakpoint depth BreakPointInfoPtr bp = m_breakpoints[i]; if (bp->m_state != BreakPointInfo::Disabled && !bp->match(cmd.getInterruptType(), *cmd.getSite())) { m_breakpoints[i]->changeBreakPointDepth(getRealStackDepth()); } } }
// There could be multiple breakpoints at one place but we can manage this // with only one breakpoint. BreakPointInfoPtr DebuggerProxy::getBreakPointAtCmd(CmdInterrupt& cmd) { TRACE(2, "DebuggerProxy::getBreakPointAtCmd\n"); for (unsigned int i = 0; i < m_breakpoints.size(); ++i) { BreakPointInfoPtr bp = m_breakpoints[i]; if (bp->m_state != BreakPointInfo::Disabled && bp->match(cmd.getInterruptType(), *cmd.getSite())) { return bp; } } return BreakPointInfoPtr(); }
void CmdStep::onSetup(DebuggerProxy *proxy, CmdInterrupt &interrupt) { assert(!m_complete); // Complete cmds should not be asked to do work. CmdFlowControl::onSetup(proxy, interrupt); // Allows a breakpoint on this same line to be hit again when control returns // from function call. BreakPointInfoPtr bp = proxy->getBreakPointAtCmd(interrupt); if (bp) { bp->setBreakable(proxy->getRealStackDepth()); } installLocationFilterForLine(interrupt.getSite()); m_needsVMInterrupt = true; }
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; }
void CmdNext::stepCurrentLine(CmdInterrupt& interrupt, ActRec* fp, PC pc) { // Special handling for yields from generators and awaits from // async. The destination of these instructions is somewhat counter // intuitive so we take care to ensure that we step to the most // appropriate place. For yields, we want to land on the next // statement when driven from a C++ iterator like ASIO. If the // generator is driven directly from PHP (i.e., a loop calling // send($foo)) then we'll land back at the callsite of send(). For // returns from generators, we follow the execution stack for now, // and end up at the caller of ASIO or send(). For async functions // stepping over an await, we land on the next statement. auto const op = peek_op(pc); if (op == OpAwait) { assertx(fp->func()->isAsync()); auto wh = c_Awaitable::fromCell(*vmsp()); if (wh && !wh->isFinished()) { TRACE(2, "CmdNext: encountered blocking await\n"); if (fp->resumed()) { setupStepSuspend(fp, pc); removeLocationFilter(); } else { // Eager execution in non-resumed mode is supported only by async // functions. We need to step over this opcode, then grab the created // AsyncFunctionWaitHandle and setup stepping like we do for // OpAwait. assertx(fp->func()->isAsyncFunction()); m_skippingAwait = true; m_needsVMInterrupt = true; removeLocationFilter(); } return; } } else if (op == OpYield || op == OpYieldK) { assertx(fp->resumed()); assertx(fp->func()->isGenerator()); TRACE(2, "CmdNext: encountered yield from generator\n"); setupStepOuts(); setupStepSuspend(fp, pc); removeLocationFilter(); return; } else if (op == OpRetC && fp->resumed()) { assertx(fp->func()->isResumable()); TRACE(2, "CmdNext: encountered return from resumed resumable\n"); setupStepOuts(); removeLocationFilter(); return; } installLocationFilterForLine(interrupt.getSite()); m_needsVMInterrupt = true; }
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; } }
void CmdNext::stepCurrentLine(CmdInterrupt& interrupt, ActRec* fp, PC pc) { // Special handling for yields from generators and awaits from // async. The destination of these instructions is somewhat counter // intuitive so we take care to ensure that we step to the most // appropriate place. For yields, we want to land on the next // statement when driven from a C++ iterator like ASIO. If the // generator is driven directly from PHP (i.e., a loop calling // send($foo)) then we'll land back at the callsite of send(). For // returns from generators, we follow the execution stack for now, // and end up at the caller of ASIO or send(). For async functions // stepping over an await, we land on the next statement. auto op = *reinterpret_cast<const Op*>(pc); if (fp->resumed() && (op == OpYield || op == OpYieldK || op == OpAsyncSuspend || op == OpRetC)) { TRACE(2, "CmdNext: encountered yield, await or return from generator\n"); // Patch the projected return point(s) in both cases for // generators, to catch if we exit the the asio iterator or if we // are being iterated directly by PHP. if ((op == OpRetC) || !fp->m_func->isAsync()) setupStepOuts(); op = *reinterpret_cast<const Op*>(pc); if (op == OpAsyncSuspend || op == OpYield || op == OpYieldK) { // Patch the next normal execution point so we can pickup the stepping // from there if the caller is C++. setupStepCont(fp, pc); } removeLocationFilter(); return; } else if (op == OpAsyncSuspend) { // We need to step over this opcode, then grab the continuation // and setup continuation stepping like we do for OpYield. TRACE(2, "CmdNext: encountered create async\n"); m_skippingAsyncSuspend = true; m_needsVMInterrupt = true; removeLocationFilter(); return; } installLocationFilterForLine(interrupt.getSite()); m_needsVMInterrupt = true; }
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; }
void CmdStep::onBeginInterrupt(DebuggerProxy *proxy, CmdInterrupt &interrupt) { m_complete = (decCount() == 0); if (!m_complete) { installLocationFilterForLine(interrupt.getSite()); } }
// 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 } }