gdbengine.cpp 148 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"
con's avatar
con committed
34

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

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

51
#include "debuggerdialogs.h"
con's avatar
con committed
52

hjk's avatar
hjk committed
53
#include <utils/qtcassert.h>
54
#include <texteditor/itexteditor.h>
55
#include <coreplugin/icore.h>
hjk's avatar
hjk committed
56

con's avatar
con committed
57
58
59
60
61
#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
#include <QtCore/QTime>
#include <QtCore/QTimer>
62
#include <QtCore/QTextStream>
con's avatar
con committed
63
64

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

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
75
#ifdef Q_OS_UNIX
con's avatar
con committed
76
77
78
#include <unistd.h>
#include <dlfcn.h>
#endif
hjk's avatar
hjk committed
79
#include <ctype.h>
con's avatar
con committed
80
81
82
83
84
85
86
87
88
89
90
91
92

using namespace Debugger;
using namespace Debugger::Internal;
using namespace Debugger::Constants;

Q_DECLARE_METATYPE(Debugger::Internal::GdbMi);

//#define DEBUG_PENDING  1
//#define DEBUG_SUBITEM  1

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

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

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

106
107
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
// 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);
}

con's avatar
con committed
142
143
144
145
146
147
///////////////////////////////////////////////////////////////////////
//
// GdbEngine
//
///////////////////////////////////////////////////////////////////////

148
149
GdbEngine::GdbEngine(DebuggerManager *parent) :
#ifdef Q_OS_WIN // Do injection loading with MinGW (call loading does not work with 64bit)
150
    m_dumperInjectionLoad(true),
151
#else
152
    m_dumperInjectionLoad(false),
153
#endif
154
    q(parent),
155
    qq(parent->engineInterface())
con's avatar
con committed
156
{
157
    m_stubProc.setMode(Core::Utils::ConsoleProcess::Debug);
158
159
160
#ifdef Q_OS_UNIX
    m_stubProc.setSettings(Core::ICore::instance()->settings());
#endif
161
162
    initializeVariables();
    initializeConnections();
con's avatar
con committed
163
164
165
166
}

GdbEngine::~GdbEngine()
{
hjk's avatar
hjk committed
167
168
    // prevent sending error messages afterwards
    m_gdbProc.disconnect(this);
con's avatar
con committed
169
170
}

171
void GdbEngine::initializeConnections()
con's avatar
con committed
172
173
{
    // Gdb Process interaction
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
    connect(&m_gdbProc, SIGNAL(error(QProcess::ProcessError)),
        this, SLOT(gdbProcError(QProcess::ProcessError)));
    connect(&m_gdbProc, SIGNAL(readyReadStandardOutput()),
        this, SLOT(readGdbStandardOutput()));
    connect(&m_gdbProc, SIGNAL(readyReadStandardError()),
        this, SLOT(readGdbStandardError()));
    connect(&m_gdbProc, SIGNAL(finished(int, QProcess::ExitStatus)),
        q, SLOT(exitDebugger()));

    connect(&m_stubProc, SIGNAL(processError(QString)),
        this, SLOT(stubError(QString)));
    connect(&m_stubProc, SIGNAL(processStarted()),
        this, SLOT(stubStarted()));
    connect(&m_stubProc, SIGNAL(wrapperStopped()),
        q, SLOT(exitDebugger()));

    connect(&m_uploadProc, SIGNAL(error(QProcess::ProcessError)),
        this, SLOT(uploadProcError(QProcess::ProcessError)));
192
193
194
195
    connect(&m_uploadProc, SIGNAL(readyReadStandardOutput()),
        this, SLOT(readUploadStandardOutput()));
    connect(&m_uploadProc, SIGNAL(readyReadStandardError()),
        this, SLOT(readUploadStandardError()));
196

con's avatar
con committed
197
    // Output
198
    connect(&m_outputCollector, SIGNAL(byteDelivery(QByteArray)),
199
        this, SLOT(readDebugeeOutput(QByteArray)));
con's avatar
con committed
200

201
202
    connect(this, SIGNAL(gdbOutputAvailable(int,QString)),
        q, SLOT(showDebuggerOutput(int,QString)),
con's avatar
con committed
203
        Qt::QueuedConnection);
204
205
    connect(this, SIGNAL(gdbInputAvailable(int,QString)),
        q, SLOT(showDebuggerInput(int,QString)),
con's avatar
con committed
206
        Qt::QueuedConnection);
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
207
208
    connect(this, SIGNAL(applicationOutputAvailable(QString)),
        q, SLOT(showApplicationOutput(QString)),
con's avatar
con committed
209
        Qt::QueuedConnection);
210

211
    // FIXME: These trigger even if the engine is not active
212
213
214
215
216
217
    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
218
219
}

220
221
void GdbEngine::initializeVariables()
{
222
    m_debuggingHelperState = DebuggingHelperUninitialized;
223
    m_gdbVersion = 100;
hjk's avatar
hjk committed
224
    m_gdbBuildVersion = -1;
225
226
227
228
229
230
231
232
233

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

    m_modulesListOutdated = true;
    m_oldestAcceptableToken = -1;
    m_outputCodec = QTextCodec::codecForLocale();
    m_pendingRequests = 0;
234
    m_autoContinue = false;
235
    m_waitingForFirstBreakpointToBeHit = false;
236
    m_commandsToRunOnTemporaryBreak.clear();
237
    m_cookieForToken.clear();
238
239
240
241
242
243
244
245
246
247
    m_customOutputForToken.clear();

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

    m_inbuffer.clear();

    m_currentFunctionArgs.clear();
    m_currentFrame.clear();
248
    m_dumperHelper.clear();
249
250
251
252
253
254
255

    // FIXME: unhandled:
    //m_outputCodecState = QTextCodec::ConverterState();
    //OutputCollector m_outputCollector;
    //QProcess m_gdbProc;
    //QProcess m_uploadProc;
    //Core::Utils::ConsoleProcess m_stubProc;
256
257
}

con's avatar
con committed
258
259
260
void GdbEngine::gdbProcError(QProcess::ProcessError error)
{
    QString msg;
261
    bool kill = true;
con's avatar
con committed
262
263
    switch (error) {
        case QProcess::FailedToStart:
264
            kill = false;
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
265
            msg = tr("The Gdb process failed to start. Either the "
con's avatar
con committed
266
                "invoked program '%1' is missing, or you may have insufficient "
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
267
                "permissions to invoke the program.")
hjk's avatar
hjk committed
268
                .arg(theDebuggerStringSetting(GdbLocation));
con's avatar
con committed
269
270
            break;
        case QProcess::Crashed:
271
            kill = false;
con's avatar
con committed
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
            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().");
    }

294
    q->showStatusMessage(msg);
con's avatar
con committed
295
296
    QMessageBox::critical(q->mainWindow(), tr("Error"), msg);
    // act as if it was closed by the core
297
298
    if (kill)
        q->exitDebugger();
con's avatar
con committed
299
300
}

301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
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().");
    }

    q->showStatusMessage(msg);
    QMessageBox::critical(q->mainWindow(), tr("Error"), msg);
}

338
339
340
void GdbEngine::readUploadStandardOutput()
{
    QByteArray ba = m_uploadProc.readAllStandardOutput();
341
    gdbOutputAvailable(LogOutput, QString::fromLocal8Bit(ba, ba.length()));
342
343
344
345
346
}

void GdbEngine::readUploadStandardError()
{
    QByteArray ba = m_uploadProc.readAllStandardError();
347
    gdbOutputAvailable(LogError, QString::fromLocal8Bit(ba, ba.length()));
348
349
}

