gdbengine.cpp 142 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
26
** contact the sales department at http://www.qtsoftware.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"
con's avatar
con committed
37
38
#include "debuggerconstants.h"
#include "debuggermanager.h"
39
#include "debuggertooltip.h"
con's avatar
con committed
40
41
42
43
44
45
46
47
48
#include "gdbmi.h"
#include "procinterrupt.h"

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

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

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

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

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

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

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
94
#   define PENDING_DEBUG(s)
con's avatar
con committed
95
96
#endif

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

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

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
142
// 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
143
144
145
146
147
148
///////////////////////////////////////////////////////////////////////
//
// GdbEngine
//
///////////////////////////////////////////////////////////////////////

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

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

173
void GdbEngine::initializeConnections()
con's avatar
con committed
174
{
175
176
    connect(qq->watchHandler(), SIGNAL(watcherInserted(WatchData)), &m_models, SLOT(insertWatcher(WatchData)));
    connect(&m_models, SIGNAL(watchDataUpdateNeeded(WatchData)), this, SLOT(updateWatchData(WatchData)));
con's avatar
con committed
177
    // Gdb Process interaction
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
    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)));
196
197
198
199
    connect(&m_uploadProc, SIGNAL(readyReadStandardOutput()),
        this, SLOT(readUploadStandardOutput()));
    connect(&m_uploadProc, SIGNAL(readyReadStandardError()),
        this, SLOT(readUploadStandardError()));
200

con's avatar
con committed
201
    // Output
202
    connect(&m_outputCollector, SIGNAL(byteDelivery(QByteArray)),
203
        this, SLOT(readDebugeeOutput(QByteArray)));
con's avatar
con committed
204

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

215
    // FIXME: These trigger even if the engine is not active
216
217
218
219
220
221
    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
222
223
}

224
225
void GdbEngine::initializeVariables()
{
226
    m_debuggingHelperState = DebuggingHelperUninitialized;
227
    m_gdbVersion = 100;
hjk's avatar
hjk committed
228
    m_gdbBuildVersion = -1;
229
230
231
232
233
234
235
236
237

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

    m_modulesListOutdated = true;
    m_oldestAcceptableToken = -1;
    m_outputCodec = QTextCodec::codecForLocale();
    m_pendingRequests = 0;
238
    m_autoContinue = false;
239
    m_waitingForFirstBreakpointToBeHit = false;
240
    m_commandsToRunOnTemporaryBreak.clear();
241
    m_cookieForToken.clear();
242
243
244
245
246
247
248
249
250
251
252
    m_customOutputForToken.clear();

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

    m_inbuffer.clear();

    m_address.clear();
    m_currentFunctionArgs.clear();
    m_currentFrame.clear();
253
    m_dumperHelper.clear();
254
255
256
257
258
259
260

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

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

299
    q->showStatusMessage(msg);
con's avatar
con committed
300
301
    QMessageBox::critical(q->mainWindow(), tr("Error"), msg);
    // act as if it was closed by the core
302
303
    if (kill)
        q->exitDebugger();
con's avatar
con committed
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
338
339
340
341
342
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);
}

343
344
345
void GdbEngine::readUploadStandardOutput()
{
    QByteArray ba = m_uploadProc.readAllStandardOutput();
346
    gdbOutputAvailable(LogOutput, QString::fromLocal8Bit(ba, ba.length()));
347
348
349
350
351
}

void GdbEngine::readUploadStandardError()
{
    QByteArray ba = m_uploadProc.readAllStandardError();
352
    gdbOutputAvailable(LogError, QString::fromLocal8Bit(ba, ba.length()));
353
354
}

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

371
372
373
374
375
376
void GdbEngine::readDebugeeOutput(const QByteArray &data)
{
    emit applicationOutputAvailable(m_outputCodec->toUnicode(
            data.constData(), data.length(), &m_outputCodecState));
}

hjk's avatar
hjk committed
377
378
void GdbEngine::debugMessage(const QString &msg)
{
379
    emit gdbOutputAvailable(LogDebug, msg);
hjk's avatar
hjk committed
380
381
}

382
void GdbEngine::handleResponse(const QByteArray &buff)
con's avatar
con committed
383
384
385
{
    static QTime lastTime;

386
    if (theDebuggerBoolSetting(LogTimeStamps))
387
        emit gdbOutputAvailable(LogTime, currentTime());
388
    emit gdbOutputAvailable(LogOutput, QString::fromLocal8Bit(buff, buff.length()));
con's avatar
con committed
389
390
391
392
393

#if 0
    qDebug() // << "#### start response handling #### "
        << currentTime()
        << lastTime.msecsTo(QTime::currentTime()) << "ms,"
394
395
        << "buf:" << buff.left(1500) << "..."
        //<< "buf:" << buff
396
        << "size:" << buff.size();
con's avatar
con committed
397
#else
398
    //qDebug() << "buf:" << buff;
con's avatar
con committed
399
400
401
402
#endif

    lastTime = QTime::currentTime();

403
404
    if (buff.isEmpty() || buff == "(gdb) ")
        return;
con's avatar
con committed
405

406
407
408
    const char *from = buff.constData();
    const char *to = from + buff.size();
    const char *inner;
con's avatar
con committed
409

410
411
412
413
    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
414
            break;
415
416
417
    if (from != inner) {
        token = QByteArray(from, inner - from).toInt();
        from = inner;
418
        //qDebug() << "found token" << token;
419
    }
con's avatar
con committed
420

421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
    // 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
436

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

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

521
        case '@': {
522
523
            QByteArray data = GdbMi::parseCString(from, to);
            m_pendingTargetStreamOutput += data;
524
            break;
con's avatar
con committed
525
526
        }

527
528
529
530
531
532
        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
533
                qq->showApplicationOutput(_(data.mid(9))); // cut "warning: "
con's avatar
con committed
534
535
536
            break;
        }

537
538
        case '^': {
            GdbResultRecord record;
con's avatar
con committed
539

540
            record.token = token;
con's avatar
con committed
541

542
543
544
            for (inner = from; inner != to; ++inner)
                if (*inner < 'a' || *inner > 'z')
                    break;
con's avatar
con committed
545

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
546
            QByteArray resultClass = QByteArray::fromRawData(from, inner - from);
547
548
549
550
551
552
553
554
555
556
557
558
            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
559

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

            //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
598
599
600
601
        }
    }
}

602
void GdbEngine::handleStubAttached(const GdbResultRecord &, const QVariant &)
603
604
605
{
    qq->notifyInferiorStopped();
    handleAqcuiredInferior();
606
    m_autoContinue = true;
607
608
609
610
}

void GdbEngine::stubStarted()
{
611
612
613
    const qint64 attachedPID = m_stubProc.applicationPID();
    qq->notifyInferiorPidChanged(attachedPID);
    postCommand(_("attach %1").arg(attachedPID), CB(handleStubAttached));
614
615
616
617
618
619
620
}

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

con's avatar
con committed
621
622
void GdbEngine::readGdbStandardError()
{
623
    qWarning() << "Unexpected gdb stderr:" << m_gdbProc.readAllStandardError();
con's avatar
con committed
624
625
626
627
}

void GdbEngine::readGdbStandardOutput()
{
628
629
    int newstart = 0;
    int scan = m_inbuffer.size();
con's avatar
con committed
630

631
    m_inbuffer.append(m_gdbProc.readAllStandardOutput());
con's avatar
con committed
632

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

void GdbEngine::interruptInferior()
{
658
    qq->notifyInferiorStopRequested();
659

660
    if (m_gdbProc.state() == QProcess::NotRunning) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
661
        debugMessage(_("TRYING TO INTERRUPT INFERIOR WITHOUT RUNNING GDB"));
662
        qq->notifyInferiorExited();
con's avatar
con committed
663
        return;
664
    }
con's avatar
con committed
665

666
    if (q->startMode() == StartRemote) {
667
        postCommand(_("-exec-interrupt"));
668
669
670
        return;
    }

671
672
    const qint64 attachedPID = q->inferiorPid();
    if (attachedPID <= 0) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
673
        debugMessage(_("TRYING TO INTERRUPT INFERIOR BEFORE PID WAS OBTAINED"));
con's avatar
con committed
674
675
676
        return;
    }

