diff --git a/share/qtcreator/debugger/dumper.py b/share/qtcreator/debugger/dumper.py
index 9b31a54289..c009f2a2d9 100644
--- a/share/qtcreator/debugger/dumper.py
+++ b/share/qtcreator/debugger/dumper.py
@@ -939,7 +939,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 f5ee86c349..bf4f5ab069 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 *
+
 
 #######################################################################
 #
@@ -1463,7 +1465,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] = '"<N/A>"'
+                self.tpExpressionWarnings.append(str(e))
+        elif (isinstance(value, Exception)):
+            self.tpExpressions[key] = '"<N/A>"'
+            self.tpExpressionWarnings.append(str(value))
+        else:
+            self.tpExpressions[key] = '"<N/A>"'
+            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 0000000000..dc6981f1fd
--- /dev/null
+++ b/share/qtcreator/debugger/gdbtracepoint.py
@@ -0,0 +1,399 @@
+############################################################################
+#
+# Copyright (C) 2019 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'] = '<PENDING>'
+                result['pending'] = self.location
+                results.append(result)
+            else:
+                if len(d[1]) > 1:
+                    result['addr'] = '<MULTIPLE>'
+                    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'] = '<no address>'
+                        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'] = '<no address>'
+                    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'] = '<PENDING>'
+            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 '<null address>'
+
+    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 '<unknown caller>'
+                    return name
+        except Exception as e:
+            return str(e)
+        return '<unknown caller>'
+
+    def _getCallstack(self):
+        """
+        Capture function for Callstack
+        """
+        try:
+            frames = []
+            frame = gdb.selected_frame()
+            if (frame is None) or (not frame.is_valid()):
+                frames.append('<unknown frame>')
+                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 '<unknown file pos>'
+            sl = frame.find_sal()
+            if sl is None:
+                return '<unknown file pos>'
+            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 '<unknown function>'
+
+    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 '<unknown pid>'
+
+    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 '<unknown process name>'
+                threads = filter(lambda t: t.num == 1, list(inf.threads()))
+                if len(threads) < 1:
+                    return '<unknown process name>'
+                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 '<monotonic time not available>'
+
+    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 '<unknown tid>'
+
+    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 '<unknown thread name>'
+
+    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 1de7d33269..1131d61306 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)
@@ -2062,6 +2058,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 bd24d6263d..51a175a188 100644
--- a/src/plugins/debugger/breakhandler.h
+++ b/src/plugins/debugger/breakhandler.h
@@ -174,6 +174,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 631bb88154..f811e70d7c 100644
--- a/src/plugins/debugger/debuggeractions.cpp
+++ b/src/plugins/debugger/debuggeractions.cpp
@@ -497,6 +497,12 @@ DebuggerSettings::DebuggerSettings()
     item->setDefaultValue(false);
     insertItem(IntelFlavor, 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 30dbe7b9af..a1005d9d74 100644
--- a/src/plugins/debugger/debuggeractions.h
+++ b/src/plugins/debugger/debuggeractions.h
@@ -133,6 +133,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 3e76d5a782..6fa976a1e2 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;
+};
+
+static 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.
@@ -2178,6 +2215,7 @@ void GdbEngine::handleCatchInsert(const DebuggerResponse &response, const Breakp
 void GdbEngine::handleBkpt(const GdbMi &bkpt, const Breakpoint &bp)
 {
     QTC_ASSERT(bp, return);
+    auto usePseudoTracepoints = boolSetting(UsePseudoTracepoints);
     const QString nr = bkpt["number"].data();
     if (nr.contains('.')) {
         // A sub-breakpoint.
@@ -2185,6 +2223,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;
     }
 
@@ -2199,12 +2241,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)
@@ -2350,6 +2398,178 @@ 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)"
+        "|{[^}]*})");
+    auto message = bp->globalBreakpoint()->requestedParameters().message;
+    if (message.isEmpty())
+    {
+        bp->setProperty(tracepointCapturePropertyName, {});
+        return;
+    }
+    QVariantList caps;
+    QRegularExpressionMatch match = capsRegExp.match(message, 0);
+    while (match.hasMatch()) {
+        auto 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 {
+            auto 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);
+        }
+    }
+    auto message = bp->message();
+    auto caps = bp->property(tracepointCapturePropertyName);
+    if (caps.isValid()) {
+        auto capsList = caps.toList();
+        const auto& miCaps = result["caps"];
+        if (capsList.length() == miCaps.childCount()) {
+            // reverse iterate to make start/end correct
+            for (int i = capsList.length() - 1; i >= 0; --i)
+            {
+               auto cap = capsList.at(i).value<TracepointCaptureData>();
+               const auto &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 auto& 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::stateAcceptsBreakpointChanges() const
 {
     switch (state()) {
@@ -2417,31 +2637,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);
+                auto tpCaps = bp->property(tracepointCapturePropertyName);
+                if (tpCaps.isValid()) {
+                    QJsonArray caps;
+                    foreach(const auto& tpCap, tpCaps.toList()) {
+                        auto 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 expresions
+                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);
@@ -4863,3 +5152,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 b354111bc9..4527d8597d 100644
--- a/src/plugins/debugger/gdb/gdbengine.h
+++ b/src/plugins/debugger/gdb/gdbengine.h
@@ -232,6 +232,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);
@@ -240,6 +243,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 a3c68a563a..cd50bae58c 100644
--- a/src/plugins/debugger/gdb/gdboptionspage.cpp
+++ b/src/plugins/debugger/gdb/gdboptionspage.cpp
@@ -153,6 +153,12 @@ GdbOptionsPageWidget::GdbOptionsPageWidget()
         "<html><head/><body>GDB shows by default AT&&T style disassembly."
         "</body></html>"));
 
+    auto checkBoxUsePseudoTracepoints = new QCheckBox(groupBoxGeneral);
+    checkBoxUsePseudoTracepoints->setText(GdbOptionsPage::tr("Use pseudo message tracepoints"));
+    checkBoxUsePseudoTracepoints->setToolTip(GdbOptionsPage::tr(
+        "<html><head/><body>Uses python to extend the ordinary GDB breakpoint class"
+        "to simulate Visual Studio tracepoints.</body></html>"));
+
     QString howToUsePython = GdbOptionsPage::tr(
         "<p>To execute simple Python commands, prefix them with \"python\".</p>"
         "<p>To execute sequences of Python commands spanning multiple lines "
@@ -225,6 +231,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 +255,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());
