Beispiel #1
0
Variant c_DebuggerClient::t_init(CVarRef options) {
  TRACE(5, "c_DebuggerClient::t_init\n");
  if (!m_client) {
    raise_warning("invalid client");
    return false;
  }
  if (m_client->getClientState() != DebuggerClient::StateUninit) {
    return m_client->getClientState() == DebuggerClient::StateReadyForCommand;
  }
  if (!options.isArray()) {
    raise_warning("options must be an array");
    return false;
  }
  m_client->setClientState(DebuggerClient::StateInitializing);

  DebuggerClientOptions ops;
  ops.apiMode = true;

  Array opsArr = options.toArray();
  if (opsArr.exists(s_user)) {
    ops.user = opsArr.rvalAtRef(s_user).toString().data();
  } else {
    raise_warning("must specify user in options");
    return false;
  }

  if (opsArr.exists(s_configFName)) {
    ops.configFName = opsArr.rvalAtRef(s_configFName).toString().data();
    FILE *f = fopen(ops.configFName.c_str(), "r");
    if (!f) {
      raise_warning("cannot access config file %s", ops.configFName.c_str());
      return false;
    }
    fclose(f);
  }

  if (opsArr.exists(s_host)) {
    ops.host = opsArr.rvalAtRef(s_host).toString().data();
  }
  if (opsArr.exists(s_port)) {
    ops.port = opsArr.rvalAtRef(s_port).toInt32();
  }
  if (opsArr.exists(s_sandbox)) {
    ops.sandbox = opsArr.rvalAtRef(s_sandbox).toString().data();
  }

  m_client->init(ops);

  if (ops.host.empty()) {
    ops.host = "localhost";
  }
  if (ops.port < 0) {
    ops.port = RuntimeOption::DebuggerServerPort;
  }
  bool ret = m_client->connect(ops.host, ops.port);
  if (!ret) {
    raise_warning("failed to connect to hhvm %s:%d", ops.host.c_str(),
                  ops.port);
    return false;
  }

  // To wait for the session start interrupt
  DebuggerCommandPtr cmd = m_client->waitForNextInterrupt();
  if (!cmd->is(DebuggerCommand::KindOfInterrupt) ||
      dynamic_pointer_cast<CmdInterrupt>(cmd)->getInterruptType() !=
      SessionStarted) {
    raise_warning("failed to load sandbox");
    return false;
  }

  ret = m_client->initializeMachine();
  assert(ret); // Always returns true in API mode.

  // To wait for the machine loading sandbox
  cmd = m_client->waitForNextInterrupt();
  if (!cmd->is(DebuggerCommand::KindOfInterrupt) ||
      dynamic_pointer_cast<CmdInterrupt>(cmd)->getInterruptType() !=
      SessionStarted) {
    raise_warning("failed to load sandbox");
    return false;
  }

  m_client->setClientState(DebuggerClient::StateReadyForCommand);

  return true;
}
Beispiel #2
0
Variant c_DebuggerClient::t_processcmd(CVarRef cmdName, CVarRef args) {
  TRACE(5, "c_DebuggerClient::t_processcmd\n");
  if (!m_client ||
      m_client->getClientState() < DebuggerClient::StateReadyForCommand) {
    raise_warning("client is not initialized");
    return uninit_null();
  }
  if (m_client->getClientState() != DebuggerClient::StateReadyForCommand) {
    raise_warning("client is not ready to take command");
    return uninit_null();
  }
  if (!cmdName.isString()) {
    raise_warning("cmdName must be string");
    return uninit_null();
  }
  if (!args.isNull() && !args.isArray()) {
    raise_warning("args must be null or array");
    return uninit_null();
  }

  static const char *s_allowedCmds[] = {
    "break", "bt", "continue", "down", "exception", "frame", "global",
    "help", "info", "inst", "konstant", "next", "out", "print", "quit",
    "set", "step", "up", "variable", "where", "=", "@", nullptr
  };

  bool allowed = false;
  for (int i = 0; ; i++) {
    const char *cmd = s_allowedCmds[i];
    if (cmd == NULL) {
      break;
    }
    if (same(cmdName, cmd)) {
      allowed = true;
      break;
    }
  }
  if (!allowed) {
    raise_warning("unsupported command %s", cmdName.toString().data());
    return uninit_null();
  }

  m_client->setCommand(cmdName.toString().data());
  StringVec *clientArgs = m_client->args();
  clientArgs->clear();
  if (!args.isNull()) {
    for (ArrayIter iter(args.toArray()); iter; ++iter) {
      CStrRef arg = iter.second().toString();
      clientArgs->push_back(std::string(arg.data(), arg.size()));
    }
  }
  try {
    if (!m_client->process()) {
      raise_warning("command \"%s\" not found", cmdName.toString().data());
    }
  } catch (DebuggerConsoleExitException &e) {
    TRACE(4, "Command raised DebuggerConsoleExitException\n");
    // Flow-control command goes here
    Logger::Info("wait for debugger client to stop");
    m_client->setClientState(DebuggerClient::StateBusy);
    DebuggerCommandPtr cmd = m_client->waitForNextInterrupt();
    TRACE(4, "waitForNextInterrupt() came back as ");
    if (!cmd) {
      TRACE(4, "null\n");
      raise_warning("not getting a command");
    } else if (cmd->is(DebuggerCommand::KindOfInterrupt)) {
      TRACE(4, "an interrupt\n");
      CmdInterruptPtr cmdInterrupt = dynamic_pointer_cast<CmdInterrupt>(cmd);
      cmdInterrupt->onClient(*m_client);
    } else {
      TRACE(4, "an previous pending command\n");
      // Previous pending commands
      cmd->handleReply(*m_client);
      cmd->setClientOutput(*m_client);
    }
    Logger::Info("debugger client ready for command");
  } catch (DebuggerClientExitException &e) {
    const std::string& nameStr = m_client->getNameApi();
    Logger::Info("client %s disconnected", nameStr.c_str());
    s_dbgCltMap.erase(nameStr);
    delete m_client;
    m_client = NULL;
    return true;
  } catch (DebuggerProtocolException &e) {
    raise_warning("DebuggerProtocolException");
    return uninit_null();
  }

  return m_client->getOutputArray();
}
Beispiel #3
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(),
                         boost::lexical_cast<string>(res->getType()));
      // Any control flow command gets installed here and we continue execution.
      m_flow = 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;
  }
}
Beispiel #4
0
// This gets it's own thread, and polls the client once per second to see if
// there is a signal, i.e., if the user has pressed Ctrl-C, etc. If there is a
// signal, it is passed as an interrupt to the proxy in an attempt to get other
// threads in the sandbox to stop.
//
// If another thread in the sandbox fails to stop and consume the signal then
// it will be passed to the dummy sandbox instead.
void DebuggerProxy::pollSignal() {
  TRACE_RB(2, "DebuggerProxy::pollSignal: starting\n");
  int signalTimeout = RuntimeOption::DebuggerSignalTimeout;
  while (!m_stopped) {
    sleep(1);

    // Block any threads that might be interrupting from communicating with the
    // client until we're done with this poll.
    Lock lock(m_signalMutex);

    // After DebuggerSignalTimeout seconds that no active thread picks
    // up the signal, we send it to dummy sandbox.
    if ((m_signum != CmdSignal::SignalNone) && m_dummySandbox &&
        (--signalTimeout <= 0)) {
      TRACE_RB(2, "DebuggerProxy::pollSignal: sending to dummy sandbox\n");
      m_dummySandbox->notifySignal(m_signum);
      m_signum = CmdSignal::SignalNone;
    }

    // Don't actually poll if another thread is already in a command
    // processing loop with the client.
    if (!m_okayToPoll) continue;

    // Send CmdSignal over to the client and wait for a response.
    CmdSignal cmd;
    if (!cmd.onServer(*this)) {
      TRACE_RB(2, "DebuggerProxy::pollSignal: "
               "Failed to send CmdSignal to client\n");
      break;
    }

    // We've sent the client a command, and we expect an immediate
    // response. Wait 10 times to give it a chance on especially
    // overloaded computers.
    DebuggerCommandPtr res;
    for (int i = 0; i < 10; i++) {
      if (DebuggerCommand::Receive(m_thrift, res,
                                   "DebuggerProxy::pollSignal()")) break;
      if (m_stopped) {
        TRACE_RB(2, "DebuggerProxy::pollSignal: "
                 "signal thread asked to stop while waiting "
                 "for CmdSignal back from the client\n");
        break;
      }
    }
    if (!res) {
      if (!m_stopped) {
        TRACE_RB(2, "DebuggerProxy::pollSignal: "
                 "Failed to get CmdSignal back from client\n");
      }
      break;
    }

    CmdSignalPtr sig = dynamic_pointer_cast<CmdSignal>(res);
    if (!sig) {
      TRACE_RB(2, "DebuggerProxy::pollSignal: "
               "bad response from signal polling: %d", res->getType());
      break;
    }

    auto newSignum = sig->getSignal();

    if (newSignum != CmdSignal::SignalNone) {
      TRACE_RB(2, "DebuggerProxy::pollSignal: "
               "got interrupt signal from client\n");
      m_signum = newSignum;
      signalTimeout = RuntimeOption::DebuggerSignalTimeout;
      Debugger::RequestInterrupt(shared_from_this());
    }
  }
  if (!m_stopped) {
    // We've noticed that the socket has closed. Stop and destory this proxy.
    TRACE_RB(2, "DebuggerProxy::pollSignal: "
             "lost communication with the client, stopping proxy\n");
    Debugger::UsageLog("server", getSandboxId(), "ProxyError", "Signal poll");
    stop();
  }
  TRACE_RB(2, "DebuggerProxy::pollSignal: ended\n");
}
Beispiel #5
0
// Returns false on timeout, true when data has been read even if that data
// didn't form a usable command. Is there is no usable command, cmd is null.
bool DebuggerCommand::Receive(DebuggerThriftBuffer& thrift,
                              DebuggerCommandPtr& cmd, const char* caller) {
  TRACE(5, "DebuggerCommand::Receive\n");
  cmd.reset();

  constexpr auto POLLING_SECONDS = 1;
  struct pollfd fds[1];
  fds[0].fd = thrift.getSocket()->fd();
  fds[0].events = POLLIN|POLLERR|POLLHUP;
  auto const ret = poll(fds, 1, POLLING_SECONDS * 1000);

  // Timeout.
  if (ret == 0) return false;

  if (ret == -1) {
    auto const errorNumber = errno; // Just in case TRACE_RB changes errno
    TRACE_RB(1, "DebuggerCommand::Receive: error %d\n", errorNumber);
    return errorNumber != EINTR; // Treat signals as timeouts
  }

  // If we don't have any data to read (POLLIN) then we're done. If we
  // do have data we'll attempt to read and decode it below, even if
  // there are other error bits set.
  if (!(fds[0].revents & POLLIN)) {
    TRACE_RB(1, "DebuggerCommand::Receive: revents %d\n", fds[0].revents);
    return true;
  }

  int32_t type;
  std::string clsname;
  try {
    thrift.reset(true);
    thrift.read(type);
    thrift.read(clsname);
  } catch (...) {
    // Note: this error case is difficult to test. But, it's exactly the same
    // as the error noted below. Make sure to keep handling of both of these
    // errors in sync.
    TRACE_RB(1, "%s: socket error receiving command", caller);
    return true;
  }

  TRACE(1, "DebuggerCommand::Receive: got cmd of type %d\n", type);

  // Not all commands are here, as not all commands need to be sent over wire.
  switch (type) {
    case KindOfBreak    : cmd = std::make_shared<CmdBreak>(); break;
    case KindOfContinue : cmd = std::make_shared<CmdContinue>(); break;
    case KindOfDown     : cmd = std::make_shared<CmdDown>(); break;
    case KindOfException: cmd = std::make_shared<CmdException>(); break;
    case KindOfFrame    : cmd = std::make_shared<CmdFrame>(); break;
    case KindOfGlobal   : cmd = std::make_shared<CmdGlobal>(); break;
    case KindOfInfo     : cmd = std::make_shared<CmdInfo>(); break;
    case KindOfConstant : cmd = std::make_shared<CmdConstant>(); break;
    case KindOfList     : cmd = std::make_shared<CmdList>(); break;
    case KindOfMachine  : cmd = std::make_shared<CmdMachine>(); break;
    case KindOfNext     : cmd = std::make_shared<CmdNext>(); break;
    case KindOfOut      : cmd = std::make_shared<CmdOut>(); break;
    case KindOfPrint    : cmd = std::make_shared<CmdPrint>(); break;
    case KindOfQuit     : cmd = std::make_shared<CmdQuit>(); break;
    case KindOfRun      : cmd = std::make_shared<CmdRun>(); break;
    case KindOfStep     : cmd = std::make_shared<CmdStep>(); break;
    case KindOfThread   : cmd = std::make_shared<CmdThread>(); break;
    case KindOfUp       : cmd = std::make_shared<CmdUp>(); break;
    case KindOfVariable : cmd = std::make_shared<CmdVariable>(); break;
    case KindOfVariableAsync :
      cmd = std::make_shared<CmdVariable>(KindOfVariableAsync);
      break;
    case KindOfWhere    : cmd = std::make_shared<CmdWhere>(); break;
    case KindOfWhereAsync:
      cmd = std::make_shared<CmdWhere>(KindOfWhereAsync);
      break;
    case KindOfEval     : cmd = std::make_shared<CmdEval>(); break;
    case KindOfInterrupt: cmd = std::make_shared<CmdInterrupt>(); break;
    case KindOfSignal   : cmd = std::make_shared<CmdSignal>(); break;
    case KindOfShell    : cmd = std::make_shared<CmdShell>(); break;
    case KindOfInternalTesting :
      cmd = std::make_shared<CmdInternalTesting>();
      break;

    case KindOfExtended: {
      assert(!clsname.empty());
      cmd = CmdExtended::CreateExtendedCommand(clsname);
      assert(cmd);
      break;
    }

    default:
      TRACE_RB(1, "%s: received bad cmd type: %d", caller, type);
      cmd.reset();
      return true;
  }
  if (!cmd->recv(thrift)) {
    // Note: this error case is easily tested, and we have a test for it. But
    // the error case noted above is quite difficult to test. Keep these two
    // in sync.
    TRACE_RB(1, "%s: socket error receiving command", caller);
    cmd.reset();
  }
  return true;
}
bool DebuggerCommand::Receive(DebuggerThriftBuffer &thrift,
                              DebuggerCommandPtr &cmd, const char *caller) {
  TRACE(5, "DebuggerCommand::Receive\n");
  cmd.reset();

  struct pollfd fds[1];
  fds[0].fd = thrift.getSocket()->fd();
  fds[0].events = POLLIN|POLLERR|POLLHUP;
  int ret = poll(fds, 1, POLLING_SECONDS * 1000);
  if (ret == 0) {
    return false;
  }
  if (ret == -1 || !(fds[0].revents & POLLIN)) {
    return errno != EINTR; // treat signals as timeouts
  }

  int32_t type;
  string clsname;
  try {
    thrift.reset(true);
    thrift.read(type);
    thrift.read(clsname);
  } catch (...) {
    Logger::Error("%s => DebuggerCommand::Receive(): socket error", caller);
    return true;
  }

  TRACE(1, "DebuggerCommand::Receive: got cmd of type %d\n", type);

  // not all commands are here, as not all commands need to be sent over wire
  switch (type) {
    case KindOfBreak    :  cmd = DebuggerCommandPtr(new CmdBreak    ()); break;
    case KindOfContinue :  cmd = DebuggerCommandPtr(new CmdContinue ()); break;
    case KindOfDown     :  cmd = DebuggerCommandPtr(new CmdDown     ()); break;
    case KindOfException:  cmd = DebuggerCommandPtr(new CmdException()); break;
    case KindOfFrame    :  cmd = DebuggerCommandPtr(new CmdFrame    ()); break;
    case KindOfGlobal   :  cmd = DebuggerCommandPtr(new CmdGlobal   ()); break;
    case KindOfInfo     :  cmd = DebuggerCommandPtr(new CmdInfo     ()); break;
    case KindOfConstant :  cmd = DebuggerCommandPtr(new CmdConstant ()); break;
    case KindOfList     :  cmd = DebuggerCommandPtr(new CmdList     ()); break;
    case KindOfMachine  :  cmd = DebuggerCommandPtr(new CmdMachine  ()); break;
    case KindOfNext     :  cmd = DebuggerCommandPtr(new CmdNext     ()); break;
    case KindOfOut      :  cmd = DebuggerCommandPtr(new CmdOut      ()); break;
    case KindOfPrint    :  cmd = DebuggerCommandPtr(new CmdPrint    ()); break;
    case KindOfQuit     :  cmd = DebuggerCommandPtr(new CmdQuit     ()); break;
    case KindOfRun      :  cmd = DebuggerCommandPtr(new CmdRun      ()); break;
    case KindOfStep     :  cmd = DebuggerCommandPtr(new CmdStep     ()); break;
    case KindOfThread   :  cmd = DebuggerCommandPtr(new CmdThread   ()); break;
    case KindOfUp       :  cmd = DebuggerCommandPtr(new CmdUp       ()); break;
    case KindOfVariable :  cmd = DebuggerCommandPtr(new CmdVariable ()); break;
    case KindOfWhere    :  cmd = DebuggerCommandPtr(new CmdWhere    ()); break;
    case KindOfUser     :  cmd = DebuggerCommandPtr(new CmdUser     ()); break;
    case KindOfEval     :  cmd = DebuggerCommandPtr(new CmdEval     ()); break;
    case KindOfInterrupt:  cmd = DebuggerCommandPtr(new CmdInterrupt()); break;
    case KindOfSignal   :  cmd = DebuggerCommandPtr(new CmdSignal   ()); break;
    case KindOfShell    :  cmd = DebuggerCommandPtr(new CmdShell    ()); break;

    case KindOfExtended: {
      assert(!clsname.empty());
      cmd = CmdExtended::CreateExtendedCommand(clsname);
      assert(cmd);
      break;
    }

    default:
      assert(false);
      Logger::Error("%s => DebuggerCommand::Receive(): bad cmd type: %d",
                    caller, type);
      return true;
  }
  if (!cmd->recv(thrift)) {
    Logger::Error("%s => DebuggerCommand::Receive(): socket error", caller);
    cmd.reset();
  }
  return true;
}
Beispiel #7
0
void CmdExtended::invokeList(DebuggerClient *client, const std::string &cls){
  DebuggerCommandPtr cmd = CreateExtendedCommand(cls);
  if (cmd) {
    cmd->list(client);
  }
}