Debugger: Make threads tooltip more verbose

CDB: Add more information and state to Thread.
Refactor thread information in CdbCore classes to prepare for more
information.
This commit is contained in:
Friedemann Kleint
2010-07-02 14:35:41 +02:00
parent dd0978796e
commit ab6f124e57
8 changed files with 197 additions and 86 deletions

View File

@@ -194,6 +194,7 @@ void CdbDebugEnginePrivate::clearForRun()
cleanStackTrace();
m_stoppedReason = StoppedOther;
m_stoppedMessage.clear();
m_engine->threadsHandler()->notifyRunning();
}
void CdbDebugEnginePrivate::cleanStackTrace()
@@ -1331,7 +1332,13 @@ void CdbDebugEnginePrivate::handleDebugEvent()
if (m_engine->state() != InferiorStopping)
m_engine->setState(InferiorStopping, Q_FUNC_INFO, __LINE__);
m_engine->setState(InferiorStopped, Q_FUNC_INFO, __LINE__);
m_eventThreadId = updateThreadList();
// Indicate artifical thread that is created when interrupting as such,
// else use stop message with cleaned newlines and blanks.
const QString currentThreadState =
m_interrupted ? CdbDebugEngine::tr("<interrupt thread>") :
(m_stoppedReason == StoppedBreakpoint ? CdbDebugEngine::tr("Breakpoint") :
m_stoppedMessage.simplified() );
m_eventThreadId = updateThreadList(currentThreadState);
m_interruptArticifialThreadId = m_interrupted ? m_eventThreadId : -1;
// Get thread to stop and its index. If avoidable, do not use
// the artifical thread that is created when interrupting,
@@ -1406,7 +1413,7 @@ bool CdbDebugEnginePrivate::setCDBThreadId(unsigned long threadId, QString *erro
return true;
}
ULONG CdbDebugEnginePrivate::updateThreadList()
ULONG CdbDebugEnginePrivate::updateThreadList(const QString &currentThreadState)
{
if (debugCDB)
qDebug() << Q_FUNC_INFO << m_hDebuggeeProcess;
@@ -1415,8 +1422,23 @@ ULONG CdbDebugEnginePrivate::updateThreadList()
ULONG currentThreadId;
QString errorMessage;
// When interrupting, an artifical thread with a breakpoint is created.
if (!CdbStackTraceContext::getThreads(interfaces(), &threads, &currentThreadId, &errorMessage))
const bool stopped = m_engine->state() == InferiorStopped;
if (!CdbStackTraceContext::getThreads(interfaces(),
stopped,
&threads, &currentThreadId,
&errorMessage))
m_engine->warning(errorMessage);
// Indicate states 'stopped' or current thread state.
// Do not indicate 'running' since we can't know if it is suspended.
if (stopped) {
const QString state = CdbDebugEngine::tr("stopped");
const bool hasCurrentState = !currentThreadState.isEmpty();
const int count = threads.size();
for (int i= 0; i < count; i++) {
threads[i].state = hasCurrentState && threads.at(i).id == currentThreadId ?
currentThreadState : state;
}
}
m_engine->threadsHandler()->setThreads(threads);
return currentThreadId;
}

View File

@@ -76,7 +76,7 @@ public:
void setDebuggeeHandles(HANDLE hDebuggeeProcess, HANDLE hDebuggeeThread);
bool isDebuggeeRunning() const { return isWatchTimerRunning(); }
ULONG updateThreadList();
ULONG updateThreadList(const QString &currentThreadState = QString());
bool setCDBThreadId(unsigned long threadId, QString *errorMessage);
void updateStackTrace();
void updateModules();

View File

