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:
Ingo Weinhold 2009-06-23 21:03:57 +00:00
parent 63e909772d
commit b0f12d64f4
9 changed files with 1078 additions and 242 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
};

View File

@ -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;

View 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;
}

View 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

View File

@ -7,6 +7,7 @@ UsePrivateHeaders shared ;
KernelMergeObject kernel_debug.o :
blue_screen.cpp
BreakpointManager.cpp
debug.cpp
debug_builtin_commands.cpp
debug_commands.cpp

View File

@ -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