gdbengine.cpp 146 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
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
    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();
            QMessageBox::critical(q->mainWindow(), tr("Error"),
                tr("Executable failed:\n") + QString::fromLocal8Bit(msg));
            q->showStatusMessage(tr("Process failed to start."));
            exitDebugger();
            //qq->notifyInferiorStopped();
            //qq->notifyInferiorExited();
        }
        return;
    }

784
    GdbCommand cmd = m_cookieForToken.take(token);
785
786
787
788
789
    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
790

791
    if (record.token < m_oldestAcceptableToken && (cmd.flags & Discardable)) {
792
        //qDebug() << "### SKIPPING OLD RESULT" << record.toString();
793
        //QMessageBox::information(q->mainWindow(), tr("Skipped"), "xxx");
con's avatar
con committed
794
795
796
797
        return;
    }

#if 0
798
    qDebug() << "# handleOutput,"
hjk's avatar
hjk committed
799
        << "cmd name:" << cmd.callbackName
800
        << " cmd synchronized:" << cmd.synchronized
con's avatar
con committed
801
802
803
804
805
        << "\n record: " << record.toString();
#endif

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

806
807
    if (cmd.callback)
        (this->*(cmd.callback))(record, cmd.cookie);
con's avatar
con committed
808

809
    if (cmd.flags & RebuildModel) {
con's avatar
con committed
810
        --m_pendingRequests;
hjk's avatar
hjk committed
811
        PENDING_DEBUG("   TYPE " << cmd.callbackName << " DECREMENTS PENDING TO: "
con's avatar
con committed
812
            << m_pendingRequests << cmd.command);
813
        if (m_pendingRequests <= 0) {
hjk's avatar
hjk committed
814
815
            PENDING_DEBUG("\n\n ....  AND TRIGGERS MODEL UPDATE\n");
            rebuildModel();
816
        }
con's avatar
con committed
817
    } else {
hjk's avatar
hjk committed
818
        PENDING_DEBUG("   UNKNOWN TYPE " << cmd.callbackName << " LEAVES PENDING AT: "
con's avatar
con committed
819
820
            << m_pendingRequests << cmd.command);
    }
821
822
823
824
825
826
827
828
829
830

    // 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
831
832
833
834
835
}

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

840
    m_gdbProc.write(command.toLocal8Bit() + "\r\n");
con's avatar
con committed
841
842
}

843
void GdbEngine::handleTargetCore(const GdbResultRecord &, const QVariant &)
844
845
{
    qq->notifyInferiorStopped();
846
847
    q->showStatusMessage(tr("Core file loaded."));
    q->resetLocation();
848
    tryLoadDebuggingHelpers();
849
850
    qq->stackHandler()->setCurrentIndex(0);
    updateLocals(); // Quick shot
851
    reloadStack();
852
    if (supportsThreads())
853
        postCommand(_("-thread-list-ids"), WatchUpdate, CB(handleStackListThreads), 0);
854
    qq->reloadRegisters();
855
856
}

857
void GdbEngine::handleQuerySources(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
858
859
{
    if (record.resultClass == GdbResultDone) {
860
        QMap<QString, QString> oldShortToFull = m_shortToFullName;
con's avatar
con committed
861
862
863
864
865
866
        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
867
            QString fileName = QString::fromLocal8Bit(item.findChild("file").data());
con's avatar
con committed
868
            GdbMi fullName = item.findChild("fullname");
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
869
            QString full = QString::fromLocal8Bit(fullName.data());
con's avatar
con committed
870
871
872
873
            #ifdef Q_OS_WIN
            full = QDir::cleanPath(full);
            #endif
            if (fullName.isValid() && QFileInfo(full).isReadable()) {
874
                //qDebug() << "STORING 2:" << fileName << full;
con's avatar
con committed
875
876
877
878
                m_shortToFullName[fileName] = full;
                m_fullToShortName[full] = fileName;
            }
        }
879
880
        if (m_shortToFullName != oldShortToFull)
            qq->sourceFileWindow()->setSourceFiles(m_shortToFullName);
con's avatar
con committed
881
882
883
    }
}

884
void GdbEngine::handleInfoThreads(const GdbResultRecord &record, const QVariant &)
885
{
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
    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;
        }
903
    }
904
905
906
907
908
    // check "* 3 Thread ..."
    QRegExp re(_("^\\*? +\\d+ +[Tt]hread (\\d+)\\.0x.* in"));
    Q_ASSERT(re.isValid());
    if (re.indexIn(data) != -1)
        maybeHandleInferiorPidChanged(re.cap(1));
909
910
}

