diff options
Diffstat (limited to 'tools')
22 files changed, 3428 insertions, 479 deletions
diff --git a/tools/qdoc3/htmlgenerator.cpp b/tools/qdoc3/htmlgenerator.cpp index 49ba5f6..89b1e98 100644 --- a/tools/qdoc3/htmlgenerator.cpp +++ b/tools/qdoc3/htmlgenerator.cpp @@ -1359,7 +1359,7 @@ void HtmlGenerator::generateClassLikeNode(const InnerNode *inner, if (!s->inherited.isEmpty()) { out() << "<ul>\n"; - generateSectionInheritedList(*s, inner, marker, true); + generateSectionInheritedList(*s, inner, marker); out() << "</ul>\n"; } } @@ -2636,44 +2636,6 @@ void HtmlGenerator::generateLegaleseList(const Node *relative, } } -/*void HtmlGenerator::generateSynopsis(const Node *node, - const Node *relative, - CodeMarker *marker, - CodeMarker::SynopsisStyle style) -{ - QString marked = marker->markedUpSynopsis(node, relative, style); - QRegExp templateTag("(<[^@>]*>)"); - if (marked.indexOf(templateTag) != -1) { - QString contents = protectEnc(marked.mid(templateTag.pos(1), - templateTag.cap(1).length())); - marked.replace(templateTag.pos(1), templateTag.cap(1).length(), - contents); - } - marked.replace(QRegExp("<@param>([a-z]+)_([1-9n])</@param>"), - "<i>\\1<sub>\\2</sub></i>"); - marked.replace("<@param>", "<i>"); - marked.replace("</@param>", "</i>"); - - if (style == CodeMarker::Summary) - marked.replace("@name>", "b>"); - - if (style == CodeMarker::SeparateList) { - QRegExp extraRegExp("<@extra>.*</@extra>"); - extraRegExp.setMinimal(true); - marked.replace(extraRegExp, ""); - } - else { - marked.replace("<@extra>", " <tt>"); - marked.replace("</@extra>", "</tt>"); - } - - if (style != CodeMarker::Detailed) { - marked.replace("<@type>", ""); - marked.replace("</@type>", ""); - } - out() << highlightedCode(marked, marker, relative); -}*/ - #ifdef QDOC_QML void HtmlGenerator::generateQmlItem(const Node *node, const Node *relative, @@ -2703,6 +2665,17 @@ void HtmlGenerator::generateQmlItem(const Node *node, marked.replace("<@type>", ""); marked.replace("</@type>", ""); } + if (node->type() == Node::QmlProperty) { + const QmlPropertyNode* qpn = static_cast<const QmlPropertyNode*>(node); + if (!summary && qpn->name() == "color" && qpn->dataType() == "color") { + if (relative && relative->name() == "GradientStop") { + qDebug() << "color : color"; + debugging_on = true; + qDebug() << " " << relative->name() << relative->type() << relative->subType(); + qDebug() << marked; + } + } + } out() << highlightedCode(marked, marker, relative); debugging_on = false; } @@ -2822,24 +2795,23 @@ void HtmlGenerator::generateOverviewList(const Node *relative, CodeMarker * /* m } } -#ifdef QDOC_NAME_ALIGNMENT void HtmlGenerator::generateSection(const NodeList& nl, const Node *relative, CodeMarker *marker, CodeMarker::SynopsisStyle style) { - bool name_alignment = true; + bool alignNames = true; if (!nl.isEmpty()) { bool twoColumn = false; if (style == CodeMarker::SeparateList) { - name_alignment = false; + alignNames = false; twoColumn = (nl.count() >= 16); } else if (nl.first()->type() == Node::Property) { twoColumn = (nl.count() >= 5); - name_alignment = false; + alignNames = false; } - if (name_alignment) { + if (alignNames) { out() << "<table class=\"alignedsummary\">\n"; } else { @@ -2857,7 +2829,7 @@ void HtmlGenerator::generateSection(const NodeList& nl, continue; } - if (name_alignment) { + if (alignNames) { out() << "<tr><td class=\"memItemLeft rightAlign topAlign\"> "; } else { @@ -2866,15 +2838,15 @@ void HtmlGenerator::generateSection(const NodeList& nl, out() << "<li class=\"fn\">"; } - generateSynopsis(*m, relative, marker, style, name_alignment); - if (name_alignment) + generateSynopsis(*m, relative, marker, style, alignNames); + if (alignNames) out() << "</td></tr>\n"; else out() << "</li>\n"; i++; ++m; } - if (name_alignment) + if (alignNames) out() << "</table>\n"; else { out() << "</ul>\n"; @@ -2889,18 +2861,18 @@ void HtmlGenerator::generateSectionList(const Section& section, CodeMarker *marker, CodeMarker::SynopsisStyle style) { - bool name_alignment = true; + bool alignNames = true; if (!section.members.isEmpty()) { bool twoColumn = false; if (style == CodeMarker::SeparateList) { - name_alignment = false; + alignNames = false; twoColumn = (section.members.count() >= 16); } else if (section.members.first()->type() == Node::Property) { twoColumn = (section.members.count() >= 5); - name_alignment = false; + alignNames = false; } - if (name_alignment) { + if (alignNames) { out() << "<table class=\"alignedsummary\">\n"; } else { @@ -2918,7 +2890,7 @@ void HtmlGenerator::generateSectionList(const Section& section, continue; } - if (name_alignment) { + if (alignNames) { out() << "<tr><td class=\"memItemLeft topAlign rightAlign\"> "; } else { @@ -2927,15 +2899,15 @@ void HtmlGenerator::generateSectionList(const Section& section, out() << "<li class=\"fn\">"; } - generateSynopsis(*m, relative, marker, style, name_alignment); - if (name_alignment) + generateSynopsis(*m, relative, marker, style, alignNames); + if (alignNames) out() << "</td></tr>\n"; else out() << "</li>\n"; i++; ++m; } - if (name_alignment) + if (alignNames) out() << "</table>\n"; else { out() << "</ul>\n"; @@ -2946,22 +2918,18 @@ void HtmlGenerator::generateSectionList(const Section& section, if (style == CodeMarker::Summary && !section.inherited.isEmpty()) { out() << "<ul>\n"; - generateSectionInheritedList(section, relative, marker, name_alignment); + generateSectionInheritedList(section, relative, marker); out() << "</ul>\n"; } } void HtmlGenerator::generateSectionInheritedList(const Section& section, const Node *relative, - CodeMarker *marker, - bool nameAlignment) + CodeMarker *marker) { QList<QPair<ClassNode *, int> >::ConstIterator p = section.inherited.begin(); while (p != section.inherited.end()) { - if (nameAlignment) - out() << "<li class=\"fn\">"; - else - out() << "<li class=\"fn\">"; + out() << "<li class=\"fn\">"; out() << (*p).second << " "; if ((*p).second == 1) { out() << section.singularMember; @@ -2981,7 +2949,7 @@ void HtmlGenerator::generateSynopsis(const Node *node, const Node *relative, CodeMarker *marker, CodeMarker::SynopsisStyle style, - bool nameAlignment) + bool alignNames) { QString marked = marker->markedUpSynopsis(node, relative, style); QRegExp templateTag("(<[^@>]*>)"); @@ -3014,14 +2982,13 @@ void HtmlGenerator::generateSynopsis(const Node *node, marked.replace("<@type>", ""); marked.replace("</@type>", ""); } - out() << highlightedCode(marked, marker, relative, style, nameAlignment); + out() << highlightedCode(marked, marker, relative, alignNames); } QString HtmlGenerator::highlightedCode(const QString& markedCode, - CodeMarker *marker, - const Node *relative, - CodeMarker::SynopsisStyle , - bool nameAlignment) + CodeMarker* marker, + const Node* relative, + bool alignNames) { QString src = markedCode; QString html; @@ -3031,20 +2998,24 @@ QString HtmlGenerator::highlightedCode(const QString& markedCode, const QChar charLangle = '<'; const QChar charAt = '@'; - // replace all <@link> tags: "(<@link node=\"([^\"]+)\">).*(</@link>)" + static const QString typeTag("type"); + static const QString headerTag("headerfile"); + static const QString funcTag("func"); static const QString linkTag("link"); + + // replace all <@link> tags: "(<@link node=\"([^\"]+)\">).*(</@link>)" bool done = false; - for (int i = 0, n = src.size(); i < n;) { + for (int i = 0, srcSize = src.size(); i < srcSize;) { if (src.at(i) == charLangle && src.at(i + 1).unicode() == '@') { - if (nameAlignment && !done) {// && (i != 0)) Why was this here? + if (alignNames && !done) {// && (i != 0)) Why was this here? html += "</td><td class=\"memItemRight bottomAlign\">"; done = true; } i += 2; - if (parseArg(src, linkTag, &i, n, &arg, &par1)) { + if (parseArg(src, linkTag, &i, srcSize, &arg, &par1)) { html += "<b>"; - QString link = linkForNode( - CodeMarker::nodeForString(par1.toString()), relative); + const Node* n = CodeMarker::nodeForString(par1.toString()); + QString link = linkForNode(n, relative); addLink(link, arg, &html); html += "</b>"; } @@ -3064,16 +3035,14 @@ QString HtmlGenerator::highlightedCode(const QString& markedCode, // replace all <@func> tags: "(<@func target=\"([^\"]*)\">)(.*)(</@func>)" src = html; html = QString(); - static const QString funcTag("func"); - for (int i = 0, n = src.size(); i < n;) { + for (int i = 0, srcSize = src.size(); i < srcSize;) { if (src.at(i) == charLangle && src.at(i + 1) == charAt) { i += 2; - if (parseArg(src, funcTag, &i, n, &arg, &par1)) { - QString link = linkForNode( - marker->resolveTarget(par1.toString(), - myTree, - relative), - relative); + if (parseArg(src, funcTag, &i, srcSize, &arg, &par1)) { + const Node* n = marker->resolveTarget(par1.toString(), + myTree, + relative); + QString link = linkForNode(n, relative); addLink(link, arg, &html); par1 = QStringRef(); } @@ -3091,295 +3060,36 @@ QString HtmlGenerator::highlightedCode(const QString& markedCode, // replace all "(<@(type|headerfile|func)(?: +[^>]*)?>)(.*)(</@\\2>)" tags src = html; html = QString(); - static const QString typeTags[] = { "type", "headerfile", "func" }; - for (int i = 0, n = src.size(); i < n;) { - if (src.at(i) == charLangle && src.at(i + 1) == charAt) { + + for (int i=0, srcSize=src.size(); i<srcSize;) { + if (src.at(i) == charLangle && src.at(i+1) == charAt) { i += 2; bool handled = false; - for (int k = 0; k != 3; ++k) { - if (parseArg(src, typeTags[k], &i, n, &arg, &par1)) { - par1 = QStringRef(); - const Node* n = marker->resolveTarget(arg.toString(), myTree, relative); - QString link = linkForNode(n,relative); - addLink(link, arg, &html); - handled = true; - break; + if (parseArg(src, typeTag, &i, srcSize, &arg, &par1)) { + par1 = QStringRef(); + const Node* n = marker->resolveTarget(arg.toString(), myTree, relative); + if (HtmlGenerator::debugging_on) { + if (n) { + qDebug() << " " << n->name() << n->type() << n->subType(); + qDebug() << " " << relative->name() << relative->type() << relative->subType(); + } } + addLink(linkForNode(n,relative), arg, &html); + handled = true; } - if (!handled) { - html += charLangle; - html += charAt; - } - } - else { - html += src.at(i++); - } - } - - // replace all - // "<@comment>" -> "<span class=\"comment\">"; - // "<@preprocessor>" -> "<span class=\"preprocessor\">"; - // "<@string>" -> "<span class=\"string\">"; - // "<@char>" -> "<span class=\"char\">"; - // "</@(?:comment|preprocessor|string|char)>" -> "</span>" - src = html; - html = QString(); - static const QString spanTags[] = { - "<@comment>", "<span class=\"comment\">", - "<@preprocessor>", "<span class=\"preprocessor\">", - "<@string>", "<span class=\"string\">", - "<@char>", "<span class=\"char\">", - "</@comment>", "</span>", - "</@preprocessor>","</span>", - "</@string>", "</span>", - "</@char>", "</span>" - // "<@char>", "<font color=blue>", - // "</@char>", "</font>", - // "<@func>", "<font color=green>", - // "</@func>", "</font>", - // "<@id>", "<i>", - // "</@id>", "</i>", - // "<@keyword>", "<b>", - // "</@keyword>", "</b>", - // "<@number>", "<font color=yellow>", - // "</@number>", "</font>", - // "<@op>", "<b>", - // "</@op>", "</b>", - // "<@param>", "<i>", - // "</@param>", "</i>", - // "<@string>", "<font color=green>", - // "</@string>", "</font>", - }; - for (int i = 0, n = src.size(); i < n;) { - if (src.at(i) == charLangle) { - bool handled = false; - for (int k = 0; k != 8; ++k) { - const QString & tag = spanTags[2 * k]; - if (tag == QStringRef(&src, i, tag.length())) { - html += spanTags[2 * k + 1]; - i += tag.length(); - handled = true; - break; - } + else if (parseArg(src, headerTag, &i, srcSize, &arg, &par1)) { + par1 = QStringRef(); + const Node* n = marker->resolveTarget(arg.toString(), myTree, relative); + addLink(linkForNode(n,relative), arg, &html); + handled = true; } - if (!handled) { - ++i; - if (src.at(i) == charAt || - (src.at(i) == QLatin1Char('/') && src.at(i + 1) == charAt)) { - // drop 'our' unknown tags (the ones still containing '@') - while (i < n && src.at(i) != QLatin1Char('>')) - ++i; - ++i; - } - else { - // retain all others - html += charLangle; - } + else if (parseArg(src, funcTag, &i, srcSize, &arg, &par1)) { + par1 = QStringRef(); + const Node* n = marker->resolveTarget(arg.toString(), myTree, relative); + addLink(linkForNode(n,relative), arg, &html); + handled = true; } - } - else { - html += src.at(i); - ++i; - } - } - - return html; -} -#else -void HtmlGenerator::generateSectionList(const Section& section, - const Node *relative, - CodeMarker *marker, - CodeMarker::SynopsisStyle style) -{ - if (!section.members.isEmpty()) { - bool twoColumn = false; - if (style == CodeMarker::SeparateList) { - twoColumn = (section.members.count() >= 16); - } - else if (section.members.first()->type() == Node::Property) { - twoColumn = (section.members.count() >= 5); - } - if (twoColumn) - out() << "<table class=\"generic\">\n"; - if (++numTableRows % 2 == 1) - out() << "<tr class=\"odd topAlign\">"; - else - out() << "<tr class=\"even topAlign\">"; - -// << "<tr><td class=\"topAlign\">"; - out() << "<ul>\n"; - - int i = 0; - NodeList::ConstIterator m = section.members.begin(); - while (m != section.members.end()) { - if ((*m)->access() == Node::Private) { - ++m; - continue; - } - - if (twoColumn && i == (int) (section.members.count() + 1) / 2) - out() << "</ul></td><td class=\"topAlign\"><ul>\n"; - - out() << "<li class=\"fn\">"; - if (style == CodeMarker::Accessors) - out() << "<b>"; - generateSynopsis(*m, relative, marker, style); - if (style == CodeMarker::Accessors) - out() << "</b>"; - out() << "</li>\n"; - i++; - ++m; - } - out() << "</ul>\n"; - if (twoColumn) - out() << "</td></tr>\n</table>\n"; - } - - if (style == CodeMarker::Summary && !section.inherited.isEmpty()) { - out() << "<ul>\n"; - generateSectionInheritedList(section, relative, marker); - out() << "</ul>\n"; - } -} - -void HtmlGenerator::generateSectionInheritedList(const Section& section, - const Node *relative, - CodeMarker *marker) -{ - QList<QPair<ClassNode *, int> >::ConstIterator p = section.inherited.begin(); - while (p != section.inherited.end()) { - out() << "<li class=\"fn\">"; - out() << (*p).second << " "; - if ((*p).second == 1) { - out() << section.singularMember; - } else { - out() << section.pluralMember; - } - out() << " inherited from <a href=\"" << fileName((*p).first) - << "#" << HtmlGenerator::cleanRef(section.name.toLower()) << "\">" - << protectEnc(marker->plainFullName((*p).first, relative)) - << "</a></li>\n"; - ++p; - } -} - -void HtmlGenerator::generateSynopsis(const Node *node, - const Node *relative, - CodeMarker *marker, - CodeMarker::SynopsisStyle style) -{ - QString marked = marker->markedUpSynopsis(node, relative, style); - QRegExp templateTag("(<[^@>]*>)"); - if (marked.indexOf(templateTag) != -1) { - QString contents = protectEnc(marked.mid(templateTag.pos(1), - templateTag.cap(1).length())); - marked.replace(templateTag.pos(1), templateTag.cap(1).length(), - contents); - } - marked.replace(QRegExp("<@param>([a-z]+)_([1-9n])</@param>"), "<i>\\1<sub>\\2</sub></i>"); - marked.replace("<@param>", "<i>"); - marked.replace("</@param>", "</i>"); - - if (style == CodeMarker::Summary) - marked.replace("@name>", "b>"); - - if (style == CodeMarker::SeparateList) { - QRegExp extraRegExp("<@extra>.*</@extra>"); - extraRegExp.setMinimal(true); - marked.replace(extraRegExp, ""); - } else { - marked.replace("<@extra>", "<tt>"); - marked.replace("</@extra>", "</tt>"); - } - - if (style != CodeMarker::Detailed) { - marked.replace("<@type>", ""); - marked.replace("</@type>", ""); - } - out() << highlightedCode(marked, marker, relative); -} - -QString HtmlGenerator::highlightedCode(const QString& markedCode, - CodeMarker *marker, - const Node *relative) -{ - QString src = markedCode; - QString html; - QStringRef arg; - QStringRef par1; - - const QChar charLangle = '<'; - const QChar charAt = '@'; - - // replace all <@link> tags: "(<@link node=\"([^\"]+)\">).*(</@link>)" - static const QString linkTag("link"); - for (int i = 0, n = src.size(); i < n;) { - if (src.at(i) == charLangle && src.at(i + 1) == charAt) { - i += 2; - if (parseArg(src, linkTag, &i, n, &arg, &par1)) { - const Node* node = CodeMarker::nodeForString(par1.toString()); - QString link = linkForNode(node, relative); - addLink(link, arg, &html); - } - else { - html += charLangle; - html += charAt; - } - } - else { - html += src.at(i++); - } - } - - if (slow) { - // is this block ever used at all? - // replace all <@func> tags: "(<@func target=\"([^\"]*)\">)(.*)(</@func>)" - src = html; - html = QString(); - static const QString funcTag("func"); - for (int i = 0, n = src.size(); i < n;) { - if (src.at(i) == charLangle && src.at(i + 1) == charAt) { - i += 2; - if (parseArg(src, funcTag, &i, n, &arg, &par1)) { - QString link = linkForNode( - marker->resolveTarget(par1.toString(), - myTree, - relative), - relative); - addLink(link, arg, &html); - par1 = QStringRef(); - } - else { - html += charLangle; - html += charAt; - } - } - else { - html += src.at(i++); - } - } - } - - // replace all "(<@(type|headerfile|func)(?: +[^>]*)?>)(.*)(</@\\2>)" tags - src = html; - html = QString(); - static const QString typeTags[] = { "type", "headerfile", "func" }; - for (int i = 0, n = src.size(); i < n;) { - if (src.at(i) == charLangle && src.at(i + 1) == charAt) { - i += 2; - bool handled = false; - for (int k = 0; k != 3; ++k) { - if (parseArg(src, typeTags[k], &i, n, &arg, &par1)) { - par1 = QStringRef(); - QString link = linkForNode( - marker->resolveTarget(arg.toString(), myTree, relative), - relative); - addLink(link, arg, &html); - handled = true; - break; - } - } if (!handled) { html += charLangle; html += charAt; @@ -3407,22 +3117,6 @@ QString HtmlGenerator::highlightedCode(const QString& markedCode, "</@preprocessor>","</span>", "</@string>", "</span>", "</@char>", "</span>" - // "<@char>", "<font color=blue>", - // "</@char>", "</font>", - // "<@func>", "<font color=green>", - // "</@func>", "</font>", - // "<@id>", "<i>", - // "</@id>", "</i>", - // "<@keyword>", "<b>", - // "</@keyword>", "</b>", - // "<@number>", "<font color=yellow>", - // "</@number>", "</font>", - // "<@op>", "<b>", - // "</@op>", "</b>", - // "<@param>", "<i>", - // "</@param>", "</i>", - // "<@string>", "<font color=green>", - // "</@string>", "</font>", }; for (int i = 0, n = src.size(); i < n;) { if (src.at(i) == charLangle) { @@ -3459,7 +3153,6 @@ QString HtmlGenerator::highlightedCode(const QString& markedCode, return html; } -#endif void HtmlGenerator::generateLink(const Atom* atom, const Node* /* relative */, diff --git a/tools/qdoc3/htmlgenerator.h b/tools/qdoc3/htmlgenerator.h index aad5021..80341de 100644 --- a/tools/qdoc3/htmlgenerator.h +++ b/tools/qdoc3/htmlgenerator.h @@ -46,8 +46,6 @@ #ifndef HTMLGENERATOR_H #define HTMLGENERATOR_H -#define QDOC_NAME_ALIGNMENT - #include <qmap.h> #include <qregexp.h> #include <QXmlStreamWriter> @@ -198,7 +196,7 @@ class HtmlGenerator : public PageGenerator void generateQmlInstantiates(const QmlClassNode* qcn, CodeMarker* marker); void generateInstantiatedBy(const ClassNode* cn, CodeMarker* marker); #endif -#ifdef QDOC_NAME_ALIGNMENT + void generateSection(const NodeList& nl, const Node *relative, CodeMarker *marker, @@ -207,28 +205,15 @@ class HtmlGenerator : public PageGenerator const Node *relative, CodeMarker *marker, CodeMarker::SynopsisStyle style, - bool nameAlignment = false); - void generateSectionInheritedList(const Section& section, - const Node *relative, - CodeMarker *marker, - bool nameAlignment = false); - QString highlightedCode(const QString& markedCode, - CodeMarker *marker, - const Node *relative, - CodeMarker::SynopsisStyle style = CodeMarker::Accessors, - bool nameAlignment = false); -#else - void generateSynopsis(const Node *node, - const Node *relative, - CodeMarker *marker, - CodeMarker::SynopsisStyle style); + bool alignNames = false); void generateSectionInheritedList(const Section& section, const Node *relative, CodeMarker *marker); QString highlightedCode(const QString& markedCode, CodeMarker *marker, - const Node *relative); -#endif + const Node *relative, + bool alignNames = false); + void generateFullName(const Node *apparentNode, const Node *relative, CodeMarker *marker, diff --git a/tools/qdoc3/tree.cpp b/tools/qdoc3/tree.cpp index d3de46c..70b998f 100644 --- a/tools/qdoc3/tree.cpp +++ b/tools/qdoc3/tree.cpp @@ -134,15 +134,16 @@ Node *Tree::findNode(const QStringList &path, Node *relative, int findFlags) /*! */ -const Node *Tree::findNode(const QStringList &path, - const Node *relative, +const Node* Tree::findNode(const QStringList &path, + const Node* start, int findFlags) const { - if (!relative) - relative = root(); + const Node* current = start; + if (!current) + current = root(); do { - const Node *node = relative; + const Node *node = current; int i; for (i = 0; i < path.size(); ++i) { @@ -171,10 +172,10 @@ const Node *Tree::findNode(const QStringList &path, if (node && i == path.size() && (!(findFlags & NonFunction) || node->type() != Node::Function || ((FunctionNode *)node)->metaness() == FunctionNode::MacroWithoutParams)) - if (node->subType() != Node::QmlPropertyGroup) + if ((node != start) && (node->subType() != Node::QmlPropertyGroup)) return node; - relative = relative->parent(); - } while (relative); + current = current->parent(); + } while (current); return 0; } diff --git a/tools/runonphone/main.cpp b/tools/runonphone/main.cpp index 885d029..7767e4b 100644 --- a/tools/runonphone/main.cpp +++ b/tools/runonphone/main.cpp @@ -62,6 +62,8 @@ void printUsage(QTextStream& outstream, QString exeName) << "-v, --verbose show debugging output" << endl << "-q, --quiet hide progress messages" << endl << "-d, --download <remote file> <local file> copy file from phone to PC after running test" << endl + << "--nocrashlog Don't capture call stack if test crashes" << endl + << "--crashlogpath <dir> Path to save crash logs (default=working dir)" << endl << endl << "USB COM ports can usually be autodetected, use -p or -f to force a specific port." << endl << "If using System TRK, it is possible to copy the program directly to sys/bin on the phone." << endl @@ -85,6 +87,8 @@ int main(int argc, char *argv[]) QString downloadLocalFile; int loglevel=1; int timeout=0; + bool crashlog = true; + QString crashlogpath; QListIterator<QString> it(args); it.next(); //skip name of program while (it.hasNext()) { @@ -126,6 +130,12 @@ int main(int argc, char *argv[]) loglevel=2; else if (arg == "--quiet" || arg == "-q") loglevel=0; + else if (arg == "--nocrashlog") + crashlog = false; + else if (arg == "--crashlogpath") { + CHECK_PARAMETER_EXISTS + crashlogpath = it.next(); + } else errstream << "unknown command line option " << arg << endl; } else { @@ -145,7 +155,7 @@ int main(int argc, char *argv[]) if (serialPortName.isEmpty()) { if (loglevel > 0) outstream << "Detecting serial ports" << endl; - foreach (const SerialPortId &id, enumerateSerialPorts()) { + foreach (const SerialPortId &id, enumerateSerialPorts(loglevel)) { if (loglevel > 0) outstream << "Port Name: " << id.portName << ", " << "Friendly Name:" << id.friendlyName << endl; @@ -199,6 +209,8 @@ int main(int argc, char *argv[]) TrkSignalHandler handler; handler.setLogLevel(loglevel); + handler.setCrashLogging(crashlog); + handler.setCrashLogPath(crashlogpath); QObject::connect(launcher.data(), SIGNAL(copyingStarted()), &handler, SLOT(copyingStarted())); QObject::connect(launcher.data(), SIGNAL(canNotConnect(const QString &)), &handler, SLOT(canNotConnect(const QString &))); @@ -215,8 +227,12 @@ int main(int argc, char *argv[]) QObject::connect(launcher.data(), SIGNAL(copyProgress(int)), &handler, SLOT(copyProgress(int))); QObject::connect(launcher.data(), SIGNAL(stateChanged(int)), &handler, SLOT(stateChanged(int))); QObject::connect(launcher.data(), SIGNAL(processStopped(uint,uint,uint,QString)), &handler, SLOT(stopped(uint,uint,uint,QString))); + QObject::connect(launcher.data(), SIGNAL(libraryLoaded(trk::Library)), &handler, SLOT(libraryLoaded(trk::Library))); + QObject::connect(launcher.data(), SIGNAL(libraryUnloaded(trk::Library)), &handler, SLOT(libraryUnloaded(trk::Library))); + QObject::connect(launcher.data(), SIGNAL(registersAndCallStackReadComplete(const QList<uint> &,const QByteArray &)), &handler, SLOT(registersAndCallStackReadComplete(const QList<uint> &,const QByteArray &))); QObject::connect(&handler, SIGNAL(resume(uint,uint)), launcher.data(), SLOT(resumeProcess(uint,uint))); QObject::connect(&handler, SIGNAL(terminate()), launcher.data(), SLOT(terminate())); + QObject::connect(&handler, SIGNAL(getRegistersAndCallStack(uint,uint)), launcher.data(), SLOT(getRegistersAndCallStack(uint,uint))); QObject::connect(launcher.data(), SIGNAL(finished()), &handler, SLOT(finished())); QTimer timer; diff --git a/tools/runonphone/serenum.h b/tools/runonphone/serenum.h index b794b97..a65c8cb 100644 --- a/tools/runonphone/serenum.h +++ b/tools/runonphone/serenum.h @@ -51,6 +51,6 @@ struct SerialPortId QString friendlyName; }; -QList<SerialPortId> enumerateSerialPorts(); +QList<SerialPortId> enumerateSerialPorts(int loglevel); #endif // WIN32SERENUM_H diff --git a/tools/runonphone/serenum_unix.cpp b/tools/runonphone/serenum_unix.cpp index b6f0293..db6375e 100644 --- a/tools/runonphone/serenum_unix.cpp +++ b/tools/runonphone/serenum_unix.cpp @@ -48,9 +48,32 @@ #include <usb.h> -QList<SerialPortId> enumerateSerialPorts() +class InterfaceInfo { - QList<QString> eligableInterfaces; +public: + InterfaceInfo(const QString &mf, const QString &pr, int mfid, int prid); + QString manufacturer; + QString product; + int manufacturerid; + int productid; +}; + +InterfaceInfo::InterfaceInfo(const QString &mf, const QString &pr, int mfid, int prid) : + manufacturer(mf), + product(pr), + manufacturerid(mfid), + productid(prid) +{ + if(mf.isEmpty()) + manufacturer = QString("[%1]").arg(mfid, 4, 16, QChar('0')); + if(pr.isEmpty()) + product = QString("[%1]").arg(prid, 4, 16, QChar('0')); +} + +QList<SerialPortId> enumerateSerialPorts(int loglevel) +{ + QList<QString> eligibleInterfaces; + QList<InterfaceInfo> eligibleInterfacesInfo; QList<SerialPortId> list; usb_init(); @@ -85,6 +108,41 @@ QList<SerialPortId> enumerateSerialPorts() } } } + + if (usableInterfaces.isEmpty()) + continue; + + QString manufacturerString; + QString productString; + + usb_dev_handle *devh = usb_open(device); + if (devh) { + QByteArray buf; + buf.resize(256); + int err = usb_get_string_simple(devh, device->descriptor.iManufacturer, buf.data(), buf.size()); + if (err < 0) { + if (loglevel > 1) + qDebug() << " can't read manufacturer name, error:" << err; + } else { + manufacturerString = QString::fromAscii(buf); + if (loglevel > 1) + qDebug() << " manufacturer:" << manufacturerString; + } + + buf.resize(256); + err = usb_get_string_simple(devh, device->descriptor.iProduct, buf.data(), buf.size()); + if (err < 0) { + if (loglevel > 1) + qDebug() << " can't read product name, error:" << err; + } else { + productString = QString::fromAscii(buf); + if (loglevel > 1) + qDebug() << " product:" << productString; + } + usb_close(devh); + } else if (loglevel > 0) { + qDebug() << " can't open usb device"; + } // second loop to find the actual data interface. foreach (int i, usableInterfaces) { @@ -94,11 +152,22 @@ QList<SerialPortId> enumerateSerialPorts() if (descriptor.bInterfaceNumber != i) continue; if (descriptor.bInterfaceClass == 10) { // "CDC Data" - // qDebug() << " found the data port" - // << "bus:" << bus->dirname - // << "device" << device->filename - // << "interface" << descriptor.bInterfaceNumber; - eligableInterfaces << QString("if%1").arg(QString::number(i), 2, QChar('0')); // fix! + if (loglevel > 1) { + qDebug() << " found the data port" + << "bus:" << bus->dirname + << "device" << device->filename + << "interface" << descriptor.bInterfaceNumber; + } + // ### manufacturer and product strings are only readable as root :( + if (!manufacturerString.isEmpty() && !productString.isEmpty()) { + eligibleInterfaces << QString("usb-%1_%2-if%3") + .arg(manufacturerString.replace(QChar(' '), QChar('_'))) + .arg(productString.replace(QChar(' '), QChar('_'))) + .arg(i, 2, 16, QChar('0')); + } else { + eligibleInterfaces << QString("if%1").arg(i, 2, 16, QChar('0')); // fix! + } + eligibleInterfacesInfo << InterfaceInfo(manufacturerString, productString, device->descriptor.idVendor, device->descriptor.idProduct); } } } @@ -106,13 +175,18 @@ QList<SerialPortId> enumerateSerialPorts() } } } + + if (loglevel > 1) + qDebug() << " searching for interfaces:" << eligibleInterfaces; QDir dir("/dev/serial/by-id/"); foreach (const QFileInfo &info, dir.entryInfoList()) { if (!info.isDir()) { - bool usable = eligableInterfaces.isEmpty(); - foreach (const QString &iface, eligableInterfaces) { + bool usable = eligibleInterfaces.isEmpty(); + foreach (const QString &iface, eligibleInterfaces) { if (info.fileName().contains(iface)) { + if (loglevel > 1) + qDebug() << " found device file:" << info.fileName() << endl; usable = true; break; } @@ -126,6 +200,21 @@ QList<SerialPortId> enumerateSerialPorts() list << id; } } + + if (list.isEmpty() && !eligibleInterfacesInfo.isEmpty() && loglevel > 0) { + qDebug() << "Possible USB devices found, but without serial drivers:"; + foreach(const InterfaceInfo &iface, eligibleInterfacesInfo) { + qDebug() << " Manufacturer:" + << iface.manufacturer + << "Product:" + << iface.product + << endl + << " Load generic driver using:" + << QString("sudo modprobe usbserial vendor=0x%1 product=0x%2") + .arg(iface.manufacturerid, 4, 16, QChar('0')) + .arg(iface.productid, 4, 16, QChar('0')); + } + } return list; } diff --git a/tools/runonphone/serenum_win.cpp b/tools/runonphone/serenum_win.cpp index 572161c..070cac2 100644 --- a/tools/runonphone/serenum_win.cpp +++ b/tools/runonphone/serenum_win.cpp @@ -53,7 +53,7 @@ //{4d36e978-e325-11ce-bfc1-08002be10318} //DEFINE_GUID(GUID_DEVCLASS_PORTS, 0x4D36E978, 0xE325, 0x11CE, 0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18 ); -QList<SerialPortId> enumerateSerialPorts() +QList<SerialPortId> enumerateSerialPorts(int) { DWORD index=0; SP_DEVINFO_DATA info; diff --git a/tools/runonphone/symbianutils/json.cpp b/tools/runonphone/symbianutils/json.cpp new file mode 100644 index 0000000..2d58824 --- /dev/null +++ b/tools/runonphone/symbianutils/json.cpp @@ -0,0 +1,490 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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/tools/runonphone/symbianutils/json.h b/tools/runonphone/symbianutils/json.h new file mode 100644 index 0000000..ffcb7ab --- /dev/null +++ b/tools/runonphone/symbianutils/json.h @@ -0,0 +1,149 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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/tools/runonphone/symbianutils/launcher.cpp b/tools/runonphone/symbianutils/launcher.cpp index ecb067e..ee87480 100644 --- a/tools/runonphone/symbianutils/launcher.cpp +++ b/tools/runonphone/symbianutils/launcher.cpp @@ -58,6 +58,33 @@ namespace trk { +struct CrashReportState { + CrashReportState(); + void clear(); + + typedef uint Thread; + typedef QList<Thread> Threads; + Threads threads; + + QList<uint> registers; + QByteArray stack; + uint sp; + uint fetchingStackPID; + uint fetchingStackTID; +}; + +CrashReportState::CrashReportState() +{ + clear(); +} + +void CrashReportState::clear() +{ + threads.clear(); + stack.clear(); + sp = fetchingStackPID = fetchingStackTID = 0; +} + struct LauncherPrivate { struct CopyState { QString sourceFileName; @@ -86,6 +113,7 @@ struct LauncherPrivate { int m_verbose; Launcher::Actions m_startupActions; bool m_closeDevice; + CrashReportState m_crashReportState; }; LauncherPrivate::LauncherPrivate(const TrkDevicePtr &d) : @@ -202,6 +230,7 @@ void Launcher::setCloseDevice(bool c) bool Launcher::startServer(QString *errorMessage) { errorMessage->clear(); + d->m_crashReportState.clear(); if (d->m_verbose) { QString msg; QTextStream str(&msg); @@ -375,7 +404,7 @@ void Launcher::handleResult(const TrkResult &result) logMessage("TEXT TRACE: " + msg); } } else { - logMessage("APPLICATION OUTPUT: " + result.data); + logMessage("APPLICATION OUTPUT: " + stringFromArray(result.data)); msg = result.data; } msg.replace("\r\n", "\n"); @@ -416,49 +445,54 @@ void Launcher::handleResult(const TrkResult &result) // target->host OS notification case TrkNotifyCreated: { // Notify Created - /* - const char *data = result.data.data(); - byte error = result.data.at(0); - byte type = result.data.at(1); // type: 1 byte; for dll item, this value is 2. - uint pid = extractInt(data + 2); // ProcessID: 4 bytes; - uint tid = extractInt(data + 6); //threadID: 4 bytes - uint codeseg = extractInt(data + 10); //code address: 4 bytes; code base address for the library - uint dataseg = extractInt(data + 14); //data address: 4 bytes; data base address for the library - uint len = extractShort(data + 18); //length: 2 bytes; length of the library name string to follow - QByteArray name = result.data.mid(20, len); // name: library name - - logMessage(prefix + "NOTE: LIBRARY LOAD: " + str); - logMessage(prefix + "TOKEN: " + result.token); - logMessage(prefix + "ERROR: " + int(error)); - logMessage(prefix + "TYPE: " + int(type)); - logMessage(prefix + "PID: " + pid); - logMessage(prefix + "TID: " + tid); - logMessage(prefix + "CODE: " + codeseg); - logMessage(prefix + "DATA: " + dataseg); - logMessage(prefix + "LEN: " + len); - logMessage(prefix + "NAME: " + name); - */ if (result.data.size() < 10) break; + const char *data = result.data.constData(); + const byte error = result.data.at(0); + Q_UNUSED(error) + const byte type = result.data.at(1); // type: 1 byte; for dll item, this value is 2. + const uint tid = extractInt(data + 6); //threadID: 4 bytes + Q_UNUSED(tid) + if (type == kDSOSDLLItem && result.data.size() >=20) { + const Library lib = Library(result); + d->m_session.libraries.push_back(lib); + emit libraryLoaded(lib); + } QByteArray ba; ba.append(result.data.mid(2, 8)); d->m_device->sendTrkMessage(TrkContinue, TrkCallback(), ba, "CONTINUE"); - //d->m_device->sendTrkAck(result.token) break; } case TrkNotifyDeleted: { // NotifyDeleted const ushort itemType = (unsigned char)result.data.at(1); - const ushort len = result.data.size() > 12 ? extractShort(result.data.data() + 10) : ushort(0); + const uint pid = result.data.size() >= 6 ? extractShort(result.data.constData() + 2) : 0; + const uint tid = result.data.size() >= 10 ? extractShort(result.data.constData() + 6) : 0; + Q_UNUSED(tid) + const ushort len = result.data.size() > 12 ? extractShort(result.data.constData() + 10) : ushort(0); const QString name = len ? QString::fromAscii(result.data.mid(12, len)) : QString(); logMessage(QString::fromLatin1("%1 %2 UNLOAD: %3"). arg(QString::fromAscii(prefix)).arg(itemType ? QLatin1String("LIB") : QLatin1String("PROCESS")). arg(name)); d->m_device->sendTrkAck(result.token); - if (itemType == 0 // process + if (itemType == kDSOSProcessItem // process && result.data.size() >= 10 && d->m_session.pid == extractInt(result.data.data() + 6)) { - copyFileFromRemote(); + if (d->m_startupActions & ActionDownload) + copyFileFromRemote(); + else + disconnectTrk(); + } + else if (itemType == kDSOSDLLItem && len) { + // Remove libraries of process. + for (QList<Library>::iterator it = d->m_session.libraries.begin(); it != d->m_session.libraries.end(); ) { + if ((*it).pid == pid && (*it).name == name) { + emit libraryUnloaded(*it); + it = d->m_session.libraries.erase(it); + } else { + ++it; + } + } } break; } @@ -705,6 +739,15 @@ void Launcher::handleCreateProcess(const TrkResult &result) logMessage(msg); } emit applicationRunning(d->m_session.pid); + //create a "library" entry for the executable which launched the process + Library lib; + lib.pid = d->m_session.pid; + lib.codeseg = d->m_session.codeseg; + lib.dataseg = d->m_session.dataseg; + lib.name = d->m_fileName.toUtf8(); + d->m_session.libraries << lib; + emit libraryLoaded(lib); + QByteArray ba; appendInt(&ba, d->m_session.pid); appendInt(&ba, d->m_session.tid); @@ -856,6 +899,30 @@ QByteArray Launcher::startProcessMessage(const QString &executable, return ba; } +QByteArray Launcher::readMemoryMessage(uint pid, uint tid, uint from, uint len) +{ + QByteArray ba; + ba.reserve(11); + ba.append(char(0x8)); // Options, FIXME: why? + appendShort(&ba, len); + appendInt(&ba, from); + appendInt(&ba, pid); + appendInt(&ba, tid); + return ba; +} + +QByteArray Launcher::readRegistersMessage(uint pid, uint tid) +{ + QByteArray ba; + ba.reserve(15); + ba.append(char(0)); // Register set, only 0 supported + appendShort(&ba, 0); //R0 + appendShort(&ba, 16); // last register CPSR + appendInt(&ba, pid); + appendInt(&ba, tid); + return ba; +} + void Launcher::startInferiorIfNeeded() { emit startingApplication(); @@ -907,4 +974,63 @@ void Launcher::releaseToDeviceManager(Launcher *launcher) sdm->releaseDevice(launcher->trkServerName()); } +void Launcher::getRegistersAndCallStack(uint pid, uint tid) +{ + d->m_device->sendTrkMessage(TrkReadRegisters, + TrkCallback(this, &Launcher::handleReadRegisters), + Launcher::readRegistersMessage(pid, tid)); + d->m_crashReportState.fetchingStackPID = pid; + d->m_crashReportState.fetchingStackTID = tid; +} + +void Launcher::handleReadRegisters(const TrkResult &result) +{ + if(result.errorCode() || result.data.size() < (17*4)) { + terminate(); + return; + } + const char* data = result.data.constData() + 1; + d->m_crashReportState.registers.clear(); + d->m_crashReportState.stack.clear(); + for (int i=0;i<17;i++) { + uint r = extractInt(data); + data += 4; + d->m_crashReportState.registers.append(r); + } + d->m_crashReportState.sp = d->m_crashReportState.registers.at(13); + + const ushort len = 1024 - (d->m_crashReportState.sp % 1024); //read to 1k boundary first + const QByteArray ba = Launcher::readMemoryMessage(d->m_crashReportState.fetchingStackPID, + d->m_crashReportState.fetchingStackTID, + d->m_crashReportState.sp, + len); + d->m_device->sendTrkMessage(TrkReadMemory, TrkCallback(this, &Launcher::handleReadStack), ba); + d->m_crashReportState.sp += len; +} + +void Launcher::handleReadStack(const TrkResult &result) +{ + if (result.errorCode()) { + //error implies memory fault when reaching end of stack + emit registersAndCallStackReadComplete(d->m_crashReportState.registers, d->m_crashReportState.stack); + return; + } + + const uint len = extractShort(result.data.constData() + 1); + d->m_crashReportState.stack.append(result.data.mid(3, len)); + + if (d->m_crashReportState.sp - d->m_crashReportState.registers.at(13) > 0x10000) { + //read enough stack, stop here + emit registersAndCallStackReadComplete(d->m_crashReportState.registers, d->m_crashReportState.stack); + return; + } + //read 1k more + const QByteArray ba = Launcher::readMemoryMessage(d->m_crashReportState.fetchingStackPID, + d->m_crashReportState.fetchingStackTID, + d->m_crashReportState.sp, + 1024); + d->m_device->sendTrkMessage(TrkReadMemory, TrkCallback(this, &Launcher::handleReadStack), ba); + d->m_crashReportState.sp += 1024; +} + } // namespace trk diff --git a/tools/runonphone/symbianutils/launcher.h b/tools/runonphone/symbianutils/launcher.h index 6db69d0..230a122 100644 --- a/tools/runonphone/symbianutils/launcher.h +++ b/tools/runonphone/symbianutils/launcher.h @@ -43,6 +43,7 @@ #define LAUNCHER_H #include "trkdevice.h" +#include "trkutils.h" #include <QtCore/QObject> #include <QtCore/QVariant> @@ -122,6 +123,9 @@ public: // Create Trk message to start a process. static QByteArray startProcessMessage(const QString &executable, const QStringList &arguments); + // Create Trk message to read memory + static QByteArray readMemoryMessage(uint pid, uint tid, uint from, uint len); + static QByteArray readRegistersMessage(uint pid, uint tid); // Parse a TrkNotifyStopped message static bool parseNotifyStopped(const QByteArray &a, uint *pid, uint *tid, uint *address, @@ -149,12 +153,18 @@ signals: void copyProgress(int percent); void stateChanged(int); void processStopped(uint pc, uint pid, uint tid, const QString& reason); + void processResumed(uint pid, uint tid); + void libraryLoaded(const trk::Library &lib); + void libraryUnloaded(const trk::Library &lib); + void registersAndCallStackReadComplete(const QList<uint>& registers, const QByteArray& stack); // Emitted by the destructor, for releasing devices of SymbianDeviceManager by name void destroyed(const QString &serverName); public slots: void terminate(); void resumeProcess(uint pid, uint tid); + //can be used to obtain traceback after a breakpoint / exception + void getRegistersAndCallStack(uint pid, uint tid); private slots: void handleResult(const trk::TrkResult &data); @@ -182,6 +192,8 @@ private: void handleStop(const TrkResult &result); void handleSupportMask(const TrkResult &result); void handleTrkVersion(const TrkResult &result); + void handleReadRegisters(const TrkResult &result); + void handleReadStack(const TrkResult &result); void copyFileToRemote(); void copyFileFromRemote(); diff --git a/tools/runonphone/symbianutils/symbianutils.pri b/tools/runonphone/symbianutils/symbianutils.pri index 6309517..f07e494 100644 --- a/tools/runonphone/symbianutils/symbianutils.pri +++ b/tools/runonphone/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/tools/runonphone/symbianutils/tcftrkdevice.cpp b/tools/runonphone/symbianutils/tcftrkdevice.cpp new file mode 100644 index 0000000..50e3937 --- /dev/null +++ b/tools/runonphone/symbianutils/tcftrkdevice.cpp @@ -0,0 +1,929 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "tcftrkdevice.h" +#include "json.h" + +#include <QtNetwork/QAbstractSocket> +#include <QtCore/QDebug> +#include <QtCore/QVector> +#include <QtCore/QQueue> +#include <QtCore/QTextStream> +#include <QtCore/QDateTime> +#include <QtCore/QFileInfo> + +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::sendRunControlTerminateCommand(const TcfTrkCallback &callBack, + const QByteArray &id, + const QVariant &cookie) +{ + QByteArray data; + JsonInputStream str(data); + str << id; + sendTcfTrkMessage(MessageWithReply, RunControlService, "terminate", data, callBack, cookie); +} + +// Non-standard: Remove executable from settings +void TcfTrkDevice::sendSettingsRemoveExecutableCommand(const QString &binaryIn, + unsigned uid, + const QStringList &additionalLibraries, + const QVariant &cookie) +{ + QByteArray setData; + JsonInputStream setStr(setData); + setStr << "" << '\0' + << '[' << "removedExecutables" << ',' << "removedLibraries" << ']' + << '\0' << '[' + << '{' << QFileInfo(binaryIn).fileName() << ':' << QString::number(uid, 16) << '}' << ',' + << additionalLibraries + << ']'; + sendTcfTrkMessage(MessageWithoutReply, SettingsService, "set", setData, TcfTrkCallback(), 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/tools/runonphone/symbianutils/tcftrkdevice.h b/tools/runonphone/symbianutils/tcftrkdevice.h new file mode 100644 index 0000000..67955e5 --- /dev/null +++ b/tools/runonphone/symbianutils/tcftrkdevice.h @@ -0,0 +1,295 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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()); + + // Preferred over Processes:Terminate by TCF TRK. + void sendRunControlTerminateCommand(const TcfTrkCallback &callBack, + const QByteArray &id, + const QVariant &cookie = QVariant()); + + void sendProcessTerminateCommand(const TcfTrkCallback &callBack, + const QByteArray &id, + const QVariant &cookie = QVariant()); + + // Non-standard: Remove executable from settings. + // Probably needs to be called after stopping. This command has no response. + void sendSettingsRemoveExecutableCommand(const QString &binaryIn, + unsigned uid, + const QStringList &additionalLibraries = QStringList(), + 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/tools/runonphone/symbianutils/tcftrkmessage.cpp b/tools/runonphone/symbianutils/tcftrkmessage.cpp new file mode 100644 index 0000000..06035ab --- /dev/null +++ b/tools/runonphone/symbianutils/tcftrkmessage.cpp @@ -0,0 +1,562 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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/tools/runonphone/symbianutils/tcftrkmessage.h b/tools/runonphone/symbianutils/tcftrkmessage.h new file mode 100644 index 0000000..510b485 --- /dev/null +++ b/tools/runonphone/symbianutils/tcftrkmessage.h @@ -0,0 +1,296 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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/tools/runonphone/symbianutils/trkdevice.cpp b/tools/runonphone/symbianutils/trkdevice.cpp index bd24300..9039184 100644 --- a/tools/runonphone/symbianutils/trkdevice.cpp +++ b/tools/runonphone/symbianutils/trkdevice.cpp @@ -636,10 +636,11 @@ private: void readMessages(); QByteArray m_trkReadBuffer; + bool linkEstablishmentMode; }; ReaderThreadBase::ReaderThreadBase(const QSharedPointer<DeviceContext> &context) : - m_context(context) + m_context(context), linkEstablishmentMode(true) { static const int trkResultMetaId = qRegisterMetaType<trk::TrkResult>(); Q_UNUSED(trkResultMetaId) @@ -662,7 +663,7 @@ void ReaderThreadBase::readMessages() { TrkResult r; QByteArray rawData; - while (extractResult(&m_trkReadBuffer, m_context->serialFrame, &r, &rawData)) { + while (extractResult(&m_trkReadBuffer, m_context->serialFrame, &r, linkEstablishmentMode, &rawData)) { emit messageReceived(r, rawData); } } diff --git a/tools/runonphone/symbianutils/trkutils.cpp b/tools/runonphone/symbianutils/trkutils.cpp index 60e391e..d89da20 100644 --- a/tools/runonphone/symbianutils/trkutils.cpp +++ b/tools/runonphone/symbianutils/trkutils.cpp @@ -52,6 +52,25 @@ namespace trk { +Library::Library() : codeseg(0), dataseg(0), pid(0) +{ +} + +Library::Library(const TrkResult &result) : codeseg(0), dataseg(0), pid(0) +{ + if (result.data.size() < 20) { + qWarning("Invalid trk creation notification received."); + return; + } + + const char *data = result.data.constData(); + pid = extractInt(data + 2); + codeseg = extractInt(data + 10); + dataseg = extractInt(data + 14); + const uint len = extractShort(data + 18); + name = result.data.mid(20, len); +} + TrkAppVersion::TrkAppVersion() { reset(); @@ -77,11 +96,11 @@ void Session::reset() extended1TypeSize = 0; extended2TypeSize = 0; pid = 0; + mainTid = 0; tid = 0; codeseg = 0; dataseg = 0; - currentThread = 0; libraries.clear(); trkAppVersion.reset(); } @@ -143,6 +162,90 @@ QString Session::deviceDescription(unsigned verbose) const return msg.arg(formatTrkVersion(trkAppVersion)); } +QByteArray Session::gdbLibraryList() const +{ + const int count = libraries.size(); + QByteArray response = "l<library-list>"; + for (int i = 0; i != count; ++i) { + const trk::Library &lib = libraries.at(i); + response += "<library name=\""; + response += lib.name; + response += "\">"; + response += "<section address=\"0x"; + response += trk::hexNumber(lib.codeseg); + response += "\"/>"; + response += "<section address=\"0x"; + response += trk::hexNumber(lib.dataseg); + response += "\"/>"; + response += "<section address=\"0x"; + response += trk::hexNumber(lib.dataseg); + response += "\"/>"; + response += "</library>"; + } + response += "</library-list>"; + return response; +} + +QByteArray Session::gdbQsDllInfo(int start, int count) const +{ + // 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] + const int libraryCount = libraries.size(); + const int end = count < 0 ? libraryCount : qMin(libraryCount, start + count); + QByteArray response(1, end == libraryCount ? 'l' : 'm'); + for (int i = start; i < end; ++i) { + if (i != start) + response += ';'; + const Library &lib = libraries.at(i); + response += "Name="; + response += lib.name.toHex(); + response += ",TextSeg="; + response += hexNumber(lib.codeseg); + response += ",DataSeg="; + response += hexNumber(lib.dataseg); + } + return response; +} + +QString Session::toString() const +{ + QString rc; + QTextStream str(&rc); + str << "Session: " << deviceDescription(false) << '\n' + << "pid: " << pid << "main thread: " << mainTid + << " current 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); + 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) @@ -188,18 +291,15 @@ SYMBIANUTILS_EXPORT QString stringFromArray(const QByteArray &ba, int maxLen) QString ascii; const int size = maxLen == -1 ? ba.size() : qMin(ba.size(), maxLen); for (int i = 0; i < size; ++i) { - //if (i == 5 || i == ba.size() - 2) - // str += " "; - int c = byte(ba.at(i)); - str += QString("%1 ").arg(c, 2, 16, QChar('0')); - if (i >= 8 && i < ba.size() - 2) - ascii += QChar(c).isPrint() ? QChar(c) : QChar('.'); + const int c = byte(ba.at(i)); + str += QString::fromAscii("%1 ").arg(c, 2, 16, QChar('0')); + ascii += QChar(c).isPrint() ? QChar(c) : QChar('.'); } if (size != ba.size()) { - str += "..."; - ascii += "..."; + str += QLatin1String("..."); + ascii += QLatin1String("..."); } - return str + " " + ascii; + return str + QLatin1String(" ") + ascii; } SYMBIANUTILS_EXPORT QByteArray hexNumber(uint n, int digits) @@ -299,20 +399,30 @@ ushort isValidTrkResult(const QByteArray &buffer, bool serialFrame, ushort& mux) return firstDelimiterPos != -1 ? firstDelimiterPos : buffer.size(); } -bool extractResult(QByteArray *buffer, bool serialFrame, TrkResult *result, QByteArray *rawData) +bool extractResult(QByteArray *buffer, bool serialFrame, TrkResult *result, bool &linkEstablishmentMode, QByteArray *rawData) { result->clear(); if(rawData) rawData->clear(); - const ushort len = isValidTrkResult(*buffer, serialFrame, result->multiplex); - if (!len) - return false; + ushort len = isValidTrkResult(*buffer, serialFrame, result->multiplex); // handle receiving application output, which is not a regular command const int delimiterPos = serialFrame ? 4 : 0; + if (linkEstablishmentMode) { + //when "hot connecting" a device, we can receive partial frames. + //this code resyncs by discarding data until a TRK frame is found + while (buffer->length() > delimiterPos + && result->multiplex != MuxTextTrace + && !(result->multiplex == MuxTrk && buffer->at(delimiterPos) == 0x7e)) { + buffer->remove(0,1); + len = isValidTrkResult(*buffer, serialFrame, result->multiplex); + } + } + if (!len) + return false; if (buffer->at(delimiterPos) != 0x7e) { result->isDebugOutput = true; result->data = buffer->mid(delimiterPos, len); - *buffer->remove(0, delimiterPos + len); + buffer->remove(0, delimiterPos + len); return true; } // FIXME: what happens if the length contains 0xfe? @@ -320,7 +430,7 @@ bool extractResult(QByteArray *buffer, bool serialFrame, TrkResult *result, QByt const QByteArray data = decode7d(buffer->mid(delimiterPos + 1, len - 2)); if(rawData) *rawData = data; - *buffer->remove(0, delimiterPos + len); + buffer->remove(0, delimiterPos + len); byte sum = 0; for (int i = 0; i < data.size(); ++i) // 3 = 2 * 0xfe + sum @@ -335,6 +445,7 @@ bool extractResult(QByteArray *buffer, bool serialFrame, TrkResult *result, QByt //logMessage(" CURR DATA: " << stringFromArray(data)); //QByteArray prefix = "READ BUF: "; //logMessage((prefix + "HEADER: " + stringFromArray(header).toLatin1()).data()); + linkEstablishmentMode = false; //have received a good TRK packet, therefore in sync return true; } diff --git a/tools/runonphone/symbianutils/trkutils.h b/tools/runonphone/symbianutils/trkutils.h index e571028..d365f0d 100644 --- a/tools/runonphone/symbianutils/trkutils.h +++ b/tools/runonphone/symbianutils/trkutils.h @@ -56,6 +56,7 @@ QT_END_NAMESPACE namespace trk { typedef unsigned char byte; +struct TrkResult; enum Command { //meta commands @@ -135,6 +136,20 @@ enum Command { TrkDSPositionFile = 0xd4 }; +enum DSOSItemTypes { + kDSOSProcessItem = 0x0000, + kDSOSThreadItem = 0x0001, + kDSOSDLLItem = 0x0002, + kDSOSAppItem = 0x0003, + kDSOSMemBlockItem = 0x0004, + kDSOSProcAttachItem = 0x0005, + kDSOSThreadAttachItem = 0x0006, + kDSOSProcAttach2Item = 0x0007, + kDSOSProcRunItem = 0x0008, + /* 0x0009 - 0x00ff reserved for general expansion */ + /* 0x0100 - 0xffff available for target-specific use */ +}; + enum SerialMultiplexor { MuxRaw = 0, MuxTextTrace = 0x0102, @@ -164,11 +179,14 @@ SYMBIANUTILS_EXPORT void appendString(QByteArray *ba, const QByteArray &str, End struct SYMBIANUTILS_EXPORT Library { - Library() {} + Library(); + explicit Library(const TrkResult &r); QByteArray name; uint codeseg; uint dataseg; + //library addresses are valid for a given process (depending on memory model, they might be loaded at the same address in all processes or not) + uint pid; }; struct SYMBIANUTILS_EXPORT TrkAppVersion @@ -187,6 +205,11 @@ struct SYMBIANUTILS_EXPORT Session Session(); void reset(); QString deviceDescription(unsigned verbose) const; + QString toString() const; + // Answer to qXfer::libraries + QByteArray gdbLibraryList() const; + // Answer to qsDllInfo, can be sent chunk-wise. + QByteArray gdbQsDllInfo(int start = 0, int count = -1) const; // Trk feedback byte cpuMajor; @@ -198,6 +221,7 @@ struct SYMBIANUTILS_EXPORT Session byte extended2TypeSize; TrkAppVersion trkAppVersion; uint pid; + uint mainTid; uint tid; uint codeseg; uint dataseg; @@ -206,12 +230,7 @@ struct SYMBIANUTILS_EXPORT Session typedef QList<Library> Libraries; Libraries libraries; - typedef uint Thread; - typedef QList<Thread> Threads; - Threads threads; - // Gdb request - uint currentThread; QStringList modules; }; diff --git a/tools/runonphone/symbianutils/trkutils_p.h b/tools/runonphone/symbianutils/trkutils_p.h index 12b0109..05df83a 100644 --- a/tools/runonphone/symbianutils/trkutils_p.h +++ b/tools/runonphone/symbianutils/trkutils_p.h @@ -55,7 +55,7 @@ void appendDateTime(QByteArray *ba, QDateTime dateTime, Endianness = TargetByteO // returns a QByteArray containing optionally // the serial frame [0x01 0x90 <len>] and 0x7e encoded7d(ba) 0x7e QByteArray frameMessage(byte command, byte token, const QByteArray &data, bool serialFrame); -bool extractResult(QByteArray *buffer, bool serialFrame, TrkResult *r, QByteArray *rawData = 0); +bool extractResult(QByteArray *buffer, bool serialFrame, TrkResult *r, bool& linkEstablishmentMode, QByteArray *rawData = 0); } // namespace trk diff --git a/tools/runonphone/trksignalhandler.cpp b/tools/runonphone/trksignalhandler.cpp index 2abf91f..b6d446f 100644 --- a/tools/runonphone/trksignalhandler.cpp +++ b/tools/runonphone/trksignalhandler.cpp @@ -42,7 +42,19 @@ #include <QDebug> #include <QCoreApplication> #include <QObject> +#include <QFile> +#include <QDir> #include "trksignalhandler.h" +#include "trkutils.h" + +class CrashState +{ +public: + uint pid; + uint tid; + QString crashReason; + uint crashPC; +}; class TrkSignalHandlerPrivate { @@ -55,6 +67,12 @@ private: QTextStream err; int loglevel; int lastpercent; + QList<trk::Library> libraries; + QFile crashlogtextfile; + QFile crashstackfile; + QList<CrashState> queuedCrashes; + QString crashlogPath; + bool crashlog; }; void TrkSignalHandler::copyingStarted() @@ -108,6 +126,7 @@ void TrkSignalHandler::startingApplication() void TrkSignalHandler::applicationRunning(uint pid) { + Q_UNUSED(pid) if (d->loglevel > 0) d->out << "Running..." << endl; } @@ -155,13 +174,153 @@ void TrkSignalHandler::setLogLevel(int level) d->loglevel = level; } +void TrkSignalHandler::setCrashLogging(bool enabled) +{ + d->crashlog = enabled; +} + +void TrkSignalHandler::setCrashLogPath(QString path) +{ + d->crashlogPath = path; +} + +bool lessThanCodeBase(const trk::Library& cs1, const trk::Library& cs2) +{ + return cs1.codeseg < cs2.codeseg; +} + void TrkSignalHandler::stopped(uint pc, uint pid, uint tid, const QString& reason) { d->err << "STOPPED: pc=" << hex << pc << " pid=" << pid << " tid=" << tid << dec << " - " << reason << endl; - // if it was a breakpoint, then we could continue with "emit resume(pid, tid);" - // since we have set no breakpoints, it will be a just in time debug of a panic / exception - emit terminate(); + + if (d->crashlog) { + CrashState cs; + cs.pid = pid; + cs.tid = tid; + cs.crashPC = pc; + cs.crashReason = reason; + + d->queuedCrashes.append(cs); + + if (d->queuedCrashes.count() == 1) { + d->err << "Fetching registers and stack..." << endl; + emit getRegistersAndCallStack(pid, tid); + } + } + else + emit resume(pid, tid); +} + +void TrkSignalHandler::registersAndCallStackReadComplete(const QList<uint>& registers, const QByteArray& stack) +{ + CrashState cs = d->queuedCrashes.first(); + QDir dir(d->crashlogPath); + d->crashlogtextfile.setFileName(dir.filePath(QString("d_exc_%1.txt").arg(cs.tid))); + d->crashstackfile.setFileName(dir.filePath(QString("d_exc_%1.stk").arg(cs.tid))); + d->crashlogtextfile.open(QIODevice::WriteOnly); + QTextStream crashlog(&d->crashlogtextfile); + + crashlog << "-----------------------------------------------------------------------------" << endl; + crashlog << "EKA2 USER CRASH LOG" << endl; + crashlog << "Thread Name: " << QString("ProcessID-%1::ThreadID-%2").arg(cs.pid).arg(cs.tid) << endl; + crashlog << "Thread ID: " << cs.tid << endl; + //this is wrong, but TRK doesn't make stack limit available so we lie + crashlog << QString("User Stack %1-%2").arg(registers.at(13), 8, 16, QChar('0')).arg(registers.at(13) + stack.size(), 8, 16, QChar('0')) << endl; + //this is also wrong, but TRK doesn't give all information for exceptions + crashlog << QString("Panic: PC=%1 ").arg(cs.crashPC, 8, 16, QChar('0')) << cs.crashReason << endl; + crashlog << endl; + crashlog << "USER REGISTERS:" << endl; + crashlog << QString("CPSR=%1").arg(registers.at(16), 8, 16, QChar('0')) << endl; + for (int i=0;i<16;i+=4) { + crashlog << QString("r%1=%2 %3 %4 %5") + .arg(i, 2, 10, QChar('0')) + .arg(registers.at(i), 8, 16, QChar('0')) + .arg(registers.at(i+1), 8, 16, QChar('0')) + .arg(registers.at(i+2), 8, 16, QChar('0')) + .arg(registers.at(i+3), 8, 16, QChar('0')) << endl; + } + crashlog << endl; + + //emit info for post mortem debug + qSort(d->libraries.begin(), d->libraries.end(), lessThanCodeBase); + d->err << "Code Segments:" << endl; + crashlog << "CODE SEGMENTS:" << endl; + for(int i=0; i<d->libraries.count(); i++) { + const trk::Library& seg = d->libraries.at(i); + if(seg.pid != cs.pid) + continue; + if (d->loglevel > 1) { + d->err << QString("Code: %1 Data: %2 Name: ") + .arg(seg.codeseg, 8, 16, QChar('0')) + .arg(seg.dataseg, 8, 16, QChar('0')) + << seg.name << endl; + } + + //produce fake code segment end addresses since we don't get the real ones from TRK + uint end; + if (i+1 < d->libraries.count()) + end = d->libraries.at(i+1).codeseg - 1; + else + end = 0xFFFFFFFF; + + crashlog << QString("%1-%2 ") + .arg(seg.codeseg, 8, 16, QChar('0')) + .arg(end, 8, 16, QChar('0')) + << seg.name << endl; + } + + d->crashlogtextfile.close(); + + if (d->loglevel > 1) { + d->err << "Registers:" << endl; + for (int i=0;i<16;i++) { + d->err << QString("R%1: %2 ").arg(i, 2, 10, QChar('0')).arg(registers.at(i), 8, 16, QChar('0')); + if (i % 4 == 3) + d->err << endl; + } + d->err << QString("CPSR: %1").arg(registers.at(16), 8, 16, QChar('0')) << endl; + + d->err << "Stack:" << endl; + uint sp = registers.at(13); + for(int i=0; i<stack.size(); i+=16, sp+=16) { + d->err << QString("%1: ").arg(sp, 8, 16, QChar('0')); + d->err << trk::stringFromArray(stack.mid(i,16)); + d->err << endl; + } + } + d->crashstackfile.open(QIODevice::WriteOnly); + d->crashstackfile.write(stack); + d->crashstackfile.close(); + + if (d->loglevel > 0) + d->err << "Crash logs saved to " << d->crashlogtextfile.fileName() << " & " << d->crashstackfile.fileName() << endl; + + // resume the thread to allow Symbian OS to handle the panic normally. + // terminate when a non main thread is suspended reboots the phone (TRK bug) + emit resume(cs.pid, cs.tid); + + //fetch next crashed thread + d->queuedCrashes.removeFirst(); + if (d->queuedCrashes.count()) { + cs = d->queuedCrashes.first(); + d->err << "Fetching registers and stack..." << endl; + emit getRegistersAndCallStack(cs.pid, cs.tid); + } + +} + +void TrkSignalHandler::libraryLoaded(const trk::Library &lib) +{ + d->libraries << lib; +} + +void TrkSignalHandler::libraryUnloaded(const trk::Library &lib) +{ + for (QList<trk::Library>::iterator i = d->libraries.begin(); i != d->libraries.end(); i++) { + if((*i).name == lib.name && (*i).pid == lib.pid) + i = d->libraries.erase(i); + } } void TrkSignalHandler::timeout() diff --git a/tools/runonphone/trksignalhandler.h b/tools/runonphone/trksignalhandler.h index d31e46f..bfe2c3e 100644 --- a/tools/runonphone/trksignalhandler.h +++ b/tools/runonphone/trksignalhandler.h @@ -43,6 +43,7 @@ #define TRKSIGNALHANDLER_H #include <QObject> #include <QString> +#include "symbianutils/trkutils.h" class TrkSignalHandlerPrivate; class TrkSignalHandler : public QObject @@ -66,13 +67,20 @@ public slots: void stateChanged(int); void stopped(uint pc, uint pid, uint tid, const QString& reason); void timeout(); + void libraryLoaded(const trk::Library &lib); + void libraryUnloaded(const trk::Library &lib); + void registersAndCallStackReadComplete(const QList<uint>& registers, const QByteArray& stack); signals: void resume(uint pid, uint tid); + void stop(uint pid, uint tid); void terminate(); + void getRegistersAndCallStack(uint pid, uint tid); public: TrkSignalHandler(); ~TrkSignalHandler(); void setLogLevel(int); + void setCrashLogging(bool); + void setCrashLogPath(QString); private: TrkSignalHandlerPrivate *d; }; |