con's avatar
con committed
350
351
352
353
#if 0
static void dump(const char *first, const char *middle, const QString & to)
{
    QByteArray ba(first, middle - first);
354
    Q_UNUSED(to)
con's avatar
con committed
355
356
357
358
359
360
361
362
363
364
365
    // 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

366
367
368
369
370
371
void GdbEngine::readDebugeeOutput(const QByteArray &data)
{
    emit applicationOutputAvailable(m_outputCodec->toUnicode(
            data.constData(), data.length(), &m_outputCodecState));
}

hjk's avatar
hjk committed
372
373
void GdbEngine::debugMessage(const QString &msg)
{
374
    emit gdbOutputAvailable(LogDebug, msg);
hjk's avatar
hjk committed
375
376
}

377
void GdbEngine::handleResponse(const QByteArray &buff)
con's avatar
con committed
378
379
380
{
    static QTime lastTime;

381
    if (theDebuggerBoolSetting(LogTimeStamps))
382
        emit gdbOutputAvailable(LogTime, currentTime());
383
    emit gdbOutputAvailable(LogOutput, QString::fromLocal8Bit(buff, buff.length()));
con's avatar
con committed
384
385
386
387
388

#if 0
    qDebug() // << "#### start response handling #### "
        << currentTime()
        << lastTime.msecsTo(QTime::currentTime()) << "ms,"
389
390
        << "buf:" << buff.left(1500) << "..."
        //<< "buf:" << buff
391
        << "size:" << buff.size();
con's avatar
con committed
392
#else
393
    //qDebug() << "buf:" << buff;
con's avatar
con committed
394
395
396
397
#endif

    lastTime = QTime::currentTime();

398
399
    if (buff.isEmpty() || buff == "(gdb) ")
        return;
con's avatar
con committed
400

401
402
403
    const char *from = buff.constData();
    const char *to = from + buff.size();
    const char *inner;
con's avatar
con committed
404

405
406
407
408
    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
409
            break;
410
411
412
    if (from != inner) {
        token = QByteArray(from, inner - from).toInt();
        from = inner;
413
        //qDebug() << "found token" << token;
414
    }
con's avatar
con committed
415

416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
    // 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
431

432
433
434
            GdbMi record;
            while (from != to) {
                GdbMi data;
hjk's avatar
hjk committed
435
                if (*from != ',') {
hjk's avatar
hjk committed
436
437
                    // happens on archer where we get 
                    // 23^running <NL> *running,thread-id="all" <NL> (gdb) 
438
                    record.m_type = GdbMi::Tuple;
hjk's avatar
hjk committed
439
440
441
442
443
                    break;
                }
                ++from; // skip ','
                data.parseResultOrValue(from, to);
                if (data.isValid()) {
444
                    //qDebug() << "parsed response:" << data.toString();
hjk's avatar
hjk committed
445
446
                    record.m_children += data;
                    record.m_type = GdbMi::Tuple;
con's avatar
con committed
447
448
                }
            }
449
450
451
452
            if (asyncClass == "stopped") {
                handleAsyncOutput(record);
            } else if (asyncClass == "running") {
                // Archer has 'thread-id="all"' here
453
454
455
456
            } 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",
457
                // symbols-loaded="0"
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
458
                QByteArray id = record.findChild("id").data();
459
                if (!id.isEmpty())
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
460
                    q->showStatusMessage(tr("Library %1 loaded.").arg(_(id)));
hjk's avatar
hjk committed
461
462
463
464
            } 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
465
466
                QByteArray id = record.findChild("id").data();
                q->showStatusMessage(tr("Library %1 unloaded.").arg(_(id)));
467
468
            } else if (asyncClass == "thread-group-created") {
                // Archer has "{id="28902"}" 
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
469
470
                QByteArray id = record.findChild("id").data();
                q->showStatusMessage(tr("Thread group %1 created.").arg(_(id)));
471
472
            } else if (asyncClass == "thread-created") {
                //"{id="1",group-id="28902"}" 
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
473
474
                QByteArray id = record.findChild("id").data();
                q->showStatusMessage(tr("Thread %1 created.").arg(_(id)));
475
476
            } else if (asyncClass == "thread-group-exited") {
                // Archer has "{id="28902"}" 
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
477
478
                QByteArray id = record.findChild("id").data();
                q->showStatusMessage(tr("Thread group %1 exited.").arg(_(id)));
479
480
            } else if (asyncClass == "thread-exited") {
                //"{id="1",group-id="28902"}" 
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
481
482
                QByteArray id = record.findChild("id").data();
                QByteArray groupid = record.findChild("group-id").data();
483
                q->showStatusMessage(tr("Thread %1 in group %2 exited.")
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
484
                    .arg(_(id)).arg(_(groupid)));
hjk's avatar
hjk committed
485
            } else if (asyncClass == "thread-selected") {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
486
487
                QByteArray id = record.findChild("id").data();
                q->showStatusMessage(tr("Thread %1 selected.").arg(_(id)));
hjk's avatar
hjk committed
488
                //"{id="2"}" 
489
            #if defined(Q_OS_MAC)
490
491
492
493
494
495
496
497
498
499
500
            } 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 {
501
                qDebug() << "IGNORED ASYNC OUTPUT"
502
                    << asyncClass << record.toString();
503
            }
