blob: 9a0c180044f31b5a5eaeef428ca46ad7534ac5e4 [file] [log] [blame]
/*******************************************************************************
* 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);
}
}
}