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);
hjk's avatar
hjk committed
151
    QString fileName = fi.absoluteFilePath();
hjk's avatar
hjk committed
152
    m_engine->postCommand(_("-file-exec-and-symbols ") + fileName,
hjk's avatar
hjk committed
153
154
155
        &GdbEngine::handleFileExecAndSymbols, "handleFileExecAndSymbols");
}

hjk's avatar
hjk committed
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
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
174
175
176
177
178
179
///////////////////////////////////////////////////////////////////////
//
// GdbEngine
//
///////////////////////////////////////////////////////////////////////

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

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

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

    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
225
        m_manager, SLOT(exitDebugger()));
226
227
228

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

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

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

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

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

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

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

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

    m_inbuffer.clear();

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

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

con's avatar
con committed
295
296
297
void GdbEngine::gdbProcError(QProcess::ProcessError error)
{
    QString msg;
298
    bool kill = true;
con's avatar
con committed
299
300
    switch (error) {
        case QProcess::FailedToStart:
301
            kill = false;
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
302
            msg = tr("The Gdb process failed to start. Either the "
con's avatar
con committed
303
                "invoked program '%1' is missing, or you may have insufficient "
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
304
                "permissions to invoke the program.")
hjk's avatar
hjk committed
305
                .arg(theDebuggerStringSetting(GdbLocation));
306
            emitStartFailed();
con's avatar
con committed
307
308
            break;
        case QProcess::Crashed:
309
            kill = false;
con's avatar
con committed
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
            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
332
333
    showStatusMessage(msg);
    QMessageBox::critical(mainWindow(), tr("Error"), msg);
con's avatar
con committed
334
    // act as if it was closed by the core
335
    if (kill)
hjk's avatar
hjk committed
336
        m_manager->exitDebugger();
con's avatar
con committed
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
371
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
372
373
    showStatusMessage(msg);
    QMessageBox::critical(mainWindow(), tr("Error"), msg);
374
375
}

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

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

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

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

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

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

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

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

    lastTime = QTime::currentTime();

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

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

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

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

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

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

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

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

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

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

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

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

593
594
            from = inner;
            if (from != to) {
hjk's avatar
hjk committed
595
596
597
598
599
600
601
602
603
                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
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
630

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

763
764
765
void GdbEngine::flushCommand(GdbCommand &cmd)
{
    ++currentToken();
766
    cmd.postTime = QTime::currentTime();
767
768
    m_cookieForToken[currentToken()] = cmd;
    cmd.command = QString::number(currentToken()) + cmd.command;
769
    if (cmd.flags & EmbedToken)
770
771
        cmd.command = cmd.command.arg(currentToken());

772
    emit gdbInputAvailable(LogInput, cmd.command);
773
    executeDebuggerCommand(cmd.command);
774
775
}

con's avatar
con committed
776
777
void GdbEngine::handleResultRecord(const GdbResultRecord &record)
{
778
779
    //qDebug() << "TOKEN:" << record.token
    //    << " ACCEPTABLE:" << m_oldestAcceptableToken;
con's avatar
con committed
780
781
782
783
784
785
786
    //qDebug() << "";
    //qDebug() << "\nRESULT" << record.token << record.toString();

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

787
788
789
790
791
792
793
794
795
    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
796
            QMessageBox::critical(mainWindow(), tr("Error"),
797
                tr("Executable failed:\n") + QString::fromLocal8Bit(msg));
hjk's avatar
hjk committed
798
            showStatusMessage(tr("Process failed to start."));
799
800
801
802
803
804
805
            exitDebugger();
            //qq->notifyInferiorStopped();
            //qq->notifyInferiorExited();
        }
        return;
    }

806
    GdbCommand cmd = m_cookieForToken.take(token);
807
808
809
810
811
    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
812

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

#if 0
820
    qDebug() << "# handleOutput,"
hjk's avatar
hjk committed
821
        << "cmd name:" << cmd.callbackName
822
        << " cmd synchronized:" << cmd.synchronized
con's avatar
con committed
823
824
825
826
827
        << "\n record: " << record.toString();
#endif

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

828
829
    if (cmd.callback)
        (this->*(cmd.callback))(record, cmd.cookie);
con's avatar
con committed
830

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

    // 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
851
        showStatusMessage(tr("Continuing after temporary stop."));
852
    }
con's avatar
con committed
853
854
855
856
}

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

hjk's avatar
hjk committed
862
    m_gdbAdapter->write(command.toLatin1() + "\r\n");
con's avatar
con committed
863
864
}

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

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

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

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

949
void GdbEngine::handleInfoShared(const GdbResultRecord &record, const QVariant &cookie)
con's avatar
con committed
950
951
952
{
    if (record.resultClass == GdbResultDone) {
        // let the modules handler do the parsing
953
        handleModulesList(record, cookie);
con's avatar
con committed
954
955
956
    }
}

957
#if 0
con's avatar
con committed
958
959
960
961
962
963
964
965
966
967
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
968
    showStatusMessage(tr("Jumped. Stopped."));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
969
    QByteArray output = record.data.findChild("logstreamoutput").data();
970
    if (output.isEmpty())
con's avatar
con committed
971
        return;
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
972
973
974
975
976
977
    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
978
            m_manager->gotoLocation(file, line, true);
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
979
980
        }
    }
con's avatar
con committed
981
}
982
#endif
con's avatar
con committed
983

984
void GdbEngine::handleExecRunToFunction(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
985
986
987
988
989
990
991
992
{
    // 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
993
    showStatusMessage(tr("Run to Function finished. Stopped."));
con's avatar
con committed
994
    GdbMi frame = record.data.findChild("frame");
995
996
997
998
    StackFrame f;
    f.file = QString::fromLocal8Bit(frame.findChild("fullname").data());
    f.line = frame.findChild("line").data().toInt();
    f.address = _(frame.findChild("addr").data());
hjk's avatar
hjk committed
999
    m_manager->gotoLocation(f, true);
con's avatar
con committed
1000
1001
}

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1002
static bool isExitedReason(const QByteArray &reason)