Files
qt-creator/src/plugins/debugger/gdb/gdbengine.cpp
Orgad Shaneh a33bf92bd2 GDB: Set OsAbi also when the executable is ELF and the toolchain is not
If the toolchain is misconfigured, it's still possible to save GDB from
crashing by reading the binary format from the executable.

Change-Id: I8f8db163e1dd6aef31fed23b1306e714e563646a
Reviewed-by: hjk <hjk@qt.io>
2017-06-21 09:13:21 +00:00

4451 lines
162 KiB
C++

/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "gdbengine.h"
#include "attachgdbadapter.h"
#include "coregdbadapter.h"
#include "gdbplainengine.h"
#include "termgdbadapter.h"
#include "remotegdbserveradapter.h"
#include <debugger/debuggerstartparameters.h>
#include <debugger/debuggerinternalconstants.h>
#include <debugger/debuggerruncontrol.h>
#include <debugger/disassemblerlines.h>
#include <debugger/debuggeractions.h>
#include <debugger/debuggercore.h>
#include <debugger/debuggermainwindow.h>
#include <debugger/debuggerplugin.h>
#include <debugger/debuggerprotocol.h>
#include <debugger/debuggertooltipmanager.h>
#include <debugger/disassembleragent.h>
#include <debugger/memoryagent.h>
#include <debugger/sourceutils.h>
#include <debugger/terminal.h>
#include <debugger/breakhandler.h>
#include <debugger/moduleshandler.h>
#include <debugger/registerhandler.h>
#include <debugger/sourcefileshandler.h>
#include <debugger/stackhandler.h>
#include <debugger/threadshandler.h>
#include <debugger/debuggersourcepathmappingwidget.h>
#include <debugger/logwindow.h>
#include <debugger/procinterrupt.h>
#include <debugger/shared/hostutils.h>
#include <coreplugin/icore.h>
#include <coreplugin/messagebox.h>
#include <projectexplorer/devicesupport/deviceprocess.h>
#include <projectexplorer/itaskhandler.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/taskhub.h>
#include <utils/algorithm.h>
#include <utils/hostosinfo.h>
#include <utils/macroexpander.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
#include <utils/savedaction.h>
#include <utils/temporaryfile.h>
#include <QBuffer>
#include <QDirIterator>
#include <QMessageBox>
#include <QProcess>
#include <QPushButton>
#include <QJsonArray>
using namespace Core;
using namespace ProjectExplorer;
using namespace Utils;
namespace Debugger {
namespace Internal {
enum { debugPending = 0 };
#define PENDING_DEBUG(s) do { if (debugPending) qDebug() << s; } while (0)
#define CB(callback) [this](const DebuggerResponse &r) { callback(r); }
#define CHECK_STATE(s) do { checkState(s, __FILE__, __LINE__); } while (0)
QString GdbEngine::tooltipIName(const QString &exp)
{
return "tooltip." + toHex(exp);
}
static bool stateAcceptsGdbCommands(DebuggerState state)
{
switch (state) {
case EngineSetupRequested:
case EngineSetupOk:
case EngineSetupFailed:
case InferiorUnrunnable:
case InferiorSetupRequested:
case InferiorSetupFailed:
case EngineRunRequested:
case InferiorRunRequested:
case InferiorRunOk:
case InferiorStopRequested:
case InferiorStopOk:
case InferiorShutdownRequested:
case EngineShutdownRequested:
case InferiorShutdownOk:
case InferiorShutdownFailed:
return true;
case DebuggerNotReady:
case InferiorStopFailed:
case InferiorSetupOk:
case EngineRunFailed:
case InferiorRunFailed:
case EngineShutdownOk:
case EngineShutdownFailed:
case DebuggerFinished:
return false;
}
return false;
}
static int &currentToken()
{
static int token = 0;
return token;
}
static bool isMostlyHarmlessMessage(const QStringRef &msg)
{
return msg == "warning: GDB: Failed to set controlling terminal: "
"Inappropriate ioctl for device\\n"
|| msg == "warning: GDB: Failed to set controlling terminal: "
"Invalid argument\\n";
}
static QString mainFunction(const DebuggerRunParameters &rp)
{
return QLatin1String(rp.toolChainAbi.os() == Abi::WindowsOS && !rp.useTerminal ? "qMain" : "main");
}
///////////////////////////////////////////////////////////////////////
//
// Debuginfo Taskhandler
//
///////////////////////////////////////////////////////////////////////
class DebugInfoTask
{
public:
QString command;
};
class DebugInfoTaskHandler : public ITaskHandler
{
public:
explicit DebugInfoTaskHandler(GdbEngine *engine)
: m_engine(engine)
{}
bool canHandle(const Task &task) const override
{
return m_debugInfoTasks.contains(task.taskId);
}
void handle(const Task &task) override
{
m_engine->requestDebugInformation(m_debugInfoTasks.value(task.taskId));
}
void addTask(unsigned id, const DebugInfoTask &task)
{
m_debugInfoTasks[id] = task;
}
QAction *createAction(QObject *parent) const override
{
QAction *action = new QAction(DebuggerPlugin::tr("Install &Debug Information"), parent);
action->setToolTip(DebuggerPlugin::tr("Tries to install missing debug information."));
return action;
}
private:
GdbEngine *m_engine;
QHash<unsigned, DebugInfoTask> m_debugInfoTasks;
};
///////////////////////////////////////////////////////////////////////
//
// GdbEngine
//
///////////////////////////////////////////////////////////////////////
GdbEngine::GdbEngine(bool useTerminal)
{
setObjectName("GdbEngine");
m_busy = false;
m_gdbVersion = 100;
m_pythonVersion = 0;
m_isQnxGdb = false;
m_registerNamesListed = false;
m_sourcesListUpdating = false;
m_oldestAcceptableToken = -1;
m_nonDiscardableCount = 0;
m_gdbOutputCodec = QTextCodec::codecForLocale();
m_inferiorOutputCodec = QTextCodec::codecForLocale();
m_pendingBreakpointRequests = 0;
m_commandsDoneCallback = 0;
m_stackNeeded = false;
m_terminalTrap = useTerminal;
m_systemDumpersLoaded = false;
m_rerunPending = false;
m_inUpdateLocals = false;
m_debugInfoTaskHandler = new DebugInfoTaskHandler(this);
//ExtensionSystem::PluginManager::addObject(m_debugInfoTaskHandler);
m_commandTimer.setSingleShot(true);
connect(&m_commandTimer, &QTimer::timeout,
this, &GdbEngine::commandTimeout);
connect(action(AutoDerefPointers), &SavedAction::valueChanged,
this, &GdbEngine::reloadLocals);
connect(action(CreateFullBacktrace), &QAction::triggered,
this, &GdbEngine::createFullBacktrace);
connect(action(UseDebuggingHelpers), &SavedAction::valueChanged,
this, &GdbEngine::reloadLocals);
connect(action(UseDynamicType), &SavedAction::valueChanged,
this, &GdbEngine::reloadLocals);
}
GdbEngine::~GdbEngine()
{
//ExtensionSystem::PluginManager::removeObject(m_debugInfoTaskHandler);
delete m_debugInfoTaskHandler;
m_debugInfoTaskHandler = 0;
// Prevent sending error messages afterwards.
disconnect();
}
DebuggerStartMode GdbEngine::startMode() const
{
return runParameters().startMode;
}
QString GdbEngine::failedToStartMessage()
{
return tr("The gdb process failed to start.");
}
QString GdbEngine::errorMessage(QProcess::ProcessError error)
{
switch (error) {
case QProcess::FailedToStart:
return failedToStartMessage() + ' ' + tr("Either the "
"invoked program \"%1\" is missing, or you may have insufficient "
"permissions to invoke the program.\n%2")
.arg(runParameters().debugger.executable, m_gdbProc.errorString());
case QProcess::Crashed:
if (isDying())
return tr("The gdb process crashed some time after starting "
"successfully.");
else
return tr("The gdb process was ended forcefully");
case QProcess::Timedout:
return tr("The last waitFor...() function timed out. "
"The state of QProcess is unchanged, and you can try calling "
"waitFor...() again.");
case QProcess::WriteError:
return 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.");
case QProcess::ReadError:
return tr("An error occurred when attempting to read from "
"the gdb process. For example, the process may not be running.");
default:
return tr("An unknown error in the gdb process occurred.");
}
}
#if 0
static void dump(const char *first, const char *middle, const QString & to)
{
QString 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
// Parse "~:gdb: unknown target exception 0xc0000139 at 0x77bef04e\n"
// and return an exception message
static inline QString msgWinException(const QString &data, unsigned *exCodeIn = 0)
{
if (exCodeIn)
*exCodeIn = 0;
const int exCodePos = data.indexOf("0x");
const int blankPos = exCodePos != -1 ? data.indexOf(' ', exCodePos + 1) : -1;
const int addressPos = blankPos != -1 ? data.indexOf("0x", blankPos + 1) : -1;
if (addressPos < 0)
return GdbEngine::tr("An exception was triggered.");
const unsigned exCode = data.mid(exCodePos, blankPos - exCodePos).toUInt(0, 0);
if (exCodeIn)
*exCodeIn = exCode;
const quint64 address = data.mid(addressPos).trimmed().toULongLong(0, 0);
QString rc;
QTextStream str(&rc);
str << GdbEngine::tr("An exception was triggered:") << ' ';
formatWindowsException(exCode, address, 0, 0, 0, str);
str << '.';
return rc;
}
static bool isNameChar(int c)
{
// could be 'stopped' or 'shlibs-added'
return (c >= 'a' && c <= 'z') || c == '-';
}
static bool contains(const QString &message, const QString &pattern, int size)
{
const int s = message.size();
if (s < size)
return false;
const int pos = message.indexOf(pattern);
if (pos == -1)
return false;
const bool beginFits = pos == 0 || message.at(pos - 1) == '\n';
const bool endFits = pos + size == s || message.at(pos + size) == '\n';
return beginFits && endFits;
}
static bool isGdbConnectionError(const QString &message)
{
// Handle messages gdb client produces when the target exits (gdbserver)
//
// we get this as response either to a specific command, e.g.
// 31^error,msg="Remote connection closed"
// or as informative output:
// &Remote connection closed
const char msg1[] = "Remote connection closed";
const char msg2[] = "Remote communication error. Target disconnected.: No error.";
const char msg3[] = "Quit";
return contains(message, msg1, sizeof(msg1) - 1)
|| contains(message, msg2, sizeof(msg2) - 1)
|| contains(message, msg3, sizeof(msg3) - 1);
}
void GdbEngine::handleResponse(const QString &buff)
{
showMessage(buff, LogOutput);
if (buff.isEmpty() || buff == "(gdb) ")
return;
const QChar *from = buff.constData();
const QChar *to = from + buff.size();
const QChar *inner;
int token = -1;
// Token is a sequence of numbers.
for (inner = from; inner != to; ++inner)
if (*inner < '0' || *inner > '9')
break;
if (from != inner) {
token = QString(from, inner - from).toInt();
from = inner;
}
// Next char decides kind of response.
const QChar c = *from++;
switch (c.unicode()) {
case '*':
case '+':
case '=': {
QString asyncClass;
for (; from != to; ++from) {
const QChar c = *from;
if (!isNameChar(c.unicode()))
break;
asyncClass += *from;
}
GdbMi result;
while (from != to) {
GdbMi data;
if (*from != ',') {
// happens on archer where we get
// 23^running <NL> *running,thread-id="all" <NL> (gdb)
result.m_type = GdbMi::Tuple;
break;
}
++from; // skip ','
data.parseResultOrValue(from, to);
if (data.isValid()) {
//qDebug() << "parsed result:" << data.toString();
result.m_children.push_back(data);
result.m_type = GdbMi::Tuple;
}
}
handleAsyncOutput(asyncClass, result);
break;
}
case '~': {
QString data = GdbMi::parseCString(from, to);
if (data.startsWith("bridgemessage={")) {
// It's already logged.
break;
}
if (data.startsWith("interpreterresult={")) {
GdbMi allData;
allData.fromStringMultiple(data);
DebuggerResponse response;
response.resultClass = ResultDone;
response.data = allData["interpreterresult"];
response.token = allData["token"].toInt();
handleResultRecord(&response);
break;
}
if (data.startsWith("interpreterasync={")) {
GdbMi allData;
allData.fromStringMultiple(data);
QString asyncClass = allData["asyncclass"].data();
if (asyncClass == "breakpointmodified")
handleInterpreterBreakpointModified(allData["interpreterasync"]);
break;
}
m_pendingConsoleStreamOutput += data;
// Show some messages to give the impression something happens.
if (data.startsWith("Reading symbols from ")) {
showStatusMessage(tr("Reading %1...").arg(data.mid(21)), 1000);
progressPing();
} else if (data.startsWith("[New ") || data.startsWith("[Thread ")) {
if (data.endsWith('\n'))
data.chop(1);
progressPing();
showStatusMessage(data, 1000);
} else if (data.startsWith("gdb: unknown target exception 0x")) {
// [Windows, most likely some DLL/Entry point not found]:
// "gdb: unknown target exception 0xc0000139 at 0x77bef04e"
// This may be fatal and cause the target to exit later
unsigned exCode;
m_lastWinException = msgWinException(data, &exCode);
showMessage(m_lastWinException, LogMisc);
const Task::TaskType type = isFatalWinException(exCode) ? Task::Error : Task::Warning;
TaskHub::addTask(type, m_lastWinException, Constants::TASK_CATEGORY_DEBUGGER_RUNTIME);
}
break;
}
case '@': {
QString data = GdbMi::parseCString(from, to);
QString msg = data.mid(2, data.size() - 4);
showMessage(msg, AppOutput);
break;
}
case '&': {
QString data = GdbMi::parseCString(from, to);
// On Windows, the contents seem to depend on the debugger
// version and/or OS version used.
if (data.startsWith("warning:"))
showMessage(data.mid(9), AppStuff); // Cut "warning: "
m_pendingLogStreamOutput += data;
if (isGdbConnectionError(data)) {
notifyInferiorExited();
break;
}
if (boolSetting(IdentifyDebugInfoPackages)) {
// From SuSE's gdb: >&"Missing separate debuginfo for ...\n"
// ">&"Try: zypper install -C \"debuginfo(build-id)=c084ee5876ed1ac12730181c9f07c3e027d8e943\"\n"
if (data.startsWith("Missing separate debuginfo for ")) {
m_lastMissingDebugInfo = data.mid(32);
} else if (data.startsWith("Try: zypper")) {
QString cmd = data.mid(4);
Task task(Task::Warning,
tr("Missing debug information for %1\nTry: %2")
.arg(m_lastMissingDebugInfo).arg(cmd),
FileName(), 0, Debugger::Constants::TASK_CATEGORY_DEBUGGER_DEBUGINFO);
TaskHub::addTask(task);
DebugInfoTask dit;
dit.command = cmd;
m_debugInfoTaskHandler->addTask(task.taskId, dit);
}
}
break;
}
case '^': {
DebuggerResponse response;
response.token = token;
for (inner = from; inner != to; ++inner)
if (*inner < 'a' || *inner > 'z')
break;
QString resultClass = QString::fromRawData(from, inner - from);
if (resultClass == "done")
response.resultClass = ResultDone;
else if (resultClass == "running")
response.resultClass = ResultRunning;
else if (resultClass == "connected")
response.resultClass = ResultConnected;
else if (resultClass == "error")
response.resultClass = ResultError;
else if (resultClass == "exit")
response.resultClass = ResultExit;
else
response.resultClass = ResultUnknown;
from = inner;
if (from != to) {
if (*from == ',') {
++from;
response.data.parseTuple_helper(from, to);
response.data.m_type = GdbMi::Tuple;
response.data.m_name = "data";
} else {
// Archer has this.
response.data.m_type = GdbMi::Tuple;
response.data.m_name = "data";
}
}
response.logStreamOutput = m_pendingLogStreamOutput;
response.consoleStreamOutput = m_pendingConsoleStreamOutput;
m_pendingLogStreamOutput.clear();
m_pendingConsoleStreamOutput.clear();
if (response.data.data().isEmpty())
response.data.fromString(response.consoleStreamOutput);
handleResultRecord(&response);
break;
}
default: {
qDebug() << "UNKNOWN RESPONSE TYPE '" << c << "'. REST: " << from;
break;
}
}
}
void GdbEngine::handleAsyncOutput(const QString &asyncClass, const GdbMi &result)
{
if (asyncClass == "stopped") {
if (m_inUpdateLocals) {
showMessage("UNEXPECTED *stopped NOTIFICATION IGNORED", LogWarning);
} else {
handleStopResponse(result);
m_pendingLogStreamOutput.clear();
m_pendingConsoleStreamOutput.clear();
}
} else if (asyncClass == "running") {
if (m_inUpdateLocals) {
showMessage("UNEXPECTED *running NOTIFICATION IGNORED", LogWarning);
} else {
GdbMi threads = result["thread-id"];
threadsHandler()->notifyRunning(threads.data());
if (runParameters().toolChainAbi.os() == Abi::WindowsOS) {
// NOTE: Each created thread spits out a *running message. We completely ignore them
// on Windows, and handle only numbered responses
// FIXME: Breakpoints on Windows are exceptions which are thrown in newly
// created threads so we have to filter out the running threads messages when
// we request a stop.
} else if (state() == InferiorRunOk || state() == InferiorSetupRequested) {
// We get multiple *running after thread creation and in Windows terminals.
showMessage(QString("NOTE: INFERIOR STILL RUNNING IN STATE %1.").
arg(DebuggerEngine::stateName(state())));
} else {
notifyInferiorRunOk();
}
}
} 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",
// symbols-loaded="0"
// id="/lib/i386-linux-gnu/libc.so.6"
// target-name="/lib/i386-linux-gnu/libc.so.6"
// host-name="/lib/i386-linux-gnu/libc.so.6"
// symbols-loaded="0",thread-group="i1"
QString id = result["id"].data();
if (!id.isEmpty())
showStatusMessage(tr("Library %1 loaded").arg(id), 1000);
progressPing();
Module module;
module.startAddress = 0;
module.endAddress = 0;
module.hostPath = result["host-name"].data();
module.modulePath = result["target-name"].data();
module.moduleName = QFileInfo(module.hostPath).baseName();
modulesHandler()->updateModule(module);
} 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"
QString id = result["id"].data();
progressPing();
showStatusMessage(tr("Library %1 unloaded").arg(id), 1000);
} else if (asyncClass == "thread-group-added") {
// 7.1-symbianelf has "{id="i1"}"
} else if (asyncClass == "thread-group-started") {
// Archer had only "{id="28902"}" at some point of 6.8.x.
// *-started seems to be standard in 7.1, but in early
// 7.0.x, there was a *-created instead.
progressPing();
// 7.1.50 has thread-group-started,id="i1",pid="3529"
QString id = result["id"].data();
showStatusMessage(tr("Thread group %1 created").arg(id), 1000);
notifyInferiorPid(result["pid"].toProcessHandle());
handleThreadGroupCreated(result);
} else if (asyncClass == "thread-created") {
//"{id="1",group-id="28902"}"
QString id = result["id"].data();
showStatusMessage(tr("Thread %1 created").arg(id), 1000);
ThreadData thread;
thread.id = ThreadId(id.toLong());
thread.groupId = result["group-id"].data();
threadsHandler()->updateThread(thread);
} else if (asyncClass == "thread-group-exited") {
// Archer has "{id="28902"}"
QString id = result["id"].data();
showStatusMessage(tr("Thread group %1 exited").arg(id), 1000);
handleThreadGroupExited(result);
} else if (asyncClass == "thread-exited") {
//"{id="1",group-id="28902"}"
QString id = result["id"].data();
QString groupid = result["group-id"].data();
showStatusMessage(tr("Thread %1 in group %2 exited")
.arg(id).arg(groupid), 1000);
threadsHandler()->removeThread(ThreadId(id.toLong()));
} else if (asyncClass == "thread-selected") {
QString id = result["id"].data();
showStatusMessage(tr("Thread %1 selected").arg(id), 1000);
//"{id="2"}"
} else if (asyncClass == "breakpoint-modified") {
// New in FSF gdb since 2011-04-27.
// "{bkpt={number="3",type="breakpoint",disp="keep",
// enabled="y",addr="<MULTIPLE>",times="1",
// original-location="\\",simple_gdbtest_app.cpp\\":135"},
// {number="3.1",enabled="y",addr="0x0805ff68",
// func="Vector<int>::Vector(int)",
// file="simple_gdbtest_app.cpp",
// fullname="/data/...line="135"},{number="3.2"...}}.."
// Note the leading comma in original-location. Filter it out.
// We don't need the field anyway.
QString ba = result.toString();
ba = '[' + ba.mid(6, ba.size() - 7) + ']';
const int pos1 = ba.indexOf(",original-location");
const int pos2 = ba.indexOf("\":", pos1 + 2);
const int pos3 = ba.indexOf('"', pos2 + 2);
ba.remove(pos1, pos3 - pos1 + 1);
GdbMi res;
res.fromString(ba);
BreakHandler *handler = breakHandler();
Breakpoint bp;
BreakpointResponse br;
foreach (const GdbMi &bkpt, res.children()) {
const QString nr = bkpt["number"].data();
BreakpointResponseId rid(nr);
if (nr.contains('.')) {
// A sub-breakpoint.
BreakpointResponse sub;
updateResponse(sub, bkpt);
sub.id = rid;
sub.type = br.type;
bp.insertSubBreakpoint(sub);
} else {
// A primary breakpoint.
bp = handler->findBreakpointByResponseId(rid);
br = bp.response();
updateResponse(br, bkpt);
bp.setResponse(br);
}
}
} else if (asyncClass == "breakpoint-created") {
// "{bkpt={number="1",type="breakpoint",disp="del",enabled="y",
// addr="<PENDING>",pending="main",times="0",
// original-location="main"}}" -- or --
// {bkpt={number="2",type="hw watchpoint",disp="keep",enabled="y",
// what="*0xbfffed48",times="0",original-location="*0xbfffed48"}}
BreakHandler *handler = breakHandler();
foreach (const GdbMi &bkpt, result.children()) {
BreakpointResponse br;
br.type = BreakpointByFileAndLine;
updateResponse(br, bkpt);
handler->handleAlienBreakpoint(br, this);
}
} else if (asyncClass == "breakpoint-deleted") {
// "breakpoint-deleted" "{id="1"}"
// New in FSF gdb since 2011-04-27.
QString nr = result["id"].data();
BreakpointResponseId rid(nr);
if (Breakpoint bp = breakHandler()->findBreakpointByResponseId(rid)) {
// This also triggers when a temporary breakpoint is hit.
// We do not really want that, as this loses all information.
// FIXME: Use a special marker for this case?
// if (!bp.isOneShot()) ... is not sufficient.
// It keeps temporary "Jump" breakpoints alive.
bp.removeAlienBreakpoint();
}
} else if (asyncClass == "cmd-param-changed") {
// New since 2012-08-09
// "{param="debug remote",value="1"}"
} else if (asyncClass == "memory-changed") {
// New since 2013
// "{thread-group="i1",addr="0x0918a7a8",len="0x10"}"
} else if (asyncClass == "tsv-created") {
// New since 2013-02-06
} else if (asyncClass == "tsv-modified") {
// New since 2013-02-06
} else {
qDebug() << "IGNORED ASYNC OUTPUT"
<< asyncClass << result.toString();
}
}
void GdbEngine::readGdbStandardError()
{
QString err = QString::fromUtf8(m_gdbProc.readAllStandardError());
showMessage("UNEXPECTED GDB STDERR: " + err);
if (err == "Undefined command: \"bb\". Try \"help\".\n")
return;
if (err.startsWith("BFD: reopening"))
return;
qWarning() << "Unexpected GDB stderr:" << err;
}
void GdbEngine::readDebuggeeOutput(const QByteArray &ba)
{
const QString msg = m_inferiorOutputCodec->toUnicode(ba.constData(), ba.size(),
&m_inferiorOutputCodecState);
if (msg.startsWith("&\"") && isMostlyHarmlessMessage(msg.midRef(2, msg.size() - 4)))
showMessage("Mostly harmless terminal warning suppressed.", LogWarning);
else
showMessage(msg, AppStuff);
}
void GdbEngine::readGdbStandardOutput()
{
m_commandTimer.start(); // Restart timer.
int newstart = 0;
int scan = m_inbuffer.size();
QByteArray out = m_gdbProc.readAllStandardOutput();
m_inbuffer.append(out);
// This can trigger when a dialog starts a nested event loop.
if (m_busy)
return;
while (newstart < m_inbuffer.size()) {
int start = newstart;
int end = m_inbuffer.indexOf('\n', scan);
if (end < 0) {
m_inbuffer.remove(0, start);
return;
}
newstart = end + 1;
scan = newstart;
if (end == start)
continue;
if (m_inbuffer.at(end - 1) == '\r') {
--end;
if (end == start)
continue;
}
m_busy = true;
QString msg = m_gdbOutputCodec->toUnicode(m_inbuffer.constData() + start, end - start,
&m_gdbOutputCodecState);
handleResponse(msg);
m_busy = false;
}
m_inbuffer.clear();
}
void GdbEngine::interruptInferior()
{
CHECK_STATE(InferiorStopRequested);
if (terminal()->sendInterrupt())
return;
if (usesExecInterrupt()) {
runCommand({"-exec-interrupt"});
} else {
showStatusMessage(tr("Stop requested..."), 5000);
showMessage("TRYING TO INTERRUPT INFERIOR");
if (HostOsInfo::isWindowsHost() && !m_isQnxGdb) {
QTC_ASSERT(state() == InferiorStopRequested, qDebug() << state(); notifyInferiorStopFailed());
QTC_ASSERT(!m_signalOperation, notifyInferiorStopFailed());
m_signalOperation = runTool()->device()->signalOperation();
QTC_ASSERT(m_signalOperation, notifyInferiorStopFailed());
connect(m_signalOperation.data(), &DeviceProcessSignalOperation::finished,
this, &GdbEngine::handleInterruptDeviceInferior);
m_signalOperation->setDebuggerCommand(runParameters().debugger.executable);
m_signalOperation->interruptProcess(inferiorPid());
} else {
interruptInferior2();
}
}
}
void GdbEngine::handleInterruptDeviceInferior(const QString &error)
{
if (error.isEmpty()) {
showMessage("Interrupted " + QString::number(inferiorPid()));
notifyInferiorStopOk();
} else {
showMessage(error, LogError);
notifyInferiorStopFailed();
}
m_signalOperation->disconnect(this);
m_signalOperation.clear();
}
void GdbEngine::runCommand(const DebuggerCommand &command)
{
const int token = ++currentToken();
DebuggerCommand cmd = command;
if (cmd.function.isEmpty()) {
showMessage(QString("EMPTY FUNCTION RUN, TOKEN: %1 ARGS: %2")
.arg(token).arg(cmd.args.toString()));
QTC_ASSERT(false, return);
}
if (!stateAcceptsGdbCommands(state())) {
showMessage(QString("NO GDB PROCESS RUNNING, CMD IGNORED: %1 %2")
.arg(cmd.function).arg(state()));
return;
}
if (cmd.flags & RebuildBreakpointModel) {
++m_pendingBreakpointRequests;
PENDING_DEBUG(" BREAKPOINT MODEL:" << cmd.function
<< "INCREMENTS PENDING TO" << m_pendingBreakpointRequests);
} else {
PENDING_DEBUG(" OTHER (IN):" << cmd.function
<< "LEAVES PENDING BREAKPOINT AT" << m_pendingBreakpointRequests);
}
if (cmd.flags & (NeedsTemporaryStop|NeedsFullStop)) {
showMessage("RUNNING NEEDS-STOP COMMAND " + cmd.function);
const bool wantContinue = bool(cmd.flags & NeedsTemporaryStop);
cmd.flags &= ~(NeedsTemporaryStop|NeedsFullStop);
if (state() == InferiorStopRequested) {
if (cmd.flags & LosesChild) {
notifyInferiorIll();
return;
}
showMessage("CHILD ALREADY BEING INTERRUPTED. STILL HOPING.");
// Calling shutdown() here breaks all situations where two
// NeedsStop commands are issued in quick succession.
m_onStop.append(cmd, wantContinue);
return;
}
if (state() == InferiorRunOk) {
showStatusMessage(tr("Stopping temporarily"), 1000);
m_onStop.append(cmd, wantContinue);
requestInterruptInferior();
return;
}
showMessage("UNSAFE STATE FOR QUEUED COMMAND. EXECUTING IMMEDIATELY");
}
if (!(cmd.flags & Discardable))
++m_nonDiscardableCount;
bool isPythonCommand = true;
if ((cmd.flags & NativeCommand) || cmd.function.contains('-') || cmd.function.contains(' '))
isPythonCommand = false;
if (isPythonCommand) {
cmd.arg("token", token);
cmd.function = "python theDumper." + cmd.function + "(" + cmd.argsToPython() + ")";
}
QTC_ASSERT(m_gdbProc.state() == QProcess::Running, return);
cmd.postTime = QTime::currentTime().msecsSinceStartOfDay();
m_commandForToken[token] = cmd;
m_flagsForToken[token] = cmd.flags;
if (cmd.flags & ConsoleCommand)
cmd.function = "-interpreter-exec console \"" + cmd.function + '"';
cmd.function = QString::number(token) + cmd.function;
showMessage(cmd.function, LogInput);
if (m_scheduledTestResponses.contains(token)) {
// Fake response for test cases.
QString buffer = m_scheduledTestResponses.value(token);
buffer.replace("@TOKEN@", QString::number(token));
m_scheduledTestResponses.remove(token);
showMessage(QString("FAKING TEST RESPONSE (TOKEN: %2, RESPONSE: %3)")
.arg(token).arg(buffer));
QMetaObject::invokeMethod(this, "handleResponse",
Q_ARG(QString, buffer));
} else {
m_gdbProc.write(cmd.function.toUtf8() + "\r\n");
if (command.flags & NeedsFlush)
m_gdbProc.write("p 0\r\n");
// Start Watchdog.
if (m_commandTimer.interval() <= 20000)
m_commandTimer.setInterval(commandTimeoutTime());
// The process can die for external reason between the "-gdb-exit" was
// sent and a response could be retrieved. We don't want the watchdog
// to bark in that case since the only possible outcome is a dead
// process anyway.
if (!cmd.function.endsWith("-gdb-exit"))
m_commandTimer.start();
//if (cmd.flags & LosesChild)
// notifyInferiorIll();
}
}
int GdbEngine::commandTimeoutTime() const
{
int time = action(GdbWatchdogTimeout)->value().toInt();
return 1000 * qMax(40, time);
}
void GdbEngine::commandTimeout()
{
QList<int> keys = m_commandForToken.keys();
Utils::sort(keys);
bool killIt = false;
foreach (int key, keys) {
const DebuggerCommand &cmd = m_commandForToken.value(key);
killIt = true;
showMessage(QString::number(key) + ": " + cmd.function);
}
QStringList commands;
foreach (const DebuggerCommand &cmd, m_commandForToken)
commands << QString("\"%1\"").arg(cmd.function);
if (killIt) {
showMessage(QString("TIMED OUT WAITING FOR GDB REPLY. "
"COMMANDS STILL IN PROGRESS: ") + commands.join(", "));
int timeOut = m_commandTimer.interval();
//m_commandTimer.stop();
const QString msg = tr("The gdb process has not responded "
"to a command within %n second(s). This could mean it is stuck "
"in an endless loop or taking longer than expected to perform "
"the operation.\nYou can choose between waiting "
"longer or aborting debugging.", 0, timeOut / 1000);
QMessageBox *mb = showMessageBox(QMessageBox::Critical,
tr("GDB not responding"), msg,
QMessageBox::Ok | QMessageBox::Cancel);
mb->button(QMessageBox::Cancel)->setText(tr("Give GDB more time"));
mb->button(QMessageBox::Ok)->setText(tr("Stop debugging"));
if (mb->exec() == QMessageBox::Ok) {
showMessage("KILLING DEBUGGER AS REQUESTED BY USER");
// This is an undefined state, so we just pull the emergency brake.
m_gdbProc.kill();
notifyEngineShutdownFailed();
} else {
showMessage("CONTINUE DEBUGGER AS REQUESTED BY USER");
}
} else {
showMessage(QString("\nNON-CRITICAL TIMEOUT\nCOMMANDS STILL IN PROGRESS: ")
+ commands.join(", "));
}
}
void GdbEngine::handleResultRecord(DebuggerResponse *response)
{
//qDebug() << "TOKEN:" << response->token
// << " ACCEPTABLE:" << m_oldestAcceptableToken;
//qDebug() << "\nRESULT" << response->token << response->toString();
int token = response->token;
if (token == -1)
return;
if (!m_commandForToken.contains(token)) {
// In theory this should not happen (rather the error should be
// reported in the "first" response to the command) in practice it
// does. We try to handle a few situations we are aware of gracefully.
// Ideally, this code should not be present at all.
showMessage(QString("COOKIE FOR TOKEN %1 ALREADY EATEN (%2). "
"TWO RESPONSES FOR ONE COMMAND?").arg(token).
arg(stateName(state())));
if (response->resultClass == ResultError) {
QString msg = response->data["msg"].data();
if (msg == "Cannot find new threads: generic error") {
// Handle a case known to occur on Linux/gdb 6.8 when debugging moc
// with helpers enabled. In this case we get a second response with
// msg="Cannot find new threads: generic error"
showMessage("APPLYING WORKAROUND #1");
AsynchronousMessageBox::critical(tr("Executable failed"), msg);
showStatusMessage(tr("Process failed to start"));
//shutdown();
notifyInferiorIll();
} else if (msg == "\"finish\" not meaningful in the outermost frame.") {
// Handle a case known to appear on GDB 6.4 symbianelf when
// the stack is cut due to access to protected memory.
//showMessage("APPLYING WORKAROUND #2");
notifyInferiorStopOk();
} else if (msg.startsWith("Cannot find bounds of current function")) {
// Happens when running "-exec-next" in a function for which
// there is no debug information. Divert to "-exec-next-step"
showMessage("APPLYING WORKAROUND #3");
notifyInferiorStopOk();
executeNextI();
} else if (msg.startsWith("Couldn't get registers: No such process.")) {
// Happens on archer-tromey-python 6.8.50.20090910-cvs
// There might to be a race between a process shutting down
// and library load messages.
showMessage("APPLYING WORKAROUND #4");
notifyInferiorStopOk();
//notifyInferiorIll();
//showStatusMessage(tr("Executable failed: %1").arg(msg));
//shutdown();
//Core::AsynchronousMessageBox::critical(tr("Executable failed"), msg);
} else if (msg.contains("Cannot insert breakpoint")) {
// For breakpoints set by address to non-existent addresses we
// might get something like "6^error,msg="Warning:\nCannot insert
// breakpoint 3.\nError accessing memory address 0x34592327:
// Input/output error.\nCannot insert breakpoint 4.\nError
// accessing memory address 0x34592335: Input/output error.\n".
// This should not stop us from proceeding.
// Most notably, that happens after a "6^running" and "*running"
// We are probably sitting at _start and can't proceed as
// long as the breakpoints are enabled.
// FIXME: Should we silently disable the offending breakpoints?
showMessage("APPLYING WORKAROUND #5");
AsynchronousMessageBox::critical(tr("Setting breakpoints failed"), msg);
QTC_CHECK(state() == InferiorRunOk);
notifyInferiorSpontaneousStop();
notifyEngineIll();
} else if (isGdbConnectionError(msg)) {
notifyInferiorExited();
} else {
// Windows: Some DLL or some function not found. Report
// the exception now in a box.
if (msg.startsWith("During startup program exited with"))
notifyInferiorExited();
else if (msg.contains("Command aborted."))
notifyInferiorSpontaneousStop();
QString logMsg;
if (!m_lastWinException.isEmpty())
logMsg = m_lastWinException + '\n';
logMsg += msg;
AsynchronousMessageBox::critical(tr("Executable Failed"), logMsg);
showStatusMessage(tr("Executable failed: %1").arg(logMsg));
}
}
return;
}
DebuggerCommand cmd = m_commandForToken.take(token);
const int flags = m_flagsForToken.take(token);
if (boolSetting(LogTimeStamps)) {
showMessage(QString("Response time: %1: %2 s")
.arg(cmd.function)
.arg(QTime::fromMSecsSinceStartOfDay(cmd.postTime).msecsTo(QTime::currentTime()) / 1000.),
LogTime);
}
if (response->token < m_oldestAcceptableToken && (flags & Discardable)) {
//showMessage(_("### SKIPPING OLD RESULT") + response.toString());
return;
}
bool isExpectedResult =
(response->resultClass == ResultError) // Can always happen.
|| (response->resultClass == ResultRunning && (flags & RunRequest))
|| (response->resultClass == ResultExit && (flags & ExitRequest))
|| (response->resultClass == ResultDone);
// ResultDone can almost "always" happen. Known examples are:
// (response->resultClass == ResultDone && cmd.function == "continue")
// Happens with some incarnations of gdb 6.8 for "jump to line"
// (response->resultClass == ResultDone && cmd.function.startsWith("jump"))
// (response->resultClass == ResultDone && cmd.function.startsWith("detach"))
// Happens when stepping finishes very quickly and issues *stopped and ^done
// instead of ^running and *stopped
// (response->resultClass == ResultDone && (cmd.flags & RunRequest));
if (!isExpectedResult) {
const DebuggerRunParameters &rp = runParameters();
Abi abi = rp.toolChainAbi;
if (abi.os() == Abi::WindowsOS
&& cmd.function.startsWith("attach")
&& (rp.startMode == AttachExternal || rp.useTerminal))
{
// Ignore spurious 'running' responses to 'attach'.
} else {
QString rsp = DebuggerResponse::stringFromResultClass(response->resultClass);
rsp = "UNEXPECTED RESPONSE '" + rsp + "' TO COMMAND '" + cmd.function + "'";
qWarning("%s", qPrintable(rsp));
showMessage(rsp);
}
}
if (!(flags & Discardable))
--m_nonDiscardableCount;
m_inUpdateLocals = (flags & InUpdateLocals);
if (cmd.callback)
cmd.callback(*response);
if (flags & RebuildBreakpointModel) {
--m_pendingBreakpointRequests;
PENDING_DEBUG(" BREAKPOINT" << cmd.function);
if (m_pendingBreakpointRequests <= 0) {
PENDING_DEBUG("\n\n ... AND TRIGGERS BREAKPOINT MODEL UPDATE\n");
attemptBreakpointSynchronization();
}
} else {
PENDING_DEBUG(" OTHER (OUT):" << cmd.function
<< "LEAVES PENDING BREAKPOINT AT" << m_pendingBreakpointRequests);
}
// Continue only if there are no commands wire anymore, so this will
// be fully synchronous.
// 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_commandsDoneCallback && m_commandForToken.isEmpty()) {
showMessage("ALL COMMANDS DONE; INVOKING CALLBACK");
CommandsDoneCallback cont = m_commandsDoneCallback;
m_commandsDoneCallback = 0;
if (response->resultClass != ResultRunning) //only start if the thing is not already running
(this->*cont)();
} else {
PENDING_DEBUG("MISSING TOKENS: " << m_commandForToken.keys());
}
if (m_commandForToken.isEmpty())
m_commandTimer.stop();
}
bool GdbEngine::acceptsDebuggerCommands() const
{
return true;
// return state() == InferiorStopOk
// || state() == InferiorUnrunnable;
}
void GdbEngine::executeDebuggerCommand(const QString &command, DebuggerLanguages languages)
{
if (!(languages & CppLanguage))
return;
QTC_CHECK(acceptsDebuggerCommands());
runCommand({command, NativeCommand});
}
// This is triggered when switching snapshots.
void GdbEngine::updateAll()
{
//PENDING_DEBUG("UPDATING ALL\n");
QTC_CHECK(state() == InferiorUnrunnable || state() == InferiorStopOk);
reloadModulesInternal();
DebuggerCommand cmd(stackCommand(action(MaximalStackDepth)->value().toInt()));
cmd.callback = [this](const DebuggerResponse &r) { handleStackListFrames(r, false); };
runCommand(cmd);
stackHandler()->setCurrentIndex(0);
runCommand({"-thread-info", CB(handleThreadInfo)});
reloadRegisters();
updateLocals();
}
void GdbEngine::handleQuerySources(const DebuggerResponse &response)
{
m_sourcesListUpdating = false;
if (response.resultClass == ResultDone) {
QMap<QString, QString> oldShortToFull = m_shortToFullName;
m_shortToFullName.clear();
m_fullToShortName.clear();
// "^done,files=[{file="../../../../bin/dumper/dumper.cpp",
// fullname="/data5/dev/ide/main/bin/dumper/dumper.cpp"},
GdbMi files = response.data["files"];
foreach (const GdbMi &item, files.children()) {
GdbMi fileName = item["file"];
if (fileName.data().endsWith("<built-in>"))
continue;
GdbMi fullName = item["fullname"];
QString file = fileName.data();
QString full;
if (fullName.isValid()) {
full = cleanupFullName(fullName.data());
m_fullToShortName[full] = file;
}
m_shortToFullName[file] = full;
}
if (m_shortToFullName != oldShortToFull)
sourceFilesHandler()->setSourceFiles(m_shortToFullName);
}
}
void GdbEngine::handleExecuteJumpToLine(const DebuggerResponse &response)
{
if (response.resultClass == ResultRunning) {
// All is fine. Waiting for a *running
// and the temporary breakpoint to be hit.
notifyInferiorRunOk(); // Only needed for gdb < 7.0.
} else if (response.resultClass == ResultError) {
// Could be "Unreasonable jump request" or similar.
QString out = tr("Cannot jump. Stopped");
QString msg = response.data["msg"].data();
if (!msg.isEmpty())
out += ". " + msg;
showStatusMessage(out);
notifyInferiorRunFailed();
} else if (response.resultClass == ResultDone) {
// This happens on old gdb. Trigger the effect of a '*stopped'.
showStatusMessage(tr("Jumped. Stopped"));
notifyInferiorSpontaneousStop();
handleStop2(response.data);
}
}
void GdbEngine::handleExecuteRunToLine(const DebuggerResponse &response)
{
if (response.resultClass == ResultRunning) {
// All is fine. Waiting for a *running
// and the temporary breakpoint to be hit.
} else if (response.resultClass == ResultDone) {
// This happens on old gdb (Mac). gdb is not stopped yet,
// but merely accepted the continue.
// >&"continue\n"
// >~"Continuing.\n"
//>~"testArray () at ../simple/app.cpp:241\n"
//>~"241\t s[1] = \"b\";\n"
//>122^done
showStatusMessage(tr("Target line hit. Stopped"));
notifyInferiorRunOk();
}
}
static bool isExitedReason(const QString &reason)
{
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
}
void GdbEngine::handleStopResponse(const GdbMi &data)
{
// Ignore trap on Windows terminals, which results in
// spurious "* stopped" message.
if (m_terminalTrap && (!data.isValid() || !data["reason"].isValid())
&& Abi::hostAbi().os() == Abi::WindowsOS) {
m_terminalTrap = false;
showMessage("IGNORING TERMINAL SIGTRAP", LogMisc);
return;
}
if (isDying()) {
notifyInferiorStopOk();
return;
}
GdbMi threads = data["stopped-thread"];
threadsHandler()->notifyStopped(threads.data());
const QString reason = data["reason"].data();
if (isExitedReason(reason)) {
// // The user triggered a stop, but meanwhile the app simply exited ...
// QTC_ASSERT(state() == InferiorStopRequested
// /*|| state() == InferiorStopRequested_Kill*/,
// qDebug() << state());
QString msg;
if (reason == "exited") {
msg = tr("Application exited with exit code %1")
.arg(data["exit-code"].toString());
} else if (reason == "exited-signalled" || reason == "signal-received") {
msg = tr("Application exited after receiving signal %1")
.arg(data["signal-name"].toString());
} else {
msg = tr("Application exited normally");
}
// Only show the message. Ramp-down will be triggered by -thread-group-exited.
showStatusMessage(msg);
return;
}
// Ignore signals from the process stub.
const GdbMi frame = data["frame"];
const QString func = frame["from"].data();
if (runParameters().useTerminal
&& data["reason"].data() == "signal-received"
&& data["signal-name"].data() == "SIGSTOP"
&& (func.endsWith("/ld-linux.so.2")
|| func.endsWith("/ld-linux-x86-64.so.2")))
{
showMessage("INTERNAL CONTINUE AFTER SIGSTOP FROM STUB", LogMisc);
notifyInferiorSpontaneousStop();
continueInferiorInternal();
return;
}
if (!m_onStop.isEmpty()) {
notifyInferiorStopOk();
showMessage("HANDLING QUEUED COMMANDS AFTER TEMPORARY STOP", LogMisc);
DebuggerCommandSequence seq = m_onStop;
m_onStop = DebuggerCommandSequence();
for (const DebuggerCommand &cmd : seq.commands())
runCommand(cmd);
if (seq.wantContinue())
continueInferiorInternal();
return;
}
BreakpointResponseId rid(data["bkptno"].data());
int lineNumber = 0;
QString fullName;
QString function;
QString language;
if (frame.isValid()) {
const GdbMi lineNumberG = frame["line"];
function = frame["function"].data(); // V4 protocol
if (function.isEmpty())
function = frame["func"].data(); // GDB's *stopped messages
language = frame["language"].data();
if (lineNumberG.isValid()) {
lineNumber = lineNumberG.toInt();
fullName = cleanupFullName(frame["fullname"].data());
if (fullName.isEmpty())
fullName = frame["file"].data();
} // found line number
} else {
showMessage("INVALID STOPPED REASON", LogWarning);
}
if (rid.isValid() && frame.isValid()) {
// Use opportunity to update the breakpoint marker position.
Breakpoint bp = breakHandler()->findBreakpointByResponseId(rid);
const BreakpointResponse &response = bp.response();
QString fileName = response.fileName;
if (fileName.isEmpty())
fileName = bp.fileName();
if (fileName.isEmpty())
fileName = fullName;
if (!fileName.isEmpty())
bp.setMarkerFileAndLine(fileName, lineNumber);
}
//qDebug() << "BP " << rid << data.toString();
// Quickly set the location marker.
if (lineNumber && !boolSetting(OperateByInstruction)
&& QFileInfo::exists(fullName)
&& function != "qt_v4TriggeredBreakpointHook"
&& function != "qt_qmlDebugMessageAvailable"
&& language != "js")
gotoLocation(Location(fullName, lineNumber));
if (state() == InferiorRunOk) {
// Stop triggered by a breakpoint or otherwise not directly
// initiated by the user.
notifyInferiorSpontaneousStop();
} else if (state() == InferiorRunRequested) {
// Stop triggered by something like "-exec-step\n"
// "&"Cannot access memory at address 0xbfffedd4\n"
// or, on S40,
// "*running,thread-id="30""
// "&"Warning:\n""
// "&"Cannot insert breakpoint -33.\n"
// "&"Error accessing memory address 0x11673fc: Input/output error.\n""
// In this case a proper response 94^error,msg="" will follow and
// be handled in the result handler.
// -- or --
// *stopped arriving earlier than ^done response to an -exec-step
notifyInferiorSpontaneousStop();
} else if (state() == InferiorStopOk) {
// That's expected.
} else if (state() == InferiorStopRequested) {
notifyInferiorStopOk();
} else if (state() == EngineRunRequested) {
// This is gdb 7+'s initial *stopped in response to attach that
// appears before the ^done is seen.
notifyEngineRunAndInferiorStopOk();
const DebuggerRunParameters &rp = runParameters();
if (rp.useTerminal)
continueInferiorInternal();
return;
} else {
QTC_CHECK(false);
}
CHECK_STATE(InferiorStopOk);
handleStop1(data);
}
static QString stopSignal(const Abi &abi)
{
return QLatin1String(abi.os() == Abi::WindowsOS ? "SIGTRAP" : "SIGINT");
}
void GdbEngine::handleStop1(const GdbMi &data)
{
CHECK_STATE(InferiorStopOk);
QTC_ASSERT(!isDying(), return);
const GdbMi frame = data["frame"];
const QString reason = data["reason"].data();
// This was seen on XP after removing a breakpoint while running
// >945*stopped,reason="signal-received",signal-name="SIGTRAP",
// signal-meaning="Trace/breakpoint trap",thread-id="2",
// frame={addr="0x7c91120f",func="ntdll!DbgUiConnectToDbg",
// args=[],from="C:\\WINDOWS\\system32\\ntdll.dll"}
// also seen on gdb 6.8-symbianelf without qXfer:libraries:read+;
// FIXME: remote.c parses "loaded" reply. It should be turning
// that into a TARGET_WAITKIND_LOADED. Does it?
// The bandaid here has the problem that it breaks for 'next' over a
// statement that indirectly loads shared libraries
// 6.1.2010: Breaks interrupting inferiors, disabled:
// if (reason == "signal-received"
// && data.findChild("signal-name").data() == "SIGTRAP") {
// continueInferiorInternal();
// return;
// }
// Jump over well-known frames.
static int stepCounter = 0;
if (boolSetting(SkipKnownFrames)) {
if (reason == "end-stepping-range" || reason == "function-finished") {
//showMessage(frame.toString());
QString funcName = frame["function"].data();
QString fileName = frame["file"].data();
if (isLeavableFunction(funcName, fileName)) {
//showMessage(_("LEAVING ") + funcName);
++stepCounter;
executeStepOut();
return;
}
if (isSkippableFunction(funcName, fileName)) {
//showMessage(_("SKIPPING ") + funcName);
++stepCounter;
executeStep();
return;
}
//if (stepCounter)
// qDebug() << "STEPCOUNTER:" << stepCounter;
stepCounter = 0;
}
}
// Show return value if possible, usually with reason "function-finished".
// *stopped,reason="function-finished",frame={addr="0x080556da",
// func="testReturnValue",args=[],file="/../app.cpp",
// fullname="/../app.cpp",line="1611"},gdb-result-var="$1",
// return-value="{d = 0x808d998}",thread-id="1",stopped-threads="all",
// core="1"
GdbMi resultVar = data["gdb-result-var"];
if (resultVar.isValid())
m_resultVarName = resultVar.data();
else
m_resultVarName.clear();
if (!m_systemDumpersLoaded) {
m_systemDumpersLoaded = true;
if (m_gdbVersion >= 70400 && boolSetting(LoadGdbDumpers))
runCommand({"importPlainDumpers on"});
else
runCommand({"importPlainDumpers off"});
}
handleStop2(data);
}
void GdbEngine::handleStop2(const GdbMi &data)
{
CHECK_STATE(InferiorStopOk);
QTC_ASSERT(!isDying(), return);
// A user initiated stop looks like the following. Note that there is
// this extra "stopper thread" created and "properly" reported by gdb.
//
// dNOTE: INFERIOR RUN OK
// dState changed from InferiorRunRequested(10) to InferiorRunOk(11).
// >*running,thread-id="all"
// >=thread-exited,id="11",group-id="i1"
// sThread 11 in group i1 exited
// dState changed from InferiorRunOk(11) to InferiorStopRequested(13).
// dCALL: INTERRUPT INFERIOR
// sStop requested...
// dTRYING TO INTERRUPT INFERIOR
// >=thread-created,id="12",group-id="i1"
// sThread 12 created
// >~"[New Thread 8576.0x1154]\n"
// s[New Thread 8576.0x1154]
// >*running,thread-id="all"
// >~"[Switching to Thread 8576.0x1154]\n"
// >*stopped,reason="signal-received",signal-name="SIGTRAP",
// signal-meaning="Trace/breakpointtrap",frame={addr="0x7c90120f",func=
// "ntdll!DbgUiConnectToDbg",args=[],from="C:\\WINDOWS\\system32\\ntdll.dll"},
// thread-id="12",stopped-threads="all"
// dNOTE: INFERIOR STOP OK
// dState changed from InferiorStopRequested(13) to InferiorStopOk(14).
const QString reason = data["reason"].data();
const DebuggerRunParameters &rp = runParameters();
bool isStopperThread = false;
if (rp.toolChainAbi.os() == Abi::WindowsOS
&& rp.useTerminal
&& reason == "signal-received"
&& data["signal-name"].data() == "SIGTRAP")
{
// This is the stopper thread. That also means that the
// reported thread is not the one we'd like to expose
// to the user.
isStopperThread = true;
}
if (reason == "watchpoint-trigger") {
// *stopped,reason="watchpoint-trigger",wpt={number="2",exp="*0xbfffed40"},
// value={old="1",new="0"},frame={addr="0x00451e1b",
// func="QScopedPointer",args=[{name="this",value="0xbfffed40"},
// {name="p",value="0x0"}],file="x.h",fullname="/home/.../x.h",line="95"},
// thread-id="1",stopped-threads="all",core="2"
const GdbMi wpt = data["wpt"];
const BreakpointResponseId rid(wpt["number"].data());
const Breakpoint bp = breakHandler()->findBreakpointByResponseId(rid);
const quint64 bpAddress = wpt["exp"].data().mid(1).toULongLong(0, 0);
QString msg;
if (bp.type() == WatchpointAtExpression)
msg = bp.msgWatchpointByExpressionTriggered(rid.majorPart(), bp.expression());
if (bp.type() == WatchpointAtAddress)
msg = bp.msgWatchpointByAddressTriggered(rid.majorPart(), bpAddress);
GdbMi value = data["value"];
GdbMi oldValue = value["old"];
GdbMi newValue = value["new"];
if (oldValue.isValid() && newValue.isValid()) {
msg += ' ';
msg += tr("Value changed from %1 to %2.")
.arg(oldValue.data()).arg(newValue.data());
}
showStatusMessage(msg);
} else if (reason == "breakpoint-hit") {
GdbMi gNumber = data["bkptno"]; // 'number' or 'bkptno'?
if (!gNumber.isValid())
gNumber = data["number"];
const BreakpointResponseId rid(gNumber.data());
const QString threadId = data["thread-id"].data();
const Breakpoint bp = breakHandler()->findBreakpointByResponseId(rid);
showStatusMessage(bp.msgBreakpointTriggered(rid.majorPart(), threadId));
m_currentThread = threadId;
} else {
QString reasontr = msgStopped(reason);
if (reason == "signal-received") {
QString name = data["signal-name"].data();
QString meaning = data["signal-meaning"].data();
// Ignore these as they are showing up regularly when
// stopping debugging.
if (name == stopSignal(rp.toolChainAbi) || rp.expectedSignals.contains(name)) {
showMessage(name + " CONSIDERED HARMLESS. CONTINUING.");
} else {
showMessage("HANDLING SIGNAL " + name);
if (boolSetting(UseMessageBoxForSignals) && !isStopperThread)
if (!showStoppedBySignalMessageBox(meaning, name)) {
showMessage("SIGNAL RECEIVED WHILE SHOWING SIGNAL MESSAGE");
return;
}
if (!name.isEmpty() && !meaning.isEmpty())
reasontr = msgStoppedBySignal(meaning, name);
}
}
if (reason.isEmpty())
showStatusMessage(msgStopped());
else
showStatusMessage(reasontr);
}
// Let the event loop run before deciding whether to update the stack.
m_stackNeeded = true; // setTokenBarrier() might reset this.
QTimer::singleShot(0, this, &GdbEngine::handleStop3);
}
void GdbEngine::handleStop3()
{
DebuggerCommand cmd("-thread-info", Discardable);
cmd.callback = CB(handleThreadInfo);
runCommand(cmd);
}
void GdbEngine::handleShowVersion(const DebuggerResponse &response)
{
showMessage("PARSING VERSION: " + response.toString());
if (response.resultClass == ResultDone) {
bool isMacGdb = false;
int gdbBuildVersion = -1;
m_gdbVersion = 100;
m_isQnxGdb = false;
QString msg = response.consoleStreamOutput;
extractGdbVersion(msg,
&m_gdbVersion, &gdbBuildVersion, &isMacGdb, &m_isQnxGdb);
// On Mac, FSF GDB does not work sufficiently well,
// and on Linux and Windows we require at least 7.4.1,
// on Android 7.3.1.
bool isSupported = m_gdbVersion >= 70300;
if (isSupported)
showMessage("SUPPORTED GDB VERSION " + msg);
else
showMessage("UNSUPPORTED GDB VERSION " + msg);
showMessage(QString("USING GDB VERSION: %1, BUILD: %2%3").arg(m_gdbVersion)
.arg(gdbBuildVersion).arg(QLatin1String(isMacGdb ? " (APPLE)" : "")));
if (usesExecInterrupt())
runCommand({"set target-async on", ConsoleCommand});
else
runCommand({"set target-async off", ConsoleCommand});
//runCommand("set build-id-verbose 2", ConsoleCommand);
}
}
void GdbEngine::handleListFeatures(const DebuggerResponse &response)
{
showMessage("FEATURES: " + response.toString());
}
void GdbEngine::handlePythonSetup(const DebuggerResponse &response)
{
CHECK_STATE(EngineSetupRequested);
if (response.resultClass == ResultDone) {
GdbMi data = response.data;
watchHandler()->addDumpers(data["dumpers"]);
m_pythonVersion = data["python"].toInt();
if (m_pythonVersion < 20700) {
int pythonMajor = m_pythonVersion / 10000;
int pythonMinor = (m_pythonVersion / 100) % 100;
QString out = "<p>"
+ tr("The selected build of GDB supports Python scripting, "
"but the used version %1.%2 is not sufficient for "
"Qt Creator. Supported versions are Python 2.7 and 3.x.")
.arg(pythonMajor).arg(pythonMinor);
showStatusMessage(out);
AsynchronousMessageBox::critical(tr("Execution Error"), out);
}
loadInitScript();
CHECK_STATE(EngineSetupRequested);
showMessage("ENGINE SUCCESSFULLY STARTED");
notifyEngineSetupOk();
} else {
QString msg = response.data["msg"].data();
if (msg.contains("Python scripting is not supported in this copy of GDB.")) {
QString out1 = "The selected build of GDB does not support Python scripting.";
QString out2 = "It cannot be used in Qt Creator.";
showStatusMessage(out1 + ' ' + out2);
AsynchronousMessageBox::critical(tr("Execution Error"), out1 + "<br>" + out2);
}
notifyEngineSetupFailed();
}
}
void GdbEngine::showExecutionError(const QString &message)
{
AsynchronousMessageBox::critical(tr("Execution Error"),
tr("Cannot continue debugged process:") + '\n' + message);
}
void GdbEngine::handleExecuteContinue(const DebuggerResponse &response)
{
CHECK_STATE(InferiorRunRequested);
if (response.resultClass == ResultRunning) {
// All is fine. Waiting for a *running.
notifyInferiorRunOk(); // Only needed for gdb < 7.0.
return;
}
QString msg = response.data["msg"].data();
if (msg.startsWith("Cannot find bounds of current function")) {
notifyInferiorRunFailed();
if (isDying())
return;
CHECK_STATE(InferiorStopOk);
showStatusMessage(tr("Stopped."), 5000);
reloadStack();
} else if (msg.startsWith("Cannot access memory at address")) {
// Happens on single step on ARM prolog and epilogs.
} else if (msg.startsWith("\"finish\" not meaningful in the outermost frame")) {
notifyInferiorRunFailed();
if (isDying())
return;
CHECK_STATE(InferiorStopOk);
// FIXME: Fix translation in master.
showStatusMessage(msg, 5000);
gotoLocation(stackHandler()->currentFrame());
} else if (msg.startsWith("Cannot execute this command while the selected thread is running.")) {
showExecutionError(msg);
notifyInferiorRunFailed() ;
} else {
showExecutionError(msg);
notifyInferiorIll();
}
}
QString GdbEngine::fullName(const QString &fileName)
{
if (fileName.isEmpty())
return QString();
QTC_ASSERT(!m_sourcesListUpdating, /* */);
return m_shortToFullName.value(fileName, QString());
}
QString GdbEngine::cleanupFullName(const QString &fileName)
{
QString cleanFilePath = fileName;
// Gdb running on windows often delivers "fullnames" which
// (a) have no drive letter and (b) are not normalized.
if (Abi::hostAbi().os() == Abi::WindowsOS) {
if (fileName.isEmpty())
return QString();
QFileInfo fi(fileName);
if (fi.isReadable())
cleanFilePath = QDir::cleanPath(fi.absoluteFilePath());
}
if (!boolSetting(AutoEnrichParameters))
return cleanFilePath;
const QString sysroot = runParameters().sysRoot;
if (QFileInfo(cleanFilePath).isReadable())
return cleanFilePath;
if (!sysroot.isEmpty() && fileName.startsWith('/')) {
cleanFilePath = sysroot + fileName;
if (QFileInfo(cleanFilePath).isReadable())
return cleanFilePath;
}
if (m_baseNameToFullName.isEmpty()) {
QString debugSource = sysroot + "/usr/src/debug";
if (QFileInfo(debugSource).isDir()) {
QDirIterator it(debugSource, QDirIterator::Subdirectories);
while (it.hasNext()) {
it.next();
QString name = it.fileName();
if (!name.startsWith('.')) {
QString path = it.filePath();
m_baseNameToFullName.insert(name, path);
}
}
}
}
cleanFilePath.clear();
const QString base = FileName::fromString(fileName).fileName();
QMap<QString, QString>::const_iterator jt = m_baseNameToFullName.constFind(base);
while (jt != m_baseNameToFullName.constEnd() && jt.key() == base) {
// FIXME: Use some heuristics to find the "best" match.
return jt.value();
//++jt;
}
return cleanFilePath;
}
void GdbEngine::shutdownInferior()
{
CHECK_STATE(InferiorShutdownRequested);
DebuggerCommand cmd;
cmd.function = QLatin1String(runParameters().closeMode == DetachAtClose ? "detach " : "kill ");
cmd.callback = CB(handleInferiorShutdown);
cmd.flags = NeedsTemporaryStop|LosesChild;
runCommand(cmd);
}
void GdbEngine::handleInferiorShutdown(const DebuggerResponse &response)
{
if (response.resultClass == ResultDone) {
// We'll get async thread-group-exited responses to which we react.
// Nothing to do here.
// notifyInferiorShutdownOk();
return;
}
// "kill" got stuck, or similar.
CHECK_STATE(InferiorShutdownRequested);
QString msg = response.data["msg"].data();
if (msg.contains(": No such file or directory.")) {
// This happens when someone removed the binary behind our back.
// It is not really an error from a user's point of view.
showMessage("NOTE: " + msg);
notifyInferiorShutdownOk();
return;
}
AsynchronousMessageBox::critical(tr("Failed to shut down application"),
msgInferiorStopFailed(msg));
notifyInferiorShutdownFailed();
}
void GdbEngine::notifyAdapterShutdownFailed()
{
showMessage("ADAPTER SHUTDOWN FAILED");
CHECK_STATE(EngineShutdownRequested);
notifyEngineShutdownFailed();
}
void GdbEngine::notifyAdapterShutdownOk()
{
CHECK_STATE(EngineShutdownRequested);
showMessage(QString("INITIATE GDBENGINE SHUTDOWN IN STATE %1, PROC: %2")
.arg(lastGoodState()).arg(m_gdbProc.state()));
m_commandsDoneCallback = 0;
switch (m_gdbProc.state()) {
case QProcess::Running: {
if (runParameters().closeMode == KillAndExitMonitorAtClose)
runCommand({"monitor exit"});
runCommand({"exitGdb", ExitRequest, CB(handleGdbExit)});
break;
}
case QProcess::NotRunning:
// Cannot find executable.
notifyEngineShutdownOk();
break;
case QProcess::Starting:
showMessage("GDB NOT REALLY RUNNING; KILLING IT");
m_gdbProc.kill();
notifyEngineShutdownFailed();
break;
}
}
void GdbEngine::handleGdbExit(const DebuggerResponse &response)
{
if (response.resultClass == ResultExit) {
showMessage("GDB CLAIMS EXIT; WAITING");
// Don't set state here, this will be handled in handleGdbFinished()
//notifyEngineShutdownOk();
} else {
QString msg = msgGdbStopFailed(response.data["msg"].data());
qDebug() << QString("GDB WON'T EXIT (%1); KILLING IT").arg(msg);
showMessage(QString("GDB WON'T EXIT (%1); KILLING IT").arg(msg));
m_gdbProc.kill();
notifyEngineShutdownFailed();
}
}
void GdbEngine::setLinuxOsAbi()
{
// In case GDB has multiple supported targets, the default osabi can be Cygwin.
if (!HostOsInfo::isWindowsHost())
return;
const DebuggerRunParameters &rp = runParameters();
bool isElf = (rp.toolChainAbi.binaryFormat() == Abi::ElfFormat);
if (!isElf && !rp.inferior.executable.isEmpty()) {
isElf = Utils::anyOf(Abi::abisOfBinary(FileName::fromString(rp.inferior.executable)),
[](const Abi &abi) {
return abi.binaryFormat() == Abi::ElfFormat;
});
}
if (isElf)
runCommand({"set osabi GNU/Linux"});
}
void GdbEngine::detachDebugger()
{
CHECK_STATE(InferiorStopOk);
QTC_ASSERT(startMode() != AttachCore, qDebug() << startMode());
DebuggerCommand cmd("detach", ExitRequest);
cmd.callback = [this](const DebuggerResponse &) {
CHECK_STATE(InferiorStopOk);
notifyInferiorExited();
};
runCommand(cmd);
}
void GdbEngine::handleThreadGroupCreated(const GdbMi &result)
{
QString groupId = result["id"].data();
QString pid = result["pid"].data();
threadsHandler()->notifyGroupCreated(groupId, pid);
}
void GdbEngine::handleThreadGroupExited(const GdbMi &result)
{
QString groupId = result["id"].data();
if (threadsHandler()->notifyGroupExited(groupId)) {
if (m_rerunPending)
m_rerunPending = false;
else
notifyInferiorExited();
}
}
static QString msgNoGdbBinaryForToolChain(const Abi &tc)
{
return GdbEngine::tr("There is no GDB binary available for binaries in format \"%1\"")
.arg(tc.toString());
}
bool GdbEngine::hasCapability(unsigned cap) const
{
if (cap & (ReverseSteppingCapability
| AutoDerefPointersCapability
| DisassemblerCapability
| RegisterCapability
| ShowMemoryCapability
| JumpToLineCapability
| ReloadModuleCapability
| ReloadModuleSymbolsCapability
| BreakOnThrowAndCatchCapability
| BreakConditionCapability
| TracePointCapability
| ReturnFromFunctionCapability
| CreateFullBacktraceCapability
| WatchpointByAddressCapability
| WatchpointByExpressionCapability
| AddWatcherCapability
| AddWatcherWhileRunningCapability
| WatchWidgetsCapability
| ShowModuleSymbolsCapability
| ShowModuleSectionsCapability
| CatchCapability
| OperateByInstructionCapability
| RunToLineCapability
| WatchComplexExpressionsCapability
| MemoryAddressCapability
| AdditionalQmlStackCapability
| NativeMixedCapability
| ResetInferiorCapability))
return true;
if (runParameters().startMode == AttachCore)
return false;
// FIXME: Remove in case we have gdb 7.x on macOS.
if (runParameters().toolChainAbi.os() == Abi::DarwinOS)
return false;
return cap == SnapshotCapability;
}
void GdbEngine::continueInferiorInternal()
{
CHECK_STATE(InferiorStopOk);
notifyInferiorRunRequested();
showStatusMessage(tr("Running requested..."), 5000);
CHECK_STATE(InferiorRunRequested);
if (isNativeMixedActiveFrame()) {
DebuggerCommand cmd("executeContinue", RunRequest);
cmd.callback = CB(handleExecuteContinue);
runCommand(cmd);
} else {
DebuggerCommand cmd("-exec-continue");
cmd.flags = RunRequest | NeedsFlush;
cmd.callback = CB(handleExecuteContinue);
runCommand(cmd);
}
}
void GdbEngine::continueInferior()
{
CHECK_STATE(InferiorStopOk);
setTokenBarrier();
continueInferiorInternal();
}
void GdbEngine::executeStep()
{
CHECK_STATE(InferiorStopOk);
setTokenBarrier();
notifyInferiorRunRequested();
showStatusMessage(tr("Step requested..."), 5000);
if (isNativeMixedActiveFrame()) {
DebuggerCommand cmd("executeStep", RunRequest);
cmd.callback = CB(handleExecuteStep);
runCommand(cmd);
} else {
DebuggerCommand cmd;
cmd.flags = RunRequest|NeedsFlush;
cmd.function = QLatin1String(isReverseDebugging() ? "reverse-step" : "-exec-step");
cmd.callback = CB(handleExecuteStep);
runCommand(cmd);
}
}
void GdbEngine::handleExecuteStep(const DebuggerResponse &response)
{
if (response.resultClass == ResultDone) {
// Step was finishing too quick, and a '*stopped' messages should
// have preceded it, so just ignore this result.
QTC_CHECK(state() == InferiorStopOk);
return;
}
CHECK_STATE(InferiorRunRequested);
if (response.resultClass == ResultRunning) {
// All is fine. Waiting for a *running.
notifyInferiorRunOk(); // Only needed for gdb < 7.0.
return;
}
QString msg = response.data["msg"].data();
if (msg.startsWith("Cannot find bounds of current function")
|| msg.contains("Error accessing memory address")
|| msg.startsWith("Cannot access memory at address")) {
// On S40: "40^error,msg="Warning:\nCannot insert breakpoint -39.\n"
//" Error accessing memory address 0x11673fc: Input/output error.\n"
notifyInferiorRunFailed();
if (isDying())
return;
executeStepI(); // Fall back to instruction-wise stepping.
} else if (msg.startsWith("Cannot execute this command while the selected thread is running.")) {
showExecutionError(msg);
notifyInferiorRunFailed();
} else if (msg.startsWith("warning: SuspendThread failed")) {
// On Win: would lead to "PC register is not available" or "\312"
continueInferiorInternal();
} else {
showExecutionError(msg);
notifyInferiorIll();
}
}
void GdbEngine::executeStepI()
{
CHECK_STATE(InferiorStopOk);
setTokenBarrier();
notifyInferiorRunRequested();
showStatusMessage(tr("Step by instruction requested..."), 5000);
DebuggerCommand cmd;
cmd.flags = RunRequest|NeedsFlush;
cmd.function = QLatin1String(isReverseDebugging() ? "reverse-stepi" : "-exec-step-instruction");
cmd.callback = CB(handleExecuteContinue);
runCommand(cmd);
}
void GdbEngine::executeStepOut()
{
CHECK_STATE(InferiorStopOk);
runCommand({"-stack-select-frame 0", Discardable});
setTokenBarrier();
notifyInferiorRunRequested();
showStatusMessage(tr("Finish function requested..."), 5000);
if (isNativeMixedActiveFrame()) {
runCommand({"executeStepOut", RunRequest});
} else {
// -exec-finish in 'main' results (correctly) in
// 40^error,msg="\"finish\" not meaningful in the outermost frame."
// However, this message does not seem to get flushed before
// anything else happen - i.e. "never". Force some extra output.
runCommand({"-exec-finish", RunRequest|NeedsFlush, CB(handleExecuteContinue)});
}
}
void GdbEngine::executeNext()
{
CHECK_STATE(InferiorStopOk);
setTokenBarrier();
notifyInferiorRunRequested();
showStatusMessage(tr("Step next requested..."), 5000);
if (isNativeMixedActiveFrame()) {
runCommand({"executeNext", RunRequest});
} else {
DebuggerCommand cmd;
cmd.flags = RunRequest;
cmd.function = QLatin1String(isReverseDebugging() ? "reverse-next" : "-exec-next");
cmd.callback = CB(handleExecuteNext);
runCommand(cmd);
}
}
void GdbEngine::handleExecuteNext(const DebuggerResponse &response)
{
if (response.resultClass == ResultDone) {
// Step was finishing too quick, and a '*stopped' messages should
// have preceded it, so just ignore this result.
CHECK_STATE(InferiorStopOk);
return;
}
CHECK_STATE(InferiorRunRequested);
if (response.resultClass == ResultRunning) {
// All is fine. Waiting for a *running.
notifyInferiorRunOk(); // Only needed for gdb < 7.0.
return;
}
CHECK_STATE(InferiorStopOk);
QString msg = response.data["msg"].data();
if (msg.startsWith("Cannot find bounds of current function")
|| msg.contains("Error accessing memory address ")) {
notifyInferiorRunFailed();
if (!isDying())
executeNextI(); // Fall back to instruction-wise stepping.
} else if (msg.startsWith("Cannot execute this command while the selected thread is running.")) {
showExecutionError(msg);
notifyInferiorRunFailed();
} else {
AsynchronousMessageBox::critical(tr("Execution Error"),
tr("Cannot continue debugged process:") + '\n' + msg);
notifyInferiorIll();
}
}
void GdbEngine::executeNextI()
{
CHECK_STATE(InferiorStopOk);
setTokenBarrier();
notifyInferiorRunRequested();
showStatusMessage(tr("Step next instruction requested..."), 5000);
DebuggerCommand cmd;
cmd.flags = RunRequest;
cmd.function = QLatin1String(isReverseDebugging() ? "reverse-nexti" : "-exec-next-instruction");
cmd.callback = CB(handleExecuteContinue);
runCommand(cmd);
}
static QString addressSpec(quint64 address)
{
return "*0x" + QString::number(address, 16);
}
void GdbEngine::executeRunToLine(const ContextData &data)
{
CHECK_STATE(InferiorStopOk);
setTokenBarrier();
notifyInferiorRunRequested();
showStatusMessage(tr("Run to line %1 requested...").arg(data.lineNumber), 5000);
#if 1
QString loc;
if (data.address)
loc = addressSpec(data.address);
else
loc = '"' + breakLocation(data.fileName) + '"' + ':' + QString::number(data.lineNumber);
runCommand({"tbreak " + loc});
runCommand({"continue", NativeCommand|RunRequest, CB(handleExecuteRunToLine)});
#else
// Seems to jump to unpredicatable places. Observed in the manual
// tests in the Foo::Foo() constructor with both gdb 6.8 and 7.1.
QString args = '"' + breakLocation(fileName) + '"' + ':' + QString::number(lineNumber);
runCommand("-exec-until " + args, RunRequest, CB(handleExecuteContinue));
#endif
}
void GdbEngine::executeRunToFunction(const QString &functionName)
{
CHECK_STATE(InferiorStopOk);
setTokenBarrier();
runCommand({"-break-insert -t " + functionName});
showStatusMessage(tr("Run to function %1 requested...").arg(functionName), 5000);
continueInferiorInternal();
}
void GdbEngine::executeJumpToLine(const ContextData &data)
{
CHECK_STATE(InferiorStopOk);
QString loc;
if (data.address)
loc = addressSpec(data.address);
else
loc = '"' + breakLocation(data.fileName) + '"' + ':' + QString::number(data.lineNumber);
runCommand({"tbreak " + loc});
notifyInferiorRunRequested();
runCommand({"jump " + loc, RunRequest, CB(handleExecuteJumpToLine)});
// will produce something like
// &"jump \"/home/apoenitz/dev/work/test1/test1.cpp\":242"
// ~"Continuing at 0x4058f3."
// ~"run1 (argc=1, argv=0x7fffbf1f5538) at test1.cpp:242"
// ~"242\t x *= 2;"
// 23^done"
}
void GdbEngine::executeReturn()
{
CHECK_STATE(InferiorStopOk);
setTokenBarrier();
notifyInferiorRunRequested();
showStatusMessage(tr("Immediate return from function requested..."), 5000);
runCommand({"-exec-return", RunRequest, CB(handleExecuteReturn)});
}
void GdbEngine::handleExecuteReturn(const DebuggerResponse &response)
{
if (response.resultClass == ResultDone) {
notifyInferiorStopOk();
updateAll();
return;
}
notifyInferiorRunFailed();
}
/*!
Discards the results of all pending watch-updating commands.
This function is called at the beginning of all step, next, finish, and so on,
debugger functions.
If non-watch-updating commands with call-backs are still in the pipe,
it will complain.
*/
void GdbEngine::setTokenBarrier()
{
//QTC_ASSERT(m_nonDiscardableCount == 0, /**/);
bool good = true;
QHashIterator<int, DebuggerCommand> it(m_commandForToken);
while (it.hasNext()) {
it.next();
if (!(m_flagsForToken.value(it.key()) & Discardable)) {
qDebug() << "TOKEN: " << it.key() << "CMD:" << it.value().function;
good = false;
}
}
QTC_ASSERT(good, return);
PENDING_DEBUG("\n--- token barrier ---\n");
showMessage("--- token barrier ---", LogMiscInput);
if (boolSetting(LogTimeStamps))
showMessage(LogWindow::logTimeStamp(), LogMiscInput);
m_oldestAcceptableToken = currentToken();
m_stackNeeded = false;
}
//////////////////////////////////////////////////////////////////////
//
// Breakpoint specific stuff
//
//////////////////////////////////////////////////////////////////////
void GdbEngine::updateResponse(BreakpointResponse &response, const GdbMi &bkpt)
{
QTC_ASSERT(bkpt.isValid(), return);
QString originalLocation;
QString file;
QString fullName;
response.multiple = false;
response.enabled = true;
response.pending = false;
response.condition.clear();
foreach (const GdbMi &child, bkpt.children()) {
if (child.hasName("number")) {
response.id = BreakpointResponseId(child.data());
} else if (child.hasName("func")) {
response.functionName = child.data();
} else if (child.hasName("addr")) {
// <MULTIPLE> happens in constructors, inline functions, and
// at other places like 'foreach' lines. In this case there are
// fields named "addr" in the response and/or the address
// is called <MULTIPLE>.
//qDebug() << "ADDR: " << child.data() << (child.data() == "<MULTIPLE>");
if (child.data() == "<MULTIPLE>")
response.multiple = true;
if (child.data().startsWith("0x"))
response.address = child.toAddress();
} else if (child.hasName("file")) {
file = child.data();
} else if (child.hasName("fullname")) {
fullName = child.data();
} else if (child.hasName("line")) {
// The line numbers here are the uncorrected ones. So don't
// change it if we know better already.
if (response.correctedLineNumber == 0)
response.lineNumber = child.toInt();
} else if (child.hasName("cond")) {
// gdb 6.3 likes to "rewrite" conditions. Just accept that fact.
response.condition = child.data();
} else if (child.hasName("enabled")) {
response.enabled = (child.data() == "y");
} else if (child.hasName("disp")) {
response.oneShot = child.data() == "del";
} else if (child.hasName("pending")) {
// Any content here would be interesting only if we did accept
// spontaneously appearing breakpoints (user using gdb commands).
if (file.isEmpty())
file = child.data();
response.pending = true;
} else if (child.hasName("at")) {
// Happens with gdb 6.4 symbianelf.
QString ba = child.data();
if (ba.startsWith('<') && ba.endsWith('>'))
ba = ba.mid(1, ba.size() - 2);
response.functionName = ba;
} else if (child.hasName("thread")) {
response.threadSpec = child.toInt();
} else if (child.hasName("type")) {
// "breakpoint", "hw breakpoint", "tracepoint", "hw watchpoint"
// {bkpt={number="2",type="hw watchpoint",disp="keep",enabled="y",
// what="*0xbfffed48",times="0",original-location="*0xbfffed48"}}
if (child.data().contains("tracepoint")) {
response.tracepoint = true;
} else if (child.data() == "hw watchpoint" || child.data() == "watchpoint") {
QString what = bkpt["what"].data();
if (what.startsWith("*0x")) {
response.type = WatchpointAtAddress;
response.address = what.mid(1).toULongLong(0, 0);
} else {
response.type = WatchpointAtExpression;
response.expression = what;
}
} else if (child.data() == "breakpoint") {
QString catchType = bkpt["catch-type"].data();
if (catchType == "throw")
response.type = BreakpointAtThrow;
else if (catchType == "catch")
response.type = BreakpointAtCatch;
else if (catchType == "fork")
response.type = BreakpointAtFork;
else if (catchType == "exec")
response.type = BreakpointAtExec;
else if (catchType == "syscall")
response.type = BreakpointAtSysCall;
}
} else if (child.hasName("times")) {
response.hitCount = child.toInt();
} else if (child.hasName("original-location")) {
originalLocation = child.data();
}
// This field is not present. Contents needs to be parsed from
// the plain "ignore" response.
//else if (child.hasName("ignore"))
// response.ignoreCount = child.data();
}
QString name;
if (!fullName.isEmpty()) {
name = cleanupFullName(fullName);
response.fileName = name;
//if (data->markerFileName().isEmpty())
// data->setMarkerFileName(name);
} else {
name = file;
// Use fullName() once we have a mapping which is more complete than
// gdb's own. No point in assigning markerFileName for now.
}
if (!name.isEmpty())
response.fileName = name;
if (response.fileName.isEmpty())
response.updateLocation(originalLocation);
}
QString GdbEngine::breakLocation(const QString &file) const
{
QString where = m_fullToShortName.value(file);
if (where.isEmpty())
return FileName::fromString(file).fileName();
return where;
}
QString GdbEngine::breakpointLocation(const BreakpointParameters &data)
{
QTC_ASSERT(data.type != UnknownBreakpointType, return QString());
// FIXME: Non-GCC-runtime
if (data.type == BreakpointAtThrow)
return QLatin1String("__cxa_throw");
if (data.type == BreakpointAtCatch)
return QLatin1String("__cxa_begin_catch");
if (data.type == BreakpointAtMain)
return mainFunction(runParameters());
if (data.type == BreakpointByFunction)
return '"' + data.functionName + '"';
if (data.type == BreakpointByAddress)
return addressSpec(data.address);
BreakpointPathUsage usage = data.pathUsage;
if (usage == BreakpointPathUsageEngineDefault)
usage = BreakpointUseShortPath;
const QString fileName = usage == BreakpointUseFullPath
? data.fileName : breakLocation(data.fileName);
// The argument is simply a C-quoted version of the argument to the
// non-MI "break" command, including the "original" quoting it wants.
return "\"\\\"" + GdbMi::escapeCString(fileName) + "\\\":"
+ QString::number(data.lineNumber) + '"';
}
QString GdbEngine::breakpointLocation2(const BreakpointParameters &data)
{
BreakpointPathUsage usage = data.pathUsage;
if (usage == BreakpointPathUsageEngineDefault)
usage = BreakpointUseShortPath;
const QString fileName = usage == BreakpointUseFullPath
? data.fileName : breakLocation(data.fileName);
return GdbMi::escapeCString(fileName) + ':' + QString::number(data.lineNumber);
}
void GdbEngine::handleInsertInterpreterBreakpoint(const DebuggerResponse &response, Breakpoint bp)
{
BreakpointResponse br = bp.response();
bool pending = response.data["pending"].toInt();
if (pending) {
bp.notifyBreakpointInsertOk();
} else {
br.id = BreakpointResponseId(response.data["number"].data());
updateResponse(br, response.data);
bp.setResponse(br);
bp.notifyBreakpointInsertOk();
}
}
void GdbEngine::handleInterpreterBreakpointModified(const GdbMi &data)
{
BreakpointModelId id(data["modelid"].data());
Breakpoint bp = breakHandler()->breakpointById(id);
BreakpointResponse br = bp.response();
updateResponse(br, data);
bp.setResponse(br);
}
void GdbEngine::handleWatchInsert(const DebuggerResponse &response, Breakpoint bp)
{
if (bp && response.resultClass == ResultDone) {
BreakpointResponse br = bp.response();
// "Hardware watchpoint 2: *0xbfffed40\n"
QString ba = response.consoleStreamOutput;
GdbMi wpt = response.data["wpt"];
if (wpt.isValid()) {
// Mac yields:
//>32^done,wpt={number="4",exp="*4355182176"}
br.id = BreakpointResponseId(wpt["number"].data());
QString exp = wpt["exp"].data();
if (exp.startsWith('*'))
br.address = exp.mid(1).toULongLong(0, 0);
bp.setResponse(br);
QTC_CHECK(!bp.needsChange());
bp.notifyBreakpointInsertOk();
} else if (ba.startsWith("Hardware watchpoint ")
|| ba.startsWith("Watchpoint ")) {
// Non-Mac: "Hardware watchpoint 2: *0xbfffed40\n"
const int end = ba.indexOf(':');
const int begin = ba.lastIndexOf(' ', end) + 1;
const QString address = ba.mid(end + 2).trimmed();
br.id = BreakpointResponseId(ba.mid(begin, end - begin));
if (address.startsWith('*'))
br.address = address.mid(1).toULongLong(0, 0);
bp.setResponse(br);
QTC_CHECK(!bp.needsChange());
bp.notifyBreakpointInsertOk();
} else {
showMessage("CANNOT PARSE WATCHPOINT FROM " + ba);
}
}
}
void GdbEngine::handleCatchInsert(const DebuggerResponse &response, Breakpoint bp)
{
if (bp && response.resultClass == ResultDone)
bp.notifyBreakpointInsertOk();
}
void GdbEngine::handleBkpt(const GdbMi &bkpt, Breakpoint bp)
{
BreakpointResponse br = bp.response();
QTC_ASSERT(bp, return);
const QString nr = bkpt["number"].data();
const BreakpointResponseId rid(nr);
QTC_ASSERT(rid.isValid(), return);
if (nr.contains('.')) {
// A sub-breakpoint.
BreakpointResponse sub;
updateResponse(sub, bkpt);
sub.id = rid;
sub.type = bp.type();
bp.insertSubBreakpoint(sub);
return;
}
// The MI output format might change, see
// http://permalink.gmane.org/gmane.comp.gdb.patches/83936
const GdbMi locations = bkpt["locations"];
if (locations.isValid()) {
foreach (const GdbMi &loc, locations.children()) {
// A sub-breakpoint.
const QString subnr = loc["number"].data();
const BreakpointResponseId subrid(subnr);
BreakpointResponse sub;
updateResponse(sub, loc);
sub.id = subrid;
sub.type = br.type;
bp.insertSubBreakpoint(sub);
}
}
// A (the?) primary breakpoint.
updateResponse(br, bkpt);
br.id = rid;
bp.setResponse(br);
}
void GdbEngine::handleBreakInsert1(const DebuggerResponse &response, Breakpoint bp)
{
if (bp.state() == BreakpointRemoveRequested) {
if (response.resultClass == ResultDone) {
// This delete was deferred. Act now.
const GdbMi mainbkpt = response.data["bkpt"];
bp.notifyBreakpointRemoveProceeding();
DebuggerCommand cmd("-break-delete " + mainbkpt["number"].data());
cmd.flags = NeedsTemporaryStop | RebuildBreakpointModel;
runCommand(cmd);
bp.notifyBreakpointRemoveOk();
return;
}
}
if (response.resultClass == ResultDone) {
// The result is a list with the first entry marked "bkpt"
// and "unmarked" rest. The "bkpt" one seems to always be
// the "main" entry. Use the "main" entry to retrieve the
// already known data from the BreakpointManager, and then
// iterate over all items to update main- and sub-data.
foreach (const GdbMi &bkpt, response.data.children())
handleBkpt(bkpt, bp);
if (bp.needsChange()) {
bp.notifyBreakpointChangeAfterInsertNeeded();
changeBreakpoint(bp);
} else {
bp.notifyBreakpointInsertOk();
}
} else if (response.data["msg"].data().contains("Unknown option")) {
// Older version of gdb don't know the -a option to set tracepoints
// ^error,msg="mi_cmd_break_insert: Unknown option ``a''"
const QString fileName = bp.fileName();
const int lineNumber = bp.lineNumber();
DebuggerCommand cmd("trace \"" + GdbMi::escapeCString(fileName) + "\":"
+ QString::number(lineNumber),
NeedsTemporaryStop | RebuildBreakpointModel);
runCommand(cmd);
} else {
// Some versions of gdb like "GNU gdb (GDB) SUSE (6.8.91.20090930-2.4)"
// know how to do pending breakpoints using CLI but not MI. So try
// again with MI.
DebuggerCommand cmd("break " + breakpointLocation2(bp.parameters()),
NeedsTemporaryStop | RebuildBreakpointModel);
cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakInsert2(r, bp); };
runCommand(cmd);
}
}
void GdbEngine::handleBreakInsert2(const DebuggerResponse &response, Breakpoint bp)
{
if (response.resultClass == ResultDone) {
QTC_ASSERT(bp, return);
bp.notifyBreakpointInsertOk();
} else {
// Note: gdb < 60800 doesn't "do" pending breakpoints.
// Not much we can do about it except implementing the
// logic on top of shared library events, and that's not
// worth the effort.
}
}
void GdbEngine::handleBreakDisable(const DebuggerResponse &response, Breakpoint bp)
{
QTC_CHECK(response.resultClass == ResultDone);
// This should only be the requested state.
QTC_ASSERT(!bp.isEnabled(), /* Prevent later recursion */);
BreakpointResponse br = bp.response();
br.enabled = false;
bp.setResponse(br);
changeBreakpoint(bp); // Maybe there's more to do.
}
void GdbEngine::handleBreakEnable(const DebuggerResponse &response, Breakpoint bp)
{
QTC_CHECK(response.resultClass == ResultDone);
// This should only be the requested state.
QTC_ASSERT(bp.isEnabled(), /* Prevent later recursion */);
BreakpointResponse br = bp.response();
br.enabled = true;
bp.setResponse(br);
changeBreakpoint(bp); // Maybe there's more to do.
}
void GdbEngine::handleBreakThreadSpec(const DebuggerResponse &response, Breakpoint bp)
{
QTC_CHECK(response.resultClass == ResultDone);
BreakpointResponse br = bp.response();
br.threadSpec = bp.threadSpec();
bp.setResponse(br);
bp.notifyBreakpointNeedsReinsertion();
insertBreakpoint(bp);
}
void GdbEngine::handleBreakLineNumber(const DebuggerResponse &response, Breakpoint bp)
{
QTC_CHECK(response.resultClass == ResultDone);
BreakpointResponse br = bp.response();
br.lineNumber = bp.lineNumber();
bp.setResponse(br);
bp.notifyBreakpointNeedsReinsertion();
insertBreakpoint(bp);
}
void GdbEngine::handleBreakIgnore(const DebuggerResponse &response, Breakpoint bp)
{
// gdb 6.8:
// ignore 2 0:
// ~"Will stop next time breakpoint 2 is reached.\n"
// 28^done
// ignore 2 12:
// &"ignore 2 12\n"
// ~"Will ignore next 12 crossings of breakpoint 2.\n"
// 29^done
//
// gdb 6.3 does not produce any console output
QTC_CHECK(response.resultClass == ResultDone);
//QString msg = _(response.consoleStreamOutput);
BreakpointResponse br = bp.response();
//if (msg.contains(__("Will stop next time breakpoint")))
// response.ignoreCount = _("0");
//else if (msg.contains(__("Will ignore next")))
// response.ignoreCount = data->ignoreCount;
// FIXME: this assumes it is doing the right thing...
const BreakpointParameters &parameters = bp.parameters();
br.ignoreCount = parameters.ignoreCount;
br.command = parameters.command;
bp.setResponse(br);
changeBreakpoint(bp); // Maybe there's more to do.
}
void GdbEngine::handleBreakCondition(const DebuggerResponse &, Breakpoint bp)
{
// Can happen at invalid condition strings.
//QTC_CHECK(response.resultClass == ResultDone)
// We just assume it was successful. Otherwise we had to parse
// the output stream data.
// The following happens on Mac:
// QByteArray msg = response.data.findChild("msg").data();
// if (msg.startsWith("Error parsing breakpoint condition. "
// " Will try again when we hit the breakpoint."))
BreakpointResponse br = bp.response();
br.condition = bp.condition();
bp.setResponse(br);
changeBreakpoint(bp); // Maybe there's more to do.
}
bool GdbEngine::stateAcceptsBreakpointChanges() const
{
switch (state()) {
case InferiorSetupRequested:
case InferiorRunRequested:
case InferiorRunOk:
case InferiorStopRequested:
case InferiorStopOk:
return true;
default:
return false;
}
}
bool GdbEngine::acceptsBreakpoint(Breakpoint bp) const
{
if (runParameters().startMode == AttachCore)
return false;
if (bp.parameters().isCppBreakpoint())
return true;
return isNativeMixedEnabled();
}
void GdbEngine::insertBreakpoint(Breakpoint bp)
{
// Set up fallback in case of pending breakpoints which aren't handled
// by the MI interface.
QTC_CHECK(bp.state() == BreakpointInsertRequested);
bp.notifyBreakpointInsertProceeding();
const BreakpointParameters &data = bp.parameters();
if (!data.isCppBreakpoint()) {
DebuggerCommand cmd("insertInterpreterBreakpoint", NeedsTemporaryStop);
bp.addToCommand(&cmd);
cmd.callback = [this, bp](const DebuggerResponse &r) { handleInsertInterpreterBreakpoint(r, bp); };
runCommand(cmd);
return;
}
const auto handleWatch = [this, bp](const DebuggerResponse &r) { handleWatchInsert(r, bp); };
const auto handleCatch = [this, bp](const DebuggerResponse &r) { handleCatchInsert(r, bp); };
BreakpointType type = bp.type();
DebuggerCommand cmd;
if (type == WatchpointAtAddress) {
cmd.function = "watch " + addressSpec(bp.address());
cmd.callback = handleWatch;
} else if (type == WatchpointAtExpression) {
cmd.function = "watch " + bp.expression();
cmd.callback = handleWatch;
} else if (type == BreakpointAtFork) {
cmd.function = "catch fork";
cmd.callback = handleCatch;
cmd.flags = NeedsTemporaryStop | RebuildBreakpointModel;
runCommand(cmd);
// Another one...
cmd.function = "catch vfork";
} else if (type == BreakpointAtExec) {
cmd.function = "catch exec";
cmd.callback = handleCatch;
} else if (type == BreakpointAtSysCall) {
cmd.function = "catch syscall";
cmd.callback = handleCatch;
} else {
if (bp.isTracepoint()) {
cmd.function = "-break-insert -a -f ";
} else {
int spec = bp.threadSpec();
cmd.function = "-break-insert ";
if (spec >= 0)
cmd.function += "-p " + QString::number(spec);
cmd.function += " -f ";
}
if (bp.isOneShot())
cmd.function += "-t ";
if (!bp.isEnabled())
cmd.function += "-d ";
if (int ignoreCount = bp.ignoreCount())
cmd.function += "-i " + QString::number(ignoreCount) + ' ';
QString condition = bp.condition();
if (!condition.isEmpty())
cmd.function += " -c \"" + condition.replace('"', "\\\"") + "\" ";
cmd.function += breakpointLocation(bp.parameters());
cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakInsert1(r, bp); };
}
cmd.flags = NeedsTemporaryStop | RebuildBreakpointModel;
runCommand(cmd);
}
void GdbEngine::changeBreakpoint(Breakpoint bp)
{
const BreakpointParameters &data = bp.parameters();
QTC_ASSERT(data.type != UnknownBreakpointType, return);
const BreakpointResponse &response = bp.response();
QTC_ASSERT(response.id.isValid(), return);
const QString bpnr = response.id.toString();
const BreakpointState state = bp.state();
if (state == BreakpointChangeRequested)
bp.notifyBreakpointChangeProceeding();
const BreakpointState state2 = bp.state();
QTC_ASSERT(state2 == BreakpointChangeProceeding, qDebug() << state2);
DebuggerCommand cmd;
if (!response.pending && data.threadSpec != response.threadSpec) {
// The only way to change this seems to be to re-set the bp completely.
cmd.function = "-break-delete " + bpnr;
cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakThreadSpec(r, bp); };
} else if (!response.pending && data.lineNumber != response.lineNumber) {
// The only way to change this seems to be to re-set the bp completely.
cmd.function = "-break-delete " + bpnr;
cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakLineNumber(r, bp); };
} else if (data.command != response.command) {
cmd.function = "-break-commands " + bpnr;
foreach (const QString &command, data.command.split(QLatin1String("\n"))) {
if (!command.isEmpty())
cmd.function += " \"" + command + '"';
}
cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakIgnore(r, bp); };
} else if (!data.conditionsMatch(response.condition)) {
cmd.function = "condition " + bpnr + ' ' + data.condition;
cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakCondition(r, bp); };
} else if (data.ignoreCount != response.ignoreCount) {
cmd.function = "ignore " + bpnr + ' ' + QString::number(data.ignoreCount);
cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakIgnore(r, bp); };
} else if (!data.enabled && response.enabled) {
cmd.function = "-break-disable " + bpnr;
cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakDisable(r, bp); };
} else if (data.enabled && !response.enabled) {
cmd.function = "-break-enable " + bpnr;
cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakEnable(r, bp); };
} else {
bp.notifyBreakpointChangeOk();
return;
}
cmd.flags = NeedsTemporaryStop | RebuildBreakpointModel;
runCommand(cmd);
}
void GdbEngine::removeBreakpoint(Breakpoint bp)
{
QTC_CHECK(bp.state() == BreakpointRemoveRequested);
BreakpointResponse br = bp.response();
const BreakpointParameters &data = bp.parameters();
if (!data.isCppBreakpoint()) {
DebuggerCommand cmd("removeInterpreterBreakpoint");
bp.addToCommand(&cmd);
runCommand(cmd);
bp.notifyBreakpointRemoveOk();
return;
}
if (br.id.isValid()) {
// We already have a fully inserted breakpoint.
bp.notifyBreakpointRemoveProceeding();
showMessage(QString("DELETING BP %1 IN %2").arg(br.id.toString()).arg(bp.fileName()));
DebuggerCommand cmd("-break-delete " + br.id.toString(), NeedsTemporaryStop | RebuildBreakpointModel);
runCommand(cmd);
// Pretend it succeeds without waiting for response. Feels better.
// Otherwise, clicking in the gutter leaves the breakpoint visible
// for quite some time, so the user assumes a mis-click and clicks
// again, effectivly re-introducing the breakpoint.
bp.notifyBreakpointRemoveOk();
} else {
// Breakpoint was scheduled to be inserted, but we haven't had
// an answer so far. Postpone activity by doing nothing.
}
}
//////////////////////////////////////////////////////////////////////
//
// Modules specific stuff
//
//////////////////////////////////////////////////////////////////////
static QString dotEscape(QString str)
{
str.replace(' ', '.');
str.replace('\\', '.');
str.replace('/', '.');
return str;
}
void GdbEngine::loadSymbols(const QString &modulePath)
{
// FIXME: gdb does not understand quoted names here (tested with 6.8)
runCommand({"sharedlibrary " + dotEscape(modulePath)});
reloadModulesInternal();
reloadStack();
updateLocals();
}
void GdbEngine::loadAllSymbols()
{
runCommand({"sharedlibrary .*"});
reloadModulesInternal();
reloadStack();
updateLocals();
}
void GdbEngine::loadSymbolsForStack()
{
bool needUpdate = false;
const Modules &modules = modulesHandler()->modules();
foreach (const StackFrame &frame, stackHandler()->frames()) {
if (frame.function == "??") {
//qDebug() << "LOAD FOR " << frame.address;
foreach (const Module &module, modules) {
if (module.startAddress <= frame.address
&& frame.address < module.endAddress) {
runCommand({"sharedlibrary " + dotEscape(module.modulePath)});
needUpdate = true;
}
}
}
}
if (needUpdate) {
//reloadModulesInternal();
reloadStack();
updateLocals();
}
}
static void handleShowModuleSymbols(const DebuggerResponse &response,
const QString &modulePath, const QString &fileName)
{
if (response.resultClass == ResultDone) {
Symbols symbols;
QFile file(fileName);
file.open(QIODevice::ReadOnly);
// Object file /opt/dev/qt/lib/libQtNetworkMyns.so.4:
// [ 0] A 0x16bd64 _DYNAMIC moc_qudpsocket.cpp
// [12] S 0xe94680 _ZN4myns5QFileC1Ev section .plt myns::QFile::QFile()
foreach (const QString &line, QString::fromLocal8Bit(file.readAll()).split('\n')) {
if (line.isEmpty())
continue;
if (line.at(0) != '[')
continue;
int posCode = line.indexOf(']') + 2;
int posAddress = line.indexOf("0x", posCode);
if (posAddress == -1)
continue;
int posName = line.indexOf(" ", posAddress);
int lenAddress = posName - posAddress;
int posSection = line.indexOf(" section ");
int lenName = 0;
int lenSection = 0;
int posDemangled = 0;
if (posSection == -1) {
lenName = line.size() - posName;
posDemangled = posName;
} else {
lenName = posSection - posName;
posSection += 10;
posDemangled = line.indexOf(' ', posSection + 1);
if (posDemangled == -1) {
lenSection = line.size() - posSection;
} else {
lenSection = posDemangled - posSection;
posDemangled += 1;
}
}
int lenDemangled = 0;
if (posDemangled != -1)
lenDemangled = line.size() - posDemangled;
Symbol symbol;
symbol.state = line.mid(posCode, 1);
symbol.address = line.mid(posAddress, lenAddress);
symbol.name = line.mid(posName, lenName);
symbol.section = line.mid(posSection, lenSection);
symbol.demangled = line.mid(posDemangled, lenDemangled);
symbols.push_back(symbol);
}
file.close();
file.remove();
Internal::showModuleSymbols(modulePath, symbols);
} else {
AsynchronousMessageBox::critical(GdbEngine::tr("Cannot Read Symbols"),
GdbEngine::tr("Cannot read symbols for module \"%1\".").arg(fileName));
}
}
void GdbEngine::requestModuleSymbols(const QString &modulePath)
{
Utils::TemporaryFile tf("gdbsymbols");
if (!tf.open())
return;
QString fileName = tf.fileName();
tf.close();
DebuggerCommand cmd("maint print msymbols \"" + fileName + "\" " + modulePath, NeedsTemporaryStop);
cmd.callback = [modulePath, fileName](const DebuggerResponse &r) {
handleShowModuleSymbols(r, modulePath, fileName);
};
runCommand(cmd);
}
void GdbEngine::requestModuleSections(const QString &moduleName)
{
// There seems to be no way to get the symbols from a single .so.
DebuggerCommand cmd("maint info section ALLOBJ", NeedsTemporaryStop);
cmd.callback = [this, moduleName](const DebuggerResponse &r) {
handleShowModuleSections(r, moduleName);
};
runCommand(cmd);
}
void GdbEngine::handleShowModuleSections(const DebuggerResponse &response,
const QString &moduleName)
{
// ~" Object file: /usr/lib/i386-linux-gnu/libffi.so.6\n"
// ~" 0xb44a6114->0xb44a6138 at 0x00000114: .note.gnu.build-id ALLOC LOAD READONLY DATA HAS_CONTENTS\n"
if (response.resultClass == ResultDone) {
const QStringList lines = response.consoleStreamOutput.split('\n');
const QString prefix = QLatin1String(" Object file: ");
const QString needle = prefix + moduleName;
Sections sections;
bool active = false;
foreach (const QString &line, lines) {
if (line.startsWith(prefix)) {
if (active)
break;
if (line == needle)
active = true;
} else {
if (active) {
QStringList items = line.split(' ', QString::SkipEmptyParts);
QString fromTo = items.value(0, QString());
const int pos = fromTo.indexOf('-');
QTC_ASSERT(pos >= 0, continue);
Section section;
section.from = fromTo.left(pos);
section.to = fromTo.mid(pos + 2);
section.address = items.value(2, QString());
section.name = items.value(3, QString());
section.flags = items.value(4, QString());
sections.append(section);
}
}
}
if (!sections.isEmpty())
Internal::showModuleSections(moduleName, sections);
}
}
void GdbEngine::reloadModules()
{
if (state() == InferiorRunOk || state() == InferiorStopOk)
reloadModulesInternal();
}
void GdbEngine::reloadModulesInternal()
{
runCommand({"info shared", NeedsTemporaryStop, CB(handleModulesList)});
}
static QString nameFromPath(const QString &path)
{
return QFileInfo(path).baseName();
}
void GdbEngine::handleModulesList(const DebuggerResponse &response)
{
if (response.resultClass == ResultDone) {
ModulesHandler *handler = modulesHandler();
Module module;
// That's console-based output, likely Linux or Windows,
// but we can avoid the target dependency here.
QString data = response.consoleStreamOutput;
QTextStream ts(&data, QIODevice::ReadOnly);
bool found = false;
while (!ts.atEnd()) {
QString line = ts.readLine();
QString symbolsRead;
QTextStream ts(&line, QIODevice::ReadOnly);
if (line.startsWith("0x")) {
ts >> module.startAddress >> module.endAddress >> symbolsRead;
module.modulePath = ts.readLine().trimmed();
module.moduleName = nameFromPath(module.modulePath);
module.symbolsRead =
(symbolsRead == "Yes" ? Module::ReadOk : Module::ReadFailed);
handler->updateModule(module);
found = true;
} else if (line.trimmed().startsWith("No")) {
// gdb 6.4 symbianelf
ts >> symbolsRead;
QTC_ASSERT(symbolsRead == "No", continue);
module.startAddress = 0;
module.endAddress = 0;
module.modulePath = ts.readLine().trimmed();
module.moduleName = nameFromPath(module.modulePath);
handler->updateModule(module);
found = true;
}
}
if (!found) {
// Mac has^done,shlib-info={num="1",name="dyld",kind="-",
// dyld-addr="0x8fe00000",reason="dyld",requested-state="Y",
// state="Y",path="/usr/lib/dyld",description="/usr/lib/dyld",
// loaded_addr="0x8fe00000",slide="0x0",prefix="__dyld_"},
// shlib-info={...}...
foreach (const GdbMi &item, response.data.children()) {
module.modulePath = item["path"].data();
module.moduleName = nameFromPath(module.modulePath);
module.symbolsRead = (item["state"].data() == "Y")
? Module::ReadOk : Module::ReadFailed;
module.startAddress =
item["loaded_addr"].data().toULongLong(0, 0);
module.endAddress = 0; // FIXME: End address not easily available.
handler->updateModule(module);
}
}
}
}
void GdbEngine::examineModules()
{
ModulesHandler *handler = modulesHandler();
foreach (const Module &module, handler->modules()) {
if (module.elfData.symbolsType == UnknownSymbols)
handler->updateModule(module);
}
}
//////////////////////////////////////////////////////////////////////
//
// Source files specific stuff
//
//////////////////////////////////////////////////////////////////////
void GdbEngine::reloadSourceFiles()
{
if ((state() == InferiorRunOk || state() == InferiorStopOk) && !m_sourcesListUpdating) {
m_sourcesListUpdating = true;
DebuggerCommand cmd("-file-list-exec-source-files", NeedsTemporaryStop);
cmd.callback = [this](const DebuggerResponse &response) {
m_sourcesListUpdating = false;
if (response.resultClass == ResultDone) {
QMap<QString, QString> oldShortToFull = m_shortToFullName;
m_shortToFullName.clear();
m_fullToShortName.clear();
// "^done,files=[{file="../../../../bin/dumper/dumper.cpp",
// fullname="/data5/dev/ide/main/bin/dumper/dumper.cpp"},
GdbMi files = response.data["files"];
foreach (const GdbMi &item, files.children()) {
GdbMi fileName = item["file"];
if (fileName.data().endsWith("<built-in>"))
continue;
GdbMi fullName = item["fullname"];
QString file = fileName.data();
QString full;
if (fullName.isValid()) {
full = cleanupFullName(fullName.data());
m_fullToShortName[full] = file;
}
m_shortToFullName[file] = full;
}
if (m_shortToFullName != oldShortToFull)
sourceFilesHandler()->setSourceFiles(m_shortToFullName);
}
};
runCommand(cmd);
}
}
//////////////////////////////////////////////////////////////////////
//
// Stack specific stuff
//
//////////////////////////////////////////////////////////////////////
void GdbEngine::selectThread(ThreadId threadId)
{
threadsHandler()->setCurrentThread(threadId);
showStatusMessage(tr("Retrieving data for stack view thread 0x%1...")
.arg(threadId.raw(), 0, 16), 10000);
DebuggerCommand cmd("-thread-select " + QString::number(threadId.raw()), Discardable);
cmd.callback = [this](const DebuggerResponse &) {
QTC_CHECK(state() == InferiorUnrunnable || state() == InferiorStopOk);
showStatusMessage(tr("Retrieving data for stack view..."), 3000);
reloadStack(); // Will reload registers.
updateLocals();
};
runCommand(cmd);
}
void GdbEngine::reloadFullStack()
{
PENDING_DEBUG("RELOAD FULL STACK");
resetLocation();
DebuggerCommand cmd = stackCommand(-1);
cmd.callback = [this](const DebuggerResponse &r) { handleStackListFrames(r, true); };
cmd.flags = Discardable;
runCommand(cmd);
}
void GdbEngine::loadAdditionalQmlStack()
{
DebuggerCommand cmd = stackCommand(-1);
cmd.arg("extraqml", "1");
cmd.callback = [this](const DebuggerResponse &r) { handleStackListFrames(r, true); };
cmd.flags = Discardable;
runCommand(cmd);
}
DebuggerCommand GdbEngine::stackCommand(int depth)
{
DebuggerCommand cmd("fetchStack");
cmd.arg("limit", depth);
cmd.arg("nativemixed", isNativeMixedActive());
return cmd;
}
void GdbEngine::reloadStack()
{
PENDING_DEBUG("RELOAD STACK");
DebuggerCommand cmd = stackCommand(action(MaximalStackDepth)->value().toInt());
cmd.callback = [this](const DebuggerResponse &r) { handleStackListFrames(r, false); };
cmd.flags = Discardable;
runCommand(cmd);
}
void GdbEngine::handleStackListFrames(const DebuggerResponse &response, bool isFull)
{
if (response.resultClass != ResultDone) {
// That always happens on symbian gdb with
// ^error,data={msg="Previous frame identical to this frame (corrupt stack?)"
// logStreamOutput: "Previous frame identical to this frame (corrupt stack?)\n"
//qDebug() << "LISTING STACK FAILED: " << response.toString();
reloadRegisters();
return;
}
GdbMi stack = response.data["stack"]; // C++
//if (!frames.isValid() || frames.childCount() == 0) // Mixed.
GdbMi frames = stack["frames"];
if (!frames.isValid())
isFull = true;
stackHandler()->setFramesAndCurrentIndex(frames, isFull);
activateFrame(stackHandler()->currentIndex());
}
void GdbEngine::activateFrame(int frameIndex)
{
if (state() != InferiorStopOk && state() != InferiorUnrunnable)
return;
StackHandler *handler = stackHandler();
if (frameIndex == handler->stackSize()) {
reloadFullStack();
return;
}
QTC_ASSERT(frameIndex < handler->stackSize(), return);
handler->setCurrentIndex(frameIndex);
gotoLocation(stackHandler()->currentFrame());
if (handler->frameAt(frameIndex).language != QmlLanguage) {
// Assuming the command always succeeds this saves a roundtrip.
// Otherwise the lines below would need to get triggered
// after a response to this -stack-select-frame here.
//if (!m_currentThread.isEmpty())
// cmd += " --thread " + m_currentThread;
runCommand({"-stack-select-frame " + QString::number(frameIndex), Discardable});
}
updateLocals();
reloadRegisters();
}
void GdbEngine::handleThreadInfo(const DebuggerResponse &response)
{
if (response.resultClass == ResultDone) {
ThreadsHandler *handler = threadsHandler();
handler->updateThreads(response.data);
// This is necessary as the current thread might not be in the list.
if (!handler->currentThread().isValid()) {
ThreadId other = handler->threadAt(0);
if (other.isValid())
selectThread(other);
}
updateViews(); // Adjust Threads combobox.
if (boolSetting(ShowThreadNames)) {
runCommand({"threadnames " + action(MaximalStackDepth)->value().toString(),
Discardable, CB(handleThreadNames)});
}
reloadStack(); // Will trigger register reload.
} else {
// Fall back for older versions: Try to get at least a list
// of running threads.
runCommand({"-thread-list-ids", Discardable, CB(handleThreadListIds)});
}
}
void GdbEngine::handleThreadListIds(const DebuggerResponse &response)
{
// "72^done,{thread-ids={thread-id="2",thread-id="1"},number-of-threads="2"}
// In gdb 7.1+ additionally: current-thread-id="1"
ThreadsHandler *handler = threadsHandler();
const QVector<GdbMi> &items = response.data["thread-ids"].children();
for (int index = 0, n = items.size(); index != n; ++index) {
ThreadData thread;
thread.id = ThreadId(items.at(index).toInt());
handler->updateThread(thread);
}
reloadStack(); // Will trigger register reload.
}
void GdbEngine::handleThreadNames(const DebuggerResponse &response)
{
if (response.resultClass == ResultDone) {
ThreadsHandler *handler = threadsHandler();
GdbMi names;
names.fromString(response.consoleStreamOutput);
foreach (const GdbMi &name, names.children()) {
ThreadData thread;
thread.id = ThreadId(name["id"].toInt());
thread.name = decodeData(name["value"].data(), name["valueencoded"].data());
handler->updateThread(thread);
}
updateViews();
}
}
//////////////////////////////////////////////////////////////////////
//
// Snapshot specific stuff
//
//////////////////////////////////////////////////////////////////////
void GdbEngine::createSnapshot()
{
QString fileName;
Utils::TemporaryFile tf("gdbsnapshot");
if (tf.open()) {
fileName = tf.fileName();
tf.close();
// This must not be quoted, it doesn't work otherwise.
DebuggerCommand cmd("gcore " + fileName, NeedsTemporaryStop | ConsoleCommand);
cmd.callback = [this, fileName](const DebuggerResponse &r) { handleMakeSnapshot(r, fileName); };
runCommand(cmd);
} else {
AsynchronousMessageBox::critical(tr("Snapshot Creation Error"),
tr("Cannot create snapshot file."));
}
}
void GdbEngine::handleMakeSnapshot(const DebuggerResponse &response, const QString &coreFile)
{
if (response.resultClass == ResultDone) {
DebuggerRunParameters rp = runParameters();
rp.startMode = AttachCore;
rp.coreFile = coreFile;
//snapshot.setDate(QDateTime::currentDateTime());
StackFrames frames = stackHandler()->frames();
QString function = "<unknown>";
if (!frames.isEmpty()) {
const StackFrame &frame = frames.at(0);
function = frame.function + ":" + QString::number(frame.line);
}
rp.displayName = function + ": " + QDateTime::currentDateTime().toString();
rp.isSnapshot = true;
auto rc = new RunControl(runControl()->runConfiguration(), ProjectExplorer::Constants::DEBUG_RUN_MODE);
(void) new DebuggerRunTool(rc, rp);
ProjectExplorerPlugin::startRunControl(rc);
} else {
QString msg = response.data["msg"].data();
AsynchronousMessageBox::critical(tr("Snapshot Creation Error"),
tr("Cannot create snapshot:") + '\n' + msg);
}
}
//////////////////////////////////////////////////////////////////////
//
// Register specific stuff
//
//////////////////////////////////////////////////////////////////////
void GdbEngine::reloadRegisters()
{
if (!Internal::isRegistersWindowVisible())
return;
if (state() != InferiorStopOk && state() != InferiorUnrunnable)
return;
if (true) {
if (!m_registerNamesListed) {
// The MI version does not give register size.
// runCommand("-data-list-register-names", CB(handleRegisterListNames));
runCommand({"maintenance print raw-registers",
CB(handleRegisterListing)});
m_registerNamesListed = true;
}
// Can cause i386-linux-nat.c:571: internal-error: Got request
// for bad register number 41.\nA problem internal to GDB has been detected.
runCommand({"-data-list-register-values r", Discardable,
CB(handleRegisterListValues)});
} else {
runCommand({"maintenance print cooked-registers",
CB(handleMaintPrintRegisters)});
}
}
static QString readWord(const QString &ba, int *pos)
{
const int n = ba.size();
while (*pos < n && ba.at(*pos) == ' ')
++*pos;
const int start = *pos;
while (*pos < n && ba.at(*pos) != ' ' && ba.at(*pos) != '\n')
++*pos;
return ba.mid(start, *pos - start);
}
void GdbEngine::handleMaintPrintRegisters(const DebuggerResponse &response)
{
if (response.resultClass != ResultDone)
return;
const QString &ba = response.consoleStreamOutput;
RegisterHandler *handler = registerHandler();
//0 1 2 3 4 5 6
//0123456789012345678901234567890123456789012345678901234567890
// Name Nr Rel Offset Size Type Raw value
// rax 0 0 0 8 int64_t 0x0000000000000000
// rip 16 16 128 8 *1 0x0000000000400dc9
// eflags 17 17 136 4 i386_eflags 0x00000246
// cs 18 18 140 4 int32_t 0x00000033
// xmm15 55 55 516 16 vec128 0x00000000000000000000000000000000
// mxcsr 56 56 532 4 i386_mxcsr 0x00001fa0
// ''
// st6 30 30 224 10 _i387_ext 0x00000000000000000000
// st7 31 31 234 10 _i387_ext 0x00000000000000000000
// fctrl 32 32 244 4 int 0x0000037f
const int n = ba.size();
int pos = 0;
while (true) {
// Skip first line, and until '\n' after each line finished.
while (pos < n && ba.at(pos) != '\n')
++pos;
if (pos >= n)
break;
++pos; // skip \n
Register reg;
reg.name = readWord(ba, &pos);
if (reg.name == "''" || reg.name == "*1:" || reg.name.isEmpty())
continue;
readWord(ba, &pos); // Nr
readWord(ba, &pos); // Rel
readWord(ba, &pos); // Offset
reg.size = readWord(ba, &pos).toInt();
reg.reportedType = readWord(ba, &pos);
reg.value.fromString(readWord(ba, &pos), HexadecimalFormat);
handler->updateRegister(reg);
}
handler->commitUpdates();
}
void GdbEngine::setRegisterValue(const QString &name, const QString &value)
{
QString fullName = name;
if (name.startsWith("xmm"))
fullName += ".uint128";
runCommand({"set $" + fullName + "=" + value});
reloadRegisters();
}
void GdbEngine::handleRegisterListNames(const DebuggerResponse &response)
{
if (response.resultClass != ResultDone) {
m_registerNamesListed = false;
return;
}
GdbMi names = response.data["register-names"];
m_registers.clear();
int gdbRegisterNumber = 0;
foreach (const GdbMi &item, names.children()) {
if (!item.data().isEmpty()) {
Register reg;
reg.name = item.data();
m_registers[gdbRegisterNumber] = reg;
}
++gdbRegisterNumber;
}
}
void GdbEngine::handleRegisterListing(const DebuggerResponse &response)
{
if (response.resultClass != ResultDone) {
m_registerNamesListed = false;
return;
}
// &"maintenance print raw-registers\n"
// >~" Name Nr Rel Offset Size Type Raw value\n"
// >~" rax 0 0 0 8 int64_t 0x0000000000000005\n"
// >~" rip 16 16 128 8 *1 0x000000000040232a\n"
// >~" '' 145 145 536 0 int0_t <invalid>\n"
m_registers.clear();
QStringList lines = response.consoleStreamOutput.split('\n');
for (int i = 1; i < lines.size(); ++i) {
const QVector<QStringRef> parts = lines.at(i).splitRef(' ', QString::SkipEmptyParts);
if (parts.size() < 7)
continue;
int gdbRegisterNumber = parts.at(1).toInt();
Register reg;
reg.name = parts.at(0).toString();
reg.size = parts.at(4).toInt();
reg.reportedType = parts.at(5).toString();
m_registers[gdbRegisterNumber] = reg;
}
}
void GdbEngine::handleRegisterListValues(const DebuggerResponse &response)
{
if (response.resultClass != ResultDone)
return;
RegisterHandler *handler = registerHandler();
// 24^done,register-values=[{number="0",value="0xf423f"},...]
const GdbMi values = response.data["register-values"];
foreach (const GdbMi &item, values.children()) {
const int number = item["number"].toInt();
Register reg = m_registers[number];
QString data = item["value"].data();
if (data.startsWith("0x")) {
reg.value.fromString(data, HexadecimalFormat);
} else if (data == "<error reading variable>") {
// Nothing. See QTCREATORBUG-14029.
} else {
// This is what GDB considers machine readable output:
// value="{v4_float = {0x00000000, 0x00000000, 0x00000000, 0x00000000},
// v2_double = {0x0000000000000000, 0x0000000000000000},
// v16_int8 = {0x00 <repeats 16 times>},
// v8_int16 = {0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
// v4_int32 = {0x00000000, 0x00000000, 0x00000000, 0x00000000},
// v2_int64 = {0x0000000000000000, 0x0000000000000000},
// uint128 = <error reading variable>}"}
// Try to make sense of it using the int32 chunks.
// Android gdb 7.10 has u32 = {0x00000000, 0x40340000}.
// Use that if available.
QString result;
int pos1 = data.indexOf("_int32");
if (pos1 == -1)
pos1 = data.indexOf("u32");
const int pos2 = data.indexOf('{', pos1) + 1;
const int pos3 = data.indexOf('}', pos2);
QString inner = data.mid(pos2, pos3 - pos2);
QStringList list = inner.split(',');
for (int i = list.size(); --i >= 0; ) {
QString chunk = list.at(i);
if (chunk.startsWith(' '))
chunk.remove(0, 1);
if (chunk.startsWith("0x"))
chunk.remove(0, 2);
QTC_ASSERT(chunk.size() == 8, continue);
result.append(chunk);
}
reg.value.fromString(result, HexadecimalFormat);
}
handler->updateRegister(reg);
}
handler->commitUpdates();
}
//////////////////////////////////////////////////////////////////////
//
// Watch specific stuff
//
//////////////////////////////////////////////////////////////////////
void GdbEngine::reloadLocals()
{
setTokenBarrier();
updateLocals();
}
void GdbEngine::handleVarAssign(const DebuggerResponse &)
{
// Everything might have changed, force re-evaluation.
setTokenBarrier();
updateLocals();
}
void GdbEngine::assignValueInDebugger(WatchItem *item,
const QString &expression, const QVariant &value)
{
DebuggerCommand cmd("assignValue");
cmd.arg("type", toHex(item->type));
cmd.arg("expr", toHex(expression));
cmd.arg("value", toHex(value.toString()));
cmd.arg("simpleType", isIntOrFloatType(item->type));
cmd.callback = CB(handleVarAssign);
runCommand(cmd);
}
class MemoryAgentCookie
{
public:
MemoryAgentCookie() {}
QByteArray *accumulator = nullptr; // Shared between split request. Last one cleans up.
uint *pendingRequests = nullptr; // Shared between split request. Last one cleans up.
QPointer<MemoryAgent> agent;
quint64 base = 0; // base address.
uint offset = 0; // offset to base, and in accumulator
uint length = 0; //
};
void GdbEngine::changeMemory(MemoryAgent *agent, quint64 addr, const QByteArray &data)
{
Q_UNUSED(agent)
DebuggerCommand cmd("-data-write-memory 0x" + QString::number(addr, 16) + " d 1", NeedsTemporaryStop);
foreach (unsigned char c, data)
cmd.function += ' ' + QString::number(uint(c));
cmd.callback = CB(handleVarAssign);
runCommand(cmd);
}
void GdbEngine::fetchMemory(MemoryAgent *agent, quint64 addr, quint64 length)
{
MemoryAgentCookie ac;
ac.accumulator = new QByteArray(length, char());
ac.pendingRequests = new uint(1);
ac.agent = agent;
ac.base = addr;
ac.length = length;
fetchMemoryHelper(ac);
}
void GdbEngine::fetchMemoryHelper(const MemoryAgentCookie &ac)
{
DebuggerCommand cmd("-data-read-memory 0x"
+ QString::number(ac.base + ac.offset, 16) + " x 1 1 "
+ QString::number(ac.length),
NeedsTemporaryStop);
cmd.callback = [this, ac](const DebuggerResponse &r) { handleFetchMemory(r, ac); };
runCommand(cmd);
}
void GdbEngine::handleFetchMemory(const DebuggerResponse &response, MemoryAgentCookie ac)
{
// ^done,addr="0x08910c88",nr-bytes="16",total-bytes="16",
// next-row="0x08910c98",prev-row="0x08910c78",next-page="0x08910c98",
// prev-page="0x08910c78",memory=[{addr="0x08910c88",
// data=["1","0","0","0","5","0","0","0","0","0","0","0","0","0","0","0"]}]
--*ac.pendingRequests;
showMessage(QString("PENDING: %1").arg(*ac.pendingRequests));
QTC_ASSERT(ac.agent, return);
if (response.resultClass == ResultDone) {
GdbMi memory = response.data["memory"];
QTC_ASSERT(memory.children().size() <= 1, return);
if (memory.children().empty())
return;
GdbMi memory0 = memory.children().at(0); // we asked for only one 'row'
GdbMi data = memory0["data"];
for (int i = 0, n = int(data.children().size()); i != n; ++i) {
const GdbMi &child = data.children().at(i);
bool ok = true;
unsigned char c = '?';
c = child.data().toUInt(&ok, 0);
QTC_ASSERT(ok, return);
(*ac.accumulator)[ac.offset + i] = c;
}
} else {
// We have an error
if (ac.length > 1) {
// ... and size > 1, split the load and re-try.
*ac.pendingRequests += 2;
uint hunk = ac.length / 2;
MemoryAgentCookie ac1 = ac;
ac1.length = hunk;
ac1.offset = ac.offset;
MemoryAgentCookie ac2 = ac;
ac2.length = ac.length - hunk;
ac2.offset = ac.offset + hunk;
fetchMemoryHelper(ac1);
fetchMemoryHelper(ac2);
}
}
if (*ac.pendingRequests <= 0) {
ac.agent->addData(ac.base, *ac.accumulator);
delete ac.pendingRequests;
delete ac.accumulator;
}
}
class DisassemblerAgentCookie
{
public:
DisassemblerAgentCookie() : agent(0) {}
DisassemblerAgentCookie(DisassemblerAgent *agent_) : agent(agent_) {}
public:
QPointer<DisassemblerAgent> agent;
};
void GdbEngine::fetchDisassembler(DisassemblerAgent *agent)
{
if (boolSetting(IntelFlavor))
runCommand({"set disassembly-flavor intel"});
else
runCommand({"set disassembly-flavor att"});
fetchDisassemblerByCliPointMixed(agent);
}
static inline QString disassemblerCommand(const Location &location, bool mixed)
{
QString command = "disassemble /r";
if (mixed)
command += 'm';
command += ' ';
if (const quint64 address = location.address()) {
command += "0x";
command += QString::number(address, 16);
} else if (!location.functionName().isEmpty()) {
command += location.functionName();
} else {
QTC_ASSERT(false, return QString(); );
}
return command;
}
void GdbEngine::fetchDisassemblerByCliPointMixed(const DisassemblerAgentCookie &ac)
{
QTC_ASSERT(ac.agent, return);
DebuggerCommand cmd(disassemblerCommand(ac.agent->location(), true), Discardable | ConsoleCommand);
cmd.callback = [this, ac](const DebuggerResponse &response) {
if (response.resultClass == ResultDone)
if (handleCliDisassemblerResult(response.consoleStreamOutput, ac.agent))
return;
// 'point, plain' can take far too long.
// Skip this feature and immediately fall back to the 'range' version:
fetchDisassemblerByCliRangeMixed(ac);
};
runCommand(cmd);
}
void GdbEngine::fetchDisassemblerByCliRangeMixed(const DisassemblerAgentCookie &ac)
{
QTC_ASSERT(ac.agent, return);
const quint64 address = ac.agent->address();
QString start = QString::number(address - 20, 16);
QString end = QString::number(address + 100, 16);
DebuggerCommand cmd("disassemble /rm 0x" + start + ",0x" + end, Discardable | ConsoleCommand);
cmd.callback = [this, ac](const DebuggerResponse &response) {
if (response.resultClass == ResultDone)
if (handleCliDisassemblerResult(response.consoleStreamOutput, ac.agent))
return;
fetchDisassemblerByCliRangePlain(ac);
};
runCommand(cmd);
}
void GdbEngine::fetchDisassemblerByCliRangePlain(const DisassemblerAgentCookie &ac0)
{
DisassemblerAgentCookie ac = ac0;
QTC_ASSERT(ac.agent, return);
const quint64 address = ac.agent->address();
QString start = QString::number(address - 20, 16);
QString end = QString::number(address + 100, 16);
DebuggerCommand cmd("disassemble /r 0x" + start + ",0x" + end, Discardable);
cmd.callback = [this, ac](const DebuggerResponse &response) {
if (response.resultClass == ResultDone)
if (handleCliDisassemblerResult(response.consoleStreamOutput, ac.agent))
return;
// Finally, give up.
//76^error,msg="No function contains program counter for selected..."
//76^error,msg="No function contains specified address."
//>568^error,msg="Line number 0 out of range;
QString msg = response.data["msg"].data();
showStatusMessage(tr("Disassembler failed: %1").arg(msg), 5000);
};
runCommand(cmd);
}
struct LineData
{
LineData() {}
LineData(int i, int f) : index(i), function(f) {}
int index;
int function;
};
bool GdbEngine::handleCliDisassemblerResult(const QString &output, DisassemblerAgent *agent)
{
QTC_ASSERT(agent, return true);
// First line is something like
// "Dump of assembler code from 0xb7ff598f to 0xb7ff5a07:"
DisassemblerLines dlines;
foreach (const QString &line, output.split('\n'))
dlines.appendUnparsed(line);
QVector<DisassemblerLine> lines = dlines.data();
typedef QMap<quint64, LineData> LineMap;
LineMap lineMap;
int currentFunction = -1;
for (int i = 0, n = lines.size(); i != n; ++i) {
const DisassemblerLine &line = lines.at(i);
if (line.address)
lineMap.insert(line.address, LineData(i, currentFunction));
else
currentFunction = i;
}
currentFunction = -1;
DisassemblerLines result;
result.setBytesLength(dlines.bytesLength());
for (LineMap::const_iterator it = lineMap.constBegin(), et = lineMap.constEnd(); it != et; ++it) {
LineData d = *it;
if (d.function != currentFunction) {
if (d.function != -1) {
DisassemblerLine &line = lines[d.function];
++line.hunk;
result.appendLine(line);
currentFunction = d.function;
}
}
result.appendLine(lines.at(d.index));
}
if (result.coversAddress(agent->address())) {
agent->setContents(result);
return true;
}
return false;
}
static SourcePathMap mergeStartParametersSourcePathMap(const DebuggerRunParameters &sp,
const SourcePathMap &in)
{
// Do not overwrite user settings.
SourcePathMap rc = sp.sourcePathMap;
for (auto it = in.constBegin(), end = in.constEnd(); it != end; ++it)
rc.insert(it.key(), it.value());
return rc;
}
//
// Starting up & shutting down
//
void GdbEngine::startGdb(const QStringList &args)
{
const QString tests = QString::fromLocal8Bit(qgetenv("QTC_DEBUGGER_TESTS"));
foreach (const QStringRef &test, tests.splitRef(QLatin1Char(',')))
m_testCases.insert(test.toInt());
foreach (int test, m_testCases)
showMessage("ENABLING TEST CASE: " + QString::number(test));
m_gdbProc.disconnect(); // From any previous runs
const DebuggerRunParameters &rp = runParameters();
if (rp.debugger.executable.isEmpty()) {
handleGdbStartFailed();
handleAdapterStartFailed(
msgNoGdbBinaryForToolChain(rp.toolChainAbi),
Constants::DEBUGGER_COMMON_SETTINGS_ID);
return;
}
QStringList gdbArgs;
gdbArgs << "-i";
gdbArgs << "mi";
if (!boolSetting(LoadGdbInit))
gdbArgs << "-n";
gdbArgs += args;
connect(&m_gdbProc, &QProcess::errorOccurred, this, &GdbEngine::handleGdbError);
connect(&m_gdbProc, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
this, &GdbEngine::handleGdbFinished);
connect(&m_gdbProc, &QtcProcess::readyReadStandardOutput, this, &GdbEngine::readGdbStandardOutput);
connect(&m_gdbProc, &QtcProcess::readyReadStandardError, this, &GdbEngine::readGdbStandardError);
showMessage("STARTING " + rp.debugger.executable + " " + gdbArgs.join(' '));
m_gdbProc.setCommand(rp.debugger.executable, QtcProcess::joinArgs(gdbArgs));
if (QFileInfo(rp.debugger.workingDirectory).isDir())
m_gdbProc.setWorkingDirectory(rp.debugger.workingDirectory);
m_gdbProc.setEnvironment(rp.debugger.environment);
m_gdbProc.start();
if (!m_gdbProc.waitForStarted()) {
handleGdbStartFailed();
QString msg;
QString wd = m_gdbProc.workingDirectory();
if (!QFileInfo(wd).isDir())
msg = failedToStartMessage() + ' ' + tr("The working directory \"%1\" is not usable.").arg(wd);
else
msg = errorMessage(QProcess::FailedToStart);
handleAdapterStartFailed(msg);
return;
}
showMessage("GDB STARTED, INITIALIZING IT");
runCommand({"show version", CB(handleShowVersion)});
//runCommand("-list-features", CB(handleListFeatures));
runCommand({"show debug-file-directory", CB(handleDebugInfoLocation)});
//runCommand("-enable-timings");
//rurun print static-members off"); // Seemingly doesn't work.
//runCommand("set debug infrun 1");
//runCommand("define hook-stop\n-thread-list-ids\n-stack-list-frames\nend");
//runCommand("define hook-stop\nprint 4\nend");
//runCommand("define hookpost-stop\nprint 5\nend");
//runCommand("define hook-call\nprint 6\nend");
//runCommand("define hookpost-call\nprint 7\nend");
runCommand({"set print object on"});
//runCommand("set step-mode on"); // we can't work with that yes
//runCommand("set exec-done-display on");
//runCommand("set print pretty on");
//runCommand("set confirm off");
//runCommand("set pagination off");
// The following does not work with 6.3.50-20050815 (Apple version gdb-1344)
// (Mac OS 10.6), but does so for gdb-966 (10.5):
//runCommand("set print inferior-events 1");
runCommand({"set breakpoint pending on"});
runCommand({"set print elements 10000"});
// Produces a few messages during symtab loading
//runCommand("set verbose on");
// one of the following is needed to prevent crashes in gdb on code like:
// template <class T> T foo() { return T(0); }
// int main() { return foo<int>(); }
// (gdb) call 'int foo<int>'()
// /build/buildd/gdb-6.8/gdb/valops.c:2069: internal-error
// This seems to be fixed, however, with 'on' it seems to _require_
// explicit casting of function pointers:
// GNU gdb (GDB) 7.5.91.20130417-cvs-ubuntu
// (gdb) p &Myns::QMetaType::typeName -> $1 = (const char *(*)(int)) 0xb7cf73b0 <Myns::QMetaType::typeName(int)>
// (gdb) p Myns::QMetaType::typeName(1024) -> 31^error,msg="Couldn't find method Myns::QMetaType::typeName"
// But we can work around on the dumper side. So let's use the default (i.e. 'on')
//runCommand("set overload-resolution off");
//runCommand(_("set demangle-style none"));
runCommand({"set unwindonsignal on"});
runCommand({"set width 0"});
runCommand({"set height 0"});
// FIXME: Provide proper Gui settings for these:
//runCommand("set breakpoint always-inserted on", ConsoleCommand);
// displaced-stepping does not work in Thumb mode.
//runCommand("set displaced-stepping on");
//runCommand("set trust-readonly-sections on", ConsoleCommand);
//runCommand("set remotecache on", ConsoleCommand);
//runCommand("set non-stop on", ConsoleCommand);
showStatusMessage(tr("Setting up inferior..."));
// Addint executable to modules list.
Module module;
module.startAddress = 0;
module.endAddress = 0;
module.modulePath = rp.inferior.executable;
module.moduleName = "<executable>";
modulesHandler()->updateModule(module);
// Apply source path mappings from global options.
//showMessage(_("Assuming Qt is installed at %1").arg(qtInstallPath));
const SourcePathMap sourcePathMap =
DebuggerSourcePathMappingWidget::mergePlatformQtPath(rp,
Internal::globalDebuggerOptions()->sourcePathMap);
const SourcePathMap completeSourcePathMap =
mergeStartParametersSourcePathMap(rp, sourcePathMap);
for (auto it = completeSourcePathMap.constBegin(), cend = completeSourcePathMap.constEnd();
it != cend;
++it) {
runCommand({"set substitute-path " + it.key() + " " + it.value()});
}
// Spaces just will not work.
foreach (const QString &src, rp.debugSourceLocation) {
if (QDir(src).exists())
runCommand({"directory " + src});
else
showMessage("# directory does not exist: " + src, LogInput);
}
if (!rp.sysRoot.isEmpty()) {
runCommand({"set sysroot " + rp.sysRoot});
// sysroot is not enough to correctly locate the sources, so explicitly
// relocate the most likely place for the debug source
runCommand({"set substitute-path /usr/src " + rp.sysRoot + "/usr/src"});
}
//QByteArray ba = QFileInfo(sp.dumperLibrary).path().toLocal8Bit();
//if (!ba.isEmpty())
// runCommand("set solib-search-path " + ba);
if (boolSetting(MultiInferior) || runParameters().multiProcess) {
//runCommand("set follow-exec-mode new");
runCommand({"set detach-on-fork off"});
}
// Finally, set up Python.
// We need to guarantee a roundtrip before the adapter proceeds.
// Make sure this stays the last command in startGdb().
// Don't use ConsoleCommand, otherwise Mac won't markup the output.
const QString dumperSourcePath = ICore::resourcePath() + "/debugger/";
if (terminal()->isUsable())
runCommand({"set inferior-tty " + QString::fromUtf8(terminal()->slaveDevice())});
const QFileInfo gdbBinaryFile(rp.debugger.executable);
const QString uninstalledData = gdbBinaryFile.absolutePath() + "/data-directory/python";
runCommand({"python sys.path.insert(1, '" + dumperSourcePath + "')"});
runCommand({"python sys.path.append('" + uninstalledData + "')"});
runCommand({"python from gdbbridge import *"});
const QString path = stringSetting(ExtraDumperFile);
if (!path.isEmpty() && QFileInfo(path).isReadable()) {
DebuggerCommand cmd("addDumperModule");
cmd.arg("path", path);
runCommand(cmd);
}
const QString commands = stringSetting(ExtraDumperCommands);
if (!commands.isEmpty())
runCommand({commands});
runCommand({"loadDumpers", CB(handlePythonSetup)});
}
void GdbEngine::handleGdbStartFailed()
{
}
void GdbEngine::loadInitScript()
{
const QString script = runParameters().overrideStartScript;
if (!script.isEmpty()) {
if (QFileInfo(script).isReadable()) {
runCommand({"source " + script});
} else {
AsynchronousMessageBox::warning(
tr("Cannot find debugger initialization script"),
tr("The debugger settings point to a script file at \"%1\" "
"which is not accessible. If a script file is not needed, "
"consider clearing that entry to avoid this warning. "
).arg(script));
}
} else {
const QString commands = nativeStartupCommands().trimmed();
if (!commands.isEmpty())
runCommand({commands});
}
}
void GdbEngine::setEnvironmentVariables()
{
Environment sysEnv = Environment::systemEnvironment();
Environment runEnv = runParameters().inferior.environment;
foreach (const EnvironmentItem &item, sysEnv.diff(runEnv)) {
if (item.unset)
runCommand({"unset environment " + item.name});
else
runCommand({"-gdb-set environment " + item.name + '='
+ item.value});
}
}
void GdbEngine::reloadDebuggingHelpers()
{
runCommand({"reloadDumpers"});
reloadLocals();
}
void GdbEngine::handleGdbError(QProcess::ProcessError error)
{
const QString msg = errorMessage(error);
showMessage("HANDLE GDB ERROR: " + msg);
// Show a message box for asynchronously reported issues.
switch (error) {
case QProcess::FailedToStart:
// This should be handled by the code trying to start the process.
break;
case QProcess::Crashed:
// This does not seem to get processFinished() in all cases.
m_gdbProc.disconnect();
handleGdbFinished(m_gdbProc.exitCode(), QProcess::CrashExit);
break;
case QProcess::ReadError:
case QProcess::WriteError:
case QProcess::Timedout:
default:
//m_gdbProc->kill();
//notifyEngineIll();
AsynchronousMessageBox::critical(tr("GDB I/O Error"), msg);
break;
}
}
void GdbEngine::handleGdbFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
if (m_commandTimer.isActive())
m_commandTimer.stop();
notifyDebuggerProcessFinished(exitCode, exitStatus, "GDB");
}
void GdbEngine::abortDebugger()
{
if (isDying()) {
// We already tried. Try harder.
showMessage("ABORTING DEBUGGER. SECOND TIME.");
m_gdbProc.kill();
} else {
// Be friendly the first time. This will change targetState().
showMessage("ABORTING DEBUGGER. FIRST TIME.");
quitDebugger();
}
}
void GdbEngine::resetInferior()
{
if (!runParameters().commandsForReset.isEmpty()) {
const QString commands = expand(runParameters().commandsForReset);
foreach (QString command, commands.split('\n')) {
command = command.trimmed();
if (!command.isEmpty())
runCommand({command, ConsoleCommand | NeedsTemporaryStop | NativeCommand});
}
}
m_rerunPending = true;
requestInterruptInferior();
runEngine();
}
void GdbEngine::handleAdapterStartFailed(const QString &msg, Id settingsIdHint)
{
CHECK_STATE(EngineSetupOk);
showMessage("ADAPTER START FAILED");
if (!msg.isEmpty() && !Internal::isTestRun()) {
const QString title = tr("Adapter start failed");
if (!settingsIdHint.isValid()) {
ICore::showWarningWithOptions(title, msg);
} else {
ICore::showWarningWithOptions(title, msg, QString(), settingsIdHint);
}
}
notifyEngineSetupFailed();
}
void GdbEngine::notifyInferiorSetupFailed()
{
// FIXME: that's not enough to stop gdb from getting confused
// by a timeout of the adapter.
//resetCommandQueue();
DebuggerEngine::notifyInferiorSetupFailed();
}
void GdbEngine::prepareForRestart()
{
m_rerunPending = false;
m_commandsDoneCallback = 0;
m_commandForToken.clear();
m_flagsForToken.clear();
}
void GdbEngine::handleInferiorPrepared()
{
const DebuggerRunParameters &rp = runParameters();
CHECK_STATE(InferiorSetupRequested);
if (!rp.commandsAfterConnect.isEmpty()) {
const QString commands = expand(rp.commandsAfterConnect);
for (const QString &command : commands.split('\n'))
runCommand({command, NativeCommand});
}
//runCommand("set follow-exec-mode new");
if (rp.breakOnMain)
runCommand({"tbreak " + mainFunction(rp)});
// Initial attempt to set breakpoints.
if (rp.startMode != AttachCore) {
showStatusMessage(tr("Setting breakpoints..."));
showMessage(tr("Setting breakpoints..."));
attemptBreakpointSynchronization();
}
if (m_commandForToken.isEmpty()) {
finishInferiorSetup();
} else {
QTC_CHECK(m_commandsDoneCallback == 0);
m_commandsDoneCallback = &GdbEngine::finishInferiorSetup;
}
}
void GdbEngine::finishInferiorSetup()
{
CHECK_STATE(InferiorSetupRequested);
if (runParameters().startMode != AttachCore) { // No breakpoints in core files.
const bool onAbort = boolSetting(BreakOnAbort);
const bool onWarning = boolSetting(BreakOnWarning);
const bool onFatal = boolSetting(BreakOnFatal);
if (onAbort || onWarning || onFatal) {
DebuggerCommand cmd("createSpecialBreakpoints");
cmd.arg("breakonabort", onAbort);
cmd.arg("breakonwarning", onWarning);
cmd.arg("breakonfatal", onFatal);
runCommand(cmd);
}
}
// It is ok to cut corners here and not wait for createSpecialBreakpoints()'s
// response, as the command is synchronous from Creator's point of view,
// and even if it fails (e.g. due to stripped binaries), continuing with
// the start up is the best we can do.
notifyInferiorSetupOk();
}
void GdbEngine::handleDebugInfoLocation(const DebuggerResponse &response)
{
if (response.resultClass == ResultDone) {
const QString debugInfoLocation = runParameters().debugInfoLocation;
if (QFile::exists(debugInfoLocation)) {
const QString curDebugInfoLocations = response.consoleStreamOutput.split('"').value(1);
QString cmd = "set debug-file-directory " + debugInfoLocation;
if (!curDebugInfoLocations.isEmpty())
cmd += HostOsInfo::pathListSeparator() + curDebugInfoLocations;
runCommand({cmd});
}
}
}
void GdbEngine::notifyInferiorSetupFailed(const QString &msg)
{
showStatusMessage(tr("Failed to start application:") + ' ' + msg);
if (state() == EngineSetupFailed) {
showMessage("INFERIOR START FAILED, BUT ADAPTER DIED ALREADY");
return; // Adapter crashed meanwhile, so this notification is meaningless.
}
showMessage("INFERIOR START FAILED");
AsynchronousMessageBox::critical(tr("Failed to start application"), msg);
DebuggerEngine::notifyInferiorSetupFailed();
}
void GdbEngine::handleAdapterCrashed(const QString &msg)
{
showMessage("ADAPTER CRASHED");
// The adapter is expected to have cleaned up after itself when we get here,
// so the effect is about the same as AdapterStartFailed => use it.
// Don't bother with state transitions - this can happen in any state and
// the end result is always the same, so it makes little sense to find a
// "path" which does not assert.
if (state() == EngineSetupRequested)
notifyEngineSetupFailed();
else
notifyEngineIll();
// No point in being friendly here ...
m_gdbProc.kill();
if (!msg.isEmpty())
AsynchronousMessageBox::critical(tr("Adapter crashed"), msg);
}
void GdbEngine::createFullBacktrace()
{
DebuggerCommand cmd("thread apply all bt full", NeedsTemporaryStop | ConsoleCommand);
cmd.callback = [this](const DebuggerResponse &response) {
if (response.resultClass == ResultDone) {
Internal::openTextEditor("Backtrace $",
response.consoleStreamOutput + response.logStreamOutput);
}
};
runCommand(cmd);
}
void GdbEngine::resetCommandQueue()
{
m_commandTimer.stop();
if (!m_commandForToken.isEmpty()) {
QString msg;
QTextStream ts(&msg);
ts << "RESETING COMMAND QUEUE. LEFT OVER TOKENS: ";
foreach (const DebuggerCommand &cmd, m_commandForToken)
ts << "CMD:" << cmd.function;
m_commandForToken.clear();
m_flagsForToken.clear();
showMessage(msg);
}
}
bool GdbEngine::usesExecInterrupt() const
{
DebuggerStartMode mode = runParameters().startMode;
return (mode == AttachToRemoteServer || mode == AttachToRemoteProcess)
&& usesTargetAsync();
}
bool GdbEngine::usesTargetAsync() const
{
return runParameters().useTargetAsync || boolSetting(TargetAsync);
}
void GdbEngine::scheduleTestResponse(int testCase, const QString &response)
{
if (!m_testCases.contains(testCase) && runParameters().testCase != testCase)
return;
int token = currentToken() + 1;
showMessage(QString("SCHEDULING TEST RESPONSE (CASE: %1, TOKEN: %2, RESPONSE: %3)")
.arg(testCase).arg(token).arg(response));
m_scheduledTestResponses[token] = response;
}
void GdbEngine::requestDebugInformation(const DebugInfoTask &task)
{
QProcess::startDetached(task.command);
}
QString GdbEngine::msgGdbStopFailed(const QString &why)
{
return tr("The gdb process could not be stopped:\n%1").arg(why);
}
QString GdbEngine::msgInferiorStopFailed(const QString &why)
{
return tr("Application process could not be stopped:\n%1").arg(why);
}
QString GdbEngine::msgInferiorSetupOk()
{
return tr("Application started");
}
QString GdbEngine::msgInferiorRunOk()
{
return tr("Application running");
}
QString GdbEngine::msgAttachedToStoppedInferior()
{
return tr("Attached to stopped application");
}
QString GdbEngine::msgConnectRemoteServerFailed(const QString &why)
{
return tr("Connecting to remote server failed:\n%1").arg(why);
}
void GdbEngine::interruptLocalInferior(qint64 pid)
{
QTC_ASSERT(state() == InferiorStopRequested, qDebug() << state(); return);
if (pid <= 0) {
showMessage("TRYING TO INTERRUPT INFERIOR BEFORE PID WAS OBTAINED", LogError);
return;
}
QString errorMessage;
if (interruptProcess(pid, GdbEngineType, &errorMessage)) {
showMessage("Interrupted " + QString::number(pid));
} else {
showMessage(errorMessage, LogError);
notifyInferiorStopFailed();
}
}
void GdbEngine::debugLastCommand()
{
runCommand(m_lastDebuggableCommand);
}
//
// Factory
//
DebuggerEngine *createGdbEngine(bool useTerminal, DebuggerStartMode sm)
{
switch (sm) {
case AttachCore:
return new GdbCoreEngine(useTerminal);
case StartRemoteProcess:
case AttachToRemoteServer:
return new GdbRemoteServerEngine(useTerminal);
case AttachExternal:
return new GdbAttachEngine(useTerminal);
default:
if (useTerminal)
return new GdbTermEngine(useTerminal);
return new GdbPlainEngine(useTerminal);
}
}
void GdbEngine::doUpdateLocals(const UpdateParameters &params)
{
m_pendingBreakpointRequests = 0;
watchHandler()->notifyUpdateStarted(params);
DebuggerCommand cmd("fetchVariables", Discardable|InUpdateLocals);
watchHandler()->appendFormatRequests(&cmd);
watchHandler()->appendWatchersAndTooltipRequests(&cmd);
const static bool alwaysVerbose = qEnvironmentVariableIsSet("QTC_DEBUGGER_PYTHON_VERBOSE");
cmd.arg("passexceptions", alwaysVerbose);
cmd.arg("fancy", boolSetting(UseDebuggingHelpers));
cmd.arg("autoderef", boolSetting(AutoDerefPointers));
cmd.arg("dyntype", boolSetting(UseDynamicType));
cmd.arg("qobjectnames", boolSetting(ShowQObjectNames));
StackFrame frame = stackHandler()->currentFrame();
cmd.arg("context", frame.context);
cmd.arg("nativemixed", isNativeMixedActive());
cmd.arg("stringcutoff", action(MaximalStringLength)->value().toString());
cmd.arg("displaystringlimit", action(DisplayStringLimit)->value().toString());
cmd.arg("resultvarname", m_resultVarName);
cmd.arg("partialvar", params.partialVariable);
m_lastDebuggableCommand = cmd;
m_lastDebuggableCommand.arg("passexceptions", "1");
cmd.callback = CB(handleFetchVariables);
runCommand(cmd);
}
void GdbEngine::handleFetchVariables(const DebuggerResponse &response)
{
m_inUpdateLocals = false;
updateLocalsView(response.data);
watchHandler()->notifyUpdateFinished();
}
QString GdbEngine::msgPtraceError(DebuggerStartMode sm)
{
if (sm == StartInternal) {
return QCoreApplication::translate("QtDumperHelper",
"ptrace: Operation not permitted.\n\n"
"Could not attach to the process. "
"Make sure no other debugger traces this process.\n"
"Check the settings of\n"
"/proc/sys/kernel/yama/ptrace_scope\n"
"For more details, see /etc/sysctl.d/10-ptrace.conf\n");
}
return QCoreApplication::translate("QtDumperHelper",
"ptrace: Operation not permitted.\n\n"
"Could not attach to the process. "
"Make sure no other debugger traces this process.\n"
"If your uid matches the uid\n"
"of the target process, check the settings of\n"
"/proc/sys/kernel/yama/ptrace_scope\n"
"For more details, see /etc/sysctl.d/10-ptrace.conf\n");
}
} // namespace Internal
} // namespace Debugger
Q_DECLARE_METATYPE(Debugger::Internal::GdbMi)