diff --git a/src/plugins/debugger/debuggerdialogs.cpp b/src/plugins/debugger/debuggerdialogs.cpp index 49705dc13a7067f5a45206b6aedb941e889555c9..3795f73688a38fd954aeb7d62406bfe4fd952f2c 100644 --- a/src/plugins/debugger/debuggerdialogs.cpp +++ b/src/plugins/debugger/debuggerdialogs.cpp @@ -570,8 +570,6 @@ bool StartExternalDialog::breakAtMain() const return m_ui->checkBoxBreakAtMain->isChecked(); } - - /////////////////////////////////////////////////////////////////////// // // StartRemoteDialog diff --git a/src/plugins/debugger/debuggerplugin.cpp b/src/plugins/debugger/debuggerplugin.cpp index 16483b8dfc941cf3daf17e5e1e9515788bd1d49c..ce67ada91698b376c7335fdd0c2f25aeb666c5b2 100644 --- a/src/plugins/debugger/debuggerplugin.cpp +++ b/src/plugins/debugger/debuggerplugin.cpp @@ -1585,6 +1585,10 @@ void DebuggerPluginPrivate::startExternalApplication() sp.breakAtMain = dlg.breakAtMain(); if (!dlg.executableArguments().isEmpty()) sp.processArgs = dlg.executableArguments().split(QLatin1Char(' ')); + // Fixme: 1 of 3 testing hacks. + if (!sp.processArgs.isEmpty() + && (sp.processArgs.front() == _("@tcf@") || sp.processArgs.front() == _("@sym@"))) + sp.toolChainType = ProjectExplorer::ToolChain::RVCT_ARMV5; startDebugger(m_debuggerRunControlFactory->create(sp)); } diff --git a/src/plugins/debugger/debuggerrunner.cpp b/src/plugins/debugger/debuggerrunner.cpp index 9c38c145008d8b48d2df57e4ab8ec4e051ad4d9a..d642beeb84098f5c22d5fcb2c4daecce72edafcc 100644 --- a/src/plugins/debugger/debuggerrunner.cpp +++ b/src/plugins/debugger/debuggerrunner.cpp @@ -338,6 +338,10 @@ void DebuggerRunControl::createEngine(const DebuggerStartParameters &sp) else engineType = engineForToolChain(sp.toolChainType); + // Fixme: 1 of 3 testing hacks. + if (sp.processArgs.size() >= 5 && sp.processArgs.at(0) == _("@tcf@")) + engineType = GdbEngineType; + if (engineType == NoEngineType && sp.startMode != AttachToRemote && !sp.executable.isEmpty()) diff --git a/src/plugins/debugger/gdb/gdb.pri b/src/plugins/debugger/gdb/gdb.pri index ffe58619521656c9dcae0dd8cb1dea1877c71fec..e94b175bdf3b14719992db49e8be45fa2c1fad6d 100644 --- a/src/plugins/debugger/gdb/gdb.pri +++ b/src/plugins/debugger/gdb/gdb.pri @@ -10,12 +10,14 @@ HEADERS += \ $$PWD/termgdbadapter.h \ $$PWD/remotegdbserveradapter.h \ $$PWD/trkgdbadapter.h \ + $$PWD/tcftrkgdbadapter.h \ $$PWD/s60debuggerbluetoothstarter.h \ $$PWD/abstractgdbprocess.h \ $$PWD/localgdbprocess.h \ $$PWD/remotegdbprocess.h \ $$PWD/remoteplaingdbadapter.h \ - $$PWD/abstractplaingdbadapter.h + $$PWD/abstractplaingdbadapter.h \ + $$PWD/symbian.h SOURCES += \ $$PWD/gdbmi.cpp \ @@ -31,12 +33,14 @@ SOURCES += \ $$PWD/termgdbadapter.cpp \ $$PWD/remotegdbserveradapter.cpp \ $$PWD/trkgdbadapter.cpp \ + $$PWD/tcftrkgdbadapter.cpp \ $$PWD/s60debuggerbluetoothstarter.cpp \ $$PWD/abstractgdbprocess.cpp \ $$PWD/localgdbprocess.cpp \ $$PWD/remotegdbprocess.cpp \ $$PWD/remoteplaingdbadapter.cpp \ - $$PWD/abstractplaingdbadapter.cpp + $$PWD/abstractplaingdbadapter.cpp \ + $$PWD/symbian.cpp FORMS += $$PWD/gdboptionspage.ui diff --git a/src/plugins/debugger/gdb/gdbengine.cpp b/src/plugins/debugger/gdb/gdbengine.cpp index c8a0ad106d705d6db5c5b7acfee02df1fe81a191..5605cb8088fd04ba67dbd5c9a861804bac53e1cc 100644 --- a/src/plugins/debugger/gdb/gdbengine.cpp +++ b/src/plugins/debugger/gdb/gdbengine.cpp @@ -43,6 +43,7 @@ #include "remotegdbserveradapter.h" #include "remoteplaingdbadapter.h" #include "trkgdbadapter.h" +#include "tcftrkgdbadapter.h" #include "watchutils.h" #include "debuggeractions.h" @@ -1745,15 +1746,14 @@ AbstractGdbAdapter *GdbEngine::createAdapter() case ProjectExplorer::ToolChain::RVCT_ARMV6: case ProjectExplorer::ToolChain::RVCT_ARMV5_GNUPOC: case ProjectExplorer::ToolChain::GCCE_GNUPOC: + // fixme: 1 of 3 testing hacks + if (sp.processArgs.size() >= 5 && sp.processArgs.at(0) == _("@tcf@")) + return new TcfTrkGdbAdapter(this); return new TrkGdbAdapter(this); default: break; } - // @todo: remove testing hack - if (sp.processArgs.size() == 3 && sp.processArgs.at(0) == _("@sym@")) - return new TrkGdbAdapter(this); - switch (sp.startMode) { case AttachCore: return new CoreGdbAdapter(this); diff --git a/src/plugins/debugger/gdb/gdbengine.h b/src/plugins/debugger/gdb/gdbengine.h index 4dd23a004ad4119361604f5c51eb571bf8b51dae..6a45096237b008a6e20ef3adde0abebafb84df80 100644 --- a/src/plugins/debugger/gdb/gdbengine.h +++ b/src/plugins/debugger/gdb/gdbengine.h @@ -99,6 +99,7 @@ private: friend class RemoteGdbServerAdapter; friend class RemotePlainGdbAdapter; friend class TrkGdbAdapter; + friend class TcfTrkGdbAdapter; private: ////////// General Interface ////////// diff --git a/src/plugins/debugger/gdb/symbian.cpp b/src/plugins/debugger/gdb/symbian.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9388fdf73389b865c8ae8913bd3e2004c4913a26 --- /dev/null +++ b/src/plugins/debugger/gdb/symbian.cpp @@ -0,0 +1,215 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "symbian.h" +#include <trkutils.h> + +#include <utils/qtcassert.h> + +#include <QtCore/QDebug> +#include <QtCore/QTextStream> + +namespace Debugger { +namespace Internal { + +/////////////////////////////////////////////////////////////////////////// +// +// MemoryRange +// +/////////////////////////////////////////////////////////////////////////// + +MemoryRange::MemoryRange(uint f, uint t) + : from(f), to(t) +{ + QTC_ASSERT(f <= t, qDebug() << "F: " << f << " T: " << t); +} + +bool MemoryRange::intersects(const MemoryRange &other) const +{ + Q_UNUSED(other); + QTC_ASSERT(false, /**/); + return false; // FIXME +} + +void MemoryRange::operator-=(const MemoryRange &other) +{ + if (from == 0 && to == 0) + return; + MEMORY_DEBUG(" SUB: " << *this << " - " << other); + if (other.from <= from && to <= other.to) { + from = to = 0; + return; + } + if (other.from <= from && other.to <= to) { + from = qMax(from, other.to); + return; + } + if (from <= other.from && to <= other.to) { + to = qMin(other.from, to); + return; + } + // This would split the range. + QTC_ASSERT(false, qDebug() << "Memory::operator-() not handled for: " + << *this << " - " << other); +} + +QDebug operator<<(QDebug d, const MemoryRange &range) +{ + return d << QString("[%1,%2] (size %3) ") + .arg(range.from, 0, 16).arg(range.to, 0, 16).arg(range.size()); +} + +namespace Symbian { + +static const char *registerNames[KnownRegisters] = +{ + "A1", "A2", "A3", "A4", + 0, 0, 0, 0, + 0, 0, 0, "AP", + "IP", "SP", "LR", "PC", + "PSTrk", 0, 0, 0, + 0, 0, 0, 0, + 0, "PSGdb" +}; + +const char *registerName(int i) +{ + return registerNames[i]; +} + +QByteArray dumpRegister(uint n, uint value) +{ + QByteArray ba; + ba += ' '; + if (n < KnownRegisters && registerNames[n]) { + ba += registerNames[n]; + } else { + ba += '#'; + ba += QByteArray::number(n); + } + ba += '='; + ba += trk::hexxNumber(value); + return ba; +} + + +/////////////////////////////////////////////////////////////////////////// +// +// Snapshot +// +/////////////////////////////////////////////////////////////////////////// + +void Snapshot::reset() +{ + for (Memory::Iterator it = memory.begin(); it != memory.end(); ++it) { + if (isReadOnly(it.key())) { + MEMORY_DEBUG("KEEPING READ-ONLY RANGE" << it.key()); + } else { + it = memory.erase(it); + } + } + for (int i = 0; i < RegisterCount; ++i) + registers[i] = 0; + registerValid = false; + wantedMemory = MemoryRange(); + lineFromAddress = 0; + lineToAddress = 0; +} + +void Snapshot::fullReset() +{ + memory.clear(); + reset(); +} + +void Snapshot::insertMemory(const MemoryRange &range, const QByteArray &ba) +{ + QTC_ASSERT(range.size() == uint(ba.size()), + qDebug() << "RANGE: " << range << " BA SIZE: " << ba.size(); return); + + MEMORY_DEBUG("INSERT: " << range); + // Try to combine with existing chunk. + Snapshot::Memory::iterator it = memory.begin(); + Snapshot::Memory::iterator et = memory.end(); + for ( ; it != et; ++it) { + if (range.from == it.key().to) { + MEMORY_DEBUG("COMBINING " << it.key() << " AND " << range); + QByteArray data = *it; + data.append(ba); + const MemoryRange res(it.key().from, range.to); + memory.remove(it.key()); + MEMORY_DEBUG(" TO(1) " << res); + insertMemory(res, data); + return; + } + if (it.key().from == range.to) { + MEMORY_DEBUG("COMBINING " << range << " AND " << it.key()); + QByteArray data = ba; + data.append(*it); + const MemoryRange res(range.from, it.key().to); + memory.remove(it.key()); + MEMORY_DEBUG(" TO(2) " << res); + insertMemory(res, data); + return; + } + } + + // Not combinable, add chunk. + memory.insert(range, ba); +} + +QString Snapshot::toString() const +{ + typedef QMap<MemoryRange, QByteArray>::const_iterator MemCacheConstIt; + QString rc; + QTextStream str(&rc); + str << "Register valid " << registerValid << ' '; + for (int i = 0; i < RegisterCount; i++) { + if (i) + str << ", "; + str << " R" << i << "=0x"; + str.setIntegerBase(16); + str << registers[i]; + str.setIntegerBase(10); + } + str << '\n'; + // For next step. + if (!memory.isEmpty()) { + str.setIntegerBase(16); + str << "Memory:\n"; + const MemCacheConstIt mcend = memory.constEnd(); + for (MemCacheConstIt it = memory.constBegin(); it != mcend; ++it) + str << " 0x" << it.key().from << " - 0x" << it.key().to << '\n'; + } + return rc; +} + +} // namespace Symbian +} // namespace Internal +} // namespace Debugger diff --git a/src/plugins/debugger/gdb/symbian.h b/src/plugins/debugger/gdb/symbian.h new file mode 100644 index 0000000000000000000000000000000000000000..63e3c4e48f1a2fb186822787af440f1de4d28d50 --- /dev/null +++ b/src/plugins/debugger/gdb/symbian.h @@ -0,0 +1,145 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef SYMBIANUTILS_H +#define SYMBIANUTILS_H + +#include <QtCore/QMap> +#include <QtCore/QByteArray> +#include <QtCore/QMetaType> + +QT_BEGIN_NAMESPACE +class QDebug; +QT_END_NAMESPACE + +//#define DEBUG_MEMORY 1 +#if DEBUG_MEMORY +# define MEMORY_DEBUG(s) qDebug() << s +#else +# define MEMORY_DEBUG(s) +#endif +#define MEMORY_DEBUGX(s) qDebug() << s + +namespace Debugger { +namespace Internal { + +struct GdbResult { + QByteArray data; +}; + +struct MemoryRange +{ + MemoryRange() : from(0), to(0) {} + MemoryRange(uint f, uint t); + void operator-=(const MemoryRange &other); + bool intersects(const MemoryRange &other) const; + quint64 hash() const { return (quint64(from) << 32) + to; } + bool operator==(const MemoryRange &other) const { return hash() == other.hash(); } + bool operator<(const MemoryRange &other) const { return hash() < other.hash(); } + uint size() const { return to - from; } + + uint from; // Inclusive. + uint to; // Exclusive. +}; + +QDebug operator<<(QDebug d, const MemoryRange &range); + +namespace Symbian { + +enum CodeMode +{ + ArmMode = 0, + ThumbMode, +}; + +enum TargetConstants +{ + RegisterCount = 17, + RegisterSP = 13, // Stack Pointer + RegisterLR = 14, // Return address + RegisterPC = 15, // Program counter + RegisterPSGdb = 25, // gdb's view of the world + RegisterPSTrk = 16, // TRK's view of the world + + MemoryChunkSize = 256 +}; + +enum { KnownRegisters = RegisterPSGdb + 1}; + +const char *registerName(int i); +QByteArray dumpRegister(uint n, uint value); + +inline bool isReadOnly(const MemoryRange &mr) +{ + return mr.from >= 0x70000000 && mr.to < 0x80000000; +} + +struct Snapshot +{ + Snapshot() { reset(); } + + void reset(); // Leaves read-only memory cache alive. + void fullReset(); // Also removes read-only memory cache. + void insertMemory(const MemoryRange &range, const QByteArray &ba); + QString toString() const; + + uint registers[RegisterCount]; + bool registerValid; + typedef QMap<MemoryRange, QByteArray> Memory; + Memory memory; + + // Current state. + MemoryRange wantedMemory; + + // For next step. + uint lineFromAddress; + uint lineToAddress; + bool stepOver; +}; + +struct Breakpoint +{ + Breakpoint(uint offset_ = 0) + { + number = 0; + offset = offset_; + mode = ArmMode; + } + uint offset; + ushort number; + CodeMode mode; +}; + +} // namespace Symbian +} // namespace Internal +} // namespace Debugger + +Q_DECLARE_METATYPE(Debugger::Internal::MemoryRange); + +#endif // SYMBIANUTILS_H diff --git a/src/plugins/debugger/gdb/tcftrkgdbadapter.cpp b/src/plugins/debugger/gdb/tcftrkgdbadapter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e6ab8354933d65046734c464b50731c75e07520c --- /dev/null +++ b/src/plugins/debugger/gdb/tcftrkgdbadapter.cpp @@ -0,0 +1,1522 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "tcftrkgdbadapter.h" + +#include "tcftrkdevice.h" +#include "trkutils.h" + +#include "registerhandler.h" +#include "threadshandler.h" +#include "debuggeractions.h" +#include "debuggerstringutils.h" +#include "watchutils.h" +#ifndef STANDALONE_RUNNER +#include "gdbengine.h" +#endif + +#include <utils/qtcassert.h> +#include <utils/savedaction.h> + +#include <QtCore/QTimer> +#include <QtCore/QDir> +#include <QtNetwork/QTcpServer> +#include <QtNetwork/QTcpSocket> + +#ifdef Q_OS_WIN +# include <windows.h> +#else +# include <sys/types.h> +# include <unistd.h> +#endif + +#define CB(callback) \ + static_cast<GdbEngine::AdapterCallback>(&TcfTrkGdbAdapter::callback), \ + STRINGIFY(callback) + +enum { debug = 0 }; + +static void appendRegister(QByteArray *ba, uint regno, uint value) +{ + ba->append(trk::hexNumber(regno, 2)); + ba->append(':'); + ba->append(trk::hexNumber(trk::swapEndian(value), 8)); + ba->append(';'); +} + +// Register names used by the 'SimpleRegister' service +static const char* tcfTrkSimpleRegisterNamesC[] = +{"R0", "R1", "R2", "R3", + "R4", "R5", "R6", "R7", + "R8", "R9", "R10", "R11", + "R12", "SP", "LR", "PC", + "CPSR"}; + +namespace Debugger { +namespace Internal { +using namespace Symbian; + +static inline QString startMsg(const trk::Session &session) +{ + return TcfTrkGdbAdapter::tr("Process started, PID: 0x%1, thread id: 0x%2, " + "code segment: 0x%3, data segment: 0x%4.") + .arg(session.pid, 0, 16).arg(session.tid, 0, 16) + .arg(session.codeseg, 0, 16).arg(session.dataseg, 0, 16); +} + +/* -------------- TcfTrkGdbAdapter: + * Startup-sequence: + * - startAdapter connects both sockets/devices + * - In the TCF Locator Event, gdb is started and adapterStarted is emitted. + * - Engine calls startInferior(), which starts the process. + * - Initial TCF module load suspended event is emitted (process is suspended). + * In the event handler, gdb is connected to the remote target. In the + * gdb answer to conntect remote, inferiorStartPrepared() is emitted. + * - Engine sets up breakpoints,etc and calls inferiorStartPhase2(), which + * resumes the suspended TCF process via gdb 'continue'. + */ + +TcfTrkGdbAdapter::TcfTrkGdbAdapter(GdbEngine *engine) : + AbstractGdbAdapter(engine), + m_running(false), + m_trkDevice(new tcftrk::TcfTrkDevice(this)), + m_gdbAckMode(true), + m_uid(0), + m_verbose(0), + m_firstModuleResumableEvent(false) +{ + m_bufferedMemoryRead = true; + // Disable buffering if gdb's dcache is used. + m_bufferedMemoryRead = false; + + m_gdbServer = 0; + m_gdbConnection = 0; + m_snapshot.reset(); +#ifdef Q_OS_WIN + const DWORD portOffset = GetCurrentProcessId() % 100; +#else + const uid_t portOffset = getuid(); +#endif + m_gdbServerName = _("127.0.0.1:%1").arg(2222 + portOffset); + + setVerbose(theDebuggerBoolSetting(VerboseLog) ? 1 : 0); + + connect(theDebuggerAction(VerboseLog), SIGNAL(valueChanged(QVariant)), + this, SLOT(setVerbose(QVariant))); + connect(m_trkDevice, SIGNAL(error(QString)), this, SLOT(tcftrkDeviceError(QString))); + connect(m_trkDevice, SIGNAL(logMessage(QString)), this, SLOT(trkLogMessage(QString))); + connect(m_trkDevice, SIGNAL(tcfEvent(tcftrk::TcfTrkEvent)), this, + SLOT(tcftrkEvent(tcftrk::TcfTrkEvent))); + + // Set register mappings + const int registerCount = sizeof(tcfTrkSimpleRegisterNamesC)/sizeof(const char*); + QVector<QByteArray> registerNames; + registerNames.reserve(registerCount); + for (int i = 0; i < registerCount; i++) + registerNames.push_back(QByteArray(tcfTrkSimpleRegisterNamesC[i])); + m_trkDevice->setRegisterNames(registerNames); +} + +TcfTrkGdbAdapter::~TcfTrkGdbAdapter() +{ + cleanup(); + logMessage("Shutting down.\n"); +} + +void TcfTrkGdbAdapter::setVerbose(const QVariant &value) +{ + setVerbose(value.toInt()); +} + +void TcfTrkGdbAdapter::setVerbose(int verbose) +{ + if (debug) + qDebug("TcfTrkGdbAdapter::setVerbose %d", verbose); + m_verbose = verbose; + m_trkDevice->setVerbose(m_verbose); +} + +void TcfTrkGdbAdapter::trkLogMessage(const QString &msg) +{ + logMessage(_("TRK ") + msg); +} + +void TcfTrkGdbAdapter::setGdbServerName(const QString &name) +{ + m_gdbServerName = name; +} + +QString TcfTrkGdbAdapter::gdbServerIP() const +{ + int pos = m_gdbServerName.indexOf(':'); + if (pos == -1) + return m_gdbServerName; + return m_gdbServerName.left(pos); +} + +uint TcfTrkGdbAdapter::gdbServerPort() const +{ + int pos = m_gdbServerName.indexOf(':'); + if (pos == -1) + return 0; + return m_gdbServerName.mid(pos + 1).toUInt(); +} + +void TcfTrkGdbAdapter::handleTcfTrkRunControlModuleLoadContextSuspendedEvent(const tcftrk::TcfTrkRunControlModuleLoadContextSuspendedEvent &se) +{ + const tcftrk::ModuleLoadEventInfo &minfo = se.info(); + // Register in session, keep modules and libraries in sync. + const QString moduleName = QString::fromUtf8(minfo.name); + if (true || minfo.loaded) { // TODO: Preliminary TCF Trk Versions always have Loaded=false? + m_session.modules.push_back(moduleName); + trk::Library library; + library.name = minfo.name; + library.codeseg = minfo.codeAddress; + library.dataseg = minfo.dataAddress; + m_session.libraries.push_back(library); + } else { + const int index = m_session.modules.indexOf(moduleName); + if (index != -1) { + m_session.modules.removeAt(index); + m_session.libraries.removeAt(index); + } else { + // Might happen with preliminary version of TCF TRK. + qWarning("Received unload for module '%s' for which no load was received.", + qPrintable(moduleName)); + } + + } + // Handle resume. + if (se.info().requireResume) { + // If it is the first, resumable load event (.exe), make + // gdb connect to remote target and resume in startInferior2(), + if (m_firstModuleResumableEvent) { + m_firstModuleResumableEvent = false; + m_tcfProcessId = se.id(); + m_session.codeseg = minfo.codeAddress; + m_session.dataseg = minfo.dataAddress; + logMessage(startMsg(m_session)); + const QByteArray symbolFile = m_symbolFile.toLocal8Bit(); + if (symbolFile.isEmpty()) { + logMessage(_("WARNING: No symbol file available."), LogWarning); + } else { + // Does not seem to be necessary anymore. + // FIXME: Startup sequence can be streamlined now as we do not + // have to wait for the TRK startup to learn the load address. + //m_engine->postCommand("add-symbol-file \"" + symbolFile + "\" " + // + QByteArray::number(m_session.codeseg)); + m_engine->postCommand("symbol-file \"" + symbolFile + "\""); + } + m_engine->postCommand("set breakpoint always-inserted on"); + m_engine->postCommand("set breakpoint auto-hw on"); + m_engine->postCommand("set trust-readonly-sections on"); // No difference? + m_engine->postCommand("set displaced-stepping on"); // No difference? + //m_engine->postCommand("set remotelogfile /tmp/gdb-remotelog"); + //m_engine->postCommand("set debug remote 1"); // FIXME: Make an option. + m_engine->postCommand("set mem inaccessible-by-default"); + m_engine->postCommand("mem 0x00400000 0x70000000 cache"); + m_engine->postCommand("mem 0x70000000 0x80000000 cache ro"); + // FIXME: replace with stack-cache for newer gdb? + m_engine->postCommand("set remotecache on"); // "info dcache" to check + m_engine->postCommand("target remote " + gdbServerName().toLatin1(), + CB(handleTargetRemote)); + if (debug) + qDebug() << "Initial module load suspended: " << m_session.toString(); + } else { + // Consecutive module load suspended: (not observed yet): Just continue + m_trkDevice->sendRunControlResumeCommand(TcfTrkCallback(), se.id()); + } + } +} + +void TcfTrkGdbAdapter::handleTargetRemote(const GdbResponse &record) +{ + QTC_ASSERT(state() == InferiorStarting, qDebug() << state()); + if (record.resultClass == GdbResultDone) { + setState(InferiorStopped); + emit inferiorPrepared(); + if (debug) + qDebug() << "handleTargetRemote" << m_session.toString(); + } else { + QString msg = tr("Connecting to TRK server adapter failed:\n") + + QString::fromLocal8Bit(record.data.findChild("msg").data()); + emit inferiorStartFailed(msg); + } +} + +void TcfTrkGdbAdapter::tcftrkEvent(const tcftrk::TcfTrkEvent &e) +{ + logMessage(e.toString()); + + switch (e.type()) { + case tcftrk::TcfTrkEvent::LocatorHello: + startGdb(); // Commands are only accepted after hello + break; + case tcftrk::TcfTrkEvent::RunControlModuleLoadSuspended: + handleTcfTrkRunControlModuleLoadContextSuspendedEvent(static_cast<const tcftrk::TcfTrkRunControlModuleLoadContextSuspendedEvent &>(e)); + break; + case tcftrk::TcfTrkEvent::RunControlContextAdded: + foreach(const tcftrk::RunControlContext &rc, static_cast<const tcftrk::TcfTrkRunControlContextAddedEvent &>(e).contexts()) + if (rc.type() == tcftrk::RunControlContext::Thread) { + m_session.threads.push_back(rc.threadId()); + if (m_session.tid == 0) + m_session.tid = rc.threadId(); + } + break; + case tcftrk::TcfTrkEvent::RunControlContextRemoved: + foreach(const QByteArray &id, static_cast<const tcftrk::TcfTrkRunControlContextRemovedEvent &>(e).ids()) + switch (tcftrk::RunControlContext::typeFromTcfId(id)) { + case tcftrk::RunControlContext::Thread: + m_session.threads.removeAll(tcftrk::RunControlContext::threadIdFromTcdfId(id)); + break; + case tcftrk::RunControlContext::Process: + sendGdbServerMessage("W00", "Process exited"); + break; + } + break; + case tcftrk::TcfTrkEvent::RunControlSuspended: { + const tcftrk::TcfTrkRunControlContextSuspendedEvent &se = static_cast<const tcftrk::TcfTrkRunControlContextSuspendedEvent &>(e); + showMessage(QString::fromLatin1("RESET SNAPSHOT (NOTIFY STOPPED: '%1')").arg(QString::fromLatin1(se.reasonID()))); + m_snapshot.reset(); + // Update registers first, then report stopped + m_running = false; + m_trkDevice->sendRegistersGetMRangeCommand(TcfTrkCallback(this, &TcfTrkGdbAdapter::handleAndReportReadRegistersAfterStop), + m_tcfProcessId, 0, + Symbian::RegisterCount); + // TODO:mov + } + break; + default: + break; + } +} + +void TcfTrkGdbAdapter::startGdb() +{ + QStringList gdbArgs; + gdbArgs.append(QLatin1String("--nx")); // Do not read .gdbinit file + if (!m_engine->startGdb(gdbArgs, QString(), QString())) { + cleanup(); + return; + } + emit adapterStarted(); +} + +void TcfTrkGdbAdapter::tcftrkDeviceError(const QString &errorString) +{ + logMessage(errorString); + if (state() == AdapterStarting) { + emit adapterStartFailed(errorString, QString()); + } else { + emit adapterCrashed(errorString); + } +} + +void TcfTrkGdbAdapter::logMessage(const QString &msg, int channel) +{ + if (m_verbose || channel != LogDebug) + showMessage(msg, channel); + if (debug) + qDebug("%s", qPrintable(msg)); +} + +// +// Gdb +// +void TcfTrkGdbAdapter::handleGdbConnection() +{ + logMessage("HANDLING GDB CONNECTION"); + QTC_ASSERT(m_gdbConnection == 0, /**/); + m_gdbConnection = m_gdbServer->nextPendingConnection(); + QTC_ASSERT(m_gdbConnection, return); + connect(m_gdbConnection, SIGNAL(disconnected()), + m_gdbConnection, SLOT(deleteLater())); + connect(m_gdbConnection, SIGNAL(readyRead()), + this, SLOT(readGdbServerCommand())); +} + +static inline QString msgGdbPacket(const QString &p) +{ + return QLatin1String("gdb: ") + p; +} + +void TcfTrkGdbAdapter::readGdbServerCommand() +{ + QTC_ASSERT(m_gdbConnection, return); + QByteArray packet = m_gdbConnection->readAll(); + m_gdbReadBuffer.append(packet); + + logMessage("gdb: -> " + currentTime() + ' ' + QString::fromAscii(packet)); + if (packet != m_gdbReadBuffer) + logMessage("buffer: " + m_gdbReadBuffer); + + QByteArray &ba = m_gdbReadBuffer; + while (ba.size()) { + char code = ba.at(0); + ba = ba.mid(1); + + if (code == '+') { + //logMessage("ACK"); + continue; + } + + if (code == '-') { + logMessage("NAK: Retransmission requested", LogError); + // This seems too harsh. + //emit adapterCrashed("Communication problem encountered."); + continue; + } + + if (code == char(0x03)) { + logMessage("INTERRUPT RECEIVED"); + interruptInferior(); + continue; + } + + if (code != '$') { + logMessage("Broken package (2) " + quoteUnprintableLatin1(ba) + + trk::hexNumber(code), LogError); + continue; + } + + int pos = ba.indexOf('#'); + if (pos == -1) { + logMessage("Invalid checksum format in " + + quoteUnprintableLatin1(ba), LogError); + continue; + } + + bool ok = false; + uint checkSum = ba.mid(pos + 1, 2).toUInt(&ok, 16); + if (!ok) { + logMessage("Invalid checksum format 2 in " + + quoteUnprintableLatin1(ba), LogError); + return; + } + + //logMessage(QString("Packet checksum: %1").arg(checkSum)); + trk::byte sum = 0; + for (int i = 0; i < pos; ++i) + sum += ba.at(i); + + if (sum != checkSum) { + logMessage(QString("ERROR: Packet checksum wrong: %1 %2 in " + + quoteUnprintableLatin1(ba)).arg(checkSum).arg(sum), LogError); + } + + QByteArray cmd = ba.left(pos); + ba.remove(0, pos + 3); + handleGdbServerCommand(cmd); + } +} + +bool TcfTrkGdbAdapter::sendGdbServerPacket(const QByteArray &packet, bool doFlush) +{ + if (!m_gdbConnection) { + logMessage(_("Cannot write to gdb: No connection (%1)") + .arg(_(packet)), LogError); + return false; + } + if (m_gdbConnection->state() != QAbstractSocket::ConnectedState) { + logMessage(_("Cannot write to gdb: Not connected (%1)") + .arg(_(packet)), LogError); + return false; + } + if (m_gdbConnection->write(packet) == -1) { + logMessage(_("Cannot write to gdb: %1 (%2)") + .arg(m_gdbConnection->errorString()).arg(_(packet)), LogError); + return false; + } + if (doFlush) + m_gdbConnection->flush(); + return true; +} + +void TcfTrkGdbAdapter::sendGdbServerAck() +{ + if (!m_gdbAckMode) + return; + logMessage("gdb: <- +"); + sendGdbServerPacket(QByteArray(1, '+'), false); +} + +void TcfTrkGdbAdapter::sendGdbServerMessage(const QByteArray &msg, const QByteArray &logNote) +{ + trk::byte sum = 0; + for (int i = 0; i != msg.size(); ++i) + sum += msg.at(i); + + char checkSum[30]; + qsnprintf(checkSum, sizeof(checkSum) - 1, "%02x ", sum); + + //logMessage(QString("Packet checksum: %1").arg(sum)); + + QByteArray packet; + packet.append('$'); + packet.append(msg); + packet.append('#'); + packet.append(checkSum); + int pad = qMax(0, 24 - packet.size()); + logMessage("gdb: <- " + currentTime() + ' ' + packet + QByteArray(pad, ' ') + logNote); + sendGdbServerPacket(packet, true); +} + +static QByteArray msgStepRangeReceived(unsigned from, unsigned to, bool over) +{ + QByteArray rc = "Stepping range received for step "; + rc += over ? "over" : "into"; + rc += " (0x"; + rc += QByteArray::number(from, 16); + rc += " to 0x"; + rc += QByteArray::number(to, 16); + rc += ')'; + return rc; +} + +// Read address/length off a memory command like m,X +static QPair<quint64, unsigned> readMemoryAddressLength(const QByteArray &cmd) +{ + QPair<quint64, unsigned> rc(0, 0); + const int pos = cmd.indexOf(','); + if (pos == -1) + return rc; + bool ok; + rc.first = cmd.mid(1, pos - 1).toULongLong(&ok, 16); + if (!ok) + return rc; + rc.second = cmd.mid(pos + 1).toUInt(&ok, 16); + if (!ok) + rc.first = 0; + return rc; +} + +void TcfTrkGdbAdapter::handleGdbServerCommand(const QByteArray &cmd) +{ + if (debug) + qDebug("handleGdbServerCommand: %s", cmd.constData()); + // http://sourceware.org/gdb/current/onlinedocs/gdb_34.html + if (0) {} + else if (cmd == "!") { + sendGdbServerAck(); + //sendGdbServerMessage("", "extended mode not enabled"); + sendGdbServerMessage("OK", "extended mode enabled"); + } + + else if (cmd.startsWith('?')) { + logMessage(msgGdbPacket(QLatin1String("Query halted"))); + // Indicate the reason the target halted. + // The reply is the same as for step and continue. + sendGdbServerAck(); + // The command below will trigger fetching a stack trace while + // the process does not seem to be fully functional. Most notably + // the PC points to a 0x9..., which is not in "our" range + //sendGdbServerMessage("T05library:r;", "target halted (library load)"); + //sendGdbServerMessage("S05", "target halted (trap)"); + sendGdbServerMessage("S00", "target halted (trap)"); + //sendGdbServerMessage("O" + QByteArray("Starting...").toHex()); + } + + else if (cmd == "c") { + logMessage(msgGdbPacket(QLatin1String("Continue"))); + sendGdbServerAck(); + sendTrkContinue(); + } + + else if (cmd.startsWith('C')) { + logMessage(msgGdbPacket(QLatin1String("Continue with signal"))); + // C sig[;addr] Continue with signal sig (hex signal number) + //Reply: See section D.3 Stop Reply Packets, for the reply specifications. + sendGdbServerAck(); + bool ok = false; + const uint signalNumber = cmd.mid(1).toUInt(&ok, 16); + m_trkDevice->sendRunControlResumeCommand(TcfTrkCallback(this, &TcfTrkGdbAdapter::handleSignalContinue), + mainThreadContextId(), + QVariant(signalNumber)); + } + + else if (cmd.startsWith('D')) { + sendGdbServerAck(); + sendGdbServerMessage("OK", "shutting down"); + } + + else if (cmd == "g") { + // Read general registers. + if (m_snapshot.registerValid) { + logMessage(msgGdbPacket(QLatin1String("Read registers"))); + sendGdbServerAck(); + reportRegisters(); + } else { + sendGdbServerAck(); + m_trkDevice->sendRegistersGetMRangeCommand(TcfTrkCallback(this, &TcfTrkGdbAdapter::handleAndReportReadRegisters), + m_tcfProcessId, 0, + Symbian::RegisterCount); + } + } + + else if (cmd == "gg") { + // Force re-reading general registers for debugging purpose. + sendGdbServerAck(); + m_snapshot.registerValid = false; + m_trkDevice->sendRegistersGetMRangeCommand(TcfTrkCallback(this, &TcfTrkGdbAdapter::handleAndReportReadRegisters), + m_tcfProcessId, 0, + Symbian::RegisterCount); + } + + else if (cmd.startsWith("salstep,")) { + // Receive address range for current line for future use when stepping. + sendGdbServerAck(); + int pos = cmd.indexOf(',', 8); + m_snapshot.lineFromAddress = cmd.mid(8, pos - 8).toUInt(0, 16); + m_snapshot.lineToAddress = cmd.mid(pos + 1).toUInt(0, 16); + m_snapshot.stepOver = false; + sendGdbServerMessage("", msgStepRangeReceived(m_snapshot.lineFromAddress, m_snapshot.lineToAddress, m_snapshot.stepOver)); + } + + else if (cmd.startsWith("salnext,")) { + // Receive address range for current line for future use when stepping. + sendGdbServerAck(); + const int pos = cmd.indexOf(',', 8); + m_snapshot.lineFromAddress = cmd.mid(8, pos - 8).toUInt(0, 16); + m_snapshot.lineToAddress = cmd.mid(pos + 1).toUInt(0, 16); + m_snapshot.stepOver = true; + sendGdbServerMessage("", msgStepRangeReceived(m_snapshot.lineFromAddress, m_snapshot.lineToAddress, m_snapshot.stepOver)); + } + + else if (cmd.startsWith("Hc")) { + logMessage(msgGdbPacket(QLatin1String("Set thread & continue"))); + // Set thread for subsequent operations (`m', `M', `g', `G', et.al.). + // for step and continue operations + //$Hc-1#09 + sendGdbServerAck(); + sendGdbServerMessage("OK", "Set current thread for step & continue"); + } + + else if (cmd.startsWith("Hg")) { + logMessage(msgGdbPacket(QLatin1String("Set thread"))); + // Set thread for subsequent operations (`m', `M', `g', `G', et.al.). + // for 'other operations. 0 - any thread + //$Hg0#df + sendGdbServerAck(); + m_session.currentThread = cmd.mid(2).toUInt(0, 16); + sendGdbServerMessage("OK", "Set current thread " + + QByteArray::number(m_session.currentThread)); + } + + else if (cmd == "k" || cmd.startsWith("vKill")) { + // Kill inferior process + logMessage(msgGdbPacket(QLatin1String("kill"))); + m_trkDevice->sendProcessTerminateCommand(TcfTrkCallback(), + m_tcfProcessId); + } + + else if (cmd.startsWith('m')) { + logMessage(msgGdbPacket(QLatin1String("Read memory"))); + // m addr,length + sendGdbServerAck(); + const QPair<quint64, unsigned> addrLength = readMemoryAddressLength(cmd); + if (addrLength.first && addrLength.second) { + readMemory(addrLength.first, addrLength.second, m_bufferedMemoryRead); + } else { + sendGdbServerMessage("E20", "Error " + cmd); + } + } + + else if (cmd.startsWith('X')) { + const int dataPos = cmd.indexOf(':'); + if (dataPos == -1) { + sendGdbServerMessage("E20", "Error (colon expected) " + cmd); + return; + } + const QPair<quint64, unsigned> addrLength = readMemoryAddressLength(cmd.left(dataPos)); + if (addrLength.first == 0) { + sendGdbServerMessage("E20", "Error (address = 0) " + cmd); + return; + } + // Requests with len=0 are apparently used to probe writing. + if (addrLength.second == 0) { + sendGdbServerMessage("OK", "Probe memory write at 0x" + QByteArray::number(addrLength.first, 16)); + return; + } + // Data appear to be plain binary. + const QByteArray data = cmd.mid(dataPos + 1); + if (addrLength.second != unsigned(data.size())) { + sendGdbServerMessage("E20", "Data length mismatch " + cmd); + return; + } + logMessage(QString::fromLatin1("Writing %1 bytes from 0x%2: %3"). + arg(addrLength.second).arg(addrLength.first, 0, 16). + arg(QString::fromAscii(data.toHex()))); + m_trkDevice->sendMemorySetCommand(TcfTrkCallback(this, &TcfTrkGdbAdapter::handleWriteMemory), + m_tcfProcessId, + addrLength.first, data); + } + + else if (cmd.startsWith('p')) { + logMessage(msgGdbPacket(QLatin1String("read register"))); + // 0xf == current instruction pointer? + //sendGdbServerMessage("0000", "current IP"); + sendGdbServerAck(); + bool ok = false; + const uint registerNumber = cmd.mid(1).toUInt(&ok, 16); + if (m_snapshot.registerValid) { + QByteArray logMsg = "Read Register"; + if (registerNumber == RegisterPSGdb) { + QByteArray ba; + appendInt(&ba, m_snapshot.registers[RegisterPSTrk], trk::LittleEndian); + logMsg += dumpRegister(registerNumber, m_snapshot.registers[RegisterPSTrk]); + sendGdbServerMessage(ba.toHex(), logMsg); + } else if (registerNumber < 16) { + QByteArray ba; + appendInt(&ba, m_snapshot.registers[registerNumber], trk::LittleEndian); + logMsg += dumpRegister(registerNumber, m_snapshot.registers[registerNumber]); + sendGdbServerMessage(ba.toHex(), logMsg); + } else { + sendGdbServerMessage("0000", "read single unknown register #" + + QByteArray::number(registerNumber)); + //sendGdbServerMessage("E01", "read single unknown register"); + } + } else { + //qDebug() << "Fetching single register"; + m_trkDevice->sendRegistersGetMRangeCommand(TcfTrkCallback(this, &TcfTrkGdbAdapter::handleAndReportReadRegistersAfterStop), + m_tcfProcessId, registerNumber, 1); + } + } + + else if (cmd.startsWith('P')) { + logMessage(msgGdbPacket(QLatin1String("write register"))); + // $Pe=70f96678#d3 + sendGdbServerAck(); + int pos = cmd.indexOf('='); + QByteArray regName = cmd.mid(1, pos - 1); + QByteArray valueName = cmd.mid(pos + 1); + bool ok = false; + const uint registerNumber = regName.toUInt(&ok, 16); + const uint value = trk::swapEndian(valueName.toUInt(&ok, 16)); + // FIXME: Assume all goes well. + m_snapshot.registers[registerNumber] = value; + logMessage(QString::fromLatin1("Setting register #%1 to 0x%2").arg(registerNumber).arg(value, 0, 16)); + m_trkDevice->sendRegistersSetCommand(TcfTrkCallback(this, &TcfTrkGdbAdapter::handleWriteRegister), + m_tcfProcessId, registerNumber, value, QVariant(registerNumber)); + // Note that App TRK refuses to write registers 13 and 14 + } + + else if (cmd == "qAttached") { + //$qAttached#8f + // 1: attached to an existing process + // 0: created a new process + sendGdbServerAck(); + sendGdbServerMessage(QByteArray(1, '0'), "new process created"); + } + + else if (cmd.startsWith("qC")) { + logMessage(msgGdbPacket(QLatin1String("query thread id"))); + // Return the current thread ID + //$qC#b4 + QTC_ASSERT(!m_session.threads.isEmpty(), return) + sendGdbServerAck(); + sendGdbServerMessage("QC" + QByteArray::number(m_session.threads.front(), 16)); + } + + else if (cmd.startsWith("qSupported")) { + //$qSupported#37 + //$qSupported:multiprocess+#c6 + //logMessage("Handling 'qSupported'"); + sendGdbServerAck(); + sendGdbServerMessage( + "PacketSize=7cf;" + "QPassSignals+;" + "QStartNoAckMode+;" + "qXfer:libraries:read+;" + //"qXfer:auxv:read+;" + "qXfer:features:read+"); + } + + else if (cmd.startsWith("qThreadExtraInfo")) { + // $qThreadExtraInfo,1f9#55 + sendGdbServerAck(); + sendGdbServerMessage(QByteArray("Nothing special").toHex()); + } + + else if (cmd == "qfDllInfo") { + // That's the _first_ query package. + // Happens with gdb 6.4.50.20060226-cvs / CodeSourcery. + // Never made it into FSF gdb that got qXfer:libraries:read instead. + // http://sourceware.org/ml/gdb/2007-05/msg00038.html + // Name=hexname,TextSeg=textaddr[,DataSeg=dataaddr] + sendGdbServerAck(); + if (!m_session.libraries.isEmpty()) { + QByteArray response(1, 'm'); + // FIXME: Limit packet length by using qsDllInfo packages? + for (int i = 0; i != m_session.libraries.size(); ++i) { + if (i) + response += ';'; + const trk::Library &lib = m_session.libraries.at(i); + response += "Name=" + lib.name.toHex() + + ",TextSeg=" + trk::hexNumber(lib.codeseg) + + ",DataSeg=" + trk::hexNumber(lib.dataseg); + } + sendGdbServerMessage(response, "library information transferred"); + } else { + sendGdbServerMessage(QByteArray(1, 'l'), "library information transfer finished"); + } + } + + else if (cmd == "qsDllInfo") { + // That's a following query package + sendGdbServerAck(); + sendGdbServerMessage(QByteArray(1, 'l'), "library information transfer finished"); + } + + else if (cmd == "qPacketInfo") { + // happens with gdb 6.4.50.20060226-cvs / CodeSourcery + // deprecated by qSupported? + sendGdbServerAck(); + sendGdbServerMessage("", "FIXME: nothing?"); + } + + else if (cmd == "qOffsets") { + sendGdbServerAck(); + QByteArray answer = "TextSeg="; + answer.append(QByteArray::number(m_session.codeseg, 16)); + answer.append(";DataSeg="); + answer.append(QByteArray::number(m_session.dataseg, 16)); + sendGdbServerMessage(answer); + } + + else if (cmd == "qSymbol::") { + if (m_verbose) + logMessage(msgGdbPacket(QLatin1String("notify can handle symbol lookup"))); + // Notify the target that GDB is prepared to serve symbol lookup requests. + sendGdbServerAck(); + if (1) + sendGdbServerMessage("OK", "no further symbols needed"); + else + sendGdbServerMessage("qSymbol:" + QByteArray("_Z7E32Mainv").toHex(), + "ask for more"); + } + + else if (cmd.startsWith("qXfer:features:read:target.xml:")) { + // $qXfer:features:read:target.xml:0,7ca#46...Ack + sendGdbServerAck(); + sendGdbServerMessage("l<target><architecture>arm</architecture></target>"); + } + + else if (cmd == "qfThreadInfo") { + // That's the _first_ query package. + sendGdbServerAck(); + if (!m_session.threads.isEmpty()) { + QByteArray response(1, 'm'); + // FIXME: Limit packet length by using qsThreadInfo packages? + qDebug() << "CURRENT THREAD: " << m_session.tid; + response += trk::hexNumber(m_session.tid); + sendGdbServerMessage(response, "thread information transferred"); + } else { + sendGdbServerMessage(QByteArray(1, 'l'), "thread information transfer finished"); + } + } + + else if (cmd == "qsThreadInfo") { + // That's a following query package + sendGdbServerAck(); + sendGdbServerMessage(QByteArray(1, 'l'), "thread information transfer finished"); + } + + else if (cmd.startsWith("qXfer:libraries:read")) { + //qDebug() << "COMMAND: " << cmd; + sendGdbServerAck(); + QByteArray response = "l<library-list>"; + for (int i = 0; i != m_session.libraries.size(); ++i) { + const trk::Library &lib = m_session.libraries.at(i); + response += "<library name=\"" + lib.name + "\">"; + //response += "<segment address=\"0x" + hexNumber(lib.codeseg) + "\"/>"; + response += "<section address=\"0x" + trk::hexNumber(lib.codeseg) + "\"/>"; + response += "<section address=\"0x" + trk::hexNumber(lib.dataseg) + "\"/>"; + response += "<section address=\"0x" + trk::hexNumber(lib.dataseg) + "\"/>"; + response += "</library>"; + } + response += "</library-list>"; + sendGdbServerMessage(response, "library information transferred"); + } + + else if (cmd == "QStartNoAckMode") { + //$qSupported#37 + logMessage("Handling 'QStartNoAckMode'"); + sendGdbServerAck(); + sendGdbServerMessage("OK", "ack no-ack mode"); + m_gdbAckMode = false; + } + + else if (cmd.startsWith("QPassSignals")) { + // list of signals to pass directly to inferior + // $QPassSignals:e;10;14;17;1a;1b;1c;21;24;25;4c;#8f + // happens only if "QPassSignals+;" is qSupported + sendGdbServerAck(); + // FIXME: use the parameters + sendGdbServerMessage("OK", "passing signals accepted"); + } + + else if (cmd == "s" || cmd.startsWith("vCont;s")) { + logMessage(msgGdbPacket(QString::fromLatin1("Step range from 0x%1"). + arg(m_snapshot.registers[RegisterPC], 0, 16))); + sendGdbServerAck(); + m_running = true; + sendTrkStepRange(); + } + + else if (cmd.startsWith('T')) { + // FIXME: check whether thread is alive + sendGdbServerAck(); + sendGdbServerMessage("OK"); // pretend all is well + } + + else if (cmd == "vCont?") { + // actions supported by the vCont packet + sendGdbServerAck(); + sendGdbServerMessage("vCont;c;C;s;S"); // we don't support vCont. + } + + else if (cmd == "vCont;c") { + // vCont[;action[:thread-id]]...' + sendGdbServerAck(); + m_running = true; + sendTrkContinue(); + } + + else if (cmd.startsWith("Z0,") || cmd.startsWith("Z1,")) { + // Insert breakpoint + sendGdbServerAck(); + logMessage(msgGdbPacket(QLatin1String("Insert breakpoint"))); + // $Z0,786a4ccc,4#99 + const int pos = cmd.lastIndexOf(','); + bool ok1 = false; + bool ok2 = false; + const uint addr = cmd.mid(3, pos - 3).toUInt(&ok1, 16); + const uint len = cmd.mid(pos + 1).toUInt(&ok2, 16); + if (!ok1) { + logMessage("MISPARSED ADDRESS FROM " + cmd + + " (" + cmd.mid(3, pos - 3) + ")" , LogError); + } else if (!ok2) { + logMessage("MISPARSED BREAKPOINT SIZE FROM " + cmd, LogError); + } else { + //qDebug() << "ADDR: " << hexNumber(addr) << " LEN: " << len; + logMessage(_("Inserting breakpoint at 0x%1, %2") + .arg(addr, 0, 16).arg(len)); + // const QByteArray ba = trkBreakpointMessage(addr, len, len == 4); + tcftrk::Breakpoint bp(addr); + bp.size = len; + bp.setContextId(m_session.pid); + // We use the automatic ids calculated from the location + // address instead of the map in snapshot. + m_trkDevice->sendBreakpointsAddCommand(TcfTrkCallback(this, &TcfTrkGdbAdapter::handleAndReportSetBreakpoint), + bp); + } + } + + else if (cmd.startsWith("z0,") || cmd.startsWith("z1,")) { + // Remove breakpoint + sendGdbServerAck(); + logMessage(msgGdbPacket(QLatin1String("Remove breakpoint"))); + // $z0,786a4ccc,4#99 + const int pos = cmd.lastIndexOf(','); + const uint addr = cmd.mid(3, pos - 3).toUInt(0, 16); + m_trkDevice->sendBreakpointsRemoveCommand(TcfTrkCallback(this, &TcfTrkGdbAdapter::handleClearBreakpoint), + tcftrk::Breakpoint::idFromLocation(addr)); + } + + else if (cmd.startsWith("qPart:") || cmd.startsWith("qXfer:")) { + QByteArray data = cmd.mid(1 + cmd.indexOf(':')); + // "qPart:auxv:read::0,147": Read OS auxiliary data (see info aux) + bool handled = false; + if (data.startsWith("auxv:read::")) { + const int offsetPos = data.lastIndexOf(':') + 1; + const int commaPos = data.lastIndexOf(','); + if (commaPos != -1) { + bool ok1 = false, ok2 = false; + const int offset = data.mid(offsetPos, commaPos - offsetPos) + .toUInt(&ok1, 16); + const int length = data.mid(commaPos + 1).toUInt(&ok2, 16); + if (ok1 && ok2) { + const QString msg = _("Read of OS auxiliary " + "vector (%1, %2) not implemented.").arg(offset).arg(length); + logMessage(msgGdbPacket(msg), LogWarning); + sendGdbServerMessage("E20", msg.toLatin1()); + handled = true; + } + } + } // auxv read + + if (!handled) { + const QString msg = QLatin1String("FIXME unknown 'XFER'-request: ") + + QString::fromAscii(cmd); + logMessage(msgGdbPacket(msg), LogWarning); + sendGdbServerMessage("E20", msg.toLatin1()); + } + + } // qPart/qXfer + else { + logMessage(msgGdbPacket(QLatin1String("FIXME unknown: ") + + QString::fromAscii(cmd)), LogWarning); + } +} + +void TcfTrkGdbAdapter::interruptInferior() +{ + m_trkDevice->sendRunControlSuspendCommand(TcfTrkCallback(), m_tcfProcessId); +} + +void TcfTrkGdbAdapter::startAdapter() +{ + const ushort tcfTrkPort = 1534; + + m_snapshot.fullReset(); + m_firstModuleResumableEvent = true; + m_tcfProcessId.clear(); + + // Retrieve parameters + const DebuggerStartParameters ¶meters = startParameters(); + m_remoteExecutable = parameters.executable; + m_remoteArguments = parameters.processArgs; + m_symbolFile = parameters.symbolFileName; + + QString tcfTrkAddress; + QSharedPointer<QTcpSocket> tcfTrkSocket(new QTcpSocket); + m_trkDevice->setDevice(tcfTrkSocket); + m_trkIODevice = tcfTrkSocket; + + if (debug) + qDebug() << parameters.processArgs; + // Fixme: 1 of 3 testing hacks. + if (parameters.processArgs.size() >= 5 && parameters.processArgs.at(0) == _("@tcf@")) { + m_remoteExecutable = parameters.processArgs.at(1); + m_uid = parameters.processArgs.at(2).toUInt(0, 16); + m_symbolFile = parameters.processArgs.at(3); + tcfTrkAddress = parameters.processArgs.at(4); + m_remoteArguments.clear(); + } else { + emit adapterStartFailed(_("Parameter error"), QString()); + } + + // Unixish gdbs accept only forward slashes + m_symbolFile.replace(QLatin1Char('\\'), QLatin1Char('/')); + // Start + QTC_ASSERT(state() == EngineStarting, qDebug() << state()); + setState(AdapterStarting); + showMessage(_("TRYING TO START ADAPTER")); + logMessage(QLatin1String("### Starting TcfTrkGdbAdapter")); + + QTC_ASSERT(m_gdbServer == 0, delete m_gdbServer); + QTC_ASSERT(m_gdbConnection == 0, m_gdbConnection = 0); + m_gdbServer = new QTcpServer(this); + + if (!m_gdbServer->listen(QHostAddress(gdbServerIP()), gdbServerPort())) { + QString msg = QString("Unable to start the gdb server at %1: %2.") + .arg(m_gdbServerName).arg(m_gdbServer->errorString()); + logMessage(msg, LogError); + emit adapterStartFailed(msg, QString()); + return; + } + + logMessage(QString("Gdb server running on %1.\nLittle endian assumed.") + .arg(m_gdbServerName)); + + connect(m_gdbServer, SIGNAL(newConnection()), + this, SLOT(handleGdbConnection())); + + logMessage(QString::fromLatin1("Connecting to TCF TRK on %1:%2") + .arg(tcfTrkAddress).arg(tcfTrkPort)); + tcfTrkSocket->connectToHost(tcfTrkAddress, tcfTrkPort); +} + +void TcfTrkGdbAdapter::startInferior() +{ + QTC_ASSERT(state() == InferiorStarting, qDebug() << state()); + m_trkDevice->sendProcessStartCommand(TcfTrkCallback(this, &TcfTrkGdbAdapter::handleCreateProcess), + m_remoteExecutable, m_uid, m_remoteArguments, + QString(), true); + +} + +void TcfTrkGdbAdapter::handleCreateProcess(const tcftrk::TcfTrkCommandResult &result) +{ + if (debug) + qDebug() << "ProcessCreated: " << result.toString(); + if (!result) { + const QString errorMessage = result.errorString(); + logMessage(QString::fromLatin1("Failed to start process: %1").arg(errorMessage), LogError); + emit inferiorStartFailed(result.errorString()); + return; + } + QTC_ASSERT(!result.values.isEmpty(), return); + + tcftrk::RunControlContext ctx; + ctx.parse(result.values.front()); + logMessage(ctx.toString()); + + m_session.pid = ctx.osid.toUInt(); + m_session.tid = 0; // Id is "p232.t34435" + const int sepPos = ctx.id.indexOf('t'); + if (sepPos != -1) + m_session.tid = ctx.id.right(sepPos + 1).toUInt(); + // See ModuleLoadSuspendedEvent for the rest. + m_session.codeseg = 0; + m_session.dataseg = 0; +} + +void TcfTrkGdbAdapter::startInferiorPhase2() +{ + m_engine->continueInferiorInternal(); +} + +// +// AbstractGdbAdapter interface implementation +// + +void TcfTrkGdbAdapter::write(const QByteArray &data) +{ + // Write magic packets directly to TRK. + if (data.startsWith("@#")) { + QByteArray data1 = data.mid(2); + if (data1.endsWith(char(10))) + data1.chop(1); + if (data1.endsWith(char(13))) + data1.chop(1); + if (data1.endsWith(' ')) + data1.chop(1); + bool ok; + const uint addr = data1.toUInt(&ok, 0); + logMessage(QString::fromLatin1("Direct step (@#) 0x%1: not implemented").arg(addr, 0, 16), LogError); + // directStep(addr); + return; + } + if (data.startsWith("@$")) { + QByteArray ba = QByteArray::fromHex(data.mid(2)); + qDebug() << "Writing: " << quoteUnprintableLatin1(ba); + // if (ba.size() >= 1) + // sendTrkMessage(ba.at(0), TrkCB(handleDirectTrk), ba.mid(1)); + return; + } + if (data.startsWith("@@")) { + logMessage(QLatin1String("Direct write (@@): not implemented"), LogError); + return; + } + m_gdbProc.write(data); +} + + +void TcfTrkGdbAdapter::cleanup() +{ + delete m_gdbServer; + m_gdbServer = 0; + if (!m_trkIODevice.isNull()) { + if (QAbstractSocket *socket = qobject_cast<QAbstractSocket *>(m_trkIODevice.data())) { + if (socket->state() == QAbstractSocket::ConnectedState) + socket->disconnect(); + } else { + m_trkIODevice->close(); + } + } +} + +void TcfTrkGdbAdapter::shutdown() +{ + cleanup(); +} + +void TcfTrkGdbAdapter::trkReloadRegisters() +{ + // Take advantage of direct access to cached register values. + QTC_ASSERT(m_snapshot.registerValid, /**/); + RegisterHandler *handler = m_engine->registerHandler(); + Registers registers = handler->registers(); + + QTC_ASSERT(registers.size() >= 26, + qDebug() << "HAVE: " << registers.size(); return); + for (int i = 0; i < 16; ++i) { + Register ® = registers[i]; + QString value = trk::hexxNumber(m_snapshot.registers[i]); + reg.changed = (value != reg.value); + if (reg.changed) + reg.value = value; + } + Register ® = registers[25]; + QString value = trk::hexxNumber(m_snapshot.registers[16]); + reg.changed = (value != reg.value); + if (reg.changed) + reg.value = value; + handler->setRegisters(registers); +} + +void TcfTrkGdbAdapter::trkReloadThreads() +{ + // Take advantage of direct access to cached register values. + QTC_ASSERT(m_snapshot.registerValid, /**/); + Threads threads; + foreach (const trk::Session::Thread &thread, m_session.threads) { + threads.append(thread); + } + ThreadsHandler *handler = m_engine->threadsHandler(); + handler->setThreads(threads); +} + +void TcfTrkGdbAdapter::handleWriteRegister(const tcftrk::TcfTrkCommandResult &result) +{ + const int registerNumber = result.cookie.toInt(); + if (result) { + sendGdbServerMessage("OK"); + } else { + logMessage(QString::fromLatin1("ERROR writing register #%1: %2").arg(registerNumber).arg(result.errorString()), LogError); + sendGdbServerMessage("E01"); + } +} + +void TcfTrkGdbAdapter::reportRegisters() +{ + QByteArray ba; + for (int i = 0; i < 16; ++i) { + const uint reg = trk::swapEndian(m_snapshot.registers[i]); + ba += trk::hexNumber(reg, 8); + } + QByteArray logMsg = "REGISTER CONTENTS: "; + if (m_verbose > 1) { + for (int i = 0; i < RegisterCount; ++i) { + logMsg += dumpRegister(i, m_snapshot.registers[i]); + logMsg += ' '; + } + } + sendGdbServerMessage(ba, logMsg); +} + +void TcfTrkGdbAdapter::handleReadRegisters(const tcftrk::TcfTrkCommandResult &result) +{ + logMessage(" REGISTER RESULT: " + result.toString()); + if (!result) { + logMessage("ERROR: " + result.errorString(), LogError); + return; + } + if (result.values.isEmpty() || result.values.front().type() != tcftrk::JsonValue::Array) { + logMessage(_("Format error in register message: ") + result.toString(), LogError); + return; + } + unsigned i = result.cookie.toUInt(); + foreach (const tcftrk::JsonValue &jr, result.values.front().children()) + m_snapshot.registers[i++] = jr.data().toUInt(0, 16); + m_snapshot.registerValid = true; + if (debug) + qDebug() << "handleReadRegisters: " << m_snapshot.toString(); +} + +void TcfTrkGdbAdapter::handleAndReportReadRegisters(const tcftrk::TcfTrkCommandResult &result) +{ + handleReadRegisters(result); + reportRegisters(); +} + +void TcfTrkGdbAdapter::handleAndReportReadRegister(const tcftrk::TcfTrkCommandResult &result) +{ + handleReadRegisters(result); + uint registerNumber = result.cookie.toUInt(); + QByteArray logMsg = "Read Register"; + if (registerNumber == RegisterPSGdb) { + QByteArray ba; + trk::appendInt(&ba, m_snapshot.registers[RegisterPSTrk], trk::LittleEndian); + logMsg += dumpRegister(registerNumber, m_snapshot.registers[RegisterPSTrk]); + sendGdbServerMessage(ba.toHex(), logMsg); + } else if (registerNumber < 16) { + QByteArray ba; + trk::appendInt(&ba, m_snapshot.registers[registerNumber], trk::LittleEndian); + logMsg += dumpRegister(registerNumber, m_snapshot.registers[registerNumber]); + sendGdbServerMessage(ba.toHex(), logMsg); + } else { + sendGdbServerMessage("0000", "read single unknown register #" + + QByteArray::number(registerNumber)); + //sendGdbServerMessage("E01", "read single unknown register"); + } +} + +void TcfTrkGdbAdapter::handleAndReportReadRegistersAfterStop(const tcftrk::TcfTrkCommandResult &result) +{ + handleReadRegisters(result); + QByteArray ba = "T05"; + for (int i = 0; i < 16; ++i) + appendRegister(&ba, i, m_snapshot.registers[i]); + // FIXME: those are not understood by gdb 6.4 + //for (int i = 16; i < 25; ++i) + // appendRegister(&ba, i, 0x0); + appendRegister(&ba, RegisterPSGdb, m_snapshot.registers[RegisterPSTrk]); + //qDebug() << "TrkGdbAdapter::handleAndReportReadRegistersAfterStop" << ba; + sendGdbServerMessage(ba, "Registers"); +} + +void TcfTrkGdbAdapter::handleAndReportSetBreakpoint(const tcftrk::TcfTrkCommandResult &result) +{ + if (result) { + sendGdbServerMessage("OK"); + } else { + logMessage(QLatin1String("Error setting breakpoint: ") + result.errorString(), LogError); + sendGdbServerMessage("E21"); + } +} + +void TcfTrkGdbAdapter::handleClearBreakpoint(const tcftrk::TcfTrkCommandResult &result) +{ + logMessage("CLEAR BREAKPOINT "); + if (!result) + logMessage("Error clearing breakpoint: " + result.errorString(), LogError); + sendGdbServerMessage("OK"); +} + +void TcfTrkGdbAdapter::handleSignalContinue(const tcftrk::TcfTrkCommandResult &result) +{ + uint signalNumber = result.cookie.toUInt(); + logMessage(QString::fromLatin1(" HANDLE SIGNAL CONTINUE %1").arg(signalNumber)); + sendGdbServerMessage("O" + QByteArray("Console output").toHex()); + sendGdbServerMessage("W81"); // "Process exited with result 1 +} + +void TcfTrkGdbAdapter::readMemory(uint addr, uint len, bool buffered) +{ + Q_ASSERT(len < (2 << 16)); + + // We try to get medium-sized chunks of data from the device + if (m_verbose > 2) + logMessage(_("readMemory %1 bytes from 0x%2 blocksize=%3") + .arg(len).arg(addr, 0, 16).arg(MemoryChunkSize)); + + m_snapshot.wantedMemory = MemoryRange(addr, addr + len); + tryAnswerGdbMemoryRequest(buffered); +} + +static QString msgMemoryReadError(uint addr, uint len = 0) +{ + const QString lenS = len ? QString::number(len) : QLatin1String("<unknown>"); + return _("Memory read error at: 0x%1 %2").arg(addr, 0, 16).arg(lenS); +} + +// Format log message for memory access with some smartness about registers +QByteArray TcfTrkGdbAdapter::memoryReadLogMessage(uint addr, const QByteArray &ba) const +{ + QByteArray logMsg = "memory contents"; + if (m_verbose > 1) { + logMsg += " addr: " + trk::hexxNumber(addr); + // indicate dereferencing of registers + if (ba.size() == 4) { + if (addr == m_snapshot.registers[RegisterPC]) { + logMsg += "[PC]"; + } else if (addr == m_snapshot.registers[RegisterPSTrk]) { + logMsg += "[PSTrk]"; + } else if (addr == m_snapshot.registers[RegisterSP]) { + logMsg += "[SP]"; + } else if (addr == m_snapshot.registers[RegisterLR]) { + logMsg += "[LR]"; + } else if (addr > m_snapshot.registers[RegisterSP] && + (addr - m_snapshot.registers[RegisterSP]) < 10240) { + logMsg += "[SP+"; // Stack area ...stack seems to be top-down + logMsg += QByteArray::number(addr - m_snapshot.registers[RegisterSP]); + logMsg += ']'; + } + } + logMsg += " length "; + logMsg += QByteArray::number(ba.size()); + logMsg += " :"; + logMsg += trk::stringFromArray(ba, 16).toAscii(); + } + return logMsg; +} + +void TcfTrkGdbAdapter::sendMemoryGetCommand(const MemoryRange &range, bool buffered) +{ + const QVariant cookie = QVariant::fromValue(range); + const TcfTrkCallback cb = buffered ? + TcfTrkCallback(this, &TcfTrkGdbAdapter::handleReadMemoryBuffered) : + TcfTrkCallback(this, &TcfTrkGdbAdapter::handleReadMemoryUnbuffered); + m_trkDevice->sendMemoryGetCommand(cb, m_tcfProcessId, range.from, range.size(), cookie); +} + +void TcfTrkGdbAdapter::handleReadMemoryBuffered(const tcftrk::TcfTrkCommandResult &result) +{ + QTC_ASSERT(qVariantCanConvert<MemoryRange>(result.cookie), return); + + const QByteArray memory = tcftrk::TcfTrkDevice::parseMemoryGet(result); + const MemoryRange range = result.cookie.value<MemoryRange>(); + + if (unsigned(memory.size()) != range.size()) { + logMessage(_("TEMPORARY: ") + msgMemoryReadError(range.from, range.size())); + logMessage(_("RETRYING UNBUFFERED")); + // FIXME: This does not handle large requests properly. + sendMemoryGetCommand(range, false); + return; + } + m_snapshot.insertMemory(range, memory); + tryAnswerGdbMemoryRequest(true); +} + +void TcfTrkGdbAdapter::handleReadMemoryUnbuffered(const tcftrk::TcfTrkCommandResult &result) +{ + QTC_ASSERT(qVariantCanConvert<MemoryRange>(result.cookie), return); + + const QByteArray memory = tcftrk::TcfTrkDevice::parseMemoryGet(result); + const MemoryRange range = result.cookie.value<MemoryRange>(); + + if (unsigned(memory.size()) != range.size()) { + logMessage(_("TEMPORARY: ") + msgMemoryReadError(range.from, range.size())); + logMessage(_("RETRYING UNBUFFERED")); + const QByteArray ba = "E20"; + sendGdbServerMessage(ba, msgMemoryReadError(32, range.from).toLatin1()); + return; + } + m_snapshot.insertMemory(range, memory); + tryAnswerGdbMemoryRequest(false); +} + +void TcfTrkGdbAdapter::tryAnswerGdbMemoryRequest(bool buffered) +{ + //logMessage("TRYING TO ANSWER MEMORY REQUEST "); + MemoryRange wanted = m_snapshot.wantedMemory; + MemoryRange needed = m_snapshot.wantedMemory; + MEMORY_DEBUG("WANTED: " << wanted); + Snapshot::Memory::const_iterator it = m_snapshot.memory.begin(); + Snapshot::Memory::const_iterator et = m_snapshot.memory.end(); + for ( ; it != et; ++it) { + MEMORY_DEBUG(" NEEDED STEP: " << needed); + needed -= it.key(); + } + MEMORY_DEBUG("NEEDED FINAL: " << needed); + + if (needed.to == 0) { + // FIXME: need to combine chunks first. + + // All fine. Send package to gdb. + it = m_snapshot.memory.begin(); + et = m_snapshot.memory.end(); + for ( ; it != et; ++it) { + if (it.key().from <= wanted.from && wanted.to <= it.key().to) { + int offset = wanted.from - it.key().from; + int len = wanted.to - wanted.from; + QByteArray ba = it.value().mid(offset, len); + sendGdbServerMessage(ba.toHex(), memoryReadLogMessage(wanted.from, ba)); + return; + } + } + // Happens when chunks are not combined + QTC_ASSERT(false, /**/); + showMessage("CHUNKS NOT COMBINED"); +# ifdef MEMORY_DEBUG + qDebug() << "CHUNKS NOT COMBINED"; + it = m_snapshot.memory.begin(); + et = m_snapshot.memory.end(); + for ( ; it != et; ++it) + qDebug() << trk::hexNumber(it.key().from) << trk::hexNumber(it.key().to); + qDebug() << "WANTED" << wanted.from << wanted.to; +# endif + sendGdbServerMessage("E22", ""); + return; + } + + MEMORY_DEBUG("NEEDED AND UNSATISFIED: " << needed); + if (buffered) { + uint blockaddr = (needed.from / MemoryChunkSize) * MemoryChunkSize; + logMessage(_("Requesting buffered memory %1 bytes from 0x%2") + .arg(MemoryChunkSize).arg(blockaddr, 0, 16)); + MemoryRange range(blockaddr, blockaddr + MemoryChunkSize); + MEMORY_DEBUG(" FETCH BUFFERED MEMORY : " << range); + sendMemoryGetCommand(range, true); + } else { // Unbuffered, direct requests + int len = needed.to - needed.from; + logMessage(_("Requesting unbuffered memory %1 bytes from 0x%2") + .arg(len).arg(needed.from, 0, 16)); + sendMemoryGetCommand(needed, false); + MEMORY_DEBUG(" FETCH UNBUFFERED MEMORY : " << needed); + } +} + +void TcfTrkGdbAdapter::handleWriteMemory(const tcftrk::TcfTrkCommandResult &result) +{ + if (result) { + sendGdbServerMessage("OK", "Write memory"); + } else { + logMessage(QLatin1String("Error writing memory: ") + result.errorString(), LogError); + sendGdbServerMessage("E21"); + } +} + +QByteArray TcfTrkGdbAdapter::mainThreadContextId() const +{ + return tcftrk::RunControlContext::tcfId(m_session.pid, m_session.tid); +} + +void TcfTrkGdbAdapter::sendTrkContinue() +{ + m_trkDevice->sendRunControlResumeCommand(TcfTrkCallback(), mainThreadContextId()); +} + +void TcfTrkGdbAdapter::sendTrkStepRange() +{ + uint from = m_snapshot.lineFromAddress; + uint to = m_snapshot.lineToAddress; + const uint pc = m_snapshot.registers[RegisterPC]; + if (from <= pc && pc <= to) { + const QString msg = QString::fromLatin1("Step in 0x%1 .. 0x%2 instead of 0x%3..."). + arg(from, 0, 16).arg(to, 0, 16).arg(pc, 0, 16); + showMessage(msg); + } else { + from = pc; + to = pc; + } + // TODO: Step range does not seem to work yet? + const tcftrk::RunControlResumeMode mode = (from == to && to == pc) ? + (m_snapshot.stepOver ? tcftrk::RM_STEP_OVER : tcftrk::RM_STEP_INTO) : + (m_snapshot.stepOver ? tcftrk::RM_STEP_OVER_RANGE : tcftrk::RM_STEP_INTO_RANGE); + + logMessage(QString::fromLatin1("Stepping from 0x%1 to 0x%2 (current PC=0x%3), mode %4"). + arg(from, 0, 16).arg(to, 0, 16).arg(pc).arg(int(mode))); + m_trkDevice->sendRunControlResumeCommand(TcfTrkCallback(this, &TcfTrkGdbAdapter::handleStep), + mainThreadContextId(), + mode, 1, from, to); +} + +void TcfTrkGdbAdapter::handleStep(const tcftrk::TcfTrkCommandResult &result) +{ + + if (!result) { // Try fallback with Continue. + logMessage(QString::fromLatin1("Error while stepping: %1 (fallback to 'continue')").arg(result.errorString()), LogWarning); + sendTrkContinue(); + // Doing nothing as below does not work as gdb seems to insist on + // making some progress through a 'step'. + //sendTrkMessage(0x12, + // TrkCB(handleAndReportReadRegistersAfterStop), + // trkReadRegistersMessage()); + return; + } + // The gdb server response is triggered later by the Stop Reply packet. + logMessage("STEP FINISHED " + currentTime()); +} + + +} // namespace Internal +} // namespace Debugger diff --git a/src/plugins/debugger/gdb/tcftrkgdbadapter.h b/src/plugins/debugger/gdb/tcftrkgdbadapter.h new file mode 100644 index 0000000000000000000000000000000000000000..0953700cdb8b2c8a02f42a4c5a37fe47d01d6e89 --- /dev/null +++ b/src/plugins/debugger/gdb/tcftrkgdbadapter.h @@ -0,0 +1,186 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef DEBUGGER_TCFTRKGDBADAPTER_H +#define DEBUGGER_TCFTRKGDBADAPTER_H + +#include "abstractgdbadapter.h" + +#include "callback.h" +#include "trkutils.h" +#include "symbian.h" + +#include <QtCore/QPointer> +#include <QtCore/QSharedPointer> +#include <QtCore/QStringList> +#include <QtCore/QHash> + +QT_BEGIN_NAMESPACE +class QTcpServer; +class QTcpSocket; +class QIODevice; +QT_END_NAMESPACE + +namespace tcftrk { + struct TcfTrkCommandResult; + class TcfTrkDevice; + class TcfTrkEvent; + class TcfTrkRunControlModuleLoadContextSuspendedEvent; +} + +namespace Debugger { +namespace Internal { + +struct MemoryRange; +struct GdbResult; + +/////////////////////////////////////////////////////////////////////// +// +// TcfTrkGdbAdapter +// +/////////////////////////////////////////////////////////////////////// + +class TcfTrkGdbAdapter : public AbstractGdbAdapter +{ + Q_OBJECT + +public: + typedef trk::Callback<const GdbResult &> GdbResultCallback; + typedef trk::Callback<const tcftrk::TcfTrkCommandResult &> TcfTrkCallback; + typedef trk::Callback<const GdbResponse &> GdbCallback; + + explicit TcfTrkGdbAdapter(GdbEngine *engine); + virtual ~TcfTrkGdbAdapter(); + void setGdbServerName(const QString &name); + QString gdbServerName() const { return m_gdbServerName; } + QString gdbServerIP() const; + uint gdbServerPort() const; + Q_SLOT void setVerbose(const QVariant &value); + void setVerbose(int verbose); + void setBufferedMemoryRead(bool b) { m_bufferedMemoryRead = b; } + + void trkReloadRegisters(); + void trkReloadThreads(); + +signals: + void output(const QString &msg); + +public: + // + // Implementation of GdbProcessBase + // + void start(const QString &program, const QStringList &args, + QIODevice::OpenMode mode = QIODevice::ReadWrite); + void write(const QByteArray &data); + bool isTrkAdapter() const { return true; } + + virtual DumperHandling dumperHandling() const { return DumperNotAvailable; } + +private: + void startAdapter(); + void startInferior(); + void startInferiorPhase2(); + void interruptInferior(); + void shutdown(); + void handleWriteRegister(const tcftrk::TcfTrkCommandResult &result); + void reportRegisters(); + void handleReadRegisters(const tcftrk::TcfTrkCommandResult &result); + void handleAndReportReadRegisters(const tcftrk::TcfTrkCommandResult &result); + void handleAndReportReadRegister(const tcftrk::TcfTrkCommandResult &result); + void handleAndReportReadRegistersAfterStop(const tcftrk::TcfTrkCommandResult &result); + void handleAndReportSetBreakpoint(const tcftrk::TcfTrkCommandResult &result); + void handleClearBreakpoint(const tcftrk::TcfTrkCommandResult &result); + void handleSignalContinue(const tcftrk::TcfTrkCommandResult &result); + void readMemory(uint addr, uint len, bool buffered); + void handleReadMemoryBuffered(const tcftrk::TcfTrkCommandResult &result); + void handleReadMemoryUnbuffered(const tcftrk::TcfTrkCommandResult &result); + void handleWriteMemory(const tcftrk::TcfTrkCommandResult &result); + void tryAnswerGdbMemoryRequest(bool buffered); + inline void sendMemoryGetCommand(const MemoryRange &range, bool buffered); + inline QByteArray mainThreadContextId() const; + QByteArray memoryReadLogMessage(uint addr, const QByteArray &ba) const; + + AbstractGdbProcess *gdbProc() { return &m_gdbProc; } + + void cleanup(); + + void handleTargetRemote(const GdbResponse &response); + + QString m_gdbServerName; // 127.0.0.1:(2222+uid) + bool m_running; + tcftrk::TcfTrkDevice *m_trkDevice; + QSharedPointer<QIODevice> m_trkIODevice; + + // + // Gdb + // + Q_SLOT void handleGdbConnection(); + Q_SLOT void readGdbServerCommand(); + Q_SLOT void tcftrkDeviceError(const QString &); + void startGdb(); + Q_SLOT void tcftrkEvent(const tcftrk::TcfTrkEvent &knownEvent); + void handleTcfTrkRunControlModuleLoadContextSuspendedEvent(const tcftrk::TcfTrkRunControlModuleLoadContextSuspendedEvent &e); + inline void sendTrkContinue(); + void sendTrkStepRange(); + void handleStep(const tcftrk::TcfTrkCommandResult &result); + void handleCreateProcess(const tcftrk::TcfTrkCommandResult &result); + + void readGdbResponse(); + void handleGdbServerCommand(const QByteArray &cmd); + void sendGdbServerMessage(const QByteArray &msg, + const QByteArray &logNote = QByteArray()); + void sendGdbServerAck(); + bool sendGdbServerPacket(const QByteArray &packet, bool doFlush); + + void logMessage(const QString &msg, int channel = LogDebug); // triggers output() if m_verbose + Q_SLOT void trkLogMessage(const QString &msg); + + QPointer<QTcpServer> m_gdbServer; + QPointer<QTcpSocket> m_gdbConnection; + QByteArray m_gdbReadBuffer; + bool m_gdbAckMode; + + // Debuggee state + trk::Session m_session; // global-ish data (process id, target information) + Symbian::Snapshot m_snapshot; // local-ish data (memory and registers) + QString m_remoteExecutable; + unsigned m_uid; + QStringList m_remoteArguments; + QString m_symbolFile; + int m_verbose; + bool m_bufferedMemoryRead; + bool m_firstModuleResumableEvent; + QByteArray m_tcfProcessId; + LocalGdbProcess m_gdbProc; +}; + +} // namespace Internal +} // namespace Debugger + +#endif // DEBUGGER_TCFTRKGDBADAPTER_H diff --git a/src/plugins/debugger/gdb/trkgdbadapter.cpp b/src/plugins/debugger/gdb/trkgdbadapter.cpp index b417d688a08fd3b0b57f1bfaa225a7e3d40dd79b..98f36a8be61ccc0bf84e5e7a32d92b8f3a2fe735 100644 --- a/src/plugins/debugger/gdb/trkgdbadapter.cpp +++ b/src/plugins/debugger/gdb/trkgdbadapter.cpp @@ -48,6 +48,8 @@ #include <QtCore/QTimer> #include <QtCore/QDir> +#include <QtNetwork/QTcpServer> +#include <QtNetwork/QTcpSocket> #ifdef Q_OS_WIN # include <windows.h> @@ -62,49 +64,14 @@ #define TrkCB(s) TrkCallback(this, &TrkGdbAdapter::s) -//#define DEBUG_MEMORY 1 -#if DEBUG_MEMORY -# define MEMORY_DEBUG(s) qDebug() << s -#else -# define MEMORY_DEBUG(s) -#endif -#define MEMORY_DEBUGX(s) qDebug() << s - using namespace trk; namespace Debugger { namespace Internal { - -enum { KnownRegisters = RegisterPSGdb + 1}; +using namespace Symbian; static inline void appendByte(QByteArray *ba, trk::byte b) { ba->append(b); } -static const char *registerNames[KnownRegisters] = -{ - "A1", "A2", "A3", "A4", - 0, 0, 0, 0, - 0, 0, 0, "AP", - "IP", "SP", "LR", "PC", - "PSTrk", 0, 0, 0, - 0, 0, 0, 0, - 0, "PSGdb" -}; - -static QByteArray dumpRegister(uint n, uint value) -{ - QByteArray ba; - ba += ' '; - if (n < KnownRegisters && registerNames[n]) { - ba += registerNames[n]; - } else { - ba += '#'; - ba += QByteArray::number(n); - } - ba += '='; - ba += hexxNumber(value); - return ba; -} - static void appendRegister(QByteArray *ba, uint regno, uint value) { ba->append(hexNumber(regno, 2)); @@ -113,123 +80,6 @@ static void appendRegister(QByteArray *ba, uint regno, uint value) ba->append(';'); } -QDebug operator<<(QDebug d, MemoryRange range) -{ - return d << QString("[%1,%2] (size %3) ") - .arg(range.from, 0, 16).arg(range.to, 0, 16).arg(range.size()); -} - -/////////////////////////////////////////////////////////////////////////// -// -// MemoryRange -// -/////////////////////////////////////////////////////////////////////////// - -MemoryRange::MemoryRange(uint f, uint t) - : from(f), to(t) -{ - QTC_ASSERT(f <= t, qDebug() << "F: " << f << " T: " << t); -} - -bool MemoryRange::intersects(const MemoryRange &other) const -{ - Q_UNUSED(other); - QTC_ASSERT(false, /**/); - return false; // FIXME -} - -void MemoryRange::operator-=(const MemoryRange &other) -{ - if (from == 0 && to == 0) - return; - MEMORY_DEBUG(" SUB: " << *this << " - " << other); - if (other.from <= from && to <= other.to) { - from = to = 0; - return; - } - if (other.from <= from && other.to <= to) { - from = qMax(from, other.to); - return; - } - if (from <= other.from && to <= other.to) { - to = qMin(other.from, to); - return; - } - // This would split the range. - QTC_ASSERT(false, qDebug() << "Memory::operator-() not handled for: " - << *this << " - " << other); -} - -bool MemoryRange::isReadOnly() const -{ - return from >= 0x70000000 && to < 0x80000000; -} - -/////////////////////////////////////////////////////////////////////////// -// -// Snapshot -// -/////////////////////////////////////////////////////////////////////////// - -void Snapshot::reset() -{ - for (Memory::Iterator it = memory.begin(); it != memory.end(); ++it) { - if (it.key().isReadOnly()) { - MEMORY_DEBUG("KEEPING READ-ONLY RANGE" << it.key()); - } else { - it = memory.erase(it); - } - } - for (int i = 0; i < RegisterCount; ++i) - registers[i] = 0; - registerValid = false; - wantedMemory = MemoryRange(); - lineFromAddress = 0; - lineToAddress = 0; -} - -void Snapshot::fullReset() -{ - memory.clear(); - reset(); -} - -void Snapshot::insertMemory(const MemoryRange &range, const QByteArray &ba) -{ - QTC_ASSERT(range.size() == uint(ba.size()), - qDebug() << "RANGE: " << range << " BA SIZE: " << ba.size(); return); - - MEMORY_DEBUG("INSERT: " << range); - // Try to combine with existing chunk. - Snapshot::Memory::iterator it = memory.begin(); - Snapshot::Memory::iterator et = memory.end(); - for ( ; it != et; ++it) { - if (range.from == it.key().to) { - MEMORY_DEBUG("COMBINING " << it.key() << " AND " << range); - QByteArray data = *it; - data.append(ba); - const MemoryRange res(it.key().from, range.to); - memory.remove(it.key()); - MEMORY_DEBUG(" TO(1) " << res); - insertMemory(res, data); - return; - } - if (it.key().from == range.to) { - MEMORY_DEBUG("COMBINING " << range << " AND " << it.key()); - QByteArray data = ba; - data.append(*it); - const MemoryRange res(range.from, it.key().to); - memory.remove(it.key()); - MEMORY_DEBUG(" TO(2) " << res); - insertMemory(res, data); - return; - } - } - - // Not combinable, add chunk. - memory.insert(range, ba); -} - /////////////////////////////////////////////////////////////////////////// // // TrkGdbAdapter @@ -385,8 +235,8 @@ QByteArray TrkGdbAdapter::trkStepRangeMessage() from = pc; to = pc; } - - //qDebug() << "USING" << int(option) << (option == 1 ? " INTO " : " OVER"); + logMessage(QString::fromLatin1("Stepping from 0x%1 to 0x%2 (current PC=0x%3), option 0x%4"). + arg(from, 0, 16).arg(to, 0, 16).arg(pc).arg(option, 0, 16)); QByteArray ba; ba.reserve(17); appendByte(&ba, option); @@ -431,10 +281,10 @@ void TrkGdbAdapter::slotEmitDelayedInferiorStartFailed() } -void TrkGdbAdapter::logMessage(const QString &msg) +void TrkGdbAdapter::logMessage(const QString &msg, int logChannel) { - if (m_verbose) - showMessage("TRK LOG: " + msg); + if (m_verbose || logChannel != LogDebug) + showMessage("TRK LOG: " + msg, logChannel); } // @@ -478,7 +328,7 @@ void TrkGdbAdapter::readGdbServerCommand() } if (code == '-') { - logMessage("NAK: Retransmission requested"); + logMessage("NAK: Retransmission requested", LogError); // This seems too harsh. //emit adapterCrashed("Communication problem encountered."); continue; @@ -492,14 +342,14 @@ void TrkGdbAdapter::readGdbServerCommand() if (code != '$') { logMessage("Broken package (2) " + quoteUnprintableLatin1(ba) - + hexNumber(code)); + + hexNumber(code), LogError); continue; } int pos = ba.indexOf('#'); if (pos == -1) { logMessage("Invalid checksum format in " - + quoteUnprintableLatin1(ba)); + + quoteUnprintableLatin1(ba), LogError); continue; } @@ -507,7 +357,7 @@ void TrkGdbAdapter::readGdbServerCommand() uint checkSum = ba.mid(pos + 1, 2).toUInt(&ok, 16); if (!ok) { logMessage("Invalid checksum format 2 in " - + quoteUnprintableLatin1(ba)); + + quoteUnprintableLatin1(ba), LogError); return; } @@ -518,7 +368,7 @@ void TrkGdbAdapter::readGdbServerCommand() if (sum != checkSum) { logMessage(QString("ERROR: Packet checksum wrong: %1 %2 in " - + quoteUnprintableLatin1(ba)).arg(checkSum).arg(sum)); + + quoteUnprintableLatin1(ba)).arg(checkSum).arg(sum), LogError); } QByteArray cmd = ba.left(pos); @@ -531,17 +381,17 @@ bool TrkGdbAdapter::sendGdbServerPacket(const QByteArray &packet, bool doFlush) { if (!m_gdbConnection) { logMessage(_("Cannot write to gdb: No connection (%1)") - .arg(_(packet))); + .arg(_(packet)), LogError); return false; } if (m_gdbConnection->state() != QAbstractSocket::ConnectedState) { logMessage(_("Cannot write to gdb: Not connected (%1)") - .arg(_(packet))); + .arg(_(packet)), LogError); return false; } if (m_gdbConnection->write(packet) == -1) { logMessage(_("Cannot write to gdb: %1 (%2)") - .arg(m_gdbConnection->errorString()).arg(_(packet))); + .arg(m_gdbConnection->errorString()).arg(_(packet)), LogError); return false; } if (doFlush) @@ -614,6 +464,18 @@ QByteArray TrkGdbAdapter::trkBreakpointMessage(uint addr, uint len, bool armMode return ba; } +static QByteArray msgStepRangeReceived(unsigned from, unsigned to, bool over) +{ + QByteArray rc = "Stepping range received for step "; + rc += over ? "over" : "into"; + rc += " (0x"; + rc += QByteArray::number(from, 16); + rc += " to 0x"; + rc += QByteArray::number(to, 16); + rc += ')'; + return rc; +} + void TrkGdbAdapter::handleGdbServerCommand(const QByteArray &cmd) { // http://sourceware.org/gdb/current/onlinedocs/gdb_34.html @@ -701,7 +563,7 @@ void TrkGdbAdapter::handleGdbServerCommand(const QByteArray &cmd) m_snapshot.lineFromAddress = cmd.mid(8, pos - 8).toUInt(0, 16); m_snapshot.lineToAddress = cmd.mid(pos + 1).toUInt(0, 16); m_snapshot.stepOver = false; - sendGdbServerMessage("", "Stepping range received for Step Into"); + sendGdbServerMessage("", msgStepRangeReceived(m_snapshot.lineFromAddress, m_snapshot.lineToAddress, m_snapshot.stepOver)); } else if (cmd.startsWith("salnext,")) { @@ -711,7 +573,7 @@ void TrkGdbAdapter::handleGdbServerCommand(const QByteArray &cmd) m_snapshot.lineFromAddress = cmd.mid(8, pos - 8).toUInt(0, 16); m_snapshot.lineToAddress = cmd.mid(pos + 1).toUInt(0, 16); m_snapshot.stepOver = true; - sendGdbServerMessage("", "Stepping range received for Step Over"); + sendGdbServerMessage("", msgStepRangeReceived(m_snapshot.lineFromAddress, m_snapshot.lineToAddress, m_snapshot.stepOver)); } else if (cmd.startsWith("Hc")) { @@ -969,8 +831,8 @@ void TrkGdbAdapter::handleGdbServerCommand(const QByteArray &cmd) } else if (cmd == "s" || cmd.startsWith("vCont;s")) { - logMessage(msgGdbPacket(QLatin1String("Step range"))); - logMessage(" from " + hexxNumber(m_snapshot.registers[RegisterPC])); + logMessage(msgGdbPacket(QString::fromLatin1("Step range from 0x%1"). + arg(m_snapshot.registers[RegisterPC], 0, 16))); sendGdbServerAck(); //m_snapshot.reset(); m_running = true; @@ -1012,9 +874,9 @@ void TrkGdbAdapter::handleGdbServerCommand(const QByteArray &cmd) const uint len = cmd.mid(pos + 1).toUInt(&ok2, 16); if (!ok1) { logMessage("MISPARSED ADDRESS FROM " + cmd + - " (" + cmd.mid(3, pos - 3) + ")"); + " (" + cmd.mid(3, pos - 3) + ")", LogError); } else if (!ok2) { - logMessage("MISPARSED BREAKPOINT SIZE FROM " + cmd); + logMessage("MISPARSED BREAKPOINT SIZE FROM " + cmd, LogError); } else { //qDebug() << "ADDR: " << hexNumber(addr) << " LEN: " << len; logMessage(_("Inserting breakpoint at 0x%1, %2") @@ -1036,7 +898,7 @@ void TrkGdbAdapter::handleGdbServerCommand(const QByteArray &cmd) const uint bp = m_session.addressToBP[addr]; if (bp == 0) { logMessage(_("NO RECORDED BP AT 0x%1, %2") - .arg(addr, 0, 16).arg(len)); + .arg(addr, 0, 16).arg(len), LogError); sendGdbServerMessage("E00"); } else { m_session.addressToBP.remove(addr); @@ -1061,7 +923,7 @@ void TrkGdbAdapter::handleGdbServerCommand(const QByteArray &cmd) if (ok1 && ok2) { const QString msg = _("Read of OS auxiliary " "vector (%1, %2) not implemented.").arg(offset).arg(length); - logMessage(msgGdbPacket(msg)); + logMessage(msgGdbPacket(msg), LogWarning); sendGdbServerMessage("E20", msg.toLatin1()); handled = true; } @@ -1071,13 +933,13 @@ void TrkGdbAdapter::handleGdbServerCommand(const QByteArray &cmd) if (!handled) { const QString msg = QLatin1String("FIXME unknown 'XFER'-request: ") + QString::fromAscii(cmd); - logMessage(msgGdbPacket(msg)); + logMessage(msgGdbPacket(msg), LogWarning); sendGdbServerMessage("E20", msg.toLatin1()); } } // qPart/qXfer else { logMessage(msgGdbPacket(QLatin1String("FIXME unknown: ") - + QString::fromAscii(cmd))); + + QString::fromAscii(cmd)), LogWarning); } } @@ -1098,7 +960,7 @@ void TrkGdbAdapter::sendTrkAck(trk::byte token) void TrkGdbAdapter::handleTrkError(const QString &msg) { - logMessage("## TRK ERROR: " + msg); + logMessage("## TRK ERROR: " + msg, LogError); emit adapterCrashed("TRK problem encountered:\n" + msg); } @@ -1110,8 +972,7 @@ void TrkGdbAdapter::handleTrkResult(const TrkResult &result) // It looks like those messages _must not_ be acknowledged. // If we do so, TRK will complain about wrong sequencing. //sendTrkAck(result.token); - logMessage(QLatin1String("APPLICATION OUTPUT: ") + - QString::fromAscii(result.data)); + logMessage(QString::fromAscii(result.data), AppOutput); sendGdbServerMessage("O" + result.data.toHex()); return; } @@ -1125,7 +986,7 @@ void TrkGdbAdapter::handleTrkResult(const TrkResult &result) QString logMsg; QTextStream(&logMsg) << prefix << "NAK: for token=" << result.token << " ERROR: " << errorMessage(result.data.at(0)) << ' ' << str; - logMessage(logMsg); + logMessage(logMsg, LogError); break; } case TrkNotifyStopped: { // 0x90 Notified Stopped @@ -1176,14 +1037,14 @@ void TrkGdbAdapter::handleTrkResult(const TrkResult &result) case TrkNotifyException: { // 0x91 Notify Exception (obsolete) showMessage(_("RESET SNAPSHOT (NOTIFY EXCEPTION)")); m_snapshot.reset(); - logMessage(prefix + "NOTE: EXCEPTION " + str); + logMessage(prefix + "NOTE: EXCEPTION " + str, AppError); sendTrkAck(result.token); break; } case 0x92: { // showMessage(_("RESET SNAPSHOT (NOTIFY INTERNAL ERROR)")); m_snapshot.reset(); - logMessage(prefix + "NOTE: INTERNAL ERROR: " + str); + logMessage(prefix + "NOTE: INTERNAL ERROR: " + str, LogError); sendTrkAck(result.token); break; } @@ -1279,7 +1140,7 @@ void TrkGdbAdapter::handleTrkResult(const TrkResult &result) break; } default: { - logMessage(prefix + "INVALID: " + str); + logMessage(prefix + "INVALID: " + str, LogError); break; } } @@ -1329,7 +1190,7 @@ void TrkGdbAdapter::handleReadRegisters(const TrkResult &result) // [80 0B 00 00 00 00 00 C9 24 FF BC 00 00 00 00 00 // 60 00 00 00 00 00 00 78 67 79 70 00 00 00 00 00...] if (result.errorCode()) { - logMessage("ERROR: " + result.errorString()); + logMessage("ERROR: " + result.errorString(), LogError); return; } const char *data = result.data.data() + 1; // Skip ok byte @@ -1342,7 +1203,7 @@ void TrkGdbAdapter::handleWriteRegister(const TrkResult &result) { logMessage(" RESULT: " + result.toString() + result.cookie.toString()); if (result.errorCode()) { - logMessage("ERROR: " + result.errorString()); + logMessage("ERROR: " + result.errorString(), LogError); sendGdbServerMessage("E01"); return; } @@ -1449,7 +1310,7 @@ QByteArray TrkGdbAdapter::memoryReadLogMessage(uint addr, const QByteArray &ba) void TrkGdbAdapter::handleReadMemoryBuffered(const TrkResult &result) { if (extractShort(result.data.data() + 1) + 3 != result.data.size()) - logMessage("\n BAD MEMORY RESULT: " + result.data.toHex() + "\n"); + logMessage("\n BAD MEMORY RESULT: " + result.data.toHex() + "\n", LogError); const MemoryRange range = result.cookie.value<MemoryRange>(); if (const int errorCode = result.errorCode()) { logMessage(_("TEMPORARY: ") + msgMemoryReadError(errorCode, range.from)); @@ -1467,7 +1328,7 @@ void TrkGdbAdapter::handleReadMemoryBuffered(const TrkResult &result) void TrkGdbAdapter::handleReadMemoryUnbuffered(const TrkResult &result) { if (extractShort(result.data.data() + 1) + 3 != result.data.size()) - logMessage("\n BAD MEMORY RESULT: " + result.data.toHex() + "\n"); + logMessage("\n BAD MEMORY RESULT: " + result.data.toHex() + "\n", LogError); const MemoryRange range = result.cookie.value<MemoryRange>(); if (const int errorCode = result.errorCode()) { logMessage(_("TEMPORARY: ") + msgMemoryReadError(errorCode, range.from)); @@ -1587,7 +1448,7 @@ void TrkGdbAdapter::reportReadMemoryBuffered(const TrkResult &result) void TrkGdbAdapter::handleStep(const TrkResult &result) { if (result.errorCode()) { - logMessage("ERROR: " + result.errorString() + " in handleStep"); + logMessage("ERROR: " + result.errorString() + " in handleStep", LogError); // Try fallback with Continue. showMessage("FALLBACK TO 'CONTINUE'"); @@ -1612,7 +1473,7 @@ void TrkGdbAdapter::handleAndReportSetBreakpoint(const TrkResult &result) // Error: 0x00 // [80 09 00 00 00 00 0A] if (result.errorCode()) { - logMessage("ERROR WHEN SETTING BREAKPOINT: " + result.errorString()); + logMessage("ERROR WHEN SETTING BREAKPOINT: " + result.errorString(), LogError); sendGdbServerMessage("E21"); return; } @@ -1629,7 +1490,7 @@ void TrkGdbAdapter::handleClearBreakpoint(const TrkResult &result) { logMessage("CLEAR BREAKPOINT "); if (result.errorCode()) { - logMessage("ERROR: " + result.errorString()); + logMessage("ERROR: " + result.errorString(), LogError); //return; } sendGdbServerMessage("OK"); @@ -1785,7 +1646,7 @@ void TrkGdbAdapter::startAdapter() if (message.isEmpty()) { emit adapterStartFailed(QString(), QString()); } else { - logMessage(message); + logMessage(message, LogError); emit adapterStartFailed(message, QString()); } return; @@ -1798,7 +1659,7 @@ void TrkGdbAdapter::startAdapter() if (!m_gdbServer->listen(QHostAddress(gdbServerIP()), gdbServerPort())) { QString msg = QString("Unable to start the gdb server at %1: %2.") .arg(m_gdbServerName).arg(m_gdbServer->errorString()); - logMessage(msg); + logMessage(msg, LogError); emit adapterStartFailed(msg, QString()); return; } @@ -1831,7 +1692,7 @@ void TrkGdbAdapter::handleCreateProcess(const TrkResult &result) //logMessage(" RESULT: " + result.toString()); // [80 08 00 00 00 01 B5 00 00 01 B6 78 67 40 00 00 40 00 00] if (result.errorCode()) { - logMessage("ERROR: " + result.errorString()); + logMessage("ERROR: " + result.errorString(), LogError); QString msg = _("Cannot start executable \"%1\" on the device:\n%2") .arg(m_remoteExecutable).arg(result.errorString()); // Delay cleanup as not to close a trk device from its read handler, @@ -1854,7 +1715,7 @@ void TrkGdbAdapter::handleCreateProcess(const TrkResult &result) const QByteArray symbolFile = m_symbolFile.toLocal8Bit(); if (symbolFile.isEmpty()) { - logMessage(_("WARNING: No symbol file available.")); + logMessage(_("WARNING: No symbol file available."), LogWarning); } else { // Does not seem to be necessary anymore. // FIXME: Startup sequence can be streamlined now as we do not @@ -1942,7 +1803,7 @@ void TrkGdbAdapter::handleDirectWrite1(const TrkResult &response) scratch = m_session.dataseg + 512; logMessage("DIRECT WRITE1: " + response.toString()); if (const int errorCode = response.errorCode()) { - logMessage("ERROR: " + response.errorString() + "in handleDirectWrite1"); + logMessage("ERROR: " + response.errorString() + "in handleDirectWrite1", LogError); } else { oldMem = response.data.mid(3); oldPC = m_snapshot.registers[RegisterPC]; @@ -1987,7 +1848,7 @@ void TrkGdbAdapter::handleDirectWrite2(const TrkResult &response) { logMessage("DIRECT WRITE2: " + response.toString()); if (const int errorCode = response.errorCode()) { - logMessage("ERROR: " + response.errorString() + "in handleDirectWrite1"); + logMessage("ERROR: " + response.errorString() + "in handleDirectWrite1", LogError); } else { // Check sendTrkMessage(0x10, TrkCB(handleDirectWrite3), @@ -1999,7 +1860,7 @@ void TrkGdbAdapter::handleDirectWrite3(const TrkResult &response) { logMessage("DIRECT WRITE3: " + response.toString()); if (const int errorCode = response.errorCode()) { - logMessage("ERROR: " + response.errorString() + "in handleDirectWrite1"); + logMessage("ERROR: " + response.errorString() + "in handleDirectWrite1", LogError); } else { // Set PC sendTrkMessage(0x13, TrkCB(handleDirectWrite4), @@ -2013,7 +1874,7 @@ void TrkGdbAdapter::handleDirectWrite4(const TrkResult &response) return; logMessage("DIRECT WRITE4: " + response.toString()); if (const int errorCode = response.errorCode()) { - logMessage("ERROR: " + response.errorString() + "in handleDirectWrite1"); + logMessage("ERROR: " + response.errorString() + "in handleDirectWrite1", LogError); } else { QByteArray ba1; appendByte(&ba1, 0x11); // options "step over" @@ -2029,7 +1890,7 @@ void TrkGdbAdapter::handleDirectWrite5(const TrkResult &response) { logMessage("DIRECT WRITE5: " + response.toString()); if (const int errorCode = response.errorCode()) { - logMessage("ERROR: " + response.errorString() + "in handleDirectWrite1"); + logMessage("ERROR: " + response.errorString() + "in handleDirectWrite1", LogError); } else { // Restore PC sendTrkMessage(0x13, TrkCB(handleDirectWrite6), @@ -2041,7 +1902,7 @@ void TrkGdbAdapter::handleDirectWrite6(const TrkResult &response) { logMessage("DIRECT WRITE6: " + response.toString()); if (const int errorCode = response.errorCode()) { - logMessage("ERROR: " + response.errorString() + "in handleDirectWrite1"); + logMessage("ERROR: " + response.errorString() + "in handleDirectWrite1", LogError); } else { // Restore memory sendTrkMessage(0x11, TrkCB(handleDirectWrite7), @@ -2053,7 +1914,7 @@ void TrkGdbAdapter::handleDirectWrite7(const TrkResult &response) { logMessage("DIRECT WRITE7: " + response.toString()); if (const int errorCode = response.errorCode()) { - logMessage("ERROR: " + response.errorString() + "in handleDirectWrite1"); + logMessage("ERROR: " + response.errorString() + "in handleDirectWrite1", LogError); } else { // Check sendTrkMessage(0x10, TrkCB(handleDirectWrite8), @@ -2065,7 +1926,7 @@ void TrkGdbAdapter::handleDirectWrite8(const TrkResult &response) { logMessage("DIRECT WRITE8: " + response.toString()); if (const int errorCode = response.errorCode()) { - logMessage("ERROR: " + response.errorString() + "in handleDirectWrite1"); + logMessage("ERROR: " + response.errorString() + "in handleDirectWrite1", LogError); } else { // Re-read registers sendTrkMessage(0x12, diff --git a/src/plugins/debugger/gdb/trkgdbadapter.h b/src/plugins/debugger/gdb/trkgdbadapter.h index c2af38308aecbcbd4ce365722859f1195a4c8657..b24c8cd1f9645b9ed46b98b322348dd19acbba30 100644 --- a/src/plugins/debugger/gdb/trkgdbadapter.h +++ b/src/plugins/debugger/gdb/trkgdbadapter.h @@ -33,19 +33,24 @@ #include "abstractgdbadapter.h" #include "trkutils.h" -#include "trkdevice.h" -#include "launcher.h" -#include "abstractgdbprocess.h" +#include "callback.h" +#include "symbian.h" -#include <QtCore/QHash> #include <QtCore/QPointer> #include <QtCore/QSharedPointer> -#include <QtCore/QQueue> -#include <QtCore/QString> #include <QtCore/QStringList> +#include <QtCore/QHash> -#include <QtNetwork/QTcpServer> -#include <QtNetwork/QTcpSocket> +QT_BEGIN_NAMESPACE +class QTcpServer; +class QTcpSocket; +QT_END_NAMESPACE + +namespace trk { +struct TrkResult; +struct TrkMessage; +class TrkDevice; +} namespace SymbianUtils { class SymbianDevice; @@ -54,80 +59,8 @@ class SymbianDevice; namespace Debugger { namespace Internal { -enum CodeMode -{ - ArmMode = 0, - ThumbMode, -}; - -enum TargetConstants -{ - RegisterCount = 17, - RegisterSP = 13, // Stack Pointer - RegisterLR = 14, // Return address - RegisterPC = 15, // Program counter - RegisterPSGdb = 25, // gdb's view of the world - RegisterPSTrk = 16, // TRK's view of the world - - MemoryChunkSize = 256 -}; - -struct MemoryRange -{ - MemoryRange() : from(0), to(0) {} - MemoryRange(uint f, uint t); - void operator-=(const MemoryRange &other); - bool intersects(const MemoryRange &other) const; - quint64 hash() const { return (quint64(from) << 32) + to; } - bool operator==(const MemoryRange &other) const { return hash() == other.hash(); } - bool operator<(const MemoryRange &other) const { return hash() < other.hash(); } - uint size() const { return to - from; } - bool isReadOnly() const; - - uint from; // Inclusive. - uint to; // Exclusive. -}; - -struct Snapshot -{ - Snapshot() { reset(); } - - void reset(); // Leaves read-only memory cache alive. - void fullReset(); // Also removes read-only memory cache. - void insertMemory(const MemoryRange &range, const QByteArray &ba); - - uint registers[RegisterCount]; - bool registerValid; - typedef QMap<MemoryRange, QByteArray> Memory; - Memory memory; - - // Current state. - MemoryRange wantedMemory; - - // For next step. - uint lineFromAddress; - uint lineToAddress; - bool stepOver; -}; - - -struct Breakpoint -{ - Breakpoint(uint offset_ = 0) - { - number = 0; - offset = offset_; - mode = ArmMode; - } - uint offset; - ushort number; - CodeMode mode; -}; - -struct GdbResult -{ - QByteArray data; -}; +struct MemoryRange; +struct GdbResult; /////////////////////////////////////////////////////////////////////// // @@ -264,21 +197,6 @@ private: QSharedPointer<trk::TrkDevice> m_trkDevice; QString m_adapterFailMessage; - // - // Gdb - // - struct GdbCommand - { - GdbCommand() : flags(0), callback(GdbCallback()), callbackName(0) {} - - int flags; - GdbCallback callback; - const char *callbackName; - QString command; - QVariant cookie; - //QTime postTime; - }; - Q_SLOT void handleGdbConnection(); Q_SLOT void readGdbServerCommand(); void readGdbResponse(); @@ -291,7 +209,7 @@ private: bool sendGdbServerPacket(const QByteArray &packet, bool doFlush); void tryAnswerGdbMemoryRequest(bool buffered); - void logMessage(const QString &msg); // triggers output() if m_verbose + void logMessage(const QString &msg, int logChannel = LogDebug); // triggers output() if m_verbose Q_SLOT void trkLogMessage(const QString &msg); QPointer<QTcpServer> m_gdbServer; @@ -299,11 +217,9 @@ private: QByteArray m_gdbReadBuffer; bool m_gdbAckMode; - QHash<int, GdbCommand> m_gdbCookieForToken; - // Debuggee state trk::Session m_session; // global-ish data (process id, target information) - Snapshot m_snapshot; // local-ish data (memory and registers) + Symbian::Snapshot m_snapshot; // local-ish data (memory and registers) QString m_remoteExecutable; QStringList m_remoteArguments; QString m_symbolFile; @@ -315,6 +231,4 @@ private: } // namespace Internal } // namespace Debugger -Q_DECLARE_METATYPE(Debugger::Internal::MemoryRange); - #endif // DEBUGGER_TRKGDBADAPTER_H diff --git a/src/shared/symbianutils/json.cpp b/src/shared/symbianutils/json.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4171125ee97e35cf6e71d3d52ff0965007b38442 --- /dev/null +++ b/src/shared/symbianutils/json.cpp @@ -0,0 +1,478 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "json.h" + +#ifdef TODO_USE_CREATOR +#include <utils/qtcassert.h> +#endif // TODO_USE_CREATOR + +#include <QtCore/QByteArray> +#include <QtCore/QTextStream> +#include <QtCore/QDebug> +#include <QtCore/QStringList> + +#include <ctype.h> + +//#define DEBUG_JASON +#ifdef DEBUG_JASON +#define JDEBUG(s) qDebug() << s +#else +#define JDEBUG(s) +#endif + +namespace tcftrk { + +static void skipSpaces(const char *&from, const char *to) +{ + while (from != to && isspace(*from)) + ++from; +} + +QTextStream &operator<<(QTextStream &os, const JsonValue &mi) +{ + return os << mi.toString(); +} + +void JsonValue::parsePair(const char *&from, const char *to) +{ + skipSpaces(from, to); + JDEBUG("parsePair: " << QByteArray(from, to - from)); + m_name = parseCString(from, to); + skipSpaces(from, to); + while (from < to && *from != ':') { + JDEBUG("not a colon" << *from); + ++from; + } + ++from; + parseValue(from, to); + skipSpaces(from, to); +} + +QByteArray JsonValue::parseNumber(const char *&from, const char *to) +{ + QByteArray result; + if (from < to && *from == '-') // Leading '-'. + result.append(*from++); + while (from < to && *from >= '0' && *from <= '9') + result.append(*from++); + return result; +} + +QByteArray JsonValue::parseCString(const char *&from, const char *to) +{ + QByteArray result; + JDEBUG("parseCString: " << QByteArray(from, to - from)); + if (*from != '"') { + qDebug() << "JSON Parse Error, double quote expected"; + ++from; // So we don't hang + return QByteArray(); + } + const char *ptr = from; + ++ptr; + while (ptr < to) { + if (*ptr == '"') { + ++ptr; + result = QByteArray(from + 1, ptr - from - 2); + break; + } + if (*ptr == '\\') { + ++ptr; + if (ptr == to) { + qDebug() << "JSON Parse Error, unterminated backslash escape"; + from = ptr; // So we don't hang + return QByteArray(); + } + } + ++ptr; + } + from = ptr; + + int idx = result.indexOf('\\'); + if (idx >= 0) { + char *dst = result.data() + idx; + const char *src = dst + 1, *end = result.data() + result.length(); + do { + char c = *src++; + switch (c) { + case 'a': *dst++ = '\a'; break; + case 'b': *dst++ = '\b'; break; + case 'f': *dst++ = '\f'; break; + case 'n': *dst++ = '\n'; break; + case 'r': *dst++ = '\r'; break; + case 't': *dst++ = '\t'; break; + case 'v': *dst++ = '\v'; break; + case '"': *dst++ = '"'; break; + case '\\': *dst++ = '\\'; break; + default: + { + int chars = 0; + uchar prod = 0; + forever { + if (c < '0' || c > '7') { + --src; + break; + } + prod = prod * 8 + c - '0'; + if (++chars == 3 || src == end) + break; + c = *src++; + } + if (!chars) { + qDebug() << "JSON Parse Error, unrecognized backslash escape"; + return QByteArray(); + } + *dst++ = prod; + } + } + while (src != end) { + char c = *src++; + if (c == '\\') + break; + *dst++ = c; + } + } while (src != end); + *dst = 0; + result.truncate(dst - result.data()); + } + + JDEBUG("parseCString, got " << result); + return result; +} + + + +void JsonValue::parseValue(const char *&from, const char *to) +{ + JDEBUG("parseValue: " << QByteArray(from, to - from)); + switch (*from) { + case '{': + parseObject(from, to); + break; + case 't': + if (to - from >= 4 && qstrncmp(from, "true", 4) == 0) { + m_data = QByteArray(from, 4); + from += m_data.size(); + m_type = Boolean; + } + break; + case 'f': + if (to - from >= 5 && qstrncmp(from, "false", 5) == 0) { + m_data = QByteArray(from, 5); + from += m_data.size(); + m_type = Boolean; + } + break; + case 'n': + if (to - from >= 4 && qstrncmp(from, "null", 4) == 0) { + m_data = QByteArray(from, 4); + from += m_data.size(); + m_type = NullObject; + } + break; + case '[': + parseArray(from, to); + break; + case '"': + m_type = String; + m_data = parseCString(from, to); + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case '-': + m_type = Number; + m_data = parseNumber(from, to); + default: + break; + } +} + +void JsonValue::parseObject(const char *&from, const char *to) +{ + JDEBUG("parseObject: " << QByteArray(from, to - from)); +#ifdef TODO_USE_CREATOR + QTC_ASSERT(*from == '{', /**/); +#endif + ++from; + m_type = Object; + while (from < to) { + if (*from == '}') { + ++from; + break; + } + JsonValue child; + child.parsePair(from, to); + if (!child.isValid()) + return; + m_children += child; + if (*from == ',') + ++from; + } +} + +void JsonValue::parseArray(const char *&from, const char *to) +{ + JDEBUG("parseArray: " << QByteArray(from, to - from)); +#ifdef TODO_USE_CREATOR + QTC_ASSERT(*from == '[', /**/); +#endif + ++from; + m_type = Array; + while (from < to) { + if (*from == ']') { + ++from; + break; + } + JsonValue child; + child.parseValue(from, to); + if (child.isValid()) + m_children += child; + if (*from == ',') + ++from; + } +} + +void JsonValue::setStreamOutput(const QByteArray &name, const QByteArray &content) +{ + if (content.isEmpty()) + return; + JsonValue child; + child.m_type = String; + child.m_name = name; + child.m_data = content; + m_children += child; + if (m_type == Invalid) + m_type = Object; +} + +static QByteArray ind(int indent) +{ + return QByteArray(2 * indent, ' '); +} + +void JsonValue::dumpChildren(QByteArray * str, bool multiline, int indent) const +{ + for (int i = 0; i < m_children.size(); ++i) { + if (i != 0) { + *str += ','; + if (multiline) + *str += '\n'; + } + if (multiline) + *str += ind(indent); + *str += m_children.at(i).toString(multiline, indent); + } +} + +class MyString : public QString { +public: + ushort at(int i) const { return constData()[i].unicode(); } +}; + +template<class ST, typename CT> +inline ST escapeCStringTpl(const ST &ba) +{ + ST ret; + ret.reserve(ba.length() * 2); + for (int i = 0; i < ba.length(); ++i) { + CT c = ba.at(i); + switch (c) { + case '\\': ret += "\\\\"; break; + case '\a': ret += "\\a"; break; + case '\b': ret += "\\b"; break; + case '\f': ret += "\\f"; break; + case '\n': ret += "\\n"; break; + case '\r': ret += "\\r"; break; + case '\t': ret += "\\t"; break; + case '\v': ret += "\\v"; break; + case '"': ret += "\\\""; break; + default: + if (c < 32 || c == 127) { + ret += '\\'; + ret += '0' + (c >> 6); + ret += '0' + ((c >> 3) & 7); + ret += '0' + (c & 7); + } else { + ret += c; + } + } + } + return ret; +} + +QString JsonValue::escapeCString(const QString &ba) +{ + return escapeCStringTpl<MyString, ushort>(static_cast<const MyString &>(ba)); +} + +QByteArray JsonValue::escapeCString(const QByteArray &ba) +{ + return escapeCStringTpl<QByteArray, uchar>(ba); +} + +QByteArray JsonValue::toString(bool multiline, int indent) const +{ + QByteArray result; + switch (m_type) { + case Invalid: + if (multiline) + result += ind(indent) + "Invalid\n"; + else + result += "Invalid"; + break; + case String: + if (!m_name.isEmpty()) + result += m_name + "="; + result += '"' + escapeCString(m_data) + '"'; + break; + case Number: + if (!m_name.isEmpty()) + result += '"' + m_name + "\":"; + result += m_data; + break; + case Boolean: + case NullObject: + if (!m_name.isEmpty()) + result += '"' + m_name + "\":"; + result += m_data; + break; + case Object: + if (!m_name.isEmpty()) + result += m_name + '='; + if (multiline) { + result += "{\n"; + dumpChildren(&result, multiline, indent + 1); + result += '\n' + ind(indent) + "}"; + } else { + result += "{"; + dumpChildren(&result, multiline, indent + 1); + result += "}"; + } + break; + case Array: + if (!m_name.isEmpty()) + result += m_name + "="; + if (multiline) { + result += "[\n"; + dumpChildren(&result, multiline, indent + 1); + result += '\n' + ind(indent) + "]"; + } else { + result += "["; + dumpChildren(&result, multiline, indent + 1); + result += "]"; + } + break; + } + return result; +} + +void JsonValue::fromString(const QByteArray &ba) +{ + const char *from = ba.constBegin(); + const char *to = ba.constEnd(); + parseValue(from, to); +} + +JsonValue JsonValue::findChild(const char *name) const +{ + for (int i = 0; i < m_children.size(); ++i) + if (m_children.at(i).m_name == name) + return m_children.at(i); + return JsonValue(); +} + +void JsonInputStream::appendCString(const char *s) +{ + m_target.append('"'); + for (const char *p = s; *p; p++) { + if (*p == '"' || *p == '\\') + m_target.append('\\'); + m_target.append(*p); + } + m_target.append('"'); +} + +void JsonInputStream::appendString(const QString &in) +{ + if (in.isEmpty()) { + m_target.append("\"\""); + return; + } + + const QChar doubleQuote('"'); + const QChar backSlash('\\'); + QString rc; + const int inSize = in.size(); + rc.reserve(in.size() + 5); + rc.append(doubleQuote); + for (int i = 0; i < inSize; i++) { + const QChar c = in.at(i); + if (c == doubleQuote || c == backSlash) + rc.append(backSlash); + rc.append(c); + } + rc.append(doubleQuote); + m_target.append(rc.toUtf8()); + return; +} + +JsonInputStream &JsonInputStream::operator<<(const QStringList &in) +{ + m_target.append('['); + const int count = in.size(); + for (int i = 0 ; i < count; i++) { + if (i) + m_target.append(','); + appendString(in.at(i)); + } + m_target.append(']'); + return *this; +} + +JsonInputStream &JsonInputStream::operator<<(const QVector<QByteArray> &ba) +{ + m_target.append('['); + const int count = ba.size(); + for (int i = 0 ; i < count; i++) { + if (i) + m_target.append(','); + appendCString(ba.at(i).constData()); + } + m_target.append(']'); + return *this; +} + +JsonInputStream &JsonInputStream::operator<<(bool b) +{ + m_target.append(b ? "true" : "false"); + return *this; +} + +} // namespace tcftrk + diff --git a/src/shared/symbianutils/json.h b/src/shared/symbianutils/json.h new file mode 100644 index 0000000000000000000000000000000000000000..ef574bdae249ab3432bf30e18e02c109c869a69f --- /dev/null +++ b/src/shared/symbianutils/json.h @@ -0,0 +1,137 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef SYMBIANUTILS_JSON_H +#define SYMBIANUTILS_JSON_H + +#include "symbianutils_global.h" + +#include <QtCore/QByteArray> +#include <QtCore/QStringList> +#include <QtCore/QVector> + +namespace tcftrk { + +class SYMBIANUTILS_EXPORT JsonValue +{ +public: + JsonValue() : m_type(Invalid) {} + explicit JsonValue(const QByteArray &str) { fromString(str); } + + QByteArray m_name; + QByteArray m_data; + QList<JsonValue> m_children; + + enum Type { + Invalid, + String, + Number, + Boolean, + Object, + NullObject, + Array, + }; + + Type m_type; + + inline Type type() const { return m_type; } + inline QByteArray name() const { return m_name; } + inline bool hasName(const char *name) const { return m_name == name; } + + inline bool isValid() const { return m_type != Invalid; } + inline bool isNumber() const { return m_type == Number; } + inline bool isString() const { return m_type == String; } + inline bool isObject() const { return m_type == Object; } + inline bool isArray() const { return m_type == Array; } + + + inline QByteArray data() const { return m_data; } + inline const QList<JsonValue> &children() const { return m_children; } + inline int childCount() const { return m_children.size(); } + + const JsonValue &childAt(int index) const { return m_children[index]; } + JsonValue &childAt(int index) { return m_children[index]; } + JsonValue findChild(const char *name) const; + + QByteArray toString(bool multiline = false, int indent = 0) const; + void fromString(const QByteArray &str); + void setStreamOutput(const QByteArray &name, const QByteArray &content); + +private: + static QByteArray parseCString(const char *&from, const char *to); + static QByteArray parseNumber(const char *&from, const char *to); + static QByteArray escapeCString(const QByteArray &ba); + static QString escapeCString(const QString &ba); + void parsePair(const char *&from, const char *to); + void parseValue(const char *&from, const char *to); + void parseObject(const char *&from, const char *to); + void parseArray(const char *&from, const char *to); + + void dumpChildren(QByteArray *str, bool multiline, int indent) const; +}; + +/* Thin wrapper around QByteArray for formatting JSON input. Use as in: + * JsonInputStream(byteArray) << '{' << "bla" << ':' << "blup" << '}'; + * Note that strings get double quotes and JSON-escaping, characters should be + * used for the array/hash delimiters. + * */ +class SYMBIANUTILS_EXPORT JsonInputStream { +public: + explicit JsonInputStream(QByteArray &a) : m_target(a) {} + + JsonInputStream &operator<<(char c) { m_target.append(c); return *this; } + JsonInputStream &operator<<(const char *c) { appendCString(c); return *this; } + JsonInputStream &operator<<(const QByteArray &a) { appendCString(a.constData()); return *this; } + JsonInputStream &operator<<(const QString &c) { appendString(c); return *this; } + + // Format as array + JsonInputStream &operator<<(const QStringList &c); + + // Format as array + JsonInputStream &operator<<(const QVector<QByteArray> &ba); + + JsonInputStream &operator<<(bool b); + + JsonInputStream &operator<<(int i) + { m_target.append(QByteArray::number(i)); return *this; } + JsonInputStream &operator<<(unsigned i) + { m_target.append(QByteArray::number(i)); return *this; } + JsonInputStream &operator<<(quint64 i) + { m_target.append(QByteArray::number(i)); return *this; } + +private: + void appendString(const QString &); + void appendCString(const char *c); + + QByteArray &m_target; +}; + +} // namespace tcftrk + +#endif // SYMBIANUTILS_JSON_H diff --git a/src/shared/symbianutils/symbianutils.pri b/src/shared/symbianutils/symbianutils.pri index 6309517b18310e117dc2abeb1c7954eeae179fdc..f07e494ed1764d738844a7ad331ce4d770e0c757 100644 --- a/src/shared/symbianutils/symbianutils.pri +++ b/src/shared/symbianutils/symbianutils.pri @@ -1,5 +1,7 @@ INCLUDEPATH *= $$PWD +QT += network + # Input HEADERS += $$PWD/symbianutils_global.h \ $$PWD/callback.h \ @@ -9,14 +11,20 @@ HEADERS += $$PWD/symbianutils_global.h \ $$PWD/launcher.h \ $$PWD/bluetoothlistener.h \ $$PWD/communicationstarter.h \ - $$PWD/symbiandevicemanager.h + $$PWD/symbiandevicemanager.h \ + $$PWD/tcftrkdevice.h \ + $$PWD/tcftrkmessage.h \ + $$PWD/json.h SOURCES += $$PWD/trkutils.cpp \ $$PWD/trkdevice.cpp \ $$PWD/launcher.cpp \ $$PWD/bluetoothlistener.cpp \ $$PWD/communicationstarter.cpp \ - $$PWD/symbiandevicemanager.cpp + $$PWD/symbiandevicemanager.cpp \ + $$PWD/tcftrkdevice.cpp \ + $$PWD/tcftrkmessage.cpp \ + $$PWD/json.cpp # Tests/trklauncher is a console application contains(QT, gui) { diff --git a/src/shared/symbianutils/tcftrkdevice.cpp b/src/shared/symbianutils/tcftrkdevice.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9f646e3ec8348ceef23613b75ffe503cafeae379 --- /dev/null +++ b/src/shared/symbianutils/tcftrkdevice.cpp @@ -0,0 +1,889 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "tcftrkdevice.h" +#include "json.h" + +#include <QtNetwork/QAbstractSocket> +#include <QtCore/QDebug> +#include <QtCore/QVector> +#include <QtCore/QQueue> +#include <QtCore/QTextStream> +#include <QtCore/QDateTime> + +enum { debug = 0 }; + +static const char messageTerminatorC[] = "\003\001"; + +namespace tcftrk { +// ------------- TcfTrkCommandError + +TcfTrkCommandError::TcfTrkCommandError() : timeMS(0), code(0), alternativeCode(0) +{ +} + +void TcfTrkCommandError::clear() +{ + timeMS = 0; + code = alternativeCode = 0; + format.clear(); + alternativeOrganization.clear(); +} + +void TcfTrkCommandError::write(QTextStream &str) const +{ + if (timeMS) { + const QDateTime time(QDate(1970, 1, 1)); + str << time.addMSecs(timeMS).toString(Qt::ISODate) << ": Error code: " << code + << " '" << format << '\''; + if (!alternativeOrganization.isEmpty()) + str << " ('" << alternativeOrganization << "', code: " << alternativeCode << ')'; + } else{ + str << "<No error>"; + } +} + +QString TcfTrkCommandError::toString() const +{ + QString rc; + QTextStream str(&rc); + write(str); + return rc; +} + +/* {"Time":1277459762255,"Code":1,"AltCode":-6,"AltOrg":"POSIX","Format":"Unknown error: -6"} */ +bool TcfTrkCommandError::parse(const QVector<JsonValue> &values) +{ + // Parse an arbitrary hash (that could as well be a command response) + // and check for error elements. + unsigned errorKeyCount = 0; + clear(); + do { + if (values.isEmpty() || values.front().type() != JsonValue::Object) + break; + foreach (const JsonValue &c, values.front().children()) { + if (c.name() == "Time") { + timeMS = c.data().toULongLong(); + errorKeyCount++; + } else if (c.name() == "Code") { + code = c.data().toInt(); + errorKeyCount++; + } else if (c.name() == "Format") { + format = c.data(); + errorKeyCount++; + } else if (c.name() == "AltCode") { + alternativeCode = c.data().toInt(); + errorKeyCount++; + } else if (c.name() == "AltOrg") { + alternativeOrganization = c.data(); + errorKeyCount++; + } + } + } while (false); + const bool errorFound = errorKeyCount >= 2u; // Should be at least 'Time', 'Code'. + if (!errorFound) + clear(); + if (debug) { + qDebug() << "TcfTrkCommandError::parse: Found error: " << errorFound; + if (!values.isEmpty()) + qDebug() << values.front().toString(); + } + return errorFound; +} + +// ------------ TcfTrkCommandResult + +TcfTrkCommandResult::TcfTrkCommandResult(Type t) : + type(t), service(LocatorService) +{ +} + +TcfTrkCommandResult::TcfTrkCommandResult(char typeChar, Services s, + const QByteArray &r, + const QVector<JsonValue> &v, + const QVariant &ck) : + type(FailReply), service(s), request(r), values(v), cookie(ck) +{ + switch (typeChar) { + case 'N': + type = FailReply; + break; + case 'P': + type = ProgressReply; + break; + case 'R': + type = commandError.parse(values) ? CommandErrorReply : SuccessReply; + break; + default: + qWarning("Unknown TCF reply type '%c'", typeChar); + } +} + +QString TcfTrkCommandResult::errorString() const +{ + QString rc; + QTextStream str(&rc); + + switch (type) { + case SuccessReply: + case ProgressReply: + str << "<No error>"; + return rc; + case FailReply: + str << "NAK"; + case CommandErrorReply: + commandError.write(str); + break; + } + // Append the failed command for reference + str << " (Command was: '"; + QByteArray printableRequest = request; + printableRequest.replace('\0', '|'); + str << printableRequest << "')"; + return rc; +} + +QString TcfTrkCommandResult::toString() const +{ + QString rc; + QTextStream str(&rc); + str << "Command answer "; + switch (type) { + case SuccessReply: + str << "[success]"; + break; + case CommandErrorReply: + str << "[command error]"; + break; + case FailReply: + str << "[fail (NAK)]"; + break; + case ProgressReply: + str << "[progress]"; + break; + } + str << ", " << values.size() << " values(s) to request: '"; + QByteArray printableRequest = request; + printableRequest.replace('\0', '|'); + str << printableRequest << "' "; + if (cookie.isValid()) + str << " cookie: " << cookie.toString(); + str << '\n'; + for (int i = 0, count = values.size(); i < count; i++) + str << '#' << i << ' ' << values.at(i).toString() << '\n'; + if (type == CommandErrorReply) + str << "Error: " << errorString(); + return rc; +} + +struct TcfTrkSendQueueEntry +{ + typedef TcfTrkDevice::MessageType MessageType; + + explicit TcfTrkSendQueueEntry(MessageType mt, + int tok, + Services s, + const QByteArray &d, + const TcfTrkCallback &cb= TcfTrkCallback(), + const QVariant &ck = QVariant()) : + messageType(mt), service(s), data(d), token(tok), cookie(ck), callback(cb) {} + + MessageType messageType; + Services service; + QByteArray data; + int token; + QVariant cookie; + TcfTrkCallback callback; +}; + +struct TcfTrkDevicePrivate { + typedef TcfTrkDevice::IODevicePtr IODevicePtr; + typedef QHash<int, TcfTrkSendQueueEntry> TokenWrittenMessageMap; + + TcfTrkDevicePrivate(); + + const QByteArray m_messageTerminator; + + IODevicePtr m_device; + unsigned m_verbose; + QByteArray m_readBuffer; + int m_token; + QQueue<TcfTrkSendQueueEntry> m_sendQueue; + TokenWrittenMessageMap m_writtenMessages; + QVector<QByteArray> m_registerNames; +}; + +TcfTrkDevicePrivate::TcfTrkDevicePrivate() : + m_messageTerminator(messageTerminatorC), + m_verbose(0), m_token(0) +{ +} + +TcfTrkDevice::TcfTrkDevice(QObject *parent) : + QObject(parent), d(new TcfTrkDevicePrivate) +{ +} + +TcfTrkDevice::~TcfTrkDevice() +{ + delete d; +} + +QVector<QByteArray> TcfTrkDevice::registerNames() const +{ + return d->m_registerNames; +} + +void TcfTrkDevice::setRegisterNames(const QVector<QByteArray>& n) +{ + d->m_registerNames = n; + if (d->m_verbose) { + QString msg; + QTextStream str(&msg); + const int count = n.size(); + str << "Registers (" << count << "): "; + for (int i = 0; i < count; i++) + str << '#' << i << '=' << n.at(i) << ' '; + emitLogMessage(msg); + } +} + +TcfTrkDevice::IODevicePtr TcfTrkDevice::device() const +{ + return d->m_device; +} + +TcfTrkDevice::IODevicePtr TcfTrkDevice::takeDevice() +{ + const IODevicePtr old = d->m_device; + if (!old.isNull()) { + old.data()->disconnect(this); + d->m_device = IODevicePtr(); + } + d->m_readBuffer.clear(); + d->m_token = 0; + d->m_sendQueue.clear(); + return old; +} + +void TcfTrkDevice::setDevice(const IODevicePtr &dp) +{ + if (dp.data() == d->m_device.data()) + return; + if (dp.isNull()) { + emitLogMessage(QLatin1String("Internal error: Attempt to set NULL device.")); + return; + } + takeDevice(); + d->m_device = dp; + connect(dp.data(), SIGNAL(readyRead()), this, SLOT(slotDeviceReadyRead())); + if (QAbstractSocket *s = qobject_cast<QAbstractSocket *>(dp.data())) { + connect(s, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(slotDeviceError())); + connect(s, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(slotDeviceSocketStateChanged())); + } +} + +void TcfTrkDevice::slotDeviceError() +{ + const QString message = d->m_device->errorString(); + emitLogMessage(message); + emit error(message); +} + +void TcfTrkDevice::slotDeviceSocketStateChanged() +{ + if (const QAbstractSocket *s = qobject_cast<const QAbstractSocket *>(d->m_device.data())) { + const QAbstractSocket::SocketState st = s->state(); + switch (st) { + case QAbstractSocket::UnconnectedState: + emitLogMessage(QLatin1String("Unconnected")); + break; + case QAbstractSocket::HostLookupState: + emitLogMessage(QLatin1String("HostLookupState")); + break; + case QAbstractSocket::ConnectingState: + emitLogMessage(QLatin1String("Connecting")); + break; + case QAbstractSocket::ConnectedState: + emitLogMessage(QLatin1String("Connected")); + break; + case QAbstractSocket::ClosingState: + emitLogMessage(QLatin1String("Closing")); + break; + default: + emitLogMessage(QString::fromLatin1("State %1").arg(st)); + break; + } + } +} + +static inline QString debugMessage(QByteArray message, const char *prefix = 0) +{ + message.replace('\0', '|'); + const QString messageS = QString::fromLatin1(message); + return prefix ? + (QLatin1String(prefix) + messageS) : messageS; +} + +void TcfTrkDevice::slotDeviceReadyRead() +{ + d->m_readBuffer += d->m_device->readAll(); + // Take complete message off front of readbuffer. + do { + const int messageEndPos = d->m_readBuffer.indexOf(d->m_messageTerminator); + if (messageEndPos == -1) + break; + const QByteArray message = d->m_readBuffer.left(messageEndPos); + if (debug) + qDebug("Read:\n%s", qPrintable(formatData(message))); + if (const int errorCode = parseMessage(message)) { + emitLogMessage(QString::fromLatin1("Parse error %1 for: %2").arg(errorCode).arg(debugMessage(message))); + } + d->m_readBuffer.remove(0, messageEndPos + d->m_messageTerminator.size()); + } while (!d->m_readBuffer.isEmpty()); + checkSendQueue(); // Send off further message +} + +// Split \0-terminated message into tokens, skipping the initial type character +static inline QVector<QByteArray> splitMessage(const QByteArray &message) +{ + QVector<QByteArray> tokens; + tokens.reserve(7); + const int messageSize = message.size(); + for (int pos = 2; pos < messageSize; ) { + const int nextPos = message.indexOf('\0', pos); + if (nextPos == -1) + break; + tokens.push_back(message.mid(pos, nextPos - pos)); + pos = nextPos + 1; + } + return tokens; +} + +int TcfTrkDevice::parseMessage(const QByteArray &message) +{ + if (d->m_verbose) + emitLogMessage(debugMessage(message, "TCF ->")); + // Special JSON parse error message or protocol format error. + // The port is usually closed after receiving it. + // "\3\2{"Time":1276096098255,"Code":3,"Format": "Protocol format error"}" + if (message.startsWith("\003\002")) { + QByteArray text = message.mid(2); + const QString errorMessage = QString::fromLatin1("Parse error received: %1").arg(QString::fromAscii(text)); + emit error(errorMessage); + return 0; + } + if (message.size() < 4 || message.at(1) != '\0') + return 1; + // Split into tokens + const char type = message.at(0); + const QVector<QByteArray> tokens = splitMessage(message); + switch (type) { + case 'E': + return parseTcfEvent(tokens); + case 'R': // Command replies + case 'N': + case 'P': + return parseTcfCommandReply(type, tokens); + default: + emitLogMessage(QString::fromLatin1("Unhandled message type: %1").arg(debugMessage(message))); + return 756; + } + return 0; +} + +int TcfTrkDevice::parseTcfCommandReply(char type, const QVector<QByteArray> &tokens) +{ + typedef TcfTrkDevicePrivate::TokenWrittenMessageMap::iterator TokenWrittenMessageMapIterator; + // Find the corresponding entry in the written messages hash. + const int tokenCount = tokens.size(); + if (tokenCount < 1) + return 234; + bool tokenOk; + const int token = tokens.at(0).toInt(&tokenOk); + if (!tokenOk) + return 235; + const TokenWrittenMessageMapIterator it = d->m_writtenMessages.find(token); + if (it == d->m_writtenMessages.end()) { + qWarning("TcfTrkDevice: Internal error: token %d not found for '%s'", + token, qPrintable(joinByteArrays(tokens))); + return 236; + } + // No callback: remove entry from map, happy + if (!it.value().callback) { + d->m_writtenMessages.erase(it); + return 0; + } + // Parse values into JSON + QVector<JsonValue> values; + values.reserve(tokenCount); + for (int i = 1; i < tokenCount; i++) { + if (!tokens.at(i).isEmpty()) { // Strange: Empty tokens occur. + const JsonValue value(tokens.at(i)); + if (value.isValid()) { + values.push_back(value); + } else { + qWarning("JSON parse error for reply to command token %d: #%d '%s'", + token, i, tokens.at(i).constData()); + d->m_writtenMessages.erase(it); + return -1; + } + } + } + + // Construct result and invoke callback, remove entry from map. + TcfTrkCallback callback = it.value().callback; + TcfTrkCommandResult result(type, it.value().service, it.value().data, + values, it.value().cookie); + d->m_writtenMessages.erase(it); + callback(result); + return 0; +} + +static const char locatorAnswerC[] = "E\0Locator\0Hello\0[\"Locator\"]"; + +int TcfTrkDevice::parseTcfEvent(const QVector<QByteArray> &tokens) +{ + // Event: Ignore the periodical heartbeat event, answer 'Hello', + // emit signal for the rest + if (tokens.size() < 3) + return 433; + const Services service = serviceFromName(tokens.at(0).constData()); + if (service == LocatorService && tokens.at(1) == "peerHeartBeat") + return 0; + QVector<JsonValue> values; + for (int i = 2; i < tokens.size(); i++) { + const JsonValue value(tokens.at(i)); + if (!value.isValid()) + return 434; + values.push_back(value); + } + // Parse known events, emit signals + QScopedPointer<TcfTrkEvent> knownEvent(TcfTrkEvent::parseEvent(service, tokens.at(1), values)); + if (!knownEvent.isNull()) { + // Answer hello event. + if (knownEvent->type() == TcfTrkEvent::LocatorHello) + writeMessage(QByteArray(locatorAnswerC, sizeof(locatorAnswerC))); + emit tcfEvent(*knownEvent); + } + emit genericTcfEvent(service, tokens.at(1), values); + + if (debug || d->m_verbose) { + QString msg; + QTextStream str(&msg); + if (knownEvent.isNull()) { + str << "Event: " << tokens.at(0) << ' ' << tokens.at(1) << '\n'; + foreach(const JsonValue &val, values) + str << " " << val.toString() << '\n'; + } else { + str << knownEvent->toString(); + } + emitLogMessage(msg); + } + + return 0; +} + +unsigned TcfTrkDevice::verbose() const +{ + return d->m_verbose; +} + +void TcfTrkDevice::setVerbose(unsigned v) +{ + d->m_verbose = v; +} + +void TcfTrkDevice::emitLogMessage(const QString &m) +{ + if (debug) + qWarning("%s", qPrintable(m)); + emit logMessage(m); +} + +bool TcfTrkDevice::checkOpen() +{ + if (d->m_device.isNull()) { + emitLogMessage(QLatin1String("Internal error: No device set on TcfTrkDevice.")); + return false; + } + if (!d->m_device->isOpen()) { + emitLogMessage(QLatin1String("Internal error: Device not open in TcfTrkDevice.")); + return false; + } + return true; +} + +void TcfTrkDevice::sendTcfTrkMessage(MessageType mt, Services service, const char *command, + const char *commandParameters, int commandParametersLength, + const TcfTrkCallback &callBack, + const QVariant &cookie) + +{ + if (!checkOpen()) + return; + // Format the message + const int token = d->m_token++; + QByteArray data; + data.reserve(30 + commandParametersLength); + data.append('C'); + data.append('\0'); + data.append(QByteArray::number(token)); + data.append('\0'); + data.append(serviceName(service)); + data.append('\0'); + data.append(command); + data.append('\0'); + if (commandParametersLength) + data.append(commandParameters, commandParametersLength); + const TcfTrkSendQueueEntry entry(mt, token, service, data, callBack, cookie); + d->m_sendQueue.enqueue(entry); + checkSendQueue(); +} + +void TcfTrkDevice::sendTcfTrkMessage(MessageType mt, Services service, const char *command, + const QByteArray &commandParameters, + const TcfTrkCallback &callBack, + const QVariant &cookie) +{ + sendTcfTrkMessage(mt, service, command, commandParameters.constData(), commandParameters.size(), + callBack, cookie); +} + +// Enclose in message frame and write. +void TcfTrkDevice::writeMessage(QByteArray data) +{ + if (!checkOpen()) + return; + + if (d->m_verbose) + emitLogMessage(debugMessage(data, "TCF <-")); + + // Ensure \0-termination which easily gets lost in QByteArray CT. + if (!data.endsWith('\0')) + data.append('\0'); + data += d->m_messageTerminator; + + if (debug > 1) + qDebug("Writing:\n%s", qPrintable(formatData(data))); + + d->m_device->write(data); + if (QAbstractSocket *as = qobject_cast<QAbstractSocket *>(d->m_device.data())) + as->flush(); +} + +void TcfTrkDevice::checkSendQueue() +{ + // Fire off messages or invoke noops until a message with reply is found + // and an entry to writtenMessages is made. + while (d->m_writtenMessages.empty()) { + if (d->m_sendQueue.isEmpty()) + break; + TcfTrkSendQueueEntry entry = d->m_sendQueue.dequeue(); + switch (entry.messageType) { + case MessageWithReply: + d->m_writtenMessages.insert(entry.token, entry); + writeMessage(entry.data); + break; + case MessageWithoutReply: + writeMessage(entry.data); + break; + case NoopMessage: // Invoke the noop-callback for synchronization + if (entry.callback) { + TcfTrkCommandResult noopResult(TcfTrkCommandResult::SuccessReply); + noopResult.cookie = entry.cookie; + entry.callback(noopResult); + } + break; + } + } +} + +// Fix slashes +static inline QString fixFileName(QString in) +{ + in.replace(QLatin1Char('/'), QLatin1Char('\\')); + return in; +} + +// Start a process (consisting of a non-reply setSettings and start). +void TcfTrkDevice::sendProcessStartCommand(const TcfTrkCallback &callBack, + const QString &binaryIn, + unsigned uid, + QStringList arguments, + QString workingDirectory, + bool debugControl, + const QStringList &additionalLibraries, + const QVariant &cookie) +{ + // Obtain the bin directory, expand by c:/sys/bin if missing + const QChar backSlash('\\'); + int slashPos = binaryIn.lastIndexOf(QLatin1Char('/')); + if (slashPos == -1) + slashPos = binaryIn.lastIndexOf(backSlash); + const QString sysBin = QLatin1String("c:/sys/bin"); + const QString binaryFileName = slashPos == -1 ? binaryIn : binaryIn.mid(slashPos + 1); + const QString binaryDirectory = slashPos == -1 ? sysBin : binaryIn.left(slashPos); + const QString binary = fixFileName(binaryDirectory + QLatin1Char('/') + binaryFileName); + + // Fixup: Does argv[0] convention exist on Symbian? + arguments.push_front(binary); + if (workingDirectory.isEmpty()) + workingDirectory = sysBin; + + // Format settings with empty dummy parameter + QByteArray setData; + JsonInputStream setStr(setData); + setStr << "" << '\0' + << '[' << "exeToLaunch" << ',' << "addExecutables" << ',' << "addLibraries" << ']' + << '\0' << '[' + << binary << ',' + << '{' << binaryFileName << ':' << QString::number(uid, 16) << '}' << ',' + << additionalLibraries + << ']'; + sendTcfTrkMessage(MessageWithoutReply, SettingsService, "set", setData); + + QByteArray startData; + JsonInputStream startStr(startData); + startStr << fixFileName(workingDirectory) + << '\0' << binary << '\0' << arguments << '\0' + << QStringList() << '\0' // Env is an array ["PATH=value"] (non-standard) + << debugControl; + sendTcfTrkMessage(MessageWithReply, ProcessesService, "start", startData, callBack, cookie); +} + +void TcfTrkDevice::sendProcessTerminateCommand(const TcfTrkCallback &callBack, + const QByteArray &id, + const QVariant &cookie) +{ + QByteArray data; + JsonInputStream str(data); + str << id; + sendTcfTrkMessage(MessageWithReply, ProcessesService, "terminate", data, callBack, cookie); +} + +void TcfTrkDevice::sendRunControlResumeCommand(const TcfTrkCallback &callBack, + const QByteArray &id, + RunControlResumeMode mode, + unsigned count, + quint64 rangeStart, + quint64 rangeEnd, + const QVariant &cookie) +{ + QByteArray resumeData; + JsonInputStream str(resumeData); + str << id << '\0' << int(mode) << '\0' << count; + switch (mode) { + case RM_STEP_OVER_RANGE: + case RM_STEP_INTO_RANGE: + case RM_REVERSE_STEP_OVER_RANGE: + case RM_REVERSE_STEP_INTO_RANGE: + str << '\0' << '{' << "RANGE_START" << ':' << rangeStart + << ',' << "RANGE_END" << ':' << rangeEnd << '}'; + break; + default: + break; + } + sendTcfTrkMessage(MessageWithReply, RunControlService, "resume", resumeData, callBack, cookie); +} + +void TcfTrkDevice::sendRunControlSuspendCommand(const TcfTrkCallback &callBack, + const QByteArray &id, + const QVariant &cookie) +{ + QByteArray data; + JsonInputStream str(data); + str << id; + sendTcfTrkMessage(MessageWithReply, RunControlService, "suspend", data, callBack, cookie); +} + +void TcfTrkDevice::sendRunControlResumeCommand(const TcfTrkCallback &callBack, + const QByteArray &id, + const QVariant &cookie) +{ + sendRunControlResumeCommand(callBack, id, RM_RESUME, 1, 0, 0, cookie); +} + +void TcfTrkDevice::sendBreakpointsAddCommand(const TcfTrkCallback &callBack, + const Breakpoint &bp, + const QVariant &cookie) +{ + QByteArray data; + JsonInputStream str(data); + str << bp; + sendTcfTrkMessage(MessageWithReply, BreakpointsService, "add", data, callBack, cookie); +} + +void TcfTrkDevice::sendBreakpointsRemoveCommand(const TcfTrkCallback &callBack, + const QByteArray &id, + const QVariant &cookie) +{ + sendBreakpointsRemoveCommand(callBack, QVector<QByteArray>(1, id), cookie); +} + +void TcfTrkDevice::sendBreakpointsRemoveCommand(const TcfTrkCallback &callBack, + const QVector<QByteArray> &ids, + const QVariant &cookie) +{ + QByteArray data; + JsonInputStream str(data); + str << ids; + sendTcfTrkMessage(MessageWithReply, BreakpointsService, "remove", data, callBack, cookie); +} + +void TcfTrkDevice::sendBreakpointsEnableCommand(const TcfTrkCallback &callBack, + const QByteArray &id, + bool enable, + const QVariant &cookie) +{ + sendBreakpointsEnableCommand(callBack, QVector<QByteArray>(1, id), enable, cookie); +} + +void TcfTrkDevice::sendBreakpointsEnableCommand(const TcfTrkCallback &callBack, + const QVector<QByteArray> &ids, + bool enable, + const QVariant &cookie) +{ + QByteArray data; + JsonInputStream str(data); + str << ids; + sendTcfTrkMessage(MessageWithReply, BreakpointsService, + enable ? "enable" : "disable", + data, callBack, cookie); +} + +void TcfTrkDevice::sendMemorySetCommand(const TcfTrkCallback &callBack, + const QByteArray &contextId, + quint64 start, const QByteArray& data, + const QVariant &cookie) +{ + QByteArray getData; + JsonInputStream str(getData); + // start/word size/mode. Mode should ideally be 1 (continue on error?) + str << contextId << '\0' << start << '\0' << 1 << '\0' << data.size() << '\0' << 1 + << '\0' << data.toBase64(); + sendTcfTrkMessage(MessageWithReply, MemoryService, "set", getData, callBack, cookie); +} + +void TcfTrkDevice::sendMemoryGetCommand(const TcfTrkCallback &callBack, + const QByteArray &contextId, + quint64 start, quint64 size, + const QVariant &cookie) +{ + QByteArray data; + JsonInputStream str(data); + // start/word size/mode. Mode should ideally be 1 (continue on error?) + str << contextId << '\0' << start << '\0' << 1 << '\0' << size << '\0' << 1; + sendTcfTrkMessage(MessageWithReply, MemoryService, "get", data, callBack, cookie); +} + +QByteArray TcfTrkDevice::parseMemoryGet(const TcfTrkCommandResult &r) +{ + if (r.type != TcfTrkCommandResult::SuccessReply || r.values.size() < 1) + return QByteArray(); + const JsonValue &memoryV = r.values.front(); + + if (memoryV.type() != JsonValue::String || memoryV.data().size() < 2 + || !memoryV.data().endsWith('=')) + return QByteArray(); + // Catch errors reported as hash: + // R.4."TlVMTA==".{"Time":1276786871255,"Code":1,"AltCode":-38,"AltOrg":"POSIX","Format":"BadDescriptor"} + // Not sure what to make of it. + if (r.values.size() >= 2 && r.values.at(1).type() == JsonValue::Object) + qWarning("Error retrieving memory: %s", r.values.at(1).toString(false).constData()); + // decode + const QByteArray memory = QByteArray::fromBase64(memoryV.data()); + if (memory.isEmpty()) + qWarning("Base64 decoding of %s failed.", memoryV.data().constData()); + if (debug) + qDebug("TcfTrkDevice::parseMemoryGet: received %d bytes", memory.size()); + return memory; +} + +void TcfTrkDevice::sendRegistersGetMCommand(const TcfTrkCallback &callBack, + const QByteArray &contextId, + const QVector<QByteArray> &ids, + const QVariant &cookie) +{ + // TODO: use "Registers" (which uses base64-encoded values) + QByteArray data; + JsonInputStream str(data); + str << contextId << '\0' << ids; + sendTcfTrkMessage(MessageWithReply, SimpleRegistersService, "get", data, callBack, cookie); +} + +void TcfTrkDevice::sendRegistersGetMRangeCommand(const TcfTrkCallback &callBack, + const QByteArray &contextId, + unsigned start, unsigned count) +{ + const unsigned end = start + count; + if (end > (unsigned)d->m_registerNames.size()) { + qWarning("TcfTrkDevice: No register name set for index %u (size: %d).", end, d->m_registerNames.size()); + return; + } + + QVector<QByteArray> ids; + ids.reserve(count); + for (unsigned i = start; i < end; i++) + ids.push_back(d->m_registerNames.at(i)); + sendRegistersGetMCommand(callBack, contextId, ids, QVariant(start)); +} + +// Set register +void TcfTrkDevice::sendRegistersSetCommand(const TcfTrkCallback &callBack, + const QByteArray &contextId, + const QByteArray &id, + unsigned value, + const QVariant &cookie) +{ + // TODO: use "Registers" (which uses base64-encoded values) + QByteArray data; + JsonInputStream str(data); + str << contextId << '\0' << QVector<QByteArray>(1, id) + << '\0' << QVector<QByteArray>(1, QByteArray::number(value, 16)); + sendTcfTrkMessage(MessageWithReply, SimpleRegistersService, "set", data, callBack, cookie); +} + +// Set register +void TcfTrkDevice::sendRegistersSetCommand(const TcfTrkCallback &callBack, + const QByteArray &contextId, + unsigned registerNumber, + unsigned value, + const QVariant &cookie) +{ + if (registerNumber >= (unsigned)d->m_registerNames.size()) { + qWarning("TcfTrkDevice: No register name set for index %u (size: %d).", registerNumber, d->m_registerNames.size()); + return; + } + sendRegistersSetCommand(callBack, contextId, + d->m_registerNames[registerNumber], + value, cookie); +} + +} // namespace tcftrk diff --git a/src/shared/symbianutils/tcftrkdevice.h b/src/shared/symbianutils/tcftrkdevice.h new file mode 100644 index 0000000000000000000000000000000000000000..be5077b2a0823895814eecab282603d031c2e1d1 --- /dev/null +++ b/src/shared/symbianutils/tcftrkdevice.h @@ -0,0 +1,271 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef TCFTRKENGINE_H +#define TCFTRKENGINE_H + +#include "symbianutils_global.h" +#include "tcftrkmessage.h" +#include "callback.h" +#include "json.h" + +#include <QtCore/QObject> +#include <QtCore/QSharedPointer> +#include <QtCore/QVector> +#include <QtCore/QVariant> +#include <QtCore/QStringList> + +QT_BEGIN_NAMESPACE +class QIODevice; +class QTextStream; +QT_END_NAMESPACE + +namespace tcftrk { + +struct TcfTrkDevicePrivate; +struct Breakpoint; + +/* Command error handling in TCF: + * 1) 'Severe' errors (JSON format, parameter format): Trk emits a + * nonstandard message (\3\2 error paramaters) and closes the connection. + * 2) Protocol errors: 'N' without error message is returned. + * 3) Errors in command execution: 'R' with a TCF error hash is returned + * (see TcfTrkCommandError). */ + +/* Error code return in 'R' reply to command + * (see top of 'Services' documentation). */ +struct SYMBIANUTILS_EXPORT TcfTrkCommandError { + TcfTrkCommandError(); + void clear(); + operator bool() const { return timeMS != 0; } + QString toString() const; + void write(QTextStream &str) const; + bool parse(const QVector<JsonValue> &values); + + quint64 timeMS; // Since 1.1.1970 + int code; + QByteArray format; // message + // 'Alternative' meaning, like altOrg="POSIX"/altCode=<some errno> + QByteArray alternativeOrganization; + int alternativeCode; +}; + +/* Answer to a Tcf command passed to the callback. */ +struct SYMBIANUTILS_EXPORT TcfTrkCommandResult { + enum Type { + SuccessReply, // 'R' and no error -> all happy. + CommandErrorReply, // 'R' with TcfTrkCommandError received + ProgressReply, // 'P', progress indicator + FailReply // 'N' Protocol NAK, severe error + }; + + explicit TcfTrkCommandResult(Type t = SuccessReply); + explicit TcfTrkCommandResult(char typeChar, Services service, + const QByteArray &request, + const QVector<JsonValue> &values, + const QVariant &cookie); + + QString toString() const; + QString errorString() const; + operator bool() const { return type == SuccessReply || type == ProgressReply; } + + Type type; + Services service; + QByteArray request; + TcfTrkCommandError commandError; + QVector<JsonValue> values; + QVariant cookie; +}; + +typedef trk::Callback<const TcfTrkCommandResult &> TcfTrkCallback; + +/* TcfTrkDevice: TCF communication helper using an asynchronous QIODevice + * implementing the TCF protocol according to: +http://dev.eclipse.org/svnroot/dsdp/org.eclipse.tm.tcf/trunk/docs/TCF%20Specification.html +http://dev.eclipse.org/svnroot/dsdp/org.eclipse.tm.tcf/trunk/docs/TCF%20Services.html + * Commands can be sent along with callbacks that are passed a + * TcfTrkCommandResult and an opaque QVariant cookie. In addition, events are emitted. +*/ + +class SYMBIANUTILS_EXPORT TcfTrkDevice : public QObject +{ + Q_PROPERTY(unsigned verbose READ verbose WRITE setVerbose) + Q_OBJECT +public: + enum MessageType { MessageWithReply, + MessageWithoutReply, /* Non-standard: "Settings:set" command does not reply */ + NoopMessage }; + + typedef QSharedPointer<QIODevice> IODevicePtr; + + explicit TcfTrkDevice(QObject *parent = 0); + virtual ~TcfTrkDevice(); + + unsigned verbose() const; + + // Mapping of register names for indices + QVector<QByteArray> registerNames() const; + void setRegisterNames(const QVector<QByteArray>& n); + + IODevicePtr device() const; + IODevicePtr takeDevice(); + void setDevice(const IODevicePtr &dp); + + void sendTcfTrkMessage(MessageType mt, Services service, + const char *command, + const char *commandParameters, int commandParametersLength, + const TcfTrkCallback &callBack = TcfTrkCallback(), + const QVariant &cookie = QVariant()); + + void sendTcfTrkMessage(MessageType mt, Services service, const char *command, + const QByteArray &commandParameters, + const TcfTrkCallback &callBack = TcfTrkCallback(), + const QVariant &cookie = QVariant()); + + // Convenience messages: Start a process + void sendProcessStartCommand(const TcfTrkCallback &callBack, + const QString &binary, + unsigned uid, + QStringList arguments = QStringList(), + QString workingDirectory = QString(), + bool debugControl = true, + const QStringList &additionalLibraries = QStringList(), + const QVariant &cookie = QVariant()); + + void sendProcessTerminateCommand(const TcfTrkCallback &callBack, + const QByteArray &id, + const QVariant &cookie = QVariant()); + + void sendRunControlSuspendCommand(const TcfTrkCallback &callBack, + const QByteArray &id, + const QVariant &cookie = QVariant()); + + // Resume / Step (see RunControlResumeMode). + void sendRunControlResumeCommand(const TcfTrkCallback &callBack, + const QByteArray &id, + RunControlResumeMode mode, + unsigned count /* = 1, currently ignored. */, + quint64 rangeStart, quint64 rangeEnd, + const QVariant &cookie = QVariant()); + + // Convenience to resume a suspended process + void sendRunControlResumeCommand(const TcfTrkCallback &callBack, + const QByteArray &id, + const QVariant &cookie = QVariant()); + + void sendBreakpointsAddCommand(const TcfTrkCallback &callBack, + const Breakpoint &b, + const QVariant &cookie = QVariant()); + + void sendBreakpointsRemoveCommand(const TcfTrkCallback &callBack, + const QByteArray &id, + const QVariant &cookie = QVariant()); + + void sendBreakpointsRemoveCommand(const TcfTrkCallback &callBack, + const QVector<QByteArray> &id, + const QVariant &cookie = QVariant()); + + void sendBreakpointsEnableCommand(const TcfTrkCallback &callBack, + const QByteArray &id, + bool enable, + const QVariant &cookie = QVariant()); + + void sendBreakpointsEnableCommand(const TcfTrkCallback &callBack, + const QVector<QByteArray> &id, + bool enable, + const QVariant &cookie = QVariant()); + + + void sendMemoryGetCommand(const TcfTrkCallback &callBack, + const QByteArray &contextId, + quint64 start, quint64 size, + const QVariant &cookie = QVariant()); + + void sendMemorySetCommand(const TcfTrkCallback &callBack, + const QByteArray &contextId, + quint64 start, const QByteArray& data, + const QVariant &cookie = QVariant()); + + // Reply is an array of hexvalues + void sendRegistersGetMCommand(const TcfTrkCallback &callBack, + const QByteArray &contextId, + const QVector<QByteArray> &ids, + const QVariant &cookie = QVariant()); + + // Convenience to get a range of register "R0" .. "R<n>". + // Cookie will be an int containing "start". + void sendRegistersGetMRangeCommand(const TcfTrkCallback &callBack, + const QByteArray &contextId, + unsigned start, unsigned count); + + // Set register + void sendRegistersSetCommand(const TcfTrkCallback &callBack, + const QByteArray &contextId, + const QByteArray &ids, + unsigned value, + const QVariant &cookie = QVariant()); + // Set register + void sendRegistersSetCommand(const TcfTrkCallback &callBack, + const QByteArray &contextId, + unsigned registerNumber, + unsigned value, + const QVariant &cookie = QVariant()); + + static QByteArray parseMemoryGet(const TcfTrkCommandResult &r); + +signals: + void genericTcfEvent(int service, const QByteArray &name, const QVector<tcftrk::JsonValue> &value); + void tcfEvent(const tcftrk::TcfTrkEvent &knownEvent); + + void logMessage(const QString &); + void error(const QString &); + +public slots: + void setVerbose(unsigned v); + +private slots: + void slotDeviceError(); + void slotDeviceSocketStateChanged(); + void slotDeviceReadyRead(); + +private: + bool checkOpen(); + void checkSendQueue(); + void writeMessage(QByteArray data); + void emitLogMessage(const QString &); + int parseMessage(const QByteArray &); + int parseTcfCommandReply(char type, const QVector<QByteArray> &tokens); + int parseTcfEvent(const QVector<QByteArray> &tokens); + + TcfTrkDevicePrivate *d; +}; + +} // namespace tcftrk + +#endif // TCFTRKENGINE_H diff --git a/src/shared/symbianutils/tcftrkmessage.cpp b/src/shared/symbianutils/tcftrkmessage.cpp new file mode 100644 index 0000000000000000000000000000000000000000..929bb8d05af5fecc74d4ea2283d0fa8015043c0e --- /dev/null +++ b/src/shared/symbianutils/tcftrkmessage.cpp @@ -0,0 +1,550 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "tcftrkmessage.h" +#include "json.h" + +#include <QtCore/QString> +#include <QtCore/QTextStream> + +// Names matching the enum +static const char *serviceNamesC[] = +{ "Locator", "RunControl", "Processes", "Memory", "Settings", "Breakpoints", + "Registers", "SimpleRegisters", + "UnknownService"}; + +namespace tcftrk { + +SYMBIANUTILS_EXPORT QString joinByteArrays(const QVector<QByteArray> &a, char sep) +{ + QString rc; + const int count = a.size(); + for (int i = 0; i < count; i++) { + if (i) + rc += QLatin1Char(sep); + rc += QString::fromUtf8(a.at(i)); + } + return rc; +} + +static inline bool jsonToBool(const JsonValue& js) +{ + return js.type() == JsonValue::Boolean && js.data() == "true"; +} + +SYMBIANUTILS_EXPORT const char *serviceName(Services s) +{ + return serviceNamesC[s]; +} + +SYMBIANUTILS_EXPORT Services serviceFromName(const char *n) +{ + const int count = sizeof(serviceNamesC)/sizeof(char *); + for (int i = 0; i < count; i++) + if (!qstrcmp(serviceNamesC[i], n)) + return static_cast<Services>(i); + return UnknownService; +} + +SYMBIANUTILS_EXPORT QString formatData(const QByteArray &a) +{ + const int columns = 16; + QString rc; + QTextStream str(&rc); + str.setIntegerBase(16); + str.setPadChar(QLatin1Char('0')); + const unsigned char *start = reinterpret_cast<const unsigned char *>(a.constData()); + const unsigned char *end = start + a.size(); + for (const unsigned char *p = start; p < end ; ) { + str << "0x"; + str.setFieldWidth(4); + str << (p - start); + str.setFieldWidth(0); + str << ' '; + QString asc; + int c = 0; + for ( ; c < columns && p < end; c++, p++) { + const unsigned u = *p; + str.setFieldWidth(2); + str << u; + str.setFieldWidth(0); + str << ' '; + switch (u) { + case '\n': + asc += QLatin1String("\\n"); + break; + case '\r': + asc += QLatin1String("\\r"); + break; + case '\t': + asc += QLatin1String("\\t"); + break; + default: + if (u >= 32 && u < 128) { + asc += QLatin1Char(' '); + asc += QLatin1Char(u); + } else { + asc += QLatin1String(" ."); + } + break; + } + } + if (const int remainder = columns - c) + str << QString(3 * remainder, QLatin1Char(' ')); + str << ' ' << asc << '\n'; + } + return rc; +} + +// ----------- RunControlContext +RunControlContext::RunControlContext() : + flags(0), resumeFlags(0) +{ +} + +void RunControlContext::clear() +{ + flags =0; + resumeFlags = 0; + id.clear(); + osid.clear(); + parentId.clear(); +} + +RunControlContext::Type RunControlContext::typeFromTcfId(const QByteArray &id) +{ + // "p12" or "p12.t34"? + return id.contains(".t") ? Thread : Process; +} + +unsigned RunControlContext::processId() const +{ + return processIdFromTcdfId(id); +} + +unsigned RunControlContext::threadId() const +{ + return threadIdFromTcdfId(id); +} + +unsigned RunControlContext::processIdFromTcdfId(const QByteArray &id) +{ + // Cut out process id from "p12" or "p12.t34"? + if (!id.startsWith('p')) + return 0; + const int dotPos = id.indexOf('.'); + const int pLen = dotPos == -1 ? id.size() : dotPos; + return id.mid(1, pLen - 1).toUInt(); +} + +unsigned RunControlContext::threadIdFromTcdfId(const QByteArray &id) +{ + const int tPos = id.indexOf(".t"); + return tPos != -1 ? id.mid(tPos + 2).toUInt() : uint(0); +} + +QByteArray RunControlContext::tcfId(unsigned processId, unsigned threadId /* = 0 */) +{ + QByteArray rc("p"); + rc += QByteArray::number(processId); + if (threadId) { + rc += ".t"; + rc += QByteArray::number(threadId); + } + return rc; +} + +RunControlContext::Type RunControlContext::type() const +{ + return RunControlContext::typeFromTcfId(id); +} + +bool RunControlContext::parse(const JsonValue &val) +{ + clear(); + if (val.type() != JsonValue::Object) + return false; + foreach(const JsonValue &c, val.children()) { + if (c.name() == "ID") { + id = c.data(); + } else if (c.name() == "OSID") { + osid = c.data(); + } else if (c.name() == "ParentID") { + parentId = c.data(); + } else if (c.name() == "IsContainer") { + if (jsonToBool(c)) + flags |= Container; + } else if (c.name() == "CanTerminate") { + if (jsonToBool(c)) + flags |= CanTerminate; + } else if (c.name() == "CanResume") { + resumeFlags = c.data().toUInt(); + } else if (c.name() == "HasState") { + if (jsonToBool(c)) + flags |= HasState; + } else if (c.name() == "CanSuspend") { + if (jsonToBool(c)) + flags |= CanSuspend; + } + } + return true; +} + +QString RunControlContext::toString() const +{ + QString rc; + QTextStream str(&rc); + format(str); + return rc; +} + +void RunControlContext::format(QTextStream &str) const +{ + str << " id='" << id << "' osid='" << osid + << "' parentId='" << parentId <<"' "; + if (flags & Container) + str << "[container] "; + if (flags & HasState) + str << "[has state] "; + if (flags & CanSuspend) + str << "[can suspend] "; + if (flags & CanSuspend) + str << "[can terminate] "; + str.setIntegerBase(16); + str << " resume_flags: 0x" << resumeFlags; + str.setIntegerBase(10); +} + +// ------ ModuleLoadEventInfo +ModuleLoadEventInfo::ModuleLoadEventInfo() : + loaded(false), codeAddress(0), dataAddress(0), requireResume(false) +{ +} + +void ModuleLoadEventInfo::clear() +{ + loaded = requireResume = false; + codeAddress = dataAddress =0; +} + +bool ModuleLoadEventInfo::parse(const JsonValue &val) +{ + clear(); + if (val.type() != JsonValue::Object) + return false; + foreach(const JsonValue &c, val.children()) { + if (c.name() == "Name") { + name = c.data(); + } else if (c.name() == "File") { + file = c.data(); + } else if (c.name() == "CodeAddress") { + codeAddress = c.data().toULongLong(); + } else if (c.name() == "DataAddress") { + dataAddress = c.data().toULongLong(); + } else if (c.name() == "Loaded") { + loaded = jsonToBool(c); + } else if (c.name() == "RequireResume") { + requireResume =jsonToBool(c); + } + } + return true; +} +void ModuleLoadEventInfo::format(QTextStream &str) const +{ + str << "name='" << name << "' file='" << file << "' " << + (loaded ? "[loaded] " : "[not loaded] "); + if (requireResume) + str << "[requires resume] "; + str.setIntegerBase(16); + str << " code: 0x" << codeAddress << " data: 0x" << dataAddress; + str.setIntegerBase(10); +} + +// ---------------------- Breakpoint + +// Types matching enum +static const char *breakPointTypesC[] = {"Software", "Hardware", "Auto"}; + +Breakpoint::Breakpoint(quint64 loc) : + type(Auto), enabled(true), ignoreCount(0), location(loc), size(1), thumb(true) +{ + if (loc) + id = idFromLocation(location); +} + +void Breakpoint::setContextId(unsigned processId, unsigned threadId) +{ + contextIds = QVector<QByteArray>(1, RunControlContext::tcfId(processId, threadId)); +} + +QByteArray Breakpoint::idFromLocation(quint64 loc) +{ + return QByteArray("BP_0x") + QByteArray::number(loc, 16); +} + +QString Breakpoint::toString() const +{ + QString rc; + QTextStream str(&rc); + str.setIntegerBase(16); + str << "Breakpoint '" << id << "' " << breakPointTypesC[type] << " for contexts '" + << joinByteArrays(contextIds, ',') << "' at 0x" << location; + str.setIntegerBase(10); + str << " size " << size; + if (enabled) + str << " [enabled]"; + if (thumb) + str << " [thumb]"; + if (ignoreCount) + str << " IgnoreCount " << ignoreCount; + return rc; +} + +JsonInputStream &operator<<(JsonInputStream &str, const Breakpoint &b) +{ + if (b.contextIds.isEmpty()) + qWarning("tcftrk::Breakpoint: No context ids specified"); + + str << '{' << "ID" << ':' << QString::fromUtf8(b.id) << ',' + << "BreakpointType" << ':' << breakPointTypesC[b.type] << ',' + << "Enabled" << ':' << b.enabled << ',' + << "IgnoreCount" << ':' << b.ignoreCount << ',' + << "ContextIds" << ':' << b.contextIds << ',' + << "Location" << ':' << QString::number(b.location) << ',' + << "Size" << ':' << b.size << ',' + << "THUMB_BREAKPOINT" << ':' << b.thumb + << '}'; + return str; +} + +// --- Events +TcfTrkEvent::TcfTrkEvent(Type type) : m_type(type) +{ +} + +TcfTrkEvent::~TcfTrkEvent() +{ +} + +TcfTrkEvent::Type TcfTrkEvent::type() const +{ + return m_type; +} + +QString TcfTrkEvent::toString() const +{ + return QString(); +} + +static const char sharedLibrarySuspendReasonC[] = "Shared Library"; + +TcfTrkEvent *TcfTrkEvent::parseEvent(Services s, const QByteArray &nameBA, const QVector<JsonValue> &values) +{ + switch (s) { + case LocatorService: + if (nameBA == "Hello" && values.size() == 1 && values.front().type() == JsonValue::Array) { + QStringList services; + foreach (const JsonValue &jv, values.front().children()) + services.push_back(QString::fromUtf8(jv.data())); + return new TcfTrkLocatorHelloEvent(services); + } + break; + case RunControlService: + if (values.empty()) + return 0; + // "id/PC/Reason/Data" + if (nameBA == "contextSuspended" && values.size() == 4) { + const QByteArray idBA = values.at(0).data(); + const quint64 pc = values.at(1).data().toULongLong(); + const QByteArray reasonBA = values.at(2).data(); + // Module load: Special + if (reasonBA == sharedLibrarySuspendReasonC) { + ModuleLoadEventInfo info; + if (!info.parse(values.at(3))) + return 0; + return new TcfTrkRunControlModuleLoadContextSuspendedEvent(idBA, reasonBA, pc, info); + } + return new TcfTrkRunControlContextSuspendedEvent(idBA, reasonBA, pc); + } // "contextSuspended" + if (nameBA == "contextAdded") + return TcfTrkRunControlContextAddedEvent::parseEvent(values); + if (nameBA == "contextRemoved" && values.front().type() == JsonValue::Array) { + QVector<QByteArray> ids; + foreach(const JsonValue &c, values.front().children()) + ids.push_back(c.data()); + return new TcfTrkRunControlContextRemovedEvent(ids); + } + break; + default: + break; + } + return 0; +} + +// -------------- TcfTrkServiceHelloEvent +TcfTrkLocatorHelloEvent::TcfTrkLocatorHelloEvent(const QStringList &s) : + TcfTrkEvent(LocatorHello), + m_services(s) +{ +} + +QString TcfTrkLocatorHelloEvent::toString() const +{ + return QLatin1String("ServiceHello: ") + m_services.join(QLatin1String(", ")); +} + +// -------------- TcfTrkIdEvent +TcfTrkIdEvent::TcfTrkIdEvent(Type t, const QByteArray &id) : + TcfTrkEvent(t), m_id(id) +{ +} + +// ---------- TcfTrkIdsEvent +TcfTrkIdsEvent::TcfTrkIdsEvent(Type t, const QVector<QByteArray> &ids) : + TcfTrkEvent(t), m_ids(ids) +{ +} + +QString TcfTrkIdsEvent::joinedIdString(const char sep) const +{ + return joinByteArrays(m_ids, sep); +} + +// ---------------- TcfTrkRunControlContextAddedEvent +TcfTrkRunControlContextAddedEvent::TcfTrkRunControlContextAddedEvent(const RunControlContexts &c) : + TcfTrkEvent(RunControlContextAdded), m_contexts(c) +{ +} + +TcfTrkRunControlContextAddedEvent + *TcfTrkRunControlContextAddedEvent::parseEvent(const QVector<JsonValue> &values) +{ + // Parse array of contexts + if (values.size() < 1 || values.front().type() != JsonValue::Array) + return 0; + + RunControlContexts contexts; + foreach (const JsonValue &v, values.front().children()) { + RunControlContext context; + if (context.parse(v)) + contexts.push_back(context); + } + return new TcfTrkRunControlContextAddedEvent(contexts); +} + +QString TcfTrkRunControlContextAddedEvent::toString() const +{ + QString rc; + QTextStream str(&rc); + str << "RunControl: " << m_contexts.size() << " context(s) " + << (type() == RunControlContextAdded ? "added" : "removed") + << '\n'; + foreach (const RunControlContext &c, m_contexts) { + c.format(str); + str << '\n'; + } + return rc; +} + +// --------------- TcfTrkRunControlContextRemovedEvent +TcfTrkRunControlContextRemovedEvent::TcfTrkRunControlContextRemovedEvent(const QVector<QByteArray> &ids) : + TcfTrkIdsEvent(RunControlContextRemoved, ids) +{ +} + +QString TcfTrkRunControlContextRemovedEvent::toString() const +{ + return QLatin1String("RunControl: Removed contexts '") + joinedIdString() + ("'."); +} + +// --------------- TcfTrkRunControlContextSuspendedEvent +TcfTrkRunControlContextSuspendedEvent::TcfTrkRunControlContextSuspendedEvent(const QByteArray &id, + const QByteArray &reason, + quint64 pc) : + TcfTrkIdEvent(RunControlSuspended, id), m_pc(pc), m_reason(reason) +{ +} + +TcfTrkRunControlContextSuspendedEvent::TcfTrkRunControlContextSuspendedEvent(Type t, + const QByteArray &id, + const QByteArray &reason, + quint64 pc) : + TcfTrkIdEvent(t, id), m_pc(pc), m_reason(reason) +{ +} + +void TcfTrkRunControlContextSuspendedEvent::format(QTextStream &str) const +{ + str.setIntegerBase(16); + str << "RunControl: '" << idString() << "' suspended at 0x" + << m_pc << ": '" << m_reason << "'."; + str.setIntegerBase(10); +} + +QString TcfTrkRunControlContextSuspendedEvent::toString() const +{ + QString rc; + QTextStream str(&rc); + format(str); + return rc; +} + +TcfTrkRunControlContextSuspendedEvent::Reason TcfTrkRunControlContextSuspendedEvent::reason() const +{ + if (m_reason == sharedLibrarySuspendReasonC) + return ModuleLoad; + if (m_reason == "Breakpoint") + return BreakPoint; + // 'Data abort exception'/'Thread has panicked' ... unfortunately somewhat unspecific. + if (m_reason.contains("exception") || m_reason.contains("panick")) + return Crash; + return Other; +} + +TcfTrkRunControlModuleLoadContextSuspendedEvent::TcfTrkRunControlModuleLoadContextSuspendedEvent(const QByteArray &id, + const QByteArray &reason, + quint64 pc, + const ModuleLoadEventInfo &mi) : + TcfTrkRunControlContextSuspendedEvent(RunControlModuleLoadSuspended, id, reason, pc), + m_mi(mi) +{ +} + +QString TcfTrkRunControlModuleLoadContextSuspendedEvent::toString() const +{ + QString rc; + QTextStream str(&rc); + TcfTrkRunControlContextSuspendedEvent::format(str); + str << ' '; + m_mi.format(str); + return rc; +} + + +} // namespace tcftrk diff --git a/src/shared/symbianutils/tcftrkmessage.h b/src/shared/symbianutils/tcftrkmessage.h new file mode 100644 index 0000000000000000000000000000000000000000..8b073c8ab406c325dca0d4fdc43b5dee0b3d9f17 --- /dev/null +++ b/src/shared/symbianutils/tcftrkmessage.h @@ -0,0 +1,284 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef TRCFTRKMESSAGE_H +#define TRCFTRKMESSAGE_H + +#include "symbianutils_global.h" + +#include <QtCore/QStringList> +#include <QtCore/QVector> + +QT_BEGIN_NAMESPACE +class QTextStream; +QT_END_NAMESPACE + +namespace tcftrk { + +class JsonValue; +class JsonInputStream; + +enum Services { + LocatorService, + RunControlService, + ProcessesService, + MemoryService, + SettingsService, // non-standard, trk specific + BreakpointsService, + RegistersService, + SimpleRegistersService, // non-standard, trk specific + UnknownService +}; // Note: Check string array 'serviceNamesC' of same size when modifying this. + +// Modes of RunControl/'Resume' (see EDF documentation). +// As of 24.6.2010, RM_RESUME, RM_STEP_OVER, RM_STEP_INTO, +// RM_STEP_OVER_RANGE, RM_STEP_INTO_RANGE are supported with +// RANG_START/RANGE_END parameters. +enum RunControlResumeMode { + RM_RESUME = 0, + RM_STEP_OVER = 1, RM_STEP_INTO = 2, + RM_STEP_OVER_LINE = 3, RM_STEP_INTO_LINE = 4, + RM_STEP_OUT = 5, RM_REVERSE_RESUME = 6, + RM_REVERSE_STEP_OVER = 7, RM_REVERSE_STEP_INTO = 8, + RM_REVERSE_STEP_OVER_LINE = 9, RM_REVERSE_STEP_INTO_LINE = 10, + RM_REVERSE_STEP_OUT = 11, RM_STEP_OVER_RANGE = 12, + RM_STEP_INTO_RANGE = 13, RM_REVERSE_STEP_OVER_RANGE = 14, + RM_REVERSE_STEP_INTO_RANGE = 15 +}; + +SYMBIANUTILS_EXPORT const char *serviceName(Services s); +SYMBIANUTILS_EXPORT Services serviceFromName(const char *); + +// Debug helpers +SYMBIANUTILS_EXPORT QString formatData(const QByteArray &a); +SYMBIANUTILS_EXPORT QString joinByteArrays(const QVector<QByteArray> &a, char sep = ','); + +// Context used in 'RunControl contextAdded' events and in reply +// to 'Processes start'. Could be thread or process. +struct SYMBIANUTILS_EXPORT RunControlContext { + enum Flags { + Container = 0x1, HasState = 0x2, CanSuspend = 0x4, + CanTerminate = 0x8 + }; + enum Type { Process, Thread }; + + RunControlContext(); + Type type() const; + unsigned processId() const; + unsigned threadId() const; + + void clear(); + bool parse(const JsonValue &v); + void format(QTextStream &str) const; + QString toString() const; + + // Helper for converting the TCF ids ("p12" or "p12.t34") + static Type typeFromTcfId(const QByteArray &id); + static unsigned processIdFromTcdfId(const QByteArray &id); + static unsigned threadIdFromTcdfId(const QByteArray &id); + static QByteArray tcfId(unsigned processId, unsigned threadId = 0); + + unsigned flags; + unsigned resumeFlags; + QByteArray id; // "p434.t699" + QByteArray osid; // Non-standard: Process or thread id + QByteArray parentId; // Parent process id of a thread. +}; + +// Module load information occuring with 'RunControl contextSuspended' events +struct SYMBIANUTILS_EXPORT ModuleLoadEventInfo { + ModuleLoadEventInfo(); + void clear(); + bool parse(const JsonValue &v); + void format(QTextStream &str) const; + + QByteArray name; + QByteArray file; + bool loaded; + quint64 codeAddress; + quint64 dataAddress; + bool requireResume; +}; + +// Breakpoint as supported by TcfTrk source June 2010 +// TODO: Add watchpoints,etc once they are implemented +struct SYMBIANUTILS_EXPORT Breakpoint { + enum Type { Software, Hardware, Auto }; + + explicit Breakpoint(quint64 loc = 0); + void setContextId(unsigned processId, unsigned threadId = 0); + QString toString() const; + + static QByteArray idFromLocation(quint64 loc); // Automagically determine from location + + Type type; + bool enabled; + int ignoreCount; + QVector<QByteArray> contextIds; // Process or thread ids. + QByteArray id; // Id of the breakpoint; + quint64 location; + unsigned size; + bool thumb; +}; + +SYMBIANUTILS_EXPORT JsonInputStream &operator<<(JsonInputStream &str, const Breakpoint &b); + +// Event hierarchy +class SYMBIANUTILS_EXPORT TcfTrkEvent { + Q_DISABLE_COPY(TcfTrkEvent) +public: + enum Type { None, + LocatorHello, + RunControlContextAdded, + RunControlContextRemoved, + RunControlSuspended, + RunControlBreakpointSuspended, + RunControlModuleLoadSuspended, + RunControlResumed + }; + + virtual ~TcfTrkEvent(); + + Type type() const; + virtual QString toString() const; + + static TcfTrkEvent *parseEvent(Services s, const QByteArray &name, const QVector<JsonValue> &val); + +protected: + explicit TcfTrkEvent(Type type = None); + +private: + const Type m_type; +}; + +// ServiceHello +class SYMBIANUTILS_EXPORT TcfTrkLocatorHelloEvent : public TcfTrkEvent { +public: + explicit TcfTrkLocatorHelloEvent(const QStringList &); + + const QStringList &services() { return m_services; } + virtual QString toString() const; + +private: + QStringList m_services; +}; + +// Base for events that just have one id as parameter +// (simple suspend) +class SYMBIANUTILS_EXPORT TcfTrkIdEvent : public TcfTrkEvent { +protected: + explicit TcfTrkIdEvent(Type t, const QByteArray &id); +public: + QByteArray id() const { return m_id; } + QString idString() const { return QString::fromUtf8(m_id); } + +private: + const QByteArray m_id; +}; + +// Base for events that just have some ids as parameter +// (context removed) +class SYMBIANUTILS_EXPORT TcfTrkIdsEvent : public TcfTrkEvent { +protected: + explicit TcfTrkIdsEvent(Type t, const QVector<QByteArray> &ids); + +public: + QVector<QByteArray> ids() const { return m_ids; } + QString joinedIdString(const char sep = ',') const; + +private: + const QVector<QByteArray> m_ids; +}; + +// RunControlContextAdded +class SYMBIANUTILS_EXPORT TcfTrkRunControlContextAddedEvent : public TcfTrkEvent { +public: + typedef QVector<RunControlContext> RunControlContexts; + + explicit TcfTrkRunControlContextAddedEvent(const RunControlContexts &c); + + const RunControlContexts &contexts() const { return m_contexts; } + virtual QString toString() const; + + static TcfTrkRunControlContextAddedEvent *parseEvent(const QVector<JsonValue> &val); + +private: + const RunControlContexts m_contexts; +}; + +// RunControlContextRemoved +class SYMBIANUTILS_EXPORT TcfTrkRunControlContextRemovedEvent : public TcfTrkIdsEvent { +public: + explicit TcfTrkRunControlContextRemovedEvent(const QVector<QByteArray> &id); + virtual QString toString() const; +}; + +// Simple RunControlContextSuspended (process/thread) +class SYMBIANUTILS_EXPORT TcfTrkRunControlContextSuspendedEvent : public TcfTrkIdEvent { +public: + enum Reason { BreakPoint, ModuleLoad, Crash, Other } ; + + explicit TcfTrkRunControlContextSuspendedEvent(const QByteArray &id, + const QByteArray &reason, + quint64 pc = 0); + virtual QString toString() const; + + quint64 pc() const { return m_pc; } + QByteArray reasonID() const { return m_reason; } + Reason reason() const; + +protected: + explicit TcfTrkRunControlContextSuspendedEvent(Type t, + const QByteArray &id, + const QByteArray &reason, + quint64 pc = 0); + void format(QTextStream &str) const; + +private: + const quint64 m_pc; + const QByteArray m_reason; +}; + +// RunControlContextSuspended due to module load +class SYMBIANUTILS_EXPORT TcfTrkRunControlModuleLoadContextSuspendedEvent : public TcfTrkRunControlContextSuspendedEvent { +public: + explicit TcfTrkRunControlModuleLoadContextSuspendedEvent(const QByteArray &id, + const QByteArray &reason, + quint64 pc, + const ModuleLoadEventInfo &mi); + + virtual QString toString() const; + const ModuleLoadEventInfo &info() const { return m_mi; } + +private: + const ModuleLoadEventInfo m_mi; +}; + +} // namespace tcftrk +#endif // TRCFTRKMESSAGE_H diff --git a/src/shared/symbianutils/trkutils.cpp b/src/shared/symbianutils/trkutils.cpp index fd9978c736ff4364d92a00e6fa1825bd07a093e7..8b40d1f88d4663ae5607ef502b246e7dd11db64e 100644 --- a/src/shared/symbianutils/trkutils.cpp +++ b/src/shared/symbianutils/trkutils.cpp @@ -131,6 +131,49 @@ QString Session::deviceDescription(unsigned verbose) const return msg.arg(formatTrkVersion(trkAppVersion)); } +QString Session::toString() const +{ + QString rc; + QTextStream str(&rc); + str << "Session: " << deviceDescription(false) << '\n' + << "pid: " << pid << " thread: " << tid << ' '; + str.setIntegerBase(16); + str << " code: 0x" << codeseg << " data: 0x" << dataseg << '\n'; + if (const int libCount = libraries.size()) { + str << "Libraries:\n"; + for (int i = 0; i < libCount; i++) + str << " #" << i << ' ' << libraries.at(i).name + << " code: 0x" << libraries.at(i).codeseg + << " data: 0x" << libraries.at(i).dataseg << '\n'; + } + if (const int moduleCount = modules.size()) { + str << "Modules:\n"; + for (int i = 0; i < moduleCount; i++) + str << " #" << i << ' ' << modules.at(i) << '\n'; + } + str.setIntegerBase(10); + str << "Current thread: " << currentThread << '\n'; + if (const int threadCount = threads.size()) { + str << "Threads:\n"; + for (int i = 0; i < threadCount; i++) + str << " #" << i << ' ' << threads.at(i); + } + + if (!addressToBP.isEmpty()) { + typedef QHash<uint, uint>::const_iterator BP_ConstIterator; + str << "Breakpoints:\n"; + const BP_ConstIterator cend = addressToBP.constEnd(); + for (BP_ConstIterator it = addressToBP.constBegin(); it != cend; ++it) { + str.setIntegerBase(16); + str << " 0x" << it.key(); + str.setIntegerBase(10); + str << ' ' << it.value() << '\n'; + } + } + + return rc; +} + // -------------- QByteArray decode7d(const QByteArray &ba) diff --git a/src/shared/symbianutils/trkutils.h b/src/shared/symbianutils/trkutils.h index e438c1b0ceae4cd41175fe386ac46f650d112e3d..2cf2c99da4b761d08c9a3c43062fe8ee0bc5d0a9 100644 --- a/src/shared/symbianutils/trkutils.h +++ b/src/shared/symbianutils/trkutils.h @@ -175,6 +175,7 @@ struct SYMBIANUTILS_EXPORT Session Session(); void reset(); QString deviceDescription(unsigned verbose) const; + QString toString() const; // Trk feedback byte cpuMajor;