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

30
31
#define QT_NO_CAST_FROM_ASCII

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

35
#include "watchutils.h"
36
#include "debuggeractions.h"
con's avatar
con committed
37
38
39
40
41
42
43
44
45
46
47
#include "debuggerconstants.h"
#include "debuggermanager.h"
#include "gdbmi.h"
#include "procinterrupt.h"

#include "disassemblerhandler.h"
#include "breakhandler.h"
#include "moduleshandler.h"
#include "registerhandler.h"
#include "stackhandler.h"
#include "watchhandler.h"
48
#include "sourcefileswindow.h"
con's avatar
con committed
49

50
#include "debuggerdialogs.h"
con's avatar
con committed
51

hjk's avatar
hjk committed
52
#include <utils/qtcassert.h>
53
#include <texteditor/itexteditor.h>
54
#include <coreplugin/icore.h>
hjk's avatar
hjk committed
55

con's avatar
con committed
56
57
58
59
60
#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
#include <QtCore/QTime>
#include <QtCore/QTimer>
61
#include <QtCore/QTextStream>
con's avatar
con committed
62
63

#include <QtGui/QAction>
64
#include <QtGui/QApplication>
con's avatar
con committed
65
66
67
68
#include <QtGui/QLabel>
#include <QtGui/QMainWindow>
#include <QtGui/QMessageBox>
#include <QtGui/QToolTip>
69
70
#include <QtGui/QDialogButtonBox>
#include <QtGui/QPushButton>
71
#ifdef Q_OS_WIN
72
#    include "shared/sharedlibraryinjector.h"
73
#endif
con's avatar
con committed
74

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
75
#ifdef Q_OS_UNIX
con's avatar
con committed
76
77
78
#include <unistd.h>
#include <dlfcn.h>
#endif
hjk's avatar
hjk committed
79
#include <ctype.h>
con's avatar
con committed
80
81
82
83
84
85
86
87
88
89
90
91
92

using namespace Debugger;
using namespace Debugger::Internal;
using namespace Debugger::Constants;

Q_DECLARE_METATYPE(Debugger::Internal::GdbMi);

//#define DEBUG_PENDING  1
//#define DEBUG_SUBITEM  1

#if DEBUG_PENDING
#   define PENDING_DEBUG(s) qDebug() << s
#else
Roberto Raggi's avatar
Roberto Raggi committed
93
#   define PENDING_DEBUG(s)
con's avatar
con committed
94
95
#endif

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
96
97
#define STRINGIFY_INTERNAL(x) #x
#define STRINGIFY(x) STRINGIFY_INTERNAL(x)
98
99
#define CB(callback) &GdbEngine::callback, STRINGIFY(callback)

con's avatar
con committed
100
101
102
103
104
105
static int &currentToken()
{
    static int token = 0;
    return token;
}

106
static const QString tooltipIName = _("tooltip");
107

con's avatar
con committed
108
109
110
111
112
113
///////////////////////////////////////////////////////////////////////
//
// GdbEngine
//
///////////////////////////////////////////////////////////////////////

114
115
116
117
118
119
GdbEngine::GdbEngine(DebuggerManager *parent) :
#ifdef Q_OS_WIN // Do injection loading with MinGW (call loading does not work with 64bit)
    m_dumperInjectionLoad(true)
#else
    m_dumperInjectionLoad(false)
#endif
con's avatar
con committed
120
121
122
{
    q = parent;
    qq = parent->engineInterface();
123
    m_stubProc.setMode(Core::Utils::ConsoleProcess::Debug);
124
125
126
#ifdef Q_OS_UNIX
    m_stubProc.setSettings(Core::ICore::instance()->settings());
#endif
127
128
    initializeVariables();
    initializeConnections();
con's avatar
con committed
129
130
131
132
}

GdbEngine::~GdbEngine()
{
hjk's avatar
hjk committed
133
134
    // prevent sending error messages afterwards
    m_gdbProc.disconnect(this);
con's avatar
con committed
135
136
}

