michael@0: // Copyright (c) 2009 The Chromium Authors. All rights reserved. michael@0: // Use of this source code is governed by a BSD-style license that can be michael@0: // found in the LICENSE file. michael@0: michael@0: // We need extended process and thread attribute support michael@0: #undef _WIN32_WINNT michael@0: #define _WIN32_WINNT 0x0600 michael@0: michael@0: #include "base/process_util.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "base/debug_util.h" michael@0: #include "base/histogram.h" michael@0: #include "base/logging.h" michael@0: #include "base/scoped_handle_win.h" michael@0: #include "base/scoped_ptr.h" michael@0: #include "base/win_util.h" michael@0: michael@0: #include michael@0: michael@0: namespace { michael@0: michael@0: // System pagesize. This value remains constant on x86/64 architectures. michael@0: const int PAGESIZE_KB = 4; michael@0: michael@0: // HeapSetInformation function pointer. michael@0: typedef BOOL (WINAPI* HeapSetFn)(HANDLE, HEAP_INFORMATION_CLASS, PVOID, SIZE_T); michael@0: michael@0: typedef BOOL (WINAPI * InitializeProcThreadAttributeListFn)( michael@0: LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList, michael@0: DWORD dwAttributeCount, michael@0: DWORD dwFlags, michael@0: PSIZE_T lpSize michael@0: ); michael@0: michael@0: typedef BOOL (WINAPI * DeleteProcThreadAttributeListFn)( michael@0: LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList michael@0: ); michael@0: michael@0: typedef BOOL (WINAPI * UpdateProcThreadAttributeFn)( michael@0: LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList, michael@0: DWORD dwFlags, michael@0: DWORD_PTR Attribute, michael@0: PVOID lpValue, michael@0: SIZE_T cbSize, michael@0: PVOID lpPreviousValue, michael@0: PSIZE_T lpReturnSize michael@0: ); michael@0: michael@0: static InitializeProcThreadAttributeListFn InitializeProcThreadAttributeListPtr; michael@0: static DeleteProcThreadAttributeListFn DeleteProcThreadAttributeListPtr; michael@0: static UpdateProcThreadAttributeFn UpdateProcThreadAttributePtr; michael@0: michael@0: static mozilla::EnvironmentLog gProcessLog("MOZ_PROCESS_LOG"); michael@0: michael@0: } // namespace michael@0: michael@0: namespace base { michael@0: michael@0: ProcessId GetCurrentProcId() { michael@0: return ::GetCurrentProcessId(); michael@0: } michael@0: michael@0: ProcessHandle GetCurrentProcessHandle() { michael@0: return ::GetCurrentProcess(); michael@0: } michael@0: michael@0: bool OpenProcessHandle(ProcessId pid, ProcessHandle* handle) { michael@0: // TODO(phajdan.jr): Take even more permissions out of this list. michael@0: ProcessHandle result = OpenProcess(PROCESS_DUP_HANDLE | michael@0: PROCESS_TERMINATE | michael@0: PROCESS_QUERY_INFORMATION | michael@0: SYNCHRONIZE, michael@0: FALSE, pid); michael@0: michael@0: if (result == INVALID_HANDLE_VALUE) michael@0: return false; michael@0: michael@0: *handle = result; michael@0: return true; michael@0: } michael@0: michael@0: bool OpenPrivilegedProcessHandle(ProcessId pid, ProcessHandle* handle) { michael@0: ProcessHandle result = OpenProcess(PROCESS_DUP_HANDLE | michael@0: PROCESS_TERMINATE | michael@0: PROCESS_QUERY_INFORMATION | michael@0: PROCESS_VM_READ | michael@0: SYNCHRONIZE, michael@0: FALSE, pid); michael@0: michael@0: if (result == INVALID_HANDLE_VALUE) michael@0: return false; michael@0: michael@0: *handle = result; michael@0: return true; michael@0: } michael@0: michael@0: void CloseProcessHandle(ProcessHandle process) { michael@0: // closing a handle twice on Windows can be catastrophic - after the first michael@0: // close the handle value may be reused, so the second close will kill that michael@0: // other new handle. michael@0: BOOL ok = CloseHandle(process); michael@0: DCHECK(ok); michael@0: } michael@0: michael@0: // Helper for GetProcId() michael@0: bool GetProcIdViaGetProcessId(ProcessHandle process, DWORD* id) { michael@0: // Dynamically get a pointer to GetProcessId(). michael@0: typedef DWORD (WINAPI *GetProcessIdFunction)(HANDLE); michael@0: static GetProcessIdFunction GetProcessIdPtr = NULL; michael@0: static bool initialize_get_process_id = true; michael@0: if (initialize_get_process_id) { michael@0: initialize_get_process_id = false; michael@0: HMODULE kernel32_handle = GetModuleHandle(L"kernel32.dll"); michael@0: if (!kernel32_handle) { michael@0: NOTREACHED(); michael@0: return false; michael@0: } michael@0: GetProcessIdPtr = reinterpret_cast(GetProcAddress( michael@0: kernel32_handle, "GetProcessId")); michael@0: } michael@0: if (!GetProcessIdPtr) michael@0: return false; michael@0: // Ask for the process ID. michael@0: *id = (*GetProcessIdPtr)(process); michael@0: return true; michael@0: } michael@0: michael@0: // Helper for GetProcId() michael@0: bool GetProcIdViaNtQueryInformationProcess(ProcessHandle process, DWORD* id) { michael@0: // Dynamically get a pointer to NtQueryInformationProcess(). michael@0: typedef NTSTATUS (WINAPI *NtQueryInformationProcessFunction)( michael@0: HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG); michael@0: static NtQueryInformationProcessFunction NtQueryInformationProcessPtr = NULL; michael@0: static bool initialize_query_information_process = true; michael@0: if (initialize_query_information_process) { michael@0: initialize_query_information_process = false; michael@0: // According to nsylvain, ntdll.dll is guaranteed to be loaded, even though michael@0: // the Windows docs seem to imply that you should LoadLibrary() it. michael@0: HMODULE ntdll_handle = GetModuleHandle(L"ntdll.dll"); michael@0: if (!ntdll_handle) { michael@0: NOTREACHED(); michael@0: return false; michael@0: } michael@0: NtQueryInformationProcessPtr = michael@0: reinterpret_cast(GetProcAddress( michael@0: ntdll_handle, "NtQueryInformationProcess")); michael@0: } michael@0: if (!NtQueryInformationProcessPtr) michael@0: return false; michael@0: // Ask for the process ID. michael@0: PROCESS_BASIC_INFORMATION info; michael@0: ULONG bytes_returned; michael@0: NTSTATUS status = (*NtQueryInformationProcessPtr)(process, michael@0: ProcessBasicInformation, michael@0: &info, sizeof info, michael@0: &bytes_returned); michael@0: if (!SUCCEEDED(status) || (bytes_returned != (sizeof info))) michael@0: return false; michael@0: michael@0: *id = static_cast(info.UniqueProcessId); michael@0: return true; michael@0: } michael@0: michael@0: ProcessId GetProcId(ProcessHandle process) { michael@0: // Get a handle to |process| that has PROCESS_QUERY_INFORMATION rights. michael@0: HANDLE current_process = GetCurrentProcess(); michael@0: HANDLE process_with_query_rights; michael@0: if (DuplicateHandle(current_process, process, current_process, michael@0: &process_with_query_rights, PROCESS_QUERY_INFORMATION, michael@0: false, 0)) { michael@0: // Try to use GetProcessId(), if it exists. Fall back on michael@0: // NtQueryInformationProcess() otherwise (< Win XP SP1). michael@0: DWORD id; michael@0: bool success = michael@0: GetProcIdViaGetProcessId(process_with_query_rights, &id) || michael@0: GetProcIdViaNtQueryInformationProcess(process_with_query_rights, &id); michael@0: CloseHandle(process_with_query_rights); michael@0: if (success) michael@0: return id; michael@0: } michael@0: michael@0: // We're screwed. michael@0: NOTREACHED(); michael@0: return 0; michael@0: } michael@0: michael@0: // from sandbox_policy_base.cc in a later version of the chromium ipc code... michael@0: bool IsInheritableHandle(HANDLE handle) { michael@0: if (!handle) michael@0: return false; michael@0: if (handle == INVALID_HANDLE_VALUE) michael@0: return false; michael@0: // File handles (FILE_TYPE_DISK) and pipe handles are known to be michael@0: // inheritable. Console handles (FILE_TYPE_CHAR) are not michael@0: // inheritable via PROC_THREAD_ATTRIBUTE_HANDLE_LIST. michael@0: DWORD handle_type = GetFileType(handle); michael@0: return handle_type == FILE_TYPE_DISK || handle_type == FILE_TYPE_PIPE; michael@0: } michael@0: michael@0: void LoadThreadAttributeFunctions() { michael@0: HMODULE kernel32 = GetModuleHandle(L"kernel32.dll"); michael@0: InitializeProcThreadAttributeListPtr = michael@0: reinterpret_cast michael@0: (GetProcAddress(kernel32, "InitializeProcThreadAttributeList")); michael@0: DeleteProcThreadAttributeListPtr = michael@0: reinterpret_cast michael@0: (GetProcAddress(kernel32, "DeleteProcThreadAttributeList")); michael@0: UpdateProcThreadAttributePtr = michael@0: reinterpret_cast michael@0: (GetProcAddress(kernel32, "UpdateProcThreadAttribute")); michael@0: } michael@0: michael@0: // Creates and returns a "thread attribute list" to pass to the child process. michael@0: // On return, is a pointer to a THREAD_ATTRIBUTE_LIST or NULL if either the michael@0: // functions we need aren't available (eg, XP or earlier) or the functions we michael@0: // need failed. michael@0: // The result of this function must be passed to FreeThreadAttributeList. michael@0: // Note that the pointer to the HANDLE array ends up embedded in the result of michael@0: // this function and must stay alive until FreeThreadAttributeList is called, michael@0: // hence it is passed in so the owner is the caller of this function. michael@0: LPPROC_THREAD_ATTRIBUTE_LIST CreateThreadAttributeList(HANDLE *handlesToInherit, michael@0: int handleCount) { michael@0: if (!InitializeProcThreadAttributeListPtr || michael@0: !DeleteProcThreadAttributeListPtr || michael@0: !UpdateProcThreadAttributePtr) michael@0: LoadThreadAttributeFunctions(); michael@0: // shouldn't happen as we are only called for Vista+, but better safe than sorry... michael@0: if (!InitializeProcThreadAttributeListPtr || michael@0: !DeleteProcThreadAttributeListPtr || michael@0: !UpdateProcThreadAttributePtr) michael@0: return NULL; michael@0: michael@0: SIZE_T threadAttrSize; michael@0: LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList; michael@0: michael@0: if (!(*InitializeProcThreadAttributeListPtr)(NULL, 1, 0, &threadAttrSize) && michael@0: GetLastError() != ERROR_INSUFFICIENT_BUFFER) michael@0: goto fail; michael@0: lpAttributeList = reinterpret_cast michael@0: (malloc(threadAttrSize)); michael@0: if (!lpAttributeList || michael@0: !(*InitializeProcThreadAttributeListPtr)(lpAttributeList, 1, 0, &threadAttrSize)) michael@0: goto fail; michael@0: michael@0: if (!(*UpdateProcThreadAttributePtr)(lpAttributeList, michael@0: 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, michael@0: handlesToInherit, michael@0: sizeof(handlesToInherit[0]) * handleCount, michael@0: NULL, NULL)) { michael@0: (*DeleteProcThreadAttributeListPtr)(lpAttributeList); michael@0: goto fail; michael@0: } michael@0: return lpAttributeList; michael@0: michael@0: fail: michael@0: if (lpAttributeList) michael@0: free(lpAttributeList); michael@0: return NULL; michael@0: } michael@0: michael@0: // Frees the data returned by CreateThreadAttributeList. michael@0: void FreeThreadAttributeList(LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList) { michael@0: // must be impossible to get a NULL DeleteProcThreadAttributeListPtr, as michael@0: // we already checked it existed when creating the data we are now freeing. michael@0: (*DeleteProcThreadAttributeListPtr)(lpAttributeList); michael@0: free(lpAttributeList); michael@0: } michael@0: michael@0: bool LaunchApp(const std::wstring& cmdline, michael@0: bool wait, bool start_hidden, ProcessHandle* process_handle) { michael@0: michael@0: // We want to inherit the std handles so dump() statements and assertion michael@0: // messages in the child process can be seen - but we *do not* want to michael@0: // blindly have all handles inherited. Vista and later has a technique michael@0: // where only specified handles are inherited - so we use this technique if michael@0: // we can. If that technique isn't available (or it fails), we just don't michael@0: // inherit anything. This means that dump() etc isn't going to be seen on michael@0: // XP release builds, but that's OK (developers who really care can run a michael@0: // debug build on XP, where the processes are marked as "console" apps, so michael@0: // things work without these hoops) michael@0: DWORD dwCreationFlags = 0; michael@0: BOOL bInheritHandles = FALSE; michael@0: // We use a STARTUPINFOEX, but if we can't do the thread attribute thing, we michael@0: // just pass the size of a STARTUPINFO. michael@0: STARTUPINFOEX startup_info_ex; michael@0: ZeroMemory(&startup_info_ex, sizeof(startup_info_ex)); michael@0: STARTUPINFO &startup_info = startup_info_ex.StartupInfo; michael@0: startup_info.cb = sizeof(startup_info); michael@0: startup_info.dwFlags = STARTF_USESHOWWINDOW; michael@0: startup_info.wShowWindow = start_hidden ? SW_HIDE : SW_SHOW; michael@0: michael@0: LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = NULL; michael@0: // Don't even bother trying pre-Vista... michael@0: if (win_util::GetWinVersion() >= win_util::WINVERSION_VISTA) { michael@0: // setup our handle array first - if we end up with no handles that can michael@0: // be inherited we can avoid trying to do the ThreadAttributeList dance... michael@0: HANDLE handlesToInherit[2]; michael@0: int handleCount = 0; michael@0: HANDLE stdOut = ::GetStdHandle(STD_OUTPUT_HANDLE); michael@0: HANDLE stdErr = ::GetStdHandle(STD_ERROR_HANDLE); michael@0: michael@0: if (IsInheritableHandle(stdOut)) michael@0: handlesToInherit[handleCount++] = stdOut; michael@0: if (stdErr != stdOut && IsInheritableHandle(stdErr)) michael@0: handlesToInherit[handleCount++] = stdErr; michael@0: michael@0: if (handleCount) michael@0: lpAttributeList = CreateThreadAttributeList(handlesToInherit, handleCount); michael@0: } michael@0: michael@0: if (lpAttributeList) { michael@0: // it's safe to inherit handles, so arrange for that... michael@0: startup_info.cb = sizeof(startup_info_ex); michael@0: startup_info.dwFlags |= STARTF_USESTDHANDLES; michael@0: startup_info.hStdOutput = ::GetStdHandle(STD_OUTPUT_HANDLE); michael@0: startup_info.hStdError = ::GetStdHandle(STD_ERROR_HANDLE); michael@0: startup_info.hStdInput = INVALID_HANDLE_VALUE; michael@0: startup_info_ex.lpAttributeList = lpAttributeList; michael@0: dwCreationFlags |= EXTENDED_STARTUPINFO_PRESENT; michael@0: bInheritHandles = TRUE; michael@0: } michael@0: PROCESS_INFORMATION process_info; michael@0: BOOL createdOK = CreateProcess(NULL, michael@0: const_cast(cmdline.c_str()), NULL, NULL, michael@0: bInheritHandles, dwCreationFlags, NULL, NULL, michael@0: &startup_info, &process_info); michael@0: if (lpAttributeList) michael@0: FreeThreadAttributeList(lpAttributeList); michael@0: if (!createdOK) michael@0: return false; michael@0: michael@0: gProcessLog.print("==> process %d launched child process %d (%S)\n", michael@0: GetCurrentProcId(), michael@0: process_info.dwProcessId, michael@0: cmdline.c_str()); michael@0: michael@0: // Handles must be closed or they will leak michael@0: CloseHandle(process_info.hThread); michael@0: michael@0: if (wait) michael@0: WaitForSingleObject(process_info.hProcess, INFINITE); michael@0: michael@0: // If the caller wants the process handle, we won't close it. michael@0: if (process_handle) { michael@0: *process_handle = process_info.hProcess; michael@0: } else { michael@0: CloseHandle(process_info.hProcess); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool LaunchApp(const CommandLine& cl, michael@0: bool wait, bool start_hidden, ProcessHandle* process_handle) { michael@0: return LaunchApp(cl.command_line_string(), wait, michael@0: start_hidden, process_handle); michael@0: } michael@0: michael@0: bool KillProcess(ProcessHandle process, int exit_code, bool wait) { michael@0: bool result = (TerminateProcess(process, exit_code) != FALSE); michael@0: if (result && wait) { michael@0: // The process may not end immediately due to pending I/O michael@0: if (WAIT_OBJECT_0 != WaitForSingleObject(process, 60 * 1000)) michael@0: DLOG(ERROR) << "Error waiting for process exit: " << GetLastError(); michael@0: } else if (!result) { michael@0: DLOG(ERROR) << "Unable to terminate process: " << GetLastError(); michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: bool DidProcessCrash(bool* child_exited, ProcessHandle handle) { michael@0: DWORD exitcode = 0; michael@0: michael@0: if (child_exited) michael@0: *child_exited = true; // On Windows it an error to call this function if michael@0: // the child hasn't already exited. michael@0: if (!::GetExitCodeProcess(handle, &exitcode)) { michael@0: NOTREACHED(); michael@0: return false; michael@0: } michael@0: if (exitcode == STILL_ACTIVE) { michael@0: // The process is likely not dead or it used 0x103 as exit code. michael@0: NOTREACHED(); michael@0: return false; michael@0: } michael@0: michael@0: // Warning, this is not generic code; it heavily depends on the way michael@0: // the rest of the code kills a process. michael@0: michael@0: if (exitcode == PROCESS_END_NORMAL_TERMINATON || michael@0: exitcode == PROCESS_END_KILLED_BY_USER || michael@0: exitcode == PROCESS_END_PROCESS_WAS_HUNG || michael@0: exitcode == 0xC0000354 || // STATUS_DEBUGGER_INACTIVE. michael@0: exitcode == 0xC000013A || // Control-C/end session. michael@0: exitcode == 0x40010004) { // Debugger terminated process/end session. michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void SetCurrentProcessPrivileges(ChildPrivileges privs) { michael@0: michael@0: } michael@0: michael@0: NamedProcessIterator::NamedProcessIterator(const std::wstring& executable_name, michael@0: const ProcessFilter* filter) michael@0: : started_iteration_(false), michael@0: executable_name_(executable_name), michael@0: filter_(filter) { michael@0: snapshot_ = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); michael@0: } michael@0: michael@0: NamedProcessIterator::~NamedProcessIterator() { michael@0: CloseHandle(snapshot_); michael@0: } michael@0: michael@0: michael@0: const ProcessEntry* NamedProcessIterator::NextProcessEntry() { michael@0: bool result = false; michael@0: do { michael@0: result = CheckForNextProcess(); michael@0: } while (result && !IncludeEntry()); michael@0: michael@0: if (result) { michael@0: return &entry_; michael@0: } michael@0: michael@0: return NULL; michael@0: } michael@0: michael@0: bool NamedProcessIterator::CheckForNextProcess() { michael@0: InitProcessEntry(&entry_); michael@0: michael@0: if (!started_iteration_) { michael@0: started_iteration_ = true; michael@0: return !!Process32First(snapshot_, &entry_); michael@0: } michael@0: michael@0: return !!Process32Next(snapshot_, &entry_); michael@0: } michael@0: michael@0: bool NamedProcessIterator::IncludeEntry() { michael@0: return _wcsicmp(executable_name_.c_str(), entry_.szExeFile) == 0 && michael@0: (!filter_ || filter_->Includes(entry_.th32ProcessID, michael@0: entry_.th32ParentProcessID)); michael@0: } michael@0: michael@0: void NamedProcessIterator::InitProcessEntry(ProcessEntry* entry) { michael@0: memset(entry, 0, sizeof(*entry)); michael@0: entry->dwSize = sizeof(*entry); michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: // ProcesMetrics michael@0: michael@0: ProcessMetrics::ProcessMetrics(ProcessHandle process) : process_(process), michael@0: last_time_(0), michael@0: last_system_time_(0) { michael@0: SYSTEM_INFO system_info; michael@0: GetSystemInfo(&system_info); michael@0: processor_count_ = system_info.dwNumberOfProcessors; michael@0: } michael@0: michael@0: // static michael@0: ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { michael@0: return new ProcessMetrics(process); michael@0: } michael@0: michael@0: ProcessMetrics::~ProcessMetrics() { } michael@0: michael@0: static uint64_t FileTimeToUTC(const FILETIME& ftime) { michael@0: LARGE_INTEGER li; michael@0: li.LowPart = ftime.dwLowDateTime; michael@0: li.HighPart = ftime.dwHighDateTime; michael@0: return li.QuadPart; michael@0: } michael@0: michael@0: int ProcessMetrics::GetCPUUsage() { michael@0: FILETIME now; michael@0: FILETIME creation_time; michael@0: FILETIME exit_time; michael@0: FILETIME kernel_time; michael@0: FILETIME user_time; michael@0: michael@0: GetSystemTimeAsFileTime(&now); michael@0: michael@0: if (!GetProcessTimes(process_, &creation_time, &exit_time, michael@0: &kernel_time, &user_time)) { michael@0: // We don't assert here because in some cases (such as in the Task Manager) michael@0: // we may call this function on a process that has just exited but we have michael@0: // not yet received the notification. michael@0: return 0; michael@0: } michael@0: int64_t system_time = (FileTimeToUTC(kernel_time) + FileTimeToUTC(user_time)) / michael@0: processor_count_; michael@0: int64_t time = FileTimeToUTC(now); michael@0: michael@0: if ((last_system_time_ == 0) || (last_time_ == 0)) { michael@0: // First call, just set the last values. michael@0: last_system_time_ = system_time; michael@0: last_time_ = time; michael@0: return 0; michael@0: } michael@0: michael@0: int64_t system_time_delta = system_time - last_system_time_; michael@0: int64_t time_delta = time - last_time_; michael@0: DCHECK(time_delta != 0); michael@0: if (time_delta == 0) michael@0: return 0; michael@0: michael@0: // We add time_delta / 2 so the result is rounded. michael@0: int cpu = static_cast((system_time_delta * 100 + time_delta / 2) / michael@0: time_delta); michael@0: michael@0: last_system_time_ = system_time; michael@0: last_time_ = time; michael@0: michael@0: return cpu; michael@0: } michael@0: michael@0: } // namespace base