blob: 32c63d31b5b8bca86c94bca9c321b5f9688ba79b [file] [log] [blame]
/*******************************************************************************
* 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 <iostream>
#include "TCFHeaders.h"
#include "WinDebugMonitor.h"
#include "Logger.h"
#include "WinProcess.h"
#include "WinThread.h"
#include "EventClientNotifier.h"
#include "AgentUtils.h"
#include "psapi.h"
#include "AgentAction.h"
#include "ContextManager.h"
#include "LoggingService.h"
#include "SettingsService.h"
#define BUFSIZE 512
std::string GetExecutableInfo(HANDLE hFile, unsigned long& baseOfCode, unsigned long& codeSize)
{
codeSize = 0;
BOOL bSuccess = FALSE;
TCHAR pszFilename[MAX_PATH+1];
HANDLE hFileMap;
std::wstring path;
// Get the file size.
DWORD dwFileSizeHi = 0;
DWORD dwFileSizeLo = GetFileSize(hFile, &dwFileSizeHi);
if( dwFileSizeLo == 0 && dwFileSizeHi == 0 )
{
printf("Cannot map a file with a length of zero.\n");
return FALSE;
}
// Create a file mapping object.
hFileMap = CreateFileMapping(hFile,
NULL,
PAGE_READONLY,
0,
1,
NULL);
if (hFileMap)
{
// Create a file mapping to get the file name.
void* pMem = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 1);
if (pMem)
{
PIMAGE_DOS_HEADER dosHeader=(PIMAGE_DOS_HEADER )pMem;
PIMAGE_NT_HEADERS pNTHeader;
pNTHeader = (PIMAGE_NT_HEADERS) ((DWORD)dosHeader + dosHeader->e_lfanew);
if ( pNTHeader->Signature == IMAGE_NT_SIGNATURE )
{
PIMAGE_OPTIONAL_HEADER OptionalHeader = (PIMAGE_OPTIONAL_HEADER)&pNTHeader->OptionalHeader;
codeSize = OptionalHeader->SizeOfCode;
baseOfCode = OptionalHeader->BaseOfCode;
}
if (GetMappedFileName (GetCurrentProcess(),
pMem,
pszFilename,
MAX_PATH))
{
// Translate path with device name to drive letters.
TCHAR szTemp[BUFSIZE];
szTemp[0] = '\0';
if (GetLogicalDriveStrings(BUFSIZE-1, szTemp))
{
TCHAR szName[MAX_PATH];
TCHAR szDrive[3] = TEXT(" :");
BOOL bFound = FALSE;
TCHAR* p = szTemp;
do
{
// Copy the drive letter to the template string
*szDrive = *p;
// Look up each device name
if (QueryDosDevice(szDrive, szName, MAX_PATH))
{
UINT uNameLen = _tcslen(szName);
if (uNameLen < MAX_PATH)
{
bFound = _tcsnicmp(pszFilename, szName,
uNameLen) == 0;
if (bFound)
{
// Reconstruct pszFilename using szTempFile
// Replace device path with DOS path
TCHAR szTempFile[MAX_PATH];
snprintf(szTempFile, sizeof(szTempFile),
TEXT("%s%s"),
szDrive,
pszFilename+uNameLen);
strncpy(pszFilename, szTempFile, _tcslen(szTempFile));
pszFilename[_tcslen(szTempFile)] = 0;
}
}
}
// Go to the next NULL character.
while (*p++);
} while (!bFound && *p); // end of string
}
}
bSuccess = TRUE;
UnmapViewOfFile(pMem);
}
CloseHandle(hFileMap);
}
return AgentUtils::makeString(pszFilename);
}
WinDebugMonitor::WinDebugMonitor(const LaunchProcessParams& params) :
DebugMonitor(params)
{
memset(&processInfo, 0, sizeof(processInfo));
handledInitialDebugBreakpoint = false;
waitForDebugEvents = true;
wfdeWait = 50;
monitorThread_ = NULL;
isAttach = false;
}
WinDebugMonitor::WinDebugMonitor(const AttachToProcessParams& params) :
DebugMonitor(params)
{
memset(&processInfo, 0, sizeof(processInfo));
// TODO don't think the NTDLL!DbgBreakPoint applies when attaching but need to test. if you hit that breakpoint
// right after attaching then this needs to be change to false
handledInitialDebugBreakpoint = true;
waitForDebugEvents = true;
wfdeWait = 50;
monitorThread_ = NULL;
this->processID = (DWORD) params.processID;
isAttach = true;
}
WinDebugMonitor::~WinDebugMonitor(void)
{
}
void WinDebugMonitor::LaunchProcess(const LaunchProcessParams& params) throw (AgentException)
{
(new WinDebugMonitor(params))->StartMonitor();
}
/*
* Static method. Entry for attaching.
*/
void WinDebugMonitor::AttachToProcess(const AttachToProcessParams& params) throw (AgentException)
{
(new WinDebugMonitor(params))->StartMonitor();
}
void WinDebugMonitor::StartDebug() {
if (! isAttach)
StartProcessForDebug();
else
AttachToProcessForDebug();
}
void WinDebugMonitor::AttachToProcessForDebug()
{
// Note this is supposed to reply to TCF request ProcessService::Command_Attach().
if (!DebugActiveProcess(processID))
{
DWORD err = GetLastError();
AgentActionReply::postReply(channel, token, set_win32_errno(err));
} else {
// Allow detach without kill.
DebugSetProcessKillOnExit(false);
// OK
AgentActionReply::postReply(channel, token, 0);
}
}
void WinDebugMonitor::StartProcessForDebug()
{
STARTUPINFO si;
memset(&si, 0, sizeof(si));
si.cb = sizeof (si);
si.dwFlags = STARTF_FORCEONFEEDBACK | STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOWNORMAL;
TCHAR* argsBuffer = new TCHAR[args.size() + sizeof(TCHAR)];
strcpy(argsBuffer, args.c_str());
std::string exeName = executable;
LPTSTR workingDirectory = NULL;
if (directory.length() > 0)
{
workingDirectory = (LPTSTR)directory.c_str();
}
char* envBuffer = NULL;
std::string envString;
if (environment.size() > 0)
{
std::vector<std::string>::iterator itEnvData;
for (itEnvData = environment.begin(); itEnvData
!= environment.end(); itEnvData++)
{
std::string value = *itEnvData;
envString += value;
envString += char(0);
}
envString += char(0);
envBuffer = new char[envString.length()];
memcpy(envBuffer, envString.c_str(), envString.length());
}
if (!CreateProcess(exeName.c_str(), argsBuffer,
(LPSECURITY_ATTRIBUTES)NULL,
(LPSECURITY_ATTRIBUTES)NULL,
FALSE,
(GetDebugChildren() ? DEBUG_PROCESS : DEBUG_ONLY_THIS_PROCESS) | CREATE_NEW_CONSOLE,
envBuffer,
workingDirectory, //NULL,
(LPSTARTUPINFO)&si,
(LPPROCESS_INFORMATION)&processInfo))
{
DWORD err = GetLastError();
std::string msg = "Failed to start process ";
msg += '\"';
msg += AgentUtils::makeUTF8String(exeName);
msg += "\"";
err = set_win32_errno(err);
AgentActionReply::postReply(channel, token, err, 1, new std::string(msg));
} else {
// AOK
AgentActionReply::postReply(channel, token, 0, 1);
processID = processInfo.dwProcessId;
}
delete[] envBuffer;
delete[] argsBuffer;
}
void WinDebugMonitor::CaptureMonitorThread()
{
DuplicateHandle(GetCurrentProcess(),GetCurrentThread(),
GetCurrentProcess(),&monitorThread_,
0,FALSE,DUPLICATE_SAME_ACCESS);
}
DWORD WINAPI debuggerMonitorThread(LPVOID param)
{
WinDebugMonitor * dpm = (WinDebugMonitor*)param;
try
{
dpm->CaptureMonitorThread();
dpm->StartDebug();
dpm->EventLoop();
}
catch (const AgentException& e)
{
DWORD error = GetLastError();
trace(LOG_ALWAYS, "Agent Exception: code=%x: %s", error, e.what());
}
return 0;
}
void WinDebugMonitor::Suspend()
{
SuspendThread(monitorThread_);
}
void WinDebugMonitor::Resume()
{
ResumeThread(monitorThread_);
}
void WinDebugMonitor::StartMonitor()
{
DWORD threadID = 0;
HANDLE startThread = CreateThread(
NULL, // default security attributes
0, // use default stack size
debuggerMonitorThread, // thread function name
this, // argument to thread function
0, // use default creation flags
&threadID); // returns the thread identifier
CloseHandle(startThread);
}
void WinDebugMonitor::EventLoop()
{
DEBUG_EVENT debugEvent;
while (waitForDebugEvents)
{
if (WaitForDebugEvent(&debugEvent, wfdeWait))
HandleDebugEvent(debugEvent);
else {
DWORD err = GetLastError();
if (err == ERROR_SEM_TIMEOUT || err == 0)
HandleNoDebugEvent();
else {
trace(LOG_ALWAYS, "WinDebugMonitor::EventLoop: error %d", err);
waitForDebugEvents = false;
}
}
}
trace(LOG_CONTEXT, "Monitor thread for process %d has exited. \n", processID);
}
void WinDebugMonitor::Attach(unsigned long pid, ContextAttachCallBack * done, void * data, int selfattach) {
// TODO: implement
}
static const char * win32_debug_event_name(int event) {
switch (event) {
case CREATE_PROCESS_DEBUG_EVENT:
return "CREATE_PROCESS_DEBUG_EVENT";
case CREATE_THREAD_DEBUG_EVENT:
return "CREATE_THREAD_DEBUG_EVENT";
case EXCEPTION_DEBUG_EVENT:
return "EXCEPTION_DEBUG_EVENT";
case EXIT_PROCESS_DEBUG_EVENT:
return "EXIT_PROCESS_DEBUG_EVENT";
case EXIT_THREAD_DEBUG_EVENT:
return "EXIT_THREAD_DEBUG_EVENT";
case LOAD_DLL_DEBUG_EVENT:
return "LOAD_DLL_DEBUG_EVENT";
case OUTPUT_DEBUG_STRING_EVENT:
return "OUTPUT_DEBUG_STRING_EVENT";
case UNLOAD_DLL_DEBUG_EVENT:
return "UNLOAD_DLL_DEBUG_EVENT";
}
return "Unknown";
}
void WinDebugMonitor::HandleDebugEvent(DEBUG_EVENT& debugEvent)
{
LogTrace("DebugProcessMonitor::HandleDebugEvent", "event code: %s", win32_debug_event_name(debugEvent.dwDebugEventCode));
switch (debugEvent.dwDebugEventCode)
{
case EXCEPTION_DEBUG_EVENT:
HandleExceptionEvent(debugEvent);
break;
case CREATE_PROCESS_DEBUG_EVENT:
HandleProcessCreatedEvent(debugEvent);
break;
case CREATE_THREAD_DEBUG_EVENT:
HandleThreadCreatedEvent(debugEvent);
break;
case EXIT_PROCESS_DEBUG_EVENT:
HandleProcessExitedEvent(debugEvent);
return;
case EXIT_THREAD_DEBUG_EVENT:
HandleThreadExitedEvent(debugEvent);
break;
case LOAD_DLL_DEBUG_EVENT:
HandleDLLLoadedEvent(debugEvent);
break;
case UNLOAD_DLL_DEBUG_EVENT:
HandleDLLUnloadedEvent(debugEvent);
break;
case OUTPUT_DEBUG_STRING_EVENT:
HandleDebugStringEvent(debugEvent);
break;
case RIP_EVENT:
HandleSystemDebugErrorEvent(debugEvent);
break;
default:
HandleUnknwonDebugEvent(debugEvent);
break;
}
}
void WinDebugMonitor::HandleNoDebugEvent()
{
while (!actions_.empty())
{
AgentAction* action = actions_.front();
actions_.pop();
action->Run();
delete action;
}
}
std::string WinDebugMonitor::GetDebugExceptionDescription(const EXCEPTION_DEBUG_INFO& exceptionInfo) {
DWORD code = exceptionInfo.ExceptionRecord.ExceptionCode;
const char* base = "Unknown Exception";
std::string detail;
switch (code) {
case EXCEPTION_SINGLE_STEP:
base = "Step";
break;
case EXCEPTION_BREAKPOINT:
base = "Breakpoint";
break;
case EXCEPTION_ACCESS_VIOLATION:
base = "Access violation";
detail = " at 0x" + AgentUtils::IntToHexString(exceptionInfo.ExceptionRecord.ExceptionInformation[1]);
break;
case DBG_CONTROL_C:
base = "Control-C";
break;
case DBG_CONTROL_BREAK:
base = "Control-Break";
break;
case STATUS_DATATYPE_MISALIGNMENT:
base = "Datatype misalignment";
break;
case STATUS_IN_PAGE_ERROR:
base = "Virtual memory paging error";
break;
case STATUS_NO_MEMORY:
base = "Out of memory";
break;
case STATUS_ILLEGAL_INSTRUCTION:
base = "Illegal instruction";
break;
case STATUS_NONCONTINUABLE_EXCEPTION:
base = "Noncontinuable exception";
break;
case STATUS_INVALID_DISPOSITION:
base = "Invalid disposition";
break;
case STATUS_ARRAY_BOUNDS_EXCEEDED:
base = "Array bounds exceeded";
break;
case STATUS_FLOAT_DENORMAL_OPERAND:
base = "Floating point denormal operand";
break;
case STATUS_FLOAT_DIVIDE_BY_ZERO:
base = "Floating point divide by zero";
break;
case STATUS_FLOAT_INEXACT_RESULT:
base = "Floating point inexact result";
break;
case STATUS_FLOAT_INVALID_OPERATION:
base = "Floating point invalid operation";
break;
case STATUS_FLOAT_STACK_CHECK:
base = "Floating point stack check";
break;
case STATUS_FLOAT_OVERFLOW:
base = "Floating point overflow";
break;
case STATUS_FLOAT_UNDERFLOW:
base = "Floating point underflow";
break;
case STATUS_INTEGER_DIVIDE_BY_ZERO:
base = "Integer divide by zero";
break;
case STATUS_INTEGER_OVERFLOW:
base = "Integer overflow";
break;
case STATUS_PRIVILEGED_INSTRUCTION:
base = "Privileged instruction";
break;
case STATUS_STACK_OVERFLOW:
base = "Stack overflow";
break;
case STATUS_DLL_NOT_FOUND:
base = "DLL not found";
// TODO: find out how to determine which DLL it was...
break;
case STATUS_DLL_INIT_FAILED:
base = "DLL initialization failed";
break;
case STATUS_ENTRYPOINT_NOT_FOUND:
base = "Entry point not found";
break;
case MS_CPLUS_EXCEPTION:
base = "C++ exception";
break;
case RPC_S_UNKNOWN_IF:
base = "RPC unknown interface";
break;
case RPC_S_SERVER_UNAVAILABLE:
base = "RPC server unavailable";
break;
}
if (detail.size() > 0) {
return std::string(base) + detail;
}
return base;
}
void WinDebugMonitor::HandleExceptionEvent(DEBUG_EVENT& debugEvent)
{
const EXCEPTION_DEBUG_INFO& excInfo = debugEvent.u.Exception;
const EXCEPTION_RECORD& e = excInfo.ExceptionRecord;
const DWORD& excCode = e.ExceptionCode;
LogTrace("DebugProcessMonitor::HandleExceptionEvent", "Exception in thread %d at 0x%x: code 0x%x (%s)",
debugEvent.dwThreadId, e.ExceptionAddress, e.ExceptionCode,
GetDebugExceptionDescription(excInfo).c_str());
// ignore initial breakpoint in NTDLL!DbgBreakPoint for newly launched process if necessary
if (!handledInitialDebugBreakpoint && e.ExceptionCode == EXCEPTION_BREAKPOINT)
{
trace(LOG_ALWAYS, "\t -- ignored: initial ntdll DbgBreakPoint breakpoint");
handledInitialDebugBreakpoint = true;
// The "DBG_EXCEPTION_NOT_HANDLED" flag will make some other unknown breakpoint(s) in system dlls
// show up on some machines. But "DBG_CONTINUE" won't.
ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE);
return;
}
bool potentialProtectedPage = excCode == EXCEPTION_ACCESS_VIOLATION;
bool reportException = SettingsService::reportExceptionEvent(excInfo);
bool handled = false;
if (reportException || potentialProtectedPage)
{
// wait to search for a threadByID until necessary
WinThread* thread = WinThread::GetThreadByID(debugEvent.dwProcessId, debugEvent.dwThreadId);
if (!thread)
assert(false);
if (thread && potentialProtectedPage)
{
trace(LOG_ALWAYS, "\t -- checking potential protected page.");
handled = thread->HandlePotentialProtctedPage(debugEvent);
}
if (thread && !handled)
{
if (reportException)
{
trace(LOG_ALWAYS, "\t -- reported to host debugger.");
thread->HandleException(debugEvent);
handled = true;
}
else
{
thread->MarkResumed();
}
}
}
if (!handled)
{
trace(LOG_ALWAYS, "\t -- ignored.");
// Note DBG_EXCEPTION_NOT_HANDLED is used here.
ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED);
}
}
void WinDebugMonitor::HandleProcessCreatedEvent(DEBUG_EVENT& debugEvent)
{
WinProcess* process = new WinProcess(this, debugEvent);
WinThread* thread = new WinThread(*process, debugEvent);
process->SetDebugging(true);
thread->SetDebugging(true);
// record in our cache
ContextManager::addContext(process);
ContextManager::addContext(thread);
// Notify host
EventClientNotifier::SendContextAdded(process);
EventClientNotifier::SendContextAdded(thread);
unsigned long codeSize = 0;
unsigned long baseOfCode = 0;
std::string imageName = GetExecutableInfo(debugEvent.u.CreateProcessInfo.hFile, baseOfCode, codeSize);
if (SettingsService::reportDebugEventForModule(imageName))
thread->HandleExecutableEvent(true, imageName, (unsigned long)debugEvent.u.CreateProcessInfo.lpBaseOfImage, codeSize + baseOfCode);
else
ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE);
CloseHandle(debugEvent.u.CreateProcessInfo.hFile);
}
void WinDebugMonitor::HandleThreadCreatedEvent(DEBUG_EVENT& debugEvent)
{
WinProcess* process = WinProcess::GetProcessByID(debugEvent.dwProcessId);
if (process) {
WinThread* thread = new WinThread(*process, debugEvent);
thread->SetDebugging(true);
ContextManager::addContext(thread);
EventClientNotifier::SendContextAdded(thread);
} else {
assert(false);
}
ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE);
}
void WinDebugMonitor::HandleProcessExitedEvent(DEBUG_EVENT& debugEvent)
{
WinProcess* process = WinProcess::GetProcessByID(debugEvent.dwProcessId);
if (process) {
EventClientNotifier::SendContextRemoved(process, true);
} else {
assert(false);
}
ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE);
}
/**
* Make sure monitor thread is killed after the process being debugged died or
* is terminated.
* Note there is one monitor thread for each debugged process. The "process_id"
* passed in should be the process being monitored.
*/
void WinDebugMonitor::ProcessDied(DWORD process_id)
{
// Terminate monitor thread if the process being monitored has died.
if (processID == process_id)
waitForDebugEvents = false;
else
assert(false);
}
void WinDebugMonitor::HandleThreadExitedEvent(DEBUG_EVENT& debugEvent)
{
WinThread* thread = WinThread::GetThreadByID(debugEvent.dwProcessId, debugEvent.dwThreadId);
if (thread) {
EventClientNotifier::SendContextRemoved(thread, true);
} else {
assert(false);
}
ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE);
}
void WinDebugMonitor::HandleDLLLoadedEvent(DEBUG_EVENT& debugEvent)
{
unsigned long codeSize = 0;
unsigned long baseOfCode = 0;
std::string moduleName = GetExecutableInfo(debugEvent.u.LoadDll.hFile, baseOfCode, codeSize);
if (SettingsService::reportDebugEventForModule(moduleName))
{
LogTrace("DebugProcessMonitor::HandleDLLLoadedEvent", "Base address: %8.8x %s", debugEvent.u.LoadDll.lpBaseOfDll, moduleName.c_str());
WinThread* thread = WinThread::GetThreadByID(debugEvent.dwProcessId, debugEvent.dwThreadId);
if (thread) {
thread->HandleExecutableEvent(true, moduleName, (unsigned long)debugEvent.u.LoadDll.lpBaseOfDll, codeSize);
}
}
else
{
ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE);
}
CloseHandle(debugEvent.u.LoadDll.hFile);
}
void WinDebugMonitor::HandleDLLUnloadedEvent(DEBUG_EVENT& debugEvent)
{
// if our process knows about this dll then it must be in the list of modules to debug
WinProcess* process = WinProcess::GetProcessByID(debugEvent.dwProcessId);
if (process)
{
Properties props = process->GetExecutablesByAddress()[(unsigned long)debugEvent.u.UnloadDll.lpBaseOfDll];
if (!props.empty())
{
WinThread* thread = WinThread::GetThreadByID(debugEvent.dwProcessId, debugEvent.dwThreadId);
if (thread) {
thread->HandleExecutableEvent(false, "", (unsigned long)debugEvent.u.UnloadDll.lpBaseOfDll, 0);
}
}
}
ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE);
}
void WinDebugMonitor::HandleDebugStringEvent(DEBUG_EVENT& debugEvent)
{
if (SettingsService::reportDebugStringEvents())
{
WinProcess* process = WinProcess::GetProcessByID(debugEvent.dwProcessId);
if (debugEvent.u.DebugString.fUnicode == 0)
{
int debugStringLength = debugEvent.u.DebugString.nDebugStringLength;
char* debugStringBuffer = new char[debugStringLength + 1];
ReadProcessMemory(process->GetProcessHandle(), debugEvent.u.DebugString.lpDebugStringData, debugStringBuffer,
debugStringLength,NULL);
debugStringBuffer[debugStringLength] = 0;
// convert from ansi to utf-8
wchar_t* wideChars = new wchar_t[debugStringLength];
// Covert to Unicode.
if (MultiByteToWideChar(CP_ACP, 0, debugStringBuffer, debugStringLength,
wideChars, debugStringLength) != 0)
{
int size_needed = WideCharToMultiByte(CP_UTF8, 0, wideChars, debugStringLength, NULL, 0, NULL, NULL);
std::string strTo( size_needed, 0 );
WideCharToMultiByte(CP_UTF8, 0, wideChars, debugStringLength, &strTo[0], size_needed, NULL, NULL);
// write console data, if console
LoggingService::WriteLoggingMessage(channel, strTo, LoggingService::GetWindowsConsoleID());
LogTrace("DebugProcessMonitor::HandleDebugStringEvent", "%s", strTo.c_str());
}
delete[] wideChars;
delete[] debugStringBuffer;
}
}
ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE);
}
void WinDebugMonitor::HandleSystemDebugErrorEvent(DEBUG_EVENT& debugEvent)
{
ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE);
}
void WinDebugMonitor::HandleUnknwonDebugEvent(DEBUG_EVENT& debugEvent)
{
ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE);
}
void WinDebugMonitor::PostAction(AgentAction* action)
{
actions_.push(action);
}