137
void GdbEngine::initializeConnections()
con's avatar
con committed
138
139
{
    // Gdb Process interaction
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
    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)));
158
159
160
161
    connect(&m_uploadProc, SIGNAL(readyReadStandardOutput()),
        this, SLOT(readUploadStandardOutput()));
    connect(&m_uploadProc, SIGNAL(readyReadStandardError()),
        this, SLOT(readUploadStandardError()));
162

con's avatar
con committed
163
    // Output
164
    connect(&m_outputCollector, SIGNAL(byteDelivery(QByteArray)),
165
        this, SLOT(readDebugeeOutput(QByteArray)));
con's avatar
con committed
166

167
168
    connect(this, SIGNAL(gdbOutputAvailable(int,QString)),
        q, SLOT(showDebuggerOutput(int,QString)),
con's avatar
con committed
169
        Qt::QueuedConnection);
170
171
    connect(this, SIGNAL(gdbInputAvailable(int,QString)),
        q, SLOT(showDebuggerInput(int,QString)),
con's avatar
con committed
172
        Qt::QueuedConnection);
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
173
174
    connect(this, SIGNAL(applicationOutputAvailable(QString)),
        q, SLOT(showApplicationOutput(QString)),
con's avatar
con committed
175
        Qt::QueuedConnection);
176

177
    // FIXME: These trigger even if the engine is not active
178
179
180
181
182
183
    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
184
185
}

186
187
void GdbEngine::initializeVariables()
{
188
    m_debuggingHelperState = DebuggingHelperUninitialized;
189
    m_gdbVersion = 100;
hjk's avatar
hjk committed
190
    m_gdbBuildVersion = -1;
191
192
193
194
195
196
197
198
199

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

    m_modulesListOutdated = true;
    m_oldestAcceptableToken = -1;
    m_outputCodec = QTextCodec::codecForLocale();
    m_pendingRequests = 0;
200
    m_autoContinue = false;
201
    m_waitingForFirstBreakpointToBeHit = false;
202
    m_commandsToRunOnTemporaryBreak.clear();
203
204
}

con's avatar
con committed
205
206
207
void GdbEngine::gdbProcError(QProcess::ProcessError error)
{
    QString msg;
208
    bool kill = true;
con's avatar
con committed
209
210
    switch (error) {
        case QProcess::FailedToStart:
211
            kill = false;
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
212
            msg = tr("The Gdb process failed to start. Either the "
con's avatar
con committed
213
                "invoked program '%1' is missing, or you may have insufficient "
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
214
                "permissions to invoke the program.")
hjk's avatar
hjk committed
215
                .arg(theDebuggerStringSetting(GdbLocation));
con's avatar
con committed
216
217
            break;
        case QProcess::Crashed:
218
            kill = false;
con's avatar
con committed
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
            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().");
    }

241
    q->showStatusMessage(msg);
con's avatar
con committed
242
243
    QMessageBox::critical(q->mainWindow(), tr("Error"), msg);
    // act as if it was closed by the core
244
245
    if (kill)
        q->exitDebugger();
con's avatar
con committed
246
247
}

248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
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);
}

285
286
287
void GdbEngine::readUploadStandardOutput()
{
    QByteArray ba = m_uploadProc.readAllStandardOutput();
288
    gdbOutputAvailable(LogOutput, QString::fromLocal8Bit(ba, ba.length()));
289
290
291
292
293
}

void GdbEngine::readUploadStandardError()
{
    QByteArray ba = m_uploadProc.readAllStandardError();
294
    gdbOutputAvailable(LogError, QString::fromLocal8Bit(ba, ba.length()));
295
296
}

