| /******************************************************************************* | |
| * 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); | |
| } | |
| } | |
| } |