* 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 <kernel.h>
#include <util/AutoLock.h>
#include <vm/vm.h>
#ifdef TRACE_BREAKPOINT_MANAGER
# define TRACE(x...) dprintf(x)
#else
# define TRACE(x...) do {} while (false)
#endif
const int32 kMaxBreakpointCount = 10240;
BreakpointManager::InstalledBreakpoint::InstalledBreakpoint(addr_t address)
:
breakpoint(NULL),
address(address)
{
}
BreakpointManager::BreakpointManager()
:
fBreakpointCount(0),
fWatchpointCount(0)
{
rw_lock_init(&fLock, "breakpoint manager");
}
BreakpointManager::~BreakpointManager()
{
WriteLocker locker(fLock);
BreakpointTree::Iterator it = fBreakpoints.GetIterator();
while (InstalledBreakpoint* installedBreakpoint = it.Next()) {
it.Remove();
if (installedBreakpoint->breakpoint->software)
delete installedBreakpoint->breakpoint;
delete installedBreakpoint;
}
while (InstalledWatchpoint* watchpoint = fWatchpoints.RemoveHead())
delete watchpoint;
while (Breakpoint* breakpoint = fHardwareBreakpoints.RemoveHead())
delete breakpoint;
rw_lock_destroy(&fLock);
}
status_t
BreakpointManager::Init()
{
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;
InstalledBreakpoint* installed = fBreakpoints.Lookup(address);
if (installed != NULL)
return B_BAD_VALUE;
installed = new(std::nothrow) InstalledBreakpoint(address);
if (installed == NULL)
return B_NO_MEMORY;
ObjectDeleter<InstalledBreakpoint> installedDeleter(installed);
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 {
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
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);
BreakpointTree::Iterator it = fBreakpoints.GetIterator();
while (InstalledBreakpoint* installedBreakpoint = it.Next()) {
it.Remove();
if (installedBreakpoint->breakpoint->software)
_UninstallSoftwareBreakpoint(installedBreakpoint->breakpoint);
else
_UninstallHardwareBreakpoint(installedBreakpoint->breakpoint);
delete installedBreakpoint;
}
while (InstalledWatchpoint* watchpoint = fWatchpoints.RemoveHead()) {
_UninstallWatchpoint(watchpoint);
delete watchpoint;
}
}
No check whether there's an actually accessible area is performed, though.
*/
bool
BreakpointManager::CanAccessAddress(const void* _address, bool write)
{
const addr_t address = (addr_t)_address;
if (IS_USER_ADDRESS(address))
return true;
return false;
}
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;
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) {
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);
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) {
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();
}
while (installed != NULL) {
Breakpoint* breakpoint = installed->breakpoint;
if (breakpoint->address >= address + size)
break;
if (breakpoint->software) {
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;
}
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();
}
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);
InstalledBreakpoint* installed = fBreakpoints.Lookup(address);
if (installed == NULL || !installed->breakpoint->software)
return;
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)
{
for (BreakpointList::Iterator it = fHardwareBreakpoints.GetIterator();
Breakpoint* breakpoint = it.Next();) {
if (!breakpoint->used)
return breakpoint;
}
if (!force)
return NULL;
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;
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;
error = _WriteMemory(address, DEBUG_SOFTWARE_BREAKPOINT,
DEBUG_SOFTWARE_BREAKPOINT_SIZE, bytesTransferred);
if (error != B_OK)
return error;
if (bytesTransferred < DEBUG_SOFTWARE_BREAKPOINT_SIZE) {
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;
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
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;
if (!CanAccessAddress(address, false))
return B_BAD_ADDRESS;
if (size <= 0)
return B_BAD_VALUE;
status_t error = B_OK;
bytesRead = 0;
while (size > 0) {
if (!CanAccessAddress(address, false)) {
error = B_BAD_ADDRESS;
break;
}
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 (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;
if (!CanAccessAddress(address, true))
return B_BAD_ADDRESS;
if (size <= 0)
return B_BAD_VALUE;
status_t error = B_OK;
bytesWritten = 0;
while (size > 0) {
if (!CanAccessAddress(address, true)) {
error = B_BAD_ADDRESS;
break;
}
area_id area = _user_area_for(address);
if (area < 0) {
TRACE("BreakpointManager::_WriteMemory(): 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("BreakpointManager::_WriteMemory(): failed to get info for "
"area %ld: %lx\n", area, error);
error = B_BAD_ADDRESS;
break;
}
int32 toWrite = size;
int32 maxWrite = (uint8*)areaInfo.address + areaInfo.size - address;
if (toWrite > maxWrite)
toWrite = maxWrite;
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;
}
error = user_memcpy(address, buffer, toWrite);
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 (error != B_OK) {
if (bytesWritten > 0)
return B_OK;
return error;
}
return B_OK;
}