con's avatar
con committed
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
#if 0
static void dump(const char *first, const char *middle, const QString & to)
{
    QByteArray ba(first, middle - first);
    Q_UNUSED(to);
    // 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

313
314
315
316
317
318
void GdbEngine::readDebugeeOutput(const QByteArray &data)
{
    emit applicationOutputAvailable(m_outputCodec->toUnicode(
            data.constData(), data.length(), &m_outputCodecState));
}

hjk's avatar
hjk committed
319
320
void GdbEngine::debugMessage(const QString &msg)
{
321
    emit gdbOutputAvailable(LogDebug, msg);
hjk's avatar
hjk committed
322
323
}

324
void GdbEngine::handleResponse(const QByteArray &buff)
con's avatar
con committed
325
326
327
{
    static QTime lastTime;

328
329
330
    if (theDebuggerBoolSetting(LogTimeStamps))
        emit gdbOutputAvailable(LogDebug, currentTime());
    emit gdbOutputAvailable(LogOutput, QString::fromLocal8Bit(buff, buff.length()));
con's avatar
con committed
331
332
333
334
335

#if 0
    qDebug() // << "#### start response handling #### "
        << currentTime()
        << lastTime.msecsTo(QTime::currentTime()) << "ms,"
336
337
        << "buf:" << buff.left(1500) << "..."
        //<< "buf:" << buff
338
        << "size:" << buff.size();
con's avatar
con committed
339
#else
340
    //qDebug() << "buf:" << buff;
con's avatar
con committed
341
342
343
344
#endif

    lastTime = QTime::currentTime();

345
346
    if (buff.isEmpty() || buff == "(gdb) ")
        return;
con's avatar
con committed
347

348
349
350
    const char *from = buff.constData();
    const char *to = from + buff.size();
    const char *inner;
con's avatar
con committed
351

352
353
354
355
    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
356
            break;
357
358
359
    if (from != inner) {
        token = QByteArray(from, inner - from).toInt();
        from = inner;
360
        //qDebug() << "found token" << token;
361
    }
con's avatar
con committed
362

363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
    // 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
378

379
380
381
            GdbMi record;
            while (from != to) {
                GdbMi data;
hjk's avatar
hjk committed
382
                if (*from != ',') {
hjk's avatar
hjk committed
383
384
                    // happens on archer where we get 
                    // 23^running <NL> *running,thread-id="all" <NL> (gdb) 
385
                    record.m_type = GdbMi::Tuple;
hjk's avatar
hjk committed
386
387
388
389
390
                    break;
                }
                ++from; // skip ','
                data.parseResultOrValue(from, to);
                if (data.isValid()) {
391
                    //qDebug() << "parsed response:" << data.toString();
hjk's avatar
hjk committed
392
393
                    record.m_children += data;
                    record.m_type = GdbMi::Tuple;
con's avatar
con committed
394
395
                }
            }
396
397
398
399
            if (asyncClass == "stopped") {
                handleAsyncOutput(record);
            } else if (asyncClass == "running") {
                // Archer has 'thread-id="all"' here
400
401
402
403
            } 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",
404
                // symbols-loaded="0"
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
405
                QByteArray id = record.findChild("id").data();
406
                if (!id.isEmpty())
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
407
                    q->showStatusMessage(tr("Library %1 loaded.").arg(_(id)));
hjk's avatar
hjk committed
408
409
410
411
            } 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
412
413
                QByteArray id = record.findChild("id").data();
                q->showStatusMessage(tr("Library %1 unloaded.").arg(_(id)));
414
415
            } else if (asyncClass == "thread-group-created") {
                // Archer has "{id="28902"}" 
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
416
417
                QByteArray id = record.findChild("id").data();
                q->showStatusMessage(tr("Thread group %1 created.").arg(_(id)));
418
419
            } else if (asyncClass == "thread-created") {
                //"{id="1",group-id="28902"}" 
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
420
421
                QByteArray id = record.findChild("id").data();
                q->showStatusMessage(tr("Thread %1 created.").arg(_(id)));
422
423
            } else if (asyncClass == "thread-group-exited") {
                // Archer has "{id="28902"}" 
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
424
425
                QByteArray id = record.findChild("id").data();
                q->showStatusMessage(tr("Thread group %1 exited.").arg(_(id)));
426
427
            } else if (asyncClass == "thread-exited") {
                //"{id="1",group-id="28902"}" 
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
428
429
                QByteArray id = record.findChild("id").data();
                QByteArray groupid = record.findChild("group-id").data();
430
                q->showStatusMessage(tr("Thread %1 in group %2 exited.")
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
431
                    .arg(_(id)).arg(_(groupid)));
hjk's avatar
hjk committed
432
            } else if (asyncClass == "thread-selected") {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
433
434
                QByteArray id = record.findChild("id").data();
                q->showStatusMessage(tr("Thread %1 selected.").arg(_(id)));
hjk's avatar
hjk committed
435
                //"{id="2"}" 
436
            #if defined(Q_OS_MAC)
437
438
439
440
441
442
443
444
445
446
447
            } 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 {
448
                qDebug() << "IGNORED ASYNC OUTPUT"
449
                    << asyncClass << record.toString();
450
            }
