Debugger: Implement conditional breakpoints.

ThreadHandler:
- When a breakpoint event is hit, we now check if there is an
  associated UserBreakpoint with a condition attached. If so,
  we schedule an evaluation request, and only stop the thread if
  that one evaluates to true (or if evaluation fails in some way).

This implements #9713.
This commit is contained in:
Rene Gollent 2014-10-30 16:49:16 -04:00
parent 43060a58fa
commit a5ce4678c0
2 changed files with 178 additions and 1 deletions

View File

@ -11,7 +11,9 @@
#include <new> #include <new>
#include <AutoDeleter.h>
#include <AutoLocker.h> #include <AutoLocker.h>
#include <Variant.h>
#include "Architecture.h" #include "Architecture.h"
#include "BreakpointManager.h" #include "BreakpointManager.h"
@ -24,11 +26,13 @@
#include "MessageCodes.h" #include "MessageCodes.h"
#include "Register.h" #include "Register.h"
#include "SourceCode.h" #include "SourceCode.h"
#include "SourceLanguage.h"
#include "SpecificImageDebugInfo.h" #include "SpecificImageDebugInfo.h"
#include "StackTrace.h" #include "StackTrace.h"
#include "Statement.h" #include "Statement.h"
#include "Team.h" #include "Team.h"
#include "Tracing.h" #include "Tracing.h"
#include "Value.h"
#include "Worker.h" #include "Worker.h"
@ -42,6 +46,43 @@ enum {
}; };
class ExpressionJobListener : public JobListener {
public:
ExpressionJobListener(ThreadHandler* handler)
:
JobListener(),
fHandler(handler)
{
fHandler->AcquireReference();
}
~ExpressionJobListener()
{
fHandler->ReleaseReference();
}
virtual void JobDone(Job* job)
{
Value* resultValue = dynamic_cast<ExpressionEvaluationJob*>(job)
->GetResultValue();
fHandler->_HandleBreakpointConditionEvaluated(resultValue);
}
virtual void JobFailed(Job* job)
{
fHandler->_HandleBreakpointConditionEvaluated(NULL);
}
virtual void JobAborted(Job* job)
{
fHandler->_HandleBreakpointConditionEvaluated(NULL);
}
private:
ThreadHandler* fHandler;
};
ThreadHandler::ThreadHandler(Thread* thread, Worker* worker, ThreadHandler::ThreadHandler(Thread* thread, Worker* worker,
DebuggerInterface* debuggerInterface, DebuggerInterface* debuggerInterface,
ImageDebugInfoJobListener* listener, ImageDebugInfoJobListener* listener,
@ -58,7 +99,10 @@ ThreadHandler::ThreadHandler(Thread* thread, Worker* worker,
fSteppedOverFunctionAddress(0), fSteppedOverFunctionAddress(0),
fPreviousInstructionPointer(0), fPreviousInstructionPointer(0),
fPreviousFrameAddress(0), fPreviousFrameAddress(0),
fSingleStepping(false) fSingleStepping(false),
fHasPendingConditionEvaluation(false),
fConditionWaitSem(-1),
fConditionResult(NULL)
{ {
fDebuggerInterface->AcquireReference(); fDebuggerInterface->AcquireReference();
} }
@ -68,6 +112,9 @@ ThreadHandler::~ThreadHandler()
{ {
_ClearContinuationState(); _ClearContinuationState();
fDebuggerInterface->ReleaseReference(); fDebuggerInterface->ReleaseReference();
if (fConditionWaitSem > 0)
delete_sem(fConditionWaitSem);
} }
@ -76,6 +123,7 @@ ThreadHandler::Init()
{ {
fWorker->ScheduleJob(new(std::nothrow) GetThreadStateJob(fDebuggerInterface, fWorker->ScheduleJob(new(std::nothrow) GetThreadStateJob(fDebuggerInterface,
fThread)); fThread));
fConditionWaitSem = create_sem(0, "breakpoint condition waiter");
} }
@ -164,6 +212,12 @@ ThreadHandler::HandleBreakpointHit(BreakpointHitEvent* event)
} }
return false; return false;
} else {
locker.Unlock();
if (_HandleBreakpointConditionIfNeeded(cpuState))
return true;
locker.Lock();
} }
} }
@ -387,6 +441,13 @@ ThreadHandler::HandleCpuStateChanged()
void void
ThreadHandler::HandleStackTraceChanged() ThreadHandler::HandleStackTraceChanged()
{ {
AutoLocker< ::Team> teamLocker(fThread->GetTeam());
if (fHasPendingConditionEvaluation && fThread->GetStackTrace() != NULL) {
fHasPendingConditionEvaluation = false;
teamLocker.Unlock();
_HandleBreakpointConditionIfNeeded(fThread->GetCpuState());
}
} }
@ -788,6 +849,111 @@ ThreadHandler::_HandleSingleStepStep(CpuState* cpuState)
} }
bool
ThreadHandler::_HandleBreakpointConditionIfNeeded(CpuState* cpuState)
{
AutoLocker< ::Team> teamLocker(fThread->GetTeam());
Breakpoint* breakpoint = fThread->GetTeam()->BreakpointAtAddress(
cpuState->InstructionPointer());
if (breakpoint == NULL)
return false;
if (!breakpoint->HasEnabledUserBreakpoint())
return false;
const UserBreakpointInstanceList& breakpoints
= breakpoint->UserBreakpoints();
for (UserBreakpointInstanceList::ConstIterator it
= breakpoints.GetIterator(); it.HasNext();) {
UserBreakpoint* userBreakpoint = it.Next()->GetUserBreakpoint();
if (!userBreakpoint->IsValid())
continue;
if (!userBreakpoint->IsEnabled())
continue;
if (!userBreakpoint->HasCondition())
continue;
StackTrace* stackTrace = fThread->GetStackTrace();
if (stackTrace == NULL) {
fThread->SetCpuState(cpuState);
fHasPendingConditionEvaluation = true;
return true;
}
StackFrame* frame = stackTrace->FrameAt(0);
FunctionDebugInfo* info = frame->Function()->GetFunctionDebugInfo();
if (info == NULL)
return false;
SpecificImageDebugInfo* specificInfo
= info->GetSpecificImageDebugInfo();
if (specificInfo == NULL)
return false;
SourceLanguage* language;
if (specificInfo->GetSourceLanguage(info, language) != B_OK)
return false;
BReference<SourceLanguage> reference(language, true);
ExpressionJobListener* listener
= new(std::nothrow) ExpressionJobListener(this);
if (listener == NULL)
return false;
status_t error = fWorker->ScheduleJob(
new(std::nothrow) ExpressionEvaluationJob(fThread->GetTeam(),
fDebuggerInterface, language, userBreakpoint->Condition(),
B_UINT64_TYPE, frame, fThread), listener);
BPrivate::ObjectDeleter<ExpressionJobListener> deleter(listener);
if (error == B_OK) {
teamLocker.Unlock();
do {
error = acquire_sem(fConditionWaitSem);
} while (error == B_INTERRUPTED);
teamLocker.Lock();
bool stop = false;
if (fConditionResult == NULL)
stop = true;
else {
BVariant value;
if (!fConditionResult->ToVariant(value))
stop = true;
if (!value.TypeIsInteger(value.Type()))
stop = true;
stop = value.ToBool();
fConditionResult->ReleaseReference();
fConditionResult = NULL;
}
if (stop)
return false;
else {
fDebuggerInterface->ContinueThread(fThread->ID());
return true;
}
}
}
return false;
}
void
ThreadHandler::_HandleBreakpointConditionEvaluated(Value* value)
{
fConditionResult = value;
if (fConditionResult != NULL)
fConditionResult->AcquireReference();
release_sem(fConditionWaitSem);
}
bool bool
ThreadHandler::_HasExitedFrame(target_addr_t framePointer) const ThreadHandler::_HasExitedFrame(target_addr_t framePointer) const
{ {

View File

@ -21,6 +21,7 @@ class DebuggerInterface;
class ImageDebugInfoJobListener; class ImageDebugInfoJobListener;
class StackFrame; class StackFrame;
class Statement; class Statement;
class Value;
class Worker; class Worker;
@ -64,6 +65,9 @@ public:
void HandleCpuStateChanged(); void HandleCpuStateChanged();
void HandleStackTraceChanged(); void HandleStackTraceChanged();
private:
friend class ExpressionJobListener;
private: private:
// ImageDebugInfoProvider // ImageDebugInfoProvider
virtual status_t GetImageDebugInfo(Image* image, virtual status_t GetImageDebugInfo(Image* image,
@ -95,6 +99,10 @@ private:
void _SingleStepThread( void _SingleStepThread(
target_addr_t instructionPointer); target_addr_t instructionPointer);
bool _HandleBreakpointConditionIfNeeded(
CpuState* cpuState);
void _HandleBreakpointConditionEvaluated(
Value* value);
bool _HandleBreakpointHitStep(CpuState* cpuState); bool _HandleBreakpointHitStep(CpuState* cpuState);
bool _HandleSingleStepStep(CpuState* cpuState); bool _HandleSingleStepStep(CpuState* cpuState);
@ -115,6 +123,9 @@ private:
target_addr_t fPreviousInstructionPointer; target_addr_t fPreviousInstructionPointer;
target_addr_t fPreviousFrameAddress; target_addr_t fPreviousFrameAddress;
bool fSingleStepping; bool fSingleStepping;
bool fHasPendingConditionEvaluation;
sem_id fConditionWaitSem;
Value* fConditionResult;
public: public:
ThreadHandler* fNext; ThreadHandler* fNext;