677
678
    if (!interruptProcess(attachedPID))
        debugMessage(_("CANNOT INTERRUPT %1").arg(attachedPID));
con's avatar
con committed
679
680
681
682
}

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

Roberto Raggi's avatar
Roberto Raggi committed
692
    qq->notifyInferiorPidChanged(pid);
693
694
    if (m_dumperInjectionLoad)
        tryLoadDebuggingHelpers();
con's avatar
con committed
695
696
}

697
void GdbEngine::postCommand(const QString &command, GdbCommandCallback callback,
698
699
                            const char *callbackName, const QVariant &cookie)
{
700
    postCommand(command, NoFlags, callback, callbackName, cookie);
701
702
}

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

712
    if (flags & RebuildModel) {
con's avatar
con committed
713
        ++m_pendingRequests;
714
        PENDING_DEBUG("   CALLBACK" << callbackName << "INCREMENTS PENDING TO:"
con's avatar
con committed
715
716
            << m_pendingRequests << command);
    } else {
717
        PENDING_DEBUG("   UNKNOWN CALLBACK" << callbackName << "LEAVES PENDING AT:"
con's avatar
con committed
718
719
720
            << m_pendingRequests << command);
    }

721
    GdbCommand cmd;
con's avatar
con committed
722
    cmd.command = command;
723
724
725
    cmd.flags = flags;
    cmd.callback = callback;
    cmd.callbackName = callbackName;
con's avatar
con committed
726
727
    cmd.cookie = cookie;

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

742
743
744
void GdbEngine::flushCommand(GdbCommand &cmd)
{
    ++currentToken();
745
    cmd.postTime = QTime::currentTime();
746
747
    m_cookieForToken[currentToken()] = cmd;
    cmd.command = QString::number(currentToken()) + cmd.command;
748
    if (cmd.flags & EmbedToken)
749
750
751
752
753
        cmd.command = cmd.command.arg(currentToken());

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

con's avatar
con committed
757
758
void GdbEngine::handleResultRecord(const GdbResultRecord &record)
{
759
760
    //qDebug() << "TOKEN:" << record.token
    //    << " ACCEPTABLE:" << m_oldestAcceptableToken;
con's avatar
con committed
761
762
763
764
765
766
767
    //qDebug() << "";
    //qDebug() << "\nRESULT" << record.token << record.toString();

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

768
    GdbCommand cmd = m_cookieForToken.take(token);
769
770
771
772
773
    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
774

775
    if (record.token < m_oldestAcceptableToken && (cmd.flags & Discardable)) {
776
        //qDebug() << "### SKIPPING OLD RESULT" << record.toString();
con's avatar
con committed
777
778
779
780
781
        //QMessageBox::information(m_mainWindow, tr("Skipped"), "xxx");
        return;
    }

#if 0
782
    qDebug() << "# handleOutput,"
hjk's avatar
hjk committed
783
        << "cmd name:" << cmd.callbackName
784
        << " cmd synchronized:" << cmd.synchronized
con's avatar
con committed
785
786
787
788
789
        << "\n record: " << record.toString();
#endif

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

790
791
    if (cmd.callback)
        (this->*(cmd.callback))(record, cmd.cookie);
con's avatar
con committed
792

793
    if (cmd.flags & RebuildModel) {
con's avatar
con committed
794
        --m_pendingRequests;
hjk's avatar
hjk committed
795
        PENDING_DEBUG("   TYPE " << cmd.callbackName << " DECREMENTS PENDING TO: "
con's avatar
con committed
796
            << m_pendingRequests << cmd.command);
797
        if (m_pendingRequests <= 0) {
hjk's avatar
hjk committed
798
799
            PENDING_DEBUG("\n\n ....  AND TRIGGERS MODEL UPDATE\n");
            rebuildModel();
800
        }
con's avatar
con committed
801
    } else {
hjk's avatar
hjk committed
802
        PENDING_DEBUG("   UNKNOWN TYPE " << cmd.callbackName << " LEAVES PENDING AT: "
con's avatar
con committed
803
804
            << m_pendingRequests << cmd.command);
    }
805
806
807
808
809
810
811
812
813
814

    // 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
815
816
817
818
819
}

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

824
    m_gdbProc.write(command.toLocal8Bit() + "\r\n");
con's avatar
con committed
825
826
}