451
452
            break;
        }
453

454
        case '~': {
455
456
457
            QByteArray data = GdbMi::parseCString(from, to);
            m_pendingConsoleStreamOutput += data;
            if (data.startsWith("Reading symbols from ")) {
458
                q->showStatusMessage(tr("Reading %1...").arg(_(data.mid(21))));
459
            }
460
461
            break;
        }
462

463
        case '@': {
464
465
            QByteArray data = GdbMi::parseCString(from, to);
            m_pendingTargetStreamOutput += data;
466
            break;
con's avatar
con committed
467
468
        }

469
470
471
472
473
474
        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
475
                qq->showApplicationOutput(_(data.mid(9))); // cut "warning: "
con's avatar
con committed
476
477
478
            break;
        }

479
480
        case '^': {
            GdbResultRecord record;
con's avatar
con committed
481

482
            record.token = token;
con's avatar
con committed
483

484
485
486
            for (inner = from; inner != to; ++inner)
                if (*inner < 'a' || *inner > 'z')
                    break;
con's avatar
con committed
487

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
488
            QByteArray resultClass = QByteArray::fromRawData(from, inner - from);
489
490
491
492
493
494
495
496
497
498
499
500
            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
501

502
503
            from = inner;
            if (from != to) {
hjk's avatar
hjk committed
504
505
506
507
508
509
510
511
512
                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
513
514
                }
            }
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539

            //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
540
541
542
543
        }
    }
}

544
void GdbEngine::handleStubAttached(const GdbResultRecord &, const QVariant &)
545
546
547
{
    qq->notifyInferiorStopped();
    handleAqcuiredInferior();
548
    m_autoContinue = true;
549
550
551
552
}

void GdbEngine::stubStarted()
{
553
554
555
    const qint64 attachedPID = m_stubProc.applicationPID();
    qq->notifyInferiorPidChanged(attachedPID);
    postCommand(_("attach %1").arg(attachedPID), CB(handleStubAttached));
556
557
558
559
560
561
562
}

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

con's avatar
con committed
563
564
void GdbEngine::readGdbStandardError()
{
565
    qWarning() << "Unexpected gdb stderr:" << m_gdbProc.readAllStandardError();
con's avatar
con committed
566
567
568
569
}

void GdbEngine::readGdbStandardOutput()
{
570
571
    int newstart = 0;
    int scan = m_inbuffer.size();
con's avatar
con committed
572

573
    m_inbuffer.append(m_gdbProc.readAllStandardOutput());
con's avatar
con committed
574

575
576
    while (newstart < m_inbuffer.size()) {
        int start = newstart;
577
        int end = m_inbuffer.indexOf('\n', scan);
578
579
580
581
582
        if (end < 0) {
            m_inbuffer.remove(0, start);
            return;
        }
        newstart = end + 1;
583
        scan = newstart;
584
585
        if (end == start)
            continue;
586
        #if defined(Q_OS_WIN)
587
588
589
590
591
        if (m_inbuffer.at(end - 1) == '\r') {
            --end;
            if (end == start)
                continue;
        }
592
        #endif
593
        handleResponse(QByteArray::fromRawData(m_inbuffer.constData() + start, end - start));
con's avatar
con committed
594
    }
595
    m_inbuffer.clear();
con's avatar
con committed
596
597
598
599
}

