* Moved breakpoint management into new class BreakpointManager and added
support for temporary breakpoints. * TeamDebugger: No longer handle debug events in the listener thread. Instead post a message to the looper thread. Makes the locking a bit easier. * Architecture: Added virtual GetInstructionInfo() and GetStatement() returning information on the instruction respectively a statement at a given address. Implemented for x86. * DebugInfo: Added virtual GetStatement() and implemented it for DebuggerDebugInfo by means of using the Architecture. * Implemented step over/into/out support. Works in principle, but has no handling for PLTs yet, i.e. stepping into functions of other libraries requires two steps ATM. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@31244 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
@@ -11,28 +11,54 @@
|
||||
|
||||
#include <AutoLocker.h>
|
||||
|
||||
#include "Architecture.h"
|
||||
#include "BreakpointManager.h"
|
||||
#include "CpuState.h"
|
||||
#include "DebuggerInterface.h"
|
||||
#include "DebugInfo.h"
|
||||
#include "FunctionDebugInfo.h"
|
||||
#include "ImageDebugInfo.h"
|
||||
#include "InstructionInfo.h"
|
||||
#include "Jobs.h"
|
||||
#include "MessageCodes.h"
|
||||
#include "SourceCode.h"
|
||||
#include "StackTrace.h"
|
||||
#include "Statement.h"
|
||||
#include "Team.h"
|
||||
#include "TeamDebugModel.h"
|
||||
#include "Worker.h"
|
||||
|
||||
|
||||
// step modes
|
||||
enum {
|
||||
STEP_NONE,
|
||||
STEP_OVER,
|
||||
STEP_INTO,
|
||||
STEP_OUT
|
||||
};
|
||||
|
||||
|
||||
ThreadHandler::ThreadHandler(TeamDebugModel* debugModel, Thread* thread,
|
||||
Worker* worker, DebuggerInterface* debuggerInterface)
|
||||
Worker* worker, DebuggerInterface* debuggerInterface,
|
||||
BreakpointManager* breakpointManager)
|
||||
:
|
||||
fDebugModel(debugModel),
|
||||
fThread(thread),
|
||||
fWorker(worker),
|
||||
fDebuggerInterface(debuggerInterface)
|
||||
fDebuggerInterface(debuggerInterface),
|
||||
fBreakpointManager(breakpointManager),
|
||||
fStepMode(STEP_NONE),
|
||||
fStepStatement(NULL),
|
||||
fBreakpointAddress(0),
|
||||
fPreviousInstructionPointer(0),
|
||||
fSingleStepping(false)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
ThreadHandler::~ThreadHandler()
|
||||
{
|
||||
_ClearContinuationState();
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +87,52 @@ ThreadHandler::HandleDebuggerCall(DebuggerCallEvent* event)
|
||||
bool
|
||||
ThreadHandler::HandleBreakpointHit(BreakpointHitEvent* event)
|
||||
{
|
||||
return _HandleThreadStopped(event->GetCpuState());
|
||||
CpuState* cpuState = event->GetCpuState();
|
||||
target_addr_t instructionPointer = cpuState->InstructionPointer();
|
||||
|
||||
// check whether this is a temporary breakpoint we're waiting for
|
||||
if (fBreakpointAddress != 0 && instructionPointer == fBreakpointAddress
|
||||
&& fStepMode != STEP_NONE) {
|
||||
if (_HandleBreakpointHitStep(cpuState))
|
||||
return true;
|
||||
} else {
|
||||
// Might be a user breakpoint, but could as well be a temporary
|
||||
// breakpoint of another thread.
|
||||
AutoLocker<TeamDebugModel> locker(fDebugModel);
|
||||
Breakpoint* breakpoint = fDebugModel->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->UserState() != USER_BREAKPOINT_ENABLED) {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
return _HandleThreadStopped(cpuState);
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +146,12 @@ ThreadHandler::HandleWatchpointHit(WatchpointHitEvent* event)
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -101,6 +178,12 @@ ThreadHandler::HandleThreadAction(uint32 action)
|
||||
return;
|
||||
}
|
||||
|
||||
// When stepping we need a stack trace. Save it before unsetting the state.
|
||||
CpuState* cpuState = fThread->GetCpuState();
|
||||
StackTrace* stackTrace = fThread->GetStackTrace();
|
||||
Reference<CpuState> cpuStateReference(cpuState);
|
||||
Reference<StackTrace> stackTraceReference(stackTrace);
|
||||
|
||||
// When continuing the thread update thread state before actually issuing
|
||||
// the command, since we need to unlock.
|
||||
if (action != MSG_THREAD_STOP)
|
||||
@@ -110,27 +193,87 @@ ThreadHandler::HandleThreadAction(uint32 action)
|
||||
|
||||
switch (action) {
|
||||
case MSG_THREAD_RUN:
|
||||
printf("MSG_THREAD_RUN\n");
|
||||
fDebuggerInterface->ContinueThread(ThreadID());
|
||||
break;
|
||||
fStepMode = STEP_NONE;
|
||||
_RunThread(0);
|
||||
return;
|
||||
case MSG_THREAD_STOP:
|
||||
printf("MSG_THREAD_STOP\n");
|
||||
fStepMode = STEP_NONE;
|
||||
fDebuggerInterface->StopThread(ThreadID());
|
||||
break;
|
||||
return;
|
||||
case MSG_THREAD_STEP_OVER:
|
||||
printf("MSG_THREAD_STEP_OVER\n");
|
||||
fDebuggerInterface->SingleStepThread(ThreadID());
|
||||
break;
|
||||
case MSG_THREAD_STEP_INTO:
|
||||
printf("MSG_THREAD_STEP_INTO\n");
|
||||
fDebuggerInterface->SingleStepThread(ThreadID());
|
||||
break;
|
||||
case MSG_THREAD_STEP_OUT:
|
||||
printf("MSG_THREAD_STEP_OUT\n");
|
||||
fDebuggerInterface->SingleStepThread(ThreadID());
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: Handle stepping correctly!
|
||||
// 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) == B_OK) {
|
||||
stackTraceReference.SetTo(stackTrace, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (stackTrace == NULL || stackTrace->CountFrames() == 0) {
|
||||
_StepFallback();
|
||||
return;
|
||||
}
|
||||
|
||||
StackFrame* frame = stackTrace->FrameAt(0);
|
||||
|
||||
// When the thread is in a syscall, do the same for all step kinds: Stop it
|
||||
// when it return 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)
|
||||
status_t error = _InstallTemporaryBreakpoint(
|
||||
frame->GetCpuState()->InstructionPointer());
|
||||
if (error != B_OK) {
|
||||
_StepFallback();
|
||||
return;
|
||||
}
|
||||
|
||||
fStepMode = STEP_OUT;
|
||||
_RunThread(frame->GetCpuState()->InstructionPointer());
|
||||
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;
|
||||
}
|
||||
|
||||
fStepMode = STEP_OUT;
|
||||
_RunThread(frame->GetCpuState()->InstructionPointer());
|
||||
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;
|
||||
}
|
||||
|
||||
if (action == MSG_THREAD_STEP_INTO) {
|
||||
// step into
|
||||
fStepMode = STEP_INTO;
|
||||
_SingleStepThread(frame->GetCpuState()->InstructionPointer());
|
||||
} else {
|
||||
// step over
|
||||
fStepMode = STEP_OVER;
|
||||
if (!_DoStepOver(frame->GetCpuState()))
|
||||
_StepFallback();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,13 +316,31 @@ ThreadHandler::HandleCpuStateChanged()
|
||||
void
|
||||
ThreadHandler::HandleStackTraceChanged()
|
||||
{
|
||||
printf("ThreadHandler::_HandleStackTraceChanged()\n");
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
ThreadHandler::GetImageDebugInfo(Image* image, ImageDebugInfo*& _info)
|
||||
{
|
||||
AutoLocker<Team> teamLocker(fThread->GetTeam());
|
||||
|
||||
if (image->GetImageDebugInfo() != NULL) {
|
||||
_info = image->GetImageDebugInfo();
|
||||
_info->AddReference();
|
||||
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)
|
||||
{
|
||||
_ClearContinuationState();
|
||||
|
||||
AutoLocker<TeamDebugModel> locker(fDebugModel);
|
||||
|
||||
_SetThreadState(THREAD_STATE_STOPPED, cpuState);
|
||||
@@ -194,3 +355,185 @@ ThreadHandler::_SetThreadState(uint32 state, CpuState* cpuState)
|
||||
fThread->SetState(state);
|
||||
fThread->SetCpuState(cpuState);
|
||||
}
|
||||
|
||||
|
||||
Statement*
|
||||
ThreadHandler::_GetStatementAtInstructionPointer(StackFrame* frame)
|
||||
{
|
||||
AutoLocker<TeamDebugModel> locker(fDebugModel);
|
||||
|
||||
// If there's source code attached to the stack frame, we can just get the
|
||||
// statement.
|
||||
SourceCode* sourceCode = frame->GetSourceCode();
|
||||
if (sourceCode != NULL) {
|
||||
Statement* statement = sourceCode->StatementAtAddress(
|
||||
frame->InstructionPointer());
|
||||
if (statement != NULL)
|
||||
statement->AddReference();
|
||||
return statement;
|
||||
}
|
||||
|
||||
locker.Unlock();
|
||||
|
||||
// We need to get the statement from the debug info of the function (if
|
||||
// any).
|
||||
FunctionDebugInfo* function = frame->Function();
|
||||
if (function == NULL)
|
||||
return NULL;
|
||||
|
||||
Statement* statement;
|
||||
if (function->GetDebugInfo()->GetStatement(function,
|
||||
frame->InstructionPointer(), statement) != B_OK) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return statement;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ThreadHandler::_StepFallback()
|
||||
{
|
||||
fStepMode = STEP_NONE;
|
||||
_SingleStepThread(0);
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
ThreadHandler::_DoStepOver(CpuState* cpuState)
|
||||
{
|
||||
// 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) != B_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (info.Type() != INSTRUCTION_TYPE_SUBROUTINE_CALL) {
|
||||
_SingleStepThread(cpuState->InstructionPointer());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_InstallTemporaryBreakpoint(info.Address() + info.Size()) != B_OK)
|
||||
return false;
|
||||
|
||||
_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->RemoveReference();
|
||||
|
||||
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:
|
||||
// If we're still in the statement, we continue single-stepping,
|
||||
// otherwise we're done.
|
||||
if (fStepStatement->ContainsAddress(
|
||||
cpuState->InstructionPointer())) {
|
||||
_SingleStepThread(cpuState->InstructionPointer());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
case STEP_INTO:
|
||||
// Should never happen -- we don't set a breakpoint in this case.
|
||||
case STEP_OUT:
|
||||
// That's the return address, so we're done.
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
ThreadHandler::_HandleSingleStepStep(CpuState* cpuState)
|
||||
{
|
||||
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;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
case STEP_OVER:
|
||||
{
|
||||
// If we have stepped out of the statement, we're done.
|
||||
if (!fStepStatement->ContainsAddress(cpuState->InstructionPointer()))
|
||||
return false;
|
||||
return _DoStepOver(cpuState);
|
||||
}
|
||||
|
||||
case STEP_OUT:
|
||||
// We never single-step in this case.
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user