gdbengine.cpp 145 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"
37
#include "debuggeragents.h"
con's avatar
con committed
38
39
#include "debuggerconstants.h"
#include "debuggermanager.h"
40
#include "debuggertooltip.h"
con's avatar
con committed
41
42
43
44
45
46
47
48
49
#include "gdbmi.h"
#include "procinterrupt.h"

#include "disassemblerhandler.h"
#include "breakhandler.h"
#include "moduleshandler.h"
#include "registerhandler.h"
#include "stackhandler.h"
#include "watchhandler.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 <QtCore/QCoreApplication>
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
    qq(parent->engineInterface())
con's avatar
con committed
157
{
158
    m_stubProc.setMode(Core::Utils::ConsoleProcess::Debug);
159
160
161
#ifdef Q_OS_UNIX
    m_stubProc.setSettings(Core::ICore::instance()->settings());
#endif
162
163
    initializeVariables();
    initializeConnections();
con's avatar
con committed
164
165
166
167
}

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

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

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

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

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

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

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

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

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

    m_inbuffer.clear();

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

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

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

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

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

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

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

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

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

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

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

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

    lastTime = QTime::currentTime();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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