void GdbEngine::interruptInferior()
{
600
    qq->notifyInferiorStopRequested();
601

602
    if (m_gdbProc.state() == QProcess::NotRunning) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
603
        debugMessage(_("TRYING TO INTERRUPT INFERIOR WITHOUT RUNNING GDB"));
604
        qq->notifyInferiorExited();
con's avatar
con committed
605
        return;
606
    }
con's avatar
con committed
607

608
    if (q->startMode() == StartRemote) {
609
        postCommand(_("-exec-interrupt"));
610
611
612
        return;
    }

613
614
    const qint64 attachedPID = q->inferiorPid();
    if (attachedPID <= 0) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
615
        debugMessage(_("TRYING TO INTERRUPT INFERIOR BEFORE PID WAS OBTAINED"));
con's avatar
con committed
616
617
618
        return;
    }

619
620
    if (!interruptProcess(attachedPID))
        debugMessage(_("CANNOT INTERRUPT %1").arg(attachedPID));
con's avatar
con committed
621
622
623
624
}

void GdbEngine::maybeHandleInferiorPidChanged(const QString &pid0)
{
625
    const qint64 pid = pid0.toLongLong();
con's avatar
con committed
626
    if (pid == 0) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
627
        debugMessage(_("Cannot parse PID from %1").arg(pid0));
con's avatar
con committed
628
629
        return;
    }
630
    if (pid == q->inferiorPid())
con's avatar
con committed
631
        return;
632
633
    debugMessage(_("FOUND PID %1").arg(pid));    

Roberto Raggi's avatar
Roberto Raggi committed
634
    qq->notifyInferiorPidChanged(pid);
635
636
    if (m_dumperInjectionLoad)
        tryLoadDebuggingHelpers();
con's avatar
con committed
637
638
}

639
void GdbEngine::postCommand(const QString &command, GdbCommandCallback callback,
640
641
                            const char *callbackName, const QVariant &cookie)
{
642
    postCommand(command, NoFlags, callback, callbackName, cookie);
643
644
}

645
void GdbEngine::postCommand(const QString &command, GdbCommandFlags flags,
646
647
                            GdbCommandCallback callback, const char *callbackName,
                            const QVariant &cookie)
con's avatar
con committed
648
649
{
    if (m_gdbProc.state() == QProcess::NotRunning) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
650
        debugMessage(_("NO GDB PROCESS RUNNING, CMD IGNORED: ") + command);
con's avatar
con committed
651
652
653
        return;
    }

654
    if (flags & RebuildModel) {
con's avatar
con committed
655
        ++m_pendingRequests;
656
        PENDING_DEBUG("   CALLBACK" << callbackName << "INCREMENTS PENDING TO:"
con's avatar
con committed
657
658
            << m_pendingRequests << command);
    } else {
659
        PENDING_DEBUG("   UNKNOWN CALLBACK" << callbackName << "LEAVES PENDING AT:"
con's avatar
con committed
660
661
662
            << m_pendingRequests << command);
    }

663
    GdbCommand cmd;
con's avatar
con committed
664
    cmd.command = command;
665
666
667
    cmd.flags = flags;
    cmd.callback = callback;
    cmd.callbackName = callbackName;
con's avatar
con committed
668
669
    cmd.cookie = cookie;

670
    if ((flags & NeedsStop) && q->status() != DebuggerInferiorStopped
671
672
673
            && q->status() != DebuggerProcessStartingUp) {
        // queue the commands that we cannot send at once
        QTC_ASSERT(q->status() == DebuggerInferiorRunning,
674
            qDebug() << "STATUS:" << q->status());
675
        q->showStatusMessage(tr("Stopping temporarily."));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
676
        debugMessage(_("QUEUING COMMAND ") + cmd.command);
677
678
679
        m_commandsToRunOnTemporaryBreak.append(cmd);
        interruptInferior();
    } else if (!command.isEmpty()) {
680
        flushCommand(cmd);
con's avatar
con committed
681
682
683
    }
}

