- When evaluating a breakpoint condition, there's no need to change the thread state unless the condition is actually met. This would lead to lots of unnecessary state switching and associated overhead in the GUI.
972 lines
25 KiB
C++
972 lines
25 KiB
C++
/*
|
|
* Copyright 2009-2012, Ingo Weinhold, [email protected].
|
|
* Copyright 2010-2014, Rene Gollent, [email protected].
|
|
* Distributed under the terms of the MIT License.
|
|
*/
|
|
|
|
|
|
#include "ThreadHandler.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <new>
|
|
|
|
#include <AutoDeleter.h>
|
|
#include <AutoLocker.h>
|
|
#include <Variant.h>
|
|
|
|
#include "Architecture.h"
|
|
#include "BreakpointManager.h"
|
|
#include "CpuState.h"
|
|
#include "DebuggerInterface.h"
|
|
#include "ExpressionInfo.h"
|
|
#include "FunctionInstance.h"
|
|
#include "ImageDebugInfo.h"
|
|
#include "InstructionInfo.h"
|
|
#include "Jobs.h"
|
|
#include "MessageCodes.h"
|
|
#include "Register.h"
|
|
#include "SourceCode.h"
|
|
#include "SourceLanguage.h"
|
|
#include "SpecificImageDebugInfo.h"
|
|
#include "StackTrace.h"
|
|
#include "Statement.h"
|
|
#include "SyntheticPrimitiveType.h"
|
|
#include "Team.h"
|
|
#include "Tracing.h"
|
|
#include "Value.h"
|
|
#include "ValueLocation.h"
|
|
#include "Worker.h"
|
|
|
|
|
|
// step modes
|
|
enum {
|
|
STEP_NONE,
|
|
STEP_OVER,
|
|
STEP_INTO,
|
|
STEP_OUT,
|
|
STEP_UNTIL
|
|
};
|
|
|
|
|
|
class ExpressionEvaluationListener : public ExpressionInfo::Listener {
|
|
public:
|
|
ExpressionEvaluationListener(ThreadHandler* handler)
|
|
:
|
|
fHandler(handler)
|
|
{
|
|
fHandler->AcquireReference();
|
|
}
|
|
|
|
~ExpressionEvaluationListener()
|
|
{
|
|
fHandler->ReleaseReference();
|
|
}
|
|
|
|
virtual void ExpressionEvaluated(ExpressionInfo* info, status_t result,
|
|
ExpressionResult* value)
|
|
{
|
|
fHandler->_HandleBreakpointConditionEvaluated(value);
|
|
}
|
|
|
|
private:
|
|
ThreadHandler* fHandler;
|
|
};
|
|
|
|
|
|
ThreadHandler::ThreadHandler(Thread* thread, Worker* worker,
|
|
DebuggerInterface* debuggerInterface,
|
|
ImageDebugInfoJobListener* listener,
|
|
BreakpointManager* breakpointManager)
|
|
:
|
|
fThread(thread),
|
|
fWorker(worker),
|
|
fDebuggerInterface(debuggerInterface),
|
|
fDebugInfoJobListener(listener),
|
|
fBreakpointManager(breakpointManager),
|
|
fStepMode(STEP_NONE),
|
|
fStepStatement(NULL),
|
|
fBreakpointAddress(0),
|
|
fSteppedOverFunctionAddress(0),
|
|
fPreviousInstructionPointer(0),
|
|
fPreviousFrameAddress(0),
|
|
fSingleStepping(false),
|
|
fConditionWaitSem(-1),
|
|
fConditionResult(NULL)
|
|
{
|
|
fDebuggerInterface->AcquireReference();
|
|
}
|
|
|
|
|
|
ThreadHandler::~ThreadHandler()
|
|
{
|
|
_ClearContinuationState();
|
|
fDebuggerInterface->ReleaseReference();
|
|
|
|
if (fConditionWaitSem > 0)
|
|
delete_sem(fConditionWaitSem);
|
|
}
|
|
|
|
|
|
void
|
|
ThreadHandler::Init()
|
|
{
|
|
fWorker->ScheduleJob(new(std::nothrow) GetThreadStateJob(fDebuggerInterface,
|
|
fThread));
|
|
fConditionWaitSem = create_sem(0, "breakpoint condition waiter");
|
|
}
|
|
|
|
|
|
status_t
|
|
ThreadHandler::SetBreakpointAndRun(target_addr_t address)
|
|
{
|
|
status_t error = _InstallTemporaryBreakpoint(address);
|
|
if (error != B_OK)
|
|
return error;
|
|
|
|
fPreviousInstructionPointer = 0;
|
|
resume_thread(ThreadID());
|
|
// TODO: This should probably better be a DebuggerInterface method,
|
|
// but this method is used only when debugging a local team anyway.
|
|
// Pretend "step out" mode, so that the temporary breakpoint hit will not
|
|
// be ignored.
|
|
fStepMode = STEP_OUT;
|
|
fSingleStepping = false;
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
bool
|
|
ThreadHandler::HandleThreadDebugged(ThreadDebuggedEvent* event,
|
|
const BString& stoppedReason)
|
|
{
|
|
return _HandleThreadStopped(NULL, THREAD_STOPPED_DEBUGGED, stoppedReason);
|
|
}
|
|
|
|
|
|
bool
|
|
ThreadHandler::HandleDebuggerCall(DebuggerCallEvent* event)
|
|
{
|
|
BString message;
|
|
fDebuggerInterface->ReadMemoryString(event->Message(), 1024, message);
|
|
return _HandleThreadStopped(NULL, THREAD_STOPPED_DEBUGGER_CALL, message);
|
|
}
|
|
|
|
|
|
bool
|
|
ThreadHandler::HandleBreakpointHit(BreakpointHitEvent* event)
|
|
{
|
|
CpuState* cpuState = event->GetCpuState();
|
|
target_addr_t instructionPointer = cpuState->InstructionPointer();
|
|
|
|
TRACE_EVENTS("ThreadHandler::HandleBreakpointHit(): ip: %" B_PRIx64 "\n",
|
|
instructionPointer);
|
|
|
|
// check whether this is a temporary breakpoint we're waiting for
|
|
if (fBreakpointAddress != 0 && instructionPointer == fBreakpointAddress
|
|
&& fStepMode != STEP_NONE) {
|
|
if (fStepMode != STEP_UNTIL && _HandleBreakpointHitStep(cpuState))
|
|
return true;
|
|
} else {
|
|
// Might be a user breakpoint, but could as well be a temporary
|
|
// breakpoint of another thread.
|
|
AutoLocker<Team> locker(fThread->GetTeam());
|
|
Breakpoint* breakpoint = fThread->GetTeam()->BreakpointAtAddress(
|
|
cpuState->InstructionPointer());
|
|
bool continueThread = false;
|
|
if (breakpoint == NULL) {
|
|
// spurious breakpoint -- might be a temporary breakpoint, that has
|
|
// already been uninstalled
|
|
continueThread = true;
|
|
} else if (!breakpoint->HasEnabledUserBreakpoint()) {
|
|
// breakpoint of another thread or one that has been disabled in
|
|
// the meantime
|
|
continueThread = true;
|
|
}
|
|
|
|
if (continueThread) {
|
|
if (fSingleStepping) {
|
|
// We might have hit a just-installed software breakpoint and
|
|
// thus haven't stepped at all. Just try again.
|
|
if (fPreviousInstructionPointer == instructionPointer) {
|
|
fDebuggerInterface->SingleStepThread(ThreadID());
|
|
return true;
|
|
}
|
|
|
|
// That shouldn't happen. Try something reasonable anyway.
|
|
if (fStepMode != STEP_NONE) {
|
|
if (_HandleSingleStepStep(cpuState))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
} else {
|
|
locker.Unlock();
|
|
if (_HandleBreakpointConditionIfNeeded(cpuState))
|
|
return true;
|
|
|
|
locker.Lock();
|
|
}
|
|
}
|
|
|
|
return _HandleThreadStopped(cpuState, THREAD_STOPPED_BREAKPOINT);
|
|
}
|
|
|
|
|
|
bool
|
|
ThreadHandler::HandleWatchpointHit(WatchpointHitEvent* event)
|
|
{
|
|
return _HandleThreadStopped(event->GetCpuState(),
|
|
THREAD_STOPPED_WATCHPOINT);
|
|
}
|
|
|
|
|
|
bool
|
|
ThreadHandler::HandleSingleStep(SingleStepEvent* event)
|
|
{
|
|
// Check whether we're stepping automatically.
|
|
if (fStepMode != STEP_NONE) {
|
|
if (_HandleSingleStepStep(event->GetCpuState()))
|
|
return true;
|
|
}
|
|
|
|
return _HandleThreadStopped(event->GetCpuState(),
|
|
THREAD_STOPPED_SINGLE_STEP);
|
|
}
|
|
|
|
|
|
bool
|
|
ThreadHandler::HandleExceptionOccurred(ExceptionOccurredEvent* event)
|
|
{
|
|
char buffer[256];
|
|
get_debug_exception_string(event->Exception(), buffer, sizeof(buffer));
|
|
return _HandleThreadStopped(NULL, THREAD_STOPPED_EXCEPTION, buffer);
|
|
}
|
|
|
|
|
|
void
|
|
ThreadHandler::HandleThreadAction(uint32 action, target_addr_t address)
|
|
{
|
|
AutoLocker<Team> locker(fThread->GetTeam());
|
|
|
|
if (fThread->State() == THREAD_STATE_UNKNOWN)
|
|
return;
|
|
|
|
// When stop is requested, thread must be running, otherwise stopped.
|
|
if (action == MSG_THREAD_STOP
|
|
? fThread->State() != THREAD_STATE_RUNNING
|
|
: fThread->State() != THREAD_STATE_STOPPED) {
|
|
return;
|
|
}
|
|
|
|
// When stepping we need a stack trace. Save it before unsetting the state.
|
|
CpuState* cpuState = fThread->GetCpuState();
|
|
StackTrace* stackTrace = fThread->GetStackTrace();
|
|
BReference<CpuState> cpuStateReference(cpuState);
|
|
BReference<StackTrace> stackTraceReference(stackTrace);
|
|
|
|
if (action == MSG_THREAD_SET_ADDRESS) {
|
|
_HandleSetAddress(cpuState, address);
|
|
return;
|
|
}
|
|
|
|
// When continuing the thread update thread state before actually issuing
|
|
// the command, since we need to unlock.
|
|
if (action != MSG_THREAD_STOP) {
|
|
_SetThreadState(THREAD_STATE_RUNNING, NULL, THREAD_STOPPED_UNKNOWN,
|
|
BString());
|
|
}
|
|
|
|
locker.Unlock();
|
|
|
|
switch (action) {
|
|
case MSG_THREAD_RUN:
|
|
fStepMode = address != 0 ? STEP_UNTIL : STEP_NONE;
|
|
if (address != 0)
|
|
_InstallTemporaryBreakpoint(address);
|
|
_RunThread(0);
|
|
return;
|
|
case MSG_THREAD_STOP:
|
|
fStepMode = STEP_NONE;
|
|
if (fDebuggerInterface->StopThread(ThreadID()) == B_OK)
|
|
fThread->SetStopRequestPending();
|
|
return;
|
|
case MSG_THREAD_STEP_OVER:
|
|
case MSG_THREAD_STEP_INTO:
|
|
case MSG_THREAD_STEP_OUT:
|
|
break;
|
|
}
|
|
|
|
TRACE_CONTROL("ThreadHandler::HandleThreadAction(MSG_THREAD_STEP_*)\n");
|
|
|
|
// We want to step. We need a stack trace for that purpose. If we don't
|
|
// have one yet, get it. Start with the CPU state.
|
|
if (stackTrace == NULL && cpuState == NULL) {
|
|
if (fDebuggerInterface->GetCpuState(fThread->ID(), cpuState) == B_OK)
|
|
cpuStateReference.SetTo(cpuState, true);
|
|
}
|
|
|
|
if (stackTrace == NULL && cpuState != NULL) {
|
|
if (fDebuggerInterface->GetArchitecture()->CreateStackTrace(
|
|
fThread->GetTeam(), this, cpuState, stackTrace, NULL, 1,
|
|
false, false) == B_OK) {
|
|
stackTraceReference.SetTo(stackTrace, true);
|
|
}
|
|
}
|
|
|
|
if (stackTrace == NULL || stackTrace->CountFrames() == 0) {
|
|
_StepFallback();
|
|
return;
|
|
}
|
|
|
|
StackFrame* frame = stackTrace->FrameAt(0);
|
|
|
|
TRACE_CONTROL(" ip: %#" B_PRIx64 "\n", frame->InstructionPointer());
|
|
|
|
target_addr_t frameIP = frame->GetCpuState()->InstructionPointer();
|
|
// When the thread is in a syscall, do the same for all step kinds: Stop it
|
|
// when it returns by means of a breakpoint.
|
|
if (frame->Type() == STACK_FRAME_TYPE_SYSCALL) {
|
|
// set a breakpoint at the CPU state's instruction pointer (points to
|
|
// the return address, unlike the stack frame's instruction pointer)
|
|
// TODO: This is doesn't work correctly anymore. When stepping over a "syscall"
|
|
// instruction the thread is stopped twice. The after the first step the PC is
|
|
// incorrectly shown at the "syscall" instruction. Then we step again and are
|
|
// stopped at the temporary breakpoint after the "syscall" instruction. There
|
|
// are two problems. The first one is that we don't (cannot?) discriminate
|
|
// between the thread being in a syscall (like in a blocking syscall) and the
|
|
// thread having been stopped (or singled-stepped) at the end of the syscall.
|
|
// The second issue is that the temporary breakpoint is probably not necessary
|
|
// anymore, since single-stepping over "syscall" instructions should just work
|
|
// as expected.
|
|
status_t error = _InstallTemporaryBreakpoint(frameIP);
|
|
if (error != B_OK) {
|
|
_StepFallback();
|
|
return;
|
|
}
|
|
|
|
fStepMode = STEP_OUT;
|
|
_RunThread(frameIP);
|
|
return;
|
|
}
|
|
|
|
// For "step out" just set a temporary breakpoint on the return address.
|
|
if (action == MSG_THREAD_STEP_OUT) {
|
|
status_t error = _InstallTemporaryBreakpoint(frame->ReturnAddress());
|
|
if (error != B_OK) {
|
|
_StepFallback();
|
|
return;
|
|
}
|
|
fPreviousInstructionPointer = frameIP;
|
|
fPreviousFrameAddress = frame->FrameAddress();
|
|
fStepMode = STEP_OUT;
|
|
_RunThread(frameIP);
|
|
return;
|
|
}
|
|
|
|
// For "step in" and "step over" we also need the source code statement at
|
|
// the current instruction pointer.
|
|
fStepStatement = _GetStatementAtInstructionPointer(frame);
|
|
if (fStepStatement == NULL) {
|
|
_StepFallback();
|
|
return;
|
|
}
|
|
|
|
TRACE_CONTROL(" statement: %#" B_PRIx64 " - %#" B_PRIx64 "\n",
|
|
fStepStatement->CoveringAddressRange().Start(),
|
|
fStepStatement->CoveringAddressRange().End());
|
|
|
|
if (action == MSG_THREAD_STEP_INTO) {
|
|
// step into
|
|
fStepMode = STEP_INTO;
|
|
_SingleStepThread(frameIP);
|
|
} else {
|
|
fPreviousFrameAddress = frame->FrameAddress();
|
|
// step over
|
|
fStepMode = STEP_OVER;
|
|
if (!_DoStepOver(frame->GetCpuState()))
|
|
_StepFallback();
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
ThreadHandler::HandleThreadStateChanged()
|
|
{
|
|
AutoLocker<Team> locker(fThread->GetTeam());
|
|
|
|
// cancel jobs for this thread
|
|
fWorker->AbortJob(SimpleJobKey(fThread, JOB_TYPE_GET_CPU_STATE));
|
|
fWorker->AbortJob(SimpleJobKey(fThread, JOB_TYPE_GET_STACK_TRACE));
|
|
|
|
// If the thread is stopped and has no CPU state yet, schedule a job.
|
|
if (fThread->State() == THREAD_STATE_STOPPED
|
|
&& fThread->GetCpuState() == NULL) {
|
|
fWorker->ScheduleJob(
|
|
new(std::nothrow) GetCpuStateJob(fDebuggerInterface, fThread));
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
ThreadHandler::HandleCpuStateChanged()
|
|
{
|
|
AutoLocker<Team> locker(fThread->GetTeam());
|
|
|
|
// cancel stack trace job for this thread
|
|
fWorker->AbortJob(SimpleJobKey(fThread, JOB_TYPE_GET_STACK_TRACE));
|
|
|
|
// If the thread has a CPU state, but no stack trace yet, schedule a job.
|
|
if (fThread->GetCpuState() != NULL && fThread->GetStackTrace() == NULL) {
|
|
fWorker->ScheduleJob(
|
|
new(std::nothrow) GetStackTraceJob(fDebuggerInterface,
|
|
fDebugInfoJobListener, fDebuggerInterface->GetArchitecture(),
|
|
fThread));
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
ThreadHandler::HandleStackTraceChanged()
|
|
{
|
|
}
|
|
|
|
|
|
status_t
|
|
ThreadHandler::GetImageDebugInfo(Image* image, ImageDebugInfo*& _info)
|
|
{
|
|
AutoLocker<Team> teamLocker(fThread->GetTeam());
|
|
|
|
if (image->GetImageDebugInfo() != NULL) {
|
|
_info = image->GetImageDebugInfo();
|
|
_info->AcquireReference();
|
|
return B_OK;
|
|
}
|
|
|
|
// Let's be lazy. If the image debug info has not been loaded yet, the user
|
|
// can't have seen any source code either.
|
|
return B_ENTRY_NOT_FOUND;
|
|
}
|
|
|
|
|
|
bool
|
|
ThreadHandler::_HandleThreadStopped(CpuState* cpuState, uint32 stoppedReason,
|
|
const BString& stoppedReasonInfo)
|
|
{
|
|
_ClearContinuationState();
|
|
|
|
AutoLocker<Team> locker(fThread->GetTeam());
|
|
|
|
_SetThreadState(THREAD_STATE_STOPPED, cpuState, stoppedReason,
|
|
stoppedReasonInfo);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool
|
|
ThreadHandler::_HandleSetAddress(CpuState* state, target_addr_t address)
|
|
{
|
|
CpuState* newState = NULL;
|
|
if (state->Clone(newState) != B_OK)
|
|
return false;
|
|
BReference<CpuState> stateReference(newState, true);
|
|
|
|
newState->SetInstructionPointer(address);
|
|
if (fDebuggerInterface->SetCpuState(fThread->ID(), newState) != B_OK)
|
|
return false;
|
|
|
|
AutoLocker<Team> locker(fThread->GetTeam());
|
|
fThread->SetStackTrace(NULL);
|
|
fThread->SetCpuState(newState);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void
|
|
ThreadHandler::_SetThreadState(uint32 state, CpuState* cpuState,
|
|
uint32 stoppedReason, const BString& stoppedReasonInfo)
|
|
{
|
|
fThread->SetState(state, stoppedReason, stoppedReasonInfo);
|
|
fThread->SetCpuState(cpuState);
|
|
}
|
|
|
|
|
|
Statement*
|
|
ThreadHandler::_GetStatementAtInstructionPointer(StackFrame* frame)
|
|
{
|
|
AutoLocker<Team> locker(fThread->GetTeam());
|
|
|
|
FunctionInstance* functionInstance = frame->Function();
|
|
if (functionInstance == NULL)
|
|
return NULL;
|
|
FunctionDebugInfo* function = functionInstance->GetFunctionDebugInfo();
|
|
|
|
// If there's source code attached to the function, we can just get the
|
|
// statement.
|
|
// SourceCode* sourceCode = function->GetSourceCode();
|
|
// if (sourceCode != NULL) {
|
|
// Statement* statement = sourceCode->StatementAtAddress(
|
|
// frame->InstructionPointer());
|
|
// if (statement != NULL)
|
|
// statement->AcquireReference();
|
|
// return statement;
|
|
// }
|
|
|
|
locker.Unlock();
|
|
|
|
// We need to get the statement from the debug info of the function.
|
|
Statement* statement;
|
|
if (function->GetSpecificImageDebugInfo()->GetStatement(function,
|
|
frame->InstructionPointer(), statement) != B_OK) {
|
|
return NULL;
|
|
}
|
|
|
|
return statement;
|
|
}
|
|
|
|
|
|
void
|
|
ThreadHandler::_StepFallback()
|
|
{
|
|
fStepMode = STEP_NONE;
|
|
_SingleStepThread(0);
|
|
}
|
|
|
|
|
|
bool
|
|
ThreadHandler::_DoStepOver(CpuState* cpuState)
|
|
{
|
|
TRACE_CONTROL("ThreadHandler::_DoStepOver()\n");
|
|
|
|
// The basic strategy is to single-step out of the statement like for
|
|
// "step into", only we have to avoid stepping into subroutines. Hence we
|
|
// check whether the current instruction is a subroutine call. If not, we
|
|
// just single-step, otherwise we set a breakpoint after the instruction.
|
|
InstructionInfo info;
|
|
if (fDebuggerInterface->GetArchitecture()->GetInstructionInfo(
|
|
cpuState->InstructionPointer(), info, cpuState) != B_OK) {
|
|
TRACE_CONTROL(" failed to get instruction info\n");
|
|
return false;
|
|
}
|
|
|
|
if (info.Type() != INSTRUCTION_TYPE_SUBROUTINE_CALL) {
|
|
_SingleStepThread(cpuState->InstructionPointer());
|
|
|
|
TRACE_CONTROL(" not a subroutine call\n");
|
|
return true;
|
|
}
|
|
|
|
TRACE_CONTROL(" subroutine call -- installing breakpoint at address "
|
|
"%#" B_PRIx64 "\n", info.Address() + info.Size());
|
|
|
|
if (_InstallTemporaryBreakpoint(info.Address() + info.Size()) != B_OK)
|
|
return false;
|
|
|
|
fSteppedOverFunctionAddress = info.TargetAddress();
|
|
|
|
_RunThread(cpuState->InstructionPointer());
|
|
return true;
|
|
}
|
|
|
|
|
|
status_t
|
|
ThreadHandler::_InstallTemporaryBreakpoint(target_addr_t address)
|
|
{
|
|
_UninstallTemporaryBreakpoint();
|
|
|
|
status_t error = fBreakpointManager->InstallTemporaryBreakpoint(address,
|
|
this);
|
|
if (error != B_OK)
|
|
return error;
|
|
|
|
fBreakpointAddress = address;
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
void
|
|
ThreadHandler::_UninstallTemporaryBreakpoint()
|
|
{
|
|
if (fBreakpointAddress == 0)
|
|
return;
|
|
|
|
fBreakpointManager->UninstallTemporaryBreakpoint(fBreakpointAddress, this);
|
|
fBreakpointAddress = 0;
|
|
}
|
|
|
|
|
|
void
|
|
ThreadHandler::_ClearContinuationState()
|
|
{
|
|
_UninstallTemporaryBreakpoint();
|
|
|
|
if (fStepStatement != NULL) {
|
|
fStepStatement->ReleaseReference();
|
|
fStepStatement = NULL;
|
|
}
|
|
|
|
fStepMode = STEP_NONE;
|
|
fSingleStepping = false;
|
|
}
|
|
|
|
|
|
void
|
|
ThreadHandler::_RunThread(target_addr_t instructionPointer)
|
|
{
|
|
fPreviousInstructionPointer = instructionPointer;
|
|
fDebuggerInterface->ContinueThread(ThreadID());
|
|
fSingleStepping = false;
|
|
}
|
|
|
|
|
|
void
|
|
ThreadHandler::_SingleStepThread(target_addr_t instructionPointer)
|
|
{
|
|
fPreviousInstructionPointer = instructionPointer;
|
|
fDebuggerInterface->SingleStepThread(ThreadID());
|
|
fSingleStepping = true;
|
|
}
|
|
|
|
|
|
bool
|
|
ThreadHandler::_HandleBreakpointHitStep(CpuState* cpuState)
|
|
{
|
|
// in any case uninstall the temporary breakpoint
|
|
_UninstallTemporaryBreakpoint();
|
|
|
|
switch (fStepMode) {
|
|
case STEP_OVER:
|
|
{
|
|
StackTrace* stackTrace = fThread->GetStackTrace();
|
|
BReference<StackTrace> stackTraceReference(stackTrace);
|
|
|
|
if (stackTrace == NULL && cpuState != NULL) {
|
|
if (fDebuggerInterface->GetArchitecture()->CreateStackTrace(
|
|
fThread->GetTeam(), this, cpuState, stackTrace, NULL,
|
|
1, false, false) == B_OK) {
|
|
stackTraceReference.SetTo(stackTrace, true);
|
|
}
|
|
}
|
|
if (stackTrace != NULL) {
|
|
StackFrame* frame = stackTrace->FrameAt(0);
|
|
// If we're not in the same frame we started in,
|
|
// keep executing.
|
|
if (frame != NULL && fPreviousFrameAddress
|
|
!= frame->FrameAddress()) {
|
|
status_t error = _InstallTemporaryBreakpoint(
|
|
cpuState->InstructionPointer());
|
|
if (error != B_OK)
|
|
_StepFallback();
|
|
else
|
|
_RunThread(cpuState->InstructionPointer());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (fPreviousFrameAddress != 0 && fSteppedOverFunctionAddress
|
|
!= cpuState->InstructionPointer()) {
|
|
TRACE_CONTROL("STEP_OVER: called function address %#" B_PRIx64
|
|
", previous frame address: %#" B_PRIx64 ", frame address: %#"
|
|
B_PRIx64 ", adding return info\n", fSteppedOverFunctionAddress,
|
|
fPreviousFrameAddress, stackTrace->FrameAt(0)->FrameAddress());
|
|
ReturnValueInfo* returnInfo = new(std::nothrow) ReturnValueInfo(
|
|
fSteppedOverFunctionAddress, cpuState);
|
|
if (returnInfo == NULL)
|
|
return false;
|
|
|
|
BReference<ReturnValueInfo> returnInfoReference(returnInfo, true);
|
|
|
|
if (fThread->AddReturnValueInfo(returnInfo) != B_OK)
|
|
return false;
|
|
|
|
returnInfoReference.Detach();
|
|
fSteppedOverFunctionAddress = 0;
|
|
}
|
|
|
|
// If we're still in the statement, we continue single-stepping,
|
|
// otherwise we're done.
|
|
if (fStepStatement->ContainsAddress(
|
|
cpuState->InstructionPointer())) {
|
|
if (!_DoStepOver(cpuState))
|
|
_StepFallback();
|
|
return true;
|
|
}
|
|
fPreviousFrameAddress = 0;
|
|
return false;
|
|
}
|
|
|
|
case STEP_INTO:
|
|
// Should never happen -- we don't set a breakpoint in this case.
|
|
return false;
|
|
|
|
case STEP_OUT:
|
|
{
|
|
// That's the return address, so we're done in theory,
|
|
// unless we're a recursive function. Check if we've actually
|
|
// exited the previous stack frame or not
|
|
if (!_HasExitedFrame(cpuState->StackFramePointer())) {
|
|
status_t error = _InstallTemporaryBreakpoint(
|
|
cpuState->InstructionPointer());
|
|
if (error != B_OK)
|
|
_StepFallback();
|
|
else
|
|
_RunThread(cpuState->InstructionPointer());
|
|
return true;
|
|
}
|
|
|
|
if (fPreviousFrameAddress == 0)
|
|
return false;
|
|
|
|
TRACE_CONTROL("ThreadHandler::_HandleBreakpointHitStep() - "
|
|
"frame pointer 0x%#" B_PRIx64 ", previous: 0x%#" B_PRIx64
|
|
" - step out adding return value\n", cpuState
|
|
->StackFramePointer(), fPreviousFrameAddress);
|
|
ReturnValueInfo* info = new(std::nothrow) ReturnValueInfo(
|
|
fPreviousInstructionPointer, cpuState);
|
|
if (info == NULL)
|
|
return false;
|
|
BReference<ReturnValueInfo> infoReference(info, true);
|
|
if (fThread->AddReturnValueInfo(info) != B_OK)
|
|
return false;
|
|
|
|
infoReference.Detach();
|
|
fPreviousFrameAddress = 0;
|
|
}
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
bool
|
|
ThreadHandler::_HandleSingleStepStep(CpuState* cpuState)
|
|
{
|
|
TRACE_CONTROL("ThreadHandler::_HandleSingleStepStep(): ip: %" B_PRIx64 "\n",
|
|
cpuState->InstructionPointer());
|
|
|
|
switch (fStepMode) {
|
|
case STEP_INTO:
|
|
{
|
|
// We continue stepping as long as we're in the statement.
|
|
if (fStepStatement->ContainsAddress(cpuState->InstructionPointer())) {
|
|
_SingleStepThread(cpuState->InstructionPointer());
|
|
return true;
|
|
}
|
|
|
|
StackTrace* stackTrace = fThread->GetStackTrace();
|
|
BReference<StackTrace> stackTraceReference(stackTrace);
|
|
|
|
if (stackTrace == NULL && cpuState != NULL) {
|
|
if (fDebuggerInterface->GetArchitecture()->CreateStackTrace(
|
|
fThread->GetTeam(), this, cpuState, stackTrace, NULL,
|
|
1, false, false) == B_OK) {
|
|
stackTraceReference.SetTo(stackTrace, true);
|
|
}
|
|
}
|
|
|
|
if (stackTrace != NULL) {
|
|
StackFrame* frame = stackTrace->FrameAt(0);
|
|
Image* image = frame->GetImage();
|
|
ImageDebugInfo* info = NULL;
|
|
if (GetImageDebugInfo(image, info) != B_OK)
|
|
return false;
|
|
|
|
BReference<ImageDebugInfo>(info, true);
|
|
if (info->GetAddressSectionType(
|
|
cpuState->InstructionPointer())
|
|
== ADDRESS_SECTION_TYPE_PLT) {
|
|
_SingleStepThread(cpuState->InstructionPointer());
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
case STEP_OVER:
|
|
{
|
|
// If we have stepped out of the statement, we're done.
|
|
if (!fStepStatement->ContainsAddress(cpuState->InstructionPointer())) {
|
|
StackTrace* stackTrace = fThread->GetStackTrace();
|
|
BReference<StackTrace> stackTraceReference(stackTrace);
|
|
if (stackTrace == NULL && cpuState != NULL) {
|
|
if (fDebuggerInterface->GetArchitecture()->CreateStackTrace(
|
|
fThread->GetTeam(), this, cpuState, stackTrace,
|
|
NULL, 1, false, false) == B_OK) {
|
|
stackTraceReference.SetTo(stackTrace, true);
|
|
}
|
|
}
|
|
|
|
|
|
if (stackTrace != NULL) {
|
|
if (_HasExitedFrame(stackTrace->FrameAt(0)
|
|
->FrameAddress())) {
|
|
TRACE_CONTROL("ThreadHandler::_HandleSingleStepStep() "
|
|
" - adding return value for STEP_OVER\n");
|
|
ReturnValueInfo* info = new(std::nothrow)
|
|
ReturnValueInfo(fStepStatement
|
|
->CoveringAddressRange().Start(), cpuState);
|
|
if (info == NULL)
|
|
return false;
|
|
BReference<ReturnValueInfo> infoReference(info, true);
|
|
if (fThread->AddReturnValueInfo(info) != B_OK)
|
|
return false;
|
|
|
|
infoReference.Detach();
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
return _DoStepOver(cpuState);
|
|
}
|
|
|
|
case STEP_OUT:
|
|
// We never single-step in this case.
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
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();
|
|
BReference<StackTrace> stackTraceReference;
|
|
if (stackTrace == NULL) {
|
|
if (fDebuggerInterface->GetArchitecture()->CreateStackTrace(
|
|
fThread->GetTeam(), this, cpuState, stackTrace, NULL, 1,
|
|
false, true) == B_OK) {
|
|
stackTraceReference.SetTo(stackTrace, true);
|
|
} else
|
|
return false;
|
|
}
|
|
|
|
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);
|
|
ExpressionEvaluationListener* listener
|
|
= new(std::nothrow) ExpressionEvaluationListener(this);
|
|
if (listener == NULL)
|
|
return false;
|
|
|
|
ExpressionInfo* expressionInfo = new(std::nothrow) ExpressionInfo(
|
|
userBreakpoint->Condition());
|
|
|
|
if (expressionInfo == NULL)
|
|
return false;
|
|
|
|
BReference<ExpressionInfo> expressionReference(expressionInfo, true);
|
|
|
|
expressionInfo->AddListener(listener);
|
|
|
|
status_t error = fWorker->ScheduleJob(
|
|
new(std::nothrow) ExpressionEvaluationJob(fThread->GetTeam(),
|
|
fDebuggerInterface, language, expressionInfo, frame, fThread));
|
|
|
|
BPrivate::ObjectDeleter<ExpressionEvaluationListener> deleter(
|
|
listener);
|
|
if (error == B_OK) {
|
|
teamLocker.Unlock();
|
|
do {
|
|
error = acquire_sem(fConditionWaitSem);
|
|
} while (error == B_INTERRUPTED);
|
|
|
|
teamLocker.Lock();
|
|
|
|
if (_CheckStopCondition()) {
|
|
if (fConditionResult != NULL) {
|
|
fConditionResult->ReleaseReference();
|
|
fConditionResult = NULL;
|
|
}
|
|
_SetThreadState(THREAD_STATE_STOPPED, cpuState,
|
|
THREAD_STOPPED_BREAKPOINT, BString());
|
|
return false;
|
|
} else {
|
|
fDebuggerInterface->ContinueThread(ThreadID());
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void
|
|
ThreadHandler::_HandleBreakpointConditionEvaluated(ExpressionResult* value)
|
|
{
|
|
fConditionResult = value;
|
|
if (fConditionResult != NULL)
|
|
fConditionResult->AcquireReference();
|
|
release_sem(fConditionWaitSem);
|
|
}
|
|
|
|
|
|
bool
|
|
ThreadHandler::_CheckStopCondition()
|
|
{
|
|
// if we we're unable to properly assess the expression result
|
|
// in any way, fall back to behaving like an unconditional breakpoint.
|
|
if (fConditionResult == NULL)
|
|
return true;
|
|
|
|
if (fConditionResult->Kind() != EXPRESSION_RESULT_KIND_PRIMITIVE)
|
|
return true;
|
|
|
|
BVariant value;
|
|
if (!fConditionResult->PrimitiveValue()->ToVariant(value))
|
|
return true;
|
|
|
|
return value.ToBool();
|
|
}
|
|
|
|
|
|
bool
|
|
ThreadHandler::_HasExitedFrame(target_addr_t framePointer) const
|
|
{
|
|
return fDebuggerInterface->GetArchitecture()->StackGrowthDirection()
|
|
== STACK_GROWTH_DIRECTION_POSITIVE
|
|
? framePointer < fPreviousFrameAddress
|
|
: framePointer > fPreviousFrameAddress;
|
|
}
|