/**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the tools applications of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** 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. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "codamessage.h" #include "json.h" #include #include // Names matching the enum static const char *serviceNamesC[] = { "Locator", "RunControl", "Processes", "Memory", "Settings", "Breakpoints", "Registers", "Logging", "FileSystem", "SymbianInstall", "SymbianOSData", "DebugSessionControl", "UnknownService"}; namespace Coda { SYMBIANUTILS_EXPORT QString joinByteArrays(const QVector &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(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(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(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("Coda::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 CodaEvent::CodaEvent(Type type) : m_type(type) { } CodaEvent::~CodaEvent() { } CodaEvent::Type CodaEvent::type() const { return m_type; } QString CodaEvent::toString() const { return QString(); } static const char sharedLibrarySuspendReasonC[] = "Shared Library"; CodaEvent *CodaEvent::parseEvent(Services s, const QByteArray &nameBA, const QVector &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 CodaLocatorHelloEvent(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(); QByteArray messageBA; // Module load: Special if (reasonBA == sharedLibrarySuspendReasonC) { ModuleLoadEventInfo info; if (!info.parse(values.at(3))) return 0; return new CodaRunControlModuleLoadContextSuspendedEvent(idBA, reasonBA, pc, info); } else { // hash containing a 'message'-key with a verbose crash message. if (values.at(3).type() == JsonValue::Object && values.at(3).childCount() && values.at(3).children().at(0).type() == JsonValue::String) messageBA = values.at(3).children().at(0).data(); } return new CodaRunControlContextSuspendedEvent(idBA, reasonBA, messageBA, pc); } // "contextSuspended" if (nameBA == "contextAdded") return CodaRunControlContextAddedEvent::parseEvent(values); if (nameBA == "contextRemoved" && values.front().type() == JsonValue::Array) { QVector ids; foreach(const JsonValue &c, values.front().children()) ids.push_back(c.data()); return new CodaRunControlContextRemovedEvent(ids); } break; case LoggingService: if ((nameBA == "writeln" || nameBA == "write" /*not yet used*/) && values.size() >= 2) return new CodaLoggingWriteEvent(values.at(0).data(), values.at(1).data()); break; case ProcessesService: if (nameBA == "exited" && values.size() >= 2) return new CodaProcessExitedEvent(values.at(0).data()); break; default: break; } return 0; } // -------------- CodaServiceHelloEvent CodaLocatorHelloEvent::CodaLocatorHelloEvent(const QStringList &s) : CodaEvent(LocatorHello), m_services(s) { } QString CodaLocatorHelloEvent::toString() const { return QLatin1String("ServiceHello: ") + m_services.join(QLatin1String(", ")); } // -------------- Logging event CodaLoggingWriteEvent::CodaLoggingWriteEvent(const QByteArray &console, const QByteArray &message) : CodaEvent(LoggingWriteEvent), m_console(console), m_message(message) { } QString CodaLoggingWriteEvent::toString() const { QByteArray msgBA = m_console; msgBA += ": "; msgBA += m_message; return QString::fromUtf8(msgBA); } // -------------- CodaIdEvent CodaIdEvent::CodaIdEvent(Type t, const QByteArray &id) : CodaEvent(t), m_id(id) { } // ---------- CodaIdsEvent CodaIdsEvent::CodaIdsEvent(Type t, const QVector &ids) : CodaEvent(t), m_ids(ids) { } QString CodaIdsEvent::joinedIdString(const char sep) const { return joinByteArrays(m_ids, sep); } // ---------------- CodaRunControlContextAddedEvent CodaRunControlContextAddedEvent::CodaRunControlContextAddedEvent(const RunControlContexts &c) : CodaEvent(RunControlContextAdded), m_contexts(c) { } CodaRunControlContextAddedEvent *CodaRunControlContextAddedEvent::parseEvent(const QVector &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 CodaRunControlContextAddedEvent(contexts); } QString CodaRunControlContextAddedEvent::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; } // --------------- CodaRunControlContextRemovedEvent CodaRunControlContextRemovedEvent::CodaRunControlContextRemovedEvent(const QVector &ids) : CodaIdsEvent(RunControlContextRemoved, ids) { } QString CodaRunControlContextRemovedEvent::toString() const { return QLatin1String("RunControl: Removed contexts '") + joinedIdString() + ("'."); } // --------------- CodaRunControlContextSuspendedEvent CodaRunControlContextSuspendedEvent::CodaRunControlContextSuspendedEvent(const QByteArray &id, const QByteArray &reason, const QByteArray &message, quint64 pc) : CodaIdEvent(RunControlSuspended, id), m_pc(pc), m_reason(reason), m_message(message) { } CodaRunControlContextSuspendedEvent::CodaRunControlContextSuspendedEvent(Type t, const QByteArray &id, const QByteArray &reason, quint64 pc) : CodaIdEvent(t, id), m_pc(pc), m_reason(reason) { } void CodaRunControlContextSuspendedEvent::format(QTextStream &str) const { str.setIntegerBase(16); str << "RunControl: '" << idString() << "' suspended at 0x" << m_pc << ": '" << m_reason << "'."; str.setIntegerBase(10); if (!m_message.isEmpty()) str << " (" <