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 "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 <QtGui/QApplication>
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
248
    m_customOutputForToken.clear();

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

    m_inbuffer.clear();

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

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

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

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

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

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

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

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

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

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

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

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

    lastTime = QTime::currentTime();

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

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

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

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

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

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

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

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

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

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

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

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

556
557
            from = inner;
            if (from != to) {
hjk's avatar
hjk committed
558
559
560
561
562
563
564
565
566
                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
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
593

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

764
    GdbCommand cmd = m_cookieForToken.take(token);
765
766
767
768
769
    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
770

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

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

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

786
787
    if (cmd.callback)
        (this->*(cmd.callback))(record, cmd.cookie);
con's avatar
con committed
788

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

    // 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
811
812
813
814
815
}

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

820
    m_gdbProc.write(command.toLocal8Bit() + "\r\n");
con's avatar
con committed
821
822
}

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

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

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

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

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

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

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

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
960
static bool isExitedReason(const QByteArray &reason)
con's avatar
con committed
961
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
962
963
964
965
    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
966
967
}

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

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

995
    // Reverse debugging. FIXME: Should only be used when available.
996
997
    //if (theDebuggerBoolSetting(EnableReverseDebugging))
    //    postCommand(_("target record"));
998

999
    tryLoadDebuggingHelpers();
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1000

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

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1021
1022
    // nicer to see a bit of the world we live in
    reloadModules();
1023
    attemptBreakpointSynchronization();
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1024
1025
}

con's avatar
con committed
1026
1027
void GdbEngine::handleAsyncOutput(const GdbMi &data)
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1028
    const QByteArray &reason = data.findChild("reason").data();
con's avatar
con committed
1029

1030
1031
    if (isExitedReason(reason)) {
        qq->notifyInferiorExited();
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1032
        QString msg;
1033
        if (