684
685
686
687
688
void GdbEngine::flushCommand(GdbCommand &cmd)
{
    ++currentToken();
    m_cookieForToken[currentToken()] = cmd;
    cmd.command = QString::number(currentToken()) + cmd.command;
689
    if (cmd.flags & EmbedToken)
690
691
692
693
694
        cmd.command = cmd.command.arg(currentToken());

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

con's avatar
con committed
698
699
void GdbEngine::handleResultRecord(const GdbResultRecord &record)
{
700
701
    //qDebug() << "TOKEN:" << record.token
    //    << " ACCEPTABLE:" << m_oldestAcceptableToken;
con's avatar
con committed
702
703
704
705
706
707
708
    //qDebug() << "";
    //qDebug() << "\nRESULT" << record.token << record.toString();

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

709
    GdbCommand cmd = m_cookieForToken.take(token);
con's avatar
con committed
710

711
    if (record.token < m_oldestAcceptableToken && (cmd.flags & Discardable)) {
712
        //qDebug() << "### SKIPPING OLD RESULT" << record.toString();
con's avatar
con committed
713
714
715
716
717
        //QMessageBox::information(m_mainWindow, tr("Skipped"), "xxx");
        return;
    }

#if 0
718
    qDebug() << "# handleOutput,"
hjk's avatar
hjk committed
719
        << "cmd name:" << cmd.callbackName
720
        << " cmd synchronized:" << cmd.synchronized
con's avatar
con committed
721
722
723
724
725
        << "\n record: " << record.toString();
#endif

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

726
727
    if (cmd.callback)
        (this->*(cmd.callback))(record, cmd.cookie);
con's avatar
con committed
728

729
    if (cmd.flags & RebuildModel) {
con's avatar
con committed
730
        --m_pendingRequests;
hjk's avatar
hjk committed
731
        PENDING_DEBUG("   TYPE " << cmd.callbackName << " DECREMENTS PENDING TO: "
con's avatar
con committed
732
            << m_pendingRequests << cmd.command);
733
        if (m_pendingRequests <= 0) {
hjk's avatar
hjk committed
734
735
            PENDING_DEBUG("\n\n ....  AND TRIGGERS MODEL UPDATE\n");
            rebuildModel();
736
        }
con's avatar
con committed
737
    } else {
hjk's avatar
hjk committed
738
        PENDING_DEBUG("   UNKNOWN TYPE " << cmd.callbackName << " LEAVES PENDING AT: "
con's avatar
con committed
739
740
            << m_pendingRequests << cmd.command);
    }
741
742
743
744
745
746
747
748
749
750

    // 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
751
752
753
754
755
}

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

760
    m_gdbProc.write(command.toLocal8Bit() + "\r\n");
con's avatar
con committed
761
762
}

763
void GdbEngine::handleTargetCore(const GdbResultRecord &, const QVariant &)
764
765
{
    qq->notifyInferiorStopped();
766
767
    q->showStatusMessage(tr("Core file loaded."));
    q->resetLocation();
768
    tryLoadDebuggingHelpers();
769
770
    qq->stackHandler()->setCurrentIndex(0);
    updateLocals(); // Quick shot
771
    reloadStack();
772
    if (supportsThreads())
773
        postCommand(_("-thread-list-ids"), WatchUpdate, CB(handleStackListThreads), 0);
774
    qq->reloadRegisters();
775
776
}

777
void GdbEngine::handleQuerySources(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
778
779
{
    if (record.resultClass == GdbResultDone) {
780
        QMap<QString, QString> oldShortToFull = m_shortToFullName;
con's avatar
con committed
781
782
783
784
785
786
        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
787
            QString fileName = QString::fromLocal8Bit(item.findChild("file").data());
con's avatar
con committed
788
            GdbMi fullName = item.findChild("fullname");
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
789
            QString full = QString::fromLocal8Bit(fullName.data());
con's avatar
con committed
790
791
792
793
            #ifdef Q_OS_WIN
            full = QDir::cleanPath(full);
            #endif
            if (fullName.isValid() && QFileInfo(full).isReadable()) {
794
                //qDebug() << "STORING 2:" << fileName << full;
con's avatar
con committed
795
796
797
798
                m_shortToFullName[fileName] = full;
                m_fullToShortName[full] = fileName;
            }
        }
799
800
        if (m_shortToFullName != oldShortToFull)
            qq->sourceFileWindow()->setSourceFiles(m_shortToFullName);
con's avatar
con committed
801
802
803
    }
}

