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

30 31
#define QT_NO_CAST_FROM_ASCII

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

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

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

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

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

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

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

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

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

Q_DECLARE_METATYPE(Debugger::Internal::GdbMi);

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

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

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

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

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

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

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

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

137
void GdbEngine::initializeConnections()
con's avatar
con committed
138 139
{
    // Gdb Process interaction
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
    connect(&m_gdbProc, SIGNAL(error(QProcess::ProcessError)),
        this, SLOT(gdbProcError(QProcess::ProcessError)));
    connect(&m_gdbProc, SIGNAL(readyReadStandardOutput()),
        this, SLOT(readGdbStandardOutput()));
    connect(&m_gdbProc, SIGNAL(readyReadStandardError()),
        this, SLOT(readGdbStandardError()));
    connect(&m_gdbProc, SIGNAL(finished(int, QProcess::ExitStatus)),
        q, SLOT(exitDebugger()));

    connect(&m_stubProc, SIGNAL(processError(QString)),
        this, SLOT(stubError(QString)));
    connect(&m_stubProc, SIGNAL(processStarted()),
        this, SLOT(stubStarted()));
    connect(&m_stubProc, SIGNAL(wrapperStopped()),
        q, SLOT(exitDebugger()));

    connect(&m_uploadProc, SIGNAL(error(QProcess::ProcessError)),
        this, SLOT(uploadProcError(QProcess::ProcessError)));
158 159 160 161
    connect(&m_uploadProc, SIGNAL(readyReadStandardOutput()),
        this, SLOT(readUploadStandardOutput()));
    connect(&m_uploadProc, SIGNAL(readyReadStandardError()),
        this, SLOT(readUploadStandardError()));
162

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

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

177
    // FIXME: These trigger even if the engine is not active
178 179 180 181 182 183
    connect(theDebuggerAction(UseDebuggingHelpers), SIGNAL(valueChanged(QVariant)),
        this, SLOT(setUseDebuggingHelpers(QVariant)));
    connect(theDebuggerAction(DebugDebuggingHelpers), SIGNAL(valueChanged(QVariant)),
        this, SLOT(setDebugDebuggingHelpers(QVariant)));
    connect(theDebuggerAction(RecheckDebuggingHelpers), SIGNAL(triggered()),
        this, SLOT(recheckDebuggingHelperAvailability()));
con's avatar
con committed
184 185
}

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

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

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

con's avatar
con committed
205 206 207
void GdbEngine::gdbProcError(QProcess::ProcessError error)
{
    QString msg;
208
    bool kill = true;
con's avatar
con committed
209 210
    switch (error) {
        case QProcess::FailedToStart:
211
            kill = false;
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
212
            msg = tr("The Gdb process failed to start. Either the "
con's avatar
con committed
213
                "invoked program '%1' is missing, or you may have insufficient "
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
214
                "permissions to invoke the program.")
hjk's avatar
hjk committed
215
                .arg(theDebuggerStringSetting(GdbLocation));
con's avatar
con committed
216 217
            break;
        case QProcess::Crashed:
218
            kill = false;
con's avatar
con committed
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
            msg = tr("The Gdb process crashed some time after starting "
                "successfully.");
            break;
        case QProcess::Timedout:
            msg = tr("The last waitFor...() function timed out. "
                "The state of QProcess is unchanged, and you can try calling "
                "waitFor...() again.");
            break;
        case QProcess::WriteError:
            msg = tr("An error occurred when attempting to write "
                "to the Gdb process. For example, the process may not be running, "
                "or it may have closed its input channel.");
            break;
        case QProcess::ReadError:
            msg = tr("An error occurred when attempting to read from "
                "the Gdb process. For example, the process may not be running.");
            break;
        default:
            msg = tr("An unknown error in the Gdb process occurred. "
                "This is the default return value of error().");
    }

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

248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
void GdbEngine::uploadProcError(QProcess::ProcessError error)
{
    QString msg;
    switch (error) {
        case QProcess::FailedToStart:
            msg = tr("The upload process failed to start. Either the "
                "invoked script '%1' is missing, or you may have insufficient "
                "permissions to invoke the program.")
                .arg(theDebuggerStringSetting(GdbLocation));
            break;
        case QProcess::Crashed:
            msg = tr("The upload process crashed some time after starting "
                "successfully.");
            break;
        case QProcess::Timedout:
            msg = tr("The last waitFor...() function timed out. "
                "The state of QProcess is unchanged, and you can try calling "
                "waitFor...() again.");
            break;
        case QProcess::WriteError:
            msg = tr("An error occurred when attempting to write "
                "to the upload process. For example, the process may not be running, "
                "or it may have closed its input channel.");
            break;
        case QProcess::ReadError:
            msg = tr("An error occurred when attempting to read from "
                "the upload process. For example, the process may not be running.");
            break;
        default:
            msg = tr("An unknown error in the upload process occurred. "
                "This is the default return value of error().");
    }

    q->showStatusMessage(msg);
    QMessageBox::critical(q->mainWindow(), tr("Error"), msg);
}

285 286 287 288 289 290 291 292 293 294 295 296
void GdbEngine::readUploadStandardOutput()
{
    QByteArray ba = m_uploadProc.readAllStandardOutput();
    gdbOutputAvailable(_("upload-out:"), QString::fromLocal8Bit(ba, ba.length()));
}

void GdbEngine::readUploadStandardError()
{
    QByteArray ba = m_uploadProc.readAllStandardError();
    gdbOutputAvailable(_("upload-err:"), QString::fromLocal8Bit(ba, ba.length()));
}

con's avatar
con committed
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
#if 0
static void dump(const char *first, const char *middle, const QString & to)
{
    QByteArray ba(first, middle - first);
    Q_UNUSED(to);
    // note that qDebug cuts off output after a certain size... (bug?)
    qDebug("\n>>>>> %s\n%s\n====\n%s\n<<<<<\n",
        qPrintable(currentTime()),
        qPrintable(QString(ba).trimmed()),
        qPrintable(to.trimmed()));
    //qDebug() << "";
    //qDebug() << qPrintable(currentTime())
    //    << " Reading response:  " << QString(ba).trimmed() << "\n";
}
#endif

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

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

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

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
328
    emit gdbOutputAvailable(_("            "), currentTime());
329
    emit gdbOutputAvailable(_("stdout:"), 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()
{
hjk's avatar
hjk committed
599
    qq->notifyInferiorStopRequested();
600

hjk's avatar
hjk committed
601
    if (m_gdbProc.state() == QProcess::NotRunning) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
602
        debugMessage(_("TRYING TO INTERRUPT INFERIOR WITHOUT RUNNING GDB"));
hjk's avatar
hjk committed
603
        qq->notifyInferiorExited();
con's avatar
con committed
604
        return;
hjk's avatar
hjk committed
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
hjk's avatar
hjk committed
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());
hjk's avatar
hjk committed
674
        q->showStatusMessage(tr("Stopping temporarily."));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
675
        debugMessage(_("QUEUING COMMAND ") + cmd.command);
hjk's avatar
hjk committed
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 694 695 696
        cmd.command = cmd.command.arg(currentToken());

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

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 718 719
    qDebug() << "# handleOutput,"
        << "cmd type:" << cmd.type
        << " 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 730 731
        --m_pendingRequests;
        PENDING_DEBUG("   TYPE " << cmd.type << " DECREMENTS PENDING TO: "
            << m_pendingRequests << cmd.command);
732 733
        if (m_pendingRequests <= 0) {
            PENDING_DEBUG(" ....  AND TRIGGERS MODEL UPDATE");
con's avatar
con committed
734
            updateWatchModel2();
735
        }
con's avatar
con committed
736 737 738 739
    } else {
        PENDING_DEBUG("   UNKNOWN TYPE " << cmd.type << " LEAVES PENDING AT: "
            << 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");
hjk's avatar