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
202
}

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

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

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

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

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

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

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

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

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

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

    lastTime = QTime::currentTime();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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