Add gdb tracepoint support for Linux

Change-Id: Id2e46bae576a730f8c1b64a247aeed12e6d721af
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Mattias Johansson
2021-01-07 13:28:05 +01:00
committed by Gustav Johansson
parent 874029809f
commit 2081038953
10 changed files with 773 additions and 19 deletions

View File

@@ -115,6 +115,31 @@ static QMessageBox *showMessageBox(QMessageBox::Icon icon,
return mb;
}
enum class TracepointCaptureType
{
Address,
Caller,
Callstack,
FilePos,
Function,
Pid,
ProcessName,
Tick,
Tid,
ThreadName,
Expression
};
struct TracepointCaptureData
{
TracepointCaptureType type;
QVariant expression;
int start;
int end;
};
const char tracepointCapturePropertyName[] = "GDB.TracepointCapture";
///////////////////////////////////////////////////////////////////////
//
// GdbEngine
@@ -307,6 +332,18 @@ void GdbEngine::handleResponse(const QString &buff)
handleInterpreterBreakpointModified(allData["interpreterasync"]);
break;
}
if (data.startsWith("tracepointhit={")) {
GdbMi allData;
allData.fromStringMultiple(data);
handleTracepointHit(allData["tracepointhit"]);
break;
}
if (data.startsWith("tracepointmodified=")) {
GdbMi allData;
allData.fromStringMultiple(data);
handleTracepointModified(allData["tracepointmodified"]);
break;
}
m_pendingConsoleStreamOutput += data;
// Fragile, but it's all we have.
@@ -2162,6 +2199,7 @@ void GdbEngine::handleCatchInsert(const DebuggerResponse &response, const Breakp
void GdbEngine::handleBkpt(const GdbMi &bkpt, const Breakpoint &bp)
{
QTC_ASSERT(bp, return);
bool usePseudoTracepoints = boolSetting(UsePseudoTracepoints);
const QString nr = bkpt["number"].data();
if (nr.contains('.')) {
// A sub-breakpoint.
@@ -2169,6 +2207,10 @@ void GdbEngine::handleBkpt(const GdbMi &bkpt, const Breakpoint &bp)
QTC_ASSERT(sub, return);
sub->params.updateFromGdbOutput(bkpt);
sub->params.type = bp->type();
if (usePseudoTracepoints && bp->isTracepoint()) {
sub->params.tracepoint = true;
sub->params.message = bp->message();
}
return;
}
@@ -2183,12 +2225,18 @@ void GdbEngine::handleBkpt(const GdbMi &bkpt, const Breakpoint &bp)
QTC_ASSERT(sub, return);
sub->params.updateFromGdbOutput(location);
sub->params.type = bp->type();
if (usePseudoTracepoints && bp->isTracepoint()) {
sub->params.tracepoint = true;
sub->params.message = bp->message();
}
}
}
// A (the?) primary breakpoint.
bp->setResponseId(nr);
bp->updateFromGdbOutput(bkpt);
if (usePseudoTracepoints && bp->isTracepoint())
bp->setMessage(bp->requestedParameters().message);
}
void GdbEngine::handleBreakInsert1(const DebuggerResponse &response, const Breakpoint &bp)
@@ -2334,6 +2382,176 @@ void GdbEngine::handleBreakCondition(const DebuggerResponse &, const Breakpoint
updateBreakpoint(bp); // Maybe there's more to do.
}
void GdbEngine::updateTracepointCaptures(const Breakpoint &bp)
{
static QRegularExpression capsRegExp(
"(^|[^\\\\])(\\$(ADDRESS|CALLER|CALLSTACK|FILEPOS|FUNCTION|PID|PNAME|TICK|TID|TNAME)"
"|{[^}]+})");
QString message = bp->globalBreakpoint()->requestedParameters().message;
if (message.isEmpty()) {
bp->setProperty(tracepointCapturePropertyName, {});
return;
}
QVariantList caps;
QRegularExpressionMatch match = capsRegExp.match(message, 0);
while (match.hasMatch()) {
QString t = match.captured(2);
if (t[0] == '$') {
TracepointCaptureType type;
if (t == "$ADDRESS")
type = TracepointCaptureType::Address;
else if (t == "$CALLER")
type = TracepointCaptureType::Caller;
else if (t == "$CALLSTACK")
type = TracepointCaptureType::Callstack;
else if (t == "$FILEPOS")
type = TracepointCaptureType::FilePos;
else if (t == "$FUNCTION")
type = TracepointCaptureType::Function;
else if (t == "$PID")
type = TracepointCaptureType::Pid;
else if (t == "$PNAME")
type = TracepointCaptureType::ProcessName;
else if (t == "$TICK")
type = TracepointCaptureType::Tick;
else if (t == "$TID")
type = TracepointCaptureType::Tid;
else if (t == "$TNAME")
type = TracepointCaptureType::ThreadName;
else
QTC_ASSERT(false, continue);
caps << QVariant::fromValue<TracepointCaptureData>(
{type, {}, match.capturedStart(2), match.capturedEnd(2)});
} else {
QString expression = t.mid(1, t.length() - 2);
caps << QVariant::fromValue<TracepointCaptureData>(
{TracepointCaptureType::Expression, expression, match.capturedStart(2), match.capturedEnd(2)});
}
match = capsRegExp.match(message, match.capturedEnd());
}
bp->setProperty(tracepointCapturePropertyName, caps);
}
void GdbEngine::handleTracepointInsert(const DebuggerResponse &response, const Breakpoint &bp)
{
QTC_ASSERT(bp, return);
if (bp->state() == BreakpointRemoveRequested) {
if (response.resultClass == ResultDone) {
// This delete was deferred. Act now.
const GdbMi mainbkpt = response.data["tracepoint"][0];
notifyBreakpointRemoveProceeding(bp);
DebuggerCommand cmd("-break-delete " + mainbkpt["number"].data());
cmd.flags = NeedsTemporaryStop;
runCommand(cmd);
notifyBreakpointRemoveOk(bp);
return;
}
}
if (response.resultClass == ResultDone) {
for (const GdbMi &bkpt : response.data["tracepoint"])
handleBkpt(bkpt, bp);
if (bp->needsChange()) {
bp->gotoState(BreakpointUpdateRequested, BreakpointInsertionProceeding);
updateBreakpoint(bp);
} else {
notifyBreakpointInsertOk(bp);
}
}
}
void GdbEngine::handleTracepointHit(const GdbMi &data)
{
const GdbMi &result = data["result"];
const QString rid = result["number"].data();
Breakpoint bp = breakHandler()->findBreakpointByResponseId(rid);
QTC_ASSERT(bp, return);
const GdbMi &warnings = data["warnings"];
if (warnings.childCount() > 0) {
for (const GdbMi &warning: warnings) {
emit appendMessageRequested(warning.toString(), ErrorMessageFormat, true);
}
}
QString message = bp->message();
QVariant caps = bp->property(tracepointCapturePropertyName);
if (caps.isValid()) {
QList<QVariant> capsList = caps.toList();
const GdbMi &miCaps = result["caps"];
if (capsList.length() == miCaps.childCount()) {
// reverse iterate to make start/end correct
for (int i = capsList.length() - 1; i >= 0; --i) {
TracepointCaptureData cap = capsList.at(i).value<TracepointCaptureData>();
const GdbMi &miCap = miCaps.childAt(i);
switch (cap.type) {
case TracepointCaptureType::Callstack: {
QStringList frames;
for (const GdbMi &frame: miCap) {
frames.append(frame.data());
}
message.replace(cap.start, cap.end - cap.start, frames.join(" <- "));
break;
}
case TracepointCaptureType::Expression: {
QString key = miCap.data();
const GdbMi &expression = data["expressions"][key.toLatin1().data()];
if (expression.isValid()) {
QString s = expression.toString();
// remove '<key>='
s = s.right(s.length() - key.length() - 1);
message.replace(cap.start, cap.end - cap.start, s);
} else {
QTC_CHECK(false);
}
break;
}
default:
message.replace(cap.start, cap.end - cap.start, miCap.data());
}
}
} else {
QTC_CHECK(false);
}
}
showMessage(message);
emit appendMessageRequested(message, NormalMessageFormat, true);
}
void GdbEngine::handleTracepointModified(const GdbMi &data)
{
QString ba = data.toString();
// remove original-location
const int pos1 = ba.indexOf("original-location=");
const int pos2 = ba.indexOf(":", pos1 + 17);
int pos3 = ba.indexOf('"', pos2 + 1);
if (ba[pos3 + 1] == ',')
++pos3;
ba.remove(pos1, pos3 - pos1 + 1);
GdbMi res;
res.fromString(ba);
BreakHandler *handler = breakHandler();
Breakpoint bp;
for (const GdbMi &bkpt : res) {
const QString nr = bkpt["number"].data();
if (nr.contains('.')) {
// A sub-breakpoint.
QTC_ASSERT(bp, continue);
SubBreakpoint loc = bp->findOrCreateSubBreakpoint(nr);
loc->params.updateFromGdbOutput(bkpt);
loc->params.type = bp->type();
if (bp->isTracepoint()) {
loc->params.tracepoint = true;
loc->params.message = bp->message();
}
} else {
// A primary breakpoint.
bp = handler->findBreakpointByResponseId(nr);
if (bp)
bp->updateFromGdbOutput(bkpt);
}
}
QTC_ASSERT(bp, return);
bp->adjustMarker();
}
bool GdbEngine::acceptsBreakpoint(const BreakpointParameters &bp) const
{
if (runParameters().startMode == AttachToCore)
@@ -2387,31 +2605,100 @@ void GdbEngine::insertBreakpoint(const Breakpoint &bp)
cmd.function = "catch syscall";
cmd.callback = handleCatch;
} else {
int spec = requested.threadSpec;
if (requested.isTracepoint()) {
cmd.function = "-break-insert -a -f ";
if (boolSetting(UsePseudoTracepoints)) {
cmd.function = "createTracepoint";
if (requested.oneShot)
cmd.arg("temporary", true);
if (int ignoreCount = requested.ignoreCount)
cmd.arg("ignore_count", ignoreCount);
QString condition = requested.condition;
if (!condition.isEmpty())
cmd.arg("condition", condition.replace('"', "\\\""));
if (spec >= 0)
cmd.arg("thread", spec);
updateTracepointCaptures(bp);
QVariant tpCaps = bp->property(tracepointCapturePropertyName);
if (tpCaps.isValid()) {
QJsonArray caps;
foreach (const auto &tpCap, tpCaps.toList()) {
TracepointCaptureData data = tpCap.value<TracepointCaptureData>();
QJsonArray cap;
cap.append(static_cast<int>(data.type));
if (data.expression.isValid())
cap.append(data.expression.toString());
else
cap.append(QJsonValue::Null);
caps.append(cap);
}
cmd.arg("caps", caps);
}
// for dumping of expressions
const static bool alwaysVerbose = qEnvironmentVariableIsSet("QTC_DEBUGGER_PYTHON_VERBOSE");
cmd.arg("passexceptions", alwaysVerbose);
cmd.arg("fancy", boolSetting(UseDebuggingHelpers));
cmd.arg("autoderef", boolSetting(AutoDerefPointers));
cmd.arg("dyntype", boolSetting(UseDynamicType));
cmd.arg("qobjectnames", boolSetting(ShowQObjectNames));
cmd.arg("nativemixed", isNativeMixedActive());
cmd.arg("stringcutoff", action(MaximalStringLength)->value().toString());
cmd.arg("displaystringlimit", action(DisplayStringLimit)->value().toString());
cmd.arg("spec", breakpointLocation2(requested));
cmd.callback = [this, bp](const DebuggerResponse &r) { handleTracepointInsert(r, bp); };
} else {
cmd.function = "-break-insert -a -f ";
if (requested.oneShot)
cmd.function += "-t ";
if (!requested.enabled)
cmd.function += "-d ";
if (int ignoreCount = requested.ignoreCount)
cmd.function += "-i " + QString::number(ignoreCount) + ' ';
QString condition = requested.condition;
if (!condition.isEmpty())
cmd.function += " -c \"" + condition.replace('"', "\\\"") + "\" ";
cmd.function += breakpointLocation(requested);
cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakInsert1(r, bp); };
}
} else {
int spec = requested.threadSpec;
cmd.function = "-break-insert ";
if (spec >= 0)
cmd.function += "-p " + QString::number(spec);
cmd.function += " -f ";
}
if (requested.oneShot)
cmd.function += "-t ";
if (requested.oneShot)
cmd.function += "-t ";
if (!requested.enabled)
cmd.function += "-d ";
if (!requested.enabled)
cmd.function += "-d ";
if (int ignoreCount = requested.ignoreCount)
cmd.function += "-i " + QString::number(ignoreCount) + ' ';
if (int ignoreCount = requested.ignoreCount)
cmd.function += "-i " + QString::number(ignoreCount) + ' ';
QString condition = requested.condition;
if (!condition.isEmpty())
cmd.function += " -c \"" + condition.replace('"', "\\\"") + "\" ";
QString condition = requested.condition;
if (!condition.isEmpty())
cmd.function += " -c \"" + condition.replace('"', "\\\"") + "\" ";
cmd.function += breakpointLocation(requested);
cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakInsert1(r, bp); };
cmd.function += breakpointLocation(requested);
cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakInsert1(r, bp); };
}
}
cmd.flags = NeedsTemporaryStop;
runCommand(cmd);
@@ -4831,3 +5118,4 @@ DebuggerEngine *createGdbEngine()
} // namespace Debugger
Q_DECLARE_METATYPE(Debugger::Internal::GdbMi)
Q_DECLARE_METATYPE(Debugger::Internal::TracepointCaptureData)