Example #1
0
void DebuggerProxy::processInterrupt(CmdInterrupt &cmd) {
  TRACE_RB(2, "DebuggerProxy::processInterrupt\n");
  // Do the server-side work for this interrupt, which just notifies the client.
  if (!cmd.onServer(*this)) {
    TRACE_RB(1, "Failed to send CmdInterrupt to client\n");
    Debugger::UsageLog("server", getSandboxId(), "ProxyError",
                       "Send interrupt");
    stopAndThrow();
  }

  Debugger::UsageLogInterrupt("server", getSandboxId(), cmd);

  // Wait for commands from the debugger client and process them. We'll stay
  // here until we get a command that should cause the thread to continue.
  while (true) {
    DebuggerCommandPtr res;
    while (!DebuggerCommand::Receive(m_thrift, res,
                                     "DebuggerProxy::processInterrupt()")) {
      // we will wait forever until DebuggerClient sends us something
      checkStop();
    }
    checkStop();
    if (res) {
      TRACE_RB(2, "Proxy got cmd type %d\n", res->getType());
      Debugger::UsageLog("server", getSandboxId(),
                         folly::to<std::string>(res->getType()));
      // Any control flow command gets installed here and we continue execution.
      m_flow = std::dynamic_pointer_cast<CmdFlowControl>(res);
      if (m_flow) {
        m_flow->onSetup(*this, cmd);
        if (!m_flow->complete()) {
          TRACE_RB(2, "Incomplete flow command %d remaining on proxy for "
                   "further processing\n", m_flow->getType());
          if (m_threadMode == Normal) {
            // We want the flow command to complete on the thread that
            // starts it.
            switchThreadMode(Sticky);
          }
        } else {
          // The flow cmd has determined that it is done with its work and
          // doesn't need to remain for later processing.
          TRACE_RB(2, "Flow command %d completed\n", m_flow->getType());
          m_flow.reset();
        }
        return;
      }
      if (res->is(DebuggerCommand::KindOfQuit)) {
        TRACE_RB(2, "Received quit command\n");
        res->onServer(*this); // acknowledge receipt so that client can quit.
        stopAndThrow();
      }
    }
    bool cmdFailure = false;
    try {
      // Perform the server-side work for this command.
      if (res) {
        if (!res->onServer(*this)) {
          TRACE_RB(1, "Failed to execute cmd %d from client\n", res->getType());
          Debugger::UsageLog("server", getSandboxId(), "ProxyError",
                             "Command failed");
          cmdFailure = true;
        }
      } else {
        TRACE_RB(1, "Failed to receive cmd from client\n");
        Debugger::UsageLog("server", getSandboxId(), "ProxyError",
                           "Command receive failed");
        cmdFailure = true;
      }
    } catch (const DebuggerException &e) {
      throw;
    } catch (const std::exception& e) {
      Logger::Warning(DEBUGGER_LOG_TAG
       "Cmd type %d onServer() threw exception %s", res->getType(), e.what());
      Debugger::UsageLog("server", getSandboxId(), "ProxyError",
                         "Command exception");
      cmdFailure = true;
    } catch (...) {
      Logger::Warning(DEBUGGER_LOG_TAG
       "Cmd type %d onServer() threw non standard exception", res->getType());
      Debugger::UsageLog("server", getSandboxId(), "ProxyError",
                         "Command exception");
      cmdFailure = true;
    }
    if (cmdFailure) stopAndThrow();
    if (res->shouldExitInterrupt()) return;
  }
}
// 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) {
        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
  }
}
Example #3
0
// Checks whether the cmd has any breakpoints that match the current Site.
// Also returns true for cmds that should always break, like SessionStarted,
// and returns true when we have special modes setup for, say, breaking on
// RequestEnded, PSPEnded, etc.
bool DebuggerProxy::checkBreakPoints(CmdInterrupt &cmd) {
  TRACE(2, "DebuggerProxy::checkBreakPoints\n");
  ReadLock lock(m_breakMutex);
  return cmd.shouldBreak(*this, m_breakpoints, getRealStackDepth());
}
Example #4
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_context->getFP();
  if (!fp) {
    // If we have no frame just wait for the next instruction to be interpreted.
    m_needsVMInterrupt = true;
    return;
  }
  PC pc = g_context->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(*reinterpret_cast<const 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() || 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)) {
      if (m_stepContTag != getResumableTag(fp)) return;
      TRACE(2, "CmdNext: hit step-cont\n");
      // We're in the continuation we expect. This may be at a
      // different stack depth, though, especially if we've moved from
      // the original function to the continuation. 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 continuation. We don't care about exception handlers
      // in continuations being driven at the same level.
      if (hasStepCont() && originalDepth &&
          (m_stepContTag != getResumableTag(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 (m_skippingAsyncSuspend) {
    m_skippingAsyncSuspend = false;
    stepAfterAsyncSuspend();
    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);
  }
}
Example #5
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;
}
Example #6
0
// Checks whether the cmd has any breakpoints that match the current Site.
// Also returns true for cmds that have should always break.
bool DebuggerProxy::checkBreakPoints(CmdInterrupt &cmd) {
  TRACE(2, "DebuggerProxy::checkBreakPoints\n");
  ReadLock lock(m_breakMutex);
  return cmd.shouldBreak(m_breakpoints);
}
Example #7
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
  }
}