gdbengine.cpp 156 KB
Newer Older
1
/**************************************************************************
con's avatar
con committed
2
3
4
**
** This file is part of Qt Creator
**
5
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
con's avatar
con committed
6
**
7
** Contact: Nokia Corporation (qt-info@nokia.com)
con's avatar
con committed
8
**
9
** Commercial Usage
10
**
11
12
13
14
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
15
**
16
** GNU Lesser General Public License Usage
17
**
18
19
20
21
22
23
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24
**
25
** If you are unsure which license is appropriate for your use, please
hjk's avatar
hjk committed
26
** contact the sales department at http://qt.nokia.com/contact.
con's avatar
con committed
27
**
28
**************************************************************************/
con's avatar
con committed
29

30
31
#define QT_NO_CAST_FROM_ASCII

con's avatar
con committed
32
#include "gdbengine.h"
33
#include "gdboptionspage.h"
34
35
#include "trkoptions.h"
#include "trkoptionspage.h"
hjk's avatar
hjk committed
36
#include "trkgdbadapter.h"
con's avatar
con committed
37

38
#include "watchutils.h"
39
#include "debuggeractions.h"
40
#include "debuggeragents.h"
con's avatar
con committed
41
42
#include "debuggerconstants.h"
#include "debuggermanager.h"
43
#include "debuggertooltip.h"
con's avatar
con committed
44
45
46
47
48
49
50
51
#include "gdbmi.h"
#include "procinterrupt.h"

#include "breakhandler.h"
#include "moduleshandler.h"
#include "registerhandler.h"
#include "stackhandler.h"
#include "watchhandler.h"
52
#include "sourcefileswindow.h"
con's avatar
con committed
53

54
#include "debuggerdialogs.h"
con's avatar
con committed
55

hjk's avatar
hjk committed
56
#include <utils/qtcassert.h>
57
#include <texteditor/itexteditor.h>
58
#include <coreplugin/icore.h>
hjk's avatar
hjk committed
59

con's avatar
con committed
60
61
62
#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
63
#include <QtCore/QMetaObject>
con's avatar
con committed
64
65
#include <QtCore/QTime>
#include <QtCore/QTimer>
66
#include <QtCore/QTextStream>
con's avatar
con committed
67
68

#include <QtGui/QAction>
69
#include <QtCore/QCoreApplication>
con's avatar
con committed
70
71
72
#include <QtGui/QLabel>
#include <QtGui/QMainWindow>
#include <QtGui/QMessageBox>
73
74
#include <QtGui/QDialogButtonBox>
#include <QtGui/QPushButton>
75
#ifdef Q_OS_WIN
76
#    include "shared/sharedlibraryinjector.h"
77
#endif
con's avatar
con committed
78

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
79
#ifdef Q_OS_UNIX
con's avatar
con committed
80
81
82
#include <unistd.h>
#include <dlfcn.h>
#endif
hjk's avatar
hjk committed
83
#include <ctype.h>
con's avatar
con committed
84