504
505
            break;
        }
506

507
        case '~': {
508
509
510
            QByteArray data = GdbMi::parseCString(from, to);
            m_pendingConsoleStreamOutput += data;
            if (data.startsWith("Reading symbols from ")) {
511
                q->showStatusMessage(tr("Reading %1...").arg(_(data.mid(21))));
512
            }
513
514
            break;
        }
515

516
        case '@': {
517
518
            QByteArray data = GdbMi::parseCString(from, to);
            m_pendingTargetStreamOutput += data;
519
            break;
con's avatar
con committed
520
521
        }

522
523
524
525
526
527
        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
528
                qq->showApplicationOutput(_(data.mid(9))); // cut "warning: "
con's avatar
con committed
529
530
531
            break;
        }

532
533
        case '^': {
            GdbResultRecord record;
con's avatar
con committed
534

535
            record.token = token;
con's avatar
con committed
536

537
538
539
            for (inner = from; inner != to; ++inner)
                if (*inner < 'a' || *inner > 'z')
                    break;
con's avatar
con committed
540

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
541
            QByteArray resultClass = QByteArray::fromRawData(from, inner - from);
542
543
544
545
546
547
548
549
550
551
552
553
            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
554

555
556
            from = inner;
            if (from != to) {
hjk's avatar
hjk committed
557
558
559
560
561
562
563
564
565
                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
566
567
                }
            }
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592

            //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
593
594
595
596
        }
    }
}

597
void GdbEngine::handleStubAttached(const GdbResultRecord &, const QVariant &)
598
599
600
{
    qq->notifyInferiorStopped();
    handleAqcuiredInferior();
601
    m_autoContinue = true;
602
603
604
605
}

void GdbEngine::stubStarted()
{
606
607
608
    const qint64 attachedPID = m_stubProc.applicationPID();
    qq->notifyInferiorPidChanged(attachedPID);
    postCommand(_("attach %1").arg(attachedPID), CB(handleStubAttached));
609
610
611
612
613
614
615
}

void GdbEngine::stubError(const QString &msg)
{
    QMessageBox::critical(q->mainWindow(), tr("Debugger Error"), msg);
}

con's avatar
con committed
616
617
void GdbEngine::readGdbStandardError()
{
618
    qWarning() << "Unexpected gdb stderr:" << m_gdbProc.readAllStandardError();
con's avatar
con committed
619
620
621
622
}