911
void GdbEngine::handleInfoProc(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
912
913
{
    if (record.resultClass == GdbResultDone) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
914
        #ifdef Q_OS_MAC
con's avatar
con committed
915
        //^done,process-id="85075"
Oswald Buddenhagen's avatar
nicer    
Oswald Buddenhagen committed
916
        maybeHandleInferiorPidChanged(_(record.data.findChild("process-id").data()));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
917
        #else
con's avatar
con committed
918
        // FIXME: use something more robust
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
919
920
        QRegExp re(__("process (\\d+)"));
        QString data = __(record.data.findChild("consolestreamoutput").data());
con's avatar
con committed
921
922
923
924
925
926
        if (re.indexIn(data) != -1)
            maybeHandleInferiorPidChanged(re.cap(1));
        #endif
    }
}

927
void GdbEngine::handleInfoShared(const GdbResultRecord &record, const QVariant &cookie)
con's avatar
con committed
928
929
930
{
    if (record.resultClass == GdbResultDone) {
        // let the modules handler do the parsing
931
        handleModulesList(record, cookie);
con's avatar
con committed
932
933
934
    }
}

935
#if 0
con's avatar
con committed
936
937
938
939
940
941
942
943
944
945
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();
946
    q->showStatusMessage(tr("Jumped. Stopped."));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
947
    QByteArray output = record.data.findChild("logstreamoutput").data();
948
    if (output.isEmpty())
con's avatar
con committed
949
        return;
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
950
951
952
953
954
955
956
957
958
    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
959
}
960
#endif
con's avatar
con committed
961

962
void GdbEngine::handleExecRunToFunction(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
963
964
965
966
967
968
969
970
{
    // 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();
971
    q->showStatusMessage(tr("Run to Function finished. Stopped."));
con's avatar
con committed
972
    GdbMi frame = record.data.findChild("frame");
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
973
    QString file = QString::fromLocal8Bit(frame.findChild("fullname").data());
con's avatar
con committed
974
    int line = frame.findChild("line").data().toInt();
975
976
    qDebug() << "HIT:" << file << line << "IN" << frame.toString()
        << "--" << record.toString();
con's avatar
con committed
977
978
979
    q->gotoLocation(file, line, true);
}

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
980
static bool isExitedReason(const QByteArray &reason)
con's avatar
con committed
981
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
982
983
984
985
    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
986
987
}

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
988
static bool isStoppedReason(const QByteArray &reason)
con's avatar
con committed
989
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
990
991
992
993
994
995
996
    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"
997
        #ifdef Q_OS_MAC
con's avatar
con committed
998
        || reason.isEmpty()
999
        #endif
con's avatar
con committed
1000
1001
1002
    ;
}

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1003
1004
void GdbEngine::handleAqcuiredInferior()
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1005
    #ifdef Q_OS_WIN
1006
    postCommand(_("info thread"), CB(handleInfoThreads));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1007
    #elif defined(Q_OS_MAC)
1008
    postCommand(_("info pid"), NeedsStop, CB(handleInfoProc));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1009
1010
    #else
    postCommand(_("info proc"), CB(handleInfoProc));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1011
    #endif
1012
    if (theDebuggerBoolSetting(ListSourceFiles))
1013
        reloadSourceFiles();
1014

1015
    // Reverse debugging. FIXME: Should only be used when available.
1016
1017
    //if (theDebuggerBoolSetting(EnableReverseDebugging))
    //    postCommand(_("target record"));
1018

1019
    tryLoadDebuggingHelpers();
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1020

1021
    #ifndef Q_OS_MAC
1022
    // intentionally after tryLoadDebuggingHelpers(),
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1023
    // otherwise we'd interupt solib loading.
hjk's avatar
hjk committed
1024
    if (theDebuggerBoolSetting(AllPluginBreakpoints)) {
1025
1026
1027
        postCommand(_("set auto-solib-add on"));
        postCommand(_("set stop-on-solib-events 0"));
        postCommand(_("sharedlibrary .*"));
hjk's avatar
hjk committed
1028
    } else if (theDebuggerBoolSetting(SelectedPluginBreakpoints)) {
1029
1030
1031
        postCommand(_("set auto-solib-add on"));
        postCommand(_("set stop-on-solib-events 1"));
        postCommand(_("sharedlibrary ")
hjk's avatar
hjk committed
1032
1033
          + theDebuggerStringSetting(SelectedPluginBreakpointsPattern));
    } else if (theDebuggerBoolSetting(NoPluginBreakpoints)) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1034
        // should be like that already
1035
1036
        if (!m_dumperInjectionLoad)
            postCommand(_("set auto-solib-add off"));
1037
        postCommand(_("set stop-on-solib-events 0"));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1038
    }
1039
1040
    #endif

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1041
1042
    // nicer to see a bit of the world we live in
    reloadModules();
1043
    attemptBreakpointSynchronization();