Added transparent software breakpoint support for user debuggers:
* The bulk of the work -- i.e. juggling the software and hardware breakpoints, watchpoints, and memory reads/writes -- is done in the new class BreakpointManager. * For the architectures a few capability macros have to be defined, one pointing to the software breakpoint instruction opcode. Done for x86. * Some more simplifications in the user debugger code, made possible by the recently introduced debugger_changed_condition attribute. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@31214 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
parent
63e909772d
commit
b0f12d64f4
@ -455,9 +455,6 @@ typedef struct {
|
||||
typedef struct {
|
||||
debug_origin origin;
|
||||
debug_cpu_state cpu_state; // cpu state
|
||||
bool software; // true, if the is a software breakpoint
|
||||
// (i.e. caused by a respective trap
|
||||
// instruction)
|
||||
} debug_breakpoint_hit;
|
||||
|
||||
// B_DEBUGGER_MESSAGE_WATCHPOINT_HIT
|
||||
|
@ -50,6 +50,34 @@ status_t arch_clear_kernel_watchpoint(void *address);
|
||||
|
||||
#include <arch_user_debugger.h>
|
||||
|
||||
// Defaults for macros defined by the architecture specific header:
|
||||
|
||||
// maximum number of instruction breakpoints
|
||||
#ifndef DEBUG_MAX_BREAKPOINTS
|
||||
# define DEBUG_MAX_BREAKPOINTS 0
|
||||
#endif
|
||||
|
||||
// maximum number of data watchpoints
|
||||
#ifndef DEBUG_MAX_WATCHPOINTS
|
||||
# define DEBUG_MAX_WATCHPOINTS 0
|
||||
#endif
|
||||
|
||||
// the software breakpoint instruction
|
||||
#if !defined(DEBUG_SOFTWARE_BREAKPOINT) \
|
||||
|| !defined(DEBUG_SOFTWARE_BREAKPOINT_SIZE)
|
||||
# undef DEBUG_SOFTWARE_BREAKPOINT
|
||||
# undef DEBUG_SOFTWARE_BREAKPOINT_SIZE
|
||||
# define DEBUG_SOFTWARE_BREAKPOINT NULL
|
||||
# define DEBUG_SOFTWARE_BREAKPOINT_SIZE 0
|
||||
#endif
|
||||
|
||||
// Boolean whether break- and watchpoints use the same registers. If != 0, then
|
||||
// DEBUG_MAX_BREAKPOINTS == DEBUG_MAX_WATCHPOINTS and either specifies the
|
||||
// total count of break- plus watchpoints.
|
||||
#ifndef DEBUG_SHARED_BREAK_AND_WATCHPOINTS
|
||||
# define DEBUG_SHARED_BREAK_AND_WATCHPOINTS 0
|
||||
#endif
|
||||
|
||||
#endif // _ASSEMBLER
|
||||
|
||||
#endif // KERNEL_ARCH_USER_DEBUGGER_H
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2005, Ingo Weinhold, bonefish@users.sf.net.
|
||||
* Copyright 2005-2009, Ingo Weinhold, bonefish@users.sf.net.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
#ifndef _KERNEL_ARCH_X86_USER_DEBUGGER_H
|
||||
@ -9,9 +9,7 @@
|
||||
|
||||
// number of breakpoints the CPU supports
|
||||
// Actually it supports 4, but DR3 is used to hold the struct thread*.
|
||||
enum {
|
||||
X86_BREAKPOINT_COUNT = 3,
|
||||
};
|
||||
#define X86_BREAKPOINT_COUNT 3
|
||||
|
||||
// debug status register DR6
|
||||
enum {
|
||||
@ -110,6 +108,9 @@ struct arch_thread_debug_info {
|
||||
uint32 flags;
|
||||
};
|
||||
|
||||
// The software breakpoint instruction (int3).
|
||||
extern const uint8 kX86SoftwareBreakpoint[1];
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@ -129,4 +130,11 @@ extern void x86_init_user_debug();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Feature macros we're supposed to define.
|
||||
#define DEBUG_MAX_BREAKPOINTS X86_BREAKPOINT_COUNT
|
||||
#define DEBUG_MAX_WATCHPOINTS X86_BREAKPOINT_COUNT
|
||||
#define DEBUG_SOFTWARE_BREAKPOINT kX86SoftwareBreakpoint
|
||||
#define DEBUG_SOFTWARE_BREAKPOINT_SIZE 1
|
||||
#define DEBUG_SHARED_BREAK_AND_WATCHPOINTS 1
|
||||
|
||||
#endif // _KERNEL_ARCH_X86_USER_DEBUGGER_H
|
||||
|
@ -20,6 +20,7 @@
|
||||
#define B_DEBUG_PROFILE_BUFFER_FLUSH_THRESHOLD 70 /* in % */
|
||||
|
||||
|
||||
struct BreakpointManager;
|
||||
struct ConditionVariable;
|
||||
struct function_profile_info;
|
||||
struct thread;
|
||||
@ -70,6 +71,9 @@ struct team_debug_info {
|
||||
// variable the thread won't be deleted (until unsetting it) -- it might
|
||||
// be removed from the team hash table, though.
|
||||
|
||||
struct BreakpointManager* breakpoint_manager;
|
||||
// manages hard- and software breakpoints
|
||||
|
||||
struct arch_team_debug_info arch_info;
|
||||
};
|
||||
|
||||
|
@ -30,6 +30,9 @@
|
||||
#define B_WATCHPOINT_NOT_FOUND B_NAME_NOT_FOUND
|
||||
// ToDo: Make those real error codes.
|
||||
|
||||
// The software breakpoint instruction (int3).
|
||||
const uint8 kX86SoftwareBreakpoint[1] = { 0xcc };
|
||||
|
||||
// maps breakpoint slot index to LEN_i LSB number
|
||||
static const uint32 sDR7Len[4] = {
|
||||
X86_DR7_LEN0_LSB, X86_DR7_LEN1_LSB, X86_DR7_LEN2_LSB, X86_DR7_LEN3_LSB
|
||||
@ -902,6 +905,9 @@ x86_handle_breakpoint_exception(struct iframe *frame)
|
||||
{
|
||||
TRACE(("i386_handle_breakpoint_exception()\n"));
|
||||
|
||||
// reset eip to the int3 instruction
|
||||
frame->eip--;
|
||||
|
||||
if (!IFRAME_IS_USER(frame)) {
|
||||
panic("breakpoint exception in kernel mode");
|
||||
return;
|
||||
|
783
src/system/kernel/debug/BreakpointManager.cpp
Normal file
783
src/system/kernel/debug/BreakpointManager.cpp
Normal file
@ -0,0 +1,783 @@
|
||||
/*
|
||||
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
|
||||
#include "BreakpointManager.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <AutoDeleter.h>
|
||||
|
||||
#include <commpage_defs.h>
|
||||
#include <kernel.h>
|
||||
#include <util/AutoLock.h>
|
||||
#include <vm.h>
|
||||
|
||||
|
||||
//#define TRACE_BREAKPOINT_MANAGER
|
||||
#ifdef TRACE_BREAKPOINT_MANAGER
|
||||
# define TRACE(x...) dprintf(x)
|
||||
#else
|
||||
# define TRACE(x...) do {} while (false)
|
||||
#endif
|
||||
|
||||
|
||||
// soft limit for the number of breakpoints
|
||||
const int32 kMaxBreakpointCount = 10240;
|
||||
|
||||
|
||||
BreakpointManager::InstalledBreakpoint::InstalledBreakpoint(addr_t address)
|
||||
:
|
||||
breakpoint(NULL),
|
||||
address(address)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark -
|
||||
|
||||
|
||||
BreakpointManager::BreakpointManager()
|
||||
:
|
||||
fBreakpointCount(0),
|
||||
fWatchpointCount(0)
|
||||
{
|
||||
rw_lock_init(&fLock, "breakpoint manager");
|
||||
}
|
||||
|
||||
|
||||
BreakpointManager::~BreakpointManager()
|
||||
{
|
||||
WriteLocker locker(fLock);
|
||||
|
||||
// delete the installed breakpoint objects
|
||||
BreakpointTree::Iterator it = fBreakpoints.GetIterator();
|
||||
while (InstalledBreakpoint* installedBreakpoint = it.Next()) {
|
||||
it.Remove();
|
||||
|
||||
// delete underlying software breakpoint
|
||||
if (installedBreakpoint->breakpoint->software)
|
||||
delete installedBreakpoint->breakpoint;
|
||||
|
||||
delete installedBreakpoint;
|
||||
}
|
||||
|
||||
// delete the watchpoints
|
||||
while (InstalledWatchpoint* watchpoint = fWatchpoints.RemoveHead())
|
||||
delete watchpoint;
|
||||
|
||||
// delete the hardware breakpoint objects
|
||||
while (Breakpoint* breakpoint = fHardwareBreakpoints.RemoveHead())
|
||||
delete breakpoint;
|
||||
|
||||
rw_lock_destroy(&fLock);
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
BreakpointManager::Init()
|
||||
{
|
||||
// create objects for the hardware breakpoints
|
||||
for (int32 i = 0; i < DEBUG_MAX_BREAKPOINTS; i++) {
|
||||
Breakpoint* breakpoint = new(std::nothrow) Breakpoint;
|
||||
if (breakpoint == NULL)
|
||||
return B_NO_MEMORY;
|
||||
|
||||
breakpoint->address = 0;
|
||||
breakpoint->installedBreakpoint = NULL;
|
||||
breakpoint->used = false;
|
||||
breakpoint->software = false;
|
||||
|
||||
fHardwareBreakpoints.Add(breakpoint);
|
||||
}
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
BreakpointManager::InstallBreakpoint(void* _address)
|
||||
{
|
||||
const addr_t address = (addr_t)_address;
|
||||
|
||||
WriteLocker locker(fLock);
|
||||
|
||||
if (fBreakpointCount >= kMaxBreakpointCount)
|
||||
return B_BUSY;
|
||||
|
||||
// check whether there's already a breakpoint at the address
|
||||
InstalledBreakpoint* installed = fBreakpoints.Lookup(address);
|
||||
if (installed != NULL)
|
||||
return B_BAD_VALUE;
|
||||
|
||||
// create the breakpoint object
|
||||
installed = new(std::nothrow) InstalledBreakpoint(address);
|
||||
if (installed == NULL)
|
||||
return B_NO_MEMORY;
|
||||
ObjectDeleter<InstalledBreakpoint> installedDeleter(installed);
|
||||
|
||||
// If we still have enough hardware breakpoints left, install a hardware
|
||||
// breakpoint.
|
||||
Breakpoint* breakpoint = _GetUnusedHardwareBreakpoint(false);
|
||||
if (breakpoint != NULL) {
|
||||
status_t error = _InstallHardwareBreakpoint(breakpoint, address);
|
||||
if (error != B_OK)
|
||||
return error;
|
||||
|
||||
breakpoint->installedBreakpoint = installed;
|
||||
installed->breakpoint = breakpoint;
|
||||
} else {
|
||||
// install a software breakpoint
|
||||
status_t error = _InstallSoftwareBreakpoint(installed, address);
|
||||
if (error != B_OK)
|
||||
return error;
|
||||
}
|
||||
|
||||
fBreakpoints.Insert(installed);
|
||||
installedDeleter.Detach();
|
||||
fBreakpointCount++;
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
BreakpointManager::UninstallBreakpoint(void* _address)
|
||||
{
|
||||
const addr_t address = (addr_t)_address;
|
||||
|
||||
WriteLocker locker(fLock);
|
||||
|
||||
InstalledBreakpoint* installed = fBreakpoints.Lookup(address);
|
||||
if (installed == NULL)
|
||||
return B_BAD_VALUE;
|
||||
|
||||
if (installed->breakpoint->software)
|
||||
_UninstallSoftwareBreakpoint(installed->breakpoint);
|
||||
else
|
||||
_UninstallHardwareBreakpoint(installed->breakpoint);
|
||||
|
||||
fBreakpoints.Remove(installed);
|
||||
delete installed;
|
||||
fBreakpointCount--;
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
BreakpointManager::InstallWatchpoint(void* _address, uint32 type, int32 length)
|
||||
{
|
||||
const addr_t address = (addr_t)_address;
|
||||
|
||||
WriteLocker locker(fLock);
|
||||
|
||||
InstalledWatchpoint* watchpoint = _FindWatchpoint(address);
|
||||
if (watchpoint != NULL)
|
||||
return B_BAD_VALUE;
|
||||
|
||||
#if DEBUG_SHARED_BREAK_AND_WATCHPOINTS
|
||||
// We need at least one hardware breakpoint for our breakpoint management.
|
||||
if (fWatchpointCount + 1 >= DEBUG_MAX_WATCHPOINTS)
|
||||
return B_BUSY;
|
||||
#else
|
||||
if (fWatchpointCount >= DEBUG_MAX_WATCHPOINTS)
|
||||
return B_BUSY;
|
||||
#endif
|
||||
|
||||
watchpoint = new(std::nothrow) InstalledWatchpoint;
|
||||
if (watchpoint == NULL)
|
||||
return B_NO_MEMORY;
|
||||
ObjectDeleter<InstalledWatchpoint> watchpointDeleter(watchpoint);
|
||||
|
||||
status_t error = _InstallWatchpoint(watchpoint, address, type, length);
|
||||
if (error != B_OK)
|
||||
return error;
|
||||
|
||||
fWatchpoints.Add(watchpointDeleter.Detach());
|
||||
fWatchpointCount++;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
BreakpointManager::UninstallWatchpoint(void* address)
|
||||
{
|
||||
WriteLocker locker(fLock);
|
||||
|
||||
InstalledWatchpoint* watchpoint = _FindWatchpoint((addr_t)address);
|
||||
if (watchpoint == NULL)
|
||||
return B_BAD_VALUE;
|
||||
|
||||
ObjectDeleter<InstalledWatchpoint> deleter(watchpoint);
|
||||
fWatchpoints.Remove(watchpoint);
|
||||
fWatchpointCount--;
|
||||
|
||||
return _UninstallWatchpoint(watchpoint);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
BreakpointManager::RemoveAllBreakpoints()
|
||||
{
|
||||
WriteLocker locker(fLock);
|
||||
|
||||
// remove the breakpoints
|
||||
BreakpointTree::Iterator it = fBreakpoints.GetIterator();
|
||||
while (InstalledBreakpoint* installedBreakpoint = it.Next()) {
|
||||
it.Remove();
|
||||
|
||||
// uninstall underlying hard/software breakpoint
|
||||
if (installedBreakpoint->breakpoint->software)
|
||||
_UninstallSoftwareBreakpoint(installedBreakpoint->breakpoint);
|
||||
else
|
||||
_UninstallHardwareBreakpoint(installedBreakpoint->breakpoint);
|
||||
|
||||
delete installedBreakpoint;
|
||||
}
|
||||
|
||||
// remove the watchpoints
|
||||
while (InstalledWatchpoint* watchpoint = fWatchpoints.RemoveHead()) {
|
||||
_UninstallWatchpoint(watchpoint);
|
||||
delete watchpoint;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*! \brief Returns whether the given address can be accessed in principle.
|
||||
No check whether there's an actually accessible area is performed, though.
|
||||
*/
|
||||
/*static*/ bool
|
||||
BreakpointManager::CanAccessAddress(const void* _address, bool write)
|
||||
{
|
||||
const addr_t address = (addr_t)_address;
|
||||
|
||||
// user addresses are always fine
|
||||
if (IS_USER_ADDRESS(address))
|
||||
return true;
|
||||
|
||||
// a commpage address can at least be read
|
||||
if (address >= USER_COMMPAGE_ADDR
|
||||
&& address < USER_COMMPAGE_ADDR + COMMPAGE_SIZE) {
|
||||
return !write;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/*! \brief Reads data from user memory.
|
||||
|
||||
Tries to read \a size bytes of data from user memory address \a address
|
||||
into the supplied buffer \a buffer. If only a part could be read the
|
||||
function won't fail. The number of bytes actually read is return through
|
||||
\a bytesRead.
|
||||
|
||||
\param address The user memory address from which to read.
|
||||
\param buffer The buffer into which to write.
|
||||
\param size The number of bytes to read.
|
||||
\param bytesRead Will be set to the number of bytes actually read.
|
||||
\return \c B_OK, if reading went fine. Then \a bytesRead will be set to
|
||||
the amount of data actually read. An error indicates that nothing
|
||||
has been read.
|
||||
*/
|
||||
status_t
|
||||
BreakpointManager::ReadMemory(const void* _address, void* buffer, size_t size,
|
||||
size_t& bytesRead)
|
||||
{
|
||||
const addr_t address = (addr_t)_address;
|
||||
|
||||
ReadLocker locker(fLock);
|
||||
|
||||
status_t error = _ReadMemory(address, buffer, size, bytesRead);
|
||||
if (error != B_OK)
|
||||
return error;
|
||||
|
||||
// If we have software breakpoints installed, fix the buffer not to contain
|
||||
// any of them.
|
||||
|
||||
// address of the first possibly intersecting software breakpoint
|
||||
const addr_t startAddress
|
||||
= std::max(address, (addr_t)DEBUG_SOFTWARE_BREAKPOINT_SIZE - 1)
|
||||
- (DEBUG_SOFTWARE_BREAKPOINT_SIZE - 1);
|
||||
|
||||
BreakpointTree::Iterator it = fBreakpoints.GetIterator(startAddress, true,
|
||||
true);
|
||||
while (InstalledBreakpoint* installed = it.Next()) {
|
||||
Breakpoint* breakpoint = installed->breakpoint;
|
||||
if (breakpoint->address >= address + size)
|
||||
break;
|
||||
|
||||
if (breakpoint->software) {
|
||||
// Software breakpoint intersects -- replace the read data with
|
||||
// the data saved in the breakpoint object.
|
||||
addr_t minAddress = std::max(breakpoint->address, address);
|
||||
size_t toCopy = std::min(address + size,
|
||||
breakpoint->address + DEBUG_SOFTWARE_BREAKPOINT_SIZE)
|
||||
- minAddress;
|
||||
memcpy((uint8*)buffer + (minAddress - address),
|
||||
breakpoint->softwareData + (minAddress - breakpoint->address),
|
||||
toCopy);
|
||||
}
|
||||
}
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
BreakpointManager::WriteMemory(void* _address, const void* _buffer, size_t size,
|
||||
size_t& bytesWritten)
|
||||
{
|
||||
bytesWritten = 0;
|
||||
|
||||
if (size == 0)
|
||||
return B_OK;
|
||||
|
||||
addr_t address = (addr_t)_address;
|
||||
const uint8* buffer = (uint8*)_buffer;
|
||||
|
||||
WriteLocker locker(fLock);
|
||||
|
||||
// We don't want to overwrite software breakpoints, so things are a bit more
|
||||
// complicated. We iterate through the intersecting software breakpoints,
|
||||
// writing the memory between them normally, but skipping the breakpoints
|
||||
// itself. We write into their softwareData instead.
|
||||
|
||||
// Get the first breakpoint -- if it starts before the address, we'll
|
||||
// handle it separately to make things in the main loop simpler.
|
||||
const addr_t startAddress
|
||||
= std::max(address, (addr_t)DEBUG_SOFTWARE_BREAKPOINT_SIZE - 1)
|
||||
- (DEBUG_SOFTWARE_BREAKPOINT_SIZE - 1);
|
||||
|
||||
BreakpointTree::Iterator it = fBreakpoints.GetIterator(startAddress, true,
|
||||
true);
|
||||
InstalledBreakpoint* installed = it.Next();
|
||||
while (installed != NULL) {
|
||||
Breakpoint* breakpoint = installed->breakpoint;
|
||||
if (breakpoint->address >= address)
|
||||
break;
|
||||
|
||||
if (breakpoint->software) {
|
||||
// We've got a breakpoint that is partially intersecting with the
|
||||
// beginning of the address range to write.
|
||||
size_t toCopy = std::min(address + size,
|
||||
breakpoint->address + DEBUG_SOFTWARE_BREAKPOINT_SIZE)
|
||||
- address;
|
||||
memcpy(breakpoint->softwareData + (address - breakpoint->address),
|
||||
buffer, toCopy);
|
||||
|
||||
address += toCopy;
|
||||
size -= toCopy;
|
||||
bytesWritten += toCopy;
|
||||
buffer += toCopy;
|
||||
}
|
||||
|
||||
installed = it.Next();
|
||||
}
|
||||
|
||||
// loop through the breakpoints intersecting with the range
|
||||
while (installed != NULL) {
|
||||
Breakpoint* breakpoint = installed->breakpoint;
|
||||
if (breakpoint->address >= address + size)
|
||||
break;
|
||||
|
||||
if (breakpoint->software) {
|
||||
// write the data up to the breakpoint (if any)
|
||||
size_t toCopy = breakpoint->address - address;
|
||||
if (toCopy > 0) {
|
||||
size_t chunkWritten;
|
||||
status_t error = _WriteMemory(address, buffer, toCopy,
|
||||
chunkWritten);
|
||||
if (error != B_OK)
|
||||
return bytesWritten > 0 ? B_OK : error;
|
||||
|
||||
address += chunkWritten;
|
||||
size -= chunkWritten;
|
||||
bytesWritten += chunkWritten;
|
||||
buffer += chunkWritten;
|
||||
|
||||
if (chunkWritten < toCopy)
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
// write to the breakpoint data
|
||||
toCopy = std::min(size, (size_t)DEBUG_SOFTWARE_BREAKPOINT_SIZE);
|
||||
memcpy(breakpoint->softwareData, buffer, toCopy);
|
||||
|
||||
address += toCopy;
|
||||
size -= toCopy;
|
||||
bytesWritten += toCopy;
|
||||
buffer += toCopy;
|
||||
}
|
||||
|
||||
installed = it.Next();
|
||||
}
|
||||
|
||||
// write remaining data
|
||||
if (size > 0) {
|
||||
size_t chunkWritten;
|
||||
status_t error = _WriteMemory(address, buffer, size, chunkWritten);
|
||||
if (error != B_OK)
|
||||
return bytesWritten > 0 ? B_OK : error;
|
||||
|
||||
bytesWritten += chunkWritten;
|
||||
}
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
BreakpointManager::PrepareToContinue(void* _address)
|
||||
{
|
||||
const addr_t address = (addr_t)_address;
|
||||
|
||||
WriteLocker locker(fLock);
|
||||
|
||||
// Check whether there's a software breakpoint at the continuation address.
|
||||
InstalledBreakpoint* installed = fBreakpoints.Lookup(address);
|
||||
if (installed == NULL || !installed->breakpoint->software)
|
||||
return;
|
||||
|
||||
// We need to replace the software breakpoint by a hardware one, or
|
||||
// we can't continue the thread.
|
||||
Breakpoint* breakpoint = _GetUnusedHardwareBreakpoint(true);
|
||||
if (breakpoint == NULL) {
|
||||
dprintf("Failed to allocate a hardware breakpoint.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
status_t error = _InstallHardwareBreakpoint(breakpoint, address);
|
||||
if (error != B_OK)
|
||||
return;
|
||||
|
||||
_UninstallSoftwareBreakpoint(installed->breakpoint);
|
||||
|
||||
breakpoint->installedBreakpoint = installed;
|
||||
installed->breakpoint = breakpoint;
|
||||
}
|
||||
|
||||
|
||||
BreakpointManager::Breakpoint*
|
||||
BreakpointManager::_GetUnusedHardwareBreakpoint(bool force)
|
||||
{
|
||||
// try to find a free one first
|
||||
for (BreakpointList::Iterator it = fHardwareBreakpoints.GetIterator();
|
||||
Breakpoint* breakpoint = it.Next();) {
|
||||
if (!breakpoint->used)
|
||||
return breakpoint;
|
||||
}
|
||||
|
||||
if (!force)
|
||||
return NULL;
|
||||
|
||||
// replace one by a software breakpoint
|
||||
for (BreakpointList::Iterator it = fHardwareBreakpoints.GetIterator();
|
||||
Breakpoint* breakpoint = it.Next();) {
|
||||
if (breakpoint->installedBreakpoint == NULL)
|
||||
continue;
|
||||
|
||||
status_t error = _InstallSoftwareBreakpoint(
|
||||
breakpoint->installedBreakpoint, breakpoint->address);
|
||||
if (error != B_OK)
|
||||
continue;
|
||||
|
||||
if (_UninstallHardwareBreakpoint(breakpoint) == B_OK)
|
||||
return breakpoint;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
BreakpointManager::_InstallSoftwareBreakpoint(InstalledBreakpoint* installed,
|
||||
addr_t address)
|
||||
{
|
||||
Breakpoint* breakpoint = new(std::nothrow) Breakpoint;
|
||||
if (breakpoint == NULL)
|
||||
return B_NO_MEMORY;
|
||||
ObjectDeleter<Breakpoint> breakpointDeleter(breakpoint);
|
||||
|
||||
breakpoint->address = address;
|
||||
breakpoint->installedBreakpoint = installed;
|
||||
breakpoint->used = true;
|
||||
breakpoint->software = true;
|
||||
|
||||
// save the memory where the software breakpoint shall be installed
|
||||
size_t bytesTransferred;
|
||||
status_t error = _ReadMemory(address, breakpoint->softwareData,
|
||||
DEBUG_SOFTWARE_BREAKPOINT_SIZE, bytesTransferred);
|
||||
if (error != B_OK)
|
||||
return error;
|
||||
if (bytesTransferred != DEBUG_SOFTWARE_BREAKPOINT_SIZE)
|
||||
return B_BAD_ADDRESS;
|
||||
|
||||
// write the breakpoint code
|
||||
error = _WriteMemory(address, DEBUG_SOFTWARE_BREAKPOINT,
|
||||
DEBUG_SOFTWARE_BREAKPOINT_SIZE, bytesTransferred);
|
||||
if (error != B_OK)
|
||||
return error;
|
||||
|
||||
if (bytesTransferred < DEBUG_SOFTWARE_BREAKPOINT_SIZE) {
|
||||
// breakpoint written partially only -- undo the written part
|
||||
if (bytesTransferred > 0) {
|
||||
size_t dummy;
|
||||
_WriteMemory(address, breakpoint->softwareData, bytesTransferred,
|
||||
dummy);
|
||||
}
|
||||
return B_BAD_ADDRESS;
|
||||
}
|
||||
|
||||
installed->breakpoint = breakpoint;
|
||||
breakpointDeleter.Detach();
|
||||
|
||||
TRACE("installed software breakpoint at %#lx\n", address);
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
BreakpointManager::_UninstallSoftwareBreakpoint(Breakpoint* breakpoint)
|
||||
{
|
||||
size_t bytesWritten;
|
||||
_WriteMemory(breakpoint->address, breakpoint->softwareData,
|
||||
DEBUG_SOFTWARE_BREAKPOINT_SIZE, bytesWritten);
|
||||
|
||||
TRACE("uninstalled software breakpoint at %#lx\n", breakpoint->address);
|
||||
|
||||
delete breakpoint;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
BreakpointManager::_InstallHardwareBreakpoint(Breakpoint* breakpoint,
|
||||
addr_t address)
|
||||
{
|
||||
status_t error = arch_set_breakpoint((void*)address);
|
||||
if (error != B_OK)
|
||||
return error;
|
||||
|
||||
// move to the tail of the list
|
||||
fHardwareBreakpoints.Remove(breakpoint);
|
||||
fHardwareBreakpoints.Add(breakpoint);
|
||||
|
||||
TRACE("installed hardware breakpoint at %#lx\n", address);
|
||||
|
||||
breakpoint->address = address;
|
||||
breakpoint->used = true;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
BreakpointManager::_UninstallHardwareBreakpoint(Breakpoint* breakpoint)
|
||||
{
|
||||
status_t error = arch_clear_breakpoint((void*)breakpoint->address);
|
||||
if (error != B_OK)
|
||||
return error;
|
||||
|
||||
TRACE("uninstalled hardware breakpoint at %#lx\n", breakpoint->address);
|
||||
|
||||
breakpoint->used = false;
|
||||
breakpoint->installedBreakpoint = NULL;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
BreakpointManager::InstalledWatchpoint*
|
||||
BreakpointManager::_FindWatchpoint(addr_t address) const
|
||||
{
|
||||
for (InstalledWatchpointList::ConstIterator it = fWatchpoints.GetIterator();
|
||||
InstalledWatchpoint* watchpoint = it.Next();) {
|
||||
if (address == watchpoint->address)
|
||||
return watchpoint;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
BreakpointManager::_InstallWatchpoint(InstalledWatchpoint* watchpoint,
|
||||
addr_t address, uint32 type, int32 length)
|
||||
{
|
||||
#if DEBUG_SHARED_BREAK_AND_WATCHPOINTS
|
||||
// We need a hardware breakpoint.
|
||||
watchpoint->breakpoint = _GetUnusedHardwareBreakpoint(true);
|
||||
if (watchpoint->breakpoint == NULL) {
|
||||
dprintf("Failed to allocate a hardware breakpoint for watchpoint.\n");
|
||||
return B_BUSY;
|
||||
}
|
||||
#endif
|
||||
|
||||
status_t error = arch_set_watchpoint((void*)address, type, length);
|
||||
if (error != B_OK)
|
||||
return error;
|
||||
|
||||
watchpoint->address = address;
|
||||
|
||||
#if DEBUG_SHARED_BREAK_AND_WATCHPOINTS
|
||||
watchpoint->breakpoint->used = true;
|
||||
#endif
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
BreakpointManager::_UninstallWatchpoint(InstalledWatchpoint* watchpoint)
|
||||
{
|
||||
#if DEBUG_SHARED_BREAK_AND_WATCHPOINTS
|
||||
watchpoint->breakpoint->used = false;
|
||||
#endif
|
||||
|
||||
return arch_clear_watchpoint((void*)watchpoint->address);
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
BreakpointManager::_ReadMemory(const addr_t _address, void* _buffer,
|
||||
size_t size, size_t& bytesRead)
|
||||
{
|
||||
const uint8* address = (const uint8*)_address;
|
||||
uint8* buffer = (uint8*)_buffer;
|
||||
|
||||
// check the parameters
|
||||
if (!CanAccessAddress(address, false))
|
||||
return B_BAD_ADDRESS;
|
||||
if (size <= 0)
|
||||
return B_BAD_VALUE;
|
||||
|
||||
// If the region to be read crosses page boundaries, we split it up into
|
||||
// smaller chunks.
|
||||
status_t error = B_OK;
|
||||
bytesRead = 0;
|
||||
while (size > 0) {
|
||||
// check whether we're still in user address space
|
||||
if (!CanAccessAddress(address, false)) {
|
||||
error = B_BAD_ADDRESS;
|
||||
break;
|
||||
}
|
||||
|
||||
// don't cross page boundaries in a single read
|
||||
int32 toRead = size;
|
||||
int32 maxRead = B_PAGE_SIZE - (addr_t)address % B_PAGE_SIZE;
|
||||
if (toRead > maxRead)
|
||||
toRead = maxRead;
|
||||
|
||||
error = user_memcpy(buffer, address, toRead);
|
||||
if (error != B_OK)
|
||||
break;
|
||||
|
||||
bytesRead += toRead;
|
||||
address += toRead;
|
||||
buffer += toRead;
|
||||
size -= toRead;
|
||||
}
|
||||
|
||||
// If reading fails, we only fail, if we haven't read anything yet.
|
||||
if (error != B_OK) {
|
||||
if (bytesRead > 0)
|
||||
return B_OK;
|
||||
return error;
|
||||
}
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
BreakpointManager::_WriteMemory(addr_t _address, const void* _buffer,
|
||||
size_t size, size_t& bytesWritten)
|
||||
{
|
||||
uint8* address = (uint8*)_address;
|
||||
const uint8* buffer = (const uint8*)_buffer;
|
||||
|
||||
// check the parameters
|
||||
if (!CanAccessAddress(address, true))
|
||||
return B_BAD_ADDRESS;
|
||||
if (size <= 0)
|
||||
return B_BAD_VALUE;
|
||||
|
||||
// If the region to be written crosses area boundaries, we split it up into
|
||||
// smaller chunks.
|
||||
status_t error = B_OK;
|
||||
bytesWritten = 0;
|
||||
while (size > 0) {
|
||||
// check whether we're still in user address space
|
||||
if (!CanAccessAddress(address, true)) {
|
||||
error = B_BAD_ADDRESS;
|
||||
break;
|
||||
}
|
||||
|
||||
// get the area for the address (we need to use _user_area_for(), since
|
||||
// we're looking for a user area)
|
||||
area_id area = _user_area_for(address);
|
||||
if (area < 0) {
|
||||
TRACE("BreakpointManager::_WriteMemory(): area not found for "
|
||||
"address: %#lx: %lx\n", address, area);
|
||||
error = area;
|
||||
break;
|
||||
}
|
||||
|
||||
area_info areaInfo;
|
||||
status_t error = get_area_info(area, &areaInfo);
|
||||
if (error != B_OK) {
|
||||
TRACE("BreakpointManager::_WriteMemory(): failed to get info for "
|
||||
"area %ld: %lx\n", area, error);
|
||||
error = B_BAD_ADDRESS;
|
||||
break;
|
||||
}
|
||||
|
||||
// restrict this round of writing to the found area
|
||||
int32 toWrite = size;
|
||||
int32 maxWrite = (uint8*)areaInfo.address + areaInfo.size - address;
|
||||
if (toWrite > maxWrite)
|
||||
toWrite = maxWrite;
|
||||
|
||||
// if the area is read-only, we temporarily need to make it writable
|
||||
bool protectionChanged = false;
|
||||
if (!(areaInfo.protection & (B_WRITE_AREA | B_KERNEL_WRITE_AREA))) {
|
||||
error = set_area_protection(area,
|
||||
areaInfo.protection | B_WRITE_AREA);
|
||||
if (error != B_OK) {
|
||||
TRACE("BreakpointManager::_WriteMemory(): failed to set new "
|
||||
"protection for area %ld: %lx\n", area, error);
|
||||
break;
|
||||
}
|
||||
protectionChanged = true;
|
||||
}
|
||||
|
||||
// copy the memory
|
||||
error = user_memcpy(address, buffer, toWrite);
|
||||
|
||||
// reset the area protection
|
||||
if (protectionChanged)
|
||||
set_area_protection(area, areaInfo.protection);
|
||||
|
||||
if (error != B_OK) {
|
||||
TRACE("BreakpointManager::_WriteMemory(): user_memcpy() failed: "
|
||||
"%lx\n", error);
|
||||
break;
|
||||
}
|
||||
|
||||
bytesWritten += toWrite;
|
||||
address += toWrite;
|
||||
buffer += toWrite;
|
||||
size -= toWrite;
|
||||
}
|
||||
|
||||
// If writing fails, we only fail, if we haven't written anything yet.
|
||||
if (error != B_OK) {
|
||||
if (bytesWritten > 0)
|
||||
return B_OK;
|
||||
return error;
|
||||
}
|
||||
|
||||
return B_OK;
|
||||
}
|
144
src/system/kernel/debug/BreakpointManager.h
Normal file
144
src/system/kernel/debug/BreakpointManager.h
Normal file
@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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 <util/DoublyLinkedList.h>
|
||||
#include <util/SplayTree.h>
|
||||
|
||||
#include <arch/user_debugger.h>
|
||||
#include <lock.h>
|
||||
|
||||
|
||||
struct BreakpointManager {
|
||||
public:
|
||||
BreakpointManager();
|
||||
~BreakpointManager();
|
||||
|
||||
status_t Init();
|
||||
|
||||
status_t InstallBreakpoint(void* address);
|
||||
status_t UninstallBreakpoint(void* address);
|
||||
|
||||
status_t InstallWatchpoint(void* address, uint32 type,
|
||||
int32 length);
|
||||
status_t UninstallWatchpoint(void* address);
|
||||
|
||||
void RemoveAllBreakpoints();
|
||||
// break- and watchpoints
|
||||
|
||||
static bool CanAccessAddress(const void* address,
|
||||
bool write);
|
||||
status_t ReadMemory(const void* _address, void* _buffer,
|
||||
size_t size, size_t& bytesRead);
|
||||
status_t WriteMemory(void* _address, const void* _buffer,
|
||||
size_t size, size_t& bytesWritten);
|
||||
|
||||
void PrepareToContinue(void* address);
|
||||
|
||||
private:
|
||||
struct InstalledBreakpoint;
|
||||
|
||||
struct Breakpoint : DoublyLinkedListLinkImpl<Breakpoint> {
|
||||
addr_t address;
|
||||
InstalledBreakpoint* installedBreakpoint;
|
||||
bool used;
|
||||
bool software;
|
||||
uint8 softwareData[
|
||||
DEBUG_SOFTWARE_BREAKPOINT_SIZE];
|
||||
};
|
||||
|
||||
typedef DoublyLinkedList<Breakpoint> BreakpointList;
|
||||
|
||||
struct InstalledBreakpoint : SplayTreeLink<InstalledBreakpoint> {
|
||||
InstalledBreakpoint* splayNext;
|
||||
Breakpoint* breakpoint;
|
||||
addr_t address;
|
||||
|
||||
InstalledBreakpoint(addr_t address);
|
||||
};
|
||||
|
||||
struct InstalledWatchpoint
|
||||
: DoublyLinkedListLinkImpl<InstalledWatchpoint> {
|
||||
addr_t address;
|
||||
#if DEBUG_SHARED_BREAK_AND_WATCHPOINTS
|
||||
Breakpoint* breakpoint;
|
||||
#endif
|
||||
};
|
||||
|
||||
typedef DoublyLinkedList<InstalledWatchpoint>
|
||||
InstalledWatchpointList;
|
||||
|
||||
struct InstalledBreakpointSplayDefinition {
|
||||
typedef addr_t KeyType;
|
||||
typedef InstalledBreakpoint NodeType;
|
||||
|
||||
static const KeyType& GetKey(const InstalledBreakpoint* node)
|
||||
{
|
||||
return node->address;
|
||||
}
|
||||
|
||||
static SplayTreeLink<NodeType>* GetLink(
|
||||
InstalledBreakpoint* node)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
static int Compare(addr_t key, const InstalledBreakpoint* node)
|
||||
{
|
||||
if (key < node->address)
|
||||
return -1;
|
||||
return key == node->address ? 0 : 1;
|
||||
}
|
||||
|
||||
// for IteratableSplayTree only
|
||||
static NodeType** GetListLink(InstalledBreakpoint* node)
|
||||
{
|
||||
return &node->splayNext;
|
||||
}
|
||||
};
|
||||
|
||||
typedef IteratableSplayTree<InstalledBreakpointSplayDefinition>
|
||||
BreakpointTree;
|
||||
|
||||
private:
|
||||
Breakpoint* _GetUnusedHardwareBreakpoint(bool force);
|
||||
|
||||
status_t _InstallSoftwareBreakpoint(
|
||||
InstalledBreakpoint* installed,
|
||||
addr_t address);
|
||||
status_t _UninstallSoftwareBreakpoint(
|
||||
Breakpoint* breakpoint);
|
||||
|
||||
status_t _InstallHardwareBreakpoint(
|
||||
Breakpoint* breakpoint, addr_t address);
|
||||
status_t _UninstallHardwareBreakpoint(
|
||||
Breakpoint* breakpoint);
|
||||
|
||||
InstalledWatchpoint* _FindWatchpoint(addr_t address) const;
|
||||
status_t _InstallWatchpoint(
|
||||
InstalledWatchpoint* watchpoint,
|
||||
addr_t address, uint32 type, int32 length);
|
||||
status_t _UninstallWatchpoint(
|
||||
InstalledWatchpoint* watchpoint);
|
||||
|
||||
status_t _ReadMemory(const addr_t _address,
|
||||
void* _buffer, size_t size,
|
||||
size_t& bytesRead);
|
||||
status_t _WriteMemory(addr_t _address,
|
||||
const void* _buffer, size_t size,
|
||||
size_t& bytesWritten);
|
||||
|
||||
private:
|
||||
rw_lock fLock;
|
||||
BreakpointList fHardwareBreakpoints;
|
||||
BreakpointTree fBreakpoints;
|
||||
InstalledWatchpointList fWatchpoints;
|
||||
int32 fBreakpointCount;
|
||||
int32 fWatchpointCount;
|
||||
};
|
||||
|
||||
|
||||
#endif // BREAKPOINT_MANAGER_H
|
@ -7,6 +7,7 @@ UsePrivateHeaders shared ;
|
||||
|
||||
KernelMergeObject kernel_debug.o :
|
||||
blue_screen.cpp
|
||||
BreakpointManager.cpp
|
||||
debug.cpp
|
||||
debug_builtin_commands.cpp
|
||||
debug_commands.cpp
|
||||
|
@ -12,7 +12,6 @@
|
||||
|
||||
#include <arch/debug.h>
|
||||
#include <arch/user_debugger.h>
|
||||
#include <commpage_defs.h>
|
||||
#include <cpu.h>
|
||||
#include <debugger.h>
|
||||
#include <kernel.h>
|
||||
@ -32,6 +31,9 @@
|
||||
#include <AutoDeleter.h>
|
||||
#include <util/AutoLock.h>
|
||||
|
||||
#include "BreakpointManager.h"
|
||||
|
||||
|
||||
//#define TRACE_USER_DEBUGGER
|
||||
#ifdef TRACE_USER_DEBUGGER
|
||||
# define TRACE(x) dprintf x
|
||||
@ -40,6 +42,12 @@
|
||||
#endif
|
||||
|
||||
|
||||
// TODO: Since the introduction of team_debug_info::debugger_changed_condition
|
||||
// there's some potential for simplifications. E.g. clear_team_debug_info() and
|
||||
// destroy_team_debug_info() are now only used in nub_thread_cleanup() (plus
|
||||
// arch_clear_team_debug_info() in install_team_debugger_init_debug_infos()).
|
||||
|
||||
|
||||
static port_id sDefaultDebuggerPort = -1;
|
||||
// accessed atomically
|
||||
|
||||
@ -222,6 +230,7 @@ clear_team_debug_info(struct team_debug_info *info, bool initLock)
|
||||
info->debugger_write_lock = -1;
|
||||
info->causing_thread = -1;
|
||||
info->image_event = 0;
|
||||
info->breakpoint_manager = NULL;
|
||||
|
||||
if (initLock) {
|
||||
B_INITIALIZE_SPINLOCK(&info->lock);
|
||||
@ -248,6 +257,10 @@ destroy_team_debug_info(struct team_debug_info *info)
|
||||
if (info) {
|
||||
arch_destroy_team_debug_info(&info->arch_info);
|
||||
|
||||
// delete the breakpoint manager
|
||||
delete info->breakpoint_manager ;
|
||||
info->breakpoint_manager = NULL;
|
||||
|
||||
// delete the debugger port write lock
|
||||
if (info->debugger_write_lock >= 0) {
|
||||
delete_sem(info->debugger_write_lock);
|
||||
@ -777,6 +790,20 @@ thread_hit_debug_event(debug_debugger_message event, const void *message,
|
||||
requireDebugger, restart);
|
||||
} while (result >= 0 && restart);
|
||||
|
||||
// Prepare to continue -- we install a debugger change condition, so no-one
|
||||
// will change the debugger while we're playing with the breakpoint manager.
|
||||
// TODO: Maybe better use ref-counting and a flag in the breakpoint manager.
|
||||
struct team* team = thread_get_current_thread()->team;
|
||||
ConditionVariable debugChangeCondition;
|
||||
prepare_debugger_change(team, debugChangeCondition);
|
||||
|
||||
if (team->debug_info.breakpoint_manager != NULL) {
|
||||
team->debug_info.breakpoint_manager->PrepareToContinue(
|
||||
arch_debug_get_interrupt_pc());
|
||||
}
|
||||
|
||||
finish_debugger_change(team);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -966,7 +993,6 @@ user_debug_team_deleted(team_id teamID, port_id debuggerPort)
|
||||
message.origin.nub_port = -1;
|
||||
write_port_etc(debuggerPort, B_DEBUGGER_MESSAGE_TEAM_DELETED, &message,
|
||||
sizeof(message), B_RELATIVE_TIMEOUT, 0);
|
||||
// TODO: Would it be OK to wait here?
|
||||
}
|
||||
}
|
||||
|
||||
@ -1217,7 +1243,6 @@ user_debug_breakpoint_hit(bool software)
|
||||
{
|
||||
// prepare the message
|
||||
debug_breakpoint_hit message;
|
||||
message.software = software;
|
||||
arch_get_debug_cpu_state(&message.cpu_state);
|
||||
|
||||
thread_hit_serious_debug_event(B_DEBUGGER_MESSAGE_BREAKPOINT_HIT, &message,
|
||||
@ -1515,6 +1540,9 @@ nub_thread_cleanup(struct thread *nubThread)
|
||||
RELEASE_TEAM_DEBUG_INFO_LOCK(nubThread->team->debug_info);
|
||||
restore_interrupts(state);
|
||||
|
||||
if (destroyDebugInfo)
|
||||
teamDebugInfo.breakpoint_manager->RemoveAllBreakpoints();
|
||||
|
||||
finish_debugger_change(nubThread->team);
|
||||
|
||||
if (destroyDebugInfo)
|
||||
@ -1526,183 +1554,6 @@ nub_thread_cleanup(struct thread *nubThread)
|
||||
}
|
||||
|
||||
|
||||
/*! \brief Returns whether the given address can be accessed in principle.
|
||||
No check whether there's an actually accessible area is performed, though.
|
||||
*/
|
||||
static bool
|
||||
can_access_address(const void* address, bool write)
|
||||
{
|
||||
// user addresses are always fine
|
||||
if (IS_USER_ADDRESS(address))
|
||||
return true;
|
||||
|
||||
// a commpage address can at least be read
|
||||
if ((addr_t)address >= USER_COMMPAGE_ADDR
|
||||
&& (addr_t)address < USER_COMMPAGE_ADDR + COMMPAGE_SIZE) {
|
||||
return !write;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/** \brief Reads data from user memory.
|
||||
*
|
||||
* Tries to read \a size bytes of data from user memory address \a address
|
||||
* into the supplied buffer \a buffer. If only a part could be read the
|
||||
* function won't fail. The number of bytes actually read is return through
|
||||
* \a bytesRead.
|
||||
*
|
||||
* \param address The user memory address from which to read.
|
||||
* \param buffer The buffer into which to write.
|
||||
* \param size The number of bytes to read.
|
||||
* \param bytesRead Will be set to the number of bytes actually read.
|
||||
* \return \c B_OK, if reading went fine. Then \a bytesRead will be set to
|
||||
* the amount of data actually read. An error indicates that nothing
|
||||
* has been read.
|
||||
*/
|
||||
static status_t
|
||||
read_user_memory(const void *_address, void *_buffer, int32 size,
|
||||
int32 &bytesRead)
|
||||
{
|
||||
const char *address = (const char*)_address;
|
||||
char *buffer = (char*)_buffer;
|
||||
|
||||
// check the parameters
|
||||
if (!can_access_address(address, false))
|
||||
return B_BAD_ADDRESS;
|
||||
if (size <= 0)
|
||||
return B_BAD_VALUE;
|
||||
|
||||
// If the region to be read crosses page boundaries, we split it up into
|
||||
// smaller chunks.
|
||||
status_t error = B_OK;
|
||||
bytesRead = 0;
|
||||
while (size > 0) {
|
||||
// check whether we're still in user address space
|
||||
if (!can_access_address(address, false)) {
|
||||
error = B_BAD_ADDRESS;
|
||||
break;
|
||||
}
|
||||
|
||||
// don't cross page boundaries in a single read
|
||||
int32 toRead = size;
|
||||
int32 maxRead = B_PAGE_SIZE - (addr_t)address % B_PAGE_SIZE;
|
||||
if (toRead > maxRead)
|
||||
toRead = maxRead;
|
||||
|
||||
error = user_memcpy(buffer, address, toRead);
|
||||
if (error != B_OK)
|
||||
break;
|
||||
|
||||
bytesRead += toRead;
|
||||
address += toRead;
|
||||
buffer += toRead;
|
||||
size -= toRead;
|
||||
}
|
||||
|
||||
// If reading fails, we only fail, if we haven't read anything yet.
|
||||
if (error != B_OK) {
|
||||
if (bytesRead > 0)
|
||||
return B_OK;
|
||||
return error;
|
||||
}
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
write_user_memory(void *_address, const void *_buffer, int32 size,
|
||||
int32 &bytesWritten)
|
||||
{
|
||||
char *address = (char*)_address;
|
||||
const char *buffer = (const char*)_buffer;
|
||||
|
||||
// check the parameters
|
||||
if (!can_access_address(address, true))
|
||||
return B_BAD_ADDRESS;
|
||||
if (size <= 0)
|
||||
return B_BAD_VALUE;
|
||||
|
||||
// If the region to be written crosses area boundaries, we split it up into
|
||||
// smaller chunks.
|
||||
status_t error = B_OK;
|
||||
bytesWritten = 0;
|
||||
while (size > 0) {
|
||||
// check whether we're still in user address space
|
||||
if (!can_access_address(address, true)) {
|
||||
error = B_BAD_ADDRESS;
|
||||
break;
|
||||
}
|
||||
|
||||
// get the area for the address (we need to use _user_area_for(), since
|
||||
// we're looking for a user area)
|
||||
area_id area = _user_area_for((void*)address);
|
||||
if (area < 0) {
|
||||
TRACE(("write_user_memory(): area not found for address: %p: "
|
||||
"%lx\n", address, area));
|
||||
error = area;
|
||||
break;
|
||||
}
|
||||
|
||||
area_info areaInfo;
|
||||
status_t error = get_area_info(area, &areaInfo);
|
||||
if (error != B_OK) {
|
||||
TRACE(("write_user_memory(): failed to get info for area %ld: "
|
||||
"%lx\n", area, error));
|
||||
error = B_BAD_ADDRESS;
|
||||
break;
|
||||
}
|
||||
|
||||
// restrict this round of writing to the found area
|
||||
int32 toWrite = size;
|
||||
int32 maxWrite = (char*)areaInfo.address + areaInfo.size - address;
|
||||
if (toWrite > maxWrite)
|
||||
toWrite = maxWrite;
|
||||
|
||||
// if the area is read-only, we temporarily need to make it writable
|
||||
bool protectionChanged = false;
|
||||
if (!(areaInfo.protection & (B_WRITE_AREA | B_KERNEL_WRITE_AREA))) {
|
||||
error = set_area_protection(area,
|
||||
areaInfo.protection | B_WRITE_AREA);
|
||||
if (error != B_OK) {
|
||||
TRACE(("write_user_memory(): failed to set new protection for "
|
||||
"area %ld: %lx\n", area, error));
|
||||
break;
|
||||
}
|
||||
protectionChanged = true;
|
||||
}
|
||||
|
||||
// copy the memory
|
||||
error = user_memcpy(address, buffer, toWrite);
|
||||
|
||||
// reset the area protection
|
||||
if (protectionChanged)
|
||||
set_area_protection(area, areaInfo.protection);
|
||||
|
||||
if (error != B_OK) {
|
||||
TRACE(("write_user_memory(): user_memcpy() failed: %lx\n", error));
|
||||
break;
|
||||
}
|
||||
|
||||
bytesWritten += toWrite;
|
||||
address += toWrite;
|
||||
buffer += toWrite;
|
||||
size -= toWrite;
|
||||
}
|
||||
|
||||
// If writing fails, we only fail, if we haven't written anything yet.
|
||||
if (error != B_OK) {
|
||||
if (bytesWritten > 0)
|
||||
return B_OK;
|
||||
return error;
|
||||
}
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
/** \brief Debug nub thread helper function that returns the debug port of
|
||||
* a thread of the same team.
|
||||
*/
|
||||
@ -1755,6 +1606,8 @@ debug_nub_thread(void *)
|
||||
|
||||
port_id port = nubThread->team->debug_info.nub_port;
|
||||
sem_id writeLock = nubThread->team->debug_info.debugger_write_lock;
|
||||
BreakpointManager* breakpointManager
|
||||
= nubThread->team->debug_info.breakpoint_manager;
|
||||
|
||||
RELEASE_TEAM_DEBUG_INFO_LOCK(nubThread->team->debug_info);
|
||||
restore_interrupts(state);
|
||||
@ -1811,16 +1664,16 @@ debug_nub_thread(void *)
|
||||
status_t result = B_OK;
|
||||
|
||||
// check the parameters
|
||||
if (!can_access_address(address, false))
|
||||
if (!BreakpointManager::CanAccessAddress(address, false))
|
||||
result = B_BAD_ADDRESS;
|
||||
else if (size <= 0 || size > B_MAX_READ_WRITE_MEMORY_SIZE)
|
||||
result = B_BAD_VALUE;
|
||||
|
||||
// read the memory
|
||||
int32 bytesRead = 0;
|
||||
size_t bytesRead = 0;
|
||||
if (result == B_OK) {
|
||||
result = read_user_memory(address, reply.read_memory.data,
|
||||
size, bytesRead);
|
||||
result = breakpointManager->ReadMemory(address,
|
||||
reply.read_memory.data, size, bytesRead);
|
||||
}
|
||||
reply.read_memory.error = result;
|
||||
|
||||
@ -1847,15 +1700,15 @@ debug_nub_thread(void *)
|
||||
status_t result = B_OK;
|
||||
|
||||
// check the parameters
|
||||
if (!can_access_address(address, true))
|
||||
if (!BreakpointManager::CanAccessAddress(address, true))
|
||||
result = B_BAD_ADDRESS;
|
||||
else if (size <= 0 || size > realSize)
|
||||
result = B_BAD_VALUE;
|
||||
|
||||
// write the memory
|
||||
int32 bytesWritten = 0;
|
||||
size_t bytesWritten = 0;
|
||||
if (result == B_OK) {
|
||||
result = write_user_memory(address, data, size,
|
||||
result = breakpointManager->WriteMemory(address, data, size,
|
||||
bytesWritten);
|
||||
}
|
||||
reply.write_memory.error = result;
|
||||
@ -2031,12 +1884,14 @@ debug_nub_thread(void *)
|
||||
|
||||
// check the address
|
||||
status_t result = B_OK;
|
||||
if (address == NULL || !can_access_address(address, false))
|
||||
if (address == NULL
|
||||
|| !BreakpointManager::CanAccessAddress(address, false)) {
|
||||
result = B_BAD_ADDRESS;
|
||||
}
|
||||
|
||||
// set the breakpoint
|
||||
if (result == B_OK)
|
||||
result = arch_set_breakpoint(address);
|
||||
result = breakpointManager->InstallBreakpoint(address);
|
||||
|
||||
if (result == B_OK)
|
||||
update_threads_breakpoints_flag();
|
||||
@ -2059,12 +1914,14 @@ debug_nub_thread(void *)
|
||||
|
||||
// check the address
|
||||
status_t result = B_OK;
|
||||
if (address == NULL || !can_access_address(address, false))
|
||||
if (address == NULL
|
||||
|| !BreakpointManager::CanAccessAddress(address, false)) {
|
||||
result = B_BAD_ADDRESS;
|
||||
}
|
||||
|
||||
// clear the breakpoint
|
||||
if (result == B_OK)
|
||||
result = arch_clear_breakpoint(address);
|
||||
result = breakpointManager->UninstallBreakpoint(address);
|
||||
|
||||
if (result == B_OK)
|
||||
update_threads_breakpoints_flag();
|
||||
@ -2086,14 +1943,18 @@ debug_nub_thread(void *)
|
||||
|
||||
// check the address and size
|
||||
status_t result = B_OK;
|
||||
if (address == NULL || !can_access_address(address, false))
|
||||
if (address == NULL
|
||||
|| !BreakpointManager::CanAccessAddress(address, false)) {
|
||||
result = B_BAD_ADDRESS;
|
||||
}
|
||||
if (length < 0)
|
||||
result = B_BAD_VALUE;
|
||||
|
||||
// set the watchpoint
|
||||
if (result == B_OK)
|
||||
result = arch_set_watchpoint(address, type, length);
|
||||
if (result == B_OK) {
|
||||
result = breakpointManager->InstallWatchpoint(address, type,
|
||||
length);
|
||||
}
|
||||
|
||||
if (result == B_OK)
|
||||
update_threads_breakpoints_flag();
|
||||
@ -2116,12 +1977,14 @@ debug_nub_thread(void *)
|
||||
|
||||
// check the address
|
||||
status_t result = B_OK;
|
||||
if (address == NULL || !can_access_address(address, false))
|
||||
if (address == NULL
|
||||
|| !BreakpointManager::CanAccessAddress(address, false)) {
|
||||
result = B_BAD_ADDRESS;
|
||||
}
|
||||
|
||||
// clear the watchpoint
|
||||
if (result == B_OK)
|
||||
result = arch_clear_watchpoint(address);
|
||||
result = breakpointManager->UninstallWatchpoint(address);
|
||||
|
||||
if (result == B_OK)
|
||||
update_threads_breakpoints_flag();
|
||||
@ -2317,10 +2180,15 @@ debug_nub_thread(void *)
|
||||
|
||||
atomic_or(&team->debug_info.flags,
|
||||
B_TEAM_DEBUG_DEBUGGER_HANDOVER);
|
||||
BreakpointManager* breakpointManager
|
||||
= team->debug_info.breakpoint_manager;
|
||||
|
||||
RELEASE_TEAM_DEBUG_INFO_LOCK(team->debug_info);
|
||||
restore_interrupts(state);
|
||||
|
||||
// remove all installed breakpoints
|
||||
breakpointManager->RemoveAllBreakpoints();
|
||||
|
||||
release_sem(writeLock);
|
||||
} else {
|
||||
// We probably got a SIGKILL. If so, we will terminate when
|
||||
@ -2545,7 +2413,7 @@ debug_nub_thread(void *)
|
||||
|
||||
Interrupts must be disabled and the team debug info lock of the team to be
|
||||
debugged must be held. The function will release the lock, but leave
|
||||
interrupts disabled. The team lock must be held, too.
|
||||
interrupts disabled.
|
||||
|
||||
The function also clears the arch specific team and thread debug infos
|
||||
(including among other things formerly set break/watchpoints).
|
||||
@ -2655,13 +2523,6 @@ install_team_debugger(team_id teamID, port_id debuggerPort,
|
||||
error = B_OK;
|
||||
done = true;
|
||||
result = team->debug_info.nub_port;
|
||||
} else if (
|
||||
teamDebugFlags & B_TEAM_DEBUG_DEBUGGER_HANDING_OVER) {
|
||||
// Another debugger is in the process of installing itself
|
||||
// as the team's debugger.
|
||||
error = (dontReplace ? B_OK : B_BAD_VALUE);
|
||||
done = true;
|
||||
result = team->debug_info.nub_port;
|
||||
} else {
|
||||
// a handover to another debugger is requested
|
||||
// Set the handing-over flag -- we'll clear both flags after
|
||||
@ -2735,16 +2596,9 @@ install_team_debugger(team_id teamID, port_id debuggerPort,
|
||||
state = disable_interrupts();
|
||||
GRAB_TEAM_DEBUG_INFO_LOCK(team->debug_info);
|
||||
|
||||
int32 teamDebugFlags = team->debug_info.flags;
|
||||
|
||||
if ((teamDebugFlags & B_TEAM_DEBUG_DEBUGGER_INSTALLED) != 0
|
||||
&& (teamDebugFlags & B_TEAM_DEBUG_DEBUGGER_HANDING_OVER) != 0
|
||||
&& team->debug_info.debugger_port == debuggerPort) {
|
||||
// Everything is as we left it above, so just clear the flags.
|
||||
atomic_and(&team->debug_info.flags,
|
||||
~(B_TEAM_DEBUG_DEBUGGER_HANDOVER
|
||||
| B_TEAM_DEBUG_DEBUGGER_HANDING_OVER));
|
||||
}
|
||||
atomic_and(&team->debug_info.flags,
|
||||
~(B_TEAM_DEBUG_DEBUGGER_HANDOVER
|
||||
| B_TEAM_DEBUG_DEBUGGER_HANDING_OVER));
|
||||
|
||||
RELEASE_TEAM_DEBUG_INFO_LOCK(team->debug_info);
|
||||
restore_interrupts(state);
|
||||
@ -2800,6 +2654,16 @@ install_team_debugger(team_id teamID, port_id debuggerPort,
|
||||
if (error == B_OK)
|
||||
error = set_port_owner(nubPort, debuggerTeam);
|
||||
|
||||
// create the breakpoint manager
|
||||
BreakpointManager* breakpointManager = NULL;
|
||||
if (error == B_OK) {
|
||||
breakpointManager = new(std::nothrow) BreakpointManager;
|
||||
if (breakpointManager != NULL)
|
||||
error = breakpointManager->Init();
|
||||
else
|
||||
error = B_NO_MEMORY;
|
||||
}
|
||||
|
||||
// spawn the nub thread
|
||||
thread_id nubThread = -1;
|
||||
if (error == B_OK) {
|
||||
@ -2815,18 +2679,10 @@ install_team_debugger(team_id teamID, port_id debuggerPort,
|
||||
state = disable_interrupts();
|
||||
GRAB_TEAM_DEBUG_INFO_LOCK(team->debug_info);
|
||||
|
||||
if (team->debug_info.flags & B_TEAM_DEBUG_DEBUGGER_INSTALLED) {
|
||||
// there's already a debugger installed
|
||||
error = (dontReplace ? B_OK : B_BAD_VALUE);
|
||||
done = true;
|
||||
result = team->debug_info.nub_port;
|
||||
|
||||
RELEASE_TEAM_DEBUG_INFO_LOCK(team->debug_info);
|
||||
} else {
|
||||
install_team_debugger_init_debug_infos(team, debuggerTeam,
|
||||
debuggerPort, nubPort, nubThread, debuggerWriteLock,
|
||||
causingThread);
|
||||
}
|
||||
team->debug_info.breakpoint_manager = breakpointManager;
|
||||
install_team_debugger_init_debug_infos(team, debuggerTeam,
|
||||
debuggerPort, nubPort, nubThread, debuggerWriteLock,
|
||||
causingThread);
|
||||
|
||||
restore_interrupts(state);
|
||||
}
|
||||
@ -2834,7 +2690,7 @@ install_team_debugger(team_id teamID, port_id debuggerPort,
|
||||
finish_debugger_change(team);
|
||||
|
||||
// if everything went fine, resume the nub thread, otherwise clean up
|
||||
if (error == B_OK && !done) {
|
||||
if (error == B_OK) {
|
||||
resume_thread(nubThread);
|
||||
} else {
|
||||
// delete port and terminate thread
|
||||
@ -2846,6 +2702,8 @@ install_team_debugger(team_id teamID, port_id debuggerPort,
|
||||
int32 result;
|
||||
wait_for_thread(nubThread, &result);
|
||||
}
|
||||
|
||||
delete breakpointManager;
|
||||
}
|
||||
|
||||
TRACE(("install_team_debugger() done2: %ld\n",
|
||||
@ -2961,11 +2819,13 @@ _user_remove_team_debugger(team_id teamID)
|
||||
|
||||
InterruptsSpinLocker debugInfoLocker(team->debug_info.lock);
|
||||
|
||||
struct team_debug_info info;
|
||||
thread_id nubThread = -1;
|
||||
port_id nubPort = -1;
|
||||
|
||||
if (team->debug_info.flags & B_TEAM_DEBUG_DEBUGGER_INSTALLED) {
|
||||
// there's a debugger installed
|
||||
info = team->debug_info;
|
||||
clear_team_debug_info(&team->debug_info, false);
|
||||
nubThread = team->debug_info.nub_thread;
|
||||
nubPort = team->debug_info.nub_port;
|
||||
} else {
|
||||
// no debugger installed
|
||||
error = B_BAD_VALUE;
|
||||
@ -2973,11 +2833,16 @@ _user_remove_team_debugger(team_id teamID)
|
||||
|
||||
debugInfoLocker.Unlock();
|
||||
|
||||
// Delete the nub port -- this will cause the nub thread to terminate and
|
||||
// remove the debugger.
|
||||
if (nubPort >= 0)
|
||||
delete_port(nubPort);
|
||||
|
||||
finish_debugger_change(team);
|
||||
|
||||
// clean up the info, if there was a debugger installed
|
||||
if (error == B_OK)
|
||||
destroy_team_debug_info(&info);
|
||||
// wait for the nub thread
|
||||
if (nubThread >= 0)
|
||||
wait_for_thread(nubThread, NULL);
|
||||
|
||||
return error;
|
||||
}
|
||||
@ -3093,7 +2958,7 @@ _user_set_debugger_breakpoint(void *address, uint32 type, int32 length,
|
||||
bool watchpoint)
|
||||
{
|
||||
// check the address and size
|
||||
if (address == NULL || !can_access_address(address, false))
|
||||
if (address == NULL || !BreakpointManager::CanAccessAddress(address, false))
|
||||
return B_BAD_ADDRESS;
|
||||
if (watchpoint && length < 0)
|
||||
return B_BAD_VALUE;
|
||||
@ -3126,7 +2991,7 @@ status_t
|
||||
_user_clear_debugger_breakpoint(void *address, bool watchpoint)
|
||||
{
|
||||
// check the address
|
||||
if (address == NULL || !can_access_address(address, false))
|
||||
if (address == NULL || !BreakpointManager::CanAccessAddress(address, false))
|
||||
return B_BAD_ADDRESS;
|
||||
|
||||
// check whether a debugger is installed already
|
||||
|
Loading…
Reference in New Issue
Block a user