Ejemplo n.º 1
0
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());
  }
}
Ejemplo n.º 2
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;
}
Ejemplo n.º 3
0
// 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);
    }
  }
}
Ejemplo n.º 4
0
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());
    }
  }
}
Ejemplo n.º 5
0
// 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();
}
Ejemplo n.º 6
0
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;
}
Ejemplo n.º 7
0
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;
}
Ejemplo n.º 8
0
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;
}
Ejemplo n.º 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.
  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;
  }
}
Ejemplo n.º 10
0
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;
}
Ejemplo n.º 11
0
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;
}
Ejemplo n.º 12
0
void CmdStep::onBeginInterrupt(DebuggerProxy *proxy, CmdInterrupt &interrupt) {
  m_complete = (decCount() == 0);
  if (!m_complete) {
    installLocationFilterForLine(interrupt.getSite());
  }
}
Ejemplo n.º 13
0
// 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
  }
}