85
86
namespace Debugger {
namespace Internal {
con's avatar
con committed
87
88
89
using namespace Debugger::Constants;

//#define DEBUG_PENDING  1
90
//#define DEBUG_SUBITEM  1
con's avatar
con committed
91
92
93
94

#if DEBUG_PENDING
#   define PENDING_DEBUG(s) qDebug() << s
#else
Roberto Raggi's avatar
Roberto Raggi committed
95
#   define PENDING_DEBUG(s)
con's avatar
con committed
96
97
#endif

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
98
99
#define STRINGIFY_INTERNAL(x) #x
#define STRINGIFY(x) STRINGIFY_INTERNAL(x)
100
101
#define CB(callback) &GdbEngine::callback, STRINGIFY(callback)

con's avatar
con committed
102
103
104
105
106
107
static int &currentToken()
{
    static int token = 0;
    return token;
}

108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// reads a MI-encoded item frome the consolestream
static bool parseConsoleStream(const GdbResultRecord &record, GdbMi *contents)
{
    GdbMi output = record.data.findChild("consolestreamoutput");
    QByteArray out = output.data();

    int markerPos = out.indexOf('"') + 1; // position of 'success marker'
    if (markerPos == 0 || out.at(markerPos) == 'f') {  // 't' or 'f'
        // custom dumper produced no output
        return false;
    }

    out = out.mid(markerPos +  1);
    out = out.left(out.lastIndexOf('"'));
    // optimization: dumper output never needs real C unquoting
    out.replace('\\', "");
    out = "dummy={" + out + "}";

    contents->fromString(out);
    //qDebug() << "CONTENTS" << contents->toString(true);
    return contents->isValid();
}

static QByteArray parsePlainConsoleStream(const GdbResultRecord &record)
{
    GdbMi output = record.data.findChild("consolestreamoutput");
    QByteArray out = output.data();
    // FIXME: proper decoding needed
    if (out.endsWith("\\n"))
        out.chop(2);
    while (out.endsWith('\n') || out.endsWith(' '))
        out.chop(1);
    int pos = out.indexOf(" = ");
    return out.mid(pos + 3);
}

hjk's avatar
hjk committed
144
145
///////////////////////////////////////////////////////////////////////
//
hjk's avatar
hjk committed
146
// PlainGdbAdapter
hjk's avatar
hjk committed
147
148
149
//
///////////////////////////////////////////////////////////////////////

hjk's avatar
hjk committed
150
void PlainGdbAdapter::attach()
hjk's avatar
hjk committed
151
{
hjk's avatar
hjk committed
152
    QFileInfo fi(m_engine->startParameters().executable);
153
    m_engine->postCommand(_("-file-exec-and-symbols \"%1\"").arg(fi.absoluteFilePath()),
hjk's avatar
hjk committed
154
155
156
        &GdbEngine::handleFileExecAndSymbols, "handleFileExecAndSymbols");
}

hjk's avatar
hjk committed
157
158
void PlainGdbAdapter::interruptInferior()
{
hjk's avatar
hjk committed
159
    if (m_engine->startMode() == StartRemote) {
hjk's avatar
hjk committed
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
        m_engine->postCommand(_("-exec-interrupt"));
        return;
    }

    const qint64 attachedPID = m_engine->inferiorPid();
    if (attachedPID <= 0) {
        m_engine->debugMessage(
            _("TRYING TO INTERRUPT INFERIOR BEFORE PID WAS OBTAINED"));
        return;
    }

    if (!interruptProcess(attachedPID))
        m_engine->debugMessage(_("CANNOT INTERRUPT %1").arg(attachedPID));
}

con's avatar
con committed
175
176
177
178
179
180
///////////////////////////////////////////////////////////////////////
//
// GdbEngine
//
///////////////////////////////////////////////////////////////////////

hjk's avatar
hjk committed
181
GdbEngine::GdbEngine(DebuggerManager *parent, AbstractGdbAdapter *gdbAdapter) :
182
#ifdef Q_OS_WIN // Do injection loading with MinGW (call loading does not work with 64bit)
183
    m_dumperInjectionLoad(true),
184
#else
185
    m_dumperInjectionLoad(false),
186
#endif
hjk's avatar
hjk committed
187
    m_manager(parent),
188
    qq(parent->engineInterface())
con's avatar
con committed
189
{
hjk's avatar
hjk committed
190
    m_gdbAdapter = gdbAdapter;
hjk's avatar
hjk committed
191
    m_gdbAdapter->setEngine(this);
192
    m_stubProc.setMode(Core::Utils::ConsoleProcess::Debug);
193
194
195
#ifdef Q_OS_UNIX
    m_stubProc.setSettings(Core::ICore::instance()->settings());
#endif
196
197
    initializeVariables();
    initializeConnections();
con's avatar
con committed
198
199
200
201
}

GdbEngine::~GdbEngine()
{
hjk's avatar
hjk committed
202
    // prevent sending error messages afterwards
hjk's avatar
hjk committed
203
204
    m_gdbAdapter->disconnect(this);
    delete m_gdbAdapter;
con's avatar
con committed
205
206
}

207
void GdbEngine::initializeConnections()
con's avatar
con committed
208
209
{
    // Gdb Process interaction
hjk's avatar
hjk committed
210
    connect(m_gdbAdapter, SIGNAL(error(QProcess::ProcessError)),
211
        this, SLOT(gdbProcError(QProcess::ProcessError)));
hjk's avatar
hjk committed
212
    connect(m_gdbAdapter, SIGNAL(readyReadStandardOutput()),
213
        this, SLOT(readGdbStandardOutput()));
hjk's avatar
hjk committed
214
    connect(m_gdbAdapter, SIGNAL(readyReadStandardError()),
215
        this, SLOT(readGdbStandardError()));
hjk's avatar
hjk committed
216
    connect(m_gdbAdapter, SIGNAL(finished(int, QProcess::ExitStatus)),
hjk's avatar
hjk committed
217
        m_manager, SLOT(exitDebugger()));
hjk's avatar
hjk committed
218
    connect(m_gdbAdapter, SIGNAL(started()),
hjk's avatar
hjk committed
219
        this, SLOT(startDebugger2()));
220
221
222
223
224
225

    connect(&m_stubProc, SIGNAL(processError(QString)),
        this, SLOT(stubError(QString)));
    connect(&m_stubProc, SIGNAL(processStarted()),
        this, SLOT(stubStarted()));
    connect(&m_stubProc, SIGNAL(wrapperStopped()),
hjk's avatar
hjk committed
226
        m_manager, SLOT(exitDebugger()));
227
228
229

    connect(&m_uploadProc, SIGNAL(error(QProcess::ProcessError)),
        this, SLOT(uploadProcError(QProcess::ProcessError)));
230
231
232
233
    connect(&m_uploadProc, SIGNAL(readyReadStandardOutput()),
        this, SLOT(readUploadStandardOutput()));
    connect(&m_uploadProc, SIGNAL(readyReadStandardError()),
        this, SLOT(readUploadStandardError()));
234

con's avatar
con committed
235
    // Output
236
    connect(&m_outputCollector, SIGNAL(byteDelivery(QByteArray)),
237
        this, SLOT(readDebugeeOutput(QByteArray)));
con's avatar
con committed
238

239
    connect(this, SIGNAL(gdbOutputAvailable(int,QString)),
hjk's avatar
hjk committed
240
        m_manager, SLOT(showDebuggerOutput(int,QString)),
con's avatar
con committed
241
        Qt::QueuedConnection);
242
    connect(this, SIGNAL(gdbInputAvailable(int,QString)),
hjk's avatar
hjk committed
243
        m_manager, SLOT(showDebuggerInput(int,QString)),
con's avatar
con committed
244
        Qt::QueuedConnection);
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
245
    connect(this, SIGNAL(applicationOutputAvailable(QString)),
hjk's avatar
hjk committed
246
        m_manager, SLOT(showApplicationOutput(QString)),
con's avatar
con committed
247
        Qt::QueuedConnection);
248

249
    // FIXME: These trigger even if the engine is not active
250
251
252
253
254
255
    connect(theDebuggerAction(UseDebuggingHelpers), SIGNAL(valueChanged(QVariant)),
        this, SLOT(setUseDebuggingHelpers(QVariant)));
    connect(theDebuggerAction(DebugDebuggingHelpers), SIGNAL(valueChanged(QVariant)),
        this, SLOT(setDebugDebuggingHelpers(QVariant)));
    connect(theDebuggerAction(RecheckDebuggingHelpers), SIGNAL(triggered()),
        this, SLOT(recheckDebuggingHelperAvailability()));
con's avatar
con committed
256
257
}

258
259
void GdbEngine::initializeVariables()
{
260
    m_debuggingHelperState = DebuggingHelperUninitialized;
261
    m_gdbVersion = 100;
hjk's avatar
hjk committed
262
    m_gdbBuildVersion = -1;
263
264
265
266
267
268
269
270
271

    m_fullToShortName.clear();
    m_shortToFullName.clear();
    m_varToType.clear();

    m_modulesListOutdated = true;
    m_oldestAcceptableToken = -1;
    m_outputCodec = QTextCodec::codecForLocale();
    m_pendingRequests = 0;
272
    m_autoContinue = false;
273
    m_waitingForFirstBreakpointToBeHit = false;
274
    m_commandsToRunOnTemporaryBreak.clear();
275
    m_cookieForToken.clear();
276
277
278
279
280
281
282
283
284
285
    m_customOutputForToken.clear();

    m_pendingConsoleStreamOutput.clear();
    m_pendingTargetStreamOutput.clear();
    m_pendingLogStreamOutput.clear();

    m_inbuffer.clear();

    m_currentFunctionArgs.clear();
    m_currentFrame.clear();
286
    m_dumperHelper.clear();
287
288
289
290

    // FIXME: unhandled:
    //m_outputCodecState = QTextCodec::ConverterState();
    //OutputCollector m_outputCollector;
hjk's avatar
hjk committed
291
    //QProcess m_gdbAdapter;
292
293
    //QProcess m_uploadProc;
    //Core::Utils::ConsoleProcess m_stubProc;
294
295
}

con's avatar
con committed
296
297
298
void GdbEngine::gdbProcError(QProcess::ProcessError error)
{
    QString msg;
299
    bool kill = true;
con's avatar
con committed
300
301
    switch (error) {
        case QProcess::FailedToStart:
302
            kill = false;
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
303
            msg = tr("The Gdb process failed to start. Either the "
con's avatar
con committed
304
                "invoked program '%1' is missing, or you may have insufficient "
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
305
                "permissions to invoke the program.")
hjk's avatar
hjk committed
306
                .arg(theDebuggerStringSetting(GdbLocation));
307
            emitStartFailed();
con's avatar
con committed
308
309
            break;
        case QProcess::Crashed:
310
            kill = false;
con's avatar
con committed
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
            msg = tr("The Gdb process crashed some time after starting "
                "successfully.");
            break;
        case QProcess::Timedout:
            msg = tr("The last waitFor...() function timed out. "
                "The state of QProcess is unchanged, and you can try calling "
                "waitFor...() again.");
            break;
        case QProcess::WriteError:
            msg = tr("An error occurred when attempting to write "
                "to the Gdb process. For example, the process may not be running, "
                "or it may have closed its input channel.");
            break;
        case QProcess::ReadError:
            msg = tr("An error occurred when attempting to read from "
                "the Gdb process. For example, the process may not be running.");
            break;
        default:
            msg = tr("An unknown error in the Gdb process occurred. "
                "This is the default return value of error().");
    }

hjk's avatar
hjk committed
333
334
    showStatusMessage(msg);
    QMessageBox::critical(mainWindow(), tr("Error"), msg);
con's avatar
con committed
335
    // act as if it was closed by the core
336
    if (kill)
hjk's avatar
hjk committed
337
        m_manager->exitDebugger();
con's avatar
con committed
338
339
}

340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
void GdbEngine::uploadProcError(QProcess::ProcessError error)
{
    QString msg;
    switch (error) {
        case QProcess::FailedToStart:
            msg = tr("The upload process failed to start. Either the "
                "invoked script '%1' is missing, or you may have insufficient "
                "permissions to invoke the program.")
                .arg(theDebuggerStringSetting(GdbLocation));
            break;
        case QProcess::Crashed:
            msg = tr("The upload process crashed some time after starting "
                "successfully.");
            break;
        case QProcess::Timedout:
            msg = tr("The last waitFor...() function timed out. "
                "The state of QProcess is unchanged, and you can try calling "
                "waitFor...() again.");
            break;
        case QProcess::WriteError:
            msg = tr("An error occurred when attempting to write "
                "to the upload process. For example, the process may not be running, "
                "or it may have closed its input channel.");
            break;
        case QProcess::ReadError:
            msg = tr("An error occurred when attempting to read from "
                "the upload process. For example, the process may not be running.");
            break;
        default:
            msg = tr("An unknown error in the upload process occurred. "
                "This is the default return value of error().");
    }

hjk's avatar
hjk committed
373
374
    showStatusMessage(msg);
    QMessageBox::critical(mainWindow(), tr("Error"), msg);
375
376
}

377
378
379
void GdbEngine::readUploadStandardOutput()
{
    QByteArray ba = m_uploadProc.readAllStandardOutput();
380
    gdbOutputAvailable(LogOutput, QString::fromLocal8Bit(ba, ba.length()));
381
382
383
384
385
}

void GdbEngine::readUploadStandardError()
{
    QByteArray ba = m_uploadProc.readAllStandardError();
386
    gdbOutputAvailable(LogError, QString::fromLocal8Bit(ba, ba.length()));
387
388
}

con's avatar
con committed
389
390
391
392
#if 0
static void dump(const char *first, const char *middle, const QString & to)
{
    QByteArray ba(first, middle - first);
393
    Q_UNUSED(to)
con's avatar
con committed
394
395
396
397
398
399
400
401
402
403
404
    // note that qDebug cuts off output after a certain size... (bug?)
    qDebug("\n>>>>> %s\n%s\n====\n%s\n<<<<<\n",
        qPrintable(currentTime()),
        qPrintable(QString(ba).trimmed()),
        qPrintable(to.trimmed()));
    //qDebug() << "";
    //qDebug() << qPrintable(currentTime())
    //    << " Reading response:  " << QString(ba).trimmed() << "\n";
}
#endif

405
406
407
408
409
410
void GdbEngine::readDebugeeOutput(const QByteArray &data)
{
    emit applicationOutputAvailable(m_outputCodec->toUnicode(
            data.constData(), data.length(), &m_outputCodecState));
}

hjk's avatar
hjk committed
411
412
void GdbEngine::debugMessage(const QString &msg)
{
413
    emit gdbOutputAvailable(LogDebug, msg);
hjk's avatar
hjk committed
414
415
}

416
void GdbEngine::handleResponse(const QByteArray &buff)
con's avatar
con committed
417
418
419
{
    static QTime lastTime;

420
    if (theDebuggerBoolSetting(LogTimeStamps))
421
        emit gdbOutputAvailable(LogTime, currentTime());
422
    emit gdbOutputAvailable(LogOutput, QString::fromLocal8Bit(buff, buff.length()));
con's avatar
con committed
423
424
425
426
427

#if 0
    qDebug() // << "#### start response handling #### "
        << currentTime()
        << lastTime.msecsTo(QTime::currentTime()) << "ms,"
428
429
        << "buf:" << buff.left(1500) << "..."
        //<< "buf:" << buff
430
        << "size:" << buff.size();
con's avatar
con committed
431
#else
432
    //qDebug() << "buf:" << buff;
con's avatar
con committed
433
434
435
436
#endif

    lastTime = QTime::currentTime();

437
438
    if (buff.isEmpty() || buff == "(gdb) ")
        return;
con's avatar
con committed
439

440
441
442
    const char *from = buff.constData();
    const char *to = from + buff.size();
    const char *inner;
con's avatar
con committed
443

444
445
446
447
    int token = -1;
    // token is a sequence of numbers
    for (inner = from; inner != to; ++inner)
        if (*inner < '0' || *inner > '9')
con's avatar
con committed
448
            break;
449
450
451
    if (from != inner) {
        token = QByteArray(from, inner - from).toInt();
        from = inner;
452
        //qDebug() << "found token" << token;
453
    }
con's avatar
con committed
454

455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
    // next char decides kind of record
    const char c = *from++;
    //qDebug() << "CODE:" << c;
    switch (c) {
        case '*':
        case '+':
        case '=': {
            QByteArray asyncClass;
            for (; from != to; ++from) {
                const char c = *from;
                if (!isNameChar(c))
                    break;
                asyncClass += *from;
            }
            //qDebug() << "ASYNCCLASS" << asyncClass;
con's avatar
con committed
470

471
472
473
            GdbMi record;
            while (from != to) {
                GdbMi data;
hjk's avatar
hjk committed
474
                if (*from != ',') {
hjk's avatar
hjk committed
475
476
                    // happens on archer where we get 
                    // 23^running <NL> *running,thread-id="all" <NL> (gdb) 
477
                    record.m_type = GdbMi::Tuple;
hjk's avatar
hjk committed
478
479
480
481
482
                    break;
                }
                ++from; // skip ','
                data.parseResultOrValue(from, to);
                if (data.isValid()) {
483
                    //qDebug() << "parsed response:" << data.toString();
hjk's avatar
hjk committed
484
485
                    record.m_children += data;
                    record.m_type = GdbMi::Tuple;
con's avatar
con committed
486
487
                }
            }
488
489
490
491
            if (asyncClass == "stopped") {
                handleAsyncOutput(record);
            } else if (asyncClass == "running") {
                // Archer has 'thread-id="all"' here
492
493
494
495
            } else if (asyncClass == "library-loaded") {
                // Archer has 'id="/usr/lib/libdrm.so.2",
                // target-name="/usr/lib/libdrm.so.2",
                // host-name="/usr/lib/libdrm.so.2",
496
                // symbols-loaded="0"
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
497
                QByteArray id = record.findChild("id").data();
498
                if (!id.isEmpty())
hjk's avatar
hjk committed
499
                    showStatusMessage(tr("Library %1 loaded.").arg(_(id)));
hjk's avatar
hjk committed
500
501
502
503
            } else if (asyncClass == "library-unloaded") {
                // Archer has 'id="/usr/lib/libdrm.so.2",
                // target-name="/usr/lib/libdrm.so.2",
                // host-name="/usr/lib/libdrm.so.2"
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
504
                QByteArray id = record.findChild("id").data();
hjk's avatar
hjk committed
505
                showStatusMessage(tr("Library %1 unloaded.").arg(_(id)));
506
507
            } else if (asyncClass == "thread-group-created") {
                // Archer has "{id="28902"}" 
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
508
                QByteArray id = record.findChild("id").data();
hjk's avatar
hjk committed
509
                showStatusMessage(tr("Thread group %1 created.").arg(_(id)));
510
511
            } else if (asyncClass == "thread-created") {
                //"{id="1",group-id="28902"}" 
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
512
                QByteArray id = record.findChild("id").data();
hjk's avatar
hjk committed
513
                showStatusMessage(tr("Thread %1 created.").arg(_(id)));
514
515
            } else if (asyncClass == "thread-group-exited") {
                // Archer has "{id="28902"}" 
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
516
                QByteArray id = record.findChild("id").data();
hjk's avatar
hjk committed
517
                showStatusMessage(tr("Thread group %1 exited.").arg(_(id)));
518
519
            } else if (asyncClass == "thread-exited") {
                //"{id="1",group-id="28902"}" 
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
520
521
                QByteArray id = record.findChild("id").data();
                QByteArray groupid = record.findChild("group-id").data();
hjk's avatar
hjk committed
522
                showStatusMessage(tr("Thread %1 in group %2 exited.")
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
523
                    .arg(_(id)).arg(_(groupid)));
hjk's avatar
hjk committed
524
            } else if (asyncClass == "thread-selected") {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
525
                QByteArray id = record.findChild("id").data();
hjk's avatar
hjk committed
526
                showStatusMessage(tr("Thread %1 selected.").arg(_(id)));
hjk's avatar
hjk committed
527
                //"{id="2"}" 
528
            #if defined(Q_OS_MAC)
529
530
531
532
533
534
535
536
537
538
539
            } else if (asyncClass == "shlibs-updated") {
                // MAC announces updated libs
            } else if (asyncClass == "shlibs-added") {
                // MAC announces added libs
                // {shlib-info={num="2", name="libmathCommon.A_debug.dylib",
                // kind="-", dyld-addr="0x7f000", reason="dyld", requested-state="Y",
                // state="Y", path="/usr/lib/system/libmathCommon.A_debug.dylib",
                // description="/usr/lib/system/libmathCommon.A_debug.dylib",
                // loaded_addr="0x7f000", slide="0x7f000", prefix=""}}
            #endif
            } else {
540
                qDebug() << "IGNORED ASYNC OUTPUT"
541
                    << asyncClass << record.toString();
542
            }
543
544
            break;
        }
545

546
        case '~': {
547
548
549
            QByteArray data = GdbMi::parseCString(from, to);
            m_pendingConsoleStreamOutput += data;
            if (data.startsWith("Reading symbols from ")) {
hjk's avatar
hjk committed
550
                showStatusMessage(tr("Reading %1...").arg(_(data.mid(21))));
551
            }
552
553
            break;
        }
554

555
        case '@': {
556
557
            QByteArray data = GdbMi::parseCString(from, to);
            m_pendingTargetStreamOutput += data;
558
            break;
con's avatar
con committed
559
560
        }

561
562
563
564
565
566
        case '&': {
            QByteArray data = GdbMi::parseCString(from, to);
            m_pendingLogStreamOutput += data;
            // On Windows, the contents seem to depend on the debugger
            // version and/or OS version used.
            if (data.startsWith("warning:"))
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
567
                qq->showApplicationOutput(_(data.mid(9))); // cut "warning: "
con's avatar
con committed
568
569
570
            break;
        }

571
572
        case '^': {
            GdbResultRecord record;
con's avatar
con committed
573

574
            record.token = token;
con's avatar
con committed
575

576
577
578
            for (inner = from; inner != to; ++inner)
                if (*inner < 'a' || *inner > 'z')
                    break;
con's avatar
con committed
579

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
580
            QByteArray resultClass = QByteArray::fromRawData(from, inner - from);
581
582
583
584
585
586
587
588
589
590
591
592
            if (resultClass == "done")
                record.resultClass = GdbResultDone;
            else if (resultClass == "running")
                record.resultClass = GdbResultRunning;
            else if (resultClass == "connected")
                record.resultClass = GdbResultConnected;
            else if (resultClass == "error")
                record.resultClass = GdbResultError;
            else if (resultClass == "exit")
                record.resultClass = GdbResultExit;
            else
                record.resultClass = GdbResultUnknown;
con's avatar
con committed
593

594
595
            from = inner;
            if (from != to) {
hjk's avatar
hjk committed
596
597
598
599
600
601
602
603
604
                if (*from == ',') {
                    ++from;
                    record.data.parseTuple_helper(from, to);
                    record.data.m_type = GdbMi::Tuple;
                    record.data.m_name = "data";
                } else {
                    // Archer has this
                    record.data.m_type = GdbMi::Tuple;
                    record.data.m_name = "data";
con's avatar
con committed
605
606
                }
            }
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631

            //qDebug() << "\nLOG STREAM:" + m_pendingLogStreamOutput;
            //qDebug() << "\nTARGET STREAM:" + m_pendingTargetStreamOutput;
            //qDebug() << "\nCONSOLE STREAM:" + m_pendingConsoleStreamOutput;
            record.data.setStreamOutput("logstreamoutput",
                m_pendingLogStreamOutput);
            record.data.setStreamOutput("targetstreamoutput",
                m_pendingTargetStreamOutput);
            record.data.setStreamOutput("consolestreamoutput",
                m_pendingConsoleStreamOutput);
            QByteArray custom = m_customOutputForToken[token];
            if (!custom.isEmpty())
                record.data.setStreamOutput("customvaluecontents",
                    '{' + custom + '}');
            //m_customOutputForToken.remove(token);
            m_pendingLogStreamOutput.clear();
            m_pendingTargetStreamOutput.clear();
            m_pendingConsoleStreamOutput.clear();

            handleResultRecord(record);
            break;
        }
        default: {
            qDebug() << "UNKNOWN RESPONSE TYPE" << c;
            break;
con's avatar
con committed
632
633
634
635
        }
    }
}

636
void GdbEngine::handleStubAttached(const GdbResultRecord &, const QVariant &)
637
638
639
{
    qq->notifyInferiorStopped();
    handleAqcuiredInferior();
640
    m_autoContinue = true;
641
642
643
644
}

void GdbEngine::stubStarted()
{
645
646
647
    const qint64 attachedPID = m_stubProc.applicationPID();
    qq->notifyInferiorPidChanged(attachedPID);
    postCommand(_("attach %1").arg(attachedPID), CB(handleStubAttached));
648
649
650
651
}

void GdbEngine::stubError(const QString &msg)
{
hjk's avatar
hjk committed
652
    QMessageBox::critical(mainWindow(), tr("Debugger Error"), msg);
653
654
}

con's avatar
con committed
655
656
void GdbEngine::readGdbStandardError()
{
hjk's avatar
hjk committed
657
    qWarning() << "Unexpected gdb stderr:" << m_gdbAdapter->readAllStandardError();
con's avatar
con committed
658
659
660
661
}

void GdbEngine::readGdbStandardOutput()
{
662
663
    int newstart = 0;
    int scan = m_inbuffer.size();
con's avatar
con committed
664

hjk's avatar
hjk committed
665
    m_inbuffer.append(m_gdbAdapter->readAllStandardOutput());
con's avatar
con committed
666

667
668
    while (newstart < m_inbuffer.size()) {
        int start = newstart;
669
        int end = m_inbuffer.indexOf('\n', scan);
670
671
672
673
674
        if (end < 0) {
            m_inbuffer.remove(0, start);
            return;
        }
        newstart = end + 1;
675
        scan = newstart;
676
677
        if (end == start)
            continue;
678
        #if defined(Q_OS_WIN)
679
680
681
682
683
        if (m_inbuffer.at(end - 1) == '\r') {
            --end;
            if (end == start)
                continue;
        }
684
        #endif
685
        handleResponse(QByteArray::fromRawData(m_inbuffer.constData() + start, end - start));
con's avatar
con committed
686
    }
687
    m_inbuffer.clear();
con's avatar
con committed
688
689
690
691
}

void GdbEngine::interruptInferior()
{
hjk's avatar
hjk committed
692
    debugMessage(_("GDBENGINE INTERRUPT INFERIOR: %1").arg(m_gdbAdapter->state()));
693
    qq->notifyInferiorStopRequested();
694

hjk's avatar
hjk committed
695
    if (m_gdbAdapter->state() == QProcess::NotRunning) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
696
        debugMessage(_("TRYING TO INTERRUPT INFERIOR WITHOUT RUNNING GDB"));
697
        qq->notifyInferiorExited();
con's avatar
con committed
698
        return;
699
    }
con's avatar
con committed
700

hjk's avatar
hjk committed
701
    m_gdbAdapter->interruptInferior();
con's avatar
con committed
702
703
704
705
}

void GdbEngine::maybeHandleInferiorPidChanged(const QString &pid0)
{
706
    const qint64 pid = pid0.toLongLong();
con's avatar
con committed
707
    if (pid == 0) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
708
        debugMessage(_("Cannot parse PID from %1").arg(pid0));
con's avatar
con committed
709
710
        return;
    }
hjk's avatar
hjk committed
711
    if (pid == inferiorPid())
con's avatar
con committed
712
        return;
713
714
    debugMessage(_("FOUND PID %1").arg(pid));    

Roberto Raggi's avatar
Roberto Raggi committed
715
    qq->notifyInferiorPidChanged(pid);
716
717
    if (m_dumperInjectionLoad)
        tryLoadDebuggingHelpers();
con's avatar
con committed
718
719
}

