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::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; }
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; }
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; }
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 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::onBeginInterrupt(DebuggerProxy& proxy, CmdInterrupt& interrupt) { TRACE(2, "CmdNext::onBeginInterrupt\n"); assertx(!m_complete); // Complete cmds should not be asked to do work. 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(); 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(peek_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() || hasStepResumable()) { 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 (atStepResumableOffset(unit, offset)) { if (m_stepResumableId != getResumableId(fp)) return; TRACE(2, "CmdNext: hit step-cont\n"); // We're in the resumable we expect. This may be at a // different stack depth, though, especially if we've moved from // the original function to the resumable. 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 resumable. We don't care about exception handlers // in resumables being driven at the same level. if (hasStepResumable() && originalDepth && (m_stepResumableId != getResumableId(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; } if (fp->m_func->isBuiltin()) { TRACE(2, "CmdNext: exception handler, ignoring builtin functions\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(); cleanupStepResumable(); } 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_skippingAwait) { m_skippingAwait = false; stepAfterAwait(); 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); } }
CmdFlowControl::~CmdFlowControl() { // Remove any location filter that may have been setup. removeLocationFilter(); }