804
void GdbEngine::handleInfoThreads(const GdbResultRecord &record, const QVariant &)
805
{
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
    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;
        }
823
    }
824
825
826
827
828
    // check "* 3 Thread ..."
    QRegExp re(_("^\\*? +\\d+ +[Tt]hread (\\d+)\\.0x.* in"));
    Q_ASSERT(re.isValid());
    if (re.indexIn(data) != -1)
        maybeHandleInferiorPidChanged(re.cap(1));
829
830
}

831
void GdbEngine::handleInfoProc(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
832
833
{
    if (record.resultClass == GdbResultDone) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
834
        #ifdef Q_OS_MAC
con's avatar
con committed
835
        //^done,process-id="85075"
Oswald Buddenhagen's avatar
nicer    
Oswald Buddenhagen committed
836
        maybeHandleInferiorPidChanged(_(record.data.findChild("process-id").data()));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
837
        #else
con's avatar
con committed
838
        // FIXME: use something more robust
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
839
840
        QRegExp re(__("process (\\d+)"));
        QString data = __(record.data.findChild("consolestreamoutput").data());
con's avatar
con committed
841
842
843
844
845
846
        if (re.indexIn(data) != -1)
            maybeHandleInferiorPidChanged(re.cap(1));
        #endif
    }
}

847
void GdbEngine::handleInfoShared(const GdbResultRecord &record, const QVariant &cookie)
con's avatar
con committed
848
849
850
{
    if (record.resultClass == GdbResultDone) {
        // let the modules handler do the parsing
851
        handleModulesList(record, cookie);
con's avatar
con committed
852
853
854
    }
}

855
#if 0
con's avatar
con committed
856
857
858
859
860
861
862
863
864
865
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();
866
    q->showStatusMessage(tr("Jumped. Stopped."));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
867
    QByteArray output = record.data.findChild("logstreamoutput").data();
868
    if (output.isEmpty())
con's avatar
con committed
869
        return;
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
870
871
872
873
874
875
876
877
878
    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
879
}
880
#endif
con's avatar
con committed
881

882
void GdbEngine::handleExecRunToFunction(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
883
884
885
886
887
888
889
890
{
    // 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();
891
    q->showStatusMessage(tr("Run to Function finished. Stopped."));
con's avatar
con committed
892
    GdbMi frame = record.data.findChild("frame");
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
893
    QString file = QString::fromLocal8Bit(frame.findChild("fullname").data());
con's avatar
con committed
894
    int line = frame.findChild("line").data().toInt();
895
896
    qDebug() << "HIT:" << file << line << "IN" << frame.toString()
        << "--" << record.toString();
con's avatar
con committed
897
898
899
    q->gotoLocation(file, line, true);
}

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
900
static bool isExitedReason(const QByteArray &reason)
con's avatar
con committed
901
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
902
903
904
905
    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
906
907
}

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
908
static bool isStoppedReason(const QByteArray &reason)
con's avatar
con committed
909
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
910
911
912
913
914
915
916
    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"
917
        #ifdef Q_OS_MAC
con's avatar
con committed
918
        || reason.isEmpty()
919
        #endif
con's avatar
con committed
920
921
922
    ;
}

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
923
924
void GdbEngine::handleAqcuiredInferior()
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
925
    #ifdef Q_OS_WIN
926
    postCommand(_("info thread"), CB(handleInfoThreads));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
927
    #elif defined(Q_OS_MAC)
928
    postCommand(_("info pid"), NeedsStop, CB(handleInfoProc));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