@@ -115,32 +115,45 @@ QList<StackFrame> CdbStackTraceContext::stackFrames() const
}
bool CdbStackTraceContext::getThreads(const CdbCore::ComInterfaces &cif,
bool stopped,
Threads *threads,
ULONG *currentThreadId,
QString *errorMessage)
{
QVector<CdbCore::Thread> coreThreads;
if (!CdbCore::StackTraceContext::getThreadList(cif, &coreThreads, currentThreadId, errorMessage))
return false;
// Get frames only if stopped.
QVector<CdbCore::StackFrame> frames;
if (stopped)
if (!CdbCore::StackTraceContext::getStoppedThreadFrames(cif, *currentThreadId,
coreThreads, &frames, errorMessage))
return false;
// Convert from Core data structures
threads->clear();
ThreadIdFrameMap threadMap;
if (!CdbCore::StackTraceContext::getThreads(cif, &threadMap,
currentThreadId, errorMessage))
return false;
const QChar slash = QLatin1Char('/');
const ThreadIdFrameMap::const_iterator cend = threadMap.constEnd();
for (ThreadIdFrameMap::const_iterator it = threadMap.constBegin(); it != cend; ++it) {
ThreadData data(it.key());
const CdbCore::StackFrame &coreFrame = it.value();
data.address = coreFrame.address;
data.function = coreFrame.function;
data.lineNumber = coreFrame.line;
// Basename only for brevity
const int slashPos = coreFrame.fileName.lastIndexOf(slash);
data.fileName = slashPos == -1 ? coreFrame.fileName : coreFrame.fileName.mid(slashPos + 1);
const int count = coreThreads.size();
if (!count)
return true;
threads->reserve(count);
const QChar slash(QLatin1Char('/'));
for (int i = 0; i < count; i++) {
const CdbCore::Thread &coreThread = coreThreads.at(i);
ThreadData data(coreThread.id);
data.targetId = QLatin1String("0x") + QString::number(coreThread.systemId);
if (stopped) {
const CdbCore::StackFrame &coreFrame = frames.at(i);
data.address = coreFrame.address;
data.function = coreFrame.function;
data.lineNumber = coreFrame.line;
// Basename only for brevity
const int slashPos = coreFrame.fileName.lastIndexOf(slash);
data.fileName = slashPos == -1 ? coreFrame.fileName : coreFrame.fileName.mid(slashPos + 1);
}
threads->push_back(data);
}
return true;
}
} // namespace Internal
} // namespace Debugger

View File

@@ -75,6 +75,7 @@ public:
// get threads in stopped state
static bool getThreads(const CdbCore::ComInterfaces &cif,
bool stopped,
Threads *threads,
ULONG *currentThreadId,
QString *errorMessage);

View File

