#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;
}
}
bool
BreakpointManager::CanAccessAddress(const void* _address, bool write)
{
const addr_t address = (addr_t)_address;
if (IS_USER_ADDRESS(address))
return true;
return false;
}
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;
}