720
void GdbEngine::postCommand(const QString &command, GdbCommandCallback callback,
721
722
                            const char *callbackName, const QVariant &cookie)
{
723
    postCommand(command, NoFlags, callback, callbackName, cookie);
724
725
}

726
void GdbEngine::postCommand(const QString &command, GdbCommandFlags flags,
727
728
                            GdbCommandCallback callback, const char *callbackName,
                            const QVariant &cookie)
con's avatar
con committed
729
{
hjk's avatar
hjk committed
730
    if (m_gdbAdapter->state() == QProcess::NotRunning) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
731
        debugMessage(_("NO GDB PROCESS RUNNING, CMD IGNORED: ") + command);
con's avatar
con committed
732
733
734
        return;
    }

735
    if (flags & RebuildModel) {
con's avatar
con committed
736
        ++m_pendingRequests;
737
        PENDING_DEBUG("   CALLBACK" << callbackName << "INCREMENTS PENDING TO:"
con's avatar
con committed
738
739
            << m_pendingRequests << command);
    } else {
740
        PENDING_DEBUG("   UNKNOWN CALLBACK" << callbackName << "LEAVES PENDING AT:"
con's avatar
con committed
741
742
743
            << m_pendingRequests << command);
    }

744
    GdbCommand cmd;
con's avatar
con committed
745
    cmd.command = command;