@@ -35,6 +35,7 @@
#include <QtCore/QDir>
#include <QtCore/QDebug>
#include <QtCore/QTextStream>
#include <QtCore/QScopedArrayPointer>
enum { debug = 0 };
@@ -74,6 +75,27 @@ void StackFrame::format(QTextStream &str) const
str.setIntegerBase(10);
}
Thread::Thread(unsigned long i, unsigned long si) :
id(i), systemId(si), dataOffset(0)
{
}
QString Thread::toString() const
{
QString rc;
QTextStream str(&rc);
str << "Thread id " << id << " System id " << systemId << " Data at 0x";
str.setIntegerBase(16);
str << dataOffset;
return rc;
}
QDebug operator<<(QDebug d, const Thread &t)
{
d.nospace() << t.toString();
return d;
}
// Check for special functions
StackTraceContext::SpecialFunction StackTraceContext::specialFunction(const QString &module,
const QString &function)
@@ -347,20 +369,21 @@ static inline QString msgGetThreadsFailed(const QString &why)
return QString::fromLatin1("Unable to determine the thread information: %1").arg(why);
}
bool StackTraceContext::getThreadIds(const CdbCore::ComInterfaces &cif,
QVector<ULONG> *threadIds,
ULONG *currentThreadId,
QString *errorMessage)
bool StackTraceContext::getThreadList(const CdbCore::ComInterfaces &cif,
QVector<Thread> *threads,
ULONG *currentThreadId,
QString *errorMessage)
{
threadIds->clear();
threads->clear();
ULONG threadCount;
*currentThreadId = 0;
// Get count
HRESULT hr= cif.debugSystemObjects->GetNumberThreads(&threadCount);
if (FAILED(hr)) {
*errorMessage= msgGetThreadsFailed(CdbCore::msgComFailed("GetNumberThreads", hr));
return false;
}
// Get ids and index of current
// Get index of current
if (!threadCount)
return true;
hr = cif.debugSystemObjects->GetCurrentThreadId(currentThreadId);
@@ -368,40 +391,50 @@ bool StackTraceContext::getThreadIds(const CdbCore::ComInterfaces &cif,
*errorMessage= msgGetThreadsFailed(CdbCore::msgComFailed("GetCurrentThreadId", hr));
return false;
}
threadIds->resize(threadCount);
hr = cif.debugSystemObjects->GetThreadIdsByIndex(0, threadCount, &(*threadIds->begin()), 0);
// Get Identifiers
threads->reserve(threadCount);
QScopedArrayPointer<ULONG> ids(new ULONG[threadCount]);
QScopedArrayPointer<ULONG> systemIds(new ULONG[threadCount]);
hr = cif.debugSystemObjects->GetThreadIdsByIndex(0, threadCount, ids.data(), systemIds.data());
if (FAILED(hr)) {
*errorMessage= msgGetThreadsFailed(CdbCore::msgComFailed("GetThreadIdsByIndex", hr));
return false;
}
// Create entries
for (ULONG i= 0; i < threadCount ; i++) {
threads->push_back(Thread(ids[i], systemIds[i]));
if (ids[i] == *currentThreadId) { // More info for current
ULONG64 offset;
if (SUCCEEDED(cif.debugSystemObjects->GetCurrentThreadDataOffset(&offset)))
threads->back().dataOffset = offset;
}
}
return true;
}
bool StackTraceContext::getThreads(const CdbCore::ComInterfaces &cif,
ThreadIdFrameMap *threads,
ULONG *currentThreadId,
QString *errorMessage)
bool StackTraceContext::getStoppedThreadFrames(const CdbCore::ComInterfaces &cif,
ULONG currentThreadId,
const QVector<Thread> &threads,
QVector<StackFrame> *frames,
QString *errorMessage)
{
threads->clear();
QVector<ULONG> threadIds;
if (!getThreadIds(cif, &threadIds, currentThreadId, errorMessage))
return false;
if (threadIds.isEmpty())
frames->clear();
if (threads.isEmpty())
return true;
frames->reserve(threads.size());
const int threadCount = threadIds.size();
const int threadCount = threads.size();
for (int i = 0; i < threadCount; i++) {
const ULONG id = threadIds.at(i);
StackFrame frame;
if (!getStoppedThreadState(cif, id, &frame, errorMessage)) {
if (!getStoppedThreadState(cif, threads.at(i).id, &frame, errorMessage)) {
qWarning("%s\n", qPrintable(*errorMessage));
errorMessage->clear();
}
threads->insert(id, frame);
frames->append(frame);
}
// Restore thread id
if (threadIds.back() != *currentThreadId) {
const HRESULT hr = cif.debugSystemObjects->SetCurrentThreadId(*currentThreadId);
if (threads.back().id != currentThreadId) {
const HRESULT hr = cif.debugSystemObjects->SetCurrentThreadId(currentThreadId);
if (FAILED(hr)) {
*errorMessage= msgGetThreadsFailed(CdbCore::msgComFailed("SetCurrentThreadId", hr));
return false;
@@ -410,19 +443,6 @@ bool StackTraceContext::getThreads(const CdbCore::ComInterfaces &cif,
return true;
}
QString StackTraceContext::formatThreads(const ThreadIdFrameMap &threads)
{
QString rc;
QTextStream str(&rc);
const ThreadIdFrameMap::const_iterator cend = threads.constEnd();
for (ThreadIdFrameMap::const_iterator it = threads.constBegin(); it != cend; ++it) {
str << '#' << it.key() << ' ';
it.value().format(str);
str << '\n';
}
return rc;
}
QDebug operator<<(QDebug d, const StackTraceContext &t)
{
d.nospace() << t.toString();

View File

@@ -63,7 +63,19 @@ struct StackFrame {
ULONG64 address;
};
struct Thread {
explicit Thread(unsigned long id = 0, unsigned long sysId = 0);
QString toString() const;
unsigned long id;
unsigned long systemId;
quint64 dataOffset; // Only for current.
};
inline bool operator<(const Thread &t1, const Thread &t2) { return t1.id < t2.id; }
QDebug operator<<(QDebug d, const StackFrame &);
QDebug operator<<(QDebug d, const Thread &);
/* Context representing a break point stack consisting of several frames.
* Maintains an on-demand constructed list of SymbolGroupContext
@@ -87,9 +99,6 @@ public:
};
static SpecialFunction specialFunction(const QString &module, const QString &function);
// A map of thread id, stack frame
typedef QMap<unsigned long, StackFrame> ThreadIdFrameMap;
enum { maxFrames = 100 };
~StackTraceContext();
@@ -112,10 +121,10 @@ public:
QString toString() const;
// Thread helpers: Retrieve a list of thread ids. Also works when running.
static inline bool getThreadIds(const CdbCore::ComInterfaces &cif,
QVector<ULONG> *threadIds,
ULONG *currentThreadId,
QString *errorMessage);
static bool getThreadList(const CdbCore::ComInterfaces &cif,
QVector<Thread> *threads,
ULONG *currentThreadId,
QString *errorMessage);
// Retrieve detailed information about a threads in stopped state.
// Potentially changes current thread id.
@@ -124,13 +133,13 @@ public:
StackFrame *topFrame,
QString *errorMessage);
// Retrieve detailed information about all threads, works in stopped state.
static bool getThreads(const CdbCore::ComInterfaces &cif,
ThreadIdFrameMap *threads,
ULONG *currentThreadId,
QString *errorMessage);
static QString formatThreads(const ThreadIdFrameMap &threads);
// Get the stack traces for threads in stopped state (only, fails when running).
// Potentially changes and restores current thread.
static bool getStoppedThreadFrames(const CdbCore::ComInterfaces &cif,
ULONG currentThreadId,
const QVector<Thread> &threads,
QVector<StackFrame> *frames,
QString *errorMessage);
protected:
virtual SymbolGroupContext *createSymbolGroup(const ComInterfaces &cif,

View File

@@ -2966,7 +2966,7 @@ void GdbEngine::handleThreadInfo(const GdbResponse &response)
const GdbMi frame = item.findChild("frame");
ThreadData thread;
thread.id = item.findChild("id").data().toInt();
thread.targetId = item.findChild("target-id").data().toInt();
thread.targetId = QString::fromAscii(item.findChild("target-id").data());
thread.core = QString::fromLatin1(item.findChild("core").data());
thread.state = QString::fromLatin1(item.findChild("state").data());
thread.address = frame.findChild("addr").data().toULongLong(&ok, 0);

View File

@@ -32,6 +32,7 @@
#include "debuggerconstants.h"
#include "debuggerengine.h"
#include <QtCore/QTextStream>
namespace Debugger {
namespace Internal {
@@ -58,6 +59,56 @@ void ThreadData::notifyRunning()
lineNumber = -1;
}
int id;
QString targetId;
QString core;
// State information when stopped
void notifyRunning(); // Clear state information
int frameLevel;
quint64 address;
QString function;
QString fileName;
QString state;
int lineNumber;
static inline QString threadToolTip(const ThreadData &thread)
{
const char tableRowStartC[] = "<tr><td>";
const char tableRowSeparatorC[] = "</td><td>";
const char tableRowEndC[] = "</td>";
QString rc;
QTextStream str(&rc);
str << "<html><head/><body><table>"
<< tableRowStartC << ThreadsHandler::tr("Thread&nbsp;id:")
<< tableRowSeparatorC << thread.id << tableRowEndC;
if (!thread.targetId.isEmpty())
str << tableRowStartC << ThreadsHandler::tr("Target&nbsp;id:")
<< tableRowSeparatorC << thread.targetId << tableRowEndC;
if (!thread.state.isEmpty())
str << tableRowStartC << ThreadsHandler::tr("State:")
<< tableRowSeparatorC << thread.state << tableRowEndC;
if (!thread.core.isEmpty())
str << tableRowStartC << ThreadsHandler::tr("Core:")
<< tableRowSeparatorC << thread.core << tableRowEndC;
if (thread.address) {
str << tableRowStartC << ThreadsHandler::tr("Stopped&nbsp;at:")
<< tableRowSeparatorC;
if (!thread.function.isEmpty())
str << thread.function << "<br>";
if (!thread.fileName.isEmpty())
str << thread.fileName << ':' << thread.lineNumber << "<br>";
str.setIntegerBase(16);
str << "0x" << thread.address;
str.setIntegerBase(10);
}
str << "</table></body></html>";
return rc;
}
////////////////////////////////////////////////////////////////////////
//
// ThreadsHandler
@@ -92,7 +143,8 @@ QVariant ThreadsHandler::data(const QModelIndex &index, int role) const
return QVariant();
const ThreadData &thread = m_threads.at(row);
if (role == Qt::DisplayRole) {
switch (role) {
case Qt::DisplayRole:
switch (index.column()) {
case ThreadData::IdColumn:
return thread.id;
@@ -112,21 +164,15 @@ QVariant ThreadsHandler::data(const QModelIndex &index, int role) const
case ThreadData::StateColumn:
return thread.state;
}
} else if (role == Qt::ToolTipRole) {
if (thread.address == 0)
return tr("Thread: %1").arg(thread.id);
// Stopped
if (thread.fileName.isEmpty())
return tr("Thread: %1 at %2 (0x%3)").arg(thread.id)
.arg(thread.function).arg(thread.address, 0, 16);
return tr("Thread: %1 at %2, %3:%4 (0x%5)").
arg(thread.id).arg(thread.function, thread.fileName)
.arg(thread.lineNumber).arg(thread.address, 0, 16);
} else if (role == Qt::DecorationRole && index.column() == 0) {
// Return icon that indicates whether this is the active stack frame
return (index.row() == m_currentIndex) ? m_positionIcon : m_emptyIcon;
case Qt::ToolTipRole:
return threadToolTip(thread);
case Qt::DecorationRole: // Return icon that indicates whether this is the active stack frame
if (index.column() == 0)
return (index.row() == m_currentIndex) ? m_positionIcon : m_emptyIcon;
break;
default:
break;
}
return QVariant();
}