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; }
// Place internal breakpoints to get out of the current function. This may place // multiple internal breakpoints, and it may place them more than one frame up. // Some instructions can cause PHP to be invoked without an explicit call. A set // which causes a destructor to run, a iteration init which causes an object's // next() method to run, a RetC which causes destructors to run, etc. This // recgonizes such cases and ensures we have internal breakpoints to cover the // destination(s) of such instructions. void CmdFlowControl::setupStepOuts() { // Existing step outs should be cleaned up before making new ones. assert(!hasStepOuts()); ActRec* fp = g_vmContext->getFP(); assert(fp); Offset returnOffset; bool fromVMEntry; while (!hasStepOuts()) { fp = g_vmContext->getPrevVMState(fp, &returnOffset, nullptr, &fromVMEntry); // If we've run off the top of the stack, just return having setup no // step outs. This will cause cmds like Next and Out to just let the program // run, which is appropriate. if (!fp) break; Unit* returnUnit = fp->m_func->unit(); PC returnPC = returnUnit->at(returnOffset); TRACE(2, "CmdFlowControl::setupStepOuts: at '%s' offset %d opcode %s\n", fp->m_func->fullName()->data(), returnOffset, opcodeToName(toOp(*returnPC))); // Don't step out to generated functions, keep looking. if (fp->m_func->line1() == 0) continue; if (fromVMEntry) { TRACE(2, "CmdFlowControl::setupStepOuts: VM entry\n"); // We only execute this for opcodes which invoke more PHP, and that does // not include switches. Thus, we'll have at most two destinations. assert(!isSwitch(*returnPC) && (numSuccs((Op*)returnPC) <= 2)); // Set an internal breakpoint after the instruction if it can fall thru. if (instrAllowsFallThru(toOp(*returnPC))) { Offset nextOffset = returnOffset + instrLen((Op*)returnPC); TRACE(2, "CmdFlowControl: step out to '%s' offset %d (fall-thru)\n", fp->m_func->fullName()->data(), nextOffset); m_stepOut1 = StepDestination(returnUnit, nextOffset); } // Set an internal breakpoint at the target of a control flow instruction. // A good example of a control flow op that invokes PHP is IterNext. if (instrIsControlFlow(toOp(*returnPC))) { Offset target = instrJumpTarget((Op*)returnPC, 0); if (target != InvalidAbsoluteOffset) { Offset targetOffset = returnOffset + target; TRACE(2, "CmdFlowControl: step out to '%s' offset %d (jump target)\n", fp->m_func->fullName()->data(), targetOffset); m_stepOut2 = StepDestination(returnUnit, targetOffset); } } // If we have no place to step out to, then unwind another frame and try // again. The most common case that leads here is Ret*, which does not // fall-thru and has no encoded target. } else { TRACE(2, "CmdFlowControl: step out to '%s' offset %d\n", fp->m_func->fullName()->data(), returnOffset); m_stepOut1 = StepDestination(returnUnit, returnOffset); } } }
// Compute the bytecode offset at which execution will resume when // this continuation resumes. Only valid on started but not actually // running continuations. Offset c_Continuation::getNextExecutionOffset() const { assert(started() && !running()); auto func = m_arPtr->m_func; PC funcBase = func->unit()->entry() + func->base(); assert(toOp(*funcBase) == OpUnpackCont); // One byte PC switchOffset = funcBase + 1; assert(toOp(*switchOffset) == OpSwitch); // The Switch opcode is one byte for the opcode itself, plus four // bytes for the jmp table size, then the jump table. Offset* jmpTable = (Offset*)(switchOffset + 5); Offset relOff = jmpTable[m_label]; return func->base() + relOff + 1; }
// Compute the bytecode offset at which execution will resume assuming // the given label. Offset c_Continuation::getExecutionOffset(int32_t label) const { auto func = actRec()->m_func; PC funcBase = func->unit()->entry() + func->base(); assert(toOp(*funcBase) == OpUnpackCont); // One byte PC switchOffset = funcBase + 1; assert(toOp(*switchOffset) == OpSwitch); // The Switch opcode is one byte for the opcode itself, plus four // bytes for the jmp table size, then the jump table. if (label >= *(int32_t*)(switchOffset + 1)) { return InvalidAbsoluteOffset; } Offset* jmpTable = (Offset*)(switchOffset + 5); Offset relOff = jmpTable[label]; return func->base() + relOff + 1; }
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); } }
// Adds a range of PCs to the filter given a collection of offset ranges. // Omit PCs which have opcodes that don't pass the given opcode filter. void PCFilter::addRanges(const Unit* unit, const OffsetRangeVec& offsets, OpcodeFilter isOpcodeAllowed) { for (auto range = offsets.cbegin(); range != offsets.cend(); ++range) { TRACE(3, "\toffsets [%d, %d)\n", range->m_base, range->m_past); for (PC pc = unit->at(range->m_base); pc < unit->at(range->m_past); pc += instrLen((Op*)pc)) { if (isOpcodeAllowed(toOp(*pc))) { TRACE(3, "\t\tpc %p\n", pc); addPC(pc); } else { TRACE(3, "\t\tpc %p -- skipping (offset %d)\n", pc, unit->offsetOf(pc)); } } } }
static ValueType genIf(Func *, Cons *cons, CodeBuilder *cb, int sp) { Cons *cond = cons; cons = cons->cdr; Cons *thenCons = cons; cons = cons->cdr; Cons *elseCons = cons; // cond int op, label; if(cond->type == CONS_CAR && (op = toOp(cond->car->str)) != -1) { Cons *lhs = cond->car->cdr; Cons *rhs = lhs->cdr; codegen(lhs, cb, sp); codegen(rhs, cb, sp+1); label = cb->createCondOp(op, sp, sp+1); } else { ValueType cty = codegen(cond, cb, sp); if(cty == VT_BOOLEAN) { cb->createIConst(sp + 1, 0); /* nil */ op = INS_IJMPEQ; label = cb->createCondOp(op, sp, sp+1); } else { label = -1; } } // then expr ValueType thentype = codegen(thenCons, cb, sp); int merge = cb->createJmp(); // else expr ValueType elsetype; if(label != -1) cb->setLabel(label); if(elseCons != NULL) { elsetype = codegen(elseCons, cb, sp); } else { cb->createIConst(sp, 0); // NIL elsetype = VT_BOOLEAN; } cb->setLabel(merge); return thentype == elsetype ? thentype : VT_INT; }
QStringList AreaParser::splitRelationship(const QString& eq) { QString found_rel; QStringList res; for(const QString& rel : ordered_rels) { if(eq.contains(rel)) { res = eq.split(rel); found_rel = rel; break; } } if(res.size() != 2) return {}; m_op = toOp(found_rel); return res; }
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_vmContext->getFP(); assert(fp); // All interrupts which reach a flow cmd have an AR. PC pc = g_vmContext->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(toOp(*pc)), fp->m_func->fullName()->data(), offset); int currentVMDepth = g_vmContext->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)) { // For step-conts we want to hit the exact same frame, for the same // continuation, not a call to the same function higher or lower on the // stack. if (!originalDepth || (m_stepContTag != getContinuationTag(fp))) return; TRACE(2, "CmdNext: hit step-cont\n"); } 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 != getContinuationTag(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 (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); } }
bool isProfileOpcode(const PC& pc) { auto const op = toOp(*pc); return op == OpRetC || op == OpCGetM; }