PageRenderTime 62ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/hphp/runtime/debugger/debugger_client.cpp

http://github.com/facebook/hiphop-php
C++ | 2564 lines | 2105 code | 286 blank | 173 comment | 450 complexity | 7a9a9ae2eb28f80e908e9b44e9b04305 MD5 | raw file
Possible License(s): LGPL-2.1, BSD-2-Clause, BSD-3-Clause, MPL-2.0-no-copyleft-exception, MIT, LGPL-2.0, Apache-2.0
  1. /*
  2. +----------------------------------------------------------------------+
  3. | HipHop for PHP |
  4. +----------------------------------------------------------------------+
  5. | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
  6. +----------------------------------------------------------------------+
  7. | This source file is subject to version 3.01 of the PHP license, |
  8. | that is bundled with this package in the file LICENSE, and is |
  9. | available through the world-wide-web at the following url: |
  10. | http://www.php.net/license/3_01.txt |
  11. | If you did not receive a copy of the PHP license and are unable to |
  12. | obtain it through the world-wide-web, please send a note to |
  13. | license@php.net so we can mail you a copy immediately. |
  14. +----------------------------------------------------------------------+
  15. */
  16. #include "hphp/runtime/debugger/debugger_client.h"
  17. #include <signal.h>
  18. #include <fstream>
  19. #include "hphp/runtime/base/array-init.h"
  20. #include "hphp/runtime/base/array-iterator.h"
  21. #include "hphp/runtime/base/builtin-functions.h"
  22. #include "hphp/runtime/base/config.h"
  23. #include "hphp/runtime/base/preg.h"
  24. #include "hphp/runtime/base/program-functions.h"
  25. #include "hphp/runtime/base/string-util.h"
  26. #include "hphp/runtime/base/variable-serializer.h"
  27. #include "hphp/runtime/debugger/cmd/all.h"
  28. #include "hphp/runtime/debugger/debugger_command.h"
  29. #include "hphp/runtime/ext/sockets/ext_sockets.h"
  30. #include "hphp/runtime/ext/std/ext_std_network.h"
  31. #include "hphp/runtime/ext/string/ext_string.h"
  32. #include "hphp/runtime/vm/treadmill.h"
  33. #include "hphp/util/logger.h"
  34. #include "hphp/util/process.h"
  35. #include "hphp/util/stack-trace.h"
  36. #include "hphp/util/string-vsnprintf.h"
  37. #include "hphp/util/text-art.h"
  38. #include "hphp/util/text-color.h"
  39. #include <boost/scoped_ptr.hpp>
  40. #include <folly/Conv.h>
  41. #include <folly/portability/Unistd.h>
  42. #define USE_VARARGS
  43. #define PREFER_STDARG
  44. #ifdef USE_EDITLINE
  45. #include <editline/readline.h>
  46. #include <histedit.h>
  47. #else
  48. #include <readline/readline.h>
  49. #include <readline/history.h>
  50. #include <algorithm>
  51. #include <map>
  52. #include <memory>
  53. #include <set>
  54. #include <vector>
  55. #endif
  56. using namespace HPHP::TextArt;
  57. #define PHP_WORD_BREAK_CHARACTERS " \t\n\"\\'`@=;,|{[()]}+*%^!~&"
  58. namespace HPHP { namespace Eval {
  59. ///////////////////////////////////////////////////////////////////////////////
  60. TRACE_SET_MOD(debugger);
  61. static boost::scoped_ptr<DebuggerClient> debugger_client;
  62. const StaticString
  63. s_name("name"),
  64. s_cmds("cmds"),
  65. s_hhvm_never_save_config("hhvm.never_save_config");
  66. static String wordwrap(const String& str, int width /* = 75 */,
  67. const String& wordbreak /* = "\n" */,
  68. bool cut /* = false */) {
  69. Array args = Array::Create();
  70. args.append(str);
  71. args.append(width);
  72. args.append(wordbreak);
  73. args.append(cut);
  74. return vm_call_user_func("wordwrap", args).toString();
  75. }
  76. struct DebuggerExtension final : Extension {
  77. DebuggerExtension() : Extension("hhvm.debugger", NO_EXTENSION_VERSION_YET) {}
  78. } s_debugger_extension;
  79. static DebuggerClient& getStaticDebuggerClient() {
  80. TRACE(2, "DebuggerClient::getStaticDebuggerClient\n");
  81. /*
  82. * DebuggerClient acquires global mutexes in its constructor, so we
  83. * allocate debugger_client lazily to ensure that all of the
  84. * global mutexes have been initialized before we enter the
  85. * constructor.
  86. *
  87. * This initialization is thread-safe because program-functions.cpp
  88. * must call Debugger::StartClient (which ends up here) before any
  89. * additional threads are created.
  90. */
  91. if (!debugger_client) {
  92. debugger_client.reset(new DebuggerClient);
  93. }
  94. return *debugger_client;
  95. }
  96. ///////////////////////////////////////////////////////////////////////////////
  97. // readline setups
  98. static char* debugger_generator(const char* text, int state) {
  99. TRACE(2, "DebuggerClient::debugger_generator\n");
  100. return getStaticDebuggerClient().getCompletion(text, state);
  101. }
  102. static char **debugger_completion(const char *text, int start, int end) {
  103. TRACE(2, "DebuggerClient::debugger_completion\n");
  104. if (getStaticDebuggerClient().setCompletion(text, start, end)) {
  105. return rl_completion_matches((char*)text, &debugger_generator);
  106. }
  107. return nullptr;
  108. }
  109. #ifndef USE_EDITLINE
  110. static rl_hook_func_t *old_rl_startup_hook = nullptr;
  111. static int saved_history_line_to_use = -1;
  112. static int last_saved_history_line = -1;
  113. static bool history_full() {
  114. return (history_is_stifled() && history_length >= history_max_entries);
  115. }
  116. static int set_saved_history() {
  117. if (history_full() && saved_history_line_to_use < history_length - 1) {
  118. saved_history_line_to_use++;
  119. }
  120. if (saved_history_line_to_use >= 0) {
  121. rl_get_previous_history(history_length - saved_history_line_to_use, 0);
  122. last_saved_history_line = saved_history_line_to_use;
  123. }
  124. saved_history_line_to_use = -1;
  125. rl_startup_hook = old_rl_startup_hook;
  126. return 0;
  127. }
  128. static int operate_and_get_next(int /*count*/, int c) {
  129. /* Accept the current line. */
  130. rl_newline (1, c);
  131. /* Find the current line, and find the next line to use. */
  132. int where = where_history();
  133. if (history_full() || (where >= history_length - 1)) {
  134. saved_history_line_to_use = where;
  135. } else {
  136. saved_history_line_to_use = where + 1;
  137. }
  138. old_rl_startup_hook = rl_startup_hook;
  139. rl_startup_hook = set_saved_history;
  140. return 0;
  141. }
  142. #endif
  143. static void debugger_signal_handler(int sig) {
  144. TRACE(2, "DebuggerClient::debugger_signal_handler\n");
  145. getStaticDebuggerClient().onSignal(sig);
  146. }
  147. void DebuggerClient::onSignal(int /*sig*/) {
  148. TRACE(2, "DebuggerClient::onSignal\n");
  149. if (m_inputState == TakingInterrupt) {
  150. if (m_sigCount == 0) {
  151. usageLogEvent("signal start");
  152. info("Pausing program execution, please wait...");
  153. } else if (m_sigCount == 1) {
  154. usageLogEvent("signal wait");
  155. help("Still attempting to pause program execution...");
  156. help(" Sometimes this takes a few seconds, so give it a chance,");
  157. help(" or press ctrl-c again to give up and terminate the debugger.");
  158. } else {
  159. usageLogEvent("signal quit");
  160. error("Debugger is quitting.");
  161. if (!getStaticDebuggerClient().isLocal()) {
  162. error(" Note: the program may still be running on the server.");
  163. }
  164. quit(); // NB: the machine is running, so can't send a real CmdQuit.
  165. return;
  166. }
  167. m_sigCount++;
  168. m_sigNum = CmdSignal::SignalBreak;
  169. } else {
  170. rl_line_buffer[0] = '\0';
  171. #ifndef USE_EDITLINE
  172. rl_free_line_state();
  173. rl_cleanup_after_signal();
  174. #endif
  175. rl_redisplay();
  176. }
  177. }
  178. int DebuggerClient::pollSignal() {
  179. TRACE(2, "DebuggerClient::pollSignal\n");
  180. if (m_scriptMode) {
  181. print(".....Debugger client still waiting for server response.....");
  182. }
  183. int ret = m_sigNum;
  184. m_sigNum = CmdSignal::SignalNone;
  185. return ret;
  186. }
  187. ///////////////////////////////////////////////////////////////////////////////
  188. /**
  189. * Initialization and shutdown.
  190. */
  191. struct ReadlineApp {
  192. ReadlineApp() {
  193. TRACE(2, "ReadlineApp::ReadlineApp\n");
  194. DebuggerClient::AdjustScreenMetrics();
  195. rl_attempted_completion_function = debugger_completion;
  196. rl_basic_word_break_characters = PHP_WORD_BREAK_CHARACTERS;
  197. #ifndef USE_EDITLINE
  198. rl_bind_keyseq("\\C-o", operate_and_get_next);
  199. rl_catch_signals = 0;
  200. #endif
  201. signal(SIGINT, debugger_signal_handler);
  202. TRACE(3, "ReadlineApp::ReadlineApp, about to call read_history\n");
  203. read_history((Process::GetHomeDirectory() +
  204. DebuggerClient::HistoryFileName).c_str());
  205. TRACE(3, "ReadlineApp::ReadlineApp, done calling read_history\n");
  206. }
  207. ~ReadlineApp() {
  208. TRACE(2, "ReadlineApp::~ReadlineApp\n");
  209. write_history((Process::GetHomeDirectory() +
  210. DebuggerClient::HistoryFileName).c_str());
  211. }
  212. };
  213. /**
  214. * Displaying a spinning wait icon.
  215. */
  216. struct ReadlineWaitCursor {
  217. ReadlineWaitCursor()
  218. : m_thread(this, &ReadlineWaitCursor::animate), m_waiting(true) {
  219. TRACE(2, "ReadlineWaitCursor::ReadlineWaitCursor\n");
  220. m_thread.start();
  221. }
  222. ~ReadlineWaitCursor() {
  223. TRACE(2, "ReadlineWaitCursor::~ReadlineWaitCursor\n");
  224. m_waiting = false;
  225. m_thread.waitForEnd();
  226. }
  227. void animate() {
  228. if (rl_point <= 0) return;
  229. auto p = rl_point - 1;
  230. auto orig = rl_line_buffer[p];
  231. while (m_waiting) {
  232. frame('|', p); frame('/', p); frame('-', p); frame('\\', p);
  233. rl_line_buffer[p] = orig;
  234. rl_redisplay();
  235. }
  236. }
  237. private:
  238. AsyncFunc<ReadlineWaitCursor> m_thread;
  239. bool m_waiting;
  240. void frame(char ch, int point) {
  241. rl_line_buffer[point] = ch;
  242. rl_redisplay();
  243. usleep(100000);
  244. }
  245. };
  246. ///////////////////////////////////////////////////////////////////////////////
  247. int DebuggerClient::LineWidth = 76;
  248. int DebuggerClient::CodeBlockSize = 20;
  249. int DebuggerClient::ScrollBlockSize = 20;
  250. const char *DebuggerClient::LineNoFormat = "%4d ";
  251. const char *DebuggerClient::LineNoFormatWithStar = "%4d*";
  252. const char *DebuggerClient::LocalPrompt = "hphpd";
  253. const char *DebuggerClient::ConfigFileName = ".hphpd.ini";
  254. const char *DebuggerClient::LegacyConfigFileName = ".hphpd.hdf";
  255. const char *DebuggerClient::HistoryFileName = ".hphpd.history";
  256. std::string DebuggerClient::HomePrefix = "/home";
  257. bool DebuggerClient::UseColor = true;
  258. bool DebuggerClient::NoPrompt = false;
  259. const char *DebuggerClient::HelpColor = nullptr;
  260. const char *DebuggerClient::InfoColor = nullptr;
  261. const char *DebuggerClient::OutputColor = nullptr;
  262. const char *DebuggerClient::ErrorColor = nullptr;
  263. const char *DebuggerClient::ItemNameColor = nullptr;
  264. const char *DebuggerClient::HighlightForeColor = nullptr;
  265. const char *DebuggerClient::HighlightBgColor = nullptr;
  266. const char *DebuggerClient::DefaultCodeColors[] = {
  267. /* None */ nullptr, nullptr,
  268. /* Keyword */ nullptr, nullptr,
  269. /* Comment */ nullptr, nullptr,
  270. /* String */ nullptr, nullptr,
  271. /* Variable */ nullptr, nullptr,
  272. /* Html */ nullptr, nullptr,
  273. /* Tag */ nullptr, nullptr,
  274. /* Declaration */ nullptr, nullptr,
  275. /* Constant */ nullptr, nullptr,
  276. /* LineNo */ nullptr, nullptr,
  277. };
  278. void DebuggerClient::LoadColors(const IniSetting::Map& ini, Hdf hdf) {
  279. TRACE(2, "DebuggerClient::LoadColors\n");
  280. HelpColor = LoadColor(ini, hdf, "Color.Help", "BROWN");
  281. InfoColor = LoadColor(ini, hdf, "Color.Info", "GREEN");
  282. OutputColor = LoadColor(ini, hdf, "Color.Output", "CYAN");
  283. ErrorColor = LoadColor(ini, hdf, "Color.Error", "RED");
  284. ItemNameColor = LoadColor(ini, hdf, "Color.ItemName", "GRAY");
  285. HighlightForeColor = LoadColor(ini, hdf, "Color.HighlightForeground", "RED");
  286. HighlightBgColor = LoadBgColor(ini, hdf, "Color.HighlightBackground", "GRAY");
  287. Hdf code = hdf["Code"];
  288. LoadCodeColor(CodeColorKeyword, ini, hdf, "Color.Code.Keyword",
  289. "CYAN");
  290. LoadCodeColor(CodeColorComment, ini, hdf, "Color.Code.Comment",
  291. "RED");
  292. LoadCodeColor(CodeColorString, ini, hdf, "Color.Code.String",
  293. "GREEN");
  294. LoadCodeColor(CodeColorVariable, ini, hdf, "Color.Code.Variable",
  295. "BROWN");
  296. LoadCodeColor(CodeColorHtml, ini, hdf, "Color.Code.Html",
  297. "GRAY");
  298. LoadCodeColor(CodeColorTag, ini, hdf, "Color.Code.Tag",
  299. "MAGENTA");
  300. LoadCodeColor(CodeColorDeclaration, ini, hdf, "Color.Code.Declaration",
  301. "BLUE");
  302. LoadCodeColor(CodeColorConstant, ini, hdf, "Color.Code.Constant",
  303. "MAGENTA");
  304. LoadCodeColor(CodeColorLineNo, ini, hdf, "Color.Code.LineNo",
  305. "GRAY");
  306. }
  307. const char *DebuggerClient::LoadColor(const IniSetting::Map& ini, Hdf hdf,
  308. const std::string& setting,
  309. const char *defaultName) {
  310. TRACE(2, "DebuggerClient::LoadColor\n");
  311. const char *name = Config::Get(ini, hdf, setting, defaultName);
  312. hdf = name; // for starter
  313. const char *color = get_color_by_name(name);
  314. if (color == nullptr) {
  315. Logger::Error("Bad color name %s", name);
  316. color = get_color_by_name(defaultName);
  317. }
  318. return color;
  319. }
  320. const char *DebuggerClient::LoadBgColor(const IniSetting::Map& ini, Hdf hdf,
  321. const std::string& setting,
  322. const char *defaultName) {
  323. TRACE(2, "DebuggerClient::LoadBgColor\n");
  324. const char *name = Config::Get(ini, hdf, setting, defaultName);
  325. hdf = name; // for starter
  326. const char *color = get_bgcolor_by_name(name);
  327. if (color == nullptr) {
  328. Logger::Error("Bad color name %s", name);
  329. color = get_bgcolor_by_name(defaultName);
  330. }
  331. return color;
  332. }
  333. void DebuggerClient::LoadCodeColor(CodeColor index, const IniSetting::Map& ini,
  334. Hdf hdf, const std::string& setting,
  335. const char *defaultName) {
  336. TRACE(2, "DebuggerClient::LoadCodeColor\n");
  337. const char *color = LoadColor(ini, hdf, setting, defaultName);
  338. DefaultCodeColors[index * 2] = color;
  339. DefaultCodeColors[index * 2 + 1] = color ? ANSI_COLOR_END : nullptr;
  340. }
  341. req::ptr<Socket> DebuggerClient::Start(const DebuggerClientOptions &options) {
  342. TRACE(2, "DebuggerClient::Start\n");
  343. auto ret = getStaticDebuggerClient().connectLocal();
  344. getStaticDebuggerClient().start(options);
  345. return ret;
  346. }
  347. void DebuggerClient::Stop() {
  348. TRACE(2, "DebuggerClient::Stop\n");
  349. if (debugger_client) {
  350. debugger_client.reset();
  351. }
  352. }
  353. void DebuggerClient::AdjustScreenMetrics() {
  354. TRACE(2, "entered: DebuggerClient::AdjustScreenMetrics\n");
  355. int rows = 0; int cols = 0;
  356. rl_get_screen_size(&rows, &cols);
  357. if (rows > 0 && cols > 0) {
  358. LineWidth = cols - 4;
  359. ScrollBlockSize = CodeBlockSize = rows - (rows >> 2);
  360. }
  361. TRACE(2, "leaving: DebuggerClient::AdjustScreenMetrics\n");
  362. }
  363. bool DebuggerClient::IsValidNumber(const std::string &arg) {
  364. TRACE(2, "DebuggerClient::IsValidNumber\n");
  365. if (arg.empty()) return false;
  366. for (auto c : arg) {
  367. if (!isdigit(c)) {
  368. return false;
  369. }
  370. }
  371. return true;
  372. }
  373. String DebuggerClient::FormatVariable(
  374. const Variant& v,
  375. char format /* = 'd' */
  376. ) {
  377. TRACE(2, "DebuggerClient::FormatVariable\n");
  378. String value;
  379. try {
  380. auto const t =
  381. format == 'r' ? VariableSerializer::Type::PrintR :
  382. format == 'v' ? VariableSerializer::Type::VarDump :
  383. VariableSerializer::Type::DebuggerDump;
  384. VariableSerializer vs(t, 0, 2);
  385. value = vs.serialize(v, true);
  386. } catch (const StringBufferLimitException& e) {
  387. value = "Serialization limit reached";
  388. } catch (...) {
  389. assertx(false);
  390. throw;
  391. }
  392. return value;
  393. }
  394. /*
  395. * Serializes a Variant, and truncates it to a limit if necessary. Returns the
  396. * truncated result, and the number of bytes truncated.
  397. */
  398. String DebuggerClient::FormatVariableWithLimit(const Variant& v, int maxlen) {
  399. assertx(maxlen >= 0);
  400. VariableSerializer vs(VariableSerializer::Type::DebuggerDump, 0, 2);
  401. auto const value = vs.serializeWithLimit(v, maxlen + 1);
  402. if (value.length() <= maxlen) {
  403. return value;
  404. }
  405. StringBuffer sb;
  406. sb.append(folly::StringPiece{value.data(), static_cast<size_t>(maxlen)});
  407. sb.append(" ...(omitted)");
  408. return sb.detach();
  409. }
  410. String DebuggerClient::FormatInfoVec(const IDebuggable::InfoVec &info,
  411. int *nameLen /* = NULL */) {
  412. TRACE(2, "DebuggerClient::FormatInfoVec\n");
  413. // vertical align names
  414. int maxlen = 0;
  415. for (unsigned int i = 0; i < info.size(); i++) {
  416. int len = strlen(info[i].first);
  417. if (len > maxlen) maxlen = len;
  418. }
  419. // print
  420. StringBuffer sb;
  421. for (unsigned int i = 0; i < info.size(); i++) {
  422. if (ItemNameColor) sb.append(ItemNameColor);
  423. std::string name = info[i].first;
  424. name += ": ";
  425. sb.append(name.substr(0, maxlen + 4));
  426. if (ItemNameColor) sb.append(ANSI_COLOR_END);
  427. if (OutputColor) sb.append(OutputColor);
  428. sb.append(info[i].second);
  429. if (OutputColor) sb.append(ANSI_COLOR_END);
  430. sb.append("\n");
  431. }
  432. if (nameLen) *nameLen = maxlen + 4;
  433. return sb.detach();
  434. }
  435. String DebuggerClient::FormatTitle(const char *title) {
  436. TRACE(2, "DebuggerClient::FormatTitle\n");
  437. String dash = HHVM_FN(str_repeat)(BOX_H, (LineWidth - strlen(title)) / 2 - 4);
  438. StringBuffer sb;
  439. sb.append("\n");
  440. sb.append(" ");
  441. sb.append(dash);
  442. sb.append(" "); sb.append(title); sb.append(" ");
  443. sb.append(dash);
  444. sb.append("\n");
  445. return sb.detach();
  446. }
  447. ///////////////////////////////////////////////////////////////////////////////
  448. DebuggerClient::DebuggerClient()
  449. : m_tutorial(0), m_scriptMode(false),
  450. m_logFile(""), m_logFileHandler(nullptr),
  451. m_mainThread(this, &DebuggerClient::run), m_stopped(false),
  452. m_inputState(TakingCommand),
  453. m_sigNum(CmdSignal::SignalNone), m_sigCount(0),
  454. m_acLen(0), m_acIndex(0), m_acPos(0), m_acLiveListsDirty(true),
  455. m_threadId(0), m_listLine(0), m_listLineFocus(0),
  456. m_frame(0),
  457. m_unknownCmd(false) {
  458. TRACE(2, "DebuggerClient::DebuggerClient\n");
  459. Debugger::InitUsageLogging();
  460. }
  461. DebuggerClient::~DebuggerClient() {
  462. TRACE(2, "DebuggerClient::~DebuggerClient\n");
  463. m_stopped = true;
  464. m_mainThread.waitForEnd();
  465. FILE *f = getLogFileHandler();
  466. if (f != nullptr) {
  467. fclose(f);
  468. setLogFileHandler(nullptr);
  469. }
  470. }
  471. void DebuggerClient::closeAllConnections() {
  472. TRACE(2, "DebuggerClient::closeAllConnections\n");
  473. for (unsigned int i = 0; i < m_machines.size(); i++) {
  474. m_machines[i]->m_thrift.close();
  475. }
  476. }
  477. bool DebuggerClient::isLocal() {
  478. TRACE(2, "DebuggerClient::isLocal\n");
  479. return m_machines[0] == m_machine;
  480. }
  481. bool DebuggerClient::connect(const std::string &host, int port) {
  482. TRACE(2, "DebuggerClient::connect\n");
  483. assertx((!m_machines.empty() && m_machines[0]->m_name == LocalPrompt));
  484. // First check for an existing connect, and reuse that.
  485. for (unsigned int i = 1; i < m_machines.size(); i++) {
  486. if (HHVM_FN(gethostbyname)(m_machines[i]->m_name) ==
  487. HHVM_FN(gethostbyname)(host)) {
  488. switchMachine(m_machines[i]);
  489. return false;
  490. }
  491. }
  492. return connectRemote(host, port);
  493. }
  494. bool DebuggerClient::connectRPC(const std::string &host, int port) {
  495. TRACE(2, "DebuggerClient::connectRPC\n");
  496. assertx(!m_machines.empty());
  497. auto local = m_machines[0];
  498. assertx(local->m_name == LocalPrompt);
  499. local->m_rpcHost = host;
  500. local->m_rpcPort = port;
  501. switchMachine(local);
  502. m_rpcHost = "rpc:" + host;
  503. usageLogEvent("RPC connect", m_rpcHost);
  504. return !local->m_interrupting;
  505. }
  506. bool DebuggerClient::disconnect() {
  507. TRACE(2, "DebuggerClient::disconnect\n");
  508. assertx(!m_machines.empty());
  509. auto local = m_machines[0];
  510. assertx(local->m_name == LocalPrompt);
  511. local->m_rpcHost.clear();
  512. local->m_rpcPort = 0;
  513. switchMachine(local);
  514. return !local->m_interrupting;
  515. }
  516. void DebuggerClient::switchMachine(std::shared_ptr<DMachineInfo> machine) {
  517. TRACE(2, "DebuggerClient::switchMachine\n");
  518. m_rpcHost.clear();
  519. machine->m_initialized = false; // even if m_machine == machine
  520. if (m_machine != machine) {
  521. m_machine = machine;
  522. m_sandboxes.clear();
  523. m_threads.clear();
  524. m_threadId = 0;
  525. m_breakpoint.reset();
  526. m_matched.clear();
  527. m_listFile.clear();
  528. m_listLine = 0;
  529. m_listLineFocus = 0;
  530. m_stacktrace.reset();
  531. m_frame = 0;
  532. }
  533. }
  534. req::ptr<Socket> DebuggerClient::connectLocal() {
  535. TRACE(2, "DebuggerClient::connectLocal\n");
  536. int fds[2];
  537. if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) != 0) {
  538. throw Exception("unable to create socket pair for local debugging");
  539. }
  540. auto socket1 = req::make<StreamSocket>(fds[0], AF_UNIX);
  541. auto socket2 = req::make<StreamSocket>(fds[1], AF_UNIX);
  542. socket1->unregister();
  543. socket2->unregister();
  544. auto machine = std::make_shared<DMachineInfo>();
  545. machine->m_sandboxAttached = true;
  546. machine->m_name = LocalPrompt;
  547. machine->m_thrift.create(socket1);
  548. assertx(m_machines.empty());
  549. m_machines.push_back(machine);
  550. switchMachine(machine);
  551. return socket2;
  552. }
  553. bool DebuggerClient::connectRemote(const std::string &host, int port) {
  554. TRACE(2, "DebuggerClient::connectRemote\n");
  555. if (port <= 0) {
  556. port = RuntimeOption::DebuggerServerPort;
  557. }
  558. info("Connecting to %s:%d...", host.c_str(), port);
  559. if (tryConnect(host, port, false)) {
  560. return true;
  561. }
  562. error("Unable to connect to %s:%d.", host.c_str(), port);
  563. return false;
  564. }
  565. bool DebuggerClient::reconnect() {
  566. TRACE(2, "DebuggerClient::reconnect\n");
  567. assertx(m_machine);
  568. auto& host = m_machine->m_name;
  569. int port = m_machine->m_port;
  570. if (port <= 0) {
  571. return false;
  572. }
  573. info("Re-connecting to %s:%d...", host.c_str(), port);
  574. m_machine->m_thrift.close(); // Close the old socket, it may still be open.
  575. if (tryConnect(host, port, true)) {
  576. return true;
  577. }
  578. error("Still unable to connect to %s:%d.", host.c_str(), port);
  579. return false;
  580. }
  581. bool DebuggerClient::tryConnect(const std::string &host, int port,
  582. bool clearmachines) {
  583. struct addrinfo *ai;
  584. struct addrinfo hint;
  585. memset(&hint, 0, sizeof(hint));
  586. hint.ai_family = AF_UNSPEC;
  587. hint.ai_socktype = SOCK_STREAM;
  588. if (RuntimeOption::DebuggerDisableIPv6) {
  589. hint.ai_family = AF_INET;
  590. }
  591. if (getaddrinfo(host.c_str(), nullptr, &hint, &ai)) {
  592. return false;
  593. }
  594. SCOPE_EXIT {
  595. freeaddrinfo(ai);
  596. };
  597. /* try possible families (v4, v6) until we get a connection */
  598. struct addrinfo *cur;
  599. for (cur = ai; cur; cur = cur->ai_next) {
  600. auto sock = req::make<StreamSocket>(
  601. socket(cur->ai_family, cur->ai_socktype, 0),
  602. cur->ai_family,
  603. cur->ai_addr->sa_data,
  604. port
  605. );
  606. sock->unregister();
  607. if (HHVM_FN(socket_connect)(Resource(sock), String(host), port)) {
  608. if (clearmachines) {
  609. for (unsigned int i = 0; i < m_machines.size(); i++) {
  610. if (m_machines[i] == m_machine) {
  611. m_machines.erase(m_machines.begin() + i);
  612. break;
  613. }
  614. }
  615. }
  616. auto machine = std::make_shared<DMachineInfo>();
  617. machine->m_name = host;
  618. machine->m_port = port;
  619. machine->m_thrift.create(sock);
  620. m_machines.push_back(machine);
  621. switchMachine(machine);
  622. return true;
  623. }
  624. }
  625. return false;
  626. }
  627. std::string DebuggerClient::getPrompt() {
  628. TRACE(2, "DebuggerClient::getPrompt\n");
  629. if (NoPrompt || !RuntimeOption::EnableDebuggerPrompt) {
  630. return "";
  631. }
  632. auto name = &m_machine->m_name;
  633. if (!m_rpcHost.empty()) {
  634. name = &m_rpcHost;
  635. }
  636. if (m_inputState == TakingCode) {
  637. std::string prompt = " ";
  638. for (unsigned i = 2; i < name->size() + 2; i++) {
  639. prompt += '.';
  640. }
  641. prompt += ' ';
  642. return prompt;
  643. }
  644. return *name + "> ";
  645. }
  646. void DebuggerClient::init(const DebuggerClientOptions &options) {
  647. TRACE(2, "DebuggerClient::init\n");
  648. m_options = options;
  649. if (!options.configFName.empty()) {
  650. m_configFileName = options.configFName;
  651. }
  652. if (options.user.empty()) {
  653. m_options.user = Process::GetCurrentUser();
  654. }
  655. usageLogEvent("init");
  656. loadConfig();
  657. if (m_scriptMode) {
  658. print("running in script mode, pid=%" PRId64 "\n",
  659. (int64_t)getpid());
  660. }
  661. if (!options.cmds.empty()) {
  662. RuntimeOption::EnableDebuggerColor = false;
  663. RuntimeOption::EnableDebuggerPrompt = false;
  664. s_use_utf8 = false;
  665. }
  666. if (UseColor && RuntimeOption::EnableDebuggerColor) Debugger::SetTextColors();
  667. if (!NoPrompt && RuntimeOption::EnableDebuggerPrompt) {
  668. info("Welcome to HipHop Debugger!");
  669. info("Type \"help\" or \"?\" for a complete list of commands.\n");
  670. }
  671. if (!options.host.empty()) {
  672. connectRemote(options.host, options.port);
  673. } else {
  674. if (options.fileName.empty()) {
  675. help("Note: no server specified, debugging local scripts only.");
  676. help("If you want to connect to a server, launch with \"-h\" or use:");
  677. help(" [m]achine [c]onnect <servername>\n");
  678. }
  679. }
  680. }
  681. void DebuggerClient::start(const DebuggerClientOptions &options) {
  682. TRACE(2, "DebuggerClient::start\n");
  683. init(options);
  684. m_mainThread.start();
  685. }
  686. // Executed by m_mainThread to run the command-line debugger.
  687. void DebuggerClient::run() {
  688. TRACE(2, "DebuggerClient::run\n");
  689. StackTraceNoHeap::AddExtraLogging("IsDebugger", "True");
  690. ReadlineApp app;
  691. TRACE(3, "DebuggerClient::run, about to call playMacro\n");
  692. playMacro("startup");
  693. if (!m_options.cmds.empty()) {
  694. m_macroPlaying = std::make_shared<Macro>();
  695. m_macroPlaying->m_cmds = m_options.cmds;
  696. m_macroPlaying->m_cmds.push_back("q");
  697. m_macroPlaying->m_index = 0;
  698. }
  699. hphp_session_init(Treadmill::SessionKind::DebuggerClient);
  700. if (m_options.extension.empty()) {
  701. hphp_invoke_simple("", true); // warm-up only
  702. } else {
  703. hphp_invoke_simple(m_options.extension, false);
  704. }
  705. while (true) {
  706. bool reconnect = false;
  707. try {
  708. eventLoop(TopLevel, DebuggerCommand::KindOfNone, "Main client loop");
  709. } catch (DebuggerClientExitException& e) { /* normal exit */
  710. } catch (DebuggerServerLostException& e) {
  711. // Loss of connection
  712. TRACE_RB(1, "DebuggerClient::run: server lost exception\n");
  713. usageLogEvent("DebuggerServerLostException", m_commandCanonical);
  714. reconnect = true;
  715. } catch (DebuggerProtocolException& e) {
  716. // Bad or unexpected data. Give reconnect a shot, it could help...
  717. TRACE_RB(1, "DebuggerClient::run: protocol exception\n");
  718. usageLogEvent("DebuggerProtocolException", m_commandCanonical);
  719. reconnect = true;
  720. } catch (...) {
  721. TRACE_RB(1, "DebuggerClient::run: unknown exception\n");
  722. usageLogEvent("UnknownException", m_commandCanonical);
  723. Logger::Error("Unhandled exception, exiting.");
  724. }
  725. // Note: it's silly to try to reconnect when stopping, or if we have a
  726. // problem while quitting.
  727. if (reconnect && !m_stopped && (m_commandCanonical != "quit")) {
  728. usageLogEvent("reconnect attempt", m_commandCanonical);
  729. if (DebuggerClient::reconnect()) {
  730. usageLogEvent("reconnect success", m_commandCanonical);
  731. continue;
  732. }
  733. usageLogEvent("reconnect failed", m_commandCanonical);
  734. Logger::Error("Unable to reconnect to server, exiting.");
  735. }
  736. break;
  737. }
  738. usageLogEvent("exit");
  739. // Closing all proxy connections will force the local proxy to pop out of
  740. // it's wait, and eventually exit the main thread.
  741. closeAllConnections();
  742. hphp_context_exit();
  743. hphp_session_exit();
  744. }
  745. ///////////////////////////////////////////////////////////////////////////////
  746. // auto-complete
  747. void DebuggerClient::updateLiveLists() {
  748. TRACE(2, "DebuggerClient::updateLiveLists\n");
  749. ReadlineWaitCursor waitCursor;
  750. CmdInfo::UpdateLiveLists(*this);
  751. m_acLiveListsDirty = false;
  752. }
  753. void DebuggerClient::promptFunctionPrototype() {
  754. TRACE(2, "DebuggerClient::promptFunctionPrototype\n");
  755. if (m_acProtoTypePrompted) return;
  756. m_acProtoTypePrompted = true;
  757. const char *p0 = rl_line_buffer;
  758. int len = strlen(p0);
  759. if (len < 2) return;
  760. const char *pLast = p0 + len - 1;
  761. while (pLast > p0 && isspace(*pLast)) --pLast;
  762. if (pLast == p0 || *pLast-- != '(') return;
  763. while (pLast > p0 && isspace(*pLast)) --pLast;
  764. const char *p = pLast;
  765. while (p >= p0 && (isalnum(*p) || *p == '_')) --p;
  766. if (p == pLast) return;
  767. std::string cls;
  768. std::string func(p + 1, pLast - p);
  769. if (p > p0 && *p-- == ':' && *p-- == ':') {
  770. pLast = p;
  771. while (p >= p0 && (isalnum(*p) || *p == '_')) --p;
  772. if (pLast > p) {
  773. cls = std::string(p + 1, pLast - p);
  774. }
  775. }
  776. String output = highlight_code(CmdInfo::GetProtoType(*this, cls, func));
  777. print("\n%s", output.data());
  778. rl_forced_update_display();
  779. }
  780. bool DebuggerClient::setCompletion(const char* text, int /*start*/,
  781. int /*end*/) {
  782. TRACE(2, "DebuggerClient::setCompletion\n");
  783. if (m_inputState == TakingCommand) {
  784. parseCommand(rl_line_buffer);
  785. if (*text) {
  786. if (!m_args.empty()) {
  787. m_args.resize(m_args.size() - 1);
  788. } else {
  789. m_command.clear();
  790. }
  791. }
  792. }
  793. return true;
  794. }
  795. void DebuggerClient::addCompletion(AutoComplete type) {
  796. TRACE(2, "DebuggerClient::addCompletion(AutoComplete type)\n");
  797. if (type < 0 || type >= AutoCompleteCount) {
  798. Logger::Error("Invalid auto completion enum: %d", type);
  799. return;
  800. }
  801. if (type == AutoCompleteCode) {
  802. addCompletion(AutoCompleteVariables);
  803. addCompletion(AutoCompleteConstants);
  804. addCompletion(AutoCompleteClasses);
  805. addCompletion(AutoCompleteFunctions);
  806. addCompletion(AutoCompleteClassMethods);
  807. addCompletion(AutoCompleteClassProperties);
  808. addCompletion(AutoCompleteClassConstants);
  809. addCompletion(AutoCompleteKeyword);
  810. } else if (type == AutoCompleteKeyword) {
  811. addCompletion(PHP_KEYWORDS);
  812. } else {
  813. m_acLists.push_back((const char **)type);
  814. }
  815. if (type == AutoCompleteFunctions || type == AutoCompleteClassMethods) {
  816. promptFunctionPrototype();
  817. }
  818. }
  819. void DebuggerClient::addCompletion(const char** list) {
  820. TRACE(2, "DebuggerClient::addCompletion(const char **list)\n");
  821. m_acLists.push_back(list);
  822. }
  823. void DebuggerClient::addCompletion(const char* name) {
  824. TRACE(2, "DebuggerClient::addCompletion(const char *name)\n");
  825. m_acStrings.push_back(name);
  826. }
  827. void DebuggerClient::addCompletion(const std::vector<std::string>& items) {
  828. TRACE(2, "DebuggerClient::addCompletion(const std::vector<std::string>)\n");
  829. m_acItems.insert(m_acItems.end(), items.begin(), items.end());
  830. }
  831. char* DebuggerClient::getCompletion(const std::vector<std::string>& items,
  832. const char* text) {
  833. TRACE(2, "DebuggerClient::getCompletion(const std::vector<std::string>\n");
  834. while (++m_acPos < (int)items.size()) {
  835. auto const p = items[m_acPos].c_str();
  836. if (m_acLen == 0 || strncasecmp(p, text, m_acLen) == 0) {
  837. return strdup(p);
  838. }
  839. }
  840. m_acPos = -1;
  841. return nullptr;
  842. }
  843. std::vector<std::string> DebuggerClient::getAllCompletions(
  844. const std::string& text
  845. ) {
  846. TRACE(2, "DebuggerClient::getAllCompletions\n");
  847. std::vector<std::string> res;
  848. if (m_acLiveListsDirty) {
  849. updateLiveLists();
  850. }
  851. for (int i = 0; i < AutoCompleteCount; ++i) {
  852. auto const& items = m_acLiveLists->get(i);
  853. for (size_t j = 0; j < items.size(); ++j) {
  854. auto const p = items[j].c_str();
  855. if (strncasecmp(p, text.c_str(), text.length()) == 0) {
  856. res.push_back(std::string(p));
  857. }
  858. }
  859. }
  860. return res;
  861. }
  862. char* DebuggerClient::getCompletion(const std::vector<const char*>& items,
  863. const char* text) {
  864. TRACE(2, "DebuggerClient::getCompletion(const std::vector<const char *>\n");
  865. while (++m_acPos < (int)items.size()) {
  866. auto const p = items[m_acPos];
  867. if (m_acLen == 0 || strncasecmp(p, text, m_acLen) == 0) {
  868. return strdup(p);
  869. }
  870. }
  871. m_acPos = -1;
  872. return nullptr;
  873. }
  874. static char first_non_whitespace(const char* s) {
  875. TRACE(2, "DebuggerClient::first_non_whitespace\n");
  876. while (*s && isspace(*s)) s++;
  877. return *s;
  878. }
  879. char* DebuggerClient::getCompletion(const char* text, int state) {
  880. TRACE(2, "DebuggerClient::getCompletion\n");
  881. if (state == 0) {
  882. m_acLen = strlen(text);
  883. m_acIndex = 0;
  884. m_acPos = -1;
  885. m_acLists.clear();
  886. m_acStrings.clear();
  887. m_acItems.clear();
  888. m_acProtoTypePrompted = false;
  889. if (m_inputState == TakingCommand) {
  890. switch (first_non_whitespace(rl_line_buffer)) {
  891. case '<':
  892. if (strncasecmp(m_command.substr(0, 5).c_str(), "<?php", 5)) {
  893. addCompletion("<?php");
  894. break;
  895. }
  896. case '@':
  897. case '=':
  898. case '$': {
  899. addCompletion(AutoCompleteCode);
  900. break;
  901. }
  902. default: {
  903. if (m_command.empty()) {
  904. addCompletion(GetCommands());
  905. addCompletion("@");
  906. addCompletion("=");
  907. addCompletion("<?php");
  908. addCompletion("?>");
  909. } else {
  910. auto cmd = createCommand();
  911. if (cmd) {
  912. if (cmd->is(DebuggerCommand::KindOfRun)) playMacro("startup");
  913. cmd->list(*this);
  914. }
  915. }
  916. break;
  917. }
  918. }
  919. } else {
  920. assertx(m_inputState == TakingCode);
  921. if (!*rl_line_buffer) {
  922. addCompletion("?>"); // so we tab, we're done
  923. } else {
  924. addCompletion(AutoCompleteCode);
  925. }
  926. }
  927. }
  928. for (; m_acIndex < (int)m_acLists.size(); m_acIndex++) {
  929. const char **list = m_acLists[m_acIndex];
  930. if ((int64_t)list == AutoCompleteFileNames) {
  931. char *p = rl_filename_completion_function(text, ++m_acPos);
  932. if (p) return p;
  933. } else if ((int64_t)list >= 0 && (int64_t)list < AutoCompleteCount) {
  934. if (m_acLiveListsDirty) {
  935. updateLiveLists();
  936. assertx(!m_acLiveListsDirty);
  937. }
  938. char *p = getCompletion(m_acLiveLists->get(int64_t(list)), text);
  939. if (p) return p;
  940. } else {
  941. for (const char *p = list[++m_acPos]; p; p = list[++m_acPos]) {
  942. if (m_acLen == 0 || strncasecmp(p, text, m_acLen) == 0) {
  943. return strdup(p);
  944. }
  945. }
  946. }
  947. m_acPos = -1;
  948. }
  949. char *p = getCompletion(m_acStrings, text);
  950. if (p) return p;
  951. return getCompletion(m_acItems, text);
  952. }
  953. ///////////////////////////////////////////////////////////////////////////////
  954. // main
  955. // Execute the initial connection protocol with a machine. A connection has been
  956. // established, and the proxy has responded with an interrupt giving us initial
  957. // control. Send breakpoints to the server, and then attach to the sandbox
  958. // if necessary. If we attach to a sandbox, then the process is off and running
  959. // again (CmdMachine continues execution on a successful attach) so return false
  960. // to indicate that a client should wait for another interrupt before attempting
  961. // further communication. Returns true if the protocol is complete and the
  962. // machine is at an interrupt.
  963. bool DebuggerClient::initializeMachine() {
  964. TRACE(2, "DebuggerClient::initializeMachine\n");
  965. // set/clear intercept for RPC thread
  966. if (!m_machines.empty() && m_machine == m_machines[0]) {
  967. CmdMachine::UpdateIntercept(*this, m_machine->m_rpcHost,
  968. m_machine->m_rpcPort);
  969. }
  970. // upload breakpoints
  971. if (!m_breakpoints.empty()) {
  972. info("Updating breakpoints...");
  973. CmdBreak::SendClientBreakpointListToServer(*this);
  974. }
  975. // attaching to default sandbox
  976. int waitForSandbox = false;
  977. if (!m_machine->m_sandboxAttached) {
  978. const char *user = m_options.user.empty() ?
  979. nullptr : m_options.user.c_str();
  980. m_machine->m_sandboxAttached = (waitForSandbox =
  981. CmdMachine::AttachSandbox(*this, user, m_options.sandbox.c_str()));
  982. if (!m_machine->m_sandboxAttached) {
  983. Logger::Error("Unable to communicate with default sandbox.");
  984. }
  985. }
  986. m_machine->m_initialized = true;
  987. if (waitForSandbox) {
  988. // Return false to wait for next interrupt from server
  989. return false;
  990. }
  991. return true;
  992. }
  993. // The main execution loop of DebuggerClient. This waits for interrupts from
  994. // the server (and responds to polls for signals). On interrupt, it presents a
  995. // command prompt, and continues pumping interrupts when a command lets the
  996. // machine run again. For nested loops it returns the command that completed
  997. // the loop, which will match the expectedCmd passed in. For all loop types,
  998. // throws one of a variety of exceptions for various errors, and throws
  999. // DebuggerClientExitException when the event loop is terminated due to the
  1000. // client stopping.
  1001. DebuggerCommandPtr DebuggerClient::eventLoop(EventLoopKind loopKind,
  1002. int expectedCmd,
  1003. const char *caller) {
  1004. TRACE(2, "DebuggerClient::eventLoop\n");
  1005. ARRPROV_USE_RUNTIME_LOCATION();
  1006. if (loopKind == NestedWithExecution) {
  1007. // Some callers have caused the server to start executing more PHP, so
  1008. // update the machine/client state accordingly.
  1009. m_inputState = TakingInterrupt;
  1010. m_machine->m_interrupting = false;
  1011. }
  1012. while (!m_stopped) {
  1013. DebuggerCommandPtr cmd;
  1014. if (DebuggerCommand::Receive(m_machine->m_thrift, cmd, caller)) {
  1015. if (!cmd) {
  1016. Logger::Error("Unable to communicate with server. Server's down?");
  1017. throw DebuggerServerLostException();
  1018. }
  1019. if (cmd->is(DebuggerCommand::KindOfSignal) ||
  1020. cmd->is(DebuggerCommand::KindOfAuth)) {
  1021. // Respond to polling from the server.
  1022. cmd->onClient(*this);
  1023. continue;
  1024. }
  1025. if (!cmd->getWireError().empty()) {
  1026. error("wire error: %s", cmd->getWireError().data());
  1027. }
  1028. if ((loopKind != TopLevel) &&
  1029. cmd->is((DebuggerCommand::Type)expectedCmd)) {
  1030. // For the nested cases, the caller has sent a cmd to the server and is
  1031. // expecting a specific response. When we get it, return it.
  1032. usageLogEvent("command done", folly::to<std::string>(expectedCmd));
  1033. m_machine->m_interrupting = true; // Machine is stopped
  1034. m_inputState = TakingCommand;
  1035. return cmd;
  1036. }
  1037. if ((loopKind == Nested) || !cmd->is(DebuggerCommand::KindOfInterrupt)) {
  1038. Logger::Error("Received bad cmd type %d, unable to communicate "
  1039. "with server.", cmd->getType());
  1040. throw DebuggerProtocolException();
  1041. }
  1042. m_sigCount = 0;
  1043. auto intr = std::dynamic_pointer_cast<CmdInterrupt>(cmd);
  1044. Debugger::UsageLogInterrupt("terminal", getSandboxId(), *intr.get());
  1045. cmd->onClient(*this);
  1046. // When we make a new connection to a machine, we have to wait for it
  1047. // to interrupt us before we can send it any messages. This is our
  1048. // opportunity to complete the connection and make it ready to use.
  1049. if (!m_machine->m_initialized) {
  1050. if (!initializeMachine()) {
  1051. // False means the machine is running and we need to wait for
  1052. // another interrupt.
  1053. continue;
  1054. }
  1055. }
  1056. // Execution has been interrupted, so go ahead and give the user
  1057. // the prompt back.
  1058. m_machine->m_interrupting = true; // Machine is stopped
  1059. m_inputState = TakingCommand;
  1060. console(); // Prompt loop
  1061. m_inputState = TakingInterrupt;
  1062. m_machine->m_interrupting = false; // Machine is running again.
  1063. if (m_scriptMode) {
  1064. print("Waiting for server response");
  1065. }
  1066. }
  1067. }
  1068. throw DebuggerClientExitException(); // Stopped, so exit.
  1069. }
  1070. // Execute the interactive command loop for the debugger client. This will
  1071. // present the prompt, wait for user input, and execute commands, then rinse
  1072. // and repeat. The loop terminates when a command is executed that causes the
  1073. // machine to resume execution, or which should cause the client to exit.
  1074. // This function is only entered when the machine being debugged is paused.
  1075. //
  1076. // If this function returns it means the process is running again.
  1077. // NB: exceptions derived from DebuggerException or DebuggerClientExeption
  1078. // indicate the machine remains paused.
  1079. void DebuggerClient::console() {
  1080. TRACE(2, "DebuggerClient::console\n");
  1081. while (true) {
  1082. const char *line = nullptr;
  1083. std::string holder;
  1084. if (m_macroPlaying) {
  1085. if (m_macroPlaying->m_index < m_macroPlaying->m_cmds.size()) {
  1086. holder = m_macroPlaying->m_cmds[m_macroPlaying->m_index++];
  1087. line = holder.c_str();
  1088. } else {
  1089. m_macroPlaying.reset();
  1090. }
  1091. }
  1092. if (line == nullptr) {
  1093. line = readline(getPrompt().c_str());
  1094. if (line == nullptr) {
  1095. // treat ^D as quit
  1096. print("quit");
  1097. line = "quit";
  1098. } else {
  1099. #ifdef USE_EDITLINE
  1100. print("%s", line); // Stay consistent with the readline library
  1101. #endif
  1102. }
  1103. } else if (!NoPrompt && RuntimeOption::EnableDebuggerPrompt) {
  1104. print("%s%s", getPrompt().c_str(), line);
  1105. }
  1106. if (*line && !m_macroPlaying &&
  1107. strcasecmp(line, "QUIT") != 0 &&
  1108. strcasecmp(line, "QUI") != 0 &&
  1109. strcasecmp(line, "QU") != 0 &&
  1110. strcasecmp(line, "Q") != 0) {
  1111. // even if line is bad command, we still want to remember it, so
  1112. // people can go back and fix typos
  1113. HIST_ENTRY *last_entry = nullptr;
  1114. if (history_length > 0 &&
  1115. (last_entry = history_get(history_length + history_base - 1))) {
  1116. // Make sure we aren't duplicating history entries
  1117. if (strcmp(line, last_entry->line)) {
  1118. add_history(line);
  1119. }
  1120. } else {
  1121. // Add history regardless, since we know that there are no
  1122. // duplicate entries.
  1123. add_history(line);
  1124. }
  1125. }
  1126. AdjustScreenMetrics();
  1127. if (*line) {
  1128. if (parse(line)) {
  1129. try {
  1130. record(line);
  1131. m_prevCmd = m_command;
  1132. if (!process()) {
  1133. error("command \"" + m_command + "\" not found");
  1134. m_command.clear();
  1135. }
  1136. } catch (DebuggerConsoleExitException& e) {
  1137. return;
  1138. }
  1139. }
  1140. } else if (m_inputState == TakingCommand) {
  1141. switch (m_prevCmd[0]) {
  1142. case 'l': // list
  1143. m_args.clear(); // as if just "list"
  1144. // fall through
  1145. case 'c': // continue
  1146. case 's': // step
  1147. case 'n': // next
  1148. case 'o': // out
  1149. try {
  1150. record(line);
  1151. m_command = m_prevCmd;
  1152. process(); // replay the same command
  1153. } catch (DebuggerConsoleExitException& e) {
  1154. return;
  1155. }
  1156. break;
  1157. }
  1158. }
  1159. }
  1160. not_reached();
  1161. }
  1162. const StaticString
  1163. s_file("file"),
  1164. s_line("line");
  1165. //
  1166. // Called when a breakpoint is reached, to produce the console
  1167. // spew showing the code around the breakpoint.
  1168. //
  1169. void DebuggerClient::shortCode(BreakPointInfoPtr bp) {
  1170. TRACE(2, "DebuggerClient::shortCode\n");
  1171. if (bp && !bp->m_file.empty() && bp->m_line1) {
  1172. Variant source = CmdList::GetSourceFile(*this, bp->m_file);
  1173. if (source.isString()) {
  1174. // Line and column where highlight should start and end
  1175. int beginHighlightLine = bp->m_line1;
  1176. int beginHighlightColumn = bp->m_char1;
  1177. int endHighlightLine = bp->m_line2;
  1178. int endHighlightColumn = bp->m_char2;
  1179. // Lines where source listing should start and end
  1180. int firstLine = std::max(beginHighlightLine - 1, 1);
  1181. int lastLine = endHighlightLine + 1;
  1182. int maxLines = getDebuggerClientMaxCodeLines();
  1183. // If MaxCodeLines == 0: don't spew any code after a [s]tep or [n]ext
  1184. // command.
  1185. if (maxLines == 0) {
  1186. return;
  1187. }
  1188. // If MaxCodeLines > 0: limit spew to a maximum of # lines.
  1189. if (maxLines > 0) {
  1190. int numHighlightLines = endHighlightLine - beginHighlightLine + 1;
  1191. if (numHighlightLines > maxLines) {
  1192. // If there are too many highlight lines, truncate spew
  1193. // by setting lastLine ...
  1194. lastLine = beginHighlightLine + maxLines - 1;
  1195. // ... and set endHighlightLine/Column so that it is just past the end
  1196. // of the spew, and all code up to the truncation will be highlighted.
  1197. endHighlightLine = lastLine + 1;
  1198. endHighlightColumn = 1;
  1199. }
  1200. }
  1201. code(source.toString(), firstLine, lastLine,
  1202. beginHighlightLine,
  1203. beginHighlightColumn,
  1204. endHighlightLine,
  1205. endHighlightColumn);
  1206. }
  1207. }
  1208. }
  1209. bool DebuggerClient::code(const String& source, int line1 /*= 0*/,
  1210. int line2 /*= 0*/,
  1211. int lineFocus0 /* = 0 */, int charFocus0 /* = 0 */,
  1212. int lineFocus1 /* = 0 */, int charFocus1 /* = 0 */) {
  1213. TRACE(2, "DebuggerClient::code\n");
  1214. if (line1 == 0 && line2 == 0) {
  1215. String highlighted = highlight_code(source, 0, lineFocus0, charFocus0,
  1216. lineFocus1, charFocus1);
  1217. if (!highlighted.empty()) {
  1218. print(highlighted);
  1219. return true;
  1220. }
  1221. return false;
  1222. }
  1223. String highlighted = highlight_php(source, 1, lineFocus0, charFocus0,
  1224. lineFocus1, charFocus1);
  1225. int line = 1;
  1226. const char *begin = highlighted.data();
  1227. StringBuffer sb;
  1228. for (const char *p = begin; *p; p++) {
  1229. if (*p == '\n') {
  1230. if (line >= line1) {
  1231. sb.append(begin, p - begin + 1);
  1232. }
  1233. if (++line > line2) break;
  1234. begin = p + 1;
  1235. }
  1236. }
  1237. if (!sb.empty()) {
  1238. print("%s%s", sb.data(),
  1239. UseColor && RuntimeOption::EnableDebuggerColor ? ANSI_COLOR_END : "\0");
  1240. return true;
  1241. }
  1242. return false;
  1243. }
  1244. char DebuggerClient::ask(const char *fmt, ...) {
  1245. TRACE(2, "DebuggerClient::ask\n");
  1246. std::string msg;
  1247. va_list ap;
  1248. va_start(ap, fmt);
  1249. string_vsnprintf(msg, fmt, ap); va_end(ap);
  1250. if (UseColor && InfoColor && RuntimeOption::EnableDebuggerColor) {
  1251. msg = InfoColor + msg + ANSI_COLOR_END;
  1252. }
  1253. fwrite(msg.data(), 1, msg.length(), stdout);
  1254. fflush(stdout);
  1255. auto input = readline("");
  1256. if (input == nullptr) return ' ';
  1257. #ifdef USE_EDITLINE
  1258. print("%s", input); // Stay consistent with the readline library
  1259. #endif
  1260. if (strlen(input) > 0) return tolower(input[0]);
  1261. return ' ';
  1262. }
  1263. #define DWRITE(ptr, size, nmemb, stream) \
  1264. do { \
  1265. /* LogFile debugger setting */ \
  1266. FILE *f = getLogFileHandler(); \
  1267. if (f != nullptr) { \
  1268. fwrite(ptr, size, nmemb, f); \
  1269. } \
  1270. \
  1271. /* For debugging, still output to stdout */ \
  1272. fwrite(ptr, size, nmemb, stream); \
  1273. } while (0) \
  1274. void DebuggerClient::print(const char* fmt, ...) {
  1275. TRACE(2, "DebuggerClient::print(const char* fmt, ...)\n");
  1276. std::string msg;
  1277. va_list ap;
  1278. va_start(ap, fmt);
  1279. string_vsnprintf(msg, fmt, ap); va_end(ap);
  1280. print(msg);
  1281. }
  1282. void DebuggerClient::print(const String& msg) {
  1283. TRACE(2, "DebuggerClient::print(const String& msg)\n");
  1284. DWRITE(msg.data(), 1, msg.length(), stdout);
  1285. DWRITE("\n", 1, 1, stdout);
  1286. fflush(stdout);
  1287. }
  1288. void DebuggerClient::print(const std::string& msg) {
  1289. TRACE(2, "DebuggerClient::print(const std::string& msg)\n");
  1290. DWRITE(msg.data(), 1, msg.size(), stdout);
  1291. DWRITE("\n", 1, 1, stdout);
  1292. fflush(stdout);
  1293. }
  1294. void DebuggerClient::print(folly::StringPiece msg) {
  1295. TRACE(2, "DebuggerClient::print(folly::StringPiece msg)\n");
  1296. DWRITE(msg.data(), 1, msg.size(), stdout);
  1297. DWRITE("\n", 1, 1, stdout);
  1298. fflush(stdout);
  1299. }
  1300. #define IMPLEMENT_COLOR_OUTPUT(name, where, color) \
  1301. void DebuggerClient::name(folly::StringPiece msg) { \
  1302. if (UseColor && color && RuntimeOption::EnableDebuggerColor) { \
  1303. DWRITE(color, 1, strlen(color), where); \
  1304. } \
  1305. DWRITE(msg.data(), 1, msg.size(), where); \
  1306. if (UseColor && color && RuntimeOption::EnableDebuggerColor) { \
  1307. DWRITE(ANSI_COLOR_END, 1, strlen(ANSI_COLOR_END), where); \
  1308. } \
  1309. DWRITE("\n", 1, 1, where); \
  1310. fflush(where); \
  1311. } \
  1312. \
  1313. void DebuggerClient::name(const String& msg) { \
  1314. name(msg.slice()); \
  1315. } \
  1316. \
  1317. void DebuggerClient::name(const std::string& msg) { \
  1318. name(folly::StringPiece{msg}); \
  1319. } \
  1320. \
  1321. void DebuggerClient::name(const char *fmt, ...) { \
  1322. std::string msg; \
  1323. va_list ap; \
  1324. va_start(ap, fmt); \
  1325. string_vsnprintf(msg, fmt, ap); va_end(ap); \
  1326. name(msg); \
  1327. } \
  1328. IMPLEMENT_COLOR_OUTPUT(help, stdout, HelpColor);
  1329. IMPLEMENT_COLOR_OUTPUT(info, stdout, InfoColor);
  1330. IMPLEMENT_COLOR_OUTPUT(output, stdout, OutputColor);
  1331. IMPLEMENT_COLOR_OUTPUT(error, stderr, ErrorColor);
  1332. #undef DWRITE
  1333. #undef IMPLEMENT_COLOR_OUTPUT
  1334. std::string DebuggerClient::wrap(const std::string &s) {
  1335. TRACE(2, "DebuggerClient::wrap\n");
  1336. String ret = wordwrap(String(s.c_str(), s.size(), CopyString), LineWidth - 4,
  1337. "\n", true);
  1338. return std::string(ret.data(), ret.size());
  1339. }
  1340. void DebuggerClient::helpTitle(const char *title) {
  1341. TRACE(2, "DebuggerClient::helpTitle\n");
  1342. help(FormatTitle(title));
  1343. }
  1344. void DebuggerClient::helpCmds(const char *cmd, const char *desc, ...) {
  1345. TRACE(2, "DebuggerClient::helpCmds(const char *cmd, const char *desc,...)\n");
  1346. std::vector<const char *> cmds;
  1347. cmds.push_back(cmd);
  1348. cmds.push_back(desc);
  1349. va_list ap;
  1350. va_start(ap, desc);
  1351. const char *s = va_arg(ap, const char *);
  1352. while (s) {
  1353. cmds.push_back(s);
  1354. s = va_arg(ap, const char *);
  1355. }
  1356. va_end(ap);
  1357. helpCmds(cmds);
  1358. }
  1359. void DebuggerClient::helpCmds(const std::vector<const char *> &cmds) {
  1360. TRACE(2, "DebuggerClient::helpCmds(const std::vector<const char *> &cmds)\n");
  1361. int left = 0; int right = 0;
  1362. for (unsigned int i = 0; i < cmds.size(); i++) {
  1363. int &width = (i % 2 ? right : left);
  1364. int len = strlen(cmds[i]);
  1365. if (width < len) width = len;
  1366. }
  1367. int margin = 8;
  1368. int leftMax = LineWidth / 3 - margin;
  1369. int rightMax = LineWidth * 3 / 3 - margin;
  1370. if (left > leftMax) left = leftMax;
  1371. if (right > rightMax) right = rightMax;
  1372. StringBuffer sb;
  1373. for (unsigned int i = 0; i < cmds.size() - 1; i += 2) {
  1374. String cmd(cmds[i], CopyString);
  1375. String desc(cmds[i+1], CopyString);
  1376. // two special formats
  1377. if (cmd.empty() && desc.empty()) {
  1378. sb.append("\n");
  1379. continue;
  1380. }
  1381. if (desc.empty()) {
  1382. sb.append(FormatTitle(cmd.data()));
  1383. sb.append("\n");
  1384. continue;
  1385. }
  1386. cmd = wordwrap(cmd, left, "\n", true);
  1387. desc = wordwrap(desc, right, "\n", true);
  1388. Array lines1 = StringUtil::Explode(cmd, "\n").toArray();
  1389. Array lines2 = StringUtil::Explode(desc, "\n").toArray();
  1390. for (int n = 0; n < lines1.size() || n < lines2.size(); n++) {
  1391. StringBuffer line;
  1392. line.append(" ");
  1393. if (n) line.append(" ");
  1394. line.append(StringUtil::Pad(lines1[n].toString(), leftMax));
  1395. if (n == 0) line.append(" ");
  1396. line.append(" ");
  1397. line.append(lines2[n].toString());
  1398. sb.append(HHVM_FN(rtrim)(line.detach()));
  1399. sb.append("\n");
  1400. }
  1401. }
  1402. help(sb.detach());
  1403. }
  1404. void DebuggerClient::helpBody(const std::string &s) {
  1405. TRACE(2, "DebuggerClient::helpBody\n");
  1406. help("%s", "");
  1407. help(wrap(s));
  1408. help("%s", "");
  1409. }
  1410. void DebuggerClient::helpSection(const std::string &s) {
  1411. TRACE(2, "DebuggerClient::helpSection\n");
  1412. help(wrap(s));
  1413. }
  1414. void DebuggerClient::tutorial(const char *text) {
  1415. TRACE(2, "DebuggerClient::tutorial\n");
  1416. if (m_tutorial < 0) return;
  1417. String ret = string_replace(String(text), "\t", " ");
  1418. ret = wordwrap(ret, LineWidth - 4, "\n", true);
  1419. Array lines = StringUtil::Explode(ret, "\n").toArray();
  1420. StringBuffer sb;
  1421. String header = " Tutorial - '[h]elp [t]utorial off|auto' to turn off ";
  1422. String hr = HHVM_FN(str_repeat)(BOX_H, LineWidth - 2);
  1423. sb.append(BOX_UL); sb.append(hr); sb.append(BOX_UR); sb.append("\n");
  1424. int wh = (LineWidth - 2 - header.size()) / 2;
  1425. sb.append(BOX_V);
  1426. sb.append(HHVM_FN(str_repeat)(" ", wh));
  1427. sb.append(header);
  1428. sb.append(HHVM_FN(str_repeat)(" ", wh));
  1429. sb.append(BOX_V);
  1430. sb.append("\n");
  1431. sb.append(BOX_VL); sb.append(hr); sb.append(BOX_VR); sb.append("\n");
  1432. for (ArrayIter iter(lines); iter; ++iter) {
  1433. sb.append(BOX_V); sb.append(' ');
  1434. sb.append(StringUtil::Pad(iter.second().toString(), LineWidth - 4));
  1435. sb.append(' '); sb.append(BOX_V); sb.append("\n");
  1436. }
  1437. sb.append(BOX_LL); sb.append(hr); sb.append(BOX_LR); sb.append("\n");
  1438. String content = sb.detach();
  1439. if (m_tutorial == 0) {
  1440. String hash = StringUtil::MD5(content);
  1441. if (m_tutorialVisited.find(hash.data()) != m_tutorialVisited.end()) {
  1442. return;
  1443. }
  1444. m_tutorialVisited.insert(hash.data());
  1445. saveConfig();
  1446. }
  1447. help(content);
  1448. }
  1449. void DebuggerClient::setTutorial(int mode) {
  1450. TRACE(2, "DebuggerClient::setTutorial\n");
  1451. if (m_tutorial != mode) {
  1452. m_tutorial = mode;
  1453. m_tutorialVisited.clear();
  1454. saveConfig();
  1455. }
  1456. }
  1457. ///////////////////////////////////////////////////////////////////////////////
  1458. // command processing
  1459. const char **DebuggerClient::GetCommands() {
  1460. static const char *cmds[] = {
  1461. "abort", "break", "continue", "down", "exception",
  1462. "frame", "global", "help", "info",
  1463. "konstant", "list", "machine", "next", "out",
  1464. "print", "quit", "run", "step", "thread",
  1465. "up", "variable", "where", "x", "y",
  1466. "zend", "!", "&",
  1467. nullptr
  1468. };
  1469. return cmds;
  1470. }
  1471. void DebuggerClient::shiftCommand() {
  1472. TRACE(2, "DebuggerClient::shiftCommand\n");
  1473. if (m_command.size() > 1) {
  1474. m_args.insert(m_args.begin(), m_command.substr(1));
  1475. m_argIdx.insert(m_argIdx.begin(), 1);
  1476. m_command = m_command.substr(0, 1);
  1477. }
  1478. }
  1479. template<class T>
  1480. DebuggerCommandPtr DebuggerClient::new_cmd(const char* name) {
  1481. m_commandCanonical = name;
  1482. return std::make_shared<T>();
  1483. }
  1484. template<class T>
  1485. DebuggerCommandPtr DebuggerClient::match_cmd(const char* name) {
  1486. return match(name) ? new_cmd<T>(name) : nullptr;
  1487. }
  1488. DebuggerCommandPtr DebuggerClient::createCommand() {
  1489. TRACE(2, "DebuggerClient::createCommand\n");
  1490. // give gdb users some love
  1491. if (m_command == "bt") return new_cmd<CmdWhere>("where");
  1492. if (m_command == "set") return new_cmd<CmdConfig>("config");
  1493. if (m_command == "complete") return new_cmd<CmdComplete>("complete");
  1494. // Internal testing
  1495. if (m_command == "internaltesting") {
  1496. return new_cmd<CmdInternalTesting>("internaltesting");
  1497. }
  1498. switch (tolower(m_command[0])) {
  1499. case 'a': return match_cmd<CmdAbort>("abort");
  1500. case 'b': return match_cmd<CmdBreak>("break");
  1501. case 'c': return match_cmd<CmdContinue>("continue");
  1502. case 'd': return match_cmd<CmdDown>("down");
  1503. case 'e': return match_cmd<CmdException>("exception");
  1504. case 'f': return match_cmd<CmdFrame>("frame");
  1505. case 'g': return match_cmd<CmdGlobal>("global");
  1506. case 'h': return match_cmd<CmdHelp>("help");
  1507. case 'i': return match_cmd<CmdInfo>("info");
  1508. case 'k': return match_cmd<CmdConstant>("konstant");
  1509. case 'l': return match_cmd<CmdList>("list");
  1510. case 'm': return match_cmd<CmdMachine>("machine");
  1511. case 'n': return match_cmd<CmdNext>("next");
  1512. case 'o': return match_cmd<CmdOut>("out");
  1513. case 'p': return match_cmd<CmdPrint>("print");
  1514. case 'q': return match_cmd<CmdQuit>("quit");
  1515. case 'r': return match_cmd<CmdRun>("run");
  1516. case 's': return match_cmd<CmdStep>("step");
  1517. case 't': return match_cmd<CmdThread>("thread");
  1518. case 'u': return match_cmd<CmdUp>("up");
  1519. case 'v': return match_cmd<CmdVariable>("variable");
  1520. case 'w': return match_cmd<CmdWhere>("where");
  1521. // these single lettter commands allow "x{cmd}" and "x {cmd}"
  1522. case 'x': shiftCommand(); return new_cmd<CmdExtended>("extended");
  1523. case '!': shiftCommand(); return new_cmd<CmdShell>("shell");
  1524. case '&': shiftCommand(); return new_cmd<CmdMacro>("macro");
  1525. }
  1526. return nullptr;
  1527. }
  1528. // Parses the current command string. If invalid return false.
  1529. // Otherwise, carry out the command and return true.
  1530. // NB: the command may throw a variety of exceptions derived from
  1531. // DebuggerClientException.
  1532. bool DebuggerClient::process() {
  1533. TRACE(2, "DebuggerClient::process\n");
  1534. clearCachedLocal();
  1535. // assume it is a known command.
  1536. m_unknownCmd = false;
  1537. switch (tolower(m_command[0])) {
  1538. case '@':
  1539. case '=':
  1540. case '$': {
  1541. processTakeCode();
  1542. return true;
  1543. }
  1544. case '<': {
  1545. if (match("<?php")) {
  1546. processTakeCode();
  1547. return true;
  1548. }
  1549. }
  1550. case '?': {
  1551. if (match("?")) {
  1552. usageLogCommand("help", m_line);
  1553. CmdHelp().onClient(*this);
  1554. return true;
  1555. }
  1556. if (match("?>")) {
  1557. processEval();
  1558. return true;
  1559. }
  1560. break;
  1561. }
  1562. default: {
  1563. auto cmd = createCommand();
  1564. if (cmd) {
  1565. usageLogCommand(m_commandCanonical, m_line);
  1566. if (cmd->is(DebuggerCommand::KindOfRun)) playMacro("startup");
  1567. cmd->onClient(*this);
  1568. } else {
  1569. m_unknownCmd = true;
  1570. processTakeCode();
  1571. }
  1572. return true;
  1573. }
  1574. }
  1575. return false;
  1576. }
  1577. ///////////////////////////////////////////////////////////////////////////////
  1578. // helpers
  1579. void DebuggerClient::addToken(std::string &token, int idx) {
  1580. TRACE(2, "DebuggerClient::addToken\n");
  1581. m_argIdx.push_back(idx);
  1582. if (m_command.empty()) {
  1583. m_command = token;
  1584. } else {
  1585. m_args.push_back(token);
  1586. }
  1587. token.clear();
  1588. }
  1589. void DebuggerClient::parseCommand(const char *line) {
  1590. TRACE(2, "DebuggerClient::parseCommand\n");
  1591. m_command.clear();
  1592. m_args.clear();
  1593. char quote = 0;
  1594. std::string token;
  1595. m_argIdx.clear();
  1596. int i = 0;
  1597. for (i = 0; line[i]; i++) {
  1598. char ch = line[i];
  1599. char next = line[i+1];
  1600. switch (ch) {
  1601. case ' ':
  1602. if (!quote) {
  1603. if (!token.empty()) {
  1604. addToken(token, i);
  1605. }
  1606. } else {
  1607. token += ch;
  1608. }
  1609. break;
  1610. case '"':
  1611. case '\'':
  1612. if (quote == 0) {
  1613. quote = ch;
  1614. token += ch;
  1615. break;
  1616. }
  1617. if (quote == ch && (next == ' ' || next == 0)) {
  1618. token += ch;
  1619. addToken(token, i);
  1620. quote = 0;
  1621. break;
  1622. }
  1623. token += ch;
  1624. break;
  1625. case '\\':
  1626. if ((next == ' ' || next == '"' || next == '\'' || next == '\\')) {
  1627. if (quote == '\'') {
  1628. token += ch;
  1629. }
  1630. i++;
  1631. token += next;
  1632. break;
  1633. }
  1634. // fall through
  1635. default:
  1636. token += ch;
  1637. break;
  1638. }
  1639. }
  1640. if (!token.empty()) {
  1641. addToken(token, i);
  1642. }
  1643. }
  1644. bool DebuggerClient::parse(const char *line) {
  1645. TRACE(2, "DebuggerClient::parse\n");
  1646. if (m_inputState != TakingCode) {
  1647. while (isspace(*line)) line++;
  1648. }
  1649. m_line = line;
  1650. if (m_inputState == TakingCommand) {
  1651. parseCommand(line);
  1652. return true;
  1653. }
  1654. if (m_inputState == TakingCode) {
  1655. int pos = checkEvalEnd();
  1656. if (pos >= 0) {
  1657. if (pos > 0) {
  1658. m_code += m_line.substr(0, pos);
  1659. }
  1660. processEval();
  1661. } else {
  1662. if (!strncasecmp(m_line.c_str(), "abort", m_line.size())) {
  1663. m_code.clear();
  1664. m_inputState = TakingCommand;
  1665. return false;
  1666. }
  1667. m_code += m_line + "\n";
  1668. }
  1669. }
  1670. return false;
  1671. }
  1672. bool DebuggerClient::match(const char *cmd) {
  1673. TRACE(2, "DebuggerClient::match\n");
  1674. assertx(cmd && *cmd);
  1675. return !strncasecmp(m_command.c_str(), cmd, m_command.size());
  1676. }
  1677. bool DebuggerClient::Match(const char *input, const char *cmd) {
  1678. TRACE(2, "DebuggerClient::Match\n");
  1679. return !strncasecmp(input, cmd, strlen(input));
  1680. }
  1681. bool DebuggerClient::arg(int index, const char *s) const {
  1682. TRACE(2, "DebuggerClient::arg\n");
  1683. assertx(s && *s);
  1684. assertx(index > 0);
  1685. --index;
  1686. return (int)m_args.size() > index &&
  1687. !strncasecmp(m_args[index].c_str(), s, m_args[index].size());
  1688. }
  1689. std::string DebuggerClient::argValue(int index) {
  1690. TRACE(2, "DebuggerClient::argValue\n");
  1691. assertx(index > 0);
  1692. --index;
  1693. if (index >= 0 && index < (int)m_args.size()) {
  1694. return m_args[index];
  1695. }
  1696. return "";
  1697. }
  1698. std::string DebuggerClient::lineRest(int index) {
  1699. TRACE(2, "DebuggerClient::lineRest\n");
  1700. assertx(index > 0);
  1701. return m_line.substr(m_argIdx[index - 1] + 1);
  1702. }
  1703. ///////////////////////////////////////////////////////////////////////////////
  1704. // comunication with DebuggerProxy
  1705. DebuggerCommandPtr DebuggerClient::xend(DebuggerCommand *cmd,
  1706. EventLoopKind loopKind) {
  1707. TRACE(2, "DebuggerClient::xend\n");
  1708. sendToServer(cmd);
  1709. return eventLoop(loopKind, cmd->getType(), "Receive for command");
  1710. }
  1711. void DebuggerClient::sendToServer(DebuggerCommand *cmd) {
  1712. TRACE(2, "DebuggerClient::sendToServer\n");
  1713. if (!cmd->send(m_machine->m_thrift)) {
  1714. Logger::Error("Send command: unable to communicate with server.");
  1715. throw DebuggerProtocolException();
  1716. }
  1717. }
  1718. ///////////////////////////////////////////////////////////////////////////////
  1719. // helpers
  1720. int DebuggerClient::checkEvalEnd() {
  1721. TRACE(2, "DebuggerClient::checkEvalEnd\n");
  1722. size_t pos = m_line.rfind("?>");
  1723. if (pos == std::string::npos) {
  1724. return -1;
  1725. }
  1726. for (size_t p = pos + 2; p < m_line.size(); p++) {
  1727. if (!isspace(m_line[p])) {
  1728. return -1;
  1729. }
  1730. }
  1731. return pos;
  1732. }
  1733. const StaticString s_UNDERSCORE("_");
  1734. // Parses the current command line as a code execution command
  1735. // and carries out the command.
  1736. void DebuggerClient::processTakeCode() {
  1737. TRACE(2, "DebuggerClient::processTakeCode\n");
  1738. assertx(m_inputState == TakingCommand);
  1739. char first = m_line[0];
  1740. if (first == '@') {
  1741. usageLogCommand("@", m_line);
  1742. m_code = std::string("<?hh ") + (m_line.c_str() + 1) + ";";
  1743. processEval();
  1744. return;
  1745. } else if (first == '=') {
  1746. usageLogCommand("=", m_line);
  1747. while (m_line.at(m_line.size() - 1) == ';') {
  1748. // strip the trailing ;
  1749. m_line = m_line.substr(0, m_line.size() - 1);
  1750. }
  1751. m_code = std::string("<?hh $_=(") + m_line.substr(1) + "); ";
  1752. if (processEval()) CmdVariable::PrintVariable(*this, s_UNDERSCORE);
  1753. return;
  1754. } else if (first != '<') {
  1755. usageLogCommand("eval", m_line);
  1756. // User entered something that did not start with @, =, or <
  1757. // and also was not a debugger command. Interpret it as PHP.
  1758. m_code = "<?hh ";
  1759. m_code += m_line + ";";
  1760. processEval();
  1761. return;
  1762. }
  1763. usageLogCommand("<?php", m_line);
  1764. m_code = "<?hh ";
  1765. m_code += m_line.substr(m_command.length()) + "\n";
  1766. m_inputState = TakingCode;
  1767. int pos = checkEvalEnd();
  1768. if (pos >= 0) {
  1769. m_code.resize(m_code.size() - m_line.size() + pos - 1);
  1770. processEval();
  1771. }
  1772. }
  1773. bool DebuggerClient::processEval() {
  1774. TRACE(2, "DebuggerClient::processEval\n");
  1775. m_inputState = TakingCommand;
  1776. m_acLiveListsDirty = true;
  1777. CmdEval eval;
  1778. eval.onClient(*this);
  1779. return !eval.failed();
  1780. }
  1781. void DebuggerClient::swapHelp() {
  1782. TRACE(2, "DebuggerClient::swapHelp\n");
  1783. assertx(m_args.size() > 0);
  1784. m_command = m_args[0];
  1785. m_args[0] = "help";
  1786. }
  1787. void DebuggerClient::quit() {
  1788. TRACE(2, "DebuggerClient::quit\n");
  1789. closeAllConnections();
  1790. throw DebuggerClientExitException();
  1791. }
  1792. DSandboxInfoPtr DebuggerClient::getSandbox(int index) const {
  1793. TRACE(2, "DebuggerClient::getSandbox\n");
  1794. if (index > 0) {
  1795. --index;
  1796. if (index >= 0 && index < (int)m_sandboxes.size()) {
  1797. return m_sandboxes[index];
  1798. }
  1799. }
  1800. return DSandboxInfoPtr();
  1801. }
  1802. // Update the current sandbox in the current machine. This should always be
  1803. // called once we're attached to a machine.
  1804. void DebuggerClient::setSandbox(DSandboxInfoPtr sandbox) {
  1805. assertx(m_machine != nullptr);
  1806. m_machine->m_sandbox = sandbox;
  1807. }
  1808. // Return the ID of the current sandbox, if there is one. If we're connected to
  1809. // a machine that is attached to a sandbox, then we'll have an ID.
  1810. std::string DebuggerClient::getSandboxId() {
  1811. if ((m_machine != nullptr) && m_machine->m_sandboxAttached &&
  1812. (m_machine->m_sandbox != nullptr)) {
  1813. return m_machine->m_sandbox->id();
  1814. }
  1815. return "None";
  1816. }
  1817. void DebuggerClient::updateThreads(std::vector<DThreadInfoPtr> threads) {
  1818. TRACE(2, "DebuggerClient::updateThreads\n");
  1819. m_threads = threads;
  1820. for (unsigned int i = 0; i < m_threads.size(); i++) {
  1821. DThreadInfoPtr thread = m_threads[i];
  1822. std::map<int64_t, int>::const_iterator iter =
  1823. m_threadIdMap.find(thread->m_id);
  1824. if (iter != m_threadIdMap.end()) {
  1825. m_threads[i]->m_index = iter->second;
  1826. } else {
  1827. int index = m_threadIdMap.size() + 1;
  1828. m_threadIdMap[thread->m_id] = index;
  1829. m_threads[i]->m_index = index;
  1830. }
  1831. }
  1832. }
  1833. DThreadInfoPtr DebuggerClient::getThread(int index) const {
  1834. TRACE(2, "DebuggerClient::getThread\n");
  1835. for (unsigned int i = 0; i < m_threads.size(); i++) {
  1836. if (m_threads[i]->m_index == index) {
  1837. return m_threads[i];
  1838. }
  1839. }
  1840. return DThreadInfoPtr();
  1841. }
  1842. // Retrieves the current source location (file, line).
  1843. // The current location is initially determined by the
  1844. // breakpoint where the debugger is currently stopped and can
  1845. // thereafter be modified by list commands and by switching the
  1846. // the stack frame. The lineFocus and charFocus parameters
  1847. // are non zero only when the source location comes from a breakpoint.
  1848. // They can be used to highlight the location of the current breakpoint
  1849. // in the edit window of an attached IDE, for example.
  1850. void DebuggerClient::getListLocation(std::string &file, int &line,
  1851. int &lineFocus0, int &charFocus0,
  1852. int &lineFocus1, int &charFocus1) {
  1853. TRACE(2, "DebuggerClient::getListLocation\n");
  1854. lineFocus0 = charFocus0 = lineFocus1 = charFocus1 = 0;
  1855. if (m_listFile.empty() && m_breakpoint) {
  1856. setListLocation(m_breakpoint->m_file, m_breakpoint->m_line1, true);
  1857. lineFocus0 = m_breakpoint->m_line1;
  1858. charFocus0 = m_breakpoint->m_char1;
  1859. lineFocus1 = m_breakpoint->m_line2;
  1860. charFocus1 = m_breakpoint->m_char2;
  1861. } else if (m_listLineFocus) {
  1862. lineFocus0 = m_listLineFocus;
  1863. }
  1864. file = m_listFile;
  1865. line = m_listLine;
  1866. }
  1867. void DebuggerClient::setListLocation(const std::string &file, int line,
  1868. bool center) {
  1869. TRACE(2, "DebuggerClient::setListLocation\n");
  1870. m_listFile = file;
  1871. if (!m_listFile.empty() && m_listFile[0] != '/' && !m_sourceRoot.empty()) {
  1872. if (m_sourceRoot[m_sourceRoot.size() - 1] != '/') {
  1873. m_sourceRoot += "/";
  1874. }
  1875. m_listFile = m_sourceRoot + m_listFile;
  1876. }
  1877. m_listLine = line;
  1878. if (center && m_listLine) {
  1879. m_listLineFocus = m_listLine;
  1880. m_listLine -= CodeBlockSize / 2;
  1881. if (m_listLine < 0) {
  1882. m_listLine = 0;
  1883. }
  1884. }
  1885. }
  1886. void DebuggerClient::setSourceRoot(const std::string &sourceRoot) {
  1887. TRACE(2, "DebuggerClient::setSourceRoot\n");
  1888. m_sourceRoot = sourceRoot;
  1889. saveConfig();
  1890. // apply change right away
  1891. setListLocation(m_listFile, m_listLine, true);
  1892. }
  1893. void DebuggerClient::setMatchedBreakPoints(
  1894. std::vector<BreakPointInfoPtr> breakpoints) {
  1895. TRACE(2, "DebuggerClient::setMatchedBreakPoints\n");
  1896. m_matched = std::move(breakpoints);
  1897. }
  1898. void DebuggerClient::setCurrentLocation(int64_t threadId,
  1899. BreakPointInfoPtr breakpoint) {
  1900. TRACE(2, "DebuggerClient::setCurrentLocation\n");
  1901. m_threadId = threadId;
  1902. m_breakpoint = breakpoint;
  1903. m_stacktrace.reset();
  1904. m_listFile.clear();
  1905. m_listLine = 0;
  1906. m_listLineFocus = 0;
  1907. m_acLiveListsDirty = true;
  1908. }
  1909. void DebuggerClient::addWatch(const char *fmt, const std::string &php) {
  1910. TRACE(2, "DebuggerClient::addWatch\n");
  1911. WatchPtr watch(new Watch());
  1912. watch->first = fmt;
  1913. watch->second = php;
  1914. m_watches.push_back(watch);
  1915. }
  1916. void DebuggerClient::setStackTrace(const Array& stacktrace) {
  1917. TRACE(2, "DebuggerClient::setStackTrace\n");
  1918. m_stacktrace = stacktrace;
  1919. }
  1920. void DebuggerClient::moveToFrame(int index, bool display /* = true */) {
  1921. TRACE(2, "DebuggerClient::moveToFrame\n");
  1922. m_frame = index;
  1923. if (m_frame >= m_stacktrace.size()) {
  1924. m_frame = m_stacktrace.size() - 1;
  1925. }
  1926. if (m_frame < 0) {
  1927. m_frame = 0;
  1928. }
  1929. const Array& frame = m_stacktrace[m_frame].toArray();
  1930. if (!frame.isNull()) {
  1931. String file = frame[s_file].toString();
  1932. int line = frame[s_line].toInt32();
  1933. if (!file.empty() && line) {
  1934. if (m_frame == 0) {
  1935. m_listFile.clear();
  1936. m_listLine = 0;
  1937. m_listLineFocus = 0;
  1938. } else {
  1939. setListLocation(file.data(), line, true);
  1940. }
  1941. }
  1942. if (display) {
  1943. printFrame(m_frame, frame);
  1944. }
  1945. }
  1946. }
  1947. const StaticString
  1948. s_args("args"),
  1949. s_namespace("namespace"),
  1950. s_class("class"),
  1951. s_function("function"),
  1952. s_id("id"),
  1953. s_ancestors("ancestors");
  1954. void DebuggerClient::printFrame(int index, const Array& frame) {
  1955. TRACE(2, "DebuggerClient::printFrame\n");
  1956. StringBuffer args;
  1957. for (ArrayIter iter(frame[s_args].toArray()); iter; ++iter) {
  1958. if (!args.empty()) args.append(", ");
  1959. args.append(FormatVariableWithLimit(iter.second(), 80));
  1960. }
  1961. StringBuffer func;
  1962. if (frame.exists(s_namespace)) {
  1963. func.append(frame[s_namespace].toString());
  1964. func.append("::");
  1965. }
  1966. if (frame.exists(s_class)) {
  1967. func.append(frame[s_class].toString());
  1968. func.append("::");
  1969. }
  1970. func.append(frame[s_function].toString());
  1971. String sindex(index);
  1972. print("#%s %s (%s)",
  1973. sindex.data(),
  1974. func.data() ? func.data() : "",
  1975. args.data() ? args.data() : "");
  1976. if (!frame[s_file].isNull()) {
  1977. int line = (int)frame[s_line].toInt32();
  1978. auto fileLineInfo =
  1979. folly::stringPrintf(" %s at %s",
  1980. String(" ").substr(0, sindex.size()).data(),
  1981. frame[s_file].toString().data());
  1982. if (line > 0) {
  1983. fileLineInfo += folly::stringPrintf(":%d", line);
  1984. } else {
  1985. fileLineInfo += ":unknown line";
  1986. }
  1987. print(fileLineInfo);
  1988. }
  1989. }
  1990. void DebuggerClient::startMacro(std::string name) {
  1991. TRACE(2, "DebuggerClient::startMacro\n");
  1992. if (m_macroRecording &&
  1993. ask("We are recording a macro. Do you want to save? [Y/n]") != 'n') {
  1994. endMacro();
  1995. }
  1996. if (name.empty()) {
  1997. name = "default";
  1998. } else {
  1999. for (unsigned int i = 0; i < m_macros.size(); i++) {
  2000. if (m_macros[i]->m_name == name) {
  2001. if (ask("This will overwrite existing one. Proceed? [y/N]") != 'y') {
  2002. return;
  2003. }
  2004. }
  2005. break;
  2006. }
  2007. }
  2008. m_macroRecording = std::make_shared<Macro>();
  2009. m_macroRecording->m_name = name;
  2010. }
  2011. void DebuggerClient::endMacro() {
  2012. TRACE(2, "DebuggerClient::endMacro\n");
  2013. if (!m_macroRecording) {
  2014. error("There is no ongoing recording.");
  2015. tutorial("Use '& [s]tart {name}' or '& [s]tart' command to start "
  2016. "macro recording first.");
  2017. return;
  2018. }
  2019. bool found = false;
  2020. for (unsigned int i = 0; i < m_macros.size(); i++) {
  2021. if (m_macros[i]->m_name == m_macroRecording->m_name) {
  2022. m_macros[i] = m_macroRecording;
  2023. found = true;
  2024. break;
  2025. }
  2026. }
  2027. if (!found) {
  2028. m_macros.push_back(m_macroRecording);
  2029. }
  2030. saveConfig();
  2031. m_macroRecording.reset();
  2032. }
  2033. bool DebuggerClient::playMacro(std::string name) {
  2034. TRACE(2, "DebuggerClient::playMacro\n");
  2035. if (name.empty()) {
  2036. name = "default";
  2037. }
  2038. for (unsigned int i = 0; i < m_macros.size(); i++) {
  2039. if (m_macros[i]->m_name == name) {
  2040. m_macroPlaying = m_macros[i];
  2041. m_macroPlaying->m_index = 0;
  2042. return true;
  2043. }
  2044. }
  2045. return false;
  2046. }
  2047. bool DebuggerClient::deleteMacro(int index) {
  2048. TRACE(2, "DebuggerClient::deleteMacro\n");
  2049. --index;
  2050. if (index >= 0 && index < (int)m_macros.size()) {
  2051. if (ask("Are you sure you want to delete the macro? [y/N]") != 'y') {
  2052. return true;
  2053. }
  2054. m_macros.erase(m_macros.begin() + index);
  2055. saveConfig();
  2056. return true;
  2057. }
  2058. return false;
  2059. }
  2060. void DebuggerClient::record(const char *line) {
  2061. TRACE(2, "DebuggerClient::record\n");
  2062. assertx(line);
  2063. if (m_macroRecording && line[0] != '&') {
  2064. m_macroRecording->m_cmds.push_back(line);
  2065. }
  2066. }
  2067. ///////////////////////////////////////////////////////////////////////////////
  2068. // helpers for usage logging
  2069. // Log the execution of a command.
  2070. void DebuggerClient::usageLogCommand(const std::string &cmd,
  2071. const std::string &data) {
  2072. Debugger::UsageLog("terminal", getSandboxId(), cmd, data);
  2073. }
  2074. // Log random, interesting events in the client.
  2075. void DebuggerClient::usageLogEvent(const std::string &eventName,
  2076. const std::string &data) {
  2077. Debugger::UsageLog("terminal", getSandboxId(),
  2078. "ClientEvent: " + eventName, data);
  2079. }
  2080. ///////////////////////////////////////////////////////////////////////////////
  2081. // configuration
  2082. void DebuggerClient::loadConfig() {
  2083. TRACE(2, "DebuggerClient::loadConfig\n");
  2084. bool usedHomeDirConfig = false;
  2085. if (m_configFileName.empty()) {
  2086. m_configFileName = Process::GetHomeDirectory() + ConfigFileName;
  2087. usedHomeDirConfig = true;
  2088. }
  2089. // make sure file exists
  2090. FILE *f = fopen(m_configFileName.c_str(), "r");
  2091. bool needToWriteFile = f == nullptr;
  2092. if (needToWriteFile) f = fopen(m_configFileName.c_str(), "a");
  2093. if (f) {
  2094. fclose(f);
  2095. } else {
  2096. m_configFileName.clear();
  2097. return;
  2098. }
  2099. Hdf config;
  2100. IniSettingMap ini = IniSetting::Map::object;
  2101. try {
  2102. if (usedHomeDirConfig) {
  2103. config.open(Process::GetHomeDirectory() + LegacyConfigFileName);
  2104. needToWriteFile = true;
  2105. }
  2106. } catch (const HdfException& e) {
  2107. // Good, they have migrated already
  2108. }
  2109. #define BIND(name, ...) \
  2110. IniSetting::Bind(&s_debugger_extension, IniSetting::PHP_INI_SYSTEM, \
  2111. "hhvm." #name, __VA_ARGS__)
  2112. m_neverSaveConfigOverride = true; // Prevent saving config while reading it
  2113. // These are system settings, but can be loaded after the core runtime
  2114. // options are loaded. So allow it.
  2115. IniSetting::s_system_settings_are_set = false;
  2116. Config::Bind(s_use_utf8, ini, config, "UTF8", true);
  2117. config["UTF8"] = s_use_utf8; // for starter
  2118. BIND(utf8, &s_use_utf8);
  2119. Config::Bind(UseColor, ini, config, "Color", true);
  2120. BIND(color, &UseColor);
  2121. if (UseColor && RuntimeOption::EnableDebuggerColor) {
  2122. LoadColors(ini, config);
  2123. }
  2124. Config::Bind(m_tutorial, ini, config, "Tutorial", 0);
  2125. BIND(tutorial, &m_tutorial);
  2126. Config::Bind(m_scriptMode, ini, config, "ScriptMode");
  2127. BIND(script_mode, &m_scriptMode);
  2128. Config::Bind(m_neverSaveConfig, ini, config, "NeverSaveConfig", false);
  2129. BIND(never_save_config, &m_neverSaveConfig);
  2130. setDebuggerClientSmallStep(Config::GetBool(ini, config, "SmallStep"));
  2131. BIND(small_step, IniSetting::SetAndGet<bool>(
  2132. [this](const bool& v) {
  2133. setDebuggerClientSmallStep(v);
  2134. return true;
  2135. },
  2136. [this]() { return getDebuggerClientSmallStep(); }
  2137. ));
  2138. setDebuggerClientMaxCodeLines(Config::GetInt16(ini, config, "MaxCodeLines",
  2139. -1));
  2140. BIND(max_code_lines, IniSetting::SetAndGet<short>(
  2141. [this](const short& v) {
  2142. setDebuggerClientMaxCodeLines(v);
  2143. return true;
  2144. },
  2145. [this]() { return getDebuggerClientMaxCodeLines(); }
  2146. ));
  2147. setDebuggerClientBypassCheck(Config::GetBool(ini, config,
  2148. "BypassAccessCheck"));
  2149. BIND(bypass_access_check, IniSetting::SetAndGet<bool>(
  2150. [this](const bool& v) { setDebuggerClientBypassCheck(v); return true; },
  2151. [this]() { return getDebuggerClientBypassCheck(); }
  2152. ));
  2153. int printLevel = Config::GetInt16(ini, config, "PrintLevel", 5);
  2154. if (printLevel > 0 && printLevel < MinPrintLevel) {
  2155. printLevel = MinPrintLevel;
  2156. }
  2157. setDebuggerClientPrintLevel(printLevel);
  2158. // For some reason gcc won't capture MinPrintLevel without this
  2159. auto const min = MinPrintLevel;
  2160. BIND(print_level, IniSetting::SetAndGet<short>(
  2161. [this, min](const short &printLevel) {
  2162. if (printLevel > 0 && printLevel < min) {
  2163. setDebuggerClientPrintLevel(min);
  2164. } else {
  2165. setDebuggerClientPrintLevel(printLevel);
  2166. }
  2167. return true;
  2168. },
  2169. [this]() { return getDebuggerClientPrintLevel(); }
  2170. ));
  2171. setDebuggerClientStackArgs(Config::GetBool(ini, config, "StackArgs", true));
  2172. BIND(stack_args, IniSetting::SetAndGet<bool>(
  2173. [this](const bool& v) { setDebuggerClientStackArgs(v); return true; },
  2174. [this]() { return getDebuggerClientStackArgs(); }
  2175. ));
  2176. setDebuggerClientShortPrintCharCount(
  2177. Config::GetInt16(ini, config, "ShortPrintCharCount", 200));
  2178. BIND(short_print_char_count, IniSetting::SetAndGet<short>(
  2179. [this](const short& v) {
  2180. setDebuggerClientShortPrintCharCount(v); return true;
  2181. },
  2182. [this]() { return getDebuggerClientShortPrintCharCount(); }
  2183. ));
  2184. Config::Bind(m_tutorialVisited, ini, config, "Tutorial.Visited");
  2185. BIND(tutorial.visited, &m_tutorialVisited);
  2186. auto macros_callback = [&](const IniSetting::Map& ini_m, const Hdf& hdf_m,
  2187. const std::string& /*ini_m_key*/) {
  2188. auto macro = std::make_shared<Macro>();
  2189. macro->load(ini_m, hdf_m);
  2190. m_macros.push_back(macro);
  2191. };
  2192. Config::Iterate(macros_callback, ini, config, "Macros");
  2193. BIND(macros, IniSetting::SetAndGet<Array>(
  2194. [this](const Array& val) {
  2195. for (ArrayIter iter(val); iter; ++iter) {
  2196. auto macro = std::make_shared<Macro>();
  2197. auto macroArr = iter.second().asCArrRef();
  2198. macro->m_name = macroArr[s_name].asCStrRef().toCppString();
  2199. for (ArrayIter cmditer(macroArr[s_cmds]); cmditer; ++cmditer) {
  2200. macro->m_cmds.push_back(cmditer.second().asCStrRef().toCppString());
  2201. }
  2202. m_macros.push_back(macro);
  2203. }
  2204. return true;
  2205. },
  2206. [this]() {
  2207. ArrayInit ret(m_macros.size(), ArrayInit::Map{});
  2208. for (auto& macro : m_macros) {
  2209. ArrayInit ret_macro(2, ArrayInit::Map{});
  2210. ret_macro.set(s_name, macro->m_name);
  2211. PackedArrayInit ret_cmds(macro->m_cmds.size());
  2212. for (auto& cmd : macro->m_cmds) {
  2213. ret_cmds.append(cmd);
  2214. }
  2215. ret_macro.set(s_cmds, ret_cmds.toArray());
  2216. ret.append(ret_macro.toArray());
  2217. }
  2218. return ret.toArray();
  2219. }
  2220. ));
  2221. Config::Bind(m_sourceRoot, ini, config, "SourceRoot");
  2222. BIND(source_root, &m_sourceRoot);
  2223. // We are guaranteed to have an ini file given how m_configFileName is set
  2224. // above
  2225. Config::ParseIniFile(m_configFileName);
  2226. // Do this after the ini processing so we don't accidentally save the config
  2227. // when we change one of the options
  2228. m_neverSaveConfigOverride = false;
  2229. IniSetting::s_system_settings_are_set = true; // We are set again
  2230. if (needToWriteFile && !m_neverSaveConfig) {
  2231. saveConfig(); // so to generate a starter for people
  2232. }
  2233. #undef BIND
  2234. }
  2235. void DebuggerClient::saveConfig() {
  2236. TRACE(2, "DebuggerClient::saveConfig\n");
  2237. if (m_neverSaveConfig || m_neverSaveConfigOverride) return;
  2238. if (m_configFileName.empty()) {
  2239. // we are not touching a file that was not successfully loaded earlier
  2240. return;
  2241. }
  2242. std::ofstream stream(m_configFileName);
  2243. stream << "hhvm.utf8 = " << s_use_utf8 << std::endl;
  2244. stream << "hhvm.color = " << UseColor << std::endl;
  2245. stream << "hhvm.source_root = " << m_sourceRoot << std::endl;
  2246. stream << "hhvm.never_save_config = " << m_neverSaveConfig << std::endl;
  2247. stream << "hhvm.tutorial = " << m_tutorial << std::endl;
  2248. unsigned int i = 0;
  2249. for (auto const& str : m_tutorialVisited) {
  2250. stream << "hhvm.tutorial.visited[" << i++ << "] = " << str << std::endl;
  2251. }
  2252. for (i = 0; i < m_macros.size(); i++) {
  2253. m_macros[i]->save(stream, i);
  2254. }
  2255. std::vector<std::string> names;
  2256. get_supported_colors(names);
  2257. for (i = 0; i < names.size(); i++) {
  2258. stream << "hhvm.color.supported_names[" << i+1 << "] = " << names[i]
  2259. << std::endl;
  2260. }
  2261. // TODO if you are clever you can make the macro do these
  2262. stream << "hhvm.bypass_access_check = " << getDebuggerClientBypassCheck()
  2263. << std::endl;
  2264. stream << "hhvm.print_level = " << getDebuggerClientPrintLevel()
  2265. << std::endl;
  2266. stream << "hhvm.stack_args = " << getDebuggerClientStackArgs()
  2267. << std::endl;
  2268. stream << "hhvm.max_code_lines = " << getDebuggerClientMaxCodeLines()
  2269. << std::endl;
  2270. stream << "hhvm.small_step = " << getDebuggerClientSmallStep()
  2271. << std::endl;
  2272. stream << "hhvm.short_print_char_count = "
  2273. << getDebuggerClientShortPrintCharCount() << std::endl;
  2274. auto legacy = Process::GetHomeDirectory() + LegacyConfigFileName;
  2275. ::unlink(legacy.c_str());
  2276. }
  2277. ///////////////////////////////////////////////////////////////////////////////
  2278. }}