Variant f_hphpd_client_ctrl(CStrRef name, CStrRef op) { TRACE(5, "in f_hphpd_client_ctrl()\n"); DebuggerClient *client = NULL; std::string nameStr = name->toCPPString(); { DbgCltMap::const_accessor acc; if (!s_dbgCltMap.find(acc, nameStr)) { if (op.equal("getstate")) { return q_DebuggerClient$$STATE_INVALID; } else { raise_warning("client %s does not exist", name.data()); return uninit_null(); } } client = acc->second; } if (op.equal("interrupt")) { if (client->getClientState() < DebuggerClient::StateReadyForCommand) { raise_warning("client is not initialized"); return uninit_null(); } if (client->getClientState() != DebuggerClient::StateBusy) { raise_warning("client is not in a busy state"); return uninit_null(); } client->onSignal(SIGINT); return uninit_null(); } else if (op.equal("getstate")) { return client->getClientState(); } else if (op.equal("reset")) { // To handle the case when client is in a bad state, e.g. the grabbing // request encountered error and did not get chance to destruct or call // sweep. It will remove the client from the map. Here we'd rather take // the risk of leaking the client than the risk of chasing dangling // pointers. // // FIXME: it's unclear why it should be possible that we would not // get a chance to destruct or call sweep. return s_dbgCltMap.erase(nameStr); } raise_warning("unknown op %s", op.data()); return uninit_null(); }
// if the DebuggerClient with the same name is already in use, return null Variant f_hphpd_get_client(CStrRef name /* = null */) { if (name.empty()) { return null; } DebuggerClient *client = NULL; std::string nameStr = name->toCPPString(); { DbgCltMap::accessor acc; if (s_dbgCltMap.insert(acc, nameStr)) { client = new DebuggerClient(nameStr); acc->second = client; } else { client = acc->second; } if (!client->apiGrab()) { // already grabbed by another request return null; } } p_DebuggerClient clt(NEWOBJ(c_DebuggerClient)); clt->m_client = client; return clt; }
Variant c_DebuggerClient::t_processcmd(CVarRef cmdName, CVarRef args) { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClient, DebuggerClient::processcmd); if (!m_client || m_client->getClientState() < DebuggerClient::StateReadyForCommand) { raise_warning("client is not initialized"); return null; } if (m_client->getClientState() != DebuggerClient::StateReadyForCommand) { raise_warning("client is not ready to take command"); return null; } if (!cmdName.isString()) { raise_warning("cmdName must be string"); return null; } if (!args.isNull() && !args.isArray()) { raise_warning("args must be null or array"); return null; } static const char *s_allowedCmds[] = { "break", "continue", "down", "exception", "frame", "global", "help", "info", "konstant", "next", "out", "print", "quit", "step", "up", "variable", "where", "bt", "set", "inst", "=", "@", NULL }; bool allowed = false; for (int i = 0; ; i++) { const char *cmd = s_allowedCmds[i]; if (cmd == NULL) { break; } if (cmdName.same(cmd)) { allowed = true; break; } } if (!allowed) { raise_warning("unsupported command %s", cmdName.toString().data()); return 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) { // Flow-control command goes here Logger::Info("wait for debugger client to stop"); m_client->setTakingInterrupt(); m_client->setClientState(DebuggerClient::StateBusy); DebuggerCommandPtr cmd = m_client->waitForNextInterrupt(); if (!cmd) { raise_warning("not getting a command"); } else if (cmd->is(DebuggerCommand::KindOfInterrupt)) { CmdInterruptPtr cmdInterrupt = dynamic_pointer_cast<CmdInterrupt>(cmd); cmdInterrupt->onClientD(m_client); } else { // 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 null; } return m_client->getOutputArray(); }
namespace HPHP { /////////////////////////////////////////////////////////////////////////////// using namespace Eval; using HPHP::VM::Transl::CallerFrame; const int64 q_DebuggerClientCmdUser$$AUTO_COMPLETE_FILENAMES = DebuggerClient::AutoCompleteFileNames; const int64 q_DebuggerClientCmdUser$$AUTO_COMPLETE_VARIABLES = DebuggerClient::AutoCompleteVariables; const int64 q_DebuggerClientCmdUser$$AUTO_COMPLETE_CONSTANTS = DebuggerClient::AutoCompleteConstants; const int64 q_DebuggerClientCmdUser$$AUTO_COMPLETE_CLASSES = DebuggerClient::AutoCompleteClasses; const int64 q_DebuggerClientCmdUser$$AUTO_COMPLETE_FUNCTIONS = DebuggerClient::AutoCompleteFunctions; const int64 q_DebuggerClientCmdUser$$AUTO_COMPLETE_CLASS_METHODS = DebuggerClient::AutoCompleteClassMethods; const int64 q_DebuggerClientCmdUser$$AUTO_COMPLETE_CLASS_PROPERTIES = DebuggerClient::AutoCompleteClassProperties; const int64 q_DebuggerClientCmdUser$$AUTO_COMPLETE_CLASS_CONSTANTS = DebuggerClient::AutoCompleteClassConstants; const int64 q_DebuggerClientCmdUser$$AUTO_COMPLETE_KEYWORDS = DebuggerClient::AutoCompleteKeyword; const int64 q_DebuggerClientCmdUser$$AUTO_COMPLETE_CODE = DebuggerClient::AutoCompleteCode; /////////////////////////////////////////////////////////////////////////////// bool f_hphpd_install_user_command(CStrRef cmd, CStrRef clsname) { return CmdUser::InstallCommand(cmd, clsname); } Array f_hphpd_get_user_commands() { return CmdUser::GetCommands(); } static const Trace::Module TRACEMOD = Trace::bcinterp; void f_hphpd_break(bool condition /* = true */) { TRACE(5, "in f_hphpd_break()\n"); if (!RuntimeOption::EnableDebugger || !condition || g_vmContext->m_dbgNoBreak) { TRACE(5, "bail !%d || !%d || %d\n", RuntimeOption::EnableDebugger, condition, g_vmContext->m_dbgNoBreak); return; } if (hhvm) { CallerFrame cf; Debugger::InterruptVMHook(HardBreakPoint); if (RuntimeOption::EvalJit && !g_vmContext->m_interpreting && DEBUGGER_FORCE_INTR) { TRACE(5, "switch mode\n"); throw VMSwitchModeException(true); } } else { ThreadInfo *ti = ThreadInfo::s_threadInfo.getNoCheck(); FrameInjection *frame = FrameInjection::GetStackFrame(1); if (frame && ti->m_reqInjectionData.debugger) { Eval::InterruptSiteFI site(frame); Eval::Debugger::InterruptHard(site); } } TRACE(5, "out f_hphpd_break()\n"); } typedef tbb::concurrent_hash_map<std::string, DebuggerClient*> DbgCltMap; static DbgCltMap s_dbgCltMap; // if the DebuggerClient with the same name is already in use, return null Variant f_hphpd_get_client(CStrRef name /* = null */) { if (name.empty()) { return null; } DebuggerClient *client = NULL; std::string nameStr = name->toCPPString(); { DbgCltMap::accessor acc; if (s_dbgCltMap.insert(acc, nameStr)) { client = new DebuggerClient(nameStr); acc->second = client; } else { client = acc->second; } if (!client->apiGrab()) { // already grabbed by another request return null; } } p_DebuggerClient clt(NEWOBJ(c_DebuggerClient)); clt->m_client = client; return clt; } Variant f_hphpd_client_ctrl(CStrRef name, CStrRef op) { DebuggerClient *client = NULL; std::string nameStr = name->toCPPString(); { DbgCltMap::const_accessor acc; if (!s_dbgCltMap.find(acc, nameStr)) { if (op.equal("getstate")) { return q_DebuggerClient$$STATE_INVALID; } else { raise_warning("client %s does not exist", name.data()); return null; } } client = acc->second; } if (op.equal("interrupt")) { if (client->getClientState() < DebuggerClient::StateReadyForCommand) { raise_warning("client is not initialized"); return null; } if (client->getClientState() != DebuggerClient::StateBusy) { raise_warning("client is not in a busy state"); return null; } client->onSignal(SIGINT); return null; } else if (op.equal("getstate")) { return client->getClientState(); } else if (op.equal("reset")) { // To handle the case when client is in a bad state, e.g. the grabbing // request encountered error and did not get chance to destruct or call // sweep. It will remove the client from the map. Here we'd rather take // the risk of leaking the client than the risk of chasing dangling // pointers. return s_dbgCltMap.erase(nameStr); } raise_warning("unknown op %s", op.data()); return null; } /////////////////////////////////////////////////////////////////////////////// c_DebuggerProxyCmdUser::c_DebuggerProxyCmdUser(const ObjectStaticCallbacks *cb) : ExtObjectData(cb) { } c_DebuggerProxyCmdUser::~c_DebuggerProxyCmdUser() { } void c_DebuggerProxyCmdUser::t___construct() { } bool c_DebuggerProxyCmdUser::t_islocal() { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerProxyCmdUser, DebuggerProxyCmdUser::islocal); return m_proxy->isLocal(); } Variant c_DebuggerProxyCmdUser::t_send(CObjRef cmd) { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerProxyCmdUser, DebuggerProxyCmdUser::send); CmdUser cmdUser(cmd); return m_proxy->send(&cmdUser); } /////////////////////////////////////////////////////////////////////////////// c_DebuggerClientCmdUser::c_DebuggerClientCmdUser(const ObjectStaticCallbacks *cb) : ExtObjectData(cb) { } c_DebuggerClientCmdUser::~c_DebuggerClientCmdUser() { } void c_DebuggerClientCmdUser::t___construct() { } void c_DebuggerClientCmdUser::t_quit() { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::quit); m_client->quit(); } static String format_string(DebuggerClient *client, int _argc, CStrRef format, CArrRef _argv) { Variant ret = f_sprintf(_argc, format, _argv); if (ret.isString()) { return ret; } client->error("Debugger extension failed to format string: %s", format.data()); return ""; } void c_DebuggerClientCmdUser::t_print(int _argc, CStrRef format, CArrRef _argv /* = null_array */) { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::print); m_client->print(format_string(m_client, _argc, format, _argv)); } void c_DebuggerClientCmdUser::t_help(int _argc, CStrRef format, CArrRef _argv /* = null_array */) { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::help); m_client->help(format_string(m_client, _argc, format, _argv)); } void c_DebuggerClientCmdUser::t_info(int _argc, CStrRef format, CArrRef _argv /* = null_array */) { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::info); m_client->info(format_string(m_client, _argc, format, _argv)); } void c_DebuggerClientCmdUser::t_output(int _argc, CStrRef format, CArrRef _argv /* = null_array */) { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::output); m_client->output(format_string(m_client, _argc, format, _argv)); } void c_DebuggerClientCmdUser::t_error(int _argc, CStrRef format, CArrRef _argv /* = null_array */) { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::error); m_client->error(format_string(m_client, _argc, format, _argv)); } void c_DebuggerClientCmdUser::t_code(CStrRef source, int highlight_line /* = 0 */, int start_line_no /* = 0 */, int end_line_no /* = 0 */) { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::code); m_client->code(source, highlight_line, start_line_no, end_line_no); } Variant c_DebuggerClientCmdUser::t_ask(int _argc, CStrRef format, CArrRef _argv /* = null_array */) { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::ask); String ret = format_string(m_client, _argc, format, _argv); return String::FromChar(m_client->ask("%s", ret.data())); } String c_DebuggerClientCmdUser::t_wrap(CStrRef str) { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::wrap); return m_client->wrap(str.data()); } void c_DebuggerClientCmdUser::t_helptitle(CStrRef str) { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::helptitle); m_client->helpTitle(str.data()); } void c_DebuggerClientCmdUser::t_helpcmds(int _argc, CStrRef cmd, CStrRef desc, CArrRef _argv /* = null_array */) { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::helpcmds); std::vector<String> holders; std::vector<const char *> cmds; cmds.push_back(cmd.data()); cmds.push_back(desc.data()); for (int i = 0; i < _argv.size(); i++) { String s = _argv[i].toString(); holders.push_back(s); cmds.push_back(s.data()); } m_client->helpCmds(cmds); } void c_DebuggerClientCmdUser::t_helpbody(CStrRef str) { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::helpbody); m_client->helpBody(str.data()); } void c_DebuggerClientCmdUser::t_helpsection(CStrRef str) { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::helpsection); m_client->helpSection(str.data()); } void c_DebuggerClientCmdUser::t_tutorial(CStrRef str) { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::tutorial); m_client->tutorial(str.data()); } String c_DebuggerClientCmdUser::t_getcode() { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::getcode); return m_client->getCode(); } String c_DebuggerClientCmdUser::t_getcommand() { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::getcommand); return m_client->getCommand(); } bool c_DebuggerClientCmdUser::t_arg(int index, CStrRef str) { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::arg); return m_client->arg(index + 1, str.data()); } int64 c_DebuggerClientCmdUser::t_argcount() { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::argcount); return m_client->argCount() - 1; } String c_DebuggerClientCmdUser::t_argvalue(int index) { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::argvalue); return m_client->argValue(index + 1); } String c_DebuggerClientCmdUser::t_argrest(int index) { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::argrest); return m_client->argRest(index + 1); } Array c_DebuggerClientCmdUser::t_args() { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::args); StringVec *args = m_client->args(); Array ret(Array::Create()); for (unsigned int i = 1; i < args->size(); i++) { ret.append(String(args->at(i))); } return ret; } Variant c_DebuggerClientCmdUser::t_send(CObjRef cmd) { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::send); CmdUser cmdUser(cmd); m_client->send(&cmdUser); return true; } Variant c_DebuggerClientCmdUser::t_xend(CObjRef cmd) { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::xend); CmdUser cmdUser(cmd); CmdUserPtr ret = m_client->xend<CmdUser>(&cmdUser); return ret->getUserCommand(); } Variant c_DebuggerClientCmdUser::t_getcurrentlocation() { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::getcurrentlocation); BreakPointInfoPtr bpi = m_client->getCurrentLocation(); Array ret(Array::Create()); if (bpi) { ret.set("file", String(bpi->m_file)); ret.set("line", (int64)bpi->m_line1); ret.set("namespace", String(bpi->getNamespace())); ret.set("class", String(bpi->getClass())); ret.set("function", String(bpi->getFunction())); ret.set("text", String(bpi->site())); } return ret; } Variant c_DebuggerClientCmdUser::t_getstacktrace() { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::getstacktrace); return m_client->getStackTrace(); } int64 c_DebuggerClientCmdUser::t_getframe() { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::getframe); return m_client->getFrame(); } void c_DebuggerClientCmdUser::t_printframe(int index) { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::printframe); m_client->printFrame(index, m_client->getStackTrace()[index]); } void c_DebuggerClientCmdUser::t_addcompletion(CVarRef list) { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClientCmdUser, DebuggerClientCmdUser::addcompletion); if (list.isInteger()) { m_client->addCompletion((DebuggerClient::AutoComplete)list.toInt64()); } else { Array arr = list.toArray(); // handles string, array and iterators std::vector<std::string> items; for (ArrayIter iter(arr); iter; ++iter) { items.push_back(iter.second().toString()->toCPPString()); } m_client->addCompletion(items); } } /////////////////////////////////////////////////////////////////////////////// const int64 q_DebuggerClient$$STATE_INVALID = -1; const int64 q_DebuggerClient$$STATE_UNINIT = DebuggerClient::StateUninit; const int64 q_DebuggerClient$$STATE_INITIALIZING = DebuggerClient::StateInitializing; const int64 q_DebuggerClient$$STATE_READY_FOR_COMMAND = DebuggerClient::StateReadyForCommand; const int64 q_DebuggerClient$$STATE_BUSY = DebuggerClient::StateBusy; c_DebuggerClient::c_DebuggerClient(const ObjectStaticCallbacks *cb) : ExtObjectData(cb) { m_client = NULL; } c_DebuggerClient::~c_DebuggerClient() { sweep(); } void c_DebuggerClient::t___construct() { } int64 c_DebuggerClient::t_getstate() { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClient, DebuggerClient::getstate); if (!m_client) { return q_DebuggerClient$$STATE_INVALID; } return m_client->getClientState(); } Variant c_DebuggerClient::t_init(CVarRef options) { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClient, DebuggerClient::init); 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("user")) { ops.user = opsArr.rvalAtRef("user").toString().data(); } else { raise_warning("must specify user in options"); return false; } if (opsArr.exists("configFName")) { ops.configFName = opsArr.rvalAtRef("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("host")) { ops.host = opsArr.rvalAtRef("host").toString().data(); } if (opsArr.exists("port")) { ops.port = opsArr.rvalAtRef("port").toInt32(); } if (opsArr.exists("sandbox")) { ops.sandbox = opsArr.rvalAtRef("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(); if (!ret) { raise_warning("failed to initialize machine info"); return false; } // 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) { INSTANCE_METHOD_INJECTION_BUILTIN(DebuggerClient, DebuggerClient::processcmd); if (!m_client || m_client->getClientState() < DebuggerClient::StateReadyForCommand) { raise_warning("client is not initialized"); return null; } if (m_client->getClientState() != DebuggerClient::StateReadyForCommand) { raise_warning("client is not ready to take command"); return null; } if (!cmdName.isString()) { raise_warning("cmdName must be string"); return null; } if (!args.isNull() && !args.isArray()) { raise_warning("args must be null or array"); return null; } static const char *s_allowedCmds[] = { "break", "continue", "down", "exception", "frame", "global", "help", "info", "konstant", "next", "out", "print", "quit", "step", "up", "variable", "where", "bt", "set", "inst", "=", "@", NULL }; bool allowed = false; for (int i = 0; ; i++) { const char *cmd = s_allowedCmds[i]; if (cmd == NULL) { break; } if (cmdName.same(cmd)) { allowed = true; break; } } if (!allowed) { raise_warning("unsupported command %s", cmdName.toString().data()); return 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) { // Flow-control command goes here Logger::Info("wait for debugger client to stop"); m_client->setTakingInterrupt(); m_client->setClientState(DebuggerClient::StateBusy); DebuggerCommandPtr cmd = m_client->waitForNextInterrupt(); if (!cmd) { raise_warning("not getting a command"); } else if (cmd->is(DebuggerCommand::KindOfInterrupt)) { CmdInterruptPtr cmdInterrupt = dynamic_pointer_cast<CmdInterrupt>(cmd); cmdInterrupt->onClientD(m_client); } else { // 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 null; } return m_client->getOutputArray(); } void c_DebuggerClient::sweep() { if (m_client) { m_client->clearCachedLocal(); m_client->apiFree(); } } /////////////////////////////////////////////////////////////////////////////// }
namespace HPHP { /////////////////////////////////////////////////////////////////////////////// TRACE_SET_MOD(debugger); using namespace Eval; using HPHP::Transl::CallerFrame; const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_FILENAMES = DebuggerClient::AutoCompleteFileNames; const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_VARIABLES = DebuggerClient::AutoCompleteVariables; const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_CONSTANTS = DebuggerClient::AutoCompleteConstants; const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_CLASSES = DebuggerClient::AutoCompleteClasses; const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_FUNCTIONS = DebuggerClient::AutoCompleteFunctions; const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_CLASS_METHODS = DebuggerClient::AutoCompleteClassMethods; const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_CLASS_PROPERTIES = DebuggerClient::AutoCompleteClassProperties; const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_CLASS_CONSTANTS = DebuggerClient::AutoCompleteClassConstants; const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_KEYWORDS = DebuggerClient::AutoCompleteKeyword; const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_CODE = DebuggerClient::AutoCompleteCode; /////////////////////////////////////////////////////////////////////////////// bool f_hphpd_install_user_command(CStrRef cmd, CStrRef clsname) { TRACE(5, "in f_hphpd_install_user_command()\n"); return CmdUser::InstallCommand(cmd, clsname); } Array f_hphpd_get_user_commands() { TRACE(5, "in f_hphpd_get_user_commands()\n"); return CmdUser::GetCommands(); } void f_hphpd_break(bool condition /* = true */) { TRACE(5, "in f_hphpd_break()\n"); if (!RuntimeOption::EnableDebugger || !condition || g_vmContext->m_dbgNoBreak) { TRACE(5, "bail !%d || !%d || %d\n", RuntimeOption::EnableDebugger, condition, g_vmContext->m_dbgNoBreak); return; } CallerFrame cf; Debugger::InterruptVMHook(HardBreakPoint); if (RuntimeOption::EvalJit && DEBUGGER_FORCE_INTR) { TRACE(5, "switch mode\n"); throw VMSwitchModeBuiltin(); } TRACE(5, "out f_hphpd_break()\n"); } // Quickly determine if a debugger is attached to the current thread. bool f_hphp_debugger_attached() { return (RuntimeOption::EnableDebugger && (Debugger::GetProxy() != nullptr)); } const StaticString s_clientIP("clientIP"), s_clientPort("clientPort"); // Determine if a debugger is attached to the current thread, and // return information about where it is connected from. The client IP // and port will be null if the connection is local. Variant f_hphp_get_debugger_info() { Array ret(Array::Create()); if (!RuntimeOption::EnableDebugger) return ret; DebuggerProxyPtr proxy = Debugger::GetProxy(); if (!proxy) return ret; Variant address; Variant port; if (proxy->getClientConnectionInfo(ref(address), ref(port))) { ret.set(s_clientIP, address); ret.set(s_clientPort, port); } return ret; } typedef tbb::concurrent_hash_map<std::string, DebuggerClient*> DbgCltMap; static DbgCltMap s_dbgCltMap; // if the DebuggerClient with the same name is already in use, return null Variant f_hphpd_get_client(CStrRef name /* = null */) { TRACE(5, "in f_hphpd_get_client()\n"); if (name.empty()) { return uninit_null(); } DebuggerClient *client = NULL; std::string nameStr = name->toCPPString(); { DbgCltMap::accessor acc; if (s_dbgCltMap.insert(acc, nameStr)) { client = new DebuggerClient(nameStr); acc->second = client; } else { client = acc->second; } if (!client->apiGrab()) { // already grabbed by another request return uninit_null(); } } p_DebuggerClient clt(NEWOBJ(c_DebuggerClient)); clt->m_client = client; return clt; } Variant f_hphpd_client_ctrl(CStrRef name, CStrRef op) { TRACE(5, "in f_hphpd_client_ctrl()\n"); DebuggerClient *client = NULL; std::string nameStr = name->toCPPString(); { DbgCltMap::const_accessor acc; if (!s_dbgCltMap.find(acc, nameStr)) { if (op.equal("getstate")) { return q_DebuggerClient$$STATE_INVALID; } else { raise_warning("client %s does not exist", name.data()); return uninit_null(); } } client = acc->second; } if (op.equal("interrupt")) { if (client->getClientState() < DebuggerClient::StateReadyForCommand) { raise_warning("client is not initialized"); return uninit_null(); } if (client->getClientState() != DebuggerClient::StateBusy) { raise_warning("client is not in a busy state"); return uninit_null(); } client->onSignal(SIGINT); return uninit_null(); } else if (op.equal("getstate")) { return client->getClientState(); } else if (op.equal("reset")) { // To handle the case when client is in a bad state, e.g. the grabbing // request encountered error and did not get chance to destruct or call // sweep. It will remove the client from the map. Here we'd rather take // the risk of leaking the client than the risk of chasing dangling // pointers. // // FIXME: it's unclear why it should be possible that we would not // get a chance to destruct or call sweep. return s_dbgCltMap.erase(nameStr); } raise_warning("unknown op %s", op.data()); return uninit_null(); } /////////////////////////////////////////////////////////////////////////////// c_DebuggerProxyCmdUser::c_DebuggerProxyCmdUser(Class* cb) : ExtObjectData(cb) { TRACE(5, "c_DebuggerProxyCmdUser::c_DebuggerProxyCmdUser\n"); } c_DebuggerProxyCmdUser::~c_DebuggerProxyCmdUser() { TRACE(5, "c_DebuggerProxyCmdUser::~c_DebuggerProxyCmdUser\n"); } void c_DebuggerProxyCmdUser::t___construct() { TRACE(5, "c_DebuggerProxyCmdUser::t___construct\n"); } bool c_DebuggerProxyCmdUser::t_islocal() { TRACE(5, "c_DebuggerProxyCmdUser::t_islocal\n"); return m_proxy->isLocal(); } Variant c_DebuggerProxyCmdUser::t_send(CObjRef cmd) { TRACE(5, "c_DebuggerProxyCmdUser::t_send\n"); CmdUser cmdUser(cmd); return m_proxy->sendToClient(&cmdUser); } /////////////////////////////////////////////////////////////////////////////// c_DebuggerClientCmdUser::c_DebuggerClientCmdUser(Class* cb) : ExtObjectData(cb) { TRACE(5, "c_DebuggerClientCmdUser::c_DebuggerClientCmdUser\n"); } c_DebuggerClientCmdUser::~c_DebuggerClientCmdUser() { TRACE(5, "c_DebuggerClientCmdUser::~c_DebuggerClientCmdUser\n"); } void c_DebuggerClientCmdUser::t___construct() { TRACE(5, "c_DebuggerClientCmdUser::t___construct\n"); } void c_DebuggerClientCmdUser::t_quit() { TRACE(5, "c_DebuggerClientCmdUser::t_quit\n"); m_client->quit(); } static String format_string(DebuggerClient &client, int _argc, CStrRef format, CArrRef _argv) { TRACE(5, "c_DebuggerClientCmdUser::format_string\n"); Variant ret = f_sprintf(_argc, format, _argv); if (ret.isString()) { return ret.toString(); } client.error("Debugger extension failed to format string: %s", format.data()); return ""; } void c_DebuggerClientCmdUser::t_print(int _argc, CStrRef format, CArrRef _argv /* = null_array */) { TRACE(5, "c_DebuggerClientCmdUser::t_print\n"); m_client->print(format_string(*m_client, _argc, format, _argv)); } void c_DebuggerClientCmdUser::t_help(int _argc, CStrRef format, CArrRef _argv /* = null_array */) { TRACE(5, "c_DebuggerClientCmdUser::t_help\n"); m_client->help(format_string(*m_client, _argc, format, _argv)); } void c_DebuggerClientCmdUser::t_info(int _argc, CStrRef format, CArrRef _argv /* = null_array */) { TRACE(5, "c_DebuggerClientCmdUser::t_info\n"); m_client->info(format_string(*m_client, _argc, format, _argv)); } void c_DebuggerClientCmdUser::t_output(int _argc, CStrRef format, CArrRef _argv /* = null_array */) { TRACE(5, "c_DebuggerClientCmdUser::t_output\n"); m_client->output(format_string(*m_client, _argc, format, _argv)); } void c_DebuggerClientCmdUser::t_error(int _argc, CStrRef format, CArrRef _argv /* = null_array */) { TRACE(5, "c_DebuggerClientCmdUser::t_error\n"); m_client->error(format_string(*m_client, _argc, format, _argv)); } void c_DebuggerClientCmdUser::t_code(CStrRef source, int highlight_line /* = 0 */, int start_line_no /* = 0 */, int end_line_no /* = 0 */) { TRACE(5, "c_DebuggerClientCmdUser::t_code\n"); m_client->code(source, start_line_no, end_line_no, highlight_line); } Variant c_DebuggerClientCmdUser::t_ask(int _argc, CStrRef format, CArrRef _argv /* = null_array */) { TRACE(5, "c_DebuggerClientCmdUser::t_ask\n"); String ret = format_string(*m_client, _argc, format, _argv); return String::FromChar(m_client->ask("%s", ret.data())); } String c_DebuggerClientCmdUser::t_wrap(CStrRef str) { TRACE(5, "c_DebuggerClientCmdUser::t_wrap\n"); return m_client->wrap(str.data()); } void c_DebuggerClientCmdUser::t_helptitle(CStrRef str) { TRACE(5, "c_DebuggerClientCmdUser::t_helptitle\n"); m_client->helpTitle(str.data()); } void c_DebuggerClientCmdUser::t_helpcmds(int _argc, CStrRef cmd, CStrRef desc, CArrRef _argv /* = null_array */) { TRACE(5, "c_DebuggerClientCmdUser::t_helpcmds\n"); std::vector<String> holders; std::vector<const char *> cmds; cmds.push_back(cmd.data()); cmds.push_back(desc.data()); for (int i = 0; i < _argv.size(); i++) { String s = _argv[i].toString(); holders.push_back(s); cmds.push_back(s.data()); } m_client->helpCmds(cmds); } void c_DebuggerClientCmdUser::t_helpbody(CStrRef str) { TRACE(5, "c_DebuggerClientCmdUser::t_helpbody\n"); m_client->helpBody(str.data()); } void c_DebuggerClientCmdUser::t_helpsection(CStrRef str) { TRACE(5, "c_DebuggerClientCmdUser::t_helpsection\n"); m_client->helpSection(str.data()); } void c_DebuggerClientCmdUser::t_tutorial(CStrRef str) { TRACE(5, "c_DebuggerClientCmdUser::t_tutorial\n"); m_client->tutorial(str.data()); } String c_DebuggerClientCmdUser::t_getcode() { TRACE(5, "c_DebuggerClientCmdUser::t_getcode\n"); return m_client->getCode(); } String c_DebuggerClientCmdUser::t_getcommand() { TRACE(5, "c_DebuggerClientCmdUser::t_getcommand\n"); return m_client->getCommand(); } bool c_DebuggerClientCmdUser::t_arg(int index, CStrRef str) { TRACE(5, "c_DebuggerClientCmdUser::t_arg\n"); return m_client->arg(index + 1, str.data()); } int64_t c_DebuggerClientCmdUser::t_argcount() { TRACE(5, "c_DebuggerClientCmdUser::t_argcount\n"); return m_client->argCount() - 1; } String c_DebuggerClientCmdUser::t_argvalue(int index) { TRACE(5, "c_DebuggerClientCmdUser::t_argvalue\n"); return m_client->argValue(index + 1); } String c_DebuggerClientCmdUser::t_linerest(int index) { TRACE(5, "c_DebuggerClientCmdUser::t_linerest\n"); return m_client->lineRest(index + 1); } Array c_DebuggerClientCmdUser::t_args() { TRACE(5, "c_DebuggerClientCmdUser::t_args\n"); StringVec *args = m_client->args(); Array ret(Array::Create()); for (unsigned int i = 1; i < args->size(); i++) { ret.append(String(args->at(i))); } return ret; } Variant c_DebuggerClientCmdUser::t_send(CObjRef cmd) { TRACE(5, "c_DebuggerClientCmdUser::t_send\n"); CmdUser cmdUser(cmd); m_client->sendToServer(&cmdUser); return true; } Variant c_DebuggerClientCmdUser::t_xend(CObjRef cmd) { TRACE(5, "c_DebuggerClientCmdUser::t_xend\n"); CmdUser cmdUser(cmd); CmdUserPtr ret = m_client->xend<CmdUser>(&cmdUser); return ret->getUserCommand(); } const StaticString s_file("file"), s_line("line"), s_namespace("namespace"), s_class("class"), s_function("function"), s_text("text"), s_user("user"), s_configFName("configFName"), s_host("host"), s_port("port"), s_sandbox("sandbox"); Variant c_DebuggerClientCmdUser::t_getcurrentlocation() { TRACE(5, "c_DebuggerClientCmdUser::t_getcurrentlocation\n"); BreakPointInfoPtr bpi = m_client->getCurrentLocation(); if (!bpi) return Array::Create(); ArrayInit ret(6); ret.set(s_file, String(bpi->m_file)); ret.set(s_line, (int64_t)bpi->m_line1); ret.set(s_namespace, String(bpi->getNamespace())); ret.set(s_class, String(bpi->getClass())); ret.set(s_function, String(bpi->getFunction())); ret.set(s_text, String(bpi->site())); return ret.create(); } Variant c_DebuggerClientCmdUser::t_getstacktrace() { TRACE(5, "c_DebuggerClientCmdUser::t_getstacktrace\n"); return m_client->getStackTrace(); } int64_t c_DebuggerClientCmdUser::t_getframe() { TRACE(5, "c_DebuggerClientCmdUser::t_getframe\n"); return m_client->getFrame(); } void c_DebuggerClientCmdUser::t_printframe(int index) { TRACE(5, "c_DebuggerClientCmdUser::t_printframe\n"); m_client->printFrame(index, m_client->getStackTrace()[index].toArray()); } void c_DebuggerClientCmdUser::t_addcompletion(CVarRef list) { TRACE(5, "c_DebuggerClientCmdUser::t_addcompletion\n"); if (list.isInteger()) { m_client->addCompletion((DebuggerClient::AutoComplete)list.toInt64()); } else { Array arr = list.toArray(); // handles string, array and iterators std::vector<std::string> items; for (ArrayIter iter(arr); iter; ++iter) { items.push_back(iter.second().toString()->toCPPString()); } m_client->addCompletion(items); } } /////////////////////////////////////////////////////////////////////////////// const int64_t q_DebuggerClient$$STATE_INVALID = -1; const int64_t q_DebuggerClient$$STATE_UNINIT = DebuggerClient::StateUninit; const int64_t q_DebuggerClient$$STATE_INITIALIZING = DebuggerClient::StateInitializing; const int64_t q_DebuggerClient$$STATE_READY_FOR_COMMAND = DebuggerClient::StateReadyForCommand; const int64_t q_DebuggerClient$$STATE_BUSY = DebuggerClient::StateBusy; c_DebuggerClient::c_DebuggerClient(Class* cb) : ExtObjectData(cb) { TRACE(5, "c_DebuggerClient::c_DebuggerClient(Class* cb)\n"); m_client = NULL; } c_DebuggerClient::~c_DebuggerClient() { TRACE(5, "c_DebuggerClient::~c_DebuggerClient()\n"); sweep(); } void c_DebuggerClient::t___construct() { TRACE(5, "c_DebuggerClient::t___construct\n"); } int64_t c_DebuggerClient::t_getstate() { TRACE(5, "c_DebuggerClient::t_getstate\n"); if (!m_client) { return q_DebuggerClient$$STATE_INVALID; } return m_client->getClientState(); } 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 c_DebuggerClient::sweep() { TRACE(5, "c_DebuggerClient::sweep\n"); if (m_client) { // Note: it's important that resetSmartAllocatedMembers happens // before clearCachedLocal(), because the smart allocated pointers // are already invalid. m_client->resetSmartAllocatedMembers(); m_client->clearCachedLocal(); m_client->apiFree(); } } /////////////////////////////////////////////////////////////////////////////// }