/******************************************************************************* | |
* Copyright (c) 2011 Nokia and others. | |
* All rights reserved. This program and the accompanying materials | |
* are made available under the terms of the Eclipse Public License v1.0 | |
* which accompanies this distribution, and is available at | |
* http://www.eclipse.org/legal/epl-v10.html | |
* | |
* Contributors: | |
* Nokia - Initial API and implementation | |
*******************************************************************************/ | |
#include "BreakpointsService.h" | |
#include "ContextManager.h" | |
#include <string> | |
#include "TCFHeaders.h" | |
#include "TCFChannel.h" | |
#include "AgentUtils.h" | |
#include "ContextManager.h" | |
#include "EventClientNotifier.h" | |
#include "Logger.h" | |
#include "WinHWBkptMgr.h" | |
#include "WinProcess.h" | |
#include "WinThread.h" | |
static const char * sServiceName = "Breakpoints"; | |
static const unsigned char sBreakInst = 0xCC; | |
bool BreakpointsService::sServiceInstalled = false; | |
// | |
// The static function reads the context ids for a breakpoint | |
// | |
static void read_id_array(InputStream* inp, void* arg) | |
{ | |
TCFInputStream inpStream(inp); | |
std::vector<std::string>* ids = (std::vector<std::string>*)(arg); | |
std::string str = inpStream.readString(); | |
ids->push_back(str); | |
} | |
// | |
// This static function is used to read all the breakpoint properties or attributes | |
// | |
static void read_breakpoint_attr(InputStream * inp, const char * nm, void * arg) { | |
TCFInputStream inpStream(inp); | |
TBreakpoint* attrs = (TBreakpoint*)arg; | |
if (::strcmp(nm, "AccessMode") == 0) { | |
attrs->accessMode = (TCFAccessMode)inpStream.readULong(); | |
return; | |
} | |
if (::strcmp(nm, "BreakpointType") == 0) { | |
std::string type = inpStream.readString(); | |
if (type.compare("Auto") == 0) { | |
attrs->bpType = type_Auto; | |
} else if (type.compare("Software") == 0) { | |
attrs->bpType = type_Software; | |
} else if (type.compare("Hardware") == 0) { | |
attrs->bpType = type_Hardware; | |
} | |
return; | |
} | |
if (::strcmp(nm, "ContextIds") == 0) { | |
json_read_array(inp,read_id_array,&attrs->iContextIds); | |
return; | |
} | |
if (::strcmp(nm, "Enabled") == 0) { | |
attrs->enabled = json_read_boolean(inp); | |
return; | |
} | |
if (::strcmp(nm, "ID") == 0) { | |
attrs->hostBreakPointId = inpStream.readString(); | |
return; | |
} | |
if (::strcmp(nm, "Location") == 0) { | |
std::string location(inpStream.readString()); | |
// atoi fails on large unsigned ints. | |
// this will also handle negative numbers, though. | |
::sscanf(location.c_str(), "%u", (unsigned int*)&attrs->address); | |
return; | |
} | |
if (::strcmp(nm, "Size") == 0) { | |
attrs->size = inpStream.readULong(); | |
return; | |
} | |
json_skip_object(inp); | |
} | |
// | |
// BreakpointService::BreakpointService() | |
// | |
BreakpointsService::BreakpointsService(Protocol* proto) | |
: TCFService(proto) | |
{ | |
AddCommand("add", CommandAddBreakpoint); | |
AddCommand("remove", CommandRemoveBreakpoint); | |
sServiceInstalled = true; | |
} | |
// | |
// static function to get the service name | |
// | |
const char* BreakpointsService::GetName() { | |
return sServiceName; | |
} | |
// | |
// BreakpointService::CommandAddBreakpoint() | |
// | |
void BreakpointsService::CommandAddBreakpoint(const char* token, Channel* c) { | |
LogTrace("BreakpointService::command_add_Breakpoint", "token: %s", token); | |
TCFChannel channel(c); | |
TBreakpoint* breakInfo = new TBreakpoint(); | |
json_read_struct(&c->inp, read_breakpoint_attr, breakInfo); | |
channel.readZero(); | |
channel.readComplete(); | |
// EDC in 3.x > 20100831 will pass a thread-specific bp (if known) at the end of the list. | |
// For now we stick with process-specific breakpoints. | |
// TODO: see if we want thread-specific breakpoints in the future. | |
std::string contextID | |
= breakInfo->iContextIds.empty() ? "" : breakInfo->iContextIds.front(); | |
Context* context = ContextManager::findContext(contextID); | |
WinProcess*& proc_context = breakInfo->process; | |
WinThread* thread_context = NULL; | |
if ((proc_context = dynamic_cast<WinProcess*>(context)) != NULL) { | |
// id is a process | |
} else if ((thread_context = dynamic_cast<WinThread*>(context)) != NULL) { | |
// id is a thread | |
proc_context = dynamic_cast<WinProcess*>(ContextManager::findContext( | |
thread_context->GetParentID())); | |
} | |
if (!proc_context || !proc_context->IsDebugging()) { | |
channel.writeCompleteReply(token, ERR_INV_CONTEXT); | |
return; | |
} | |
// "thread_context" is not required. | |
if (thread_context && !thread_context->IsDebugging()) { | |
channel.writeCompleteReply(token, ERR_INV_CONTEXT); | |
return; | |
} | |
if (!breakInfo->enabled) { | |
trace(LOG_ALWAYS, "!!! cannot set disabled breakpoints"); | |
channel.writeCompleteReply(token, ERR_UNSUPPORTED); | |
return; | |
} | |
int err; | |
if (ACCESSMODE_EXECUTE == breakInfo->accessMode) | |
err = InsertBreak(breakInfo); | |
else | |
err = InsertWatch(breakInfo); | |
if (err == 0) { | |
trace(LOG_ALWAYS, "Breakpoint added for ID = %s", breakInfo->hostBreakPointId.c_str()); | |
sBreakPointMap[breakInfo->hostBreakPointId] = breakInfo; | |
} | |
channel.writeCompleteReply(token, err); | |
} | |
// | |
// BreakpointService::CommandRemoveBreakpoint() | |
// | |
void BreakpointsService::CommandRemoveBreakpoint(const char* token, Channel* c) { | |
LogTrace("BreakpointService::command_remove_Breakpoint", "token: %s", token); | |
TCFChannel channel(c); | |
int err = 0; | |
int overallError = 0; | |
std::vector<std::string> breakIdList; | |
json_read_array(&c->inp, read_id_array, &breakIdList); | |
channel.readZero(); | |
channel.readComplete(); | |
std::vector<std::string>::iterator iter ; | |
for (iter = breakIdList.begin(); iter != breakIdList.end(); ) { | |
TID2BreakpointMap::iterator it = sBreakPointMap.find(*iter++); | |
if (it == sBreakPointMap.end()) { | |
if (!overallError) | |
overallError = ERR_OTHER; | |
continue; | |
} | |
TBreakpoint* bp = it->second; | |
sBreakPointMap.erase(it->first); | |
if (ACCESSMODE_EXECUTE == bp->accessMode) | |
err = ClearBreak(bp); | |
else | |
err = ClearWatch(bp); | |
delete bp; | |
// ignore "not found" from the driver, since some breakpoints | |
// are removed automatically on process kill / detach / etc. | |
if (err != 0 && err != ERR_OTHER && !overallError) | |
overallError = err; | |
} | |
channel.writeCompleteReply(token, overallError); | |
} | |
/* | |
* Is the Breakpoints service installed in the agent ? | |
*/ | |
bool BreakpointsService::ServiceInstalled() { | |
return sServiceInstalled; | |
} | |
/* | |
* Insert a software breakpoint in a process. | |
*/ | |
int BreakpointsService::InsertBreak(const TBreakpoint* bpInfo) { | |
const HANDLE& procHandle = bpInfo->process->GetProcessHandle(); | |
// save byte at breakpoint address. | |
if (! ::ReadProcessMemory(procHandle, (LPCVOID)bpInfo->address, (LPVOID)&bpInfo->originalByte, 1, NULL)) | |
return ::GetLastError(); | |
// write breakpoint (INT3) to memory | |
if (! ::WriteProcessMemory(procHandle, (LPVOID)bpInfo->address, (LPVOID)&sBreakInst, 1, NULL)) | |
return ::GetLastError(); | |
// make sure original instruction isn't already in cache | |
if (! ::FlushInstructionCache(procHandle, (LPCVOID)bpInfo->address, 1)) | |
return ::GetLastError(); | |
return 0; | |
} | |
/* | |
* Clear a breakpoint from a process. | |
*/ | |
int BreakpointsService::ClearBreak(const TBreakpoint* bpInfo) { | |
const HANDLE& procHandle = bpInfo->process->GetProcessHandle(); | |
// write original byte back over top of (INT3) in memory | |
if (! ::WriteProcessMemory(procHandle, (LPVOID)bpInfo->address, (LPVOID)&bpInfo->originalByte, 1, NULL)) | |
return ::GetLastError(); | |
// make sure bp instruction isn't already in cache | |
if (! ::FlushInstructionCache(procHandle, (LPCVOID)bpInfo->address, 1)) | |
return ::GetLastError(); | |
return 0; | |
} | |
/* | |
* establish a hardware watchpoint using the hardware debug registers | |
*/ | |
int BreakpointsService::InsertWatch(TBreakpoint* bpInfo) { | |
return WinHWBkptMgr::SetHardwareBreak(bpInfo); | |
} | |
/* | |
* clear the hardware debug register for a given watchpoint | |
*/ | |
int BreakpointsService::ClearWatch(TBreakpoint* bpInfo) { | |
return WinHWBkptMgr::ClearHardwareBreak(bpInfo); | |
} | |
/* | |
* find if there's a breakpoint at the given address in a process. | |
*/ | |
TBreakpoint* BreakpointsService::FindBreakpointByAddress( | |
const HANDLE& processHandle, const ContextAddress address) { | |
TID2BreakpointMap::iterator iter; | |
for (iter = sBreakPointMap.begin(); iter != sBreakPointMap.end(); iter++) { | |
TBreakpoint* bp = iter->second; | |
if (ACCESSMODE_EXECUTE == bp->accessMode | |
&& bp->process && processHandle == bp->process->GetProcessHandle() // same process | |
&& address == bp->address) | |
{ | |
return bp; | |
} | |
} | |
return NULL; | |
} | |
/* | |
* find if there's a watchpoint at the given address in a process. | |
*/ | |
TBreakpoint* | |
BreakpointsService::FindWatchpointByAddress(const HANDLE& processHandle, | |
ContextAddress address) | |
{ | |
TID2BreakpointMap::iterator iter; | |
for (iter = sBreakPointMap.begin(); iter != sBreakPointMap.end(); iter++) { | |
TBreakpoint* wp = iter->second; | |
if (ACCESSMODE_EXECUTE != wp->accessMode | |
&& wp->process && processHandle == wp->process->GetProcessHandle() // same process | |
&& wp->address <= address && address < wp->address+wp->size) | |
{ | |
return wp; | |
} | |
} | |
return NULL; | |
} | |
/* | |
* Given a memory buffer read from a process, remove any breakpoint instructions added by debugger in the buffer. | |
*/ | |
void BreakpointsService::RemoveBreakpointsFromMemoryRead( | |
const HANDLE& processHandle, ContextAddress address, char* memBuffer, | |
const unsigned long size) { | |
TID2BreakpointMap::iterator iter; | |
for (iter = sBreakPointMap.begin(); iter != sBreakPointMap.end(); iter++) { | |
TBreakpoint* bp = iter->second; | |
if (ACCESSMODE_EXECUTE == bp->accessMode | |
&& bp->process && processHandle == bp->process->GetProcessHandle() // same process | |
&& address <= bp->address && address+size > bp->address) // bp falls in the buffer | |
{ | |
char *byteToChange = memBuffer + (bp->address - address); | |
*byteToChange = bp->originalByte; | |
} | |
} | |
} | |
/* | |
* Given a memory buffer that has been writen to a process, re-inssert any breakpoints in the buffer range. | |
*/ | |
void BreakpointsService::ReInsertBreakpointsAfterMemoryWrite( | |
const HANDLE& processHandle, ContextAddress address, char* memBuffer, | |
const unsigned long size) { | |
TID2BreakpointMap::iterator iter; | |
for (iter = sBreakPointMap.begin(); iter != sBreakPointMap.end(); iter++) { | |
TBreakpoint* bp = iter->second; | |
if (ACCESSMODE_EXECUTE == bp->accessMode | |
&& bp->process && processHandle == bp->process->GetProcessHandle() // same process | |
&& address <= bp->address && address+size > bp->address) // bp falls in the buffer | |
{ | |
InsertBreak(bp); | |
} | |
} | |
} |