Ejemplo n.º 1
0
// Handle an interrupt from the VM.
void DebuggerProxy::interrupt(CmdInterrupt &cmd) {
  TRACE_RB(2, "DebuggerProxy::interrupt\n");
  // Make any breakpoints that have passed breakable again.
  setBreakableForBreakpointsNotMatching(cmd);

  // At this point we have an interrupt, but we don't know if we're on the
  // thread the proxy considers "current".
  // NB: BreakPointReached really means we've got control of a VM thread from
  // the opcode hook. This could be for a breakpoint, stepping, etc.

  // Wait until this thread is the one this proxy wants to debug.
  if (!blockUntilOwn(cmd, true)) return;

  // We know we're on the "current" thread, so we can process any active flow
  // command, stop if we're at a breakpoint, handle other interrupts, etc.
  if (checkFlowBreak(cmd)) {
    // We've hit a breakpoint and now need to make sure that breakpoints
    // wont be hit again for this site until control leaves this site.
    // (Breakpoints can still get hit if control reaches this site during
    // a call that is part of this site because the flags are stacked.)
    unsetBreakableForBreakpointsMatching(cmd);
    while (true) {
      try {
        // We're about to send the client an interrupt and start
        // waiting for commands back from it. Disable signal polling
        // during this time, since our protocol requires that only one
        // thread talk to the client at a time.
        disableSignalPolling();
        SCOPE_EXIT { enableSignalPolling(); };
        processInterrupt(cmd);
      } catch (const DebuggerException &e) {
        TRACE(2, "DebuggerException from processInterrupt!\n");
        switchThreadMode(Normal);
        throw;
      } catch (...) {
        TRACE(2, "Unknown exception from processInterrupt!\n");
        assert(false); // no other exceptions should be seen here
        switchThreadMode(Normal);
        throw;
      }
      if (cmd.getInterruptType() == PSPEnded) break;
      if (!m_newThread) break; // we're not switching threads
      switchThreadMode(Normal, m_newThread->m_id);
      m_newThread.reset();
      blockUntilOwn(cmd, false);
    }
  }

  if ((m_threadMode == Normal) || (cmd.getInterruptType() == PSPEnded)) {
    // If the thread mode is Normal we let other threads with
    // interrupts go ahead and process them. We also do this when the
    // thread is at PSPEnded because the thread is done.
    switchThreadMode(Normal);
  }
}
Ejemplo n.º 2
0
// Check if we should stop due to flow control, breakpoints, and signals.
bool DebuggerProxy::checkFlowBreak(CmdInterrupt &cmd) {
  TRACE(2, "DebuggerProxy::checkFlowBreak\n");
  // If there is an outstanding Ctrl-C from the client, go ahead and break now.
  // Note: this stops any flow control command we might have in-flight.
  if (m_signum == CmdSignal::SignalBreak) {
    Lock lock(m_signalMutex);
    if (m_signum == CmdSignal::SignalBreak) {
      m_signum = CmdSignal::SignalNone;
      m_flow.reset();
      return true;
    }
  }

  // Note that even if fcShouldBreak, we still need to test bpShouldBreak
  // so to correctly report breakpoint reached + evaluating watchpoints;
  // vice versa, so to give a flow cmd a chance to react.
  // Note: a Continue cmd is a bit special. We need to process it _only_ if
  // we decide to break.
  bool fcShouldBreak = false; // should I break according to flow control?
  bool bpShouldBreak = false; // should I break according to breakpoints?

  if ((cmd.getInterruptType() == BreakPointReached ||
       cmd.getInterruptType() == HardBreakPoint ||
       cmd.getInterruptType() == ExceptionHandler) && m_flow) {
    if (!m_flow->is(DebuggerCommand::KindOfContinue)) {
      m_flow->onBeginInterrupt(*this, cmd);
      if (m_flow->complete()) {
        fcShouldBreak = true;
        m_flow.reset();
      }
    }
  }

  // NB: this also checks whether we should be stopping at special interrupt
  // sites, like SessionStarted, RequestEnded, ExceptionThrown, etc.
  bpShouldBreak = checkBreakPoints(cmd);

  // This is done before KindOfContinue testing.
  if (!fcShouldBreak && !bpShouldBreak) {
    return false;
  }

  if ((cmd.getInterruptType() == BreakPointReached ||
       cmd.getInterruptType() == HardBreakPoint) && m_flow) {
    if (m_flow->is(DebuggerCommand::KindOfContinue)) {
      m_flow->onBeginInterrupt(*this, cmd);
      if (m_flow->complete()) m_flow.reset();
      return false;
    }
  }

  return true;
}
Ejemplo n.º 3
0
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;
}
Ejemplo n.º 4
0
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;
}
Ejemplo n.º 5
0
// Check if we should stop due to flow control, breakpoints, and signals.
bool DebuggerProxy::checkFlowBreak(CmdInterrupt &cmd) {
  TRACE(2, "DebuggerProxy::checkFlowBreak\n");
  // If there is an outstanding Ctrl-C from the client, go ahead and break now.
  // Note: this stops any flow control command we might have in-flight.
  if (m_signum == CmdSignal::SignalBreak) {
    Lock lock(m_signumMutex);
    if (m_signum == CmdSignal::SignalBreak) {
      m_signum = CmdSignal::SignalNone;
      m_flow.reset();
      return true;
    }
  }

  // Note that even if fcShouldBreak, we still need to test bpShouldBreak
  // so to correctly report breakpoint reached + evaluating watchpoints;
  // vice versa, so to decCount() on m_flow.
  bool fcShouldBreak = false; // should I break according to flow control?
  bool bpShouldBreak = false; // should I break according to breakpoints?

  if ((cmd.getInterruptType() == BreakPointReached ||
       cmd.getInterruptType() == HardBreakPoint) && m_flow) {
    fcShouldBreak = breakByFlowControl(cmd);
  }

  bpShouldBreak = checkBreakPoints(cmd);
  if (!fcShouldBreak && !bpShouldBreak) {
    return false; // this is done before KindOfContinue testing
  }


  return true;
}
Ejemplo n.º 6
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.º 7
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.º 8
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.º 9
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.º 10
0
bool DebuggerProxy::processFlowBreak(CmdInterrupt &cmd) {
  TRACE(2, "DebuggerProxy::processFlowBreak\n");
  if ((cmd.getInterruptType() == BreakPointReached ||
       cmd.getInterruptType() == HardBreakPoint) && m_flow) {
    if (m_flow->is(DebuggerCommand::KindOfContinue)) {
      if (!m_flow->decCount()) m_flow.reset();
      return false;
    }
  }
  return true;
}
Ejemplo n.º 11
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);
  }
}
Ejemplo n.º 12
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.º 13
0
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);
  }
}
Ejemplo n.º 14
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
  }
}