746
747
748
    cmd.flags = flags;
    cmd.callback = callback;
    cmd.callbackName = callbackName;
con's avatar
con committed
749
750
    cmd.cookie = cookie;

hjk's avatar
hjk committed
751
752
    if ((flags & NeedsStop) && status() != DebuggerInferiorStopped
            && status() != DebuggerProcessStartingUp) {
753
        // queue the commands that we cannot send at once
hjk's avatar
hjk committed
754
755
756
        QTC_ASSERT(status() == DebuggerInferiorRunning,
            qDebug() << "STATUS:" << status());
        showStatusMessage(tr("Stopping temporarily."));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
757
        debugMessage(_("QUEUING COMMAND ") + cmd.command);
758
759
760
        m_commandsToRunOnTemporaryBreak.append(cmd);
        interruptInferior();
    } else if (!command.isEmpty()) {
761
        flushCommand(cmd);
con's avatar
con committed
762
763
764
    }
}

765
766
void GdbEngine::flushCommand(GdbCommand &cmd)
{
767
768
769
770
771
772
    if (m_gdbAdapter->state() != QProcess::Running) {
        emit gdbInputAvailable(LogInput, cmd.command);
        debugMessage(_("GDB PROCESS NOT RUNNING, PLAIN CMD IGNORED: ") + cmd.command);
        return;
    }

773
    ++currentToken();
774
    cmd.postTime = QTime::currentTime();
775
776
    m_cookieForToken[currentToken()] = cmd;
    cmd.command = QString::number(currentToken()) + cmd.command;
777
    if (cmd.flags & EmbedToken)
778
        cmd.command = cmd.command.arg(currentToken());
779
    emit gdbInputAvailable(LogInput, cmd.command);
780
781

    m_gdbAdapter->write(cmd.command.toLatin1() + "\r\n");
782
783
}

con's avatar
con committed
784
785
void GdbEngine::handleResultRecord(const GdbResultRecord &record)
{
786
787
    //qDebug() << "TOKEN:" << record.token
    //    << " ACCEPTABLE:" << m_oldestAcceptableToken;
con's avatar
con committed
788
789
790
791
792
793
794
    //qDebug() << "";
    //qDebug() << "\nRESULT" << record.token << record.toString();

    int token = record.token;
    if (token == -1)
        return;

795
796
797
798
799
800
801
802
803
    if (!m_cookieForToken.contains(token)) {
        // In theory this should not happen, in practice it does.
        debugMessage(_("COOKIE FOR TOKEN %1 ALREADY EATEN. "
            "TWO RESPONSES FOR ONE COMMAND?").arg(token));
        // handle a case known to occur on Linux/gdb 6.8 when debugging moc
        // with helpers enabled. In this case we get a second response with
        // msg="Cannot find new threads: generic error"
        if (record.resultClass == GdbResultError) {
            QByteArray msg = record.data.findChild("msg").data();
hjk's avatar
hjk committed
804
            QMessageBox::critical(mainWindow(), tr("Error"),
805
                tr("Executable failed:\n") + QString::fromLocal8Bit(msg));
hjk's avatar
hjk committed
806
            showStatusMessage(tr("Process failed to start."));
807
808
809
810
811
812
813
            exitDebugger();
            //qq->notifyInferiorStopped();
            //qq->notifyInferiorExited();
        }
        return;
    }

814
    GdbCommand cmd = m_cookieForToken.take(token);
815
816
817
818
819
    if (theDebuggerBoolSetting(LogTimeStamps)) {
        emit gdbOutputAvailable(LogTime, _("Response time: %1: %2 s")
            .arg(cmd.command)
            .arg(cmd.postTime.msecsTo(QTime::currentTime()) / 1000.));
    }
con's avatar
con committed
820

821
    if (record.token < m_oldestAcceptableToken && (cmd.flags & Discardable)) {
822
        //qDebug() << "### SKIPPING OLD RESULT" << record.toString();
hjk's avatar
hjk committed
823
        //QMessageBox::information(mainWindow(), tr("Skipped"), "xxx");
con's avatar
con committed
824
825
826
827
        return;
    }

#if 0
828
    qDebug() << "# handleOutput,"
hjk's avatar
hjk committed
829
        << "cmd name:" << cmd.callbackName
830
        << " cmd synchronized:" << cmd.synchronized
con's avatar
con committed
831
832
833
834
835
        << "\n record: " << record.toString();
#endif

    // << "\n data: " << record.data.toString(true);

836
837
    if (cmd.callback)
        (this->*(cmd.callback))(record, cmd.cookie);
con's avatar
con committed
838

839
    if (cmd.flags & RebuildModel) {
con's avatar
con committed
840
        --m_pendingRequests;
hjk's avatar
hjk committed
841
        PENDING_DEBUG("   TYPE " << cmd.callbackName << " DECREMENTS PENDING TO: "
con's avatar
con committed
842
            << m_pendingRequests << cmd.command);
843
        if (m_pendingRequests <= 0) {
hjk's avatar
hjk committed
844
845
            PENDING_DEBUG("\n\n ....  AND TRIGGERS MODEL UPDATE\n");
            rebuildModel();
846
        }
con's avatar
con committed
847
    } else {
hjk's avatar
hjk committed
848
        PENDING_DEBUG("   UNKNOWN TYPE " << cmd.callbackName << " LEAVES PENDING AT: "
con's avatar
con committed
849
850
            << m_pendingRequests << cmd.command);
    }
851

852
853
    // Continue only if there are no commands wire anymore, so this will
    // be fully synchroneous.
854
855
856
857
858
859
860
    // This is somewhat inefficient, as it makes the last command synchronous.
    // An optimization would be requesting the continue immediately when the
    // event loop is entered, and let individual commands have a flag to suppress
    // that behavior.
    if (m_cookieForToken.isEmpty() && m_autoContinue) {
        m_autoContinue = false;
        continueInferior();
hjk's avatar
hjk committed
861
        showStatusMessage(tr("Continuing after temporary stop."));
862
    }
con's avatar
con committed
863
864
865
866
}

void GdbEngine::executeDebuggerCommand(const QString &command)
{
hjk's avatar
hjk committed
867
    if (m_gdbAdapter->state() != QProcess::Running) {
868
        debugMessage(_("GDB PROCESS NOT RUNNING, PLAIN CMD IGNORED: ") + command);
con's avatar
con committed
869
870
871
        return;
    }

hjk's avatar
hjk committed
872
    m_gdbAdapter->write(command.toLatin1() + "\r\n");
con's avatar
con committed
873
874
}

875
void GdbEngine::handleTargetCore(const GdbResultRecord &, const QVariant &)
876
877
{
    qq->notifyInferiorStopped();
hjk's avatar
hjk committed
878
879
    showStatusMessage(tr("Core file loaded."));
    m_manager->resetLocation();
880
    tryLoadDebuggingHelpers();
881
882
    qq->stackHandler()->setCurrentIndex(0);
    updateLocals(); // Quick shot
883
    reloadStack();
884
    if (supportsThreads())
885
        postCommand(_("-thread-list-ids"), WatchUpdate, CB(handleStackListThreads), 0);
886
    qq->reloadRegisters();
887
888
}

889
void GdbEngine::handleQuerySources(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
890
891
{
    if (record.resultClass == GdbResultDone) {
892
        QMap<QString, QString> oldShortToFull = m_shortToFullName;
con's avatar
con committed
893
894
895
896
897
898
        m_shortToFullName.clear();
        m_fullToShortName.clear();
        // "^done,files=[{file="../../../../bin/gdbmacros/gdbmacros.cpp",
        // fullname="/data5/dev/ide/main/bin/gdbmacros/gdbmacros.cpp"},
        GdbMi files = record.data.findChild("files");
        foreach (const GdbMi &item, files.children()) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
899
            QString fileName = QString::fromLocal8Bit(item.findChild("file").data());
con's avatar
con committed
900
            GdbMi fullName = item.findChild("fullname");
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
901
            QString full = QString::fromLocal8Bit(fullName.data());
con's avatar
con committed
902
903
904
905
            #ifdef Q_OS_WIN
            full = QDir::cleanPath(full);
            #endif
            if (fullName.isValid() && QFileInfo(full).isReadable()) {
906
                //qDebug() << "STORING 2:" << fileName << full;
con's avatar
con committed
907
908
909
910
                m_shortToFullName[fileName] = full;
                m_fullToShortName[full] = fileName;
            }
        }
911
912
        if (m_shortToFullName != oldShortToFull)
            qq->sourceFileWindow()->setSourceFiles(m_shortToFullName);
con's avatar
con committed
913
914
915
    }
}

916
void GdbEngine::handleInfoThreads(const GdbResultRecord &record, const QVariant &)
917
{
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
    if (record.resultClass != GdbResultDone)
        return;
    // FIXME: use something more robust
    // WIN:     [New thread 3380.0x2bc]
    //          * 3 Thread 2312.0x4d0  0x7c91120f in ?? ()
    // LINUX:   * 1 Thread 0x7f466273c6f0 (LWP 21455)  0x0000000000404542 in ...
    const QString data = _(record.data.findChild("consolestreamoutput").data());
    if (data.isEmpty())
        return;
    // check "[New thread 3380.0x2bc]"
    if (data.startsWith(QLatin1Char('['))) {
        QRegExp ren(_("^\\[New thread (\\d+)\\.0x.*"));
        Q_ASSERT(ren.isValid());
        if (ren.indexIn(data) != -1) {
            maybeHandleInferiorPidChanged(ren.cap(1));
            return;
        }
935
    }
936
937
938
939
940
    // check "* 3 Thread ..."
    QRegExp re(_("^\\*? +\\d+ +[Tt]hread (\\d+)\\.0x.* in"));
    Q_ASSERT(re.isValid());
    if (re.indexIn(data) != -1)
        maybeHandleInferiorPidChanged(re.cap(1));
941
942
}

943
void GdbEngine::handleInfoProc(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
944
945
{
    if (record.resultClass == GdbResultDone) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
946
        #ifdef Q_OS_MAC
con's avatar
con committed
947
        //^done,process-id="85075"
Oswald Buddenhagen's avatar
nicer    
Oswald Buddenhagen committed
948
        maybeHandleInferiorPidChanged(_(record.data.findChild("process-id").data()));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
949
        #else
con's avatar
con committed
950
        // FIXME: use something more robust
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
951
952
        QRegExp re(__("process (\\d+)"));
        QString data = __(record.data.findChild("consolestreamoutput").data());
con's avatar
con committed
953
954
955
956
957
958
        if (re.indexIn(data) != -1)
            maybeHandleInferiorPidChanged(re.cap(1));
        #endif
    }
}

959
void GdbEngine::handleInfoShared(const GdbResultRecord &record, const QVariant &cookie)
con's avatar
con committed
960
961
962
{
    if (record.resultClass == GdbResultDone) {
        // let the modules handler do the parsing
963
        handleModulesList(record, cookie);
con's avatar
con committed
964
965
966
    }
}

967
#if 0
con's avatar
con committed
968
969
970
971
972
973
974
975
976
977
void GdbEngine::handleExecJumpToLine(const GdbResultRecord &record)
{
    // FIXME: remove this special case as soon as 'jump'
    // is supported by MI
    // "&"jump /home/apoenitz/dev/work/test1/test1.cpp:242"
    // ~"Continuing at 0x4058f3."
    // ~"run1 (argc=1, argv=0x7fffb213a478) at test1.cpp:242"
    // ~"242\t x *= 2;"
    //109^done"
    qq->notifyInferiorStopped();
hjk's avatar
hjk committed
978
    showStatusMessage(tr("Jumped. Stopped."));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
979
    QByteArray output = record.data.findChild("logstreamoutput").data();
980
    if (output.isEmpty())
con's avatar
con committed
981
        return;
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
982
983
984
985
986
987
    int idx1 = output.indexOf(' ') + 1;
    if (idx1 > 0) {
        int idx2 = output.indexOf(':', idx1);
        if (idx2 > 0) {
            QString file = QString::fromLocal8Bit(output.mid(idx1, idx2 - idx1));
            int line = output.mid(idx2 + 1).toInt();
hjk's avatar
hjk committed
988
            m_manager->gotoLocation(file, line, true);
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
989
990
        }
    }
con's avatar
con committed
991
}
992
#endif
con's avatar
con committed
993

994
void GdbEngine::handleExecRunToFunction(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
995
996
997
998
999
1000
1001
1002
{
    // FIXME: remove this special case as soon as there's a real
    // reason given when the temporary breakpoint is hit.
    // reight now we get:
    // 14*stopped,thread-id="1",frame={addr="0x0000000000403ce4",
    // func="foo",args=[{name="str",value="@0x7fff0f450460"}],
    // file="main.cpp",fullname="/tmp/g/main.cpp",line="37"}
    qq->notifyInferiorStopped();
hjk's avatar
hjk committed
1003
    showStatusMessage(tr("Run to Function finished. Stopped."));
con's avatar
con committed
1004
    GdbMi frame = record.data.findChild("frame");
1005
1006
1007
1008
    StackFrame f;
    f.file = QString::fromLocal8Bit(frame.findChild("fullname").data());
    f.line = frame.findChild("line").data().toInt();
    f.address = _(frame.findChild("addr").data());
hjk's avatar
hjk committed
1009
    m_manager->gotoLocation(f, true);
con's avatar
con committed
1010
1011
}

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1012
static bool isExitedReason(const QByteArray &reason)
con's avatar
con committed
1013
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1014
1015
1016
1017
    return reason == "exited-normally"   // inferior exited normally
        || reason == "exited-signalled"  // inferior exited because of a signal
        //|| reason == "signal-received" // inferior received signal
        || reason == "exited";           // inferior exited
con's avatar
con committed
1018
1019
}

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1020
static bool isStoppedReason(const QByteArray &reason)
con's avatar
con committed
1021
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1022
1023
1024
1025
1026
1027
1028
    return reason == "function-finished"  // -exec-finish
        || reason == "signal-received"  // handled as "isExitedReason"
        || reason == "breakpoint-hit"     // -exec-continue
        || reason == "end-stepping-range" // -exec-next, -exec-step
        || reason == "location-reached"   // -exec-until
        || reason == "access-watchpoint-trigger"
        || reason == "read-watchpoint-trigger"
1029
        #ifdef Q_OS_MAC