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

30
31
#define QT_NO_CAST_FROM_ASCII

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

36
#include "watchutils.h"
37
#include "debuggeractions.h"
38
#include "debuggeragents.h"
con's avatar
con committed
39
40
#include "debuggerconstants.h"
#include "debuggermanager.h"
41
#include "debuggertooltip.h"
con's avatar
con committed
42
43
44
45
46
47
48
49
#include "gdbmi.h"
#include "procinterrupt.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
#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
61
#include <QtCore/QMetaObject>
con's avatar
con committed
62
63
#include <QtCore/QTime>
#include <QtCore/QTimer>
64
#include <QtCore/QTextStream>
con's avatar
con committed
65
66

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

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

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

//#define DEBUG_PENDING  1
hjk's avatar
hjk committed
88
//#define DEBUG_SUBITEM  1
con's avatar
con committed
89
90
91
92

#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);
}

hjk's avatar
hjk committed
142
143
///////////////////////////////////////////////////////////////////////
//
hjk's avatar
hjk committed
144
// PlainGdbAdapter
hjk's avatar
hjk committed
145
146
147
//
///////////////////////////////////////////////////////////////////////

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

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

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

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

con's avatar
con committed
173
174
175
176
177
178
///////////////////////////////////////////////////////////////////////
//
// GdbEngine
//
///////////////////////////////////////////////////////////////////////

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

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

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

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

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

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

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

247
    // FIXME: These trigger even if the engine is not active
248
249
250
251
252
253
    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
254
255
}

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

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

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

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

    m_inbuffer.clear();

    m_currentFunctionArgs.clear();
    m_currentFrame.clear();
284
    m_dumperHelper.clear();
285
286
287
288

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

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

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

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

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

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

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

con's avatar
con committed
387
388
389
390
#if 0
static void dump(const char *first, const char *middle, const QString & to)
{
    QByteArray ba(first, middle - first);
391
    Q_UNUSED(to)
con's avatar
con committed
392
393
394
395
396
397
398
399
400
401
402
    // 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

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

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

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

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

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

    lastTime = QTime::currentTime();

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

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

442
443
444
445
    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
446
            break;
447
448
449
    if (from != inner) {
        token = QByteArray(from, inner - from).toInt();
        from = inner;
450
        //qDebug() << "found token" << token;
451
    }
con's avatar
con committed
452

453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
    // 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
468

469
470
471
            GdbMi record;
            while (from != to) {
                GdbMi data;
hjk's avatar
hjk committed
472
                if (*from != ',') {
hjk's avatar
hjk committed
473
474
                    // happens on archer where we get 
                    // 23^running <NL> *running,thread-id="all" <NL> (gdb) 
475
                    record.m_type = GdbMi::Tuple;
hjk's avatar
hjk committed
476
477
478
479
480
                    break;
                }
                ++from; // skip ','
                data.parseResultOrValue(from, to);
                if (data.isValid()) {
481
                    //qDebug() << "parsed response:" << data.toString();
hjk's avatar
hjk committed
482
483
                    record.m_children += data;
                    record.m_type = GdbMi::Tuple;
con's avatar
con committed
484
485
                }
            }
486
487
488
489
            if (asyncClass == "stopped") {
                handleAsyncOutput(record);
            } else if (asyncClass == "running") {
                // Archer has 'thread-id="all"' here
490
491
492
493
            } 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",
494
                // symbols-loaded="0"
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
495
                QByteArray id = record.findChild("id").data();
496
                if (!id.isEmpty())
hjk's avatar
hjk committed
497
                    showStatusMessage(tr("Library %1 loaded.").arg(_(id)));
hjk's avatar
hjk committed
498
499
500
501
            } 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
502
                QByteArray id = record.findChild("id").data();
hjk's avatar
hjk committed
503
                showStatusMessage(tr("Library %1 unloaded.").arg(_(id)));