929
930
    #else
    postCommand(_("info proc"), CB(handleInfoProc));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
931
    #endif
932
    if (theDebuggerBoolSetting(ListSourceFiles))
933
        reloadSourceFiles();
934

935
    // Reverse debugging. FIXME: Should only be used when available.
936
937
    //if (theDebuggerBoolSetting(EnableReverseDebugging))
    //    postCommand(_("target record"));
938

939
    tryLoadDebuggingHelpers();
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
940

941
    #ifndef Q_OS_MAC
942
    // intentionally after tryLoadDebuggingHelpers(),
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
943
    // otherwise we'd interupt solib loading.
hjk's avatar
hjk committed
944
    if (theDebuggerBoolSetting(AllPluginBreakpoints)) {
945
946
947
        postCommand(_("set auto-solib-add on"));
        postCommand(_("set stop-on-solib-events 0"));
        postCommand(_("sharedlibrary .*"));
hjk's avatar
hjk committed
948
    } else if (theDebuggerBoolSetting(SelectedPluginBreakpoints)) {
949
950
951
        postCommand(_("set auto-solib-add on"));
        postCommand(_("set stop-on-solib-events 1"));
        postCommand(_("sharedlibrary ")
hjk's avatar
hjk committed
952
953
          + theDebuggerStringSetting(SelectedPluginBreakpointsPattern));
    } else if (theDebuggerBoolSetting(NoPluginBreakpoints)) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
954
        // should be like that already
955
956
        if (!m_dumperInjectionLoad)
            postCommand(_("set auto-solib-add off"));
957
        postCommand(_("set stop-on-solib-events 0"));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
958
    }
959
960
    #endif

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
961
962
    // nicer to see a bit of the world we live in
    reloadModules();
963
    attemptBreakpointSynchronization();
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
964
965
}

con's avatar
con committed
966
967
void GdbEngine::handleAsyncOutput(const GdbMi &data)
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
968
    const QByteArray &reason = data.findChild("reason").data();
con's avatar
con committed
969

970
971
    if (isExitedReason(reason)) {
        qq->notifyInferiorExited();
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
972
        QString msg;
973
        if (reason == "exited") {
974
975
976
977
978
            msg = tr("Program exited with exit code %1")
                .arg(_(data.findChild("exit-code").toString()));
        } else if (reason == "exited-signalled" || reason == "signal-received") {
            msg = tr("Program exited after receiving signal %1")
                .arg(_(data.findChild("signal-name").toString()));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
979
        } else {
980
            msg = tr("Program exited normally");
981
982
        }
        q->showStatusMessage(msg);
983
        postCommand(_("-gdb-exit"), CB(handleExit));
984
985
986
987
        return;
    }


988
989
990
    //MAC: bool isFirstStop = data.findChild("bkptno").data() == "1";
    //!MAC: startSymbolName == data.findChild("frame").findChild("func")
    if (m_waitingForFirstBreakpointToBeHit) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
991
992
        m_waitingForFirstBreakpointToBeHit = false;

993
        // If the executable dies already that early we might get something
994
        // like >49*stopped,reason="exited",exit-code="0177"
995
996
        // This is handled now above.

997
        qq->notifyInferiorStopped();
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
998
        handleAqcuiredInferior();
999
        m_autoContinue = true;
1000
        return;
1001
1002
    }

1003
    if (!m_commandsToRunOnTemporaryBreak.isEmpty()) {
Roberto Raggi's avatar
Roberto Raggi committed
1004
        QTC_ASSERT(q->status() == DebuggerInferiorStopRequested,
1005
            qDebug() << "STATUS:" << q->status())
1006
1007
        qq->notifyInferiorStopped();
        // FIXME: racy
1008
1009
        while (!m_commandsToRunOnTemporaryBreak.isEmpty()) {
            GdbCommand cmd = m_commandsToRunOnTemporaryBreak.takeFirst();
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1010
            debugMessage(_("RUNNING QUEUED COMMAND %1 %2")
1011