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

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

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

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

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

#include <QtGui/QAction>
65
#include <QtGui/QApplication>
con's avatar
con committed
66
67
68
#include <QtGui/QLabel>
#include <QtGui/QMainWindow>
#include <QtGui/QMessageBox>
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
106
107
108
109
110
111
static int &currentToken()
{
    static int token = 0;
    return token;
}

///////////////////////////////////////////////////////////////////////
//
// GdbEngine
//
///////////////////////////////////////////////////////////////////////

112
113
114
115
116
117
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
118
119
120
{
    q = parent;
    qq = parent->engineInterface();
121
    m_stubProc.setMode(Core::Utils::ConsoleProcess::Debug);
122
123
124
#ifdef Q_OS_UNIX
    m_stubProc.setSettings(Core::ICore::instance()->settings());
#endif
125
126
    initializeVariables();
    initializeConnections();
con's avatar
con committed
127
128
129
130
}

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

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

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

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

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

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

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

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

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

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

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

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

con's avatar
con committed
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
#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

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

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

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

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

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

    lastTime = QTime::currentTime();

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

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

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

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

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

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

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

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

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

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

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

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

501
502
            from = inner;
            if (from != to) {
hjk's avatar
hjk committed
503
504
505
506
507
508
509
510
511
                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
512
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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

969
970
    if (isExitedReason(reason)) {
        qq->notifyInferiorExited();
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
971
        QString msg;
972
        if (reason == "exited") {
973
974
975
976
977
            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
978
        } else {
979
            msg = tr("Program exited normally");
980
981
        }
        q->showStatusMessage(msg);
982
        postCommand(_("-gdb-exit"), CB(handleExit));
983
984
985
986
        return;
    }


987
988
989
    //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
990
991
        m_waitingForFirstBreakpointToBeHit = false;

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

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

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