diff --git a/src/plugins/debugger/cdb/cdbdebugengine.cpp b/src/plugins/debugger/cdb/cdbdebugengine.cpp index d020447d7131c57edf3501d72626bb151b53597a..462a71cf8755482a992def56ca2c3233924c408e 100644 --- a/src/plugins/debugger/cdb/cdbdebugengine.cpp +++ b/src/plugins/debugger/cdb/cdbdebugengine.cpp @@ -120,6 +120,11 @@ QString msgComFailed(const char *func, HRESULT hr) return QString::fromLatin1("%1 failed: %2").arg(QLatin1String(func), msgDebugEngineComResult(hr)); } +QString msgDebuggerCommandFailed(const QString &command, HRESULT hr) +{ + return QString::fromLatin1("Unable to execute '%1': %2").arg(command, msgDebugEngineComResult(hr)); +} + static const char *msgNoStackTraceC = "Internal error: no stack trace present."; static inline QString msgLibLoadFailed(const QString &lib, const QString &why) @@ -295,6 +300,8 @@ CdbDebugEnginePrivate::CdbDebugEnginePrivate(DebuggerManager *manager, m_hDebuggeeThread(0), m_breakEventMode(BreakEventHandle), m_dumper(new CdbDumperHelper(manager, &m_cif)), + m_currentThreadId(-1), + m_eventThreadId(-1), m_watchTimer(-1), m_debugEventCallBack(engine), m_engine(engine), @@ -437,9 +444,8 @@ void CdbDebugEnginePrivate::clearForRun() qDebug() << Q_FUNC_INFO; m_breakEventMode = BreakEventHandle; - m_firstActivatedFrame = false; + m_eventThreadId = -1; cleanStackTrace(); - m_editorToolTipCache.clear(); } void CdbDebugEnginePrivate::cleanStackTrace() @@ -448,6 +454,8 @@ void CdbDebugEnginePrivate::cleanStackTrace() delete m_currentStackTrace; m_currentStackTrace = 0; } + m_firstActivatedFrame = false; + m_editorToolTipCache.clear(); } CdbDebugEngine::CdbDebugEngine(DebuggerManager *manager, const QSharedPointer<CdbOptions> &options) : @@ -965,24 +973,85 @@ bool CdbDebugEnginePrivate::executeContinueCommand(const QString &command) return success; } -void CdbDebugEngine::stepExec() +static inline QString msgStepFailed(unsigned long executionStatus, int threadId, const QString &why) +{ + if (executionStatus == DEBUG_STATUS_STEP_OVER) + return QString::fromLatin1("Thread %1: Unable to step over: %2").arg(threadId).arg(why); + return QString::fromLatin1("Thread %1: Unable to step into: %2").arg(threadId).arg(why); +} + +// Step with DEBUG_STATUS_STEP_OVER ('p'-command) or +// DEBUG_STATUS_STEP_INTO ('t'-trace-command) or +// its reverse equivalents in the case of single threads. +bool CdbDebugEngine::step(unsigned long executionStatus) { - // Step into if (debugCDB) - qDebug() << Q_FUNC_INFO; + qDebug() << Q_FUNC_INFO << executionStatus << "curr " << m_d->m_currentThreadId << " evt " << m_d->m_eventThreadId; - m_d->clearForRun(); - m_d->setCodeLevel(); + // State of reverse stepping as of 10/2009 (Debugging tools 6.11@404): + // The constants exist, but invoking the calls leads to E_NOINTERFACE. + // Also there is no CDB command for it. + if (executionStatus == DEBUG_STATUS_REVERSE_STEP_OVER || executionStatus == DEBUG_STATUS_REVERSE_STEP_INTO) { + warning(tr("Reverse stepping is not implemented.")); + return false; + } + + // SetExecutionStatus() continues the thread that triggered the + // stop event (~# p). This can be confusing if the user is looking + // at the stack trace of another thread and wants to step that one. If that + // is the case, explicitly tell it to step the current thread using a command. + const int triggeringEventThread = m_d->m_eventThreadId; + const bool sameThread = triggeringEventThread == -1 + || m_d->m_currentThreadId == triggeringEventThread + || manager()->threadsHandler()->threads().size() == 1; + m_d->clearForRun(); // clears thread ids + m_d->setCodeLevel(); // Step by instruction or source line setState(InferiorRunningRequested, Q_FUNC_INFO, __LINE__); - const HRESULT hr = m_d->m_cif.debugControl->SetExecutionStatus(DEBUG_STATUS_STEP_INTO); - if (SUCCEEDED(hr)) { + bool success = false; + if (sameThread) { // Step event-triggering thread, use fast API + const HRESULT hr = m_d->m_cif.debugControl->SetExecutionStatus(executionStatus); + success = SUCCEEDED(hr); + if (!success) + warning(msgStepFailed(executionStatus, m_d->m_currentThreadId, msgComFailed("SetExecutionStatus", hr))); + } else { + // Need to use a command to explicitly specify the current thread + QString command; + QTextStream str(&command); + str << '~' << m_d->m_currentThreadId << ' ' + << (executionStatus == DEBUG_STATUS_STEP_OVER ? 'p' : 't'); + manager()->showDebuggerOutput(tr("Stepping %1").arg(command)); + const HRESULT hr = m_d->m_cif.debugControl->Execute(DEBUG_OUTCTL_THIS_CLIENT, command.toLatin1().constData(), DEBUG_EXECUTE_ECHO); + success = SUCCEEDED(hr); + if (!success) + warning(msgStepFailed(executionStatus, m_d->m_currentThreadId, msgDebuggerCommandFailed(command, hr))); + } + if (success) { + startWatchTimer(); setState(InferiorRunning, Q_FUNC_INFO, __LINE__); } else { setState(InferiorStopped, Q_FUNC_INFO, __LINE__); - warning(msgFunctionFailed(Q_FUNC_INFO, msgComFailed("SetExecutionStatus", hr))); } - m_d->m_breakEventMode = CdbDebugEnginePrivate::BreakEventIgnoreOnce; - startWatchTimer(); + return success; +} + +void CdbDebugEngine::stepExec() +{ + step(manager()->isReverseDebugging() ? DEBUG_STATUS_REVERSE_STEP_INTO : DEBUG_STATUS_STEP_INTO); +} + +void CdbDebugEngine::nextExec() +{ + step(manager()->isReverseDebugging() ? DEBUG_STATUS_REVERSE_STEP_OVER : DEBUG_STATUS_STEP_OVER); +} + +void CdbDebugEngine::stepIExec() +{ + stepExec(); // Step into by instruction (figured out by step) +} + +void CdbDebugEngine::nextIExec() +{ + nextExec(); // Step over by instruction (figured out by step) } void CdbDebugEngine::stepOutExec() @@ -1027,37 +1096,6 @@ void CdbDebugEngine::stepOutExec() warning(msgFunctionFailed(Q_FUNC_INFO, errorMessage)); } -void CdbDebugEngine::nextExec() -{ - // Step over - if (debugCDB) - qDebug() << Q_FUNC_INFO; - m_d->clearForRun(); - m_d->setCodeLevel(); // Step by instruction - setState(InferiorRunningRequested, Q_FUNC_INFO, __LINE__); - const HRESULT hr = m_d->m_cif.debugControl->SetExecutionStatus(DEBUG_STATUS_STEP_OVER); - // To control threads, use: - // -- const HRESULT hr = m_d->m_cif.debugControl->Execute(DEBUG_OUTCTL_THIS_CLIENT, "~* p", 0); - if (SUCCEEDED(hr)) { - setState(InferiorRunning, Q_FUNC_INFO, __LINE__); - startWatchTimer(); - } else { - setState(InferiorStopped, Q_FUNC_INFO, __LINE__); - warning(msgFunctionFailed(Q_FUNC_INFO, msgComFailed("SetExecutionStatus", hr))); - } -} - -void CdbDebugEngine::stepIExec() -{ - // Step by instruction - nextExec(); -} - -void CdbDebugEngine::nextIExec() -{ - nextExec(); -} - void CdbDebugEngine::continueInferior() { QString errorMessage; @@ -1603,7 +1641,7 @@ 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_currentThreadId = updateThreadList(); + m_eventThreadId = m_currentThreadId = updateThreadList(); manager()->showDebuggerOutput(LogMisc, CdbDebugEngine::tr("Stopped, current thread: %1").arg(m_currentThreadId)); ThreadsHandler *threadsHandler = manager()->threadsHandler(); const int threadIndex = threadIndexById(threadsHandler, m_currentThreadId); @@ -1701,7 +1739,7 @@ void CdbDebugEnginePrivate::updateStackTrace() if (debugCDB) qDebug() << Q_FUNC_INFO; // Create a new context - clearForRun(); + cleanStackTrace(); QString errorMessage; m_engine->reloadRegisters(); if (!setCDBThreadId(m_currentThreadId, &errorMessage)) { @@ -1727,6 +1765,15 @@ void CdbDebugEnginePrivate::updateStackTrace() current = i; break; } + // Visibly warn the users about missing top frames/all frames, as they otherwise + // might think stepping is broken. + if (!stackFrames.at(0).isUsable()) { + const QString topFunction = count ? stackFrames.at(0).function : QString(); + const QString msg = current >= 0 ? + CdbDebugEngine::tr("Thread %1: Missing debug information for top stack frame (%2).").arg(m_currentThreadId).arg(topFunction) : + CdbDebugEngine::tr("Thread %1: No debug information available (%2).").arg(m_currentThreadId).arg(topFunction); + m_engine->warning(msg); + } manager()->stackHandler()->setFrames(stackFrames); m_firstActivatedFrame = true; diff --git a/src/plugins/debugger/cdb/cdbdebugengine.h b/src/plugins/debugger/cdb/cdbdebugengine.h index 740cbfec27596a21a0f409cac4754568460f0bf5..c96a0205961859e92e546b7c0ab91ef7700f05b1 100644 --- a/src/plugins/debugger/cdb/cdbdebugengine.h +++ b/src/plugins/debugger/cdb/cdbdebugengine.h @@ -124,6 +124,7 @@ private: bool evaluateExpression(const QString &expression, QString *value, QString *type, QString *errorMessage); void evaluateWatcher(WatchData *wd); QString editorToolTip(const QString &exp, const QString &function); + bool step(unsigned long executionStatus); CdbDebugEnginePrivate *m_d; diff --git a/src/plugins/debugger/cdb/cdbdebugengine_p.h b/src/plugins/debugger/cdb/cdbdebugengine_p.h index afbb7ab36cf66f10a7c696a6c9d8ec2ce7645516..f302713872537735514f8b33d66754e4cca9f08d 100644 --- a/src/plugins/debugger/cdb/cdbdebugengine_p.h +++ b/src/plugins/debugger/cdb/cdbdebugengine_p.h @@ -157,6 +157,7 @@ struct CdbDebugEnginePrivate HANDLE m_hDebuggeeProcess; HANDLE m_hDebuggeeThread; int m_currentThreadId; + int m_eventThreadId; HandleBreakEventMode m_breakEventMode; int m_watchTimer; diff --git a/src/plugins/debugger/cdb/cdbstackframecontext.cpp b/src/plugins/debugger/cdb/cdbstackframecontext.cpp index 1b9b18a04f0ae6574dd9c0d5be14453cc2402d2d..76d5e00cf8897130f9691bb37c8ab84a5ba19f5a 100644 --- a/src/plugins/debugger/cdb/cdbstackframecontext.cpp +++ b/src/plugins/debugger/cdb/cdbstackframecontext.cpp @@ -128,50 +128,21 @@ WatchHandleDumperInserter::WatchHandleDumperInserter(WatchHandler *wh, Q_ASSERT(m_hexNullPattern.isValid()); } -// Is this a non-null pointer to a dumpeable item with a value -// "0x4343 class QString *" ? - Insert a fake '*' dereferenced item -// and run dumpers on it. If that succeeds, insert the fake items owned by dumpers, -// which will trigger the ignore predicate. -// Note that the symbol context does not create '*' dereferenced items for -// classes (see note in its header documentation). -bool WatchHandleDumperInserter::expandPointerToDumpable(const WatchData &wd, QString *errorMessage) -{ - if (debugCDBWatchHandling) - qDebug() << ">expandPointerToDumpable" << wd.iname; - bool handled = false; - do { - if (!isPointerType(wd.type)) - break; - const int classPos = wd.value.indexOf(" class "); - if (classPos == -1) - break; - const QString hexAddrS = wd.value.mid(0, classPos); - if (m_hexNullPattern.exactMatch(hexAddrS)) - break; - const QString type = stripPointerType(wd.value.mid(classPos + 7)); - WatchData derefedWd; - derefedWd.setType(type); - derefedWd.setAddress(hexAddrS); - derefedWd.name = QString(QLatin1Char('*')); - derefedWd.iname = wd.iname + QLatin1String(".*"); - derefedWd.source = OwnerDumper | CdbStackFrameContext::ChildrenKnownBit; - const CdbDumperHelper::DumpResult dr = m_dumper->dumpType(derefedWd, true, &m_dumperResult, errorMessage); - if (dr != CdbDumperHelper::DumpOk) - break; - // Insert the pointer item with 1 additional child + its dumper results - // Note: formal arguments might already be expanded in the symbol group. - WatchData ptrWd = wd; - ptrWd.source = OwnerDumper | CdbStackFrameContext::ChildrenKnownBit; - ptrWd.setHasChildren(true); - ptrWd.setChildrenUnneeded(); - m_wh->insertData(ptrWd); - m_wh->insertBulkData(m_dumperResult); - handled = true; - } while (false); - if (debugCDBWatchHandling) - qDebug() << "<expandPointerToDumpable returns " << handled << *errorMessage; - return handled; +// Prevent recursion of the model by setting value and type +static inline void fixDumperValueAndType(WatchData *wd, const WatchData *source = 0) +{ + static const QString unknown = QCoreApplication::translate("CdbStackFrameContext", "<Unknown>"); + if (wd->isTypeNeeded() || wd->type.isEmpty()) { + wd->setType(source ? source->type : unknown); + } + if (wd->isValueNeeded()) { + if (source && source->isValueKnown()) { + wd->setValue(source->value); + } else { + wd->setValue(unknown); + } + } } // When querying an item, the queried item is sometimes returned in incomplete form. @@ -180,6 +151,7 @@ static inline void fixDumperResult(const WatchData &source, QList<WatchData> *result, bool suppressGrandChildren) { + const int size = result->size(); if (!size) return; @@ -188,16 +160,7 @@ static inline void fixDumperResult(const WatchData &source, WatchData &returned = result->front(); if (returned.iname != source.iname) return; - if (returned.type.isEmpty()) - returned.setType(source.type); - if (returned.isValueNeeded()) { - if (source.isValueKnown()) { - returned.setValue(source.value); - } else { - // Should not happen - returned.setValue(QCoreApplication::translate("CdbStackFrameContext", "<Unknown>")); - } - } + fixDumperValueAndType(&returned, &source); // Indicate owner and known children returned.source = OwnerDumper; if (returned.isChildrenKnown() && returned.isHasChildrenKnown() && returned.hasChildren) @@ -225,11 +188,60 @@ static inline void fixDumperResult(const WatchData &source, if (suppressGrandChildren && (wd.isChildrenNeeded() || wd.isHasChildrenNeeded())) wd.setHasChildren(false); } + // <Out of scope value> have sometimes missing types. Kill recursion + fixDumperValueAndType(&wd); } if (debugCDBWatchHandling) debugWatchDataList(*result, "<fixDumperResult"); } +// Is this a non-null pointer to a dumpeable item with a value +// "0x4343 class QString *" ? - Insert a fake '*' dereferenced item +// and run dumpers on it. If that succeeds, insert the fake items owned by dumpers, +// which will trigger the ignore predicate. +// Note that the symbol context does not create '*' dereferenced items for +// classes (see note in its header documentation). +bool WatchHandleDumperInserter::expandPointerToDumpable(const WatchData &wd, QString *errorMessage) +{ + if (debugCDBWatchHandling) + qDebug() << ">expandPointerToDumpable" << wd.toString(); + + bool handled = false; + do { + if (!isPointerType(wd.type)) + break; + const int classPos = wd.value.indexOf(" class "); + if (classPos == -1) + break; + const QString hexAddrS = wd.value.mid(0, classPos); + if (m_hexNullPattern.exactMatch(hexAddrS)) + break; + const QString type = stripPointerType(wd.value.mid(classPos + 7)); + WatchData derefedWd; + derefedWd.setType(type); + derefedWd.setAddress(hexAddrS); + derefedWd.name = QString(QLatin1Char('*')); + derefedWd.iname = wd.iname + QLatin1String(".*"); + derefedWd.source = OwnerDumper | CdbStackFrameContext::ChildrenKnownBit; + const CdbDumperHelper::DumpResult dr = m_dumper->dumpType(derefedWd, true, &m_dumperResult, errorMessage); + if (dr != CdbDumperHelper::DumpOk) + break; + fixDumperResult(derefedWd, &m_dumperResult, true); + // Insert the pointer item with 1 additional child + its dumper results + // Note: formal arguments might already be expanded in the symbol group. + WatchData ptrWd = wd; + ptrWd.source = OwnerDumper | CdbStackFrameContext::ChildrenKnownBit; + ptrWd.setHasChildren(true); + ptrWd.setChildrenUnneeded(); + m_wh->insertData(ptrWd); + m_wh->insertBulkData(m_dumperResult); + handled = true; + } while (false); + if (debugCDBWatchHandling) + qDebug() << "<expandPointerToDumpable returns " << handled << *errorMessage; + return handled; +} + WatchHandleDumperInserter &WatchHandleDumperInserter::operator=(WatchData &wd) { if (debugCDBWatchHandling) diff --git a/src/plugins/debugger/watchhandler.cpp b/src/plugins/debugger/watchhandler.cpp index 8f6bba03ac1e8ca28516d28b71faa4a488d44d0e..cc1c09cff63dfeceecb13faea8c85a036adb51bd 100644 --- a/src/plugins/debugger/watchhandler.cpp +++ b/src/plugins/debugger/watchhandler.cpp @@ -257,6 +257,8 @@ QString WatchData::toString() const if (isChildrenNeeded()) str << "children=<needed>,"; + if (source) + str << "source=" << source; str.flush(); if (res.endsWith(QLatin1Char(','))) res.truncate(res.size() - 1);