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; }
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(); }
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; } }
// 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"); }
// 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; }
void CmdExtended::invokeList(DebuggerClient *client, const std::string &cls){ DebuggerCommandPtr cmd = CreateExtendedCommand(cls); if (cmd) { cmd->list(client); } }