From 43b0f7e0d78311f9c484992b9bce69916b78a031 Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Thu, 25 Jun 2009 23:51:09 +0000 Subject: [PATCH] * 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 --- src/apps/debugger/BreakpointManager.cpp | 235 +++++++++++ src/apps/debugger/BreakpointManager.h | 43 ++ src/apps/debugger/Jamfile | 2 + src/apps/debugger/MessageCodes.h | 1 + src/apps/debugger/TeamDebugger.cpp | 135 ++----- src/apps/debugger/TeamDebugger.h | 5 +- src/apps/debugger/ThreadHandler.cpp | 379 +++++++++++++++++- src/apps/debugger/ThreadHandler.h | 41 +- src/apps/debugger/arch/Architecture.h | 10 + src/apps/debugger/arch/InstructionInfo.cpp | 44 ++ src/apps/debugger/arch/InstructionInfo.h | 50 +++ .../debugger/arch/x86/ArchitectureX86.cpp | 75 ++++ src/apps/debugger/arch/x86/ArchitectureX86.h | 6 + src/apps/debugger/debug_info/DebugInfo.h | 5 + .../debugger/debug_info/DebuggerDebugInfo.cpp | 8 + .../debugger/debug_info/DebuggerDebugInfo.h | 3 + 16 files changed, 919 insertions(+), 123 deletions(-) create mode 100644 src/apps/debugger/BreakpointManager.cpp create mode 100644 src/apps/debugger/BreakpointManager.h create mode 100644 src/apps/debugger/arch/InstructionInfo.cpp create mode 100644 src/apps/debugger/arch/InstructionInfo.h diff --git a/src/apps/debugger/BreakpointManager.cpp b/src/apps/debugger/BreakpointManager.cpp new file mode 100644 index 0000000000..d5a0ffc998 --- /dev/null +++ b/src/apps/debugger/BreakpointManager.cpp @@ -0,0 +1,235 @@ +/* + * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + +#include "BreakpointManager.h" + +#include + +#include + +#include + +#include "DebuggerInterface.h" +#include "TeamDebugModel.h" + + +BreakpointManager::BreakpointManager(TeamDebugModel* debugModel, + DebuggerInterface* debuggerInterface) + : + fLock("breakpoint manager"), + fDebugModel(debugModel), + fDebuggerInterface(debuggerInterface) +{ +} + + +BreakpointManager::~BreakpointManager() +{ +} + + +status_t +BreakpointManager::Init() +{ + return fLock.InitCheck(); +} + + +status_t +BreakpointManager::InstallUserBreakpoint(target_addr_t address, + bool enabled) +{ + user_breakpoint_state state = enabled + ? USER_BREAKPOINT_ENABLED : USER_BREAKPOINT_DISABLED; + + AutoLocker modelLocker(fDebugModel); + + // If there already is a breakpoint, it might already have the requested + // state. + Breakpoint* breakpoint = fDebugModel->BreakpointAtAddress(address); + if (breakpoint != NULL && breakpoint->UserState() == state) + return B_OK; + + // create a breakpoint, if it doesn't exist yet + if (breakpoint == NULL) { + Image* image = fDebugModel->GetTeam()->ImageByAddress(address); + if (image == NULL) + return B_BAD_ADDRESS; + + breakpoint = new(std::nothrow) Breakpoint(image, address); + if (breakpoint == NULL) + return B_NO_MEMORY; + + if (!fDebugModel->AddBreakpoint(breakpoint)) + return B_NO_MEMORY; + } + + user_breakpoint_state oldState = breakpoint->UserState(); + + // set the breakpoint state + breakpoint->SetUserState(state); + fDebugModel->NotifyUserBreakpointChanged(breakpoint); + + AutoLocker installLocker(fLock); + // We need to make the installation decision with both locks held, and + // we keep this lock until we have the breakpoint installed/uninstalled. + + bool install = breakpoint->ShouldBeInstalled(); + if (breakpoint->IsInstalled() == install) + return B_OK; + + // The breakpoint needs to be installed/uninstalled. + Reference breakpointReference(breakpoint); + modelLocker.Unlock(); + + status_t error = install + ? fDebuggerInterface->InstallBreakpoint(address) + : fDebuggerInterface->UninstallBreakpoint(address); + + // Mark the breakpoint installed/uninstalled, if everything went fine. + if (error == B_OK) { + breakpoint->SetInstalled(install); + return B_OK; + } + + // revert on error + installLocker. Unlock(); + modelLocker.Lock(); + + breakpoint->SetUserState(oldState); + fDebugModel->NotifyUserBreakpointChanged(breakpoint); + + if (breakpoint->IsUnused()) + fDebugModel->RemoveBreakpoint(breakpoint); + + return error; +} + + +void +BreakpointManager::UninstallUserBreakpoint(target_addr_t address) +{ + AutoLocker modelLocker(fDebugModel); + + Breakpoint* breakpoint = fDebugModel->BreakpointAtAddress(address); + if (breakpoint == NULL || breakpoint->UserState() == USER_BREAKPOINT_NONE) + return; + + // set the breakpoint state + breakpoint->SetUserState(USER_BREAKPOINT_NONE); + fDebugModel->NotifyUserBreakpointChanged(breakpoint); + + AutoLocker installLocker(fLock); + // We need to make the uninstallation decision with both locks held, and + // we keep this lock until we have the breakpoint uninstalled. + + // check whether the breakpoint needs to be uninstalled + bool uninstall = !breakpoint->ShouldBeInstalled() + && breakpoint->IsInstalled(); + + // if unused remove it + Reference breakpointReference(breakpoint); + if (breakpoint->IsUnused()) + fDebugModel->RemoveBreakpoint(breakpoint); + + modelLocker.Unlock(); + + if (uninstall) { + fDebuggerInterface->UninstallBreakpoint(address); + breakpoint->SetInstalled(false); + } +} + + +status_t +BreakpointManager::InstallTemporaryBreakpoint(target_addr_t address, + BreakpointClient* client) +{ + AutoLocker modelLocker(fDebugModel); + + // create a breakpoint, if it doesn't exist yet + Breakpoint* breakpoint = fDebugModel->BreakpointAtAddress(address); + if (breakpoint == NULL) { + Image* image = fDebugModel->GetTeam()->ImageByAddress(address); + if (image == NULL) + return B_BAD_ADDRESS; + + breakpoint = new(std::nothrow) Breakpoint(image, address); + if (breakpoint == NULL) + return B_NO_MEMORY; + + if (!fDebugModel->AddBreakpoint(breakpoint)) + return B_NO_MEMORY; + } + + Reference breakpointReference(breakpoint); + + // add the client + status_t error; + if (breakpoint->AddClient(client)) { + AutoLocker installLocker(fLock); + // We need to make the installation decision with both locks held, + // and we keep this lock until we have the breakpoint installed. + + if (breakpoint->IsInstalled()) + return B_OK; + + // install + modelLocker.Unlock(); + + error = fDebuggerInterface->InstallBreakpoint(address); + if (error == B_OK) { + breakpoint->SetInstalled(true); + return B_OK; + } + + installLocker.Unlock(); + modelLocker.Lock(); + + breakpoint->RemoveClient(client); + } else + error = B_NO_MEMORY; + + // clean up on error + if (breakpoint->IsUnused()) + fDebugModel->RemoveBreakpoint(breakpoint); + + return error; +} + + +void +BreakpointManager::UninstallTemporaryBreakpoint(target_addr_t address, + BreakpointClient* client) +{ + AutoLocker modelLocker(fDebugModel); + + Breakpoint* breakpoint = fDebugModel->BreakpointAtAddress(address); + if (breakpoint == NULL) + return; + + // remove the client + breakpoint->RemoveClient(client); + + AutoLocker installLocker(fLock); + // We need to make the uninstallation decision with both locks held, and + // we keep this lock until we have the breakpoint uninstalled. + + // check whether the breakpoint needs to be uninstalled + bool uninstall = !breakpoint->ShouldBeInstalled() + && breakpoint->IsInstalled(); + + // if unused remove it + Reference breakpointReference(breakpoint); + if (breakpoint->IsUnused()) + fDebugModel->RemoveBreakpoint(breakpoint); + + modelLocker.Unlock(); + + if (uninstall) { + fDebuggerInterface->UninstallBreakpoint(address); + breakpoint->SetInstalled(false); + } +} diff --git a/src/apps/debugger/BreakpointManager.h b/src/apps/debugger/BreakpointManager.h new file mode 100644 index 0000000000..8b50bd1b56 --- /dev/null +++ b/src/apps/debugger/BreakpointManager.h @@ -0,0 +1,43 @@ +/* + * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ +#ifndef BREAKPOINT_MANAGER_H +#define BREAKPOINT_MANAGER_H + +#include + +#include "Breakpoint.h" + + +class DebuggerInterface; +class TeamDebugModel; + + +class BreakpointManager { +public: + BreakpointManager(TeamDebugModel* debugModel, + DebuggerInterface* debuggerInterface); + ~BreakpointManager(); + + status_t Init(); + + status_t InstallUserBreakpoint(target_addr_t address, + bool enabled); + void UninstallUserBreakpoint(target_addr_t address); + + status_t InstallTemporaryBreakpoint( + target_addr_t address, + BreakpointClient* client); + void UninstallTemporaryBreakpoint( + target_addr_t address, + BreakpointClient* client); + +private: + BLocker fLock; // used to synchronize un-/installing + TeamDebugModel* fDebugModel; + DebuggerInterface* fDebuggerInterface; +}; + + +#endif // BREAKPOINT_MANAGER_H diff --git a/src/apps/debugger/Jamfile b/src/apps/debugger/Jamfile index a2d390b60a..6b63258fc8 100644 --- a/src/apps/debugger/Jamfile +++ b/src/apps/debugger/Jamfile @@ -21,6 +21,7 @@ SubDirHdrs [ FDirName $(HAIKU_TOP) src bin debug ] ; SubDirHdrs [ FDirName $(debugAnalyzerSources) gui ] ; Application Debugger : + BreakpointManager.cpp Debugger.cpp # ElfFile.cpp Jobs.cpp @@ -31,6 +32,7 @@ Application Debugger : # arch Architecture.cpp CpuState.cpp + InstructionInfo.cpp Register.cpp # arch/x86 diff --git a/src/apps/debugger/MessageCodes.h b/src/apps/debugger/MessageCodes.h index 05367b0ce9..aa04da3646 100644 --- a/src/apps/debugger/MessageCodes.h +++ b/src/apps/debugger/MessageCodes.h @@ -20,6 +20,7 @@ enum { MSG_THREAD_STACK_TRACE_CHANGED = 'tstc', MSG_STACK_FRAME_SOURCE_CODE_CHANGED = 'sfsc', MSG_USER_BREAKPOINT_CHANGED = 'ubrc', + MSG_DEBUGGER_EVENT = 'dbge', MSG_TEAM_DEBUGGER_QUIT = 'dbqt' }; diff --git a/src/apps/debugger/TeamDebugger.cpp b/src/apps/debugger/TeamDebugger.cpp index afa1661d2f..12bc43d409 100644 --- a/src/apps/debugger/TeamDebugger.cpp +++ b/src/apps/debugger/TeamDebugger.cpp @@ -17,6 +17,7 @@ #include "debug_utils.h" +#include "BreakpointManager.h" #include "CpuState.h" #include "DebuggerInterface.h" #include "Jobs.h" @@ -34,6 +35,7 @@ TeamDebugger::TeamDebugger(Listener* listener) fTeamID(-1), fDebuggerInterface(NULL), fWorker(NULL), + fBreakpointManager(NULL), fDebugEventListener(-1), fTeamWindow(NULL), fTerminating(false), @@ -74,6 +76,7 @@ TeamDebugger::~TeamDebugger() threadHandler = next; } + delete fBreakpointManager; delete fDebuggerInterface; delete fWorker; delete fDebugModel; @@ -140,6 +143,16 @@ TeamDebugger::Init(team_id teamID, thread_id threadID, bool stopInMain) if (error != B_OK) return error; + // create the breakpoint manager + fBreakpointManager = new(std::nothrow) BreakpointManager(fDebugModel, + fDebuggerInterface); + if (fBreakpointManager == NULL) + return B_NO_MEMORY; + + error = fBreakpointManager->Init(); + if (error != B_OK) + return error; + // set team debugging flags fDebuggerInterface->SetTeamDebuggingFlags( B_TEAM_DEBUG_THREADS | B_TEAM_DEBUG_IMAGES); @@ -157,7 +170,8 @@ TeamDebugger::Init(team_id teamID, thread_id threadID, bool stopInMain) return error; ThreadHandler* handler = new(std::nothrow) ThreadHandler( - fDebugModel, thread, fWorker, fDebuggerInterface); + fDebugModel, thread, fWorker, fDebuggerInterface, + fBreakpointManager); if (handler == NULL) return B_NO_MEMORY; @@ -293,6 +307,16 @@ TeamDebugger::MessageReceived(BMessage* message) break; } + case MSG_DEBUGGER_EVENT: + { + DebugEvent* event; + if (message->FindPointer("event", (void**)&event) != B_OK) + break; + + _HandleDebuggerMessage(event); + delete event; + } + default: BLooper::MessageReceived(message); break; @@ -468,13 +492,12 @@ event->Team()); continue; } - _HandleDebuggerMessage(event); - -// if (event->EventType() == B_DEBUGGER_MESSAGE_TEAM_DELETED -// || event->EventType() == B_DEBUGGER_MESSAGE_TEAM_EXEC) { -// // TODO:... -// break; -// } + BMessage message(MSG_DEBUGGER_EVENT); + if (message.AddPointer("event", event) != B_OK + || PostMessage(&message) != B_OK) { + // TODO: Continue thread if necessary! + delete event; + } } return B_OK; @@ -591,7 +614,8 @@ TeamDebugger::_HandleThreadCreated(ThreadCreatedEvent* event) fTeam->AddThread(info, &thread); ThreadHandler* handler = new(std::nothrow) ThreadHandler( - fDebugModel, thread, fWorker, fDebuggerInterface); + fDebugModel, thread, fWorker, fDebuggerInterface, + fBreakpointManager); if (handler != NULL) { fThreadHandlers.Insert(handler); handler->Init(); @@ -633,78 +657,12 @@ TeamDebugger::_HandleImageDeleted(ImageDeletedEvent* event) } -status_t -TeamDebugger::_SetUserBreakpoint(target_addr_t address, bool enabled) -{ - user_breakpoint_state state = enabled - ? USER_BREAKPOINT_ENABLED : USER_BREAKPOINT_DISABLED; - - AutoLocker locker(fDebugModel); - - // If there already is a breakpoint, it might already have the requested - // state. - Breakpoint* breakpoint = fDebugModel->BreakpointAtAddress(address); - if (breakpoint != NULL && breakpoint->UserState() == state) - return B_OK; - - // create a breakpoint, if it doesn't exist yet - if (breakpoint == NULL) { - Image* image = fTeam->ImageByAddress(address); - if (image == NULL) - return B_OK; - - breakpoint = new(std::nothrow) Breakpoint(image, address); - if (breakpoint == NULL) - return B_NO_MEMORY; - - if (!fDebugModel->AddBreakpoint(breakpoint)) - return B_NO_MEMORY; - } - - user_breakpoint_state oldState = breakpoint->UserState(); - - // set the breakpoint state - breakpoint->SetUserState(state); - fDebugModel->NotifyUserBreakpointChanged(breakpoint); - - bool install = breakpoint->ShouldBeInstalled(); - if (breakpoint->IsInstalled() == install) - return B_OK; - - // The breakpoint needs to be installed/uninstalled. - locker.Unlock(); - - status_t error = install - ? fDebuggerInterface->InstallBreakpoint(address) - : fDebuggerInterface->UninstallBreakpoint(address); - - locker.Lock(); - - breakpoint = fDebugModel->BreakpointAtAddress(address); - - // Mark the breakpoint installed/uninstalled, if everything went fine. - if (error == B_OK) { - breakpoint->SetInstalled(install); -printf("-> breakpoint %sinstalled successfully!\n", install ? "" : "un"); - return B_OK; - } - - // revert on error - breakpoint->SetUserState(oldState); - fDebugModel->NotifyUserBreakpointChanged(breakpoint); - - if (breakpoint->IsUnused()) - fDebugModel->RemoveBreakpoint(breakpoint); - - return error; -} - - void TeamDebugger::_HandleSetUserBreakpoint(target_addr_t address, bool enabled) { printf("TeamDebugger::_HandleSetUserBreakpoint(%#llx, %d)\n", address, enabled); - status_t error = _SetUserBreakpoint(address, enabled); + status_t error = fBreakpointManager->InstallUserBreakpoint(address, + enabled); if (error != B_OK) { _NotifyUser("Install Breakpoint", "Failed to install breakpoint: %s", strerror(error)); @@ -716,28 +674,7 @@ void TeamDebugger::_HandleClearUserBreakpoint(target_addr_t address) { printf("TeamDebugger::_HandleClearUserBreakpoint(%#llx)\n", address); - AutoLocker locker(fDebugModel); - - Breakpoint* breakpoint = fDebugModel->BreakpointAtAddress(address); - if (breakpoint == NULL || breakpoint->UserState() == USER_BREAKPOINT_NONE) - return; - - // set the breakpoint state - breakpoint->SetUserState(USER_BREAKPOINT_NONE); - fDebugModel->NotifyUserBreakpointChanged(breakpoint); - - // check whether the breakpoint needs to be uninstalled - bool uninstall = !breakpoint->ShouldBeInstalled() - && breakpoint->IsInstalled(); - - // if unused remove it - if (breakpoint->IsUnused()) - fDebugModel->RemoveBreakpoint(breakpoint); - - locker.Unlock(); - - if (uninstall) - fDebuggerInterface->UninstallBreakpoint(address); + fBreakpointManager->UninstallUserBreakpoint(address); } diff --git a/src/apps/debugger/TeamDebugger.h b/src/apps/debugger/TeamDebugger.h index f40fb70cc8..899ae9a0fb 100644 --- a/src/apps/debugger/TeamDebugger.h +++ b/src/apps/debugger/TeamDebugger.h @@ -76,10 +76,6 @@ private: bool _HandleImageDeleted( ImageDeletedEvent* event); - - status_t _SetUserBreakpoint(target_addr_t address, - bool enabled); - void _HandleSetUserBreakpoint(target_addr_t address, bool enabled); void _HandleClearUserBreakpoint( @@ -100,6 +96,7 @@ private: // protected by the team lock DebuggerInterface* fDebuggerInterface; Worker* fWorker; + BreakpointManager* fBreakpointManager; thread_id fDebugEventListener; TeamWindow* fTeamWindow; volatile bool fTerminating; diff --git a/src/apps/debugger/ThreadHandler.cpp b/src/apps/debugger/ThreadHandler.cpp index 1dd06472c0..b636c505e8 100644 --- a/src/apps/debugger/ThreadHandler.cpp +++ b/src/apps/debugger/ThreadHandler.cpp @@ -11,28 +11,54 @@ #include +#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 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 cpuStateReference(cpuState); + Reference 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 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 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 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; + } +} diff --git a/src/apps/debugger/ThreadHandler.h b/src/apps/debugger/ThreadHandler.h index d394186500..a58b29b0a9 100644 --- a/src/apps/debugger/ThreadHandler.h +++ b/src/apps/debugger/ThreadHandler.h @@ -8,21 +8,28 @@ #include #include +#include "Breakpoint.h" #include "DebugEvent.h" +#include "ImageDebugInfoProvider.h" #include "Thread.h" +class BreakpointManager; class DebuggerInterface; +class StackFrame; +class Statement; class TeamDebugModel; class Worker; class ThreadHandler : public Referenceable, - public HashTableLink { + public HashTableLink, private ImageDebugInfoProvider, + private BreakpointClient { public: ThreadHandler(TeamDebugModel* debugModel, Thread* thread, Worker* worker, - DebuggerInterface* debuggerInterface); + DebuggerInterface* debuggerInterface, + BreakpointManager* breakpointManager); ~ThreadHandler(); void Init(); @@ -30,6 +37,8 @@ public: thread_id ThreadID() const { return fThread->ID(); } Thread* GetThread() const { return fThread; } + // All Handle*() methods are invoked in team debugger thread, + // looper lock held. bool HandleThreadDebugged( ThreadDebuggedEvent* event); bool HandleDebuggerCall( @@ -50,16 +59,44 @@ public: void HandleStackTraceChanged(); private: + // ImageDebugInfoProvider + virtual status_t GetImageDebugInfo(Image* image, + ImageDebugInfo*& _info); + bool _HandleThreadStopped(CpuState* cpuState); void _SetThreadState(uint32 state, CpuState* cpuState); + Statement* _GetStatementAtInstructionPointer( + StackFrame* frame); + + void _StepFallback(); + bool _DoStepOver(CpuState* cpuState); + + status_t _InstallTemporaryBreakpoint( + target_addr_t address); + void _UninstallTemporaryBreakpoint(); + void _ClearContinuationState(); + void _RunThread(target_addr_t instructionPointer); + void _SingleStepThread( + target_addr_t instructionPointer); + + + bool _HandleBreakpointHitStep(CpuState* cpuState); + bool _HandleSingleStepStep(CpuState* cpuState); + private: TeamDebugModel* fDebugModel; Thread* fThread; Worker* fWorker; DebuggerInterface* fDebuggerInterface; + BreakpointManager* fBreakpointManager; + uint32 fStepMode; + Statement* fStepStatement; + target_addr_t fBreakpointAddress; + target_addr_t fPreviousInstructionPointer; + bool fSingleStepping; }; diff --git a/src/apps/debugger/arch/Architecture.h b/src/apps/debugger/arch/Architecture.h index 32801964df..467aa5ea5a 100644 --- a/src/apps/debugger/arch/Architecture.h +++ b/src/apps/debugger/arch/Architecture.h @@ -9,16 +9,20 @@ #include +#include "ArchitectureTypes.h" + class CpuState; class DebuggerInterface; class FunctionDebugInfo; class Image; class ImageDebugInfoProvider; +class InstructionInfo; class Register; class SourceCode; class StackFrame; class StackTrace; +class Statement; class Team; @@ -51,9 +55,15 @@ public: // Called after a CreateStackFrame() // with the image/function corresponding // to the CPU state. + virtual status_t DisassembleCode(FunctionDebugInfo* function, const void* buffer, size_t bufferSize, SourceCode*& _sourceCode) = 0; + virtual status_t GetStatement(FunctionDebugInfo* function, + target_addr_t address, + Statement*& _statement) = 0; + virtual status_t GetInstructionInfo(target_addr_t address, + InstructionInfo& _info) = 0; status_t CreateStackTrace(Team* team, ImageDebugInfoProvider* imageInfoProvider, diff --git a/src/apps/debugger/arch/InstructionInfo.cpp b/src/apps/debugger/arch/InstructionInfo.cpp new file mode 100644 index 0000000000..03afae474e --- /dev/null +++ b/src/apps/debugger/arch/InstructionInfo.cpp @@ -0,0 +1,44 @@ +/* + * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + +#include "InstructionInfo.h" + + +InstructionInfo::InstructionInfo() + : + fAddress(0), + fSize(0), + fType(INSTRUCTION_TYPE_OTHER), + fBreakpointAllowed(false), + fDisassembledLine() +{ +} + + +InstructionInfo::InstructionInfo(target_addr_t address, target_size_t size, + instruction_type type, bool breakpointAllowed, + const BString& disassembledLine) + : + fAddress(address), + fSize(size), + fType(type), + fBreakpointAllowed(breakpointAllowed), + fDisassembledLine(disassembledLine) +{ +} + + +bool +InstructionInfo::SetTo(target_addr_t address, target_size_t size, + instruction_type type, bool breakpointAllowed, + const BString& disassembledLine) +{ + fAddress = address; + fSize = size; + fType = type; + fBreakpointAllowed = breakpointAllowed; + fDisassembledLine = disassembledLine; + return disassembledLine.Length() == 0 || fDisassembledLine.Length() > 0; +} diff --git a/src/apps/debugger/arch/InstructionInfo.h b/src/apps/debugger/arch/InstructionInfo.h new file mode 100644 index 0000000000..03452a3f2e --- /dev/null +++ b/src/apps/debugger/arch/InstructionInfo.h @@ -0,0 +1,50 @@ +/* + * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ +#ifndef INSTRUCTION_INFO_H +#define INSTRUCTION_INFO_H + +#include + +#include "ArchitectureTypes.h" + + +enum instruction_type { + INSTRUCTION_TYPE_SUBROUTINE_CALL, + INSTRUCTION_TYPE_OTHER +}; + + +class InstructionInfo { +public: + InstructionInfo(); + InstructionInfo(target_addr_t address, + target_size_t size, instruction_type type, + bool breakpointAllowed, + const BString& disassembledLine); + + bool SetTo(target_addr_t address, target_size_t size, + instruction_type type, + bool breakpointAllowed, + const BString& disassembledLine); + + target_addr_t Address() const { return fAddress; } + target_size_t Size() const { return fSize; } + instruction_type Type() const { return fType; } + bool IsBreakpointAllowed() const + { return fBreakpointAllowed; } + const char* DisassembledLine() const + { return fDisassembledLine.String(); } + + +private: + target_addr_t fAddress; + target_size_t fSize; + instruction_type fType; + bool fBreakpointAllowed; + BString fDisassembledLine; +}; + + +#endif // INSTRUCTION_INFO_H diff --git a/src/apps/debugger/arch/x86/ArchitectureX86.cpp b/src/apps/debugger/arch/x86/ArchitectureX86.cpp index 10b03cd948..d4df937d86 100644 --- a/src/apps/debugger/arch/x86/ArchitectureX86.cpp +++ b/src/apps/debugger/arch/x86/ArchitectureX86.cpp @@ -15,6 +15,7 @@ #include "DebuggerInterface.h" #include "DisassembledCode.h" #include "FunctionDebugInfo.h" +#include "InstructionInfo.h" #include "StackFrame.h" #include "Statement.h" @@ -307,6 +308,80 @@ ArchitectureX86::DisassembleCode(FunctionDebugInfo* function, } +status_t +ArchitectureX86::GetStatement(FunctionDebugInfo* function, + target_addr_t address, Statement*& _statement) +{ +// TODO: This is not architecture dependent anymore! + // get the instruction info + InstructionInfo info; + status_t error = GetInstructionInfo(address, info); + if (error != B_OK) + return error; + + // create a statement + ContiguousStatement* statement = new(std::nothrow) ContiguousStatement( + SourceLocation(0), SourceLocation(1), + TargetAddressRange(info.Address(), info.Size()), + info.IsBreakpointAllowed()); + if (statement == NULL) + return B_NO_MEMORY; + + _statement = statement; + return B_OK; +} + + +status_t +ArchitectureX86::GetInstructionInfo(target_addr_t address, + InstructionInfo& _info) +{ + // read the code + uint8 buffer[16]; + // TODO: What's the maximum instruction size? + ssize_t bytesRead = fDebuggerInterface->ReadMemory(address, buffer, + sizeof(buffer)); + if (bytesRead < 0) + return bytesRead; + + // init disassembler + DisassemblerX86 disassembler; + status_t error = disassembler.Init(address, buffer, bytesRead); + if (error != B_OK) + return error; + + // disassemble the instruction + BString line; + target_addr_t instructionAddress; + target_size_t instructionSize; + bool breakpointAllowed; + error = disassembler.GetNextInstruction(line, instructionAddress, + instructionSize, breakpointAllowed); + if (error != B_OK) + return error; + + instruction_type instructionType = INSTRUCTION_TYPE_OTHER; + if (buffer[0] == 0xff) { + // absolute call with r/m32 + instructionType = INSTRUCTION_TYPE_SUBROUTINE_CALL; + } else if (buffer[0] == 0xe8 && instructionSize == 5) { + // relative call with rel32 -- don't categorize the call with 0 as + // subroutine call, since it is only used to get the address of the GOT + if (buffer[1] != 0 || buffer[2] != 0 || buffer[3] != 0 + || buffer[4] != 0) { + instructionType = INSTRUCTION_TYPE_SUBROUTINE_CALL; + } + } + + if (!_info.SetTo(instructionAddress, instructionSize, instructionType, + breakpointAllowed, line)) { + return B_NO_MEMORY; + } + + return B_OK; +} + + void ArchitectureX86::_AddRegister(int32 index, const char* name, register_format format, uint32 bitSize, register_type type) diff --git a/src/apps/debugger/arch/x86/ArchitectureX86.h b/src/apps/debugger/arch/x86/ArchitectureX86.h index 85d490cb90..83e12b02e3 100644 --- a/src/apps/debugger/arch/x86/ArchitectureX86.h +++ b/src/apps/debugger/arch/x86/ArchitectureX86.h @@ -33,9 +33,15 @@ public: Image* previousImage, FunctionDebugInfo* previousFunction, CpuState* previousCpuState); + virtual status_t DisassembleCode(FunctionDebugInfo* function, const void* buffer, size_t bufferSize, SourceCode*& _sourceCode); + virtual status_t GetStatement(FunctionDebugInfo* function, + target_addr_t address, + Statement*& _statement); + virtual status_t GetInstructionInfo(target_addr_t address, + InstructionInfo& _info); private: void _AddRegister(int32 index, const char* name, diff --git a/src/apps/debugger/debug_info/DebugInfo.h b/src/apps/debugger/debug_info/DebugInfo.h index 2db8fa4f23..7482cf8dd4 100644 --- a/src/apps/debugger/debug_info/DebugInfo.h +++ b/src/apps/debugger/debug_info/DebugInfo.h @@ -17,6 +17,7 @@ class FunctionDebugInfo; class Image; class SourceCode; class StackFrame; +class Statement; class DebugInfo : public Referenceable { @@ -37,6 +38,10 @@ public: virtual status_t LoadSourceCode(FunctionDebugInfo* function, SourceCode*& _sourceCode) = 0; // returns reference + virtual status_t GetStatement(FunctionDebugInfo* function, + target_addr_t address, + Statement*& _statement) = 0; + // returns reference }; diff --git a/src/apps/debugger/debug_info/DebuggerDebugInfo.cpp b/src/apps/debugger/debug_info/DebuggerDebugInfo.cpp index dd4a7b239d..9a1b71d684 100644 --- a/src/apps/debugger/debug_info/DebuggerDebugInfo.cpp +++ b/src/apps/debugger/debug_info/DebuggerDebugInfo.cpp @@ -95,6 +95,14 @@ DebuggerDebugInfo::LoadSourceCode(FunctionDebugInfo* function, } +status_t +DebuggerDebugInfo::GetStatement(FunctionDebugInfo* function, + target_addr_t address, Statement*& _statement) +{ + return fArchitecture->GetStatement(function, address, _statement); +} + + SymbolInfo* DebuggerDebugInfo::_FindSymbol(target_addr_t address) { diff --git a/src/apps/debugger/debug_info/DebuggerDebugInfo.h b/src/apps/debugger/debug_info/DebuggerDebugInfo.h index 11849a2d06..41a874cb21 100644 --- a/src/apps/debugger/debug_info/DebuggerDebugInfo.h +++ b/src/apps/debugger/debug_info/DebuggerDebugInfo.h @@ -36,6 +36,9 @@ public: CpuState*& _previousCpuState); virtual status_t LoadSourceCode(FunctionDebugInfo* function, SourceCode*& _sourceCode); + virtual status_t GetStatement(FunctionDebugInfo* function, + target_addr_t address, + Statement*& _statement); private: typedef BObjectList SymbolList;