void GdbEngine::readGdbStandardOutput()
{
623
624
    int newstart = 0;
    int scan = m_inbuffer.size();
con's avatar
con committed
625

626
    m_inbuffer.append(m_gdbProc.readAllStandardOutput());
con's avatar
con committed
627

628
629
    while (newstart < m_inbuffer.size()) {
        int start = newstart;
630
        int end = m_inbuffer.indexOf('\n', scan);
631
632
633
634
635
        if (end < 0) {
            m_inbuffer.remove(0, start);
            return;
        }
        newstart = end + 1;
636
        scan = newstart;
637
638
        if (end == start)
            continue;
639
        #if defined(Q_OS_WIN)
640
641
642
643
644
        if (m_inbuffer.at(end - 1) == '\r') {
            --end;
            if (end == start)
                continue;
        }
645
        #endif
646
        handleResponse(QByteArray::fromRawData(m_inbuffer.constData() + start, end - start));
con's avatar
con committed
647
    }
648
    m_inbuffer.clear();
con's avatar
con committed
649
650
651
652
}

void GdbEngine::interruptInferior()
{
653
    qq->notifyInferiorStopRequested();
654

655
    if (m_gdbProc.state() == QProcess::NotRunning) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
656
        debugMessage(_("TRYING TO INTERRUPT INFERIOR WITHOUT RUNNING GDB"));
657
        qq->notifyInferiorExited();
con's avatar
con committed
658
        return;
659
    }
con's avatar
con committed
660

661
    if (q->startMode() == StartRemote) {
662
        postCommand(_("-exec-interrupt"));
663
664
665
        return;
    }

666
667
    const qint64 attachedPID = q->inferiorPid();
    if (attachedPID <= 0) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
668
        debugMessage(_("TRYING TO INTERRUPT INFERIOR BEFORE PID WAS OBTAINED"));
con's avatar
con committed
669
670
671
        return;
    }

672
673
    if (!interruptProcess(attachedPID))
        debugMessage(_("CANNOT INTERRUPT %1").arg(attachedPID));
con's avatar
con committed
674
675
676
677
}

void GdbEngine::maybeHandleInferiorPidChanged(const QString &pid0)
{
678
    const qint64 pid = pid0.toLongLong();
con's avatar
con committed
679
    if (pid == 0) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
680
        debugMessage(_("Cannot parse PID from %1").arg(pid0));
con's avatar
con committed
681
682
        return;
    }
683
    if (pid == q->inferiorPid())
con's avatar
con committed
684
        return;
685
686
    debugMessage(_("FOUND PID %1").arg(pid));    

Roberto Raggi's avatar
Roberto Raggi committed
687
    qq->notifyInferiorPidChanged(pid);
688
689
    if (m_dumperInjectionLoad)
        tryLoadDebuggingHelpers();
con's avatar
con committed
690
691
}

692
void GdbEngine::postCommand(const QString &command, GdbCommandCallback callback,
693
694
                            const char *callbackName, const QVariant &cookie)
{
695
    postCommand(command, NoFlags, callback, callbackName, cookie);
696
697
}

698
void GdbEngine::postCommand(const QString &command, GdbCommandFlags flags,
699
700
                            GdbCommandCallback callback, const char *callbackName,
                            const QVariant &cookie)
con's avatar
con committed
701
702
{
    if (m_gdbProc.state() == QProcess::NotRunning) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
703
        debugMessage(_("NO GDB PROCESS RUNNING, CMD IGNORED: ") + command);
con's avatar
con committed
704
705
706
        return;
    }

707
    if (flags & RebuildModel) {
con's avatar
con committed
708
        ++m_pendingRequests;
709
        PENDING_DEBUG("   CALLBACK" << callbackName << "INCREMENTS PENDING TO:"
con's avatar
con committed
710
711
            << m_pendingRequests << command);
    } else {
712
        PENDING_DEBUG("   UNKNOWN CALLBACK" << callbackName << "LEAVES PENDING AT:"
con's avatar
con committed
713
714
715
            << m_pendingRequests << command);
    }

716
    GdbCommand cmd;
con's avatar
con committed
717
    cmd.command = command;
718
719
720
    cmd.flags = flags;
    cmd.callback = callback;
    cmd.callbackName = callbackName;
con's avatar
con committed
721
722
    cmd.cookie = cookie;

