/******************************************************************************* | |
* 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 "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, "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, "ContextIds") == 0) | |
{ | |
json_read_array(inp,read_id_array,&attrs->iContextIds); | |
return; | |
} | |
if (strcmp(nm, "ID") == 0) | |
{ | |
attrs->hostBreakPointId = inpStream.readString(); | |
return; | |
} | |
if (strcmp(nm, "Enabled") == 0) | |
{ | |
attrs->enabled = json_read_boolean(inp); | |
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.front(); | |
Context* context = ContextManager::findContext(contextID); | |
RunControlContext* thread_context = 0; | |
WinProcess* proc_context = 0; | |
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; | |
} | |
breakInfo->procHandle = proc_context->GetProcessHandle(); | |
int err = InsertBreak(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); | |
err = ClearBreak(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) | |
{ | |
// save byte at breakpoint address. | |
if (! ReadProcessMemory(bpInfo->procHandle, (LPCVOID)bpInfo->address, (LPVOID)&bpInfo->originalByte, 1, NULL)) | |
return GetLastError(); | |
// write breakpoint (INT3) to memory | |
if (! WriteProcessMemory(bpInfo->procHandle, (LPVOID)bpInfo->address, (LPVOID)&sBreakInst, 1, NULL)) | |
return GetLastError(); | |
// make sure original instruction isn't already in cache | |
if (! FlushInstructionCache(bpInfo->procHandle, (LPCVOID) bpInfo->address, 1)) | |
return GetLastError(); | |
return 0; | |
} | |
/* | |
* Clear a breakpoint from a process. | |
*/ | |
int BreakpointsService::ClearBreak(const TBreakpoint* bpInfo) | |
{ | |
// write breakpoint (INT3) to memory | |
if (! WriteProcessMemory(bpInfo->procHandle, (LPVOID)bpInfo->address, (LPVOID)&bpInfo->originalByte, 1, NULL)) | |
return GetLastError(); | |
// make sure bp instruction isn't already in cache | |
if (! FlushInstructionCache(bpInfo->procHandle, (LPCVOID) bpInfo->address, 1)) | |
return GetLastError(); | |
return 0; | |
} | |
/* | |
* find if there's a breakpoint at the given address in a process. | |
*/ | |
TBreakpoint* BreakpointsService::FindBreakpointByAddress(HANDLE processHandle, unsigned long address) | |
{ | |
TID2BreakpointMap::iterator iter; | |
for (iter = sBreakPointMap.begin(); iter != sBreakPointMap.end(); iter++) { | |
TBreakpoint* bp = iter->second; | |
if (processHandle == bp->procHandle && // same process | |
address == bp->address) | |
return bp; | |
} | |
return NULL; | |
} | |
/* | |
* Given a memory buffer read from a process, remove any breakpoint instructions added by debugger in the buffer. | |
*/ | |
void BreakpointsService::RemoveBreakpointsFromMemoryRead(HANDLE processHandle, ContextAddress address, char* memBuffer, unsigned long size) | |
{ | |
TID2BreakpointMap::iterator iter; | |
for (iter = sBreakPointMap.begin(); iter != sBreakPointMap.end(); iter++) { | |
TBreakpoint* bp = iter->second; | |
if (processHandle == bp->procHandle && // 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(HANDLE processHandle, ContextAddress address, char* memBuffer, unsigned long size) | |
{ | |
TID2BreakpointMap::iterator iter; | |
for (iter = sBreakPointMap.begin(); iter != sBreakPointMap.end(); iter++) { | |
TBreakpoint* bp = iter->second; | |
if (processHandle == bp->procHandle && // same process | |
address <= bp->address && address+size > bp->address) // bp falls in the buffer | |
{ | |
InsertBreak(bp); | |
} | |
} | |
} |