gdbengine.cpp 144 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 8
**
** Contact:  Qt Software Information (qt-info@nokia.com)
**
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 26
** If you are unsure which license is appropriate for your use, please
** contact the sales department at qt-sales@nokia.com.
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 75 76 77 78

#if defined(Q_OS_LINUX) || defined(Q_OS_MAC)
#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 208 209
void GdbEngine::gdbProcError(QProcess::ProcessError error)
{
    QString msg;
    switch (error) {
        case QProcess::FailedToStart:
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 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
            break;
        case QProcess::Crashed:
            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().");
    }

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

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
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);
}

281 282 283 284 285 286 287 288 289 290 291 292
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
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
#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

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

hjk's avatar
hjk committed
315 316
void GdbEngine::debugMessage(const QString &msg)
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
317
    emit gdbOutputAvailable(_("debug:"), msg);
hjk's avatar
hjk committed
318 319
}

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

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
324
    emit gdbOutputAvailable(_("            "), currentTime());
325
    emit gdbOutputAvailable(_("stdout:"), QString::fromLocal8Bit(buff, buff.length()));
con's avatar
con committed
326 327 328 329 330

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

    lastTime = QTime::currentTime();

340 341
    if (buff.isEmpty() || buff == "(gdb) ")
        return;
con's avatar
con committed
342

343 344 345
    const char *from = buff.constData();
    const char *to = from + buff.size();
    const char *inner;
con's avatar
con committed
346

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

358 359 360 361 362 363 364 365 366 367 368 369 370 371 372
    // 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
373

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

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

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

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

474 475
        case '^': {
            GdbResultRecord record;
con's avatar
con committed
476

477
            record.token = token;
con's avatar
con committed
478

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

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

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

            //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
535 536 537 538
        }
    }
}

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

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

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

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

void GdbEngine::readGdbStandardOutput()
{
565 566
    int newstart = 0;
    int scan = m_inbuffer.size();
con's avatar
con committed
567

568
    m_inbuffer.append(m_gdbProc.readAllStandardOutput());
con's avatar
con committed
569

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

void GdbEngine::interruptInferior()
{
hjk's avatar
hjk committed
595
    qq->notifyInferiorStopRequested();
596

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

603
    if (q->startMode() == StartRemote) {
604
        postCommand(_("-exec-interrupt"));
605 606 607
        return;
    }

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

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

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

Roberto Raggi's avatar
Roberto Raggi committed
629
    qq->notifyInferiorPidChanged(pid);
630 631
    if (m_dumperInjectionLoad)
        tryLoadDebuggingHelpers();
con's avatar
con committed
632 633
}

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

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

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

658
    GdbCommand cmd;
con's avatar
con committed
659
    cmd.command = command;
660 661 662
    cmd.flags = flags;
    cmd.callback = callback;
    cmd.callbackName = callbackName;
con's avatar
con committed
663 664
    cmd.cookie = cookie;

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

679 680 681 682 683
void GdbEngine::flushCommand(GdbCommand &cmd)
{
    ++currentToken();
    m_cookieForToken[currentToken()] = cmd;
    cmd.command = QString::number(currentToken()) + cmd.command;
684
    if (cmd.flags & EmbedToken)
685 686 687 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);
    emit gdbInputAvailable(QString(), cmd.command);
}

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

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

704
    GdbCommand cmd = m_cookieForToken.take(token);
con's avatar
con committed
705

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

#if 0
713 714 715
    qDebug() << "# handleOutput,"
        << "cmd type:" << cmd.type
        << " cmd synchronized:" << cmd.synchronized
con's avatar
con committed
716 717 718 719 720
        << "\n record: " << record.toString();
#endif

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

721 722
    if (cmd.callback)
        (this->*(cmd.callback))(record, cmd.cookie);
con's avatar
con committed
723

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

    // 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
746 747 748 749 750
}

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

755
    m_gdbProc.write(command.toLocal8Bit() + "\r\n");
con's avatar
con committed
756 757
}

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

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

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

826
void GdbEngine::handleInfoProc(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
827 828 829 830
{
    if (record.resultClass == GdbResultDone) {
        #if defined(Q_OS_MAC)
        //^done,process-id="85075"
Oswald Buddenhagen's avatar
nicer  
Oswald Buddenhagen committed
831
        maybeHandleInferiorPidChanged(_(record.data.findChild("process-id").data()));
con's avatar
con committed
832 833 834 835
        #endif

        #if defined(Q_OS_LINUX) || defined(Q_OS_WIN)
        // FIXME: use something more robust
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
836 837
        QRegExp re(__("process (\\d+)"));
        QString data = __(record.data.findChild("consolestreamoutput").data());
con's avatar
con committed
838 839 840 841 842 843
        if (re.indexIn(data) != -1)
            maybeHandleInferiorPidChanged(re.cap(1));
        #endif
    }
}

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

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

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

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

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

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

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

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 993 994 995
        // If the executable dies already that early we might get something
        // like stdout:49*stopped,reason="exited",exit-code="0177"
        // This is handled now above.

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