827
void GdbEngine::handleTargetCore(const GdbResultRecord &, const QVariant &)
828
829
{
    qq->notifyInferiorStopped();
830
831
    q->showStatusMessage(tr("Core file loaded."));
    q->resetLocation();
832
    tryLoadDebuggingHelpers();
833
834
    qq->stackHandler()->setCurrentIndex(0);
    updateLocals(); // Quick shot
835
    reloadStack();
836
    if (supportsThreads())
837
        postCommand(_("-thread-list-ids"), WatchUpdate, CB(handleStackListThreads), 0);
838
    qq->reloadRegisters();
839
840
}

841
void GdbEngine::handleQuerySources(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
842
843
{
    if (record.resultClass == GdbResultDone) {
844
        QMap<QString, QString> oldShortToFull = m_shortToFullName;
con's avatar
con committed
845
846
847
848
849
850
        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
851
            QString fileName = QString::fromLocal8Bit(item.findChild("file").data());
con's avatar
con committed
852
            GdbMi fullName = item.findChild("fullname");
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
853
            QString full = QString::fromLocal8Bit(fullName.data());
con's avatar
con committed
854
855
856
857
            #ifdef Q_OS_WIN
            full = QDir::cleanPath(full);
            #endif
            if (fullName.isValid() && QFileInfo(full).isReadable()) {
858
                //qDebug() << "STORING 2:" << fileName << full;
con's avatar
con committed
859
860
861
862
                m_shortToFullName[fileName] = full;
                m_fullToShortName[full] = fileName;
            }
        }
863
864
        if (m_shortToFullName != oldShortToFull)
            qq->sourceFileWindow()->setSourceFiles(m_shortToFullName);
con's avatar
con committed
865
866
867
    }
}

868
void GdbEngine::handleInfoThreads(const GdbResultRecord &record, const QVariant &)
869
{
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
    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;
        }
887
    }
888
889
890
891
892
    // check "* 3 Thread ..."
    QRegExp re(_("^\\*? +\\d+ +[Tt]hread (\\d+)\\.0x.* in"));
    Q_ASSERT(re.isValid());
    if (re.indexIn(data) != -1)
        maybeHandleInferiorPidChanged(re.cap(1));
893
894
}

895
void GdbEngine::handleInfoProc(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
896
897
{
    if (record.resultClass == GdbResultDone) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
898
        #ifdef Q_OS_MAC
con's avatar
con committed
899
        //^done,process-id="85075"
Oswald Buddenhagen's avatar
nicer    
Oswald Buddenhagen committed
900
        maybeHandleInferiorPidChanged(_(record.data.findChild("process-id").data()));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
901
        #else
con's avatar
con committed
902
        // FIXME: use something more robust
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
903
904
        QRegExp re(__("process (\\d+)"));
        QString data = __(record.data.findChild("consolestreamoutput").data());
con's avatar
con committed
905
906
907
908
909
910
        if (re.indexIn(data) != -1)
            maybeHandleInferiorPidChanged(re.cap(1));
        #endif
    }
}

911
void GdbEngine::handleInfoShared(const GdbResultRecord &record, const QVariant &cookie)
con's avatar
con committed
912
913
914
{
    if (record.resultClass == GdbResultDone) {
        // let the modules handler do the parsing
915
        handleModulesList(record, cookie);
con's avatar
con committed
916
917
918
    }
}

