Commit 3d55c45e authored by Friedemann Kleint's avatar Friedemann Kleint
Browse files

Continued CDB breakpoint handling.

Enable temporarily stopping for setting breakpoints. Fix terminating the
debuggee in exitDebugger().
parent 472e2502
......@@ -243,6 +243,14 @@ int BreakHandler::rowCount(const QModelIndex &parent) const
return parent.isValid() ? 0 : size();
}
bool BreakHandler::hasPendingBreakpoints() const
{
for (int i = size() - 1; i >= 0; i--)
if (at(i)->pending)
return true;
return false;
}
void BreakHandler::removeAt(int index)
{
BreakpointData *data = at(index);
......
......@@ -120,6 +120,7 @@ public:
BreakpointData *at(int index) const { return index < size() ? m_bp.at(index) : 0; }
int size() const { return m_bp.size(); }
bool hasPendingBreakpoints() const;
void append(BreakpointData *data) { m_bp.append(data); }
void removeAt(int index); // also deletes the marker
void clear(); // also deletes all the marker
......
......@@ -99,10 +99,16 @@ void CDBBreakPoint::clearExpressionData()
QDebug operator<<(QDebug dbg, const CDBBreakPoint &bp)
{
dbg.nospace() << "fileName='" << bp.fileName << "' condition='"
<< bp.condition << "' ignoreCount=" << bp.ignoreCount
<< " lineNumber=" << bp.lineNumber
<< " funcName='" << bp.funcName << '\'';
QDebug nsp = dbg.nospace();
if (!bp.fileName.isEmpty()) {
nsp << "fileName='" << bp.fileName << ':' << bp.lineNumber << '\'';
} else {
nsp << "funcName='" << bp.funcName << '\'';
}
if (!bp.condition.isEmpty())
nsp << " condition='" << bp.condition << '\'';
if (bp.ignoreCount)
nsp << " ignoreCount=" << bp.ignoreCount;
return dbg;
}
......@@ -238,21 +244,28 @@ bool CDBBreakPoint::parseExpression(const QString &expr)
return true;
}
bool CDBBreakPoint::getBreakPointCount(IDebugControl4* debugControl, ULONG *count, QString *errorMessage /* = 0*/)
{
const HRESULT hr = debugControl->GetNumberBreakpoints(count);
if (FAILED(hr)) {
if (errorMessage)
*errorMessage = QString::fromLatin1("Cannot determine breakpoint count: %1").
arg(msgComFailed("GetNumberBreakpoints", hr));
return false;
}
return true;
}
bool CDBBreakPoint::getBreakPoints(IDebugControl4* debugControl, QList<CDBBreakPoint> *bps, QString *errorMessage)
{
ULONG count = 0;
bps->clear();
// get number
HRESULT hr = debugControl->GetNumberBreakpoints(&count);
if (FAILED(hr)) {
*errorMessage = QString::fromLatin1("Cannot retrieve breakpoints: %1").
arg(msgComFailed("GetNumberBreakpoints", hr));
if (!getBreakPointCount(debugControl, &count, errorMessage))
return false;
}
// retrieve one by one and parse
for (ULONG b= 0; b < count; b++) {
IDebugBreakpoint2 *ibp = 0;
hr = debugControl->GetBreakpointByIndex2(b, &ibp);
const HRESULT hr = debugControl->GetBreakpointByIndex2(b, &ibp);
if (FAILED(hr)) {
*errorMessage = QString::fromLatin1("Cannot retrieve breakpoint %1: %2").
arg(b).arg(msgComFailed("GetBreakpointByIndex2", hr));
......@@ -271,66 +284,68 @@ bool CDBBreakPoint::synchronizeBreakPoints(IDebugControl4* debugControl,
BreakHandler *handler,
QString *errorMessage)
{
typedef QMap<CDBBreakPoint, bool> BreakPointPendingMap;
BreakPointPendingMap breakPointPendingMap;
// convert BreakHandler's bps into a map of BreakPoint->Pending
typedef QMap<CDBBreakPoint, int> BreakPointIndexMap;
BreakPointIndexMap breakPointIndexMap;
// convert BreakHandler's bps into a map of BreakPoint->BreakHandler->Index
if (debugCDB)
qDebug() << Q_FUNC_INFO;
const int handlerCount = handler->size();
for (int i=0; i < handlerCount; ++i) {
BreakpointData* breakpoint = handler->at(i);
const bool pending = breakpoint->pending;
breakPointPendingMap.insert(CDBBreakPoint(*breakpoint), pending);
if (pending)
breakpoint->pending = false;
}
ULONG engineCount;
for (int i=0; i < handlerCount; ++i)
breakPointIndexMap.insert(CDBBreakPoint(*handler->at(i)), i);
// get number of engine breakpoints
HRESULT hr = debugControl->GetNumberBreakpoints(&engineCount);
if (FAILED(hr)) {
*errorMessage = QString::fromLatin1("Cannot retrieve number of breakpoints: %1").
arg(msgComFailed("GetNumberBreakpoints", hr));
ULONG engineCount;
if (!getBreakPointCount(debugControl, &engineCount, errorMessage))
return false;
}
// Starting from end, check if engine breakpoints are still in handler.
// If not->remove
if (engineCount) {
for (ULONG eb = engineCount - 1u; ; eb--) {
// get engine breakpoint
// get engine breakpoint.
IDebugBreakpoint2 *ibp = 0;
hr = debugControl->GetBreakpointByIndex2(eb, &ibp);
HRESULT hr = debugControl->GetBreakpointByIndex2(eb, &ibp);
if (FAILED(hr)) {
*errorMessage = QString::fromLatin1("Cannot retrieve breakpoint %1: %2").
arg(eb).arg(msgComFailed("GetBreakpointByIndex2", hr));
return false;
}
CDBBreakPoint engineBreakPoint;
if (!engineBreakPoint.retrieve(ibp, errorMessage))
return false;
// Still in handler?
if (!breakPointPendingMap.contains(engineBreakPoint)) {
if (debugCDB)
qDebug() << " Removing" << engineBreakPoint;
hr = debugControl->RemoveBreakpoint2(ibp);
if (FAILED(hr)) {
*errorMessage = QString::fromLatin1("Cannot remove breakpoint %1: %2").
arg(engineBreakPoint.expression(), msgComFailed("RemoveBreakpoint2", hr));
// Ignore one shot break points set by "Step out"
ULONG flags = 0;
hr = ibp->GetFlags(&flags);
if (!(flags & DEBUG_BREAKPOINT_ONE_SHOT)) {
CDBBreakPoint engineBreakPoint;
if (!engineBreakPoint.retrieve(ibp, errorMessage))
return false;
}
} // not in handler
// Still in handler?
if (!breakPointIndexMap.contains(engineBreakPoint)) {
if (debugCDB)
qDebug() << " Removing" << engineBreakPoint;
hr = debugControl->RemoveBreakpoint2(ibp);
if (FAILED(hr)) {
*errorMessage = QString::fromLatin1("Cannot remove breakpoint %1: %2").
arg(engineBreakPoint.expression(), msgComFailed("RemoveBreakpoint2", hr));
return false;
}
} // not in handler
} // one shot
if (!eb)
break;
}
}
// Add pending breakpoints
const BreakPointPendingMap::const_iterator pcend = breakPointPendingMap.constEnd();
for (BreakPointPendingMap::const_iterator it = breakPointPendingMap.constBegin(); it != pcend; ++it) {
if (it.value()) {
const BreakPointIndexMap::const_iterator pcend = breakPointIndexMap.constEnd();
for (BreakPointIndexMap::const_iterator it = breakPointIndexMap.constBegin(); it != pcend; ++it) {
const int index = it.value();
if (handler->at(index)->pending) {
if (debugCDB)
qDebug() << " Adding " << it.key();
if (!it.key().add(debugControl, errorMessage))
return false;
if (it.key().add(debugControl, errorMessage)) {
handler->at(index)->pending = false;
} else {
const QString msg = QString::fromLatin1("Failed to add breakpoint '%1': %2").arg(it.key().expression(), *errorMessage);
qWarning("%s\n", qPrintable(msg));
}
}
}
if (debugCDB > 1) {
......
......@@ -69,6 +69,7 @@ struct CDBBreakPoint {
bool retrieve(IDebugBreakpoint2 *ibp, QString *errorMessage);
bool parseExpression(const QString &expr);
// Retrieve all breakpoints from the engine
static bool getBreakPointCount(IDebugControl4* debugControl, ULONG *count, QString *errorMessage = 0);
static bool getBreakPoints(IDebugControl4* debugControl, QList<CDBBreakPoint> *bps, QString *errorMessage);
// Synchronize (halted) engine with BreakHandler.
static bool synchronizeBreakPoints(IDebugControl4* ctl, BreakHandler *bh, QString *errorMessage);
......
......@@ -65,6 +65,8 @@ namespace Internal {
typedef QList<WatchData> WatchList;
// ----- Message helpers
QString msgDebugEngineComResult(HRESULT hr)
{
switch (hr) {
......@@ -104,6 +106,28 @@ QString msgComFailed(const char *func, HRESULT hr)
static const char *msgNoStackTraceC = "Internal error: no stack trace present.";
// ----- Engine helpers
static inline ULONG getInterruptTimeOutSecs(IDebugControl4 *ctl)
{
ULONG rc = 0;
ctl->GetInterruptTimeout(&rc);
return rc;
}
static inline bool getExecutionStatus(IDebugControl4 *ctl,
ULONG *executionStatus,
QString *errorMessage)
{
const HRESULT hr = ctl->GetExecutionStatus(executionStatus);
if (FAILED(hr)) {
*errorMessage = msgComFailed("GetExecutionStatus", hr);
return false;
}
return true;
}
// --------- DebuggerEngineLibrary
DebuggerEngineLibrary::DebuggerEngineLibrary() :
m_debugCreate(0)
{
......@@ -163,7 +187,7 @@ SyntaxSetter::~SyntaxSetter()
CdbDebugEnginePrivate::CdbDebugEnginePrivate(DebuggerManager *parent, CdbDebugEngine* engine) :
m_hDebuggeeProcess(0),
m_hDebuggeeThread(0),
m_bIgnoreNextDebugEvent(false),
m_breakEventMode(BreakEventHandle),
m_watchTimer(-1),
m_debugEventCallBack(engine),
m_debugOutputCallBack(engine),
......@@ -224,7 +248,8 @@ bool CdbDebugEnginePrivate::init(QString *errorMessage)
*errorMessage = QString::fromLatin1("Creation of IDebugRegisters2 failed: %1").arg(msgDebugEngineComResult(hr));
return false;
}
if (debugCDB)
qDebug() << QString::fromLatin1("CDB Initialization succeeded, interrupt time out %1s.").arg(getInterruptTimeOutSecs(m_pDebugControl));
return true;
}
......@@ -257,11 +282,18 @@ CdbDebugEnginePrivate::~CdbDebugEnginePrivate()
m_pDebugRegisters->Release();
}
void CdbDebugEnginePrivate::cleanStackTrace()
void CdbDebugEnginePrivate::clearForRun()
{
if (debugCDB)
qDebug() << Q_FUNC_INFO;
m_breakEventMode = BreakEventHandle;
m_firstActivatedFrame = false;
cleanStackTrace();
}
void CdbDebugEnginePrivate::cleanStackTrace()
{
if (m_currentStackTrace) {
delete m_currentStackTrace;
m_currentStackTrace = 0;
......@@ -317,7 +349,7 @@ bool CdbDebugEngine::startDebugger()
m_d->m_debuggerManager->showStatusMessage("Starting Debugger", -1);
QString errorMessage;
bool rc = false;
m_d->m_bIgnoreNextDebugEvent = false;
m_d->clearForRun();
const DebuggerStartMode mode = m_d->m_debuggerManager->startMode();
switch (mode) {
case AttachExternal:
......@@ -417,7 +449,7 @@ void CdbDebugEngine::processTerminated(unsigned long exitCode)
if (debugCDB)
qDebug() << Q_FUNC_INFO << exitCode;
m_d->cleanStackTrace();
m_d->clearForRun();
m_d->setDebuggeeHandles(0, 0);
m_d->m_debuggerManagerAccess->notifyInferiorExited();
m_d->m_debuggerManager->exitDebugger();
......@@ -429,23 +461,40 @@ void CdbDebugEngine::exitDebugger()
qDebug() << Q_FUNC_INFO;
if (m_d->m_hDebuggeeProcess) {
m_d->cleanStackTrace();
QString errorMessage;
m_d->clearForRun();
bool wasRunning = false;
// Terminate or detach if we are running
HRESULT hr;
switch (m_d->m_mode) {
case AttachExternal:
if (m_d->isDebuggeeRunning()) { // Process must be stopped in order to detach
DebugBreakProcess(m_d->m_hDebuggeeProcess);
wasRunning = m_d->isDebuggeeRunning();
if (wasRunning) { // Process must be stopped in order to detach
m_d->interruptInterferiorProcess(&errorMessage);
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
hr = m_d->m_pDebugClient->DetachCurrentProcess();
if (FAILED(hr))
errorMessage += msgComFailed("DetachCurrentProcess", hr);
if (debugCDB)
qDebug() << Q_FUNC_INFO << "detached" << msgDebugEngineComResult(hr);
break;
case StartExternal:
case StartInternal:
// Terminate and waitr for stop events.
wasRunning = m_d->isDebuggeeRunning();
if (wasRunning) { // Process must be stopped in order to terminate
m_d->interruptInterferiorProcess(&errorMessage);
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
// Terminate and wait for stop events.
hr = m_d->m_pDebugClient->TerminateCurrentProcess();
if (FAILED(hr))
errorMessage += msgComFailed("TerminateCurrentProcess", hr);
if (!wasRunning) {
hr = m_d->m_pDebugClient->TerminateProcesses();
if (FAILED(hr))
errorMessage += msgComFailed("TerminateProcesses", hr);
}
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
if (debugCDB)
qDebug() << Q_FUNC_INFO << "terminated" << msgDebugEngineComResult(hr);
......@@ -455,8 +504,9 @@ void CdbDebugEngine::exitDebugger()
break;
}
m_d->setDebuggeeHandles(0, 0);
if (!errorMessage.isEmpty())
qWarning("exitDebugger: %s\n", qPrintable(errorMessage));
}
killWatchTimer();
}
......@@ -601,10 +651,11 @@ void CdbDebugEngine::stepExec()
if (debugCDB)
qDebug() << Q_FUNC_INFO;
m_d->cleanStackTrace();
m_d->clearForRun();
const HRESULT hr = m_d->m_pDebugControl->SetExecutionStatus(DEBUG_STATUS_STEP_INTO);
Q_UNUSED(hr)
m_d->m_bIgnoreNextDebugEvent = true;
m_d->m_breakEventMode = CdbDebugEnginePrivate::BreakEventIgnoreOnce;
startWatchTimer();
}
......@@ -621,34 +672,33 @@ void CdbDebugEngine::stepOutExec()
return;
}
// Set a temporary breakpoint and continue
const StackFrame& frame = stackframes.at(idx);
bool ok;
ULONG64 address = frame.address.toULongLong(&ok, 16);
if (!ok) {
qWarning("stepOutExec: cannot obtain address from stack frame");
return;
}
IDebugBreakpoint2* pBP;
HRESULT hr = m_d->m_pDebugControl->AddBreakpoint2(DEBUG_BREAKPOINT_CODE, DEBUG_ANY_ID, &pBP);
if (FAILED(hr) || !pBP) {
qWarning("stepOutExec: cannot create temporary breakpoint");
return;
}
pBP->SetOffset(address);
bool success = false;
QString errorMessage;
do {
const ULONG64 address = frame.address.toULongLong(&success, 16);
if (!success) {
errorMessage = QLatin1String("Cannot obtain address from stack frame");
break;
}
//QString str = '`' + frame.file + ':' + frame.line + '`';
//hr = pBP->SetOffsetExpressionWide(str.utf16());
//if (FAILED(hr)) {
// qWarning("SetOffsetExpressionWide failed");
// return;
//}
IDebugBreakpoint2* pBP;
HRESULT hr = m_d->m_pDebugControl->AddBreakpoint2(DEBUG_BREAKPOINT_CODE, DEBUG_ANY_ID, &pBP);
if (FAILED(hr) || !pBP) {
errorMessage = QString::fromLatin1("Cannot create temporary breakpoint: %1").arg(msgDebugEngineComResult(hr));
break;
}
pBP->AddFlags(DEBUG_BREAKPOINT_ENABLED);
pBP->AddFlags(DEBUG_BREAKPOINT_ONE_SHOT);
//hr = m_pDebugControl->SetExecutionStatus(DEBUG_STATUS_GO);
continueInferior();
pBP->SetOffset(address);
pBP->AddFlags(DEBUG_BREAKPOINT_ENABLED);
pBP->AddFlags(DEBUG_BREAKPOINT_ONE_SHOT);
if (!m_d->continueInferior(&errorMessage))
break;
success = true;
} while (false);
if (!success)
qWarning("stepOutExec: %s\n", qPrintable(errorMessage));
}
void CdbDebugEngine::nextExec()
......@@ -656,7 +706,7 @@ void CdbDebugEngine::nextExec()
if (debugCDB)
qDebug() << Q_FUNC_INFO;
m_d->cleanStackTrace();
m_d->clearForRun();
const HRESULT hr = m_d->m_pDebugControl->SetExecutionStatus(DEBUG_STATUS_STEP_OVER);
if (SUCCEEDED(hr)) {
startWatchTimer();
......@@ -675,7 +725,7 @@ void CdbDebugEngine::nextIExec()
if (debugCDB)
qDebug() << Q_FUNC_INFO;
m_d->cleanStackTrace();
m_d->clearForRun();
const HRESULT hr = m_d->m_pDebugControl->Execute(DEBUG_OUTCTL_THIS_CLIENT, "p", 0);
if (SUCCEEDED(hr)) {
startWatchTimer();
......@@ -685,40 +735,87 @@ void CdbDebugEngine::nextIExec()
}
void CdbDebugEngine::continueInferior()
{
QString errorMessage;
if (!m_d->continueInferior(&errorMessage))
qWarning("continueInferior: %s\n", qPrintable(errorMessage));
}
// Continue process without notifications
bool CdbDebugEnginePrivate::continueInferiorProcess(QString *errorMessage)
{
if (debugCDB)
qDebug() << Q_FUNC_INFO;
const HRESULT hr = m_pDebugControl->SetExecutionStatus(DEBUG_STATUS_GO);
if (FAILED(hr)) {
*errorMessage = msgComFailed("SetExecutionStatus", hr);
return false;
}
return true;
}
m_d->cleanStackTrace();
killWatchTimer();
m_d->m_debuggerManager->resetLocation();
// Continue process with notifications
bool CdbDebugEnginePrivate::continueInferior(QString *errorMessage)
{
ULONG executionStatus;
m_d->m_debuggerManagerAccess->notifyInferiorRunningRequested();
HRESULT hr = m_d->m_pDebugControl->GetExecutionStatus(&executionStatus);
if (SUCCEEDED(hr) && executionStatus != DEBUG_STATUS_GO) {
hr = m_d->m_pDebugControl->SetExecutionStatus(DEBUG_STATUS_GO);
if (SUCCEEDED(hr)) {
startWatchTimer();
m_d->m_debuggerManagerAccess->notifyInferiorRunning();
} else {
qWarning("%s failed: %s", Q_FUNC_INFO, qPrintable(msgDebugEngineComResult(hr)));
}
if (!getExecutionStatus(m_pDebugControl, &executionStatus, errorMessage))
return false;
if (debugCDB)
qDebug() << Q_FUNC_INFO << "\n ex=" << executionStatus;
if (executionStatus == DEBUG_STATUS_GO) {
qWarning("continueInferior() called while debuggee is running.");
return true;
}
clearForRun();
m_engine->killWatchTimer();
m_debuggerManager->resetLocation();
m_debuggerManagerAccess->notifyInferiorRunningRequested();
if (!continueInferiorProcess(errorMessage))
return false;
m_engine->startWatchTimer();
m_debuggerManagerAccess->notifyInferiorRunning();
return true;
}
void CdbDebugEngine::interruptInferior()
bool CdbDebugEnginePrivate::interruptInterferiorProcess(QString *errorMessage)
{
if (debugCDB)
qDebug() << Q_FUNC_INFO << m_d->m_hDebuggeeProcess;
// Interrupt the interferior process without notifications
if (debugCDB) {
ULONG executionStatus;
getExecutionStatus(m_pDebugControl, &executionStatus, errorMessage);
qDebug() << Q_FUNC_INFO << "\n ex=" << executionStatus;
}
if (!DebugBreakProcess(m_hDebuggeeProcess)) {
*errorMessage = QString::fromLatin1("DebugBreakProcess failed: %1").arg(Core::Utils::winErrorMessage(GetLastError()));
return false;
}
#if 0
const HRESULT hr = m_pDebugControl->SetInterrupt(DEBUG_INTERRUPT_ACTIVE|DEBUG_INTERRUPT_EXIT);
if (FAILED(hr)) {
*errorMessage = QString::fromLatin1("Unable to interrupt debuggee after %1s: %2").
arg(getInterruptTimeOutSecs(m_pDebugControl)).arg(msgComFailed("SetInterrupt", hr));
return false;
}
#endif
return true;
}
//TODO: better use IDebugControl::SetInterrupt?
if (!m_d->m_hDebuggeeProcess)
void CdbDebugEngine::interruptInferior()
{
if (!m_d->m_hDebuggeeProcess || !m_d->isDebuggeeRunning())
return;
if (DebugBreakProcess(m_d->m_hDebuggeeProcess)) {
QString errorMessage;
if (m_d->interruptInterferiorProcess(&errorMessage)) {
m_d->m_debuggerManagerAccess->notifyInferiorStopped();
} else {
qWarning("DebugBreakProcess failed: %s", Core::Utils::winErrorMessage(GetLastError()));
qWarning("interruptInferior: %s\n", qPrintable(errorMessage));
}
}
......@@ -840,16 +937,21 @@ void CdbDebugEngine::activateFrame(int frameIndex)
if (oldIndex != frameIndex)
stackHandler->setCurrentIndex(frameIndex);
if (oldIndex != frameIndex || m_d->m_firstActivatedFrame)
if (!m_d->updateLocals(frameIndex, watchHandler, &errorMessage))
break;
const StackFrame &frame = stackHandler->currentFrame();
if (!frame.isUsable()) {
// Clean out model
watchHandler->reinitializeWatchers();
watchHandler->rebuildModel();
errorMessage = QString::fromLatin1("%1: file %2 unusable.").
arg(QLatin1String(Q_FUNC_INFO), frame.file);
break;
}
if (oldIndex != frameIndex || m_d->m_firstActivatedFrame)
if (!m_d->updateLocals(frameIndex, watchHandler, &errorMessage))
break;
m_d->m_debuggerManager->gotoLocation(frame.file, frame.line, true);
success =true;
} while (false);
......@@ -874,19 +976,42 @@ void CdbDebugEngine::selectThread(int index)
void CdbDebugEngine::attemptBreakpointSynchronization()
{
if (debugCDB)
qDebug() << Q_FUNC_INFO;
QString errorMessage;
if (!m_d->attemptBreakpointSynchronization(&errorMessage))
qWarning("attemptBreakpointSynchronization: %s\n", qPrintable(errorMessage));
}
if (!m_d->m_hDebuggeeProcess) {
qWarning("attemptBreakpointSynchronization() called while debugger is not running");
return;
bool CdbDebugEnginePrivate::attemptBreakpointSynchronization(QString *errorMessage)
{
if (!m_hDebuggeeProcess) {
*errorMessage = QLatin1String("attemptBreakpointSynchronization() called while debugger is not running");
return false;
}
// This is called from
// 1) CreateProcessEvent with the halted engine
// 2) from the break handler, potentially while the debuggee is running
// If the debuggee is running (for which the execution status is
// no reliable indicator), we temporarily halt and have ourselves
// called again from the debug event handler.
ULONG dummy;
const bool wasRunning = !CDBBreakPoint::getBreakPointCount(m_pDebugControl, &dummy);
if (debugCDB)
qDebug() << Q_FUNC_INFO << "\n Running=" << wasRunning;