22f8788e2f
Cleanup a lot of hangs with either the debugger client or server in a variety of error conditions, mostly related to communication errors or the client or server exiting unexpectedly. One of the biggest fixes is that all cases where the client was left in a state where Ctrl-C wouldn't work have been fixed. Remove lots of little snippets of dead code. If you see a function (or small set of functions/fields) deleted then it was actually dead. I debated whether to keep throwing DebuggerClientExitException on the server, and I decided to keep it. I think it's reasonable that if you've got the server stopped and you quit the debugger that the request gets terminated rather than continuing to run. I also considered a big change to the way Ctrl-C works, but ended up staying with what was there with just a bit of cleanup. We need to guard against people banging on Ctrl-C, which is a reasonable behavior, and I think it feels pretty reasonable with the updated message. Finally, added many comments about how this stuff works.
2426 linhas
74 KiB
C++
2426 linhas
74 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 3.01 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| http://www.php.net/license/3_01.txt |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#include "hphp/runtime/debugger/debugger_client.h"
|
|
#include "hphp/runtime/debugger/debugger_command.h"
|
|
#include "hphp/runtime/debugger/cmd/all.h"
|
|
#include "hphp/runtime/base/complex_types.h"
|
|
#include "hphp/runtime/base/variable_serializer.h"
|
|
#include "hphp/runtime/base/string_util.h"
|
|
#include "hphp/runtime/base/preg.h"
|
|
#include "hphp/runtime/ext/ext_json.h"
|
|
#include "hphp/runtime/ext/ext_socket.h"
|
|
#include "hphp/runtime/ext/ext_network.h"
|
|
#include "hphp/util/text_color.h"
|
|
#include "hphp/util/text_art.h"
|
|
#include "hphp/util/logger.h"
|
|
#include "hphp/util/process.h"
|
|
#include <boost/format.hpp>
|
|
#include <boost/scoped_ptr.hpp>
|
|
|
|
#define USE_VARARGS
|
|
#define PREFER_STDARG
|
|
#include "readline/readline.h"
|
|
#include "readline/history.h"
|
|
|
|
using namespace HPHP::Util::TextArt;
|
|
|
|
#define PHP_WORD_BREAK_CHARACTERS " \t\n\"\\'`@=;,|{[()]}+*%^!~&"
|
|
|
|
namespace HPHP { namespace Eval {
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
TRACE_SET_MOD(debugger);
|
|
|
|
static boost::scoped_ptr<DebuggerClient> debugger_client;
|
|
|
|
static DebuggerClient& getStaticDebuggerClient() {
|
|
TRACE(2, "DebuggerClient::getStaticDebuggerClient\n");
|
|
/*
|
|
* DebuggerClient acquires global mutexes in its constructor, so we
|
|
* allocate s_debugger_client lazily to ensure that all of the
|
|
* global mutexes have been initialized before we enter the
|
|
* constructor.
|
|
*
|
|
* This initialization is thread-safe because program_functions.cpp
|
|
* must call Debugger::StartClient (which ends up here) before any
|
|
* additional threads are created.
|
|
*/
|
|
if (!debugger_client) {
|
|
debugger_client.reset(new DebuggerClient);
|
|
}
|
|
return *debugger_client;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// readline setups
|
|
|
|
static char* debugger_generator(const char* text, int state) {
|
|
TRACE(2, "DebuggerClient::debugger_generator\n");
|
|
return getStaticDebuggerClient().getCompletion(text, state);
|
|
}
|
|
|
|
static char **debugger_completion(const char *text, int start, int end) {
|
|
TRACE(2, "DebuggerClient::debugger_completion\n");
|
|
if (getStaticDebuggerClient().setCompletion(text, start, end)) {
|
|
return rl_completion_matches((char*)text, &debugger_generator);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static void debugger_signal_handler(int sig) {
|
|
TRACE(2, "DebuggerClient::debugger_signal_handler\n");
|
|
getStaticDebuggerClient().onSignal(sig);
|
|
}
|
|
|
|
void DebuggerClient::onSignal(int sig) {
|
|
TRACE(2, "DebuggerClient::onSignal\n");
|
|
if (m_inputState == TakingInterrupt) {
|
|
time_t now = time(0);
|
|
|
|
if (m_sigTime) {
|
|
int secWait = 10;
|
|
int secLeft = secWait - (now - m_sigTime);
|
|
if (secLeft <= 0) {
|
|
error("Program is not responding. Please restart debugger to get a "
|
|
"new connection.");
|
|
quit(); // NB: the machine is running, so can't send a real CmdQuit.
|
|
return;
|
|
}
|
|
info("Please wait. If not responding in %d second%s, "
|
|
"press Ctrl-C again to quit.", secLeft, secLeft > 1 ? "s" : "");
|
|
} else {
|
|
info("Pausing program execution, please wait...");
|
|
usageLog("signal");
|
|
m_sigTime = now;
|
|
}
|
|
m_signum = CmdSignal::SignalBreak;
|
|
} else {
|
|
rl_replace_line("", 0);
|
|
rl_redisplay();
|
|
}
|
|
}
|
|
|
|
int DebuggerClient::pollSignal() {
|
|
TRACE(2, "DebuggerClient::pollSignal\n");
|
|
int ret = m_signum;
|
|
m_signum = CmdSignal::SignalNone;
|
|
return ret;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* Initialization and shutdown.
|
|
*/
|
|
class ReadlineApp {
|
|
public:
|
|
ReadlineApp() {
|
|
TRACE(2, "ReadlineApp::ReadlineApp\n");
|
|
DebuggerClient::AdjustScreenMetrics();
|
|
|
|
rl_attempted_completion_function = debugger_completion;
|
|
rl_basic_word_break_characters = PHP_WORD_BREAK_CHARACTERS;
|
|
|
|
rl_catch_signals = 0;
|
|
signal(SIGINT, debugger_signal_handler);
|
|
|
|
TRACE(3, "ReadlineApp::ReadlineApp, about to call read_history\n");
|
|
read_history((Process::GetHomeDirectory() +
|
|
DebuggerClient::HistoryFileName).c_str());
|
|
TRACE(3, "ReadlineApp::ReadlineApp, done calling read_history\n");
|
|
}
|
|
|
|
~ReadlineApp() {
|
|
TRACE(2, "ReadlineApp::~ReadlineApp\n");
|
|
write_history((Process::GetHomeDirectory() +
|
|
DebuggerClient::HistoryFileName).c_str());
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Displaying a spinning wait icon.
|
|
*/
|
|
class ReadlineWaitCursor {
|
|
public:
|
|
ReadlineWaitCursor()
|
|
: m_thread(this, &ReadlineWaitCursor::animate), m_waiting(true) {
|
|
TRACE(2, "ReadlineWaitCursor::ReadlineWaitCursor\n");
|
|
m_thread.start();
|
|
}
|
|
|
|
~ReadlineWaitCursor() {
|
|
TRACE(2, "ReadlineWaitCursor::~ReadlineWaitCursor\n");
|
|
m_waiting = false;
|
|
m_thread.waitForEnd();
|
|
}
|
|
|
|
void animate() {
|
|
TRACE(2, "ReadlineWaitCursor::animate\n");
|
|
m_line = rl_line_buffer;
|
|
while (m_waiting) {
|
|
frame('|'); frame('/'); frame('-'); frame('\\');
|
|
rl_replace_line(m_line.c_str(), 1);
|
|
rl_redisplay();
|
|
}
|
|
}
|
|
|
|
private:
|
|
AsyncFunc<ReadlineWaitCursor> m_thread;
|
|
bool m_waiting;
|
|
string m_line;
|
|
|
|
void frame(char ch) {
|
|
TRACE(2, "ReadlineWaitCursor::getStaticDebuggerClient\n");
|
|
string line = m_line + ch;
|
|
rl_replace_line(line.c_str(), 1);
|
|
rl_redisplay();
|
|
usleep(100000);
|
|
}
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
int DebuggerClient::LineWidth = 76;
|
|
int DebuggerClient::CodeBlockSize = 20;
|
|
int DebuggerClient::ScrollBlockSize = 20;
|
|
const char *DebuggerClient::LineNoFormat = "%4d ";
|
|
const char *DebuggerClient::LocalPrompt = "hphpd";
|
|
const char *DebuggerClient::ConfigFileName = ".hphpd.hdf";
|
|
const char *DebuggerClient::HistoryFileName = ".hphpd.history";
|
|
std::string DebuggerClient::HomePrefix = "/home";
|
|
|
|
bool DebuggerClient::UseColor = true;
|
|
bool DebuggerClient::NoPrompt = false;
|
|
|
|
const char *DebuggerClient::HelpColor = nullptr;
|
|
const char *DebuggerClient::InfoColor = nullptr;
|
|
const char *DebuggerClient::OutputColor = nullptr;
|
|
const char *DebuggerClient::ErrorColor = nullptr;
|
|
const char *DebuggerClient::ItemNameColor = nullptr;
|
|
const char *DebuggerClient::HighlightForeColor = nullptr;
|
|
const char *DebuggerClient::HighlightBgColor = nullptr;
|
|
|
|
const char *DebuggerClient::DefaultCodeColors[] = {
|
|
/* None */ nullptr, nullptr,
|
|
/* Keyword */ nullptr, nullptr,
|
|
/* Comment */ nullptr, nullptr,
|
|
/* String */ nullptr, nullptr,
|
|
/* Variable */ nullptr, nullptr,
|
|
/* Html */ nullptr, nullptr,
|
|
/* Tag */ nullptr, nullptr,
|
|
/* Declaration */ nullptr, nullptr,
|
|
/* Constant */ nullptr, nullptr,
|
|
/* LineNo */ nullptr, nullptr,
|
|
};
|
|
|
|
void DebuggerClient::LoadColors(Hdf hdf) {
|
|
TRACE(2, "DebuggerClient::LoadColors\n");
|
|
HelpColor = LoadColor(hdf["Help"], "BROWN");
|
|
InfoColor = LoadColor(hdf["Info"], "GREEN");
|
|
OutputColor = LoadColor(hdf["Output"], "CYAN");
|
|
ErrorColor = LoadColor(hdf["Error"], "RED");
|
|
ItemNameColor = LoadColor(hdf["ItemName"], "GRAY");
|
|
|
|
HighlightForeColor = LoadColor(hdf["HighlightForeground"], "RED");
|
|
HighlightBgColor = LoadBgColor(hdf["HighlightBackground"], "GRAY");
|
|
|
|
Hdf code = hdf["Code"];
|
|
LoadCodeColor(CodeColorKeyword, code["Keyword"], "CYAN");
|
|
LoadCodeColor(CodeColorComment, code["Comment"], "RED");
|
|
LoadCodeColor(CodeColorString, code["String"], "GREEN");
|
|
LoadCodeColor(CodeColorVariable, code["Variable"], "BROWN");
|
|
LoadCodeColor(CodeColorHtml, code["Html"], "GRAY");
|
|
LoadCodeColor(CodeColorTag, code["Tag"], "MAGENTA");
|
|
LoadCodeColor(CodeColorDeclaration, code["Declaration"], "BLUE");
|
|
LoadCodeColor(CodeColorConstant, code["Constant"], "MAGENTA");
|
|
LoadCodeColor(CodeColorLineNo, code["LineNo"], "GRAY");
|
|
}
|
|
|
|
const char *DebuggerClient::LoadColor(Hdf hdf, const char *defaultName) {
|
|
TRACE(2, "DebuggerClient::LoadColor\n");
|
|
const char *name = hdf.get(defaultName);
|
|
hdf = name; // for starter
|
|
const char *color = get_color_by_name(name);
|
|
if (color == nullptr) {
|
|
Logger::Error("Bad color name %s", name);
|
|
color = get_color_by_name(defaultName);
|
|
}
|
|
return color;
|
|
}
|
|
|
|
const char *DebuggerClient::LoadBgColor(Hdf hdf, const char *defaultName) {
|
|
TRACE(2, "DebuggerClient::LoadBgColor\n");
|
|
const char *name = hdf.get(defaultName);
|
|
hdf = name; // for starter
|
|
const char *color = get_bgcolor_by_name(name);
|
|
if (color == nullptr) {
|
|
Logger::Error("Bad color name %s", name);
|
|
color = get_bgcolor_by_name(defaultName);
|
|
}
|
|
return color;
|
|
}
|
|
|
|
void DebuggerClient::LoadCodeColor(CodeColor index, Hdf hdf,
|
|
const char *defaultName) {
|
|
TRACE(2, "DebuggerClient::LoadCodeColor\n");
|
|
const char *color = LoadColor(hdf, defaultName);
|
|
DefaultCodeColors[index * 2] = color;
|
|
DefaultCodeColors[index * 2 + 1] = color ? ANSI_COLOR_END : nullptr;
|
|
}
|
|
|
|
SmartPtr<Socket> DebuggerClient::Start(const DebuggerClientOptions &options) {
|
|
TRACE(2, "DebuggerClient::Start\n");
|
|
SmartPtr<Socket> ret = getStaticDebuggerClient().connectLocal();
|
|
getStaticDebuggerClient().start(options);
|
|
return ret;
|
|
}
|
|
|
|
void DebuggerClient::Shutdown() {
|
|
TRACE(2, "DebuggerClient::Shutdown\n");
|
|
if (debugger_client) {
|
|
debugger_client->resetSmartAllocatedMembers();
|
|
debugger_client.reset();
|
|
}
|
|
}
|
|
|
|
void DebuggerClient::Stop() {
|
|
TRACE(2, "DebuggerClient::Stop\n");
|
|
getStaticDebuggerClient().stop();
|
|
}
|
|
|
|
void DebuggerClient::AdjustScreenMetrics() {
|
|
TRACE(2, "entered: DebuggerClient::AdjustScreenMetrics\n");
|
|
int rows = 0; int cols = 0;
|
|
rl_get_screen_size(&rows, &cols);
|
|
if (rows > 0 && cols > 0) {
|
|
LineWidth = cols - 4;
|
|
ScrollBlockSize = CodeBlockSize = rows - (rows >> 2);
|
|
}
|
|
TRACE(2, "leaving: DebuggerClient::AdjustScreenMetrics\n");
|
|
}
|
|
|
|
bool DebuggerClient::IsValidNumber(const std::string &arg) {
|
|
TRACE(2, "DebuggerClient::IsValidNumber\n");
|
|
if (arg.empty()) return false;
|
|
for (unsigned int i = 0; i < arg.size(); i++) {
|
|
if (!isdigit(arg[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
String DebuggerClient::FormatVariable(CVarRef v, int maxlen /* = 80 */,
|
|
bool vardump /* = false */) {
|
|
TRACE(2, "DebuggerClient::FormatVariable\n");
|
|
String value;
|
|
if (maxlen <= 0) {
|
|
try {
|
|
VariableSerializer::Type t = vardump ?
|
|
VariableSerializer::VarDump :
|
|
VariableSerializer::DebuggerDump;
|
|
VariableSerializer vs(t, 0, 2);
|
|
value = vs.serialize(v, true);
|
|
} catch (StringBufferLimitException &e) {
|
|
value = "Serialization limit reached";
|
|
} catch (...) {
|
|
assert(false);
|
|
throw;
|
|
}
|
|
} else {
|
|
VariableSerializer vs(VariableSerializer::DebuggerDump, 0, 2);
|
|
value = vs.serializeWithLimit(v, maxlen);
|
|
}
|
|
|
|
if (maxlen <= 0 || value.length() - maxlen < 30) {
|
|
return value;
|
|
}
|
|
|
|
StringBuffer sb;
|
|
sb.append(value.substr(0, maxlen));
|
|
sb.append(" ...(omitted)");
|
|
return sb.detach();
|
|
}
|
|
|
|
String DebuggerClient::FormatInfoVec(const IDebuggable::InfoVec &info,
|
|
int *nameLen /* = NULL */) {
|
|
TRACE(2, "DebuggerClient::FormatInfoVec\n");
|
|
// vertical align names
|
|
int maxlen = 0;
|
|
for (unsigned int i = 0; i < info.size(); i++) {
|
|
int len = strlen(info[i].first);
|
|
if (len > maxlen) maxlen = len;
|
|
}
|
|
|
|
// print
|
|
StringBuffer sb;
|
|
for (unsigned int i = 0; i < info.size(); i++) {
|
|
if (ItemNameColor) sb.append(ItemNameColor);
|
|
string name = info[i].first;
|
|
name += ": ";
|
|
sb.append(name.substr(0, maxlen + 4));
|
|
if (ItemNameColor) sb.append(ANSI_COLOR_END);
|
|
if (OutputColor) sb.append(OutputColor);
|
|
sb.append(info[i].second);
|
|
if (OutputColor) sb.append(ANSI_COLOR_END);
|
|
sb.append("\n");
|
|
}
|
|
if (nameLen) *nameLen = maxlen + 4;
|
|
return sb.detach();
|
|
}
|
|
|
|
String DebuggerClient::FormatTitle(const char *title) {
|
|
TRACE(2, "DebuggerClient::FormatTitle\n");
|
|
String dash = StringUtil::Repeat(BOX_H, (LineWidth - strlen(title)) / 2 - 4);
|
|
|
|
StringBuffer sb;
|
|
sb.append("\n");
|
|
sb.append(" ");
|
|
sb.append(dash);
|
|
sb.append(" "); sb.append(title); sb.append(" ");
|
|
sb.append(dash);
|
|
sb.append("\n");
|
|
return sb.detach();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
DebuggerClient::DebuggerClient(std::string name /* = "" */)
|
|
: m_tutorial(0), m_printFunction(""),
|
|
m_logFile(""), m_logFileHandler(nullptr),
|
|
m_mainThread(this, &DebuggerClient::run), m_stopped(false),
|
|
m_inputState(TakingCommand),
|
|
m_signum(CmdSignal::SignalNone), m_sigTime(0),
|
|
m_acLen(0), m_acIndex(0), m_acPos(0), m_acLiveListsDirty(true),
|
|
m_threadId(0), m_listLine(0), m_listLineFocus(0), m_frame(0),
|
|
m_clientState(StateUninit), m_inApiUse(false),
|
|
m_nameForApi(name) {
|
|
TRACE(2, "DebuggerClient::DebuggerClient\n");
|
|
Debugger::InitUsageLogging();
|
|
}
|
|
|
|
DebuggerClient::~DebuggerClient() {
|
|
TRACE(2, "DebuggerClient::~DebuggerClient\n");
|
|
m_stopped = true;
|
|
m_mainThread.waitForEnd();
|
|
FILE *f = getLogFileHandler();
|
|
if (f != nullptr) {
|
|
fclose(f);
|
|
setLogFileHandler(nullptr);
|
|
}
|
|
}
|
|
|
|
void DebuggerClient::closeAllConnections() {
|
|
TRACE(2, "DebuggerClient::closeAllConnections\n");
|
|
for (unsigned int i = 0; i < m_machines.size(); i++) {
|
|
m_machines[i]->m_thrift.close();
|
|
}
|
|
}
|
|
|
|
bool DebuggerClient::isLocal() {
|
|
TRACE(2, "DebuggerClient::isLocal\n");
|
|
return !isApiMode() && m_machines[0] == m_machine;
|
|
}
|
|
|
|
bool DebuggerClient::connect(const std::string &host, int port) {
|
|
TRACE(2, "DebuggerClient::connect\n");
|
|
assert(isApiMode() ||
|
|
(!m_machines.empty() && m_machines[0]->m_name == LocalPrompt));
|
|
// First check for an existing connect, and reuse that.
|
|
for (unsigned int i = 1; i < m_machines.size(); i++) {
|
|
if (f_gethostbyname(m_machines[i]->m_name) ==
|
|
f_gethostbyname(host)) {
|
|
switchMachine(m_machines[i]);
|
|
return false;
|
|
}
|
|
}
|
|
return connectRemote(host, port);
|
|
}
|
|
|
|
bool DebuggerClient::connectRPC(const std::string &host, int port) {
|
|
TRACE(2, "DebuggerClient::connectRPC\n");
|
|
assert(!m_machines.empty());
|
|
DMachineInfoPtr local = m_machines[0];
|
|
assert(local->m_name == LocalPrompt);
|
|
local->m_rpcHost = host;
|
|
local->m_rpcPort = port;
|
|
switchMachine(local);
|
|
m_rpcHost = "rpc:" + host;
|
|
return !local->m_interrupting;
|
|
}
|
|
|
|
bool DebuggerClient::disconnect() {
|
|
TRACE(2, "DebuggerClient::disconnect\n");
|
|
assert(!m_machines.empty());
|
|
DMachineInfoPtr local = m_machines[0];
|
|
assert(local->m_name == LocalPrompt);
|
|
local->m_rpcHost.clear();
|
|
local->m_rpcPort = 0;
|
|
switchMachine(local);
|
|
return !local->m_interrupting;
|
|
}
|
|
|
|
void DebuggerClient::switchMachine(DMachineInfoPtr machine) {
|
|
TRACE(2, "DebuggerClient::switchMachine\n");
|
|
m_rpcHost.clear();
|
|
machine->m_initialized = false; // even if m_machine == machine
|
|
|
|
if (m_machine != machine) {
|
|
m_machine = machine;
|
|
m_sandboxes.clear();
|
|
m_threads.clear();
|
|
m_threadId = 0;
|
|
m_breakpoint.reset();
|
|
m_matched.clear();
|
|
m_listFile.clear();
|
|
m_listLine = 0;
|
|
m_listLineFocus = 0;
|
|
m_stacktrace.reset();
|
|
m_frame = 0;
|
|
}
|
|
}
|
|
|
|
SmartPtr<Socket> DebuggerClient::connectLocal() {
|
|
TRACE(2, "DebuggerClient::connectLocal\n");
|
|
int fds[2];
|
|
if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) != 0) {
|
|
throw Exception("unable to create socket pair for local debugging");
|
|
}
|
|
SmartPtr<Socket> socket1(new Socket(fds[0], AF_UNIX));
|
|
SmartPtr<Socket> socket2(new Socket(fds[1], AF_UNIX));
|
|
|
|
socket1->unregister();
|
|
socket2->unregister();
|
|
DMachineInfoPtr machine(new DMachineInfo());
|
|
machine->m_sandboxAttached = true;
|
|
machine->m_name = LocalPrompt;
|
|
machine->m_thrift.create(socket1);
|
|
assert(m_machines.empty());
|
|
m_machines.push_back(machine);
|
|
switchMachine(machine);
|
|
return socket2;
|
|
}
|
|
|
|
bool DebuggerClient::connectRemote(const std::string &host, int port) {
|
|
TRACE(2, "DebuggerClient::connectRemote\n");
|
|
if (port <= 0) {
|
|
port = RuntimeOption::DebuggerServerPort;
|
|
}
|
|
info("Connecting to %s:%d...", host.c_str(), port);
|
|
Socket *sock = new Socket(socket(PF_INET, SOCK_STREAM, 0), PF_INET,
|
|
host.c_str(), port);
|
|
// Ensure the socket is not swept---it is cached across requests in
|
|
// API mode, and in client mode we expect to destruct it ourselves
|
|
// when ~DebuggerClient runs.
|
|
sock->unregister();
|
|
Object obj(sock); // Destroy sock if we don't connect.
|
|
if (f_socket_connect(sock, String(host), port)) {
|
|
DMachineInfoPtr machine(new DMachineInfo());
|
|
machine->m_name = host;
|
|
machine->m_port = port;
|
|
machine->m_thrift.create(SmartPtr<Socket>(sock));
|
|
m_machines.push_back(machine);
|
|
switchMachine(machine);
|
|
return true;
|
|
}
|
|
error("Unable to connect to %s:%d.", host.c_str(), port);
|
|
return false;
|
|
}
|
|
|
|
bool DebuggerClient::reconnect() {
|
|
TRACE(2, "DebuggerClient::reconnect\n");
|
|
assert(m_machine);
|
|
string &host = m_machine->m_name;
|
|
int port = m_machine->m_port;
|
|
if (port) {
|
|
info("Re-connecting to %s:%d...", host.c_str(), port);
|
|
m_machine->m_thrift.close(); // Close the old socket, it may still be open.
|
|
Socket *sock = new Socket(socket(PF_INET, SOCK_STREAM, 0), PF_INET,
|
|
host.c_str(), port);
|
|
sock->unregister();
|
|
Object obj(sock); // Destroy sock if we don't connect.
|
|
if (f_socket_connect(sock, String(host), port)) {
|
|
for (unsigned int i = 0; i < m_machines.size(); i++) {
|
|
if (m_machines[i] == m_machine) {
|
|
m_machines.erase(m_machines.begin() + i);
|
|
break;
|
|
}
|
|
}
|
|
DMachineInfoPtr machine(new DMachineInfo());
|
|
machine->m_name = host;
|
|
machine->m_port = port;
|
|
machine->m_thrift.create(SmartPtr<Socket>(sock));
|
|
m_machines.push_back(machine);
|
|
switchMachine(machine);
|
|
return true;
|
|
}
|
|
error("Still unable to connect to %s:%d.", host.c_str(), port);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::string DebuggerClient::getPrompt() {
|
|
TRACE(2, "DebuggerClient::getPrompt\n");
|
|
if (NoPrompt || !RuntimeOption::EnableDebuggerPrompt) {
|
|
return "";
|
|
}
|
|
string *name = &m_machine->m_name;
|
|
if (!m_rpcHost.empty()) {
|
|
name = &m_rpcHost;
|
|
}
|
|
if (m_inputState == TakingCode) {
|
|
string prompt = " ";
|
|
for (unsigned int i = 2; i < name->size() + 2; i++) {
|
|
prompt += '.';
|
|
}
|
|
prompt += ' ';
|
|
return prompt;
|
|
}
|
|
return *name + "> ";
|
|
}
|
|
|
|
void DebuggerClient::init(const DebuggerClientOptions &options) {
|
|
TRACE(2, "DebuggerClient::init\n");
|
|
m_options = options;
|
|
|
|
if (isApiMode()) {
|
|
if (options.user.empty()) {
|
|
return;
|
|
}
|
|
m_configFileName = options.configFName;
|
|
if (m_configFileName.empty()) {
|
|
m_configFileName = HomePrefix + "/" + options.user + "/" +
|
|
ConfigFileName;
|
|
}
|
|
} else {
|
|
if (!options.configFName.empty()) {
|
|
m_configFileName = options.configFName;
|
|
}
|
|
m_options.user = Process::GetCurrentUser();
|
|
}
|
|
|
|
usageLog("init");
|
|
|
|
loadConfig();
|
|
|
|
if (!options.cmds.empty()) {
|
|
RuntimeOption::EnableDebuggerColor = false;
|
|
RuntimeOption::EnableDebuggerPrompt = false;
|
|
s_use_utf8 = false;
|
|
}
|
|
|
|
if (options.apiMode) {
|
|
RuntimeOption::EnableDebuggerColor = false;
|
|
RuntimeOption::EnableDebuggerPrompt = false;
|
|
m_outputBuf.clear();
|
|
}
|
|
|
|
if (UseColor && RuntimeOption::EnableDebuggerColor) Debugger::SetTextColors();
|
|
|
|
if (!NoPrompt && RuntimeOption::EnableDebuggerPrompt) {
|
|
info("Welcome to HipHop Debugger!");
|
|
info("Type \"help\" or \"?\" for a complete list of commands.\n");
|
|
}
|
|
|
|
if (!options.host.empty()) {
|
|
connectRemote(options.host, options.port);
|
|
}
|
|
}
|
|
|
|
void DebuggerClient::start(const DebuggerClientOptions &options) {
|
|
TRACE(2, "DebuggerClient::start\n");
|
|
init(options);
|
|
m_mainThread.start();
|
|
}
|
|
|
|
void DebuggerClient::stop() {
|
|
TRACE(2, "DebuggerClient::stop\n");
|
|
m_stopped = true;
|
|
m_mainThread.waitForEnd();
|
|
}
|
|
|
|
// Executed by m_mainThread to run the command-line debugger.
|
|
void DebuggerClient::run() {
|
|
TRACE(2, "DebuggerClient::run\n");
|
|
StackTraceNoHeap::AddExtraLogging("IsDebugger", "True");
|
|
// Make sure we don't run the interface thread for API mode
|
|
assert(!isApiMode());
|
|
|
|
ReadlineApp app;
|
|
TRACE(3, "DebuggerClient::run, about to call playMacro\n");
|
|
playMacro("startup");
|
|
|
|
if (!m_options.cmds.empty()) {
|
|
m_macroPlaying = MacroPtr(new Macro());
|
|
m_macroPlaying->m_cmds = m_options.cmds;
|
|
m_macroPlaying->m_cmds.push_back("q");
|
|
m_macroPlaying->m_index = 0;
|
|
}
|
|
|
|
hphp_session_init();
|
|
ExecutionContext *context = hphp_context_init();
|
|
if (m_options.extension.empty()) {
|
|
hphp_invoke_simple("", true); // warm-up only
|
|
} else {
|
|
hphp_invoke_simple(m_options.extension);
|
|
}
|
|
while (true) {
|
|
bool reconnect = false;
|
|
try {
|
|
runImpl();
|
|
} catch (DebuggerServerLostException &e) {
|
|
// Loss of connection
|
|
TRACE_RB(1, "DebuggerClient::run: server lost exception\n");
|
|
usageLog(m_commandCanonical, "DebuggerServerLostException");
|
|
reconnect = true;
|
|
} catch (DebuggerProtocolException &e) {
|
|
// Bad or unexpected data. Give reconnect a shot, it could help...
|
|
TRACE_RB(1, "DebuggerClient::run: protocol exception\n");
|
|
usageLog(m_commandCanonical, "DebuggerProtocolException");
|
|
reconnect = true;
|
|
} catch (...) {
|
|
TRACE_RB(1, "DebuggerClient::run: unknown exception\n");
|
|
usageLog(m_commandCanonical, "UnknownException");
|
|
Logger::Error("Unhandled exception, exiting.");
|
|
}
|
|
// Note: it's silly to try to reconnect when stopping, or if we have a
|
|
// problem while quitting.
|
|
if (reconnect && !m_stopped && (m_commandCanonical != "quit")) {
|
|
if (DebuggerClient::reconnect()) continue;
|
|
Logger::Error("Unable to reconnect to server, exiting.");
|
|
}
|
|
break;
|
|
}
|
|
// Closing all proxy connections will force the local proxy to pop out of
|
|
// it's wait, and eventually exit the main thread.
|
|
closeAllConnections();
|
|
hphp_context_exit(context, false);
|
|
hphp_session_exit();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// auto-complete
|
|
|
|
void DebuggerClient::updateLiveLists() {
|
|
TRACE(2, "DebuggerClient::updateLiveLists\n");
|
|
ReadlineWaitCursor waitCursor;
|
|
CmdInfo::UpdateLiveLists(*this);
|
|
m_acLiveListsDirty = false;
|
|
}
|
|
|
|
void DebuggerClient::promptFunctionPrototype() {
|
|
TRACE(2, "DebuggerClient::promptFunctionPrototype\n");
|
|
if (m_acProtoTypePrompted) return;
|
|
m_acProtoTypePrompted = true;
|
|
|
|
const char *p0 = rl_line_buffer;
|
|
int len = strlen(p0);
|
|
if (len < 2) return;
|
|
|
|
const char *pLast = p0 + len - 1;
|
|
while (pLast > p0 && isspace(*pLast)) --pLast;
|
|
if (pLast == p0 || *pLast-- != '(') return;
|
|
while (pLast > p0 && isspace(*pLast)) --pLast;
|
|
|
|
const char *p = pLast;
|
|
while (p >= p0 && (isalnum(*p) || *p == '_')) --p;
|
|
if (p == pLast) return;
|
|
|
|
string cls;
|
|
string func(p + 1, pLast - p);
|
|
if (p > p0 && *p-- == ':' && *p-- == ':') {
|
|
pLast = p;
|
|
while (p >= p0 && (isalnum(*p) || *p == '_')) --p;
|
|
if (pLast > p) {
|
|
cls = string(p + 1, pLast - p);
|
|
}
|
|
}
|
|
|
|
String output = highlight_code(CmdInfo::GetProtoType(*this, cls, func));
|
|
print("\n%s", output.data());
|
|
rl_forced_update_display();
|
|
}
|
|
|
|
bool DebuggerClient::setCompletion(const char *text, int start, int end) {
|
|
TRACE(2, "DebuggerClient::setCompletion\n");
|
|
if (m_inputState == TakingCommand) {
|
|
parseCommand(rl_line_buffer);
|
|
if (*text) {
|
|
if (!m_args.empty()) {
|
|
m_args.resize(m_args.size() - 1);
|
|
} else {
|
|
m_command.clear();
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void DebuggerClient::addCompletion(AutoComplete type) {
|
|
TRACE(2, "DebuggerClient::addCompletion(AutoComplete type)\n");
|
|
if (type < 0 || type >= AutoCompleteCount) {
|
|
Logger::Error("Invalid auto completion enum: %d", type);
|
|
return;
|
|
}
|
|
|
|
if (type == AutoCompleteCode) {
|
|
addCompletion(AutoCompleteVariables);
|
|
addCompletion(AutoCompleteConstants);
|
|
addCompletion(AutoCompleteClasses);
|
|
addCompletion(AutoCompleteFunctions);
|
|
addCompletion(AutoCompleteClassMethods);
|
|
addCompletion(AutoCompleteClassProperties);
|
|
addCompletion(AutoCompleteClassConstants);
|
|
addCompletion(AutoCompleteKeyword);
|
|
} else if (type == AutoCompleteKeyword) {
|
|
addCompletion(PHP_KEYWORDS);
|
|
} else {
|
|
m_acLists.push_back((const char **)type);
|
|
}
|
|
|
|
if (type == AutoCompleteFunctions || type == AutoCompleteClassMethods) {
|
|
rl_completion_suppress_append = 1;
|
|
promptFunctionPrototype();
|
|
}
|
|
}
|
|
|
|
void DebuggerClient::addCompletion(const char **list) {
|
|
TRACE(2, "DebuggerClient::addCompletion(const char **list)\n");
|
|
m_acLists.push_back(list);
|
|
}
|
|
|
|
void DebuggerClient::addCompletion(const char *name) {
|
|
TRACE(2, "DebuggerClient::addCompletion(const char *name)\n");
|
|
m_acStrings.push_back(name);
|
|
}
|
|
|
|
void DebuggerClient::addCompletion(const std::vector<std::string> &items) {
|
|
TRACE(2, "DebuggerClient::addCompletion(const std::vector<std::string>)\n");
|
|
m_acItems.insert(m_acItems.end(), items.begin(), items.end());
|
|
}
|
|
|
|
char *DebuggerClient::getCompletion(const std::vector<std::string> &items,
|
|
const char *text) {
|
|
TRACE(2, "DebuggerClient::getCompletion(const std::vector<std::string>\n");
|
|
while (++m_acPos < (int)items.size()) {
|
|
const char *p = items[m_acPos].c_str();
|
|
if (m_acLen == 0 || strncasecmp(p, text, m_acLen) == 0) {
|
|
return strdup(p);
|
|
}
|
|
}
|
|
m_acPos = -1;
|
|
return nullptr;
|
|
}
|
|
|
|
std::vector<std::string> DebuggerClient::getAllCompletions(
|
|
std::string const &text) {
|
|
TRACE(2, "DebuggerClient::getAllCompletions\n");
|
|
std::vector<std::string> res;
|
|
|
|
if (m_acLiveListsDirty) {
|
|
updateLiveLists();
|
|
}
|
|
|
|
for (int i = 0; i < AutoCompleteCount; ++i) {
|
|
const std::vector<std::string> &items = m_acLiveLists[i];
|
|
for (size_t j = 0; j < items.size(); ++j) {
|
|
const char *p = items[j].c_str();
|
|
if (strncasecmp(p, text.c_str(), text.length()) == 0) {
|
|
res.push_back(std::string(p));
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
char *DebuggerClient::getCompletion(const std::vector<const char *> &items,
|
|
const char *text) {
|
|
TRACE(2, "DebuggerClient::getCompletion(const std::vector<const char *>\n");
|
|
while (++m_acPos < (int)items.size()) {
|
|
const char *p = items[m_acPos];
|
|
if (m_acLen == 0 || strncasecmp(p, text, m_acLen) == 0) {
|
|
return strdup(p);
|
|
}
|
|
}
|
|
m_acPos = -1;
|
|
return nullptr;
|
|
}
|
|
|
|
static char first_non_whitespace(const char *s) {
|
|
TRACE(2, "DebuggerClient::first_non_whitespace\n");
|
|
while (*s && isspace(*s)) s++;
|
|
return *s;
|
|
}
|
|
|
|
char *DebuggerClient::getCompletion(const char *text, int state) {
|
|
TRACE(2, "DebuggerClient::getCompletion\n");
|
|
if (state == 0) {
|
|
m_acLen = strlen(text);
|
|
m_acIndex = 0;
|
|
m_acPos = -1;
|
|
m_acLists.clear();
|
|
m_acStrings.clear();
|
|
m_acItems.clear();
|
|
m_acProtoTypePrompted = false;
|
|
if (m_inputState == TakingCommand) {
|
|
switch (first_non_whitespace(rl_line_buffer)) {
|
|
case '<':
|
|
if (strncasecmp(m_command.substr(0, 5).c_str(), "<?php", 5)) {
|
|
addCompletion("<?php");
|
|
break;
|
|
}
|
|
case '@':
|
|
case '=':
|
|
case '$': {
|
|
addCompletion(AutoCompleteCode);
|
|
break;
|
|
}
|
|
default: {
|
|
if (m_command.empty()) {
|
|
addCompletion(GetCommands());
|
|
addCompletion("@");
|
|
addCompletion("=");
|
|
addCompletion("<?php");
|
|
addCompletion("?>");
|
|
} else {
|
|
DebuggerCommand *cmd = createCommand();
|
|
if (cmd) {
|
|
if (cmd->is(DebuggerCommand::KindOfRun)) playMacro("startup");
|
|
DebuggerCommandPtr deleter(cmd);
|
|
cmd->list(*this);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
assert(m_inputState == TakingCode);
|
|
if (!*rl_line_buffer) {
|
|
addCompletion("?>"); // so we tab, we're done
|
|
} else {
|
|
addCompletion(AutoCompleteCode);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (; m_acIndex < (int)m_acLists.size(); m_acIndex++) {
|
|
const char **list = m_acLists[m_acIndex];
|
|
if ((int64_t)list == AutoCompleteFileNames) {
|
|
char *p = rl_filename_completion_function(text, ++m_acPos);
|
|
if (p) return p;
|
|
} else if ((int64_t)list >= 0 && (int64_t)list < AutoCompleteCount) {
|
|
if (m_acLiveListsDirty) {
|
|
updateLiveLists();
|
|
assert(!m_acLiveListsDirty);
|
|
}
|
|
char *p = getCompletion(m_acLiveLists[(int64_t)list], text);
|
|
if (p) return p;
|
|
} else {
|
|
for (const char *p = list[++m_acPos]; p; p = list[++m_acPos]) {
|
|
if (m_acLen == 0 || strncasecmp(p, text, m_acLen) == 0) {
|
|
return strdup(p);
|
|
}
|
|
}
|
|
}
|
|
m_acPos = -1;
|
|
}
|
|
|
|
char *p = getCompletion(m_acStrings, text);
|
|
if (p) return p;
|
|
|
|
return getCompletion(m_acItems, text);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// main
|
|
|
|
// Execute the initial connection protocol with a machine. A connection has been
|
|
// established, and the proxy has responded with an interrupt giving us initial
|
|
// control. Send breakpoints to the server, and then attach to the sandbox
|
|
// if necessary. If we attach to a sandbox, then the process is off and running
|
|
// again (CmdMachine continues execution on a successful attach) so return false
|
|
// to indicate that a client should wait for another interrupt before attempting
|
|
// further communication. Returns true if the protocol is complete and the
|
|
// machine is at an interrupt.
|
|
bool DebuggerClient::initializeMachine() {
|
|
TRACE(2, "DebuggerClient::initializeMachine\n");
|
|
// set/clear intercept for RPC thread
|
|
if (!m_machines.empty() && m_machine == m_machines[0]) {
|
|
CmdMachine::UpdateIntercept(*this, m_machine->m_rpcHost,
|
|
m_machine->m_rpcPort);
|
|
}
|
|
|
|
// upload breakpoints
|
|
if (!m_breakpoints.empty()) {
|
|
info("Updating breakpoints...");
|
|
CmdBreak::SendClientBreakpointListToServer(*this);
|
|
}
|
|
|
|
// attaching to default sandbox
|
|
int waitForSandbox = false;
|
|
if (!m_machine->m_sandboxAttached) {
|
|
const char *user = m_options.user.empty() ?
|
|
nullptr : m_options.user.c_str();
|
|
m_machine->m_sandboxAttached = (waitForSandbox =
|
|
CmdMachine::AttachSandbox(*this, user, m_options.sandbox.c_str()));
|
|
if (!m_machine->m_sandboxAttached) {
|
|
Logger::Error("Unable to communicate with default sandbox.");
|
|
}
|
|
}
|
|
|
|
m_machine->m_initialized = true;
|
|
if (!isApiMode() && waitForSandbox) {
|
|
// Return false to wait for next interrupt from server
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Only used in API mode, to listen for another interupt from the machine.
|
|
DebuggerCommandPtr DebuggerClient::waitForNextInterrupt() {
|
|
TRACE(2, "DebuggerClient::waitForNextInterrupt\n");
|
|
assert(isApiMode());
|
|
const char *func = "DebuggerClient::waitForNextInterrupt()";
|
|
m_machine->m_interrupting = false; // Machine is running again.
|
|
m_inputState = TakingInterrupt;
|
|
while (!m_stopped) {
|
|
DebuggerCommandPtr cmd;
|
|
if (DebuggerCommand::Receive(m_machine->m_thrift, cmd, func)) {
|
|
if (!cmd) {
|
|
Logger::Error("Unable to communicate with server.");
|
|
return DebuggerCommandPtr();
|
|
}
|
|
if (cmd->is(DebuggerCommand::KindOfSignal)) {
|
|
cmd->onClient(*this);
|
|
continue;
|
|
}
|
|
if (!cmd->is(DebuggerCommand::KindOfInterrupt)) {
|
|
int expected = DebuggerCommand::KindOfInterrupt;
|
|
if (!m_pendingCommands.empty() &&
|
|
cmd->is((DebuggerCommand::Type)m_pendingCommands.back())) {
|
|
m_pendingCommands.pop_back();
|
|
// found a match, fall through
|
|
} else {
|
|
Logger::Error("Bad cmd type: %d expecting %d", cmd->getType(),
|
|
expected);
|
|
return DebuggerCommandPtr();
|
|
}
|
|
} else {
|
|
CmdInterruptPtr intr = dynamic_pointer_cast<CmdInterrupt>(cmd);
|
|
Debugger::UsageLogInterrupt(getUsageMode(), *intr.get());
|
|
}
|
|
m_sigTime = 0;
|
|
m_machine->m_interrupting = true;
|
|
setClientState(StateReadyForCommand);
|
|
m_inputState = TakingCommand;
|
|
return cmd;
|
|
}
|
|
}
|
|
return DebuggerCommandPtr();
|
|
}
|
|
|
|
// The main execution loop of DebuggerClient. This waits for interrupts from
|
|
// the server (and responds to polls for signals). On interrupt, it presents
|
|
// a command prompt, and continues pumping interrupts when a command lets the
|
|
// machine run again.
|
|
// When this returns, the command line client will exit.
|
|
void DebuggerClient::runImpl() {
|
|
TRACE(2, "DebuggerClient::runImpl\n");
|
|
assert(!isApiMode());
|
|
const char *func = "Main client loop";
|
|
|
|
DebuggerCommandPtr cmd;
|
|
try {
|
|
while (!m_stopped) {
|
|
// The machine should be running at this point, otherwise it will never
|
|
// send us anything and we'll wait forever.
|
|
assert(!m_machine->m_interrupting);
|
|
if (DebuggerCommand::Receive(m_machine->m_thrift, cmd, func)) {
|
|
if (!cmd) {
|
|
Logger::Error("Unable to communicate with server. Server's down?");
|
|
throw DebuggerServerLostException();
|
|
}
|
|
if (cmd->is(DebuggerCommand::KindOfSignal)) {
|
|
cmd->onClient(*this);
|
|
continue;
|
|
}
|
|
if (!cmd->is(DebuggerCommand::KindOfInterrupt)) {
|
|
Logger::Error("Received bad cmd type %d, unable to communicate "
|
|
"with server.", cmd->getType());
|
|
throw DebuggerServerLostException();
|
|
}
|
|
m_sigTime = 0;
|
|
CmdInterruptPtr intr = dynamic_pointer_cast<CmdInterrupt>(cmd);
|
|
Debugger::UsageLogInterrupt(getUsageMode(), *intr.get());
|
|
cmd->onClient(*this);
|
|
|
|
// When we make a new connection to a machine, we have to wait for it
|
|
// to interrupt us before we can send it any messages. This is our
|
|
// opportunity to complete the connection and make it ready to use.
|
|
if (!m_machine->m_initialized) {
|
|
if (!initializeMachine()) {
|
|
// False means the machine is running and we need to wait for
|
|
// another interrupt.
|
|
continue;
|
|
}
|
|
}
|
|
// Execution has been interrupted, so go ahead and give the user
|
|
// the prompt back.
|
|
m_machine->m_interrupting = true; // Machine is stopped
|
|
m_inputState = TakingCommand;
|
|
console(); // Prompt loop
|
|
m_inputState = TakingInterrupt;
|
|
m_machine->m_interrupting = false; // Machine is running again.
|
|
}
|
|
}
|
|
} catch (DebuggerClientExitException &e) { /* normal exit */ }
|
|
}
|
|
|
|
// Execute the interactive command loop for the debugger client. This will
|
|
// present the prompt, wait for user input, and execute commands, then rinse
|
|
// and repeat. The loop terminates when a command is executed that causes the
|
|
// machine to resume execution, or which should cause the client to exit.
|
|
// This function is only entered when the machine being debugged is paused.
|
|
//
|
|
// If this function returns it means the process is running again.
|
|
// NB: exceptions derrived from DebuggerException or DebuggerClientExeption
|
|
// indicate the machine remains paused.
|
|
void DebuggerClient::console() {
|
|
TRACE(2, "DebuggerClient::console\n");
|
|
while (true) {
|
|
const char *line = nullptr;
|
|
|
|
string holder;
|
|
if (m_macroPlaying) {
|
|
if (m_macroPlaying->m_index < m_macroPlaying->m_cmds.size()) {
|
|
holder = m_macroPlaying->m_cmds[m_macroPlaying->m_index++];
|
|
line = holder.c_str();
|
|
} else {
|
|
m_macroPlaying.reset();
|
|
}
|
|
}
|
|
if (line == nullptr) {
|
|
line = readline(getPrompt().c_str());
|
|
if (line == nullptr) {
|
|
// treat ^D as quit
|
|
print("quit");
|
|
line = "quit";
|
|
}
|
|
} else if (!NoPrompt && RuntimeOption::EnableDebuggerPrompt) {
|
|
print("%s%s", getPrompt().c_str(), line);
|
|
}
|
|
if (*line && !m_macroPlaying) {
|
|
// even if line is bad command, we still want to remember it, so
|
|
// people can go back and fix typos
|
|
add_history(line);
|
|
}
|
|
|
|
AdjustScreenMetrics();
|
|
|
|
if (*line) {
|
|
if (parse(line)) {
|
|
try {
|
|
record(line);
|
|
m_prevCmd = m_command;
|
|
if (!process()) {
|
|
error("command \"" + m_command + "\" not found");
|
|
m_command.clear();
|
|
}
|
|
} catch (DebuggerConsoleExitException &e) {
|
|
return;
|
|
}
|
|
}
|
|
} else if (m_inputState == TakingCommand) {
|
|
switch (m_prevCmd[0]) {
|
|
case 'l': // list
|
|
m_args.clear(); // as if just "list"
|
|
// fall through
|
|
case 'c': // continue
|
|
case 's': // step
|
|
case 'n': // next
|
|
case 'o': // out
|
|
try {
|
|
record(line);
|
|
m_command = m_prevCmd;
|
|
process(); // replay the same command
|
|
} catch (DebuggerConsoleExitException &e) {
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
not_reached();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// output functions
|
|
|
|
String DebuggerClient::getPrintString() {
|
|
TRACE(2, "DebuggerClient::getPrintString\n");
|
|
String s(m_outputBuf); // makes a copy;
|
|
m_outputBuf.clear();
|
|
return s;
|
|
}
|
|
|
|
static const StaticString
|
|
s_output_type("output_type"),
|
|
s_file("file"),
|
|
s_line("line"),
|
|
s_line_no("line_no"),
|
|
s_watch_values("watch_values"),
|
|
s_stacktrace("stacktrace"),
|
|
s_frame("frame"),
|
|
s_values("values"),
|
|
s_text("text"),
|
|
s_invalid("invalid"),
|
|
s_cmd("cmd"),
|
|
s_code_loc("code_loc");
|
|
|
|
Array DebuggerClient::getOutputArray() {
|
|
TRACE(2, "DebuggerClient::getOutputArray\n");
|
|
Array ret;
|
|
switch (m_outputType) {
|
|
case OTCodeLoc:
|
|
ret.set(s_output_type, s_code_loc);
|
|
ret.set(s_file, m_otFile);
|
|
ret.set(s_line_no, m_otLineNo);
|
|
ret.set(s_watch_values, m_otValues);
|
|
break;
|
|
case OTStacktrace:
|
|
ret.set(s_output_type, s_stacktrace);
|
|
ret.set(s_stacktrace, m_stacktrace);
|
|
ret.set(s_frame, m_frame);
|
|
break;
|
|
case OTValues:
|
|
ret.set(s_output_type, s_values);
|
|
ret.set(s_values, m_otValues);
|
|
break;
|
|
case OTText:
|
|
ret.set(s_output_type, s_text);
|
|
break;
|
|
default:
|
|
ret.set(s_output_type, s_invalid);
|
|
}
|
|
ret.set(s_cmd, m_command);
|
|
ret.set(s_text, getPrintString());
|
|
return ret;
|
|
}
|
|
|
|
//
|
|
// Called when a breakpoint is reached, to produce the console
|
|
// spew showing the code around the breakpoint.
|
|
//
|
|
void DebuggerClient::shortCode(BreakPointInfoPtr bp) {
|
|
TRACE(2, "DebuggerClient::shortCode\n");
|
|
if (bp && !bp->m_file.empty() && bp->m_line1) {
|
|
Variant source = CmdList::GetSourceFile(*this, bp->m_file);
|
|
if (source.isString()) {
|
|
// Line and column where highlight should start and end
|
|
int beginHighlightLine = bp->m_line1;
|
|
int beginHighlightColumn = bp->m_char1;
|
|
int endHighlightLine = bp->m_line2;
|
|
int endHighlightColumn = bp->m_char2;
|
|
// Lines where source listing should start and end
|
|
int firstLine = std::max(beginHighlightLine - 1, 1);
|
|
int lastLine = endHighlightLine + 1;
|
|
int maxLines = getDebuggerClientMaxCodeLines();
|
|
|
|
// If MaxCodeLines == 0: don't spew any code after a [s]tep or [n]ext
|
|
// command.
|
|
if (maxLines == 0) {
|
|
return;
|
|
}
|
|
|
|
// If MaxCodeLines > 0: limit spew to a maximum of # lines.
|
|
if (maxLines > 0) {
|
|
int numHighlightLines = endHighlightLine - beginHighlightLine + 1;
|
|
if (numHighlightLines > maxLines) {
|
|
// If there are too many highlight lines, truncate spew
|
|
// by setting lastLine ...
|
|
lastLine = beginHighlightLine + maxLines - 1;
|
|
// ... and set endHighlightLine/Column so that it is just past the end
|
|
// of the spew, and all code up to the truncation will be highlighted.
|
|
endHighlightLine = lastLine + 1;
|
|
endHighlightColumn = 1;
|
|
}
|
|
}
|
|
|
|
code(source, firstLine, lastLine,
|
|
beginHighlightLine,
|
|
beginHighlightColumn,
|
|
endHighlightLine,
|
|
endHighlightColumn);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool DebuggerClient::code(CStrRef source, int line1 /*= 0*/, int line2 /*= 0*/,
|
|
int lineFocus0 /* = 0 */, int charFocus0 /* = 0 */,
|
|
int lineFocus1 /* = 0 */, int charFocus1 /* = 0 */) {
|
|
TRACE(2, "DebuggerClient::code\n");
|
|
if (line1 == 0 && line2 == 0) {
|
|
String highlighted;
|
|
if (isApiMode()) {
|
|
highlighted = source;
|
|
} else {
|
|
highlighted = highlight_code(source, 0, lineFocus0, charFocus0,
|
|
lineFocus1, charFocus1);
|
|
}
|
|
if (!highlighted.empty()) {
|
|
print(highlighted);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
String highlighted;
|
|
if (isApiMode()) {
|
|
highlighted = source;
|
|
} else {
|
|
highlighted = highlight_php(source, 1, lineFocus0, charFocus0,
|
|
lineFocus1, charFocus1);
|
|
}
|
|
int line = 1;
|
|
const char *begin = highlighted.data();
|
|
StringBuffer sb;
|
|
for (const char *p = begin; *p; p++) {
|
|
if (*p == '\n') {
|
|
if (line >= line1) {
|
|
sb.append(begin, p - begin + 1);
|
|
}
|
|
if (++line > line2) break;
|
|
begin = p + 1;
|
|
}
|
|
}
|
|
if (!sb.empty()) {
|
|
print("%s%s", sb.data(),
|
|
UseColor && RuntimeOption::EnableDebuggerColor ? ANSI_COLOR_END : "\0");
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
char DebuggerClient::ask(const char *fmt, ...) {
|
|
TRACE(2, "DebuggerClient::ask\n");
|
|
assert(!isApiMode());
|
|
string msg;
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
Util::string_vsnprintf(msg, fmt, ap); va_end(ap);
|
|
if (UseColor && InfoColor && RuntimeOption::EnableDebuggerColor) {
|
|
msg = InfoColor + msg + ANSI_COLOR_END;
|
|
}
|
|
fwrite(msg.data(), 1, msg.length(), stdout);
|
|
fflush(stdout);
|
|
return tolower(getchar());
|
|
}
|
|
|
|
#define FWRITE(ptr, size, nmemb, stream) \
|
|
do { \
|
|
if (isApiMode()) { \
|
|
m_outputBuf.append(ptr, size * nmemb); \
|
|
} \
|
|
\
|
|
/* LogFile debugger setting */ \
|
|
FILE *f = getLogFileHandler(); \
|
|
if (f != nullptr) { \
|
|
fwrite(ptr, size, nmemb, f); \
|
|
} \
|
|
\
|
|
/* For debugging, still output to stdout */ \
|
|
fwrite(ptr, size, nmemb, stream); \
|
|
} while (0) \
|
|
|
|
void DebuggerClient::print(const char *fmt, ...) {
|
|
TRACE(2, "DebuggerClient::print(const char *fmt, ...)\n");
|
|
string msg;
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
Util::string_vsnprintf(msg, fmt, ap); va_end(ap);
|
|
print(msg);
|
|
}
|
|
|
|
void DebuggerClient::print(const std::string &s) {
|
|
TRACE(2, "DebuggerClient::print(const std::string &s)\n");
|
|
FWRITE(s.data(), 1, s.length(), stdout);
|
|
FWRITE("\n", 1, 1, stdout);
|
|
fflush(stdout);
|
|
}
|
|
|
|
void DebuggerClient::print(CStrRef msg) {
|
|
TRACE(2, "DebuggerClient::print(CStrRef msg)\n");
|
|
FWRITE(msg.data(), 1, msg.length(), stdout);
|
|
FWRITE("\n", 1, 1, stdout);
|
|
fflush(stdout);
|
|
}
|
|
|
|
#define IMPLEMENT_COLOR_OUTPUT(name, where, color) \
|
|
void DebuggerClient::name(CStrRef msg) { \
|
|
if (UseColor && color && RuntimeOption::EnableDebuggerColor) { \
|
|
FWRITE(color, 1, strlen(color), where); \
|
|
} \
|
|
FWRITE(msg.data(), 1, msg.length(), where); \
|
|
if (UseColor && color && RuntimeOption::EnableDebuggerColor) { \
|
|
FWRITE(ANSI_COLOR_END, 1, strlen(ANSI_COLOR_END), where); \
|
|
} \
|
|
FWRITE("\n", 1, 1, where); \
|
|
fflush(where); \
|
|
} \
|
|
void DebuggerClient::name(const char *fmt, ...) { \
|
|
string msg; \
|
|
va_list ap; \
|
|
va_start(ap, fmt); \
|
|
Util::string_vsnprintf(msg, fmt, ap); va_end(ap); \
|
|
name(msg); \
|
|
} \
|
|
void DebuggerClient::name(const std::string &msg) { \
|
|
name(String(msg.data(), msg.length(), AttachLiteral)); \
|
|
} \
|
|
|
|
IMPLEMENT_COLOR_OUTPUT(help, stdout, HelpColor);
|
|
IMPLEMENT_COLOR_OUTPUT(info, stdout, InfoColor);
|
|
IMPLEMENT_COLOR_OUTPUT(output, stdout, OutputColor);
|
|
IMPLEMENT_COLOR_OUTPUT(error, stderr, ErrorColor);
|
|
|
|
#undef FWRITE
|
|
#undef IMPLEMENT_COLOR_OUTPUT
|
|
|
|
string DebuggerClient::wrap(const std::string &s) {
|
|
TRACE(2, "DebuggerClient::wrap\n");
|
|
String ret = StringUtil::WordWrap(String(s.c_str(), s.size(), AttachLiteral),
|
|
LineWidth - 4, "\n", true);
|
|
return string(ret.data(), ret.size());
|
|
}
|
|
|
|
void DebuggerClient::helpTitle(const char *title) {
|
|
TRACE(2, "DebuggerClient::helpTitle\n");
|
|
help(FormatTitle(title));
|
|
}
|
|
|
|
void DebuggerClient::helpCmds(const char *cmd, const char *desc, ...) {
|
|
TRACE(2, "DebuggerClient::helpCmds(const char *cmd, const char *desc,...)\n");
|
|
std::vector<const char *> cmds;
|
|
cmds.push_back(cmd);
|
|
cmds.push_back(desc);
|
|
|
|
va_list ap;
|
|
va_start(ap, desc);
|
|
const char *s = va_arg(ap, const char *);
|
|
while (s) {
|
|
cmds.push_back(s);
|
|
s = va_arg(ap, const char *);
|
|
}
|
|
va_end(ap);
|
|
|
|
helpCmds(cmds);
|
|
}
|
|
|
|
void DebuggerClient::helpCmds(const std::vector<const char *> &cmds) {
|
|
TRACE(2, "DebuggerClient::helpCmds(const std::vector<const char *> &cmds)\n");
|
|
int left = 0; int right = 0;
|
|
for (unsigned int i = 0; i < cmds.size(); i++) {
|
|
int &width = (i % 2 ? right : left);
|
|
int len = strlen(cmds[i]);
|
|
if (width < len) width = len;
|
|
}
|
|
|
|
int margin = 8;
|
|
int leftMax = LineWidth / 3 - margin;
|
|
int rightMax = LineWidth * 3 / 3 - margin;
|
|
if (left > leftMax) left = leftMax;
|
|
if (right > rightMax) right = rightMax;
|
|
|
|
StringBuffer sb;
|
|
for (unsigned int i = 0; i < cmds.size() - 1; i += 2) {
|
|
String cmd(cmds[i], AttachLiteral);
|
|
String desc(cmds[i+1], AttachLiteral);
|
|
|
|
// two special formats
|
|
if (cmd.empty() && desc.empty()) {
|
|
sb.append("\n");
|
|
continue;
|
|
}
|
|
if (desc.empty()) {
|
|
sb.append(FormatTitle(cmd.data()));
|
|
sb.append("\n");
|
|
continue;
|
|
}
|
|
|
|
cmd = StringUtil::WordWrap(cmd, left, "\n", true);
|
|
desc = StringUtil::WordWrap(desc, right, "\n", true);
|
|
Array lines1 = StringUtil::Explode(cmd, "\n").toArray();
|
|
Array lines2 = StringUtil::Explode(desc, "\n").toArray();
|
|
for (int n = 0; n < lines1.size() || n < lines2.size(); n++) {
|
|
StringBuffer line;
|
|
line.append(" ");
|
|
if (n) line.append(" ");
|
|
line.append(StringUtil::Pad(lines1[n], leftMax));
|
|
if (n == 0) line.append(" ");
|
|
line.append(" ");
|
|
line.append(lines2[n].toString());
|
|
|
|
sb.append(StringUtil::Trim(line.detach(), StringUtil::TrimRight));
|
|
sb.append("\n");
|
|
}
|
|
}
|
|
|
|
help(sb.detach());
|
|
}
|
|
|
|
void DebuggerClient::helpBody(const std::string &s) {
|
|
TRACE(2, "DebuggerClient::helpBody\n");
|
|
help("");
|
|
help(wrap(s));
|
|
help("");
|
|
}
|
|
|
|
void DebuggerClient::helpSection(const std::string &s) {
|
|
TRACE(2, "DebuggerClient::helpSection\n");
|
|
help(wrap(s));
|
|
}
|
|
|
|
void DebuggerClient::tutorial(const char *text) {
|
|
TRACE(2, "DebuggerClient::tutorial\n");
|
|
if (m_tutorial < 0) return;
|
|
|
|
String ret = String(text).replace("\t", " ");
|
|
ret = StringUtil::WordWrap(ret, LineWidth - 4, "\n", true);
|
|
Array lines = StringUtil::Explode(ret, "\n").toArray();
|
|
|
|
StringBuffer sb;
|
|
String header = " Tutorial - '[h]elp [t]utorial off' to turn off ";
|
|
String hr = StringUtil::Repeat(BOX_H, LineWidth - 2);
|
|
|
|
sb.append(BOX_UL); sb.append(hr); sb.append(BOX_UR); sb.append("\n");
|
|
|
|
int wh = (LineWidth - 2 - header.size()) / 2;
|
|
sb.append(BOX_V);
|
|
sb.append(StringUtil::Repeat(" ", wh));
|
|
sb.append(header);
|
|
sb.append(StringUtil::Repeat(" ", wh));
|
|
sb.append(BOX_V);
|
|
sb.append("\n");
|
|
|
|
sb.append(BOX_VL); sb.append(hr); sb.append(BOX_VR); sb.append("\n");
|
|
for (ArrayIter iter(lines); iter; ++iter) {
|
|
sb.append(BOX_V); sb.append(' ');
|
|
sb.append(StringUtil::Pad(iter.second(), LineWidth - 4));
|
|
sb.append(' '); sb.append(BOX_V); sb.append("\n");
|
|
}
|
|
sb.append(BOX_LL); sb.append(hr); sb.append(BOX_LR); sb.append("\n");
|
|
|
|
String content = sb.detach();
|
|
|
|
if (m_tutorial == 0) {
|
|
String hash = StringUtil::MD5(content);
|
|
if (m_tutorialVisited.find(hash.data()) != m_tutorialVisited.end()) {
|
|
return;
|
|
}
|
|
m_tutorialVisited.insert(hash.data());
|
|
saveConfig();
|
|
}
|
|
|
|
help(content);
|
|
}
|
|
|
|
void DebuggerClient::setTutorial(int mode) {
|
|
TRACE(2, "DebuggerClient::setTutorial\n");
|
|
if (m_tutorial != mode) {
|
|
m_tutorial = mode;
|
|
m_tutorialVisited.clear();
|
|
saveConfig();
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// command processing
|
|
|
|
const char **DebuggerClient::GetCommands() {
|
|
static const char *cmds[] = {
|
|
"abort", "break", "continue", "down", "exception",
|
|
"frame", "global", "help", "info",
|
|
"konstant", "list", "machine", "next", "out",
|
|
"print", "quit", "run", "step", "thread",
|
|
"up", "variable", "where", "x", "y",
|
|
"zend", "!", "&",
|
|
nullptr
|
|
};
|
|
return cmds;
|
|
}
|
|
|
|
void DebuggerClient::shiftCommand() {
|
|
TRACE(2, "DebuggerClient::shiftCommand\n");
|
|
if (m_command.size() > 1) {
|
|
m_args.insert(m_args.begin(), m_command.substr(1));
|
|
m_argIdx.insert(m_argIdx.begin(), 1);
|
|
m_command = m_command.substr(0, 1);
|
|
}
|
|
}
|
|
|
|
DebuggerCommand *DebuggerClient::createCommand() {
|
|
TRACE(2, "DebuggerClient::createCommand\n");
|
|
#define MATCH_CMD(name, cmd) \
|
|
do { \
|
|
if (match(name)) { \
|
|
m_commandCanonical = name; \
|
|
return new cmd(); \
|
|
} \
|
|
return nullptr; \
|
|
} while(0) \
|
|
|
|
#define NEW_CMD_NAME(name, cmd) \
|
|
do { \
|
|
m_commandCanonical = name; \
|
|
return new cmd(); \
|
|
} while(0) \
|
|
|
|
// give gdb users some love
|
|
if (m_command == "bt") NEW_CMD_NAME("where", CmdWhere);
|
|
if (m_command == "set") NEW_CMD_NAME("config", CmdConfig);
|
|
if (m_command == "complete") NEW_CMD_NAME("complete", CmdComplete);
|
|
|
|
// Internal testing
|
|
if (m_command == "internaltesting") {
|
|
NEW_CMD_NAME("internaltesting", CmdInternalTesting);
|
|
}
|
|
|
|
switch (tolower(m_command[0])) {
|
|
case 'a': MATCH_CMD("abort" , CmdAbort );
|
|
case 'b': MATCH_CMD("break" , CmdBreak );
|
|
case 'c': MATCH_CMD("continue" , CmdContinue );
|
|
case 'd': MATCH_CMD("down" , CmdDown );
|
|
case 'e': MATCH_CMD("exception", CmdException);
|
|
case 'f': MATCH_CMD("frame" , CmdFrame );
|
|
case 'g': MATCH_CMD("global" , CmdGlobal );
|
|
case 'h': MATCH_CMD("help" , CmdHelp );
|
|
case 'i': MATCH_CMD("info" , CmdInfo );
|
|
case 'k': MATCH_CMD("konstant" , CmdConstant );
|
|
case 'l': MATCH_CMD("list" , CmdList );
|
|
case 'm': MATCH_CMD("machine" , CmdMachine );
|
|
case 'n': MATCH_CMD("next" , CmdNext );
|
|
case 'o': MATCH_CMD("out" , CmdOut );
|
|
case 'p': MATCH_CMD("print" , CmdPrint );
|
|
case 'q': MATCH_CMD("quit" , CmdQuit );
|
|
case 'r': MATCH_CMD("run" , CmdRun );
|
|
case 's': MATCH_CMD("step" , CmdStep );
|
|
case 't': MATCH_CMD("thread" , CmdThread );
|
|
case 'u': MATCH_CMD("up" , CmdUp );
|
|
case 'v': MATCH_CMD("variable" , CmdVariable );
|
|
case 'w': MATCH_CMD("where" , CmdWhere );
|
|
case 'z': MATCH_CMD("zend" , CmdZend );
|
|
|
|
// these single lettter commands allow "x{cmd}" and "x {cmd}"
|
|
case 'x': shiftCommand(); NEW_CMD_NAME("extended", CmdExtended);
|
|
case 'y': shiftCommand(); NEW_CMD_NAME("user", CmdUser);
|
|
case '!': shiftCommand(); NEW_CMD_NAME("shell", CmdShell);
|
|
case '&': shiftCommand(); NEW_CMD_NAME("macro", CmdMacro);
|
|
}
|
|
return nullptr;
|
|
#undef MATCH_CMD
|
|
#undef NEW_CMD_NAME
|
|
}
|
|
|
|
// Parses the current command string. If invalid return false.
|
|
// Otherwise, carry out the command and return true.
|
|
// NB: the command may throw a variety of exceptions derrived from
|
|
// DebuggerClientException.
|
|
bool DebuggerClient::process() {
|
|
TRACE(2, "DebuggerClient::process\n");
|
|
clearCachedLocal();
|
|
if (isApiMode()) {
|
|
// construct m_line based on m_command and m_args
|
|
m_line = m_command;
|
|
m_argIdx.clear();
|
|
m_argIdx.push_back(m_line.size());
|
|
for (unsigned int i = 0; i < m_args.size(); i++) {
|
|
m_line += " " + m_args[i];
|
|
m_argIdx.push_back(m_line.size());
|
|
}
|
|
}
|
|
switch (tolower(m_command[0])) {
|
|
case '@':
|
|
case '=':
|
|
case '$': {
|
|
processTakeCode();
|
|
return true;
|
|
}
|
|
case '<': {
|
|
if (match("<?php")) {
|
|
processTakeCode();
|
|
return true;
|
|
}
|
|
}
|
|
case '?': {
|
|
if (match("?")) {
|
|
usageLog("help", m_line);
|
|
CmdHelp().onClient(*this);
|
|
return true;
|
|
}
|
|
if (match("?>")) {
|
|
processEval();
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
DebuggerCommand *cmd = createCommand();
|
|
if (cmd) {
|
|
usageLog(m_commandCanonical, m_line);
|
|
if (cmd->is(DebuggerCommand::KindOfRun)) playMacro("startup");
|
|
DebuggerCommandPtr deleter(cmd);
|
|
cmd->onClient(*this);
|
|
} else {
|
|
processTakeCode();
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// helpers
|
|
|
|
void DebuggerClient::addToken(std::string &token, int idx) {
|
|
TRACE(2, "DebuggerClient::addToken\n");
|
|
m_argIdx.push_back(idx);
|
|
if (m_command.empty()) {
|
|
m_command = token;
|
|
} else {
|
|
m_args.push_back(token);
|
|
}
|
|
token.clear();
|
|
}
|
|
|
|
void DebuggerClient::parseCommand(const char *line) {
|
|
TRACE(2, "DebuggerClient::parseCommand\n");
|
|
m_command.clear();
|
|
m_args.clear();
|
|
|
|
char quote = 0;
|
|
string token;
|
|
m_argIdx.clear();
|
|
int i = 0;
|
|
for (i = 0; line[i]; i++) {
|
|
char ch = line[i];
|
|
char next = line[i+1];
|
|
switch (ch) {
|
|
case ' ':
|
|
if (!quote) {
|
|
if (!token.empty()) {
|
|
addToken(token, i);
|
|
}
|
|
} else {
|
|
token += ch;
|
|
}
|
|
break;
|
|
case '"':
|
|
case '\'':
|
|
if (quote == 0) {
|
|
quote = ch;
|
|
token += ch;
|
|
break;
|
|
}
|
|
if (quote == ch && (next == ' ' || next == 0)) {
|
|
token += ch;
|
|
addToken(token, i);
|
|
quote = 0;
|
|
break;
|
|
}
|
|
token += ch;
|
|
break;
|
|
case '\\':
|
|
if ((next == ' ' || next == '"' || next == '\'' || next == '\\')) {
|
|
if (quote == '\'') {
|
|
token += ch;
|
|
}
|
|
i++;
|
|
token += next;
|
|
break;
|
|
}
|
|
// fall through
|
|
default:
|
|
token += ch;
|
|
break;
|
|
}
|
|
}
|
|
if (!token.empty()) {
|
|
addToken(token, i);
|
|
}
|
|
}
|
|
|
|
bool DebuggerClient::parse(const char *line) {
|
|
TRACE(2, "DebuggerClient::parse\n");
|
|
if (m_inputState != TakingCode) {
|
|
while (isspace(*line)) line++;
|
|
}
|
|
m_line = line;
|
|
|
|
if (m_inputState == TakingCommand) {
|
|
parseCommand(line);
|
|
return true;
|
|
}
|
|
|
|
if (m_inputState == TakingCode) {
|
|
int pos = checkEvalEnd();
|
|
if (pos >= 0) {
|
|
if (pos > 0) {
|
|
m_code += m_line.substr(0, pos);
|
|
}
|
|
processEval();
|
|
} else {
|
|
if (!strncasecmp(m_line.c_str(), "abort", m_line.size())) {
|
|
m_code.clear();
|
|
m_inputState = TakingCommand;
|
|
return false;
|
|
}
|
|
m_code += m_line + "\n";
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool DebuggerClient::match(const char *cmd) {
|
|
TRACE(2, "DebuggerClient::match\n");
|
|
assert(cmd && *cmd);
|
|
return !strncasecmp(m_command.c_str(), cmd, m_command.size());
|
|
}
|
|
|
|
bool DebuggerClient::Match(const char *input, const char *cmd) {
|
|
TRACE(2, "DebuggerClient::Match\n");
|
|
return !strncasecmp(input, cmd, strlen(input));
|
|
}
|
|
|
|
bool DebuggerClient::arg(int index, const char *s) {
|
|
TRACE(2, "DebuggerClient::arg\n");
|
|
assert(s && *s);
|
|
assert(index > 0);
|
|
--index;
|
|
return (int)m_args.size() > index &&
|
|
!strncasecmp(m_args[index].c_str(), s, m_args[index].size());
|
|
}
|
|
|
|
std::string DebuggerClient::argValue(int index) {
|
|
TRACE(2, "DebuggerClient::argValue\n");
|
|
assert(index > 0);
|
|
--index;
|
|
if (index >= 0 && index < (int)m_args.size()) {
|
|
return m_args[index];
|
|
}
|
|
return "";
|
|
}
|
|
|
|
std::string DebuggerClient::lineRest(int index) {
|
|
TRACE(2, "DebuggerClient::lineRest\n");
|
|
assert(index > 0);
|
|
return m_line.substr(m_argIdx[index - 1] + 1);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// comunication with DebuggerProxy
|
|
|
|
DebuggerCommandPtr DebuggerClient::xend(DebuggerCommand *cmd) {
|
|
TRACE(2, "DebuggerClient::xend\n");
|
|
sendToServer(cmd);
|
|
return recvFromServer(cmd->getType());
|
|
}
|
|
|
|
void DebuggerClient::sendToServer(DebuggerCommand *cmd) {
|
|
TRACE(2, "DebuggerClient::sendToServer\n");
|
|
if (!cmd->send(m_machine->m_thrift)) {
|
|
Logger::Error("Send command: unable to communicate with server.");
|
|
throw DebuggerProtocolException();
|
|
}
|
|
}
|
|
|
|
DebuggerCommandPtr DebuggerClient::recvFromServer(int expected) {
|
|
TRACE(2, "DebuggerClient::recvFromServer\n");
|
|
const char *func = "Receive result";
|
|
|
|
DebuggerCommandPtr res;
|
|
while (true) {
|
|
while (!DebuggerCommand::Receive(m_machine->m_thrift, res, func)) {
|
|
if (m_stopped) throw DebuggerClientExitException();
|
|
}
|
|
if (!res) {
|
|
Logger::Error("Unable to communicate with server. Server's down?");
|
|
throw DebuggerServerLostException();
|
|
}
|
|
if (!res->getWireError().empty()) {
|
|
error("wire error: %s", res->getWireError().data());
|
|
}
|
|
if (res->is((DebuggerCommand::Type)expected)) {
|
|
usageLog("done", boost::lexical_cast<string>(expected));
|
|
break;
|
|
}
|
|
if (!res->is(DebuggerCommand::KindOfInterrupt)) {
|
|
Logger::Error("%s: unexpected return: %d", func, res->getType());
|
|
throw DebuggerProtocolException();
|
|
}
|
|
|
|
CmdInterruptPtr intr = dynamic_pointer_cast<CmdInterrupt>(res);
|
|
Debugger::UsageLogInterrupt(getUsageMode(), *intr.get());
|
|
|
|
if (isApiMode()) {
|
|
// Hit breakpoint during eval
|
|
// Could also happen with CmdPrint, but since CmdPrint is also used
|
|
// for handling watch, it could potentially lead to infinite recursion
|
|
// Do not support KindOfPrint until we separate watch from it.
|
|
if (expected == DebuggerCommand::KindOfEval ||
|
|
expected == DebuggerCommand::KindOfPrint) {
|
|
m_pendingCommands.push_back(expected);
|
|
} else {
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// eval() can cause more breakpoints
|
|
res->onClient(*this);
|
|
console();
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// helpers
|
|
|
|
int DebuggerClient::checkEvalEnd() {
|
|
TRACE(2, "DebuggerClient::checkEvalEnd\n");
|
|
size_t pos = m_line.rfind("?>");
|
|
if (pos == string::npos) {
|
|
return -1;
|
|
}
|
|
|
|
for (size_t p = pos + 2; p < m_line.size(); p++) {
|
|
if (!isspace(m_line[p])) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return pos;
|
|
}
|
|
|
|
// Parses the current command line as a code execution command
|
|
// and carries out the command.
|
|
void DebuggerClient::processTakeCode() {
|
|
TRACE(2, "DebuggerClient::processTakeCode\n");
|
|
assert(m_inputState == TakingCommand);
|
|
|
|
char first = m_line[0];
|
|
if (first == '@') {
|
|
usageLog("@", m_line);
|
|
m_code = string("<?php ") + (m_line.c_str() + 1) + ";";
|
|
processEval();
|
|
return;
|
|
} else if (first == '=') {
|
|
usageLog("=", m_line);
|
|
while (m_line.at(m_line.size() - 1) == ';') {
|
|
// strip the trailing ;
|
|
m_line = m_line.substr(0, m_line.size() - 1);
|
|
}
|
|
m_code = string("<?php $_=(") + m_line.substr(1) + "); " + m_printFunction;
|
|
processEval();
|
|
return;
|
|
} else if (first != '<') {
|
|
usageLog("eval", m_line);
|
|
// User entered something that did not start with @, =, or <
|
|
// and also was not a debugger command. Interpret it as PHP.
|
|
m_code = "<?php ";
|
|
m_code += m_line + ";";
|
|
processEval();
|
|
return;
|
|
}
|
|
usageLog("<?php", m_line);
|
|
m_code = "<?php ";
|
|
m_code += m_line.substr(m_command.length()) + "\n";
|
|
m_inputState = TakingCode;
|
|
|
|
int pos = checkEvalEnd();
|
|
if (pos >= 0) {
|
|
m_code.resize(m_code.size() - m_line.size() + pos - 1);
|
|
processEval();
|
|
}
|
|
}
|
|
|
|
void DebuggerClient::processEval() {
|
|
TRACE(2, "DebuggerClient::processEval\n");
|
|
m_inputState = TakingCommand;
|
|
m_acLiveListsDirty = true;
|
|
CmdEval().onClient(*this);
|
|
}
|
|
|
|
void DebuggerClient::swapHelp() {
|
|
TRACE(2, "DebuggerClient::swapHelp\n");
|
|
assert(m_args.size() > 0);
|
|
m_command = m_args[0];
|
|
m_args[0] = "help";
|
|
}
|
|
|
|
void DebuggerClient::quit() {
|
|
TRACE(2, "DebuggerClient::quit\n");
|
|
closeAllConnections();
|
|
throw DebuggerClientExitException();
|
|
}
|
|
|
|
DSandboxInfoPtr DebuggerClient::getSandbox(int index) const {
|
|
TRACE(2, "DebuggerClient::getSandbox\n");
|
|
if (index > 0) {
|
|
--index;
|
|
if (index >= 0 && index < (int)m_sandboxes.size()) {
|
|
return m_sandboxes[index];
|
|
}
|
|
}
|
|
return DSandboxInfoPtr();
|
|
}
|
|
|
|
void DebuggerClient::updateThreads(DThreadInfoPtrVec threads) {
|
|
TRACE(2, "DebuggerClient::updateThreads\n");
|
|
m_threads = threads;
|
|
for (unsigned int i = 0; i < m_threads.size(); i++) {
|
|
DThreadInfoPtr thread = m_threads[i];
|
|
std::map<int64_t, int>::const_iterator iter =
|
|
m_threadIdMap.find(thread->m_id);
|
|
if (iter != m_threadIdMap.end()) {
|
|
m_threads[i]->m_index = iter->second;
|
|
} else {
|
|
int index = m_threadIdMap.size() + 1;
|
|
m_threadIdMap[thread->m_id] = index;
|
|
m_threads[i]->m_index = index;
|
|
}
|
|
}
|
|
}
|
|
|
|
DThreadInfoPtr DebuggerClient::getThread(int index) const {
|
|
TRACE(2, "DebuggerClient::getThread\n");
|
|
for (unsigned int i = 0; i < m_threads.size(); i++) {
|
|
if (m_threads[i]->m_index == index) {
|
|
return m_threads[i];
|
|
}
|
|
}
|
|
return DThreadInfoPtr();
|
|
}
|
|
|
|
// Retrieves the current source location (file, line).
|
|
// The current location is initially determined by the
|
|
// breakpoint where the debugger is currently stopped and can
|
|
// thereafter be modified by list commands and by switching the
|
|
// the stack frame. The lineFocus and and charFocus parameters
|
|
// are non zero only when the source location comes from a breakpoint.
|
|
// They can be used to highlight the location of the current breakpoint
|
|
// in the edit window of an attached IDE, for example.
|
|
void DebuggerClient::getListLocation(std::string &file, int &line,
|
|
int &lineFocus0, int &charFocus0,
|
|
int &lineFocus1, int &charFocus1) {
|
|
TRACE(2, "DebuggerClient::getListLocation\n");
|
|
lineFocus0 = charFocus0 = lineFocus1 = charFocus1 = 0;
|
|
if (m_listFile.empty() && m_breakpoint) {
|
|
setListLocation(m_breakpoint->m_file, m_breakpoint->m_line1, true);
|
|
lineFocus0 = m_breakpoint->m_line1;
|
|
charFocus0 = m_breakpoint->m_char1;
|
|
lineFocus1 = m_breakpoint->m_line2;
|
|
charFocus1 = m_breakpoint->m_char2;
|
|
} else if (m_listLineFocus) {
|
|
lineFocus0 = m_listLineFocus;
|
|
}
|
|
file = m_listFile;
|
|
line = m_listLine;
|
|
}
|
|
|
|
void DebuggerClient::setListLocation(const std::string &file, int line,
|
|
bool center) {
|
|
TRACE(2, "DebuggerClient::setListLocation\n");
|
|
m_listFile = file;
|
|
if (!m_listFile.empty() && m_listFile[0] != '/' && !m_sourceRoot.empty()) {
|
|
if (m_sourceRoot[m_sourceRoot.size() - 1] != '/') {
|
|
m_sourceRoot += "/";
|
|
}
|
|
m_listFile = m_sourceRoot + m_listFile;
|
|
}
|
|
|
|
m_listLine = line;
|
|
if (center && m_listLine) {
|
|
m_listLineFocus = m_listLine;
|
|
m_listLine -= CodeBlockSize / 2;
|
|
if (m_listLine < 0) {
|
|
m_listLine = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DebuggerClient::setSourceRoot(const std::string &sourceRoot) {
|
|
TRACE(2, "DebuggerClient::setSourceRoot\n");
|
|
m_config["SourceRoot"] = m_sourceRoot = sourceRoot;
|
|
saveConfig();
|
|
|
|
// apply change right away
|
|
setListLocation(m_listFile, m_listLine, true);
|
|
}
|
|
|
|
void DebuggerClient::setMatchedBreakPoints(BreakPointInfoPtrVec breakpoints) {
|
|
TRACE(2, "DebuggerClient::setMatchedBreakPoints\n");
|
|
m_matched = breakpoints;
|
|
}
|
|
|
|
void DebuggerClient::setCurrentLocation(int64_t threadId,
|
|
BreakPointInfoPtr breakpoint) {
|
|
TRACE(2, "DebuggerClient::setCurrentLocation\n");
|
|
m_threadId = threadId;
|
|
m_breakpoint = breakpoint;
|
|
m_stacktrace.reset();
|
|
m_listFile.clear();
|
|
m_listLine = 0;
|
|
m_listLineFocus = 0;
|
|
m_acLiveListsDirty = true;
|
|
}
|
|
|
|
void DebuggerClient::addWatch(const char *fmt, const std::string &php) {
|
|
TRACE(2, "DebuggerClient::addWatch\n");
|
|
WatchPtr watch(new Watch());
|
|
watch->first = fmt;
|
|
watch->second = php;
|
|
m_watches.push_back(watch);
|
|
}
|
|
|
|
void DebuggerClient::setStackTrace(CArrRef stacktrace) {
|
|
TRACE(2, "DebuggerClient::setStackTrace\n");
|
|
m_stacktrace = stacktrace;
|
|
}
|
|
|
|
void DebuggerClient::moveToFrame(int index, bool display /* = true */) {
|
|
TRACE(2, "DebuggerClient::moveToFrame\n");
|
|
m_frame = index;
|
|
if (m_frame >= m_stacktrace.size()) {
|
|
m_frame = m_stacktrace.size() - 1;
|
|
}
|
|
if (m_frame < 0) {
|
|
m_frame = 0;
|
|
}
|
|
CArrRef frame = m_stacktrace[m_frame];
|
|
if (!frame.isNull()) {
|
|
String file = frame[s_file];
|
|
int line = frame[s_line].toInt32();
|
|
if (!file.empty() && line) {
|
|
if (m_frame == 0) {
|
|
m_listFile.clear();
|
|
m_listLine = 0;
|
|
m_listLineFocus = 0;
|
|
} else {
|
|
setListLocation(file.data(), line, true);
|
|
}
|
|
}
|
|
if (display) {
|
|
printFrame(m_frame, frame);
|
|
}
|
|
}
|
|
}
|
|
|
|
static const StaticString
|
|
s_args("args"),
|
|
s_namespace("namespace"),
|
|
s_class("class"),
|
|
s_function("function");
|
|
|
|
void DebuggerClient::printFrame(int index, CArrRef frame) {
|
|
TRACE(2, "DebuggerClient::printFrame\n");
|
|
StringBuffer args;
|
|
for (ArrayIter iter(frame[s_args]); iter; ++iter) {
|
|
if (!args.empty()) args.append(", ");
|
|
String value = FormatVariable(iter.second());
|
|
args.append(value);
|
|
}
|
|
|
|
StringBuffer func;
|
|
if (frame.exists(s_namespace)) {
|
|
func.append(frame[s_namespace].toString());
|
|
func.append("::");
|
|
}
|
|
if (frame.exists(s_class)) {
|
|
func.append(frame[s_class].toString());
|
|
func.append("::");
|
|
}
|
|
func.append(frame[s_function].toString());
|
|
|
|
String sindex(index);
|
|
print("#%s %s (%s)\n %s at %s:%d",
|
|
sindex.data(),
|
|
func.data() ? func.data() : "",
|
|
args.data() ? args.data() : "",
|
|
String(" ").substr(0, sindex.size()).data(),
|
|
frame[s_file].toString().data(),
|
|
(int)frame[s_line].toInt32());
|
|
}
|
|
|
|
void DebuggerClient::startMacro(std::string name) {
|
|
TRACE(2, "DebuggerClient::startMacro\n");
|
|
if (m_macroRecording &&
|
|
ask("We are recording a macro. Do you want to save? [Y/n]") != 'n') {
|
|
endMacro();
|
|
}
|
|
|
|
if (name.empty()) {
|
|
name = "default";
|
|
} else {
|
|
for (unsigned int i = 0; i < m_macros.size(); i++) {
|
|
if (m_macros[i]->m_name == name) {
|
|
if (ask("This will overwrite existing one. Proceed? [y/N]") != 'y') {
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
m_macroRecording = MacroPtr(new Macro());
|
|
m_macroRecording->m_name = name;
|
|
}
|
|
|
|
void DebuggerClient::endMacro() {
|
|
TRACE(2, "DebuggerClient::endMacro\n");
|
|
if (!m_macroRecording) {
|
|
error("There is no ongoing recording.");
|
|
tutorial("Use '& [s]tart {name}' or '& [s]tart' command to start "
|
|
"macro recording first.");
|
|
return;
|
|
}
|
|
|
|
bool found = false;
|
|
for (unsigned int i = 0; i < m_macros.size(); i++) {
|
|
if (m_macros[i]->m_name == m_macroRecording->m_name) {
|
|
m_macros[i] = m_macroRecording;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
m_macros.push_back(m_macroRecording);
|
|
}
|
|
saveConfig();
|
|
m_macroRecording.reset();
|
|
}
|
|
|
|
bool DebuggerClient::playMacro(std::string name) {
|
|
TRACE(2, "DebuggerClient::playMacro\n");
|
|
if (name.empty()) {
|
|
name = "default";
|
|
}
|
|
for (unsigned int i = 0; i < m_macros.size(); i++) {
|
|
if (m_macros[i]->m_name == name) {
|
|
m_macroPlaying = m_macros[i];
|
|
m_macroPlaying->m_index = 0;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool DebuggerClient::deleteMacro(int index) {
|
|
TRACE(2, "DebuggerClient::deleteMacro\n");
|
|
--index;
|
|
if (index >= 0 && index < (int)m_macros.size()) {
|
|
if (ask("Are you sure you want to delete the macro? [y/N]") != 'y') {
|
|
return true;
|
|
}
|
|
m_macros.erase(m_macros.begin() + index);
|
|
saveConfig();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void DebuggerClient::record(const char *line) {
|
|
TRACE(2, "DebuggerClient::record\n");
|
|
assert(line);
|
|
if (m_macroRecording && line[0] != '&') {
|
|
m_macroRecording->m_cmds.push_back(line);
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// helpers for server API
|
|
|
|
bool DebuggerClient::apiGrab() {
|
|
TRACE(2, "DebuggerClient::apiGrab\n");
|
|
Lock l(m_inApiUseLck);
|
|
if (m_inApiUse) {
|
|
return false;
|
|
}
|
|
m_inApiUse = true;
|
|
return true;
|
|
}
|
|
|
|
void DebuggerClient::apiFree() {
|
|
TRACE(2, "DebuggerClient::apiFree\n");
|
|
Lock l(m_inApiUseLck);
|
|
m_inApiUse = false;
|
|
}
|
|
|
|
void DebuggerClient::resetSmartAllocatedMembers() {
|
|
TRACE(2, "DebuggerClient::resetSmartAllocatedMembers\n");
|
|
// Essentially sets these to null: so it's safe to run their
|
|
// destructors at sweep time or after the SmartAllocators are torn
|
|
// down.
|
|
new (&m_stacktrace) Array();
|
|
new (&m_otValues) Array();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// helpers for usage logging
|
|
|
|
const char *DebuggerClient::getUsageMode() {
|
|
return isApiMode() ? "api" : "terminal";
|
|
}
|
|
|
|
void DebuggerClient::usageLog(const std::string &cmd, const std::string &data) {
|
|
Debugger::UsageLog(getUsageMode(), cmd, data);
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// configuration
|
|
|
|
void DebuggerClient::loadConfig() {
|
|
TRACE(2, "DebuggerClient::loadConfig\n");
|
|
if (m_configFileName.empty()) {
|
|
m_configFileName = Process::GetHomeDirectory() + ConfigFileName;
|
|
}
|
|
|
|
// make sure file exists
|
|
FILE *f = fopen(m_configFileName.c_str(), "r");
|
|
bool needToWriteFile = f == nullptr;
|
|
if (needToWriteFile) f = fopen(m_configFileName.c_str(), "a");
|
|
if (f) {
|
|
fclose(f);
|
|
} else {
|
|
m_configFileName.clear();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
m_config.open(m_configFileName);
|
|
} catch (const HdfException &e) {
|
|
Logger::Error("Unable to load configuration file: %s", e.what());
|
|
m_configFileName.clear();
|
|
}
|
|
|
|
s_use_utf8 = m_config["UTF8"].getBool(true);
|
|
m_config["UTF8"] = s_use_utf8; // for starter
|
|
|
|
Hdf color = m_config["Color"];
|
|
UseColor = color.getBool(true);
|
|
color = UseColor; // for starter
|
|
if (UseColor && RuntimeOption::EnableDebuggerColor) {
|
|
defineColors(); // (1) no one can overwrite, (2) for starter
|
|
LoadColors(color);
|
|
}
|
|
|
|
m_tutorial = m_config["Tutorial"].getInt32(0);
|
|
std::string pprint = m_config["PrettyPrint"].getString("hphpd_print_value");
|
|
setDebuggerBypassCheck(m_config["BypassAccessCheck"].getBool());
|
|
setDebuggerClientSmallStep(m_config["SmallStep"].getBool());
|
|
int printLevel = m_config["PrintLevel"].getInt16(3);
|
|
if (printLevel > 0 && printLevel < MinPrintLevel) {
|
|
printLevel = MinPrintLevel;
|
|
}
|
|
setDebuggerPrintLevel(printLevel);
|
|
|
|
int maxCodeLines = m_config["MaxCodeLines"].getInt16(-1);
|
|
m_config["MaxCodeLines"] = maxCodeLines;
|
|
setDebuggerClientMaxCodeLines(maxCodeLines);
|
|
|
|
m_printFunction = (boost::format(
|
|
"(function_exists(\"%s\") ? %s($_) : print_r(print_r($_, true)));")
|
|
% pprint % pprint).str();
|
|
|
|
m_config["Tutorial"]["Visited"].get(m_tutorialVisited);
|
|
|
|
for (Hdf node = m_config["Macros"].firstChild(); node.exists();
|
|
node = node.next()) {
|
|
MacroPtr macro(new Macro());
|
|
macro->load(node);
|
|
m_macros.push_back(macro);
|
|
}
|
|
|
|
m_sourceRoot = m_config["SourceRoot"].getString();
|
|
|
|
if (needToWriteFile) {
|
|
saveConfig(); // so to generate a starter for people
|
|
}
|
|
}
|
|
|
|
void DebuggerClient::saveConfig() {
|
|
TRACE(2, "DebuggerClient::saveConfig\n");
|
|
if (m_configFileName.empty()) {
|
|
// we are not touching a file that was not successfully loaded earlier
|
|
return;
|
|
}
|
|
|
|
m_config["Tutorial"] = m_tutorial;
|
|
Hdf visited = m_config["Tutorial"]["Visited"];
|
|
unsigned int i = 0;
|
|
for (std::set<string>::const_iterator iter = m_tutorialVisited.begin();
|
|
iter != m_tutorialVisited.end(); ++iter) {
|
|
visited[i++] = *iter;
|
|
}
|
|
|
|
m_config.remove("Macros");
|
|
Hdf macros = m_config["Macros"];
|
|
for (i = 0; i < m_macros.size(); i++) {
|
|
m_macros[i]->save(macros[i]);
|
|
}
|
|
|
|
m_config.write(m_configFileName);
|
|
}
|
|
|
|
void DebuggerClient::defineColors() {
|
|
TRACE(2, "DebuggerClient::defineColors\n");
|
|
vector<string> names;
|
|
get_supported_colors(names);
|
|
Hdf support = m_config["Color"]["SupportedNames"];
|
|
for (unsigned int i = 0; i < names.size(); i++) {
|
|
support[i+1] = names[i];
|
|
}
|
|
|
|
Hdf emacs = m_config["Color"]["Palette"]["emacs"];
|
|
emacs["Keyword"] = "CYAN";
|
|
emacs["Comment"] = "RED";
|
|
emacs["String"] = "GREEN";
|
|
emacs["Variable"] = "BROWN";
|
|
emacs["Html"] = "GRAY";
|
|
emacs["Tag"] = "MAGENTA";
|
|
emacs["Declaration"] = "BLUE";
|
|
emacs["Constant"] = "MAGENTA";
|
|
emacs["LineNo"] = "GRAY";
|
|
|
|
Hdf vim = m_config["Color"]["Palette"]["vim"];
|
|
vim["Keyword"] = "MAGENTA";
|
|
vim["Comment"] = "BLUE";
|
|
vim["String"] = "RED";
|
|
vim["Variable"] = "CYAN";
|
|
vim["Html"] = "GRAY";
|
|
vim["Tag"] = "MAGENTA";
|
|
vim["Declaration"] = "WHITE";
|
|
vim["Constant"] = "WHITE";
|
|
vim["LineNo"] = "GRAY";
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}}
|