diff --git a/share/qtcreator/debugger/dumper.py b/share/qtcreator/debugger/dumper.py index 519480dafc4..c31e20840c9 100644 --- a/share/qtcreator/debugger/dumper.py +++ b/share/qtcreator/debugger/dumper.py @@ -942,7 +942,7 @@ class DumperBase(): for (k, v) in list(value.items())]) + '}' if isinstance(value, list): return '[' + ','.join([self.resultToMi(k) - for k in list(value.items())]) + ']' + for k in value]) + ']' return '"%s"' % value def variablesToMi(self, value, prefix): diff --git a/share/qtcreator/debugger/gdbbridge.py b/share/qtcreator/debugger/gdbbridge.py index 53159ae2303..46738743230 100644 --- a/share/qtcreator/debugger/gdbbridge.py +++ b/share/qtcreator/debugger/gdbbridge.py @@ -38,6 +38,8 @@ import tempfile from dumper import DumperBase, Children, toInteger, TopLevelItem from utils import TypeCode +from gdbtracepoint import * + ####################################################################### # @@ -1466,7 +1468,55 @@ class Dumper(DumperBase): print(timeit.repeat('theDumper.fetchVariables(%s)' % args, 'from __main__ import theDumper', number=10)) + def tracepointModified(self, tp): + self.tpExpressions = {} + self.tpExpressionWarnings = [] + s = self.resultToMi(tp.dicts()) + def handler(): + print("tracepointmodified=%s" % s) + gdb.post_event(handler) + def tracepointHit(self, tp, result): + expressions = '{' + ','.join(["%s=%s" % (k,v) for k,v in self.tpExpressions.items()]) + '}' + warnings = [] + if 'warning' in result.keys(): + warnings.append(result.pop('warning')) + warnings += self.tpExpressionWarnings + r = self.resultToMi(result) + w = self.resultToMi(warnings) + def handler(): + print("tracepointhit={result=%s,expressions=%s,warnings=%s}" % (r, expressions, w)) + gdb.post_event(handler) + + def tracepointExpression(self, tp, expression, value, args): + key = "x" + str(len(self.tpExpressions)) + if (isinstance(value, gdb.Value)): + try: + val = self.fromNativeValue(value) + self.prepare(args) + with TopLevelItem(self, expression): + self.putItem(val) + self.tpExpressions[key] = self.output + except Exception as e: + self.tpExpressions[key] = '""' + self.tpExpressionWarnings.append(str(e)) + elif (isinstance(value, Exception)): + self.tpExpressions[key] = '""' + self.tpExpressionWarnings.append(str(value)) + else: + self.tpExpressions[key] = '""' + self.tpExpressionWarnings.append('Unknown expression value type') + return key + + def createTracepoint(self, args): + """ + Creates a tracepoint + """ + tp = GDBTracepoint.create(args, + onModified=self.tracepointModified, + onHit=self.tracepointHit, + onExpression=lambda tp, expr, val: self.tracepointExpression(tp, expr, val, args)) + self.reportResult("tracepoint=%s" % self.resultToMi(tp.dicts()), args) class CliDumper(Dumper): def __init__(self): Dumper.__init__(self) diff --git a/share/qtcreator/debugger/gdbtracepoint.py b/share/qtcreator/debugger/gdbtracepoint.py new file mode 100644 index 00000000000..7d27450a251 --- /dev/null +++ b/share/qtcreator/debugger/gdbtracepoint.py @@ -0,0 +1,399 @@ +############################################################################ +# +# Copyright (C) 2021 The Qt Company Ltd. +# Contact: https://www.qt.io/licensing/ +# +# This file is part of Qt Creator. +# +# Commercial License Usage +# Licensees holding valid commercial Qt licenses may use this file in +# accordance with the commercial license agreement provided with the +# Software or, alternatively, in accordance with the terms contained in +# a written agreement between you and The Qt Company. For licensing terms +# and conditions see https://www.qt.io/terms-conditions. For further +# information use the contact form at https://www.qt.io/contact-us. +# +# GNU General Public License Usage +# Alternatively, this file may be used under the terms of the GNU +# General Public License version 3 as published by the Free Software +# Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +# included in the packaging of this file. Please review the following +# information to ensure the GNU General Public License requirements will +# be met: https://www.gnu.org/licenses/gpl-3.0.html. +# +############################################################################ + +import gdb +import sys +import time + +# for ProcessName capture +try: + import psutil +except: + psutil = None + +# Caps types +Address, \ +Caller, \ +Callstack, \ +FilePos, \ +Function, \ +Pid, \ +ProcessName, \ +Tick, \ +Tid, \ +ThreadName, \ +Expression, \ + = range(0, 11) + +class GDBTracepoint(gdb.Breakpoint): + """ + Python Breakpoint extension for "tracepoints", breakpoints that do not stop the inferior + """ + + @staticmethod + def create(args, onModified, onHit, onExpression): + """ + Static creator function + """ + tp_kwargs = {} + if 'temporary' in args.keys(): + tp_kwargs['temporary'] = args['temporary'] + spec = args['spec'] + tp = GDBTracepoint(spec, **tp_kwargs) + tp.onModified = onModified + tp.onHit = onHit + tp.onExpression = onExpression + if 'ignore_count' in args.keys(): + tp.ignore_count = args['ignore_count'] + if 'enabled' in args.keys(): + tp.enabled = args['enabled'] + if 'thread' in args.keys(): + tp.thread = args['thread'] + if 'condition' in args.keys(): + tp.condition = args['condition'] + if 'caps' in args.keys(): + for ce in args['caps']: + tp.addCaps(ce[0], ce[1]) + return tp + + def __init__(self, spec, **kwargs): + """ + Constructor + """ + kwargs['internal'] = True + super(GDBTracepoint, self).__init__(spec, **kwargs) + self.caps = [] + + _hexSize = 8 if sys.maxsize > 2**32 else 4 + _hasMonotonicTime = False if sys.version_info[0] <= 2 or (sys.version_info[0] == 3 and sys.version_info[1] < 3) else True + + def dicts(self): + """ + Returns dictionareis for mi representation + """ + results = [] + result = {} + result['number'] = str(self.number) + result['enabled'] = 'y' if self.enabled else 'n' + result['type'] = 'pseudo_tracepoint' + result['disp'] = 'del' if self.temporary else 'keep' + result['times'] = str(self.hit_count) + result['original-location'] = self.location + try: + d = gdb.decode_line(self.location) + if d[1] is None: + result['addr'] = '' + result['pending'] = self.location + results.append(result) + else: + if len(d[1]) > 1: + result['addr'] = '' + results.append(result) + for i, sl in enumerate(d[1]): + result_ = {} + result_['number'] = result['number'] + "." + str(i + 1) + result_['enabled'] = 'y' if self.enabled else 'n' + if sl.pc is None: + result_['addr'] = '' + else: + result_['addr'] = '{0:#0{1}x}'.format(sl.pc, self._hexSize + 2) + if sl.symtab and sl.symtab.is_valid(): + func = self._getFunctionFromAddr(sl.pc) + if func: + result_['func'] = func.print_name + result_['file'] = sl.symtab.filename + result_['fullname'] = sl.symtab.fullname() + result_['line'] = sl.line + results.append(result_) + else: + sl = d[1][0] + if sl.pc is None: + result['addr'] = '' + else: + result['addr'] = '{0:#0{1}x}'.format(sl.pc, self._hexSize + 2) + if sl.symtab and sl.symtab.is_valid(): + func = self._getFunctionFromAddr(sl.pc) + if func: + result['func'] = func.print_name + result['file'] = sl.symtab.filename + result['fullname'] = sl.symtab.fullname() + result['line'] = sl.line + results.append(result) + except Exception as e: + import traceback + traceback.print_exc() + result['addr'] = '' + result['pending'] = self.location + results.append(result) + return results + + def addCaps(self, capsType, expression=None): + """ + Adds capture expressions for a tracepoint + + :param caps_type: Type of capture + :param expression: Expression for Expression caps type + """ + if capsType != Expression: + expression = None + else: + if expression is None: + expression = '' + self.caps.append((self.capsMap[capsType], expression)) + + def stop(self): + """ + Overridden stop function, this evaluates conditions and captures data from the inferior + + :return: Always False + """ + try: + self.onModified(self) + result = {} + result['number'] = self.number + try: + if self.condition: + try: + result = gdb.parse_and_eval(self.condition) + if result.type.code == gdb.TYPE_CODE_BOOL and str(result) == 'false': + return False + except: + pass + if self.ignore_count > 0: + return False + if self.thread and gdb.selected_thread().global_num != self.thread: + return False + except Exception as e: + result['warning'] = str(e) + self.onHit(self, result) + return False + if len(self.caps) > 0: + caps = [] + try: + for func, expr in self.caps: + if expr is None: + caps.append(func(self)) + else: + caps.append(func(self, expr)) + except Exception as e: + result['warning'] = str(e) + self.onHit(self, result) + return False + result['caps'] = caps + self.onHit(self, result) + return False + except: + # Always return false, regardless... + return False + + def _getFunctionFromAddr(self, addr): + try: + block = gdb.block_for_pc(addr) + while block and not block.function: + block = block.superblock + if block is None: + return None + return block.function + except: + return None + + def _getAddress(self): + """ + Capture function for Address + """ + try: + frame = gdb.selected_frame() + if not (frame is None) and (frame.is_valid()): + return '{0:#0{1}x}'.format(frame.pc(), self._hexSize + 2) + except Exception as e: + return str(e) + return '' + + def _getCaller(self): + """ + Capture function for Caller + """ + try: + frame = gdb.selected_frame() + if not (frame is None) and (frame.is_valid()): + frame = frame.older() + if not (frame is None) and (frame.is_valid()): + name = frame.name() + if name is None: + return '' + return name + except Exception as e: + return str(e) + return '' + + def _getCallstack(self): + """ + Capture function for Callstack + """ + try: + frames = [] + frame = gdb.selected_frame() + if (frame is None) or (not frame.is_valid()): + frames.append('') + return str(frames) + while not (frame is None): + func = frame.function() + if func is None: + frames.append('{0:#0{1}x}'.format(frame.pc(), self._hexSize + 2)) + else: + sl = frame.find_sal() + if sl is None: + frames.append(func.symtab.filename) + else: + frames.append(func.symtab.filename + ':' + str(sl.line)) + frame = frame.older() + return frames + except Exception as e: + return str(e) + + def _getFilePos(self): + """ + Capture function for FilePos + """ + try: + frame = gdb.selected_frame() + if (frame is None) or (not frame.is_valid()): + return '' + sl = frame.find_sal() + if sl is None: + return '' + return sl.symtab.filename + ':' + str(sl.line) + except Exception as e: + return str(e) + + def _getFunction(self): + """ + Capture function for Function + """ + try: + frame = gdb.selected_frame() + if not (frame is None): + return str(frame.name()) + except Exception as e: + return str(e) + return '' + + def _getPid(self): + """ + Capture function for Pid + """ + try: + thread = gdb.selected_thread() + if not (thread is None): + (pid, lwpid, tid) = thread.ptid + return str(pid) + except Exception as e: + return str(e) + return '' + + def _getProcessName(slef): + """ + Capture for ProcessName + """ + # gdb does not expose process name, neither does (standard) python + # You can use for example psutil, but it might not be present. + # Default to name of thread with ID 1 + inf = gdb.selected_inferior() + if psutil is None: + try: + if inf is None: + return '' + threads = filter(lambda t: t.num == 1, list(inf.threads())) + if len(threads) < 1: + return '' + thread = threads[0] + # use thread name + return thread.name + except Exception as e: + return str(e) + else: + return psutil.Process(inf.pid).name() + + def _getTick(self): + """ + Capture function for Tick + """ + if self._hasMonotonicTime: + return str(int(time.monotonic() * 1000)) + else: + return '' + + def _getTid(self): + """ + Capture function for Tid + """ + try: + thread = gdb.selected_thread() + if not (thread is None): + (pid, lwpid, tid) = thread.ptid + if tid == 0: + return str(lwpid) + else: + return str(tid) + except Exception as e: + return str(e) + return '' + + def _getThreadName(self): + """ + Capture function for ThreadName + """ + try: + thread = gdb.selected_thread() + if not (thread is None): + return str(thread.name) + except Exception as e: + return str(e) + return '' + + def _getExpression(self, expression): + """ + Capture function for Expression + + :param expr: The expression to evaluate + """ + try: + value = gdb.parse_and_eval(expression) + if value: + return self.onExpression(self, expression, value) + except Exception as e: + return self.onExpression(self, expression, e) + + capsMap = {Address: _getAddress, + Caller: _getCaller, + Callstack: _getCallstack, + FilePos: _getFilePos, + Function: _getFunction, + Pid: _getPid, + ProcessName: _getProcessName, + Tid: _getTid, + Tick: _getTick, + ThreadName: _getThreadName, + Expression: _getExpression} diff --git a/src/plugins/debugger/breakhandler.cpp b/src/plugins/debugger/breakhandler.cpp index fa40001b897..73f0f518cf5 100644 --- a/src/plugins/debugger/breakhandler.cpp +++ b/src/plugins/debugger/breakhandler.cpp @@ -611,17 +611,13 @@ void BreakpointDialog::setPartsEnabled(unsigned partsMask) m_lineEditModule->setEnabled(partsMask & ModulePart); m_labelTracepoint->setEnabled(partsMask & TracePointPart); - m_labelTracepoint->hide(); m_checkBoxTracepoint->setEnabled(partsMask & TracePointPart); - m_checkBoxTracepoint->hide(); m_labelCommands->setEnabled(partsMask & CommandPart); m_textEditCommands->setEnabled(partsMask & CommandPart); m_labelMessage->setEnabled(partsMask & TracePointPart); - m_labelMessage->hide(); m_lineEditMessage->setEnabled(partsMask & TracePointPart); - m_lineEditMessage->hide(); } void BreakpointDialog::clearOtherParts(unsigned partsMask) @@ -2106,6 +2102,8 @@ QString BreakpointItem::msgBreakpointTriggered(const QString &threadId) const QVariant SubBreakpointItem::data(int column, int role) const { if (role == Qt::DecorationRole && column == 0) { + if (params.tracepoint) + return Icons::TRACEPOINT.icon(); return params.enabled ? Icons::BREAKPOINT.icon() : Icons::BREAKPOINT_DISABLED.icon(); } diff --git a/src/plugins/debugger/breakhandler.h b/src/plugins/debugger/breakhandler.h index ad334d10b8a..20bd7365a12 100644 --- a/src/plugins/debugger/breakhandler.h +++ b/src/plugins/debugger/breakhandler.h @@ -176,6 +176,7 @@ public: void setIgnoreCount(int count) { m_parameters.ignoreCount = count; } void setCommand(const QString &command) { m_parameters.command = command; } void setCondition(const QString &condition) { m_parameters.condition = condition; } + void setMessage(const QString& message) { m_parameters.message = message; } QString msgWatchpointByAddressTriggered(quint64 address) const; QString msgWatchpointByAddressTriggered(quint64 address, const QString &threadId) const; diff --git a/src/plugins/debugger/debuggeractions.cpp b/src/plugins/debugger/debuggeractions.cpp index 53e04570e7c..14f571ab31f 100644 --- a/src/plugins/debugger/debuggeractions.cpp +++ b/src/plugins/debugger/debuggeractions.cpp @@ -506,6 +506,12 @@ DebuggerSettings::DebuggerSettings() item->setDefaultValue(true); insertItem(UseAnnotationsInMainEditor, item); + item = new SavedAction; + item->setSettingsKey(debugModeGroup, "UsePseudoTracepoints"); + item->setCheckable(true); + item->setDefaultValue(true); + insertItem(UsePseudoTracepoints, item); + item = new SavedAction; item->setSettingsKey(debugModeGroup, "UseToolTips"); item->setText(tr("Use tooltips in main editor when debugging")); diff --git a/src/plugins/debugger/debuggeractions.h b/src/plugins/debugger/debuggeractions.h index 8137f4c4008..6f590b35121 100644 --- a/src/plugins/debugger/debuggeractions.h +++ b/src/plugins/debugger/debuggeractions.h @@ -134,6 +134,7 @@ enum DebuggerActionCode WarnOnReleaseBuilds, MultiInferior, IntelFlavor, + UsePseudoTracepoints, // Stack MaximalStackDepth, diff --git a/src/plugins/debugger/gdb/gdbengine.cpp b/src/plugins/debugger/gdb/gdbengine.cpp index 87f77402cea..11c13d67c6b 100644 --- a/src/plugins/debugger/gdb/gdbengine.cpp +++ b/src/plugins/debugger/gdb/gdbengine.cpp @@ -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( + {type, {}, match.capturedStart(2), match.capturedEnd(2)}); + } else { + QString expression = t.mid(1, t.length() - 2); + caps << QVariant::fromValue( + {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 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(); + 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 '=' + 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(); + QJsonArray cap; + cap.append(static_cast(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) diff --git a/src/plugins/debugger/gdb/gdbengine.h b/src/plugins/debugger/gdb/gdbengine.h index b04bc055698..ca92bef8c6f 100644 --- a/src/plugins/debugger/gdb/gdbengine.h +++ b/src/plugins/debugger/gdb/gdbengine.h @@ -223,6 +223,9 @@ private: ////////// General Interface ////////// void handleBreakCondition(const DebuggerResponse &response, const Breakpoint &bp); void handleBreakThreadSpec(const DebuggerResponse &response, const Breakpoint &bp); void handleBreakLineNumber(const DebuggerResponse &response, const Breakpoint &bp); + void handleTracepointInsert(const DebuggerResponse &response, const Breakpoint &bp); + void handleTracepointHit(const GdbMi &data); + void handleTracepointModified(const GdbMi &data); void handleInsertInterpreterBreakpoint(const DebuggerResponse &response, const Breakpoint &bp); void handleInterpreterBreakpointModified(const GdbMi &data); void handleWatchInsert(const DebuggerResponse &response, const Breakpoint &bp); @@ -231,6 +234,7 @@ private: ////////// General Interface ////////// QString breakpointLocation(const BreakpointParameters &data); // For gdb/MI. QString breakpointLocation2(const BreakpointParameters &data); // For gdb/CLI fallback. QString breakLocation(const QString &file) const; + void updateTracepointCaptures(const Breakpoint &bp); // // Modules specific stuff diff --git a/src/plugins/debugger/gdb/gdboptionspage.cpp b/src/plugins/debugger/gdb/gdboptionspage.cpp index 0580e050e3b..643802fa594 100644 --- a/src/plugins/debugger/gdb/gdboptionspage.cpp +++ b/src/plugins/debugger/gdb/gdboptionspage.cpp @@ -153,6 +153,11 @@ GdbOptionsPageWidget::GdbOptionsPageWidget() "GDB shows by default AT&&T style disassembly." "")); + auto checkBoxUsePseudoTracepoints = new QCheckBox(groupBoxGeneral); + checkBoxUsePseudoTracepoints->setText(GdbOptionsPage::tr("Use pseudo message tracepoints")); + checkBoxUsePseudoTracepoints->setToolTip(GdbOptionsPage::tr( + "Uses python to extend the ordinary GDB breakpoint class.")); + QString howToUsePython = GdbOptionsPage::tr( "

To execute simple Python commands, prefix them with \"python\".

" "

To execute sequences of Python commands spanning multiple lines " @@ -225,6 +230,7 @@ GdbOptionsPageWidget::GdbOptionsPageWidget() formLayout->addRow(checkBoxLoadGdbInit); formLayout->addRow(checkBoxLoadGdbDumpers); formLayout->addRow(checkBoxIntelFlavor); + formLayout->addRow(checkBoxUsePseudoTracepoints); auto startLayout = new QGridLayout(groupBoxStartupCommands); startLayout->addWidget(textEditStartupCommands, 0, 0, 1, 1); @@ -248,6 +254,7 @@ GdbOptionsPageWidget::GdbOptionsPageWidget() group.insert(action(IntelFlavor), checkBoxIntelFlavor); group.insert(action(UseMessageBoxForSignals), checkBoxUseMessageBoxForSignals); group.insert(action(SkipKnownFrames), checkBoxSkipKnownFrames); + group.insert(action(UsePseudoTracepoints), checkBoxUsePseudoTracepoints); //lineEditSelectedPluginBreakpointsPattern-> // setEnabled(action(SelectedPluginBreakpoints)->value().toBool());