504
505
            } else if (asyncClass == "thread-group-created") {
                // Archer has "{id="28902"}" 
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
506
                QByteArray id = record.findChild("id").data();
hjk's avatar
hjk committed
507
                showStatusMessage(tr("Thread group %1 created.").arg(_(id)));
508
509
            } else if (asyncClass == "thread-created") {
                //"{id="1",group-id="28902"}" 
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
510
                QByteArray id = record.findChild("id").data();
hjk's avatar
hjk committed
511
                showStatusMessage(tr("Thread %1 created.").arg(_(id)));
512
513
            } else if (asyncClass == "thread-group-exited") {
                // Archer has "{id="28902"}" 
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
514
                QByteArray id = record.findChild("id").data();
hjk's avatar
hjk committed
515
                showStatusMessage(tr("Thread group %1 exited.").arg(_(id)));
516
517
            } else if (asyncClass == "thread-exited") {
                //"{id="1",group-id="28902"}" 
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
518
519
                QByteArray id = record.findChild("id").data();
                QByteArray groupid = record.findChild("group-id").data();
hjk's avatar
hjk committed
520
                showStatusMessage(tr("Thread %1 in group %2 exited.")
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
521
                    .arg(_(id)).arg(_(groupid)));
hjk's avatar
hjk committed
522
            } else if (asyncClass == "thread-selected") {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
523
                QByteArray id = record.findChild("id").data();
hjk's avatar
hjk committed
524
                showStatusMessage(tr("Thread %1 selected.").arg(_(id)));
hjk's avatar
hjk committed
525
                //"{id="2"}" 
526
            #if defined(Q_OS_MAC)
527
528
529
530
531
532
533
534
535
536
537
            } 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 {
538
                qDebug() << "IGNORED ASYNC OUTPUT"
539
                    << asyncClass << record.toString();
540
            }
541
542
            break;
        }
543

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

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

559
560
561
562
563
564
        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
565
                qq->showApplicationOutput(_(data.mid(9))); // cut "warning: "
con's avatar
con committed
566
567
568
            break;
        }

569
570
        case '^': {
            GdbResultRecord record;
con's avatar
con committed
571

572
            record.token = token;
con's avatar
con committed
573

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

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
578
            QByteArray resultClass = QByteArray::fromRawData(from, inner - from);
579
580
581
582
583
584
585
586
587
588
589
590
            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
591

592
593
            from = inner;
            if (from != to) {
hjk's avatar
hjk committed
594
595
596
597
598
599
600
601
602
                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
603
604
                }
            }
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629

            //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
630
631
632
633
        }
    }
}

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

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

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

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

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

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

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

void GdbEngine::interruptInferior()
{
690
    qq->notifyInferiorStopRequested();
691

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

hjk's avatar
hjk committed
698
    m_gdbAdapter->interruptInferior();
con's avatar
con committed
699
700
701
702
}

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

Roberto Raggi's avatar
Roberto Raggi committed
712
    qq->notifyInferiorPidChanged(pid);
713
714
    if (m_dumperInjectionLoad)
        tryLoadDebuggingHelpers();
con's avatar
con committed
715
716
}

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

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

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

741
    GdbCommand cmd;
con's avatar
con committed
742
    cmd.command = command;
743
744
745
    cmd.flags = flags;
    cmd.callback = callback;
    cmd.callbackName = callbackName;
con's avatar
con committed
746
747
    cmd.cookie = cookie;

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

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

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

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

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

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

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

811
    GdbCommand cmd = m_cookieForToken.take(token);
812
813
814
815
816
    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
817

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

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

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

833
834
    if (cmd.callback)
        (this->*(cmd.callback))(record, cmd.cookie);
con's avatar
con committed
835

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

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

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

hjk's avatar
hjk committed
869
    m_gdbAdapter->write(command.toLatin1() + "\r\n");
con's avatar
con committed
870
871
}

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

886
void GdbEngine::handleQuerySources(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
887
888
{
    if (record.resultClass == GdbResultDone) {
889
        QMap<QString, QString> oldShortToFull = m_shortToFullName;
con's avatar
con committed
890
891
892
893
894
895
        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
896
            QString fileName = QString::fromLocal8Bit(item.findChild("file").data());
con's avatar
con committed
897
            GdbMi fullName = item.findChild("fullname");
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
898
            QString full = QString::fromLocal8Bit(fullName.data());
con's avatar
con committed
899
900
901
902
            #ifdef Q_OS_WIN
            full = QDir::cleanPath(full);
            #endif
            if (fullName.isValid() && QFileInfo(full).isReadable()) {
903
                //qDebug() << "STORING 2:" << fileName << full;
con's avatar
con committed
904
905
906
907
                m_shortToFullName[fileName] = full;
                m_fullToShortName[full] = fileName;
            }
        }
908
909
        if (m_shortToFullName != oldShortToFull)
            qq->sourceFileWindow()->setSourceFiles(m_shortToFullName);
con's avatar
con committed
910
911
912
    }
}

913
void GdbEngine::handleInfoThreads(const GdbResultRecord &record, const QVariant &)
914
{
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
    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;
        }
932
    }
933
934
935
936
937
    // check "* 3 Thread ..."
    QRegExp re(_("^\\*? +\\d+ +[Tt]hread (\\d+)\\.0x.* in"));
    Q_ASSERT(re.isValid());
    if (re.indexIn(data) != -1)
        maybeHandleInferiorPidChanged(re.cap(1));
938
939
}

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

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

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

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