919
#if 0
con's avatar
con committed
920
921
922
923
924
925
926
927
928
929
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();
930
    q->showStatusMessage(tr("Jumped. Stopped."));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
931
    QByteArray output = record.data.findChild("logstreamoutput").data();
932
    if (output.isEmpty())
con's avatar
con committed
933
        return;
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
934
935
936
937
938
939
940
941
942
    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
943
}
944
#endif
con's avatar
con committed
945

946
void GdbEngine::handleExecRunToFunction(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
947
948
949
950
951
952
953
954
{
    // 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();
955
    q->showStatusMessage(tr("Run to Function finished. Stopped."));
con's avatar
con committed
956
    GdbMi frame = record.data.findChild("frame");
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
957
    QString file = QString::fromLocal8Bit(frame.findChild("fullname").data());
con's avatar
con committed
958
    int line = frame.findChild("line").data().toInt();
959
960
    qDebug() << "HIT:" << file << line << "IN" << frame.toString()
        << "--" << record.toString();
con's avatar
con committed
961
962
963
    q->gotoLocation(file, line, true);
}

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
964
static bool isExitedReason(const QByteArray &reason)
con's avatar
con committed
965
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
966
967
968
969
    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
970
971
}

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
972
static bool isStoppedReason(const QByteArray &reason)
con's avatar
con committed
973
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
974
975
976
977
978
979
980
    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"
981
        #ifdef Q_OS_MAC
con's avatar
con committed
982
        || reason.isEmpty()
983
        #endif
con's avatar
con committed
984
985
986
    ;
}

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
987
988
void GdbEngine::handleAqcuiredInferior()
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
989
    #ifdef Q_OS_WIN
990
    postCommand(_("info thread"), CB(handleInfoThreads));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
991
    #elif defined(Q_OS_MAC)
992
    postCommand(_("info pid"), NeedsStop, CB(handleInfoProc));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
993
994
    #else
    postCommand(_("info proc"), CB(handleInfoProc));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
995
    #endif
996
    if (theDebuggerBoolSetting(ListSourceFiles))
997
        reloadSourceFiles();
998

999
    // Reverse debugging. FIXME: Should only be used when available.
1000
1001
    //if (theDebuggerBoolSetting(EnableReverseDebugging))
    //    postCommand(_("target record"));
1002

1003
    tryLoadDebuggingHelpers();
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1004

1005
    #ifndef Q_OS_MAC
1006
    // intentionally after tryLoadDebuggingHelpers(),
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1007
    // otherwise we'd interupt solib loading.
hjk's avatar
hjk committed
1008
    if (theDebuggerBoolSetting(AllPluginBreakpoints)) {
1009
1010
1011
        postCommand(_("set auto-solib-add on"));
        postCommand(_("set stop-on-solib-events 0"));
        postCommand(_("sharedlibrary .*"));
hjk's avatar
hjk committed
1012
    } else if (theDebuggerBoolSetting(SelectedPluginBreakpoints)) {
1013
1014
1015
        postCommand(_("set auto-solib-add on"));
        postCommand(_("set stop-on-solib-events 1"));
        postCommand(_("sharedlibrary ")
hjk's avatar
hjk committed
1016
1017
          + theDebuggerStringSetting(SelectedPluginBreakpointsPattern));
    } else if (theDebuggerBoolSetting(NoPluginBreakpoints)) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1018
        // should be like that already
1019
1020
        if (!m_dumperInjectionLoad)
            postCommand(_("set auto-solib-add off"));
1021
        postCommand(_("set stop-on-solib-events 0"));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1022
    }
1023
1024
    #endif

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1025
1026
    // nicer to see a bit of the world we live in
    reloadModules();
1027
    attemptBreakpointSynchronization();
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1028
1029
}

con's avatar
con committed
1030
1031
void GdbEngine::handleAsyncOutput(const GdbMi &data)
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1032
    const QByteArray &reason = data.findChild("reason").data();
con's avatar
con committed
1033

1034
1035
    if (isExitedReason(reason