| /******************************************************************************* |
| * Copyright (c) 2009 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 <stdio.h> |
| #include <assert.h> |
| |
| #include "stdafx.h" |
| #include "WinThread.h" |
| #include "WinProcess.h" |
| #include "AgentUtils.h" |
| #include "EventClientNotifier.h" |
| #include "Logger.h" |
| #include "WinDebugMonitor.h" |
| #include "ResumeContextAction.h" |
| #include "ProtocolConstants.h" |
| #include "RunControlService.h" |
| #include "BreakpointsService.h" |
| |
| std::map<std::pair<int, int>, WinThread*> WinThread::threadIDMap_; |
| |
| WinThread::WinThread(WinProcess& process, DEBUG_EVENT& debugEvent) : |
| ThreadContext(debugEvent.dwThreadId, process.GetID(), CreateInternalID(debugEvent.dwThreadId, process.GetID())), |
| threadLookupPair_(debugEvent.dwProcessId, debugEvent.dwThreadId), |
| parentProcess_(process) |
| { |
| process.AddChild(this); |
| |
| threadIDMap_[threadLookupPair_] = this; |
| |
| threadContextValid_ = false; |
| if (debugEvent.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT) { |
| handle_ = debugEvent.u.CreateProcessInfo.hThread; |
| startAddress_ |
| = (unsigned long) debugEvent.u.CreateProcessInfo.lpStartAddress; |
| localBase_ = debugEvent.u.CreateProcessInfo.lpThreadLocalBase; |
| } else if (debugEvent.dwDebugEventCode == CREATE_THREAD_DEBUG_EVENT) { |
| handle_ = debugEvent.u.CreateThread.hThread; |
| startAddress_ |
| = (unsigned long) debugEvent.u.CreateThread.lpStartAddress; |
| localBase_ = debugEvent.u.CreateThread.lpThreadLocalBase; |
| } |
| isSuspended_ = false; |
| isTerminating_ = false; |
| isUserSuspended_ = false; |
| |
| initialize(); |
| } |
| |
| // Initialize thread specific properties. |
| void WinThread::initialize() |
| { |
| char buf[32]; |
| _snprintf(buf, sizeof(buf), "0x%08x", startAddress_); |
| SetProperty(PROP_NAME, new PropertyValue(buf)); |
| |
| int supportedResumeModes = (1 << RM_RESUME) | (1 << RM_STEP_INTO); |
| SetProperty(PROP_CAN_RESUME, new PropertyValue(supportedResumeModes)); |
| |
| SetProperty(PROP_CAN_TERMINATE, new PropertyValue(true)); |
| SetProperty(PROP_CAN_SUSPEND, new PropertyValue(true)); |
| } |
| |
| int WinThread::GetThreadID() { |
| return GetOSID(); |
| } |
| |
| WinThread::~WinThread(void) { |
| parentProcess_.RemoveChild(this); |
| threadIDMap_.erase(threadLookupPair_); |
| } |
| |
| ContextAddress WinThread::GetPCAddress() { |
| // The following is actually the address of the instruction that causes |
| // the exception, not the actual PC register value which is usually |
| // pointing to the byte after the exception instruction. |
| // But what we need here is PC value. |
| // |
| // exceptionInfo_.ExceptionRecord.ExceptionAddress; |
| |
| assert(threadContextValid_); |
| return threadContextInfo_.Eip; |
| } |
| |
| const char* WinThread::GetSuspendReason() { |
| const char* reason = REASON_EXCEPTION; |
| |
| switch (exceptionInfo_.ExceptionRecord.ExceptionCode) { |
| case USER_SUSPEND_THREAD: |
| return REASON_USER_REQUEST; |
| case EXCEPTION_SINGLE_STEP: |
| return REASON_STEP; |
| case EXCEPTION_BREAKPOINT: |
| return REASON_BREAKPOINT; |
| } |
| |
| return reason; |
| } |
| |
| |
| std::string WinThread::GetExceptionMessage() { |
| if (exceptionInfo_.ExceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP |
| || exceptionInfo_.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT |
| || exceptionInfo_.ExceptionRecord.ExceptionCode == USER_SUSPEND_THREAD) |
| return ""; |
| |
| return WinDebugMonitor::GetDebugExceptionDescription(exceptionInfo_); |
| } |
| |
| void WinThread::MarkSuspended() { |
| isSuspended_ = true; |
| threadContextValid_ = false; |
| } |
| |
| void WinThread::HandleException(DEBUG_EVENT& debugEvent) { |
| MarkSuspended(); |
| exceptionInfo_ = debugEvent.u.Exception; |
| EnsureValidContextInfo(); |
| AdjustPC(); |
| EventClientNotifier::SendContextSuspended(this, |
| GetPCAddress(), GetSuspendReason(), GetExceptionMessage()); |
| |
| } |
| |
| /* |
| * Check if the program is stopped due to a software breakpoint |
| * installed by the agent, if yes, move PC back by one byte. |
| */ |
| void WinThread::AdjustPC() { |
| // Bail out if the agent does not install & manage |
| // breakpoints (namely the EDC host uses generic |
| // software breakpoint mechanism). |
| if (! BreakpointsService::ServiceInstalled()) |
| return; |
| |
| /* |
| * Check |
| * 1. Did we stop due to a breakpoint exception ? |
| * -- This is to prevent adjusting PC for other exceptions such as |
| * divide-by-zero & invalid code. |
| * 2. is there a software breakpoint at the byte right before the PC? |
| * -- this is to exclude the case of user-inserted "int 3" instruction. |
| */ |
| if (exceptionInfo_.ExceptionRecord.ExceptionCode != EXCEPTION_BREAKPOINT) |
| return; |
| |
| ContextAddress pc = GetPCAddress(); |
| pc--; |
| if (NULL != BreakpointsService::FindBreakpointByAddress(parentProcess_.GetProcessHandle(), pc)) { |
| SetRegisterValue("EIP", 4, (char*)&pc); |
| } |
| } |
| |
| void WinThread::HandleExecutableEvent(bool isLoaded, const std::string& exePath, |
| unsigned long baseAddress, unsigned long codeSize) { |
| MarkSuspended(); |
| EnsureValidContextInfo(); |
| |
| if(parentProcess_.GetExecutables().find(exePath) != parentProcess_.GetExecutables().end()) |
| { |
| Properties existingProps = parentProcess_.GetExecutables()[exePath]; |
| if (existingProps[PROP_MODULE_LOADED]->getBoolValue()) |
| { |
| delete existingProps[PROP_MODULE_LOADED]; |
| existingProps[PROP_MODULE_LOADED] = new PropertyValue(false); |
| EventClientNotifier::SendExecutableEvent(this, |
| threadContextInfo_.Eip, existingProps); |
| } |
| } |
| |
| Properties props; |
| props[PROP_FILE] = new PropertyValue(exePath); |
| props[PROP_NAME] = new PropertyValue(AgentUtils::GetFileNameFromPath(exePath)); |
| props[PROP_MODULE_LOADED] = new PropertyValue(isLoaded); |
| props[PROP_IMAGE_BASE_ADDRESS] = new PropertyValue((int) baseAddress); |
| props[PROP_CODE_SIZE] = new PropertyValue((int) codeSize); |
| |
| parentProcess_.GetExecutables()[exePath] = props; |
| EventClientNotifier::SendExecutableEvent(this, |
| threadContextInfo_.Eip, props); |
| } |
| |
| bool WinThread::isSuspended() { |
| return isSuspended_; |
| } |
| |
| #ifndef CONTEXT_ALL |
| #define CONTEXT_ALL (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | \ |
| CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS | \ |
| CONTEXT_EXTENDED_REGISTERS) |
| #endif |
| |
| void WinThread::EnsureValidContextInfo() { |
| if (!threadContextValid_ && isSuspended()) { |
| threadContextInfo_.ContextFlags = CONTEXT_ALL; |
| if (GetThreadContext(handle_, &threadContextInfo_) != 0) { |
| registerValueCache_.clear(); |
| // Cache general registers |
| registerValueCache_["EAX"] = AgentUtils::IntToHexString( |
| threadContextInfo_.Eax); |
| registerValueCache_["ECX"] = AgentUtils::IntToHexString( |
| threadContextInfo_.Ecx); |
| registerValueCache_["EDX"] = AgentUtils::IntToHexString( |
| threadContextInfo_.Edx); |
| registerValueCache_["EBX"] = AgentUtils::IntToHexString( |
| threadContextInfo_.Ebx); |
| registerValueCache_["ESP"] = AgentUtils::IntToHexString( |
| threadContextInfo_.Esp); |
| registerValueCache_["EBP"] = AgentUtils::IntToHexString( |
| threadContextInfo_.Ebp); |
| registerValueCache_["ESI"] = AgentUtils::IntToHexString( |
| threadContextInfo_.Esi); |
| registerValueCache_["EDI"] = AgentUtils::IntToHexString( |
| threadContextInfo_.Edi); |
| registerValueCache_["EIP"] = AgentUtils::IntToHexString( |
| threadContextInfo_.Eip); |
| registerValueCache_["GS"] = AgentUtils::IntToHexString( |
| threadContextInfo_.SegGs); |
| registerValueCache_["FS"] = AgentUtils::IntToHexString( |
| threadContextInfo_.SegFs); |
| registerValueCache_["ES"] = AgentUtils::IntToHexString( |
| threadContextInfo_.SegEs); |
| registerValueCache_["DS"] = AgentUtils::IntToHexString( |
| threadContextInfo_.SegDs); |
| registerValueCache_["CS"] = AgentUtils::IntToHexString( |
| threadContextInfo_.SegCs); |
| registerValueCache_["EFL"] = AgentUtils::IntToHexString( |
| threadContextInfo_.EFlags); |
| registerValueCache_["SS"] = AgentUtils::IntToHexString( |
| threadContextInfo_.SegSs); |
| |
| threadContextValid_ = true; |
| } |
| } |
| } |
| |
| void WinThread::SetContextInfo() { |
| if (isSuspended()) { |
| threadContextInfo_.ContextFlags = CONTEXT_ALL; |
| // Set general registers values |
| threadContextInfo_.Eax = AgentUtils::HexStringToInt( |
| registerValueCache_["EAX"]); |
| threadContextInfo_.Ecx = AgentUtils::HexStringToInt( |
| registerValueCache_["ECX"]); |
| threadContextInfo_.Edx = AgentUtils::HexStringToInt( |
| registerValueCache_["EDX"]); |
| threadContextInfo_.Ebx = AgentUtils::HexStringToInt( |
| registerValueCache_["EBX"]); |
| threadContextInfo_.Esp = AgentUtils::HexStringToInt( |
| registerValueCache_["ESP"]); |
| threadContextInfo_.Ebp = AgentUtils::HexStringToInt( |
| registerValueCache_["EBP"]); |
| threadContextInfo_.Esi = AgentUtils::HexStringToInt( |
| registerValueCache_["ESI"]); |
| threadContextInfo_.Edi = AgentUtils::HexStringToInt( |
| registerValueCache_["EDI"]); |
| threadContextInfo_.Eip = AgentUtils::HexStringToInt( |
| registerValueCache_["EIP"]); |
| threadContextInfo_.SegGs = AgentUtils::HexStringToInt( |
| registerValueCache_["GS"]); |
| threadContextInfo_.SegFs = AgentUtils::HexStringToInt( |
| registerValueCache_["FS"]); |
| threadContextInfo_.SegEs = AgentUtils::HexStringToInt( |
| registerValueCache_["ES"]); |
| threadContextInfo_.SegDs = AgentUtils::HexStringToInt( |
| registerValueCache_["DS"]); |
| threadContextInfo_.SegCs = AgentUtils::HexStringToInt( |
| registerValueCache_["CS"]); |
| threadContextInfo_.EFlags = AgentUtils::HexStringToInt( |
| registerValueCache_["EFL"]); |
| threadContextInfo_.SegSs = AgentUtils::HexStringToInt( |
| registerValueCache_["SS"]); |
| SetThreadContext(handle_, &threadContextInfo_); |
| } |
| } |
| |
| WinThread* WinThread::GetThreadByID(int processID, int threadID) { |
| std::pair<int, int> ptPair(processID, threadID); |
| std::map<std::pair<int, int>, WinThread*>::iterator iter = threadIDMap_.find(ptPair); |
| if (iter == threadIDMap_.end()) |
| return NULL; |
| else |
| return iter->second; |
| } |
| |
| std::vector<std::string> WinThread::GetRegisterValues( |
| const std::vector<std::string>& registerIDs) { |
| std::vector<std::string> registerValues; |
| |
| if (isSuspended()) { |
| EnsureValidContextInfo(); |
| |
| std::vector<std::string>::const_iterator itVectorData; |
| for (itVectorData = registerIDs.begin(); itVectorData |
| != registerIDs.end(); itVectorData++) { |
| std::string registerID = *itVectorData; |
| std::string registerValue = registerValueCache_[registerID]; |
| registerValues.push_back(registerValue); |
| } |
| } |
| |
| return registerValues; |
| } |
| |
| /* |
| * Get pointer to register value cache for a given register. |
| * Return NULL if the register is not found. |
| */ |
| void* WinThread::getRegisterValueBuffer(const std::string& regName) { |
| void* v = NULL; |
| |
| if (regName == "EAX") |
| v = (void*)&threadContextInfo_.Eax; |
| else if (regName == "EBX") |
| v = (void*)&threadContextInfo_.Ebx; |
| else if (regName == "ECX") |
| v = (void*)&threadContextInfo_.Ecx; |
| else if (regName == "EDX") |
| v = (void*)&threadContextInfo_.Edx; |
| else if (regName == "ESP") |
| v = (void*)&threadContextInfo_.Esp; |
| else if (regName == "EBP") |
| v = (void*)&threadContextInfo_.Ebp; |
| else if (regName == "ESI") |
| v = (void*)&threadContextInfo_.Esi; |
| else if (regName == "EDI") |
| v = (void*)&threadContextInfo_.Edi; |
| else if (regName == "EIP") |
| v = (void*)&threadContextInfo_.Eip; |
| else if (regName == "EFL") |
| v = (void*)&threadContextInfo_.EFlags; |
| else if (regName == "GS") |
| v = (void*)&threadContextInfo_.SegGs; |
| else if (regName == "FS") |
| v = (void*)&threadContextInfo_.SegFs; |
| else if (regName == "ES") |
| v = (void*)&threadContextInfo_.SegEs; |
| else if (regName == "DS") |
| v = (void*)&threadContextInfo_.SegDs; |
| else if (regName == "CS") |
| v = (void*)&threadContextInfo_.SegCs; |
| else if (regName == "SS") |
| v = (void*)&threadContextInfo_.SegSs; |
| else { |
| assert(false); |
| } |
| |
| return v; |
| } |
| |
| /* |
| * Read one register. |
| * Return binary data buffer, which caller should free by calling delete[]. |
| */ |
| char* WinThread::GetRegisterValue(const std::string& regName, int regSize) { |
| |
| char* ret = NULL; |
| |
| if (isSuspended()) { |
| EnsureValidContextInfo(); |
| |
| ret = new char[regSize]; |
| |
| void* v = getRegisterValueBuffer(regName); |
| assert(v != NULL); |
| |
| memcpy((void*)ret, v, regSize); |
| } |
| |
| return ret; |
| } |
| |
| bool WinThread::SetRegisterValue(const std::string& regName, int regSize, char* val) { |
| |
| if (! isSuspended()) |
| return false; |
| |
| void* v = getRegisterValueBuffer(regName); |
| assert(v != NULL); |
| |
| memcpy(v, (void*)val, regSize); |
| return SetThreadContext(handle_, &threadContextInfo_); |
| } |
| |
| void WinThread::SetRegisterValues(const std::vector<std::string>& registerIDs, |
| const std::vector<std::string>& registerValues) { |
| if (isSuspended()) { |
| std::vector<std::string>::const_reverse_iterator itVectorData; |
| int idx = registerValues.size(); |
| for (itVectorData = registerIDs.rbegin(); itVectorData |
| != registerIDs.rend(); itVectorData++) { |
| std::string registerID = *itVectorData; |
| registerValueCache_[registerID] = registerValues[--idx]; |
| } |
| |
| SetContextInfo(); |
| } |
| } |
| |
| int WinThread::ReadMemory(const ReadWriteMemoryParams& params) throw (AgentException) { |
| return parentProcess_.ReadMemory(params); |
| } |
| |
| int WinThread::WriteMemory(const ReadWriteMemoryParams& params) throw (AgentException) { |
| return parentProcess_.WriteMemory(params); |
| } |
| |
| void WinThread::Terminate(const AgentActionParams& params) throw (AgentException) { |
| parentProcess_.Terminate(params); |
| } |
| |
| void WinThread::Suspend(const AgentActionParams& params) throw (AgentException) { |
| DWORD suspendCount = SuspendThread(handle_); |
| MarkSuspended(); |
| EnsureValidContextInfo(); |
| exceptionInfo_.ExceptionRecord.ExceptionCode = USER_SUSPEND_THREAD; // "Suspended" |
| isUserSuspended_ = true; |
| if (! isTerminating_) // don't send Suspend event if we are terminating. |
| EventClientNotifier::SendContextSuspended(this, |
| GetPCAddress(), GetSuspendReason(), GetExceptionMessage()); |
| Logger::getLogger().Log(Logger::LOG_NORMAL, "WinThread::Suspend", |
| "suspendCount: %d", suspendCount); |
| |
| params.reportSuccessForAction(); |
| } |
| |
| void WinThread::Resume(const AgentActionParams& params) throw (AgentException) { |
| if (! isSuspended()) { |
| params.reportSuccessForAction(); |
| return; |
| } |
| |
| if (isUserSuspended_){ |
| ResumeThread(handle_); |
| isUserSuspended_ = false; |
| params.reportSuccessForAction(); |
| } |
| else { |
| parentProcess_.GetMonitor()->PostAction(new ResumeContextAction( |
| params, parentProcess_, *this, RM_RESUME)); |
| } |
| } |
| |
| /* |
| * Enable single instruction step by setting Trap Flag (TF) bit. |
| */ |
| void WinThread::EnableSingleStep() { |
| #define FLAG_TRACE_BIT 0x100 |
| // The bit will be auto-cleared after next resume. |
| threadContextInfo_.EFlags |= FLAG_TRACE_BIT; |
| SetThreadContext(handle_, &threadContextInfo_); |
| } |
| |
| void WinThread::SingleStep(const AgentActionParams& params) throw (AgentException) { |
| parentProcess_.GetMonitor()->PostAction(new ResumeContextAction( |
| params, parentProcess_, *this, RM_STEP_INTO)); |
| } |
| |
| void WinThread::PrepareForTermination(const AgentActionParams& params) throw (AgentException) { |
| isTerminating_ = true; |
| |
| if (isSuspended()) { |
| Suspend(params); |
| ContinueDebugEvent(parentProcess_.GetOSID(), GetOSID(), DBG_CONTINUE); |
| } |
| } |