2 +----------------------------------------------------------------------+
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"
29 ///////////////////////////////////////////////////////////////////////////////
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
);
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 */) {
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
);
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
;
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
;
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
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() {
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()) {
199 client
->error("Debugger extension failed to format string: %s",
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
)));
308 Variant
c_DebuggerClientCmdUser::t_send(CObjRef cmd
) {
309 CmdUser
cmdUser(cmd
);
310 m_client
->sendToServer(&cmdUser
);
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();
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()));
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());
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
) {
386 c_DebuggerClient::~c_DebuggerClient() {
390 void c_DebuggerClient::t___construct() {
393 int64_t c_DebuggerClient::t_getstate() {
395 return q_DebuggerClient$$STATE_INVALID
;
397 return m_client
->getClientState();
400 Variant
c_DebuggerClient::t_init(CVarRef options
) {
402 raise_warning("invalid client");
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");
412 m_client
->setClientState(DebuggerClient::StateInitializing
);
414 DebuggerClientOptions ops
;
417 Array opsArr
= options
.toArray();
418 if (opsArr
.exists(s_user
)) {
419 ops
.user
= opsArr
.rvalAtRef(s_user
).toString().data();
421 raise_warning("must specify user in options");
425 if (opsArr
.exists(s_configFName
)) {
426 ops
.configFName
= opsArr
.rvalAtRef(s_configFName
).toString().data();
427 FILE *f
= fopen(ops
.configFName
.c_str(), "r");
429 raise_warning("cannot access config file %s", ops
.configFName
.c_str());
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();
447 if (ops
.host
.empty()) {
448 ops
.host
= "localhost";
451 ops
.port
= RuntimeOption::DebuggerServerPort
;
453 bool ret
= m_client
->connect(ops
.host
, ops
.port
);
455 raise_warning("failed to connect to hhvm %s:%d", ops
.host
.c_str(),
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() !=
465 raise_warning("failed to load sandbox");
469 ret
= m_client
->initializeMachine();
471 raise_warning("failed to initialize machine info");
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() !=
480 raise_warning("failed to load sandbox");
484 m_client
->setClientState(DebuggerClient::StateReadyForCommand
);
489 Variant
c_DebuggerClient::t_processcmd(CVarRef cmdName
, CVarRef args
) {
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
];
520 if (cmdName
.same(cmd
)) {
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();
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()));
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();
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
);
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
);
567 } catch (DebuggerProtocolException
&e
) {
568 raise_warning("DebuggerProtocolException");
569 return uninit_null();
572 return m_client
->getOutputArray();
575 void c_DebuggerClient::sweep() {
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();
586 ///////////////////////////////////////////////////////////////////////////////