make #includes consistent
[hiphop-php.git] / hphp / runtime / ext / ext_debugger.cpp
blob0c4e4c7223019f097507c309f43ca8314830c6de
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
6 | Copyright (c) 1997-2010 The PHP Group |
7 +----------------------------------------------------------------------+
8 | This source file is subject to version 3.01 of the PHP license, |
9 | that is bundled with this package in the file LICENSE, and is |
10 | available through the world-wide-web at the following url: |
11 | http://www.php.net/license/3_01.txt |
12 | If you did not receive a copy of the PHP license and are unable to |
13 | obtain it through the world-wide-web, please send a note to |
14 | license@php.net so we can mail you a copy immediately. |
15 +----------------------------------------------------------------------+
18 #include "hphp/runtime/ext/ext_debugger.h"
19 #include "hphp/runtime/ext/ext_string.h"
20 #include "hphp/runtime/eval/debugger/cmd/cmd_user.h"
21 #include "hphp/runtime/eval/debugger/cmd/cmd_interrupt.h"
22 #include "hphp/runtime/vm/debugger_hook.h"
23 #include "hphp/runtime/vm/translator/translator-inline.h"
24 #include "tbb/concurrent_hash_map.h"
25 #include "hphp/util/logger.h"
26 #include "hphp/system/lib/systemlib.h"
28 namespace HPHP {
29 ///////////////////////////////////////////////////////////////////////////////
31 using namespace Eval;
32 using HPHP::Transl::CallerFrame;
34 const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_FILENAMES =
35 DebuggerClient::AutoCompleteFileNames;
36 const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_VARIABLES =
37 DebuggerClient::AutoCompleteVariables;
38 const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_CONSTANTS =
39 DebuggerClient::AutoCompleteConstants;
40 const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_CLASSES =
41 DebuggerClient::AutoCompleteClasses;
42 const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_FUNCTIONS =
43 DebuggerClient::AutoCompleteFunctions;
44 const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_CLASS_METHODS =
45 DebuggerClient::AutoCompleteClassMethods;
46 const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_CLASS_PROPERTIES =
47 DebuggerClient::AutoCompleteClassProperties;
48 const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_CLASS_CONSTANTS =
49 DebuggerClient::AutoCompleteClassConstants;
50 const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_KEYWORDS =
51 DebuggerClient::AutoCompleteKeyword;
52 const int64_t q_DebuggerClientCmdUser$$AUTO_COMPLETE_CODE =
53 DebuggerClient::AutoCompleteCode;
55 ///////////////////////////////////////////////////////////////////////////////
57 bool f_hphpd_install_user_command(CStrRef cmd, CStrRef clsname) {
58 return CmdUser::InstallCommand(cmd, clsname);
61 Array f_hphpd_get_user_commands() {
62 return CmdUser::GetCommands();
65 static const Trace::Module TRACEMOD = Trace::bcinterp;
67 void f_hphpd_break(bool condition /* = true */) {
68 TRACE(5, "in f_hphpd_break()\n");
69 if (!RuntimeOption::EnableDebugger || !condition ||
70 g_vmContext->m_dbgNoBreak) {
71 TRACE(5, "bail !%d || !%d || %d\n", RuntimeOption::EnableDebugger,
72 condition, g_vmContext->m_dbgNoBreak);
73 return;
75 CallerFrame cf;
76 Debugger::InterruptVMHook(HardBreakPoint);
77 if (RuntimeOption::EvalJit && !g_vmContext->m_interpreting &&
78 DEBUGGER_FORCE_INTR) {
79 TRACE(5, "switch mode\n");
80 throw VMSwitchModeException(true);
82 TRACE(5, "out f_hphpd_break()\n");
85 typedef tbb::concurrent_hash_map<std::string, DebuggerClient*> DbgCltMap;
86 static DbgCltMap s_dbgCltMap;
88 // if the DebuggerClient with the same name is already in use, return null
89 Variant f_hphpd_get_client(CStrRef name /* = null */) {
90 if (name.empty()) {
91 return uninit_null();
93 DebuggerClient *client = NULL;
94 std::string nameStr = name->toCPPString();
96 DbgCltMap::accessor acc;
97 if (s_dbgCltMap.insert(acc, nameStr)) {
98 client = new DebuggerClient(nameStr);
99 acc->second = client;
100 } else {
101 client = acc->second;
103 if (!client->apiGrab()) {
104 // already grabbed by another request
105 return uninit_null();
108 p_DebuggerClient clt(NEWOBJ(c_DebuggerClient));
109 clt->m_client = client;
110 return clt;
113 Variant f_hphpd_client_ctrl(CStrRef name, CStrRef op) {
114 DebuggerClient *client = NULL;
115 std::string nameStr = name->toCPPString();
117 DbgCltMap::const_accessor acc;
118 if (!s_dbgCltMap.find(acc, nameStr)) {
119 if (op.equal("getstate")) {
120 return q_DebuggerClient$$STATE_INVALID;
121 } else {
122 raise_warning("client %s does not exist", name.data());
123 return uninit_null();
126 client = acc->second;
128 if (op.equal("interrupt")) {
129 if (client->getClientState() < DebuggerClient::StateReadyForCommand) {
130 raise_warning("client is not initialized");
131 return uninit_null();
133 if (client->getClientState() != DebuggerClient::StateBusy) {
134 raise_warning("client is not in a busy state");
135 return uninit_null();
137 client->onSignal(SIGINT);
138 return uninit_null();
139 } else if (op.equal("getstate")) {
140 return client->getClientState();
141 } else if (op.equal("reset")) {
142 // To handle the case when client is in a bad state, e.g. the grabbing
143 // request encountered error and did not get chance to destruct or call
144 // sweep. It will remove the client from the map. Here we'd rather take
145 // the risk of leaking the client than the risk of chasing dangling
146 // pointers.
148 // FIXME: it's unclear why it should be possible that we would not
149 // get a chance to destruct or call sweep.
150 return s_dbgCltMap.erase(nameStr);
153 raise_warning("unknown op %s", op.data());
155 return uninit_null();
158 ///////////////////////////////////////////////////////////////////////////////
160 c_DebuggerProxyCmdUser::c_DebuggerProxyCmdUser(Class* cb) : ExtObjectData(cb) {
163 c_DebuggerProxyCmdUser::~c_DebuggerProxyCmdUser() {
166 void c_DebuggerProxyCmdUser::t___construct() {
169 bool c_DebuggerProxyCmdUser::t_islocal() {
170 return m_proxy->isLocal();
173 Variant c_DebuggerProxyCmdUser::t_send(CObjRef cmd) {
174 CmdUser cmdUser(cmd);
175 return m_proxy->sendToClient(&cmdUser);
178 ///////////////////////////////////////////////////////////////////////////////
180 c_DebuggerClientCmdUser::c_DebuggerClientCmdUser(Class* cb) : ExtObjectData(cb) {
183 c_DebuggerClientCmdUser::~c_DebuggerClientCmdUser() {
186 void c_DebuggerClientCmdUser::t___construct() {
189 void c_DebuggerClientCmdUser::t_quit() {
190 m_client->quit();
193 static String format_string(DebuggerClient *client,
194 int _argc, CStrRef format, CArrRef _argv) {
195 Variant ret = f_sprintf(_argc, format, _argv);
196 if (ret.isString()) {
197 return ret;
199 client->error("Debugger extension failed to format string: %s",
200 format.data());
201 return "";
204 void c_DebuggerClientCmdUser::t_print(int _argc, CStrRef format,
205 CArrRef _argv /* = null_array */) {
206 m_client->print(format_string(m_client, _argc, format, _argv));
209 void c_DebuggerClientCmdUser::t_help(int _argc, CStrRef format,
210 CArrRef _argv /* = null_array */) {
211 m_client->help(format_string(m_client, _argc, format, _argv));
214 void c_DebuggerClientCmdUser::t_info(int _argc, CStrRef format,
215 CArrRef _argv /* = null_array */) {
216 m_client->info(format_string(m_client, _argc, format, _argv));
219 void c_DebuggerClientCmdUser::t_output(int _argc, CStrRef format,
220 CArrRef _argv /* = null_array */) {
221 m_client->output(format_string(m_client, _argc, format, _argv));
224 void c_DebuggerClientCmdUser::t_error(int _argc, CStrRef format,
225 CArrRef _argv /* = null_array */) {
226 m_client->error(format_string(m_client, _argc, format, _argv));
229 void c_DebuggerClientCmdUser::t_code(CStrRef source, int highlight_line /* = 0 */,
230 int start_line_no /* = 0 */,
231 int end_line_no /* = 0 */) {
232 m_client->code(source, highlight_line, start_line_no, end_line_no);
235 Variant c_DebuggerClientCmdUser::t_ask(int _argc, CStrRef format,
236 CArrRef _argv /* = null_array */) {
237 String ret = format_string(m_client, _argc, format, _argv);
238 return String::FromChar(m_client->ask("%s", ret.data()));
241 String c_DebuggerClientCmdUser::t_wrap(CStrRef str) {
242 return m_client->wrap(str.data());
245 void c_DebuggerClientCmdUser::t_helptitle(CStrRef str) {
246 m_client->helpTitle(str.data());
249 void c_DebuggerClientCmdUser::t_helpcmds(int _argc, CStrRef cmd, CStrRef desc,
250 CArrRef _argv /* = null_array */) {
251 std::vector<String> holders;
252 std::vector<const char *> cmds;
253 cmds.push_back(cmd.data());
254 cmds.push_back(desc.data());
255 for (int i = 0; i < _argv.size(); i++) {
256 String s = _argv[i].toString();
257 holders.push_back(s);
258 cmds.push_back(s.data());
260 m_client->helpCmds(cmds);
263 void c_DebuggerClientCmdUser::t_helpbody(CStrRef str) {
264 m_client->helpBody(str.data());
267 void c_DebuggerClientCmdUser::t_helpsection(CStrRef str) {
268 m_client->helpSection(str.data());
271 void c_DebuggerClientCmdUser::t_tutorial(CStrRef str) {
272 m_client->tutorial(str.data());
275 String c_DebuggerClientCmdUser::t_getcode() {
276 return m_client->getCode();
279 String c_DebuggerClientCmdUser::t_getcommand() {
280 return m_client->getCommand();
283 bool c_DebuggerClientCmdUser::t_arg(int index, CStrRef str) {
284 return m_client->arg(index + 1, str.data());
287 int64_t c_DebuggerClientCmdUser::t_argcount() {
288 return m_client->argCount() - 1;
291 String c_DebuggerClientCmdUser::t_argvalue(int index) {
292 return m_client->argValue(index + 1);
295 String c_DebuggerClientCmdUser::t_linerest(int index) {
296 return m_client->lineRest(index + 1);
299 Array c_DebuggerClientCmdUser::t_args() {
300 StringVec *args = m_client->args();
301 Array ret(Array::Create());
302 for (unsigned int i = 1; i < args->size(); i++) {
303 ret.append(String(args->at(i)));
305 return ret;
308 Variant c_DebuggerClientCmdUser::t_send(CObjRef cmd) {
309 CmdUser cmdUser(cmd);
310 m_client->sendToServer(&cmdUser);
311 return true;
314 Variant c_DebuggerClientCmdUser::t_xend(CObjRef cmd) {
315 CmdUser cmdUser(cmd);
316 CmdUserPtr ret = m_client->xend<CmdUser>(&cmdUser);
317 return ret->getUserCommand();
320 static const StaticString s_file("file");
321 static const StaticString s_line("line");
322 static const StaticString s_namespace("namespace");
323 static const StaticString s_class("class");
324 static const StaticString s_function("function");
325 static const StaticString s_text("text");
326 static const StaticString s_user("user");
327 static const StaticString s_configFName("configFName");
328 static const StaticString s_host("host");
329 static const StaticString s_port("port");
330 static const StaticString s_sandbox("sandbox");
332 Variant c_DebuggerClientCmdUser::t_getcurrentlocation() {
333 BreakPointInfoPtr bpi = m_client->getCurrentLocation();
334 if (!bpi) return Array::Create();
335 ArrayInit ret(6);
336 ret.set(s_file, String(bpi->m_file));
337 ret.set(s_line, (int64_t)bpi->m_line1);
338 ret.set(s_namespace, String(bpi->getNamespace()));
339 ret.set(s_class, String(bpi->getClass()));
340 ret.set(s_function, String(bpi->getFunction()));
341 ret.set(s_text, String(bpi->site()));
342 return ret.create();
345 Variant c_DebuggerClientCmdUser::t_getstacktrace() {
346 return m_client->getStackTrace();
349 int64_t c_DebuggerClientCmdUser::t_getframe() {
350 return m_client->getFrame();
353 void c_DebuggerClientCmdUser::t_printframe(int index) {
354 m_client->printFrame(index, m_client->getStackTrace()[index]);
357 void c_DebuggerClientCmdUser::t_addcompletion(CVarRef list) {
358 if (list.isInteger()) {
359 m_client->addCompletion((DebuggerClient::AutoComplete)list.toInt64());
360 } else {
361 Array arr = list.toArray(); // handles string, array and iterators
362 std::vector<std::string> items;
363 for (ArrayIter iter(arr); iter; ++iter) {
364 items.push_back(iter.second().toString()->toCPPString());
366 m_client->addCompletion(items);
370 ///////////////////////////////////////////////////////////////////////////////
372 const int64_t q_DebuggerClient$$STATE_INVALID = -1;
373 const int64_t q_DebuggerClient$$STATE_UNINIT
374 = DebuggerClient::StateUninit;
375 const int64_t q_DebuggerClient$$STATE_INITIALIZING
376 = DebuggerClient::StateInitializing;
377 const int64_t q_DebuggerClient$$STATE_READY_FOR_COMMAND
378 = DebuggerClient::StateReadyForCommand;
379 const int64_t q_DebuggerClient$$STATE_BUSY
380 = DebuggerClient::StateBusy;
382 c_DebuggerClient::c_DebuggerClient(Class* cb) : ExtObjectData(cb) {
383 m_client = NULL;
386 c_DebuggerClient::~c_DebuggerClient() {
387 sweep();
390 void c_DebuggerClient::t___construct() {
393 int64_t c_DebuggerClient::t_getstate() {
394 if (!m_client) {
395 return q_DebuggerClient$$STATE_INVALID;
397 return m_client->getClientState();
400 Variant c_DebuggerClient::t_init(CVarRef options) {
401 if (!m_client) {
402 raise_warning("invalid client");
403 return false;
405 if (m_client->getClientState() != DebuggerClient::StateUninit) {
406 return m_client->getClientState() == DebuggerClient::StateReadyForCommand;
408 if (!options.isArray()) {
409 raise_warning("options must be an array");
410 return false;
412 m_client->setClientState(DebuggerClient::StateInitializing);
414 DebuggerClientOptions ops;
415 ops.apiMode = true;
417 Array opsArr = options.toArray();
418 if (opsArr.exists(s_user)) {
419 ops.user = opsArr.rvalAtRef(s_user).toString().data();
420 } else {
421 raise_warning("must specify user in options");
422 return false;
425 if (opsArr.exists(s_configFName)) {
426 ops.configFName = opsArr.rvalAtRef(s_configFName).toString().data();
427 FILE *f = fopen(ops.configFName.c_str(), "r");
428 if (!f) {
429 raise_warning("cannot access config file %s", ops.configFName.c_str());
430 return false;
432 fclose(f);
435 if (opsArr.exists(s_host)) {
436 ops.host = opsArr.rvalAtRef(s_host).toString().data();
438 if (opsArr.exists(s_port)) {
439 ops.port = opsArr.rvalAtRef(s_port).toInt32();
441 if (opsArr.exists(s_sandbox)) {
442 ops.sandbox = opsArr.rvalAtRef(s_sandbox).toString().data();
445 m_client->init(ops);
447 if (ops.host.empty()) {
448 ops.host = "localhost";
450 if (ops.port < 0) {
451 ops.port = RuntimeOption::DebuggerServerPort;
453 bool ret = m_client->connect(ops.host, ops.port);
454 if (!ret) {
455 raise_warning("failed to connect to hhvm %s:%d", ops.host.c_str(),
456 ops.port);
457 return false;
460 // To wait for the session start interrupt
461 DebuggerCommandPtr cmd = m_client->waitForNextInterrupt();
462 if (!cmd->is(DebuggerCommand::KindOfInterrupt) ||
463 dynamic_pointer_cast<CmdInterrupt>(cmd)->getInterruptType() !=
464 SessionStarted) {
465 raise_warning("failed to load sandbox");
466 return false;
469 ret = m_client->initializeMachine();
470 if (!ret) {
471 raise_warning("failed to initialize machine info");
472 return false;
475 // To wait for the machine loading sandbox
476 cmd = m_client->waitForNextInterrupt();
477 if (!cmd->is(DebuggerCommand::KindOfInterrupt) ||
478 dynamic_pointer_cast<CmdInterrupt>(cmd)->getInterruptType() !=
479 SessionStarted) {
480 raise_warning("failed to load sandbox");
481 return false;
484 m_client->setClientState(DebuggerClient::StateReadyForCommand);
486 return true;
489 Variant c_DebuggerClient::t_processcmd(CVarRef cmdName, CVarRef args) {
490 if (!m_client ||
491 m_client->getClientState() < DebuggerClient::StateReadyForCommand) {
492 raise_warning("client is not initialized");
493 return uninit_null();
495 if (m_client->getClientState() != DebuggerClient::StateReadyForCommand) {
496 raise_warning("client is not ready to take command");
497 return uninit_null();
499 if (!cmdName.isString()) {
500 raise_warning("cmdName must be string");
501 return uninit_null();
503 if (!args.isNull() && !args.isArray()) {
504 raise_warning("args must be null or array");
505 return uninit_null();
508 static const char *s_allowedCmds[] = {
509 "break", "continue", "down", "exception", "frame", "global",
510 "help", "info", "konstant", "next", "out", "print", "quit", "step",
511 "up", "variable", "where", "bt", "set", "inst", "=", "@", NULL
514 bool allowed = false;
515 for (int i = 0; ; i++) {
516 const char *cmd = s_allowedCmds[i];
517 if (cmd == NULL) {
518 break;
520 if (cmdName.same(cmd)) {
521 allowed = true;
522 break;
525 if (!allowed) {
526 raise_warning("unsupported command %s", cmdName.toString().data());
527 return uninit_null();
530 m_client->setCommand(cmdName.toString().data());
531 StringVec *clientArgs = m_client->args();
532 clientArgs->clear();
533 if (!args.isNull()) {
534 for (ArrayIter iter(args.toArray()); iter; ++iter) {
535 CStrRef arg = iter.second().toString();
536 clientArgs->push_back(std::string(arg.data(), arg.size()));
539 try {
540 if (!m_client->process()) {
541 raise_warning("command \"%s\" not found", cmdName.toString().data());
543 } catch (DebuggerConsoleExitException &e) {
544 // Flow-control command goes here
545 Logger::Info("wait for debugger client to stop");
546 m_client->setTakingInterrupt();
547 m_client->setClientState(DebuggerClient::StateBusy);
548 DebuggerCommandPtr cmd = m_client->waitForNextInterrupt();
549 if (!cmd) {
550 raise_warning("not getting a command");
551 } else if (cmd->is(DebuggerCommand::KindOfInterrupt)) {
552 CmdInterruptPtr cmdInterrupt = dynamic_pointer_cast<CmdInterrupt>(cmd);
553 cmdInterrupt->onClientD(m_client);
554 } else {
555 // Previous pending commands
556 cmd->handleReply(m_client);
557 cmd->setClientOutput(m_client);
559 Logger::Info("debugger client ready for command");
560 } catch (DebuggerClientExitException &e) {
561 const std::string& nameStr = m_client->getNameApi();
562 Logger::Info("client %s disconnected", nameStr.c_str());
563 s_dbgCltMap.erase(nameStr);
564 delete m_client;
565 m_client = NULL;
566 return true;
567 } catch (DebuggerProtocolException &e) {
568 raise_warning("DebuggerProtocolException");
569 return uninit_null();
572 return m_client->getOutputArray();
575 void c_DebuggerClient::sweep() {
576 if (m_client) {
577 // Note: it's important that resetSmartAllocatedMembers happens
578 // before clearCachedLocal(), because the smart allocated pointers
579 // are already invalid.
580 m_client->resetSmartAllocatedMembers();
581 m_client->clearCachedLocal();
582 m_client->apiFree();
586 ///////////////////////////////////////////////////////////////////////////////