Commit c70f968f authored by Friedemann Kleint's avatar Friedemann Kleint

CDB: Fix step into, improve multithread dumping, exception logging

Use new call syntax of 6.11. for dumper call loading. Execute
Dumpers in a single thread (current) if at all possible (not
in some WaitFor or artificial break thread). Show one more
frame in threads view if it is FastCallReturn. Fix step into
(ignore one event), log some more exceptions. Generally log
exceptions to the debugger windows to be able to see stuff
like DLL missing, etc.
parent 1110b622
......@@ -1080,7 +1080,7 @@
\l{http://www.microsoft.com/whdc/devtools/debugging/installx86.Mspx}{32-bit}
or
\l{http://www.microsoft.com/whdc/devtools/debugging/install64bit.Mspx}{64-bit}
package (Version 6.10 for the 32-bit or the 64-bit version of Qt Creator, respectively),
package (Version 6.11.1.404 for the 32-bit or the 64-bit version of Qt Creator, respectively),
which is freely available for download from the
\l{http://msdn.microsoft.com/en-us/default.aspx}
{Microsoft Developer Network}.
......
......@@ -302,6 +302,7 @@ CdbDebugEnginePrivate::CdbDebugEnginePrivate(DebuggerManager *manager,
m_dumper(new CdbDumperHelper(manager, &m_cif)),
m_currentThreadId(-1),
m_eventThreadId(-1),
m_interrupted(false),
m_watchTimer(-1),
m_debugEventCallBack(engine),
m_engine(engine),
......@@ -445,6 +446,7 @@ void CdbDebugEnginePrivate::clearForRun()
m_breakEventMode = BreakEventHandle;
m_eventThreadId = -1;
m_interrupted = false;
cleanStackTrace();
}
......@@ -1026,6 +1028,9 @@ bool CdbDebugEngine::step(unsigned long executionStatus)
warning(msgStepFailed(executionStatus, m_d->m_currentThreadId, msgDebuggerCommandFailed(command, hr)));
}
if (success) {
// Oddity: Step into will first break at the calling function. Ignore
if (executionStatus == DEBUG_STATUS_STEP_INTO || executionStatus == DEBUG_STATUS_REVERSE_STEP_INTO)
m_d->m_breakEventMode = CdbDebugEnginePrivate::BreakEventIgnoreOnce;
startWatchTimer();
setState(InferiorRunning, Q_FUNC_INFO, __LINE__);
} else {
......@@ -1169,7 +1174,9 @@ bool CdbDebugEnginePrivate::interruptInterferiorProcess(QString *errorMessage)
qDebug() << Q_FUNC_INFO << "\n ex=" << executionStatus;
}
if (!DebugBreakProcess(m_hDebuggeeProcess)) {
if (DebugBreakProcess(m_hDebuggeeProcess)) {
m_interrupted = true;
} else {
*errorMessage = QString::fromLatin1("DebugBreakProcess failed: %1").arg(Utils::winErrorMessage(GetLastError()));
return false;
}
......@@ -1704,12 +1711,34 @@ ULONG CdbDebugEnginePrivate::updateThreadList()
QList<ThreadData> threads;
ULONG currentThreadId;
QString errorMessage;
// When interrupting, an artifical thread with a breakpoint is created.
if (!CdbStackTraceContext::getThreads(m_cif, true, &threads, &currentThreadId, &errorMessage))
m_engine->warning(errorMessage);
manager()->threadsHandler()->setThreads(threads);
return currentThreadId;
}
// Figure out the thread to run the dumpers in (see notes on.
// CdbDumperHelper). Avoid the artifical threads created by interrupt
// and threads that are in waitFor().
// A stricter version could only use the thread if it is the event
// thread of a step or breakpoint hit (see CdbDebugEnginePrivate::m_interrupted).
static inline unsigned long dumperThreadId(const QList<StackFrame> &frames,
unsigned long currentThread)
{
if (frames.empty())
return CdbDumperHelper::InvalidDumperCallThread;
if (frames.at(0).function == QLatin1String(CdbStackTraceContext::winFuncDebugBreakPoint))
return CdbDumperHelper::InvalidDumperCallThread;
const int waitCheckDepth = qMin(frames.size(), 5);
static const QString waitForPrefix = QLatin1String(CdbStackTraceContext::winFuncWaitForPrefix);
for (int f = 0; f < waitCheckDepth; f++)
if (frames.at(f).function.startsWith(waitForPrefix))
return CdbDumperHelper::InvalidDumperCallThread;
return currentThread;
}
void CdbDebugEnginePrivate::updateStackTrace()
{
if (debugCDB)
......@@ -1750,11 +1779,19 @@ void CdbDebugEnginePrivate::updateStackTrace()
CdbDebugEngine::tr("Thread %1: No debug information available (%2).").arg(m_currentThreadId).arg(topFunction);
m_engine->warning(msg);
}
// Set up dumper with a thread (or invalid)
const unsigned long dumperThread = dumperThreadId(stackFrames, m_currentThreadId);
if (debugCDB)
qDebug() << "updateStackTrace() current: " << m_currentThreadId << " dumper=" << dumperThread;
m_dumper->setDumperCallThread(dumperThread);
// Display frames
manager()->stackHandler()->setFrames(stackFrames);
m_firstActivatedFrame = true;
if (current >= 0) {
manager()->stackHandler()->setCurrentIndex(current);
// First time : repaint
if (m_dumper->isEnabled() && m_dumper->state() != CdbDumperHelper::Initialized)
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
m_engine->activateFrame(current);
}
manager()->watchHandler()->updateWatchers();
......
......@@ -156,6 +156,7 @@ struct CdbDebugEnginePrivate
const QSharedPointer<CdbOptions> m_options;
HANDLE m_hDebuggeeProcess;
HANDLE m_hDebuggeeThread;
bool m_interrupted;
int m_currentThreadId;
int m_eventThreadId;
HandleBreakEventMode m_breakEventMode;
......
......@@ -248,6 +248,7 @@ STDMETHODIMP CdbDebugEventCallback::Exception(
if (debugCDB)
qDebug() << Q_FUNC_INFO << "\nex=" << Exception->ExceptionCode << " fatal=" << fatal << msg;
m_pEngine->manager()->showApplicationOutput(msg);
m_pEngine->manager()->showDebuggerOutput(LogMisc, msg);
if (fatal)
m_pEngine->m_d->notifyCrashed();
return S_OK;
......
......@@ -41,8 +41,10 @@
#include <QtCore/QRegExp>
#include <QtCore/QCoreApplication>
#include <QtCore/QTextStream>
#include <QtCore/QTime>
enum { loadDebug = 0 };
enum { dumpDebug = 0 };
static const char *dumperModuleNameC = "gdbmacros";
static const char *qtCoreModuleNameC = "QtCore";
......@@ -158,24 +160,25 @@ static bool debuggeeLoadLibrary(DebuggerManager *manager,
// We want to call "HMODULE LoadLibraryA(LPCTSTR lpFileName)"
// (void* LoadLibraryA(char*)). However, despite providing a symbol
// server, the debugger refuses to recognize it as a function.
// Set up the call stack with a function of same signature (qstrdup)
// and change the call register to LoadLibraryA() before executing "g".
// Call with a prototype of 'qstrdup', as it is the same
// Prepare call: Locate 'qstrdup' in the (potentially namespaced) corelib. For some
// reason, the symbol is present in QtGui as well without type information.
QString dummyFunc = QLatin1String("*qstrdup");
if (resolveSymbol(cif->debugSymbols, QLatin1String("QtCore[d]*4!"), &dummyFunc, errorMessage) != ResolveSymbolOk)
return false;
QString callCmd = QLatin1String(".call ");
callCmd += dummyFunc;
callCmd += QLatin1String("(0x");
callCmd += QString::number(nameAddress, 16);
callCmd += QLatin1Char(')');
QString callCmd; {
QTextStream str(&callCmd);
str.setIntegerBase(16);
str << ".call /s " << dummyFunc << " Kernel32!LoadLibraryA(0x" << nameAddress << ')';
}
if (loadDebug)
qDebug() << "Calling" << callCmd;
if (!CdbDebugEnginePrivate::executeDebuggerCommand(cif->debugControl, callCmd, errorMessage))
return false;
if (!CdbDebugEnginePrivate::executeDebuggerCommand(cif->debugControl, QLatin1String("r eip=Kernel32!LoadLibraryA"), errorMessage))
return false;
// This will hit a breakpoint.
if (!CdbDebugEnginePrivate::executeDebuggerCommand(cif->debugControl, QString(QLatin1Char('g')), errorMessage))
// Execute current thread. This will hit a breakpoint.
if (!CdbDebugEnginePrivate::executeDebuggerCommand(cif->debugControl, QLatin1String("~. g"), errorMessage))
return false;
const HRESULT hr = cif->debugControl->WaitForEvent(0, waitTimeOutMS);
if (FAILED(hr)) {
......@@ -185,6 +188,14 @@ static bool debuggeeLoadLibrary(DebuggerManager *manager,
return true;
}
// Format a "go" in a thread
static inline QString goCommand(unsigned long threadId)
{
QString rc;
QTextStream(&rc) << '~' << threadId << " g";
return rc;
}
// ---- Load messages
static inline QString msgMethod(bool injectOrCall)
{
......@@ -226,7 +237,9 @@ CdbDumperHelper::CdbDumperHelper(DebuggerManager *manager,
m_inBufferSize(0),
m_outBufferAddress(0),
m_outBufferSize(0),
m_buffer(0)
m_buffer(0),
m_dumperCallThread(0),
m_goCommand(goCommand(m_dumperCallThread))
{
}
......@@ -324,7 +337,7 @@ CdbDumperHelper::CallLoadResult CdbDumperHelper::initCallLoad(QString *errorMess
bool CdbDumperHelper::ensureInitialized(QString *errorMessage)
{
if (loadDebug)
qDebug() << Q_FUNC_INFO << '\n' << m_state;
qDebug() << "ensureInitialized thread: " << m_dumperCallThread << " state: " << m_state;
switch (m_state) {
case Disabled:
......@@ -372,6 +385,9 @@ bool CdbDumperHelper::ensureInitialized(QString *errorMessage)
m_manager->showDebuggerOutput(LogMisc, *errorMessage);
m_manager->showQtDumperLibraryWarning(*errorMessage);
}
if (loadDebug)
qDebug() << Q_FUNC_INFO << '\n' << ok;
return ok;
}
......@@ -451,7 +467,7 @@ bool CdbDumperHelper::initKnownTypes(QString *errorMessage)
*errorMessage = QtDumperHelper::msgDumperOutdated(dumperVersionRequired, m_helper.dumperVersion());
return false;
}
if (loadDebug)
if (loadDebug || dumpDebug)
qDebug() << Q_FUNC_INFO << m_helper.toString(true);
return true;
}
......@@ -492,8 +508,11 @@ bool CdbDumperHelper::callDumper(const QString &callCmd, const QByteArray &inBuf
// by using 'gN' (go not handled -> pass handling to dumper __try/__catch block)
for (int i = 0; i < 10; i++) {
const int oldExceptionCount = exLogger.exceptionCount();
// Go. If an exception occurs in loop 2, let the dumper handle it.
const QString goCmd = i ? QString(QLatin1String("gN")) : QString(QLatin1Char('g'));
// Go in current thread. If an exception occurs in loop 2,
// let the dumper handle it.
QString goCmd = m_goCommand;
if (i)
goCmd = QLatin1Char('N');
if (!CdbDebugEnginePrivate::executeDebuggerCommand(m_cif->debugControl, goCmd, errorMessage))
return false;
HRESULT hr = m_cif->debugControl->WaitForEvent(0, waitTimeOutMS);
......@@ -556,6 +575,18 @@ static inline QString msgNotHandled(const QString &type)
CdbDumperHelper::DumpResult CdbDumperHelper::dumpType(const WatchData &wd, bool dumpChildren,
QList<WatchData> *result, QString *errorMessage)
{
if (dumpDebug)
qDebug() << ">dumpType() thread: " << m_dumperCallThread << " state: " << m_state << wd.type << QTime::currentTime().toString();
const CdbDumperHelper::DumpResult rc = dumpTypeI(wd, dumpChildren, result, errorMessage);
if (dumpDebug)
qDebug() << "<dumpType() state: " << m_state << wd.type << " returns " << rc << *errorMessage << QTime::currentTime().toString();
return rc;
}
CdbDumperHelper::DumpResult CdbDumperHelper::dumpTypeI(const WatchData &wd, bool dumpChildren,
QList<WatchData> *result, QString *errorMessage)
{
errorMessage->clear();
// Check failure cache and supported types
if (m_state == Disabled) {
*errorMessage = QLatin1String("Dumpers are disabled");
......@@ -570,6 +601,20 @@ CdbDumperHelper::DumpResult CdbDumperHelper::dumpType(const WatchData &wd, bool
return DumpNotHandled;
}
// Do we have a thread
if (m_dumperCallThread == InvalidDumperCallThread) {
*errorMessage = QString::fromLatin1("No thread to call.");
if (loadDebug)
qDebug() << *errorMessage;
return DumpNotHandled;
}
// Delay initialization as much as possible
if (isIntOrFloatType(wd.type)) {
*errorMessage = QString::fromLatin1("Unhandled POD: " ) + wd.type;
return DumpNotHandled;
}
// Ensure types are parsed and known.
if (!ensureInitialized(errorMessage)) {
*errorMessage = msgDumpFailed(wd, errorMessage);
......@@ -722,5 +767,18 @@ bool CdbDumperHelper::runTypeSizeQuery(const QString &typeName, int *size, QStri
return true;
}
unsigned long CdbDumperHelper::dumperCallThread()
{
return m_dumperCallThread;
}
void CdbDumperHelper::setDumperCallThread(unsigned long t)
{
if (m_dumperCallThread != t) {
m_dumperCallThread = t;
m_goCommand = goCommand(m_dumperCallThread);
}
}
} // namespace Internal
} // namespace Debugger
......@@ -55,7 +55,17 @@ struct CdbComInterfaces;
* dumpType() is the main query function to obtain a list of WatchData from
* WatchData item produced by the smbol context.
* Call disable(), should the debuggee crash (as performing debuggee
* calls is no longer possible, then).*/
* calls is no longer possible, then).
*
* dumperCallThread specifies the thread to use when making the calls.
* As of Debugging Tools v 6.11.1.404 (6.10.2009), calls cannot be executed
* when the current thread is in some WaitFor...() function. The call will
* then hang (regardless whether that thread or some other, non-blocking thread
* is used), and the debuggee will be in running state afterwards (causing errors
* from ReadVirtual, etc).
* The current thread can be used when stepping or a breakpoint was
* hit. When interrupting the inferior, an artifical thread is created,
* that is not usable, either. */
class CdbDumperHelper
{
......@@ -93,6 +103,10 @@ public:
inline CdbComInterfaces *comInterfaces() const { return m_cif; }
enum { InvalidDumperCallThread = 0xFFFFFFFF };
unsigned long dumperCallThread();
void setDumperCallThread(unsigned long t);
private:
enum CallLoadResult { CallLoadOk, CallLoadError, CallLoadNoQtApp, CallLoadAlreadyLoaded };
......@@ -103,6 +117,9 @@ private:
bool initResolveSymbols(QString *errorMessage);
bool initKnownTypes(QString *errorMessage);
inline DumpResult dumpTypeI(const WatchData &d, bool dumpChildren,
QList<WatchData> *result, QString *errorMessage);
bool getTypeSize(const QString &typeName, int *size, QString *errorMessage);
bool runTypeSizeQuery(const QString &typeName, int *size, QString *errorMessage);
bool callDumper(const QString &call, const QByteArray &inBuffer, const char **outputPtr,
......@@ -134,6 +151,8 @@ private:
QStringList m_failedTypes;
QtDumperHelper m_helper;
unsigned long m_dumperCallThread;
QString m_goCommand;
};
} // namespace Internal
......
......@@ -41,7 +41,11 @@ enum { debugExc = 0 };
// Special exception codes.
enum { cppExceptionCode = 0xe06d7363, startupCompleteTrap = 0x406d1388,
rpcServerUnavailableExceptionCode = 0x6ba,
dllNotFoundExceptionCode = 0xc0000135 };
dllNotFoundExceptionCode = 0xc0000135,
dllInitFailed = 0xc0000142,
missingSystemFile = 0xc0000143,
appInitFailed = 0xc0000143
};
namespace Debugger {
namespace Internal {
......@@ -172,6 +176,12 @@ void formatException(const EXCEPTION_RECORD64 *e, QTextStream &str)
case dllNotFoundExceptionCode:
str << "DLL not found";
break;
case dllInitFailed:
str << "DLL failed to initialize";
break;
case missingSystemFile:
str << "System file is missing";
break;
case EXCEPTION_ACCESS_VIOLATION: {
const bool writeOperation = e->ExceptionInformation[0];
str << (writeOperation ? "write" : "read")
......
......@@ -40,6 +40,10 @@
namespace Debugger {
namespace Internal {
const char *CdbStackTraceContext::winFuncFastSystemCallRet = "ntdll!KiFastSystemCallRet";
const char *CdbStackTraceContext::winFuncDebugBreakPoint = "ntdll!DbgBreakPoint";
const char *CdbStackTraceContext::winFuncWaitForPrefix = "kernel32!WaitFor";
CdbStackTraceContext::CdbStackTraceContext(const QSharedPointer<CdbDumperHelper> &dumper) :
m_dumper(dumper),
m_cif(dumper->comInterfaces()),
......@@ -232,6 +236,7 @@ static inline bool getStoppedThreadState(const CdbComInterfaces &cif,
ThreadData *t,
QString *errorMessage)
{
enum { MaxFrames = 2 };
ULONG currentThread;
HRESULT hr = cif.debugSystemObjects->GetCurrentThreadId(&currentThread);
if (FAILED(hr)) {
......@@ -246,20 +251,26 @@ static inline bool getStoppedThreadState(const CdbComInterfaces &cif,
}
}
ULONG frameCount;
DEBUG_STACK_FRAME topFrame[1];
hr = cif.debugControl->GetStackTrace(0, 0, 0, topFrame, 1, &frameCount);
// Ignore the top frame if it is "ntdll!KiFastSystemCallRet", which is
// not interesting for display.
DEBUG_STACK_FRAME frames[MaxFrames];
hr = cif.debugControl->GetStackTrace(0, 0, 0, frames, MaxFrames, &frameCount);
if (FAILED(hr)) {
*errorMessage = msgGetThreadStateFailed(t->id, msgComFailed("GetStackTrace", hr));
return false;
}
t->address = topFrame[0].InstructionOffset;
// Ignore the top frame if it is "ntdll!KiFastSystemCallRet", which is
// not interesting for display.
WCHAR wszBuf[MAX_PATH];
cif.debugSymbols->GetNameByOffsetWide(topFrame[0].InstructionOffset, wszBuf, MAX_PATH, 0, 0);
for (int frame = 0; frame < MaxFrames; frame++) {
cif.debugSymbols->GetNameByOffsetWide(frames[frame].InstructionOffset, wszBuf, MAX_PATH, 0, 0);
t->function = QString::fromUtf16(reinterpret_cast<const ushort *>(wszBuf));
if (frame != 0 || t->function != QLatin1String(CdbStackTraceContext::winFuncFastSystemCallRet)) {
t->address = frames[frame].InstructionOffset;
cif.debugSymbols->GetNameByOffsetWide(frames[frame].InstructionOffset, wszBuf, MAX_PATH, 0, 0);
t->function = QString::fromUtf16(reinterpret_cast<const ushort *>(wszBuf));
ULONG ulLine;
hr = cif.debugSymbols->GetLineByOffsetWide(topFrame[0].InstructionOffset, &ulLine, wszBuf, MAX_PATH, 0, 0);
hr = cif.debugSymbols->GetLineByOffsetWide(frames[frame].InstructionOffset, &ulLine, wszBuf, MAX_PATH, 0, 0);
if (SUCCEEDED(hr)) {
t->line = ulLine;
// Just display base name
......@@ -270,6 +281,9 @@ static inline bool getStoppedThreadState(const CdbComInterfaces &cif,
t->file.remove(0, slashPos + 1);
}
}
break;
} // was not "ntdll!KiFastSystemCallRet"
}
return true;
}
......
......@@ -63,6 +63,13 @@ class CdbStackTraceContext
public:
enum { maxFrames = 100 };
// Some well known-functions
static const char *winFuncFastSystemCallRet;
// WaitFor...
static const char *winFuncWaitForPrefix;
// Dummy function used for interrupting a debuggee
static const char *winFuncDebugBreakPoint;
~CdbStackTraceContext();
static CdbStackTraceContext *create(const QSharedPointer<CdbDumperHelper> &dumper,
unsigned long threadid,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment