Debugger: Use Qt's JSON encoder for debugger protocol

The V4 debug service expects correct JSON as input and gdb, lldb, and
pdb expect Python object literals. There is a subset of JSON that is
also valid as Python object literals and we use that for the protocol
spoken with gdb, lldb, and pdb. The strings passed to CDB are tunneled
through JSON strings and converted to byte arrays before sending them.

Change-Id: I87319b5450e5c3c3b29c565b75cddaa612767611
Task-number: QTCREATORBUG-14931
Reviewed-by: hjk <hjk@theqtcompany.com>
This commit is contained in:
Ulf Hermann
2015-09-14 13:40:35 +02:00
parent 7d3bb6fdee
commit d5707e0e32
8 changed files with 99 additions and 119 deletions

View File

@@ -1174,7 +1174,7 @@ void CdbEngine::executeDebuggerCommand(const QString &command, DebuggerLanguages
// Post command to the cdb process // Post command to the cdb process
void CdbEngine::runCommand(const DebuggerCommand &dbgCmd, int flags) void CdbEngine::runCommand(const DebuggerCommand &dbgCmd, int flags)
{ {
QByteArray cmd = dbgCmd.function + dbgCmd.arguments(); QByteArray cmd = dbgCmd.function + dbgCmd.argsToString();
if (!m_accessible) { if (!m_accessible) {
const QString msg = QString::fromLatin1("Attempt to issue command \"%1\" to non-accessible session (%2)") const QString msg = QString::fromLatin1("Attempt to issue command \"%1\" to non-accessible session (%2)")
.arg(QString::fromLocal8Bit(cmd), QString::fromLatin1(stateName(state()))); .arg(QString::fromLocal8Bit(cmd), QString::fromLatin1(stateName(state())));
@@ -1197,8 +1197,8 @@ void CdbEngine::runCommand(const DebuggerCommand &dbgCmd, int flags)
// pass along token for identification in hash. // pass along token for identification in hash.
const int token = m_nextCommandToken++; const int token = m_nextCommandToken++;
str << m_extensionCommandPrefixBA << dbgCmd.function << " -t " << token; str << m_extensionCommandPrefixBA << dbgCmd.function << " -t " << token;
if (!dbgCmd.args.isEmpty()) if (dbgCmd.args.isString())
str << ' ' << dbgCmd.args; str << ' ' << dbgCmd.argsToString();
m_commandForToken.insert(token, dbgCmd); m_commandForToken.insert(token, dbgCmd);
} else { } else {
str << cmd; str << cmd;
@@ -1332,7 +1332,7 @@ void CdbEngine::doUpdateLocals(const UpdateParameters &updateParameters)
str << blankSeparator << updateParameters.partialVariable; str << blankSeparator << updateParameters.partialVariable;
DebuggerCommand cmd("locals"); DebuggerCommand cmd("locals");
cmd.args = arguments; cmd.args = QLatin1String(arguments);
cmd.callback = [this, partialUpdate](const DebuggerResponse &r) { handleLocals(r, partialUpdate); }; cmd.callback = [this, partialUpdate](const DebuggerResponse &r) { handleLocals(r, partialUpdate); };
runCommand(cmd, ExtensionCommand); runCommand(cmd, ExtensionCommand);
} }
@@ -1557,8 +1557,10 @@ void CdbEngine::fetchMemory(MemoryAgent *agent, QObject *editor, quint64 addr, q
void CdbEngine::postFetchMemory(const MemoryViewCookie &cookie) void CdbEngine::postFetchMemory(const MemoryViewCookie &cookie)
{ {
DebuggerCommand cmd("memory"); DebuggerCommand cmd("memory");
ByteArrayInputStream str(cmd.args); QByteArray args;
ByteArrayInputStream str(args);
str << cookie.address << ' ' << cookie.length; str << cookie.address << ' ' << cookie.length;
cmd.args = QLatin1String(args);
cmd.callback = [this, cookie](const DebuggerResponse &response) { cmd.callback = [this, cookie](const DebuggerResponse &response) {
if (response.resultClass == ResultDone && cookie.agent) { if (response.resultClass == ResultDone && cookie.agent) {
const QByteArray data = QByteArray::fromBase64(response.data.data()); const QByteArray data = QByteArray::fromBase64(response.data.data());
@@ -1619,7 +1621,7 @@ void CdbEngine::reloadFullStack()
if (debug) if (debug)
qDebug("%s", Q_FUNC_INFO); qDebug("%s", Q_FUNC_INFO);
DebuggerCommand cmd("stack"); DebuggerCommand cmd("stack");
cmd.args = "unlimited"; cmd.args = QStringLiteral("unlimited");
cmd.callback = CB(handleStackTrace); cmd.callback = CB(handleStackTrace);
runCommand(cmd, ExtensionCommand); runCommand(cmd, ExtensionCommand);
} }
@@ -1627,7 +1629,7 @@ void CdbEngine::reloadFullStack()
void CdbEngine::listBreakpoints() void CdbEngine::listBreakpoints()
{ {
DebuggerCommand cmd("breakpoints"); DebuggerCommand cmd("breakpoints");
cmd.args = "-v"; cmd.args = QStringLiteral("-v");
cmd.callback = CB(handleBreakPoints); cmd.callback = CB(handleBreakPoints);
runCommand(cmd, ExtensionCommand); runCommand(cmd, ExtensionCommand);
} }
@@ -1845,11 +1847,12 @@ unsigned CdbEngine::examineStopReason(const GdbMi &stopReason,
QString::number(threadId)); QString::number(threadId));
DebuggerCommand cmd("expression"); DebuggerCommand cmd("expression");
cmd.args = parameters.condition; QByteArray args = parameters.condition;
if (cmd.args.contains(' ') && !cmd.args.startsWith('"')) { if (args.contains(' ') && !args.startsWith('"')) {
cmd.args.prepend('"'); args.prepend('"');
cmd.args.append('"'); args.append('"');
} }
cmd.args = QLatin1String(args);
cmd.callback = [this, id, stopReason](const DebuggerResponse &response) { cmd.callback = [this, id, stopReason](const DebuggerResponse &response) {
handleExpression(response, id, stopReason); handleExpression(response, id, stopReason);
}; };
@@ -3092,7 +3095,7 @@ void CdbEngine::watchPoint(const QPoint &p)
void CdbEngine::postWidgetAtCommand() void CdbEngine::postWidgetAtCommand()
{ {
DebuggerCommand cmd("widgetat"); DebuggerCommand cmd("widgetat");
cmd.args = QByteArray::number(m_watchPointX) + ' ' + QByteArray::number(m_watchPointY); cmd.args = QString::fromLatin1("%1 %2").arg(m_watchPointX, m_watchPointY);
cmd.callback = CB(handleWidgetAt); cmd.callback = CB(handleWidgetAt);
runCommand(cmd, ExtensionCommand); runCommand(cmd, ExtensionCommand);
} }

View File

@@ -36,6 +36,8 @@
#include <QHostAddress> #include <QHostAddress>
#include <QRegExp> #include <QRegExp>
#include <QTimeZone> #include <QTimeZone>
#include <QJsonArray>
#include <QJsonDocument>
#include <ctype.h> #include <ctype.h>
@@ -779,106 +781,80 @@ QString decodeData(const QByteArray &ba, DebuggerEncoding encoding)
// //
////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////
void DebuggerCommand::argHelper(const char *name, const QByteArray &data) template<typename Value>
QJsonValue addToJsonObject(const QJsonValue &args, const char *name, const Value &value)
{ {
args.append('"'); QTC_ASSERT(args.isObject() || args.isNull(), return args);
args.append(name); QJsonObject obj = args.toObject();
args.append("\":"); obj.insert(QLatin1String(name), value);
args.append(data); return obj;
args.append(",");
} }
void DebuggerCommand::arg(const char *name, int value) void DebuggerCommand::arg(const char *name, int value)
{ {
argHelper(name, QByteArray::number(value)); args = addToJsonObject(args, name, value);
} }
void DebuggerCommand::arg(const char *name, qlonglong value) void DebuggerCommand::arg(const char *name, qlonglong value)
{ {
argHelper(name, QByteArray::number(value)); args = addToJsonObject(args, name, value);
} }
void DebuggerCommand::arg(const char *name, qulonglong value) void DebuggerCommand::arg(const char *name, qulonglong value)
{ {
argHelper(name, QByteArray::number(value)); // gdb and lldb will correctly cast the value back to unsigned if needed, so this is no problem.
args = addToJsonObject(args, name, qint64(value));
} }
void DebuggerCommand::arg(const char *name, const QString &value) void DebuggerCommand::arg(const char *name, const QString &value)
{ {
arg(name, value.toUtf8().data()); args = addToJsonObject(args, name, value);
} }
void DebuggerCommand::arg(const char *name, const QByteArray &value) void DebuggerCommand::arg(const char *name, const QByteArray &value)
{ {
arg(name, value.data()); args = addToJsonObject(args, name, QLatin1String(value));
} }
void DebuggerCommand::arg(const char *name, const char *value) void DebuggerCommand::arg(const char *name, const char *value)
{ {
args.append('"'); args = addToJsonObject(args, name, QLatin1String(value));
args.append(name);
args.append("\":\"");
args.append(value);
args.append("\",");
} }
void DebuggerCommand::arg(const char *name, const QList<int> &list) void DebuggerCommand::arg(const char *name, const QList<int> &list)
{ {
beginList(name); QJsonArray numbers;
foreach (int item, list) { foreach (int item, list)
args.append(QByteArray::number(item)); numbers.append(item);
args.append(','); args = addToJsonObject(args, name, numbers);
}
endList();
} }
void DebuggerCommand::arg(const char *value) void DebuggerCommand::arg(const char *value)
{ {
args.append("\""); QTC_ASSERT(args.isArray() || args.isNull(), return);
args.append(value); QJsonArray arr = args.toArray();
args.append("\","); arr.append(QLatin1String(value));
args = arr;
} }
void DebuggerCommand::beginList(const char *name) void DebuggerCommand::arg(const char *name, const QJsonValue &value)
{ {
if (name) { args = addToJsonObject(args, name, value);
args += '"';
args += name;
args += "\":";
}
args += '[';
} }
void DebuggerCommand::endList() QByteArray DebuggerCommand::argsToPython() const
{ {
if (args.endsWith(',')) // TODO: Verify that this is really Python.
args.chop(1);
args += "],"; if (args.isArray())
return QJsonDocument(args.toArray()).toJson(QJsonDocument::Compact);
else
return QJsonDocument(args.toObject()).toJson(QJsonDocument::Compact);
} }
void DebuggerCommand::beginGroup(const char *name) QByteArray DebuggerCommand::argsToString() const
{ {
if (name) { return args.toString().toLatin1();
args += '"';
args += name;
args += "\":";
}
args += '{';
}
void DebuggerCommand::endGroup()
{
if (args.endsWith(','))
args.chop(1);
args += "},";
}
QByteArray DebuggerCommand::arguments() const
{
QByteArray result = args;
if (result.endsWith(','))
result.chop(1);
return result;
} }
} // namespace Internal } // namespace Internal

View File

@@ -34,6 +34,8 @@
#include <QByteArray> #include <QByteArray>
#include <QList> #include <QList>
#include <QString> #include <QString>
#include <QJsonValue>
#include <QJsonObject>
#include <functional> #include <functional>
#include <vector> #include <vector>
@@ -53,7 +55,7 @@ public:
DebuggerCommand(const char *f, int flags = 0, Callback cb = Callback()) DebuggerCommand(const char *f, int flags = 0, Callback cb = Callback())
: function(f), callback(cb), flags(flags) : function(f), callback(cb), flags(flags)
{} {}
DebuggerCommand(const char *f, const char *a, int flags = 0, Callback cb = Callback()) DebuggerCommand(const char *f, const QJsonValue &a, int flags = 0, Callback cb = Callback())
: function(f), args(a), callback(cb), flags(flags) : function(f), args(a), callback(cb), flags(flags)
{} {}
DebuggerCommand(const char *f, Callback cb) DebuggerCommand(const char *f, Callback cb)
@@ -61,7 +63,7 @@ public:
{} {}
DebuggerCommand(const QByteArray &f) : function(f), flags(0) {} DebuggerCommand(const QByteArray &f) : function(f), flags(0) {}
void arg(const char *name); void arg(const char *value);
void arg(const char *name, int value); void arg(const char *name, int value);
void arg(const char *name, qlonglong value); void arg(const char *name, qlonglong value);
void arg(const char *name, qulonglong value); void arg(const char *name, qulonglong value);
@@ -69,21 +71,16 @@ public:
void arg(const char *name, const QByteArray &value); void arg(const char *name, const QByteArray &value);
void arg(const char *name, const char *value); void arg(const char *name, const char *value);
void arg(const char *name, const QList<int> &list); void arg(const char *name, const QList<int> &list);
void arg(const char *name, const QJsonValue &value);
void beginList(const char *name = 0); QByteArray argsToPython() const;
void endList(); QByteArray argsToString() const;
void beginGroup(const char *name = 0);
void endGroup();
QByteArray arguments() const;
QByteArray function; QByteArray function;
QByteArray args; QJsonValue args;
Callback callback; Callback callback;
uint postTime; // msecsSinceStartOfDay uint postTime; // msecsSinceStartOfDay
int flags; int flags;
private:
void argHelper(const char *name, const QByteArray &value);
}; };
/* /*

View File

@@ -84,6 +84,7 @@
#include <QProcess> #include <QProcess>
#include <QPushButton> #include <QPushButton>
#include <QTemporaryFile> #include <QTemporaryFile>
#include <QJsonArray>
using namespace Core; using namespace Core;
using namespace ProjectExplorer; using namespace ProjectExplorer;
@@ -874,7 +875,7 @@ void GdbEngine::maybeHandleInferiorPidChanged(const QString &pid0)
void GdbEngine::runCommand(const DebuggerCommand &command) void GdbEngine::runCommand(const DebuggerCommand &command)
{ {
QByteArray cmd = command.function + "({" + command.args + "})"; QByteArray cmd = command.function + "(" + command.argsToPython() + ")";
postCommand("python theDumper." + cmd, command.flags, command.callback); postCommand("python theDumper." + cmd, command.flags, command.callback);
} }

View File

@@ -66,6 +66,7 @@
#include <QTimer> #include <QTimer>
#include <QToolTip> #include <QToolTip>
#include <QVariant> #include <QVariant>
#include <QJsonArray>
using namespace Core; using namespace Core;
using namespace Utils; using namespace Utils;
@@ -136,7 +137,7 @@ void LldbEngine::runCommand(const DebuggerCommand &command_)
DebuggerCommand command = command_; DebuggerCommand command = command_;
command.arg("token", tok); command.arg("token", tok);
QByteArray token = QByteArray::number(tok); QByteArray token = QByteArray::number(tok);
QByteArray cmd = command.function + "({" + command.args + "})"; QByteArray cmd = command.function + "(" + command.argsToPython() + ")";
showMessage(_(token + cmd + '\n'), LogInput); showMessage(_(token + cmd + '\n'), LogInput);
m_commandForToken[currentToken()] = command; m_commandForToken[currentToken()] = command;
m_lldbProc.write("script theDumper." + cmd + "\n"); m_lldbProc.write("script theDumper." + cmd + "\n");
@@ -335,10 +336,10 @@ void LldbEngine::setupInferior()
cmd2.arg("useTerminal", rp.useTerminal); cmd2.arg("useTerminal", rp.useTerminal);
cmd2.arg("startMode", rp.startMode); cmd2.arg("startMode", rp.startMode);
cmd2.beginList("processArgs"); QJsonArray processArgs;
foreach (const QString &arg, args.toUnixArgs()) foreach (const QString &arg, args.toUnixArgs())
cmd2.arg(arg.toUtf8().toHex()); processArgs.append(QLatin1String(arg.toUtf8().toHex()));
cmd2.endList(); cmd2.arg("processArgs", processArgs);
if (rp.useTerminal) { if (rp.useTerminal) {
QTC_ASSERT(state() == InferiorSetupRequested, qDebug() << state()); QTC_ASSERT(state() == InferiorSetupRequested, qDebug() << state());
@@ -830,7 +831,7 @@ void LldbEngine::doUpdateLocals(const UpdateParameters &params)
//cmd.arg("resultvarname", m_resultVarName); //cmd.arg("resultvarname", m_resultVarName);
m_lastDebuggableCommand = cmd; m_lastDebuggableCommand = cmd;
m_lastDebuggableCommand.args.replace("\"passexceptions\":0", "\"passexceptions\":1"); m_lastDebuggableCommand.arg("passexceptions", 0);
cmd.callback = [this](const DebuggerResponse &response) { cmd.callback = [this](const DebuggerResponse &response) {
updateLocalsView(response.data); updateLocalsView(response.data);

View File

@@ -62,6 +62,7 @@
#include <QFileInfo> #include <QFileInfo>
#include <QTimer> #include <QTimer>
#include <QVariant> #include <QVariant>
#include <QJsonArray>
using namespace Core; using namespace Core;
@@ -97,7 +98,7 @@ void PdbEngine::postDirectCommand(const QByteArray &command)
void PdbEngine::runCommand(const DebuggerCommand &cmd) void PdbEngine::runCommand(const DebuggerCommand &cmd)
{ {
QTC_ASSERT(m_proc.state() == QProcess::Running, notifyEngineIll()); QTC_ASSERT(m_proc.state() == QProcess::Running, notifyEngineIll());
QByteArray command = "qdebug('" + cmd.function + "',{" + cmd.args + "})"; QByteArray command = "qdebug('" + cmd.function + "'," + cmd.argsToPython() + ")";
showMessage(_(command), LogInput); showMessage(_(command), LogInput);
m_proc.write(command + '\n'); m_proc.write(command + '\n');
} }

View File

@@ -1733,14 +1733,15 @@ QmlV8ObjectData QmlEnginePrivate::extractData(const QVariant &data) const
void QmlEnginePrivate::runCommand(const DebuggerCommand &command, const QmlCallback &cb) void QmlEnginePrivate::runCommand(const DebuggerCommand &command, const QmlCallback &cb)
{ {
QByteArray msg = "{\"seq\":" + QByteArray::number(++sequence) + "," QJsonObject object;
+ "\"type\":\"request\"," object.insert(QStringLiteral("seq"), ++sequence);
+ "\"command\":\"" + command.function + "\"," object.insert(QStringLiteral("type"), QStringLiteral("request"));
+ "\"arguments\":{" + command.arguments() + "}}"; object.insert(QStringLiteral("command"), QLatin1String(command.function));
object.insert(QStringLiteral("arguments"), command.args);
if (cb) if (cb)
callbackForToken[sequence] = cb; callbackForToken[sequence] = cb;
runDirectCommand(V8REQUEST, msg); runDirectCommand(V8REQUEST, QJsonDocument(object).toJson(QJsonDocument::Compact));
} }
void QmlEnginePrivate::runDirectCommand(const QByteArray &type, const QByteArray &msg) void QmlEnginePrivate::runDirectCommand(const QByteArray &type, const QByteArray &msg)

View File

@@ -58,6 +58,8 @@
#include <QProcess> #include <QProcess>
#include <QTabWidget> #include <QTabWidget>
#include <QTextEdit> #include <QTextEdit>
#include <QJsonArray>
#include <QJsonObject>
#include <QTimer> #include <QTimer>
#include <algorithm> #include <algorithm>
@@ -1591,57 +1593,55 @@ QByteArray WatchHandler::individualFormatRequests() const
void WatchHandler::appendFormatRequests(DebuggerCommand *cmd) void WatchHandler::appendFormatRequests(DebuggerCommand *cmd)
{ {
cmd->beginList("expanded"); QJsonArray expanded;
QSetIterator<QByteArray> jt(m_model->m_expandedINames); QSetIterator<QByteArray> jt(m_model->m_expandedINames);
while (jt.hasNext()) { while (jt.hasNext())
QByteArray iname = jt.next(); expanded.append(QLatin1String(jt.next()));
//WatchItem *item = m_model->findItem(iname);
cmd->arg(iname);
//cmd->arg("format", item->requestedFormat());
}
cmd->endList();
cmd->beginGroup("typeformats"); cmd->arg("expanded", expanded);
QJsonObject typeformats;
QHashIterator<QByteArray, int> it(theTypeFormats); QHashIterator<QByteArray, int> it(theTypeFormats);
while (it.hasNext()) { while (it.hasNext()) {
it.next(); it.next();
const int format = it.value(); const int format = it.value();
if (format != AutomaticFormat) if (format != AutomaticFormat)
cmd->arg(it.key(), format); typeformats.insert(QLatin1String(it.key()), format);
} }
cmd->endGroup(); cmd->arg("typeformats", typeformats);
cmd->beginGroup("formats"); QJsonObject formats;
QHashIterator<QByteArray, int> it2(theIndividualFormats); QHashIterator<QByteArray, int> it2(theIndividualFormats);
while (it2.hasNext()) { while (it2.hasNext()) {
it2.next(); it2.next();
const int format = it2.value(); const int format = it2.value();
if (format != AutomaticFormat) if (format != AutomaticFormat)
cmd->arg(it2.key(), format); formats.insert(QLatin1String(it2.key()), format);
} }
cmd->endGroup(); cmd->arg("formats", formats);
}
static inline QJsonObject watcher(const QByteArray &iname, const QByteArray &exp)
{
QJsonObject watcher;
watcher.insert(QStringLiteral("iname"), QLatin1String(iname));
watcher.insert(QStringLiteral("exp"), QLatin1String(exp.toHex()));
return watcher;
} }
void WatchHandler::appendWatchersAndTooltipRequests(DebuggerCommand *cmd) void WatchHandler::appendWatchersAndTooltipRequests(DebuggerCommand *cmd)
{ {
cmd->beginList("watchers"); QJsonArray watchers;
DebuggerToolTipContexts toolTips = DebuggerToolTipManager::pendingTooltips(m_model->m_engine); DebuggerToolTipContexts toolTips = DebuggerToolTipManager::pendingTooltips(m_model->m_engine);
foreach (const DebuggerToolTipContext &p, toolTips) { foreach (const DebuggerToolTipContext &p, toolTips)
cmd->beginGroup(); watchers.append(watcher(p.iname, p.expression.toLatin1()));
cmd->arg("iname", p.iname);
cmd->arg("exp", p.expression.toLatin1().toHex());
cmd->endGroup();
}
QHashIterator<QByteArray, int> it(WatchHandler::watcherNames()); QHashIterator<QByteArray, int> it(WatchHandler::watcherNames());
while (it.hasNext()) { while (it.hasNext()) {
it.next(); it.next();
cmd->beginGroup(); watchers.append(watcher("watch." + QByteArray::number(it.value()), it.key()));
cmd->arg("iname", "watch." + QByteArray::number(it.value()));
cmd->arg("exp", it.key().toHex());
cmd->endGroup();
} }
cmd->endList(); cmd->arg("watchers", watchers);
} }
void WatchHandler::addDumpers(const GdbMi &dumpers) void WatchHandler::addDumpers(const GdbMi &dumpers)