723
    if ((flags & NeedsStop) && q->status() != DebuggerInferiorStopped
724
725
726
            && q->status() != DebuggerProcessStartingUp) {
        // queue the commands that we cannot send at once
        QTC_ASSERT(q->status() == DebuggerInferiorRunning,
727
            qDebug() << "STATUS:" << q->status());
728
        q->showStatusMessage(tr("Stopping temporarily."));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
729
        debugMessage(_("QUEUING COMMAND ") + cmd.command);
730
731
732
        m_commandsToRunOnTemporaryBreak.append(cmd);
        interruptInferior();
    } else if (!command.isEmpty()) {
733
        flushCommand(cmd);
con's avatar
con committed
734
735
736
    }
}

737
738
739
void GdbEngine::flushCommand(GdbCommand &cmd)
{
    ++currentToken();
740
    cmd.postTime = QTime::currentTime();
741
742
    m_cookieForToken[currentToken()] = cmd;
    cmd.command = QString::number(currentToken()) + cmd.command;
743
    if (cmd.flags & EmbedToken)
744
745
746
747
748
        cmd.command = cmd.command.arg(currentToken());

    m_gdbProc.write(cmd.command.toLatin1() + "\r\n");
    //emit gdbInputAvailable(QString(), "         " +  currentTime());
    //emit gdbInputAvailable(QString(), "[" + currentTime() + "]    " + cmd.command);
749
    emit gdbInputAvailable(LogInput, cmd.command);
750
751
}

con's avatar
con committed
752
753
void GdbEngine::handleResultRecord(const GdbResultRecord &record)
{
754
755
    //qDebug() << "TOKEN:" << record.token
    //    << " ACCEPTABLE:" << m_oldestAcceptableToken;
con's avatar
con committed
756
757
758
759
760
761
762
    //qDebug() << "";
    //qDebug() << "\nRESULT" << record.token << record.toString();

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

763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
    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();
            QMessageBox::critical(q->mainWindow(), tr("Error"),
                tr("Executable failed:\n") + QString::fromLocal8Bit(msg));
            q->showStatusMessage(tr("Process failed to start."));
            exitDebugger();
            //qq->notifyInferiorStopped();
            //qq->notifyInferiorExited();
        }
        return;
    }

782
    GdbCommand cmd = m_cookieForToken.take(token);
783
784
785
786
787
    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
788

789
    if (record.token < m_oldestAcceptableToken && (cmd.flags & Discardable)) {
790
        //qDebug() << "### SKIPPING OLD RESULT" << record.toString();
791
        //QMessageBox::information(q->mainWindow(), tr("Skipped"), "xxx");
con's avatar
con committed
792
793
794
795
        return;
    }

#if 0
796
    qDebug() << "# handleOutput,"
hjk's avatar
hjk committed
797
        << "cmd name:" << cmd.callbackName
798
        << " cmd synchronized:" << cmd.synchronized
con's avatar
con committed
799
800
801
802
803
        << "\n record: " << record.toString();
#endif

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

804
805
    if (cmd.callback)
        (this->*(cmd.callback))(record, cmd.cookie);
con's avatar
con committed
806

807
    if (cmd.flags & RebuildModel) {
con's avatar
con committed
808
        --m_pendingRequests;
hjk's avatar
hjk committed
809
        PENDING_DEBUG("   TYPE " << cmd.callbackName << " DECREMENTS PENDING TO: "
con's avatar
con committed
810
            << m_pendingRequests << cmd.command);
811
        if (m_pendingRequests <= 0) {
hjk's avatar
hjk committed
812
813
            PENDING_DEBUG("\n\n ....  AND TRIGGERS MODEL UPDATE\n");
            rebuildModel();
814
        }
con's avatar
con committed
815
    } else {
hjk's avatar
hjk committed
816
        PENDING_DEBUG("   UNKNOWN TYPE " << cmd.callbackName << " LEAVES PENDING AT: "
con's avatar
con committed
817
818
            << m_pendingRequests << cmd.command);
    }
819
820
821
822
823
824
825
826
827
828

    // 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();
        q->showStatusMessage(tr("Continuing after temporary stop."));
    }
con's avatar
con committed
829
830
831
832
833
}

void GdbEngine::executeDebuggerCommand(const QString &command)
{
    if (m_gdbProc.state() == QProcess::NotRunning) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
834
        debugMessage(_("NO GDB PROCESS RUNNING, PLAIN CMD IGNORED: ") + command);
con's avatar
con committed
835
836
837
        return;
    }

838
    m_gdbProc.write(command.toLocal8Bit() + "\r\n");
con's avatar
con committed
839
840
}

841
void GdbEngine::handleTargetCore(const GdbResultRecord &, const QVariant &)
842
843
{
    qq->notifyInferiorStopped();
844
845
    q->showStatusMessage(tr("Core file loaded."));
    q->resetLocation();
846
    tryLoadDebuggingHelpers();
847
848
    qq->stackHandler()->setCurrentIndex(0);
    updateLocals(); // Quick shot
849
    reloadStack();
850
    if (supportsThreads())
851
        postCommand(_("-thread-list-ids"), WatchUpdate, CB(handleStackListThreads), 0);
852
    qq->reloadRegisters();
853
854
}

855
void GdbEngine::handleQuerySources(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
856
857
{
    if (record.resultClass == GdbResultDone) {
858
        QMap<QString, QString> oldShortToFull = m_shortToFullName;
con's avatar
con committed
859
860
861
862
863
864
        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
865
            QString fileName = QString::fromLocal8Bit(item.findChild("file").data());
con's avatar
con committed
866
            GdbMi fullName = item.findChild("fullname");
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
867
            QString full = QString::fromLocal8Bit(fullName.data());
con's avatar
con committed
868
869
870
871
            #ifdef Q_OS_WIN
            full = QDir::cleanPath(full);
            #endif
            if (fullName.isValid() && QFileInfo(full).isReadable()) {
872
                //qDebug() << "STORING 2:" << fileName << full;
con's avatar
con committed
873
874
875
876
                m_shortToFullName[fileName] = full;
                m_fullToShortName[full] = fileName;
            }
        }
877
878
        if (m_shortToFullName != oldShortToFull)
            qq->sourceFileWindow()->setSourceFiles(m_shortToFullName);
con's avatar
con committed
879
880
881
    }
}

882
void GdbEngine::handleInfoThreads(const GdbResultRecord &record, const QVariant &)
883
{
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
    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;
        }
901
    }
902
903
904
905
906
    // check "* 3 Thread ..."
    QRegExp re(_("^\\*? +\\d+ +[Tt]hread (\\d+)\\.0x.* in"));
    Q_ASSERT(re.isValid());
    if (re.indexIn(data) != -1)
        maybeHandleInferiorPidChanged(re.cap(1));
907
908
}

909
void GdbEngine::handleInfoProc(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
910
911
{
    if (record.resultClass == GdbResultDone) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
912
        #ifdef Q_OS_MAC
con's avatar
con committed
913
        //^done,process-id="85075"
Oswald Buddenhagen's avatar
nicer    
Oswald Buddenhagen committed
914
        maybeHandleInferiorPidChanged(_(record.data.findChild("process-id").data()));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
915
        #else
con's avatar
con committed
916
        // FIXME: use something more robust
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
917
918
        QRegExp re(__("process (\\d+)"));
        QString data = __(record.data.findChild("consolestreamoutput").data());
con's avatar
con committed
919
920
921
922
923
924
        if (re.indexIn(data) != -1)
            maybeHandleInferiorPidChanged(re.cap(1));
        #endif
    }
}

925
void GdbEngine::handleInfoShared(const GdbResultRecord &record, const QVariant &cookie)
con's avatar
con committed
926
927
928
{
    if (record.resultClass == GdbResultDone) {
        // let the modules handler do the parsing
929
        handleModulesList(record, cookie);
con's avatar
con committed
930
931
932
    }
}

933
#if 0
con's avatar
con committed
934
935
936
937
938
939
940
941
942
943
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();
944
    q->showStatusMessage(tr("Jumped. Stopped."));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
945
    QByteArray output = record.data.findChild("logstreamoutput").data();
946
    if (output.isEmpty())
con's avatar
con committed
947
        return;
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
948
949
950
951
952
953
954
955
956
    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();
            q->gotoLocation(file, line, true);
        }
    }
con's avatar
con committed
957
}
958
#endif
con's avatar
con committed
959

960
void GdbEngine::handleExecRunToFunction(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
961
962
963
964
965
966
967
968
{
    // 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();
969
    q->showStatusMessage(tr("Run to Function finished. Stopped."));
con's avatar
con committed
970
    GdbMi frame = record.data.findChild("frame");
971
972
973
974
975
    StackFrame f;
    f.file = QString::fromLocal8Bit(frame.findChild("fullname").data());
    f.line = frame.findChild("line").data().toInt();
    f.address = _(frame.findChild("addr").data());
    q->gotoLocation(f, true);
con's avatar
con committed
976
977
}

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
978
static bool isExitedReason(const QByteArray &reason)
con's avatar
con committed
979
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
980
981
982
983
    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
984
985
}

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
986
static bool isStoppedReason(const QByteArray &reason)
con's avatar
con committed
987
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
988
989
990
991
992
993
994
    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"
995
        #ifdef Q_OS_MAC
con's avatar
con committed
996
        || reason.isEmpty()
997
        #endif
con's avatar
con committed
998
999
1000
    ;
}

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1001
1002
void GdbEngine::handleAqcuiredInferior()
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1003
    #ifdef Q_OS_WIN
1004
    postCommand(_("info thread"), CB(handleInfoThreads));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1005
    #elif defined(Q_OS_MAC)
1006
    postCommand(_("info pid"), NeedsStop, CB(handleInfoProc));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1007
1008
    #else
    postCommand(_("info proc"), CB(handleInfoProc));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1009
    #endif
1010
    if (theDebuggerBoolSetting(ListSourceFiles))
1011
        reloadSourceFiles();
1012

1013
    // Reverse debugging. FIXME: Should only be used when available.
1014
1015
    //if (theDebuggerBoolSetting(EnableReverseDebugging))
    //    postCommand(_("target record"));
1016

1017
    tryLoadDebuggingHelpers();
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1018

1019
    #ifndef Q_OS_MAC
1020
    // intentionally after tryLoadDebuggingHelpers(),
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1021
    // otherwise we'd interupt solib loading.
hjk's avatar
hjk committed
1022
    if (theDebuggerBoolSetting(AllPluginBreakpoints)) {
1023
1024
1025
        postCommand(_("set auto-solib-add on"));
        postCommand(_("set stop-on-solib-events 0"));
        postCommand(_("sharedlibrary .*"));
hjk's avatar
hjk committed
1026
    } else if (theDebuggerBoolSetting(SelectedPluginBreakpoints)) {
1027
1028
1029
        postCommand(_("set auto-solib-add on"));
        postCommand(_("set stop-on-solib-events 1"));
        postCommand(_("sharedlibrary ")
hjk's avatar
hjk committed
1030
1031
          + theDebuggerStringSetting(SelectedPluginBreakpointsPattern));
    } else if (theDebuggerBoolSetting(NoPluginBreakpoints)) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1032
        // should be like that already
1033
1034
        if (!m_dumperInjectionLoad)
            postCommand(_("set auto-solib-add off"));
1035
        postCommand(_("set stop-on-solib-events 0"));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1036
    }
1037
1038
    #endif

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1039
1040
    // nicer to see a bit of the world we live in
    reloadModules();
1041
    attemptBreakpointSynchronization();
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1042
1043
}

con's avatar
con committed
1044
1045
void GdbEngine::handleAsyncOutput(const GdbMi &data)
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1046
    const QByteArray &reason = data.findChild("reason").data