Exemple #1
0
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;
}
Exemple #4
0
// 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;
}
Exemple #5
0
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));
      }
    }
  }
}
Exemple #7
0
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;
}
Exemple #8
0
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;
}
Exemple #9
0
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);
  }
}
Exemple #10
0
bool isProfileOpcode(const PC& pc) {
  auto const op = toOp(*pc);
  return op == OpRetC || op == OpCGetM;
}