/**************************************************************************** | |
** | |
** Copyright (C) 2011 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$ | |
** GNU Lesser General Public License Usage | |
** 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. | |
** | |
** GNU General Public License Usage | |
** Alternatively, this file may be used under the terms of the GNU General | |
** Public License version 3.0 as published by the Free Software Foundation | |
** and appearing in the file LICENSE.GPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU General | |
** Public License version 3.0 requirements will be met: | |
** http://www.gnu.org/copyleft/gpl.html. | |
** | |
** Other Usage | |
** Alternatively, this file may be used in accordance with the terms and | |
** conditions contained in a signed written agreement between you and Nokia. | |
** | |
** | |
** | |
** | |
** | |
** $QT_END_LICENSE$ | |
** | |
****************************************************************************/ | |
#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 | |
{ | |
friend class TrkSignalHandler; | |
public: | |
TrkSignalHandlerPrivate(); | |
~TrkSignalHandlerPrivate(); | |
private: | |
QTextStream out; | |
QTextStream err; | |
int loglevel; | |
int lastpercent; | |
QList<trk::Library> libraries; | |
QFile crashlogtextfile; | |
QFile crashstackfile; | |
QList<CrashState> queuedCrashes; | |
QList<int> dyingThreads; | |
QString crashlogPath; | |
bool crashlog; | |
bool terminateNeeded; | |
}; | |
void TrkSignalHandler::copyingStarted() | |
{ | |
if (d->loglevel > 0) | |
d->out << "Copying..." << endl; | |
} | |
void TrkSignalHandler::canNotConnect(const QString &errorMessage) | |
{ | |
d->err << "Cannot connect - " << errorMessage << endl; | |
} | |
void TrkSignalHandler::canNotCreateFile(const QString &filename, const QString &errorMessage) | |
{ | |
d->err << "Cannot create file (" << filename << ") - " << errorMessage << endl; | |
} | |
void TrkSignalHandler::canNotWriteFile(const QString &filename, const QString &errorMessage) | |
{ | |
d->err << "Cannot write file (" << filename << ") - " << errorMessage << endl; | |
} | |
void TrkSignalHandler::canNotCloseFile(const QString &filename, const QString &errorMessage) | |
{ | |
d->err << "Cannot close file (" << filename << ") - " << errorMessage << endl; | |
} | |
void TrkSignalHandler::installingStarted() | |
{ | |
if (d->loglevel > 0) | |
d->out << "Installing..." << endl; | |
} | |
void TrkSignalHandler::canNotInstall(const QString &packageFilename, const QString &errorMessage) | |
{ | |
d->err << "Cannot install file (" << packageFilename << ") - " << errorMessage << endl; | |
} | |
void TrkSignalHandler::installingFinished() | |
{ | |
if (d->loglevel > 0) | |
d->out << "Installing finished" << endl; | |
} | |
void TrkSignalHandler::startingApplication() | |
{ | |
if (d->loglevel > 0) | |
d->out << "Starting app..." << endl; | |
} | |
void TrkSignalHandler::applicationRunning(uint pid) | |
{ | |
Q_UNUSED(pid) | |
if (d->loglevel > 0) | |
d->out << "Running..." << endl; | |
} | |
void TrkSignalHandler::canNotRun(const QString &errorMessage) | |
{ | |
d->err << "Cannot run - " << errorMessage << endl; | |
} | |
void TrkSignalHandler::finished() | |
{ | |
if (d->loglevel > 0) | |
d->out << "Done." << endl; | |
QCoreApplication::quit(); | |
} | |
void TrkSignalHandler::applicationOutputReceived(const QString &output) | |
{ | |
d->out << output << flush; | |
} | |
void TrkSignalHandler::copyProgress(int percent) | |
{ | |
if (d->loglevel > 0) { | |
if (d->lastpercent == 0) | |
d->out << "[ ]\r[" << flush; | |
while (percent > d->lastpercent) { | |
d->out << QLatin1Char('#'); | |
d->lastpercent+=2; //because typical console is 80 chars wide | |
} | |
d->out.flush(); | |
if (percent==100) | |
d->out << endl; | |
} | |
} | |
void TrkSignalHandler::stateChanged(int state) | |
{ | |
if (d->loglevel > 1) | |
d->out << "State" << state << endl; | |
} | |
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 (d->crashlog) { | |
CrashState cs; | |
cs.pid = pid; | |
cs.tid = tid; | |
cs.crashPC = pc; | |
cs.crashReason = reason; | |
if (d->dyingThreads.contains(tid)) { | |
if(d->queuedCrashes.isEmpty()) | |
emit terminate(); | |
else | |
d->terminateNeeded = true; | |
} else { | |
d->queuedCrashes.append(cs); | |
d->dyingThreads.append(tid); | |
if (d->queuedCrashes.count() == 1) { | |
d->err << "Fetching registers and stack..." << endl; | |
emit getRegistersAndCallStack(pid, tid); | |
} | |
} | |
} | |
else | |
emit terminate(); | |
} | |
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); | |
} | |
else if (d->terminateNeeded) | |
emit terminate(); | |
} | |
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() | |
{ | |
d->err << "FAILED: stopping test due to timeout" << endl; | |
emit terminate(); | |
} | |
TrkSignalHandlerPrivate::TrkSignalHandlerPrivate() | |
: out(stdout), | |
err(stderr), | |
loglevel(0), | |
lastpercent(0), | |
terminateNeeded(false) | |
{ | |
} | |
TrkSignalHandlerPrivate::~TrkSignalHandlerPrivate() | |
{ | |
out.flush(); | |
err.flush(); | |
} | |
TrkSignalHandler::TrkSignalHandler() | |
: d(new TrkSignalHandlerPrivate()) | |
{ | |
} | |
TrkSignalHandler::~TrkSignalHandler() | |
{ | |
delete d; | |
} |