From f38a5605365eda67a15a8558caf2bb455fdc415c Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Mon, 25 Feb 2019 11:45:15 -0800 Subject: [PATCH 1/2] #1379 / win / suspend/resume: use native Windows APIs instead of dealing with process threads --- HISTORY.rst | 3 + psutil/_psutil_windows.c | 105 ++++++----------------------- psutil/_pswindows.py | 4 +- psutil/arch/windows/global.c | 10 +++ psutil/arch/windows/global.h | 6 ++ psutil/arch/windows/ntextapi.h | 8 +++ psutil/arch/windows/process_info.c | 7 +- 7 files changed, 54 insertions(+), 89 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 013645bdc..3addc0270 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -7,6 +7,9 @@ XXXX-XX-XX **Enhancements** +- 1379_: [Windows] Process suspend() and resume() now use NtSuspendProcess + and NtResumeProcess instead of stopping/resuming all threads of a process. + This is faster and more reliable (aka this is what ProcessHacker does). - 1420_: [Windows] in case of exception disk_usage() now also shows the path name. - 1422_: [Windows] Windows APIs requiring to be dynamically loaded from DLL diff --git a/psutil/_psutil_windows.c b/psutil/_psutil_windows.c index 54b401d31..cc9263b62 100644 --- a/psutil/_psutil_windows.c +++ b/psutil/_psutil_windows.c @@ -1019,93 +1019,31 @@ psutil_proc_cwd(PyObject *self, PyObject *args) { /* * Resume or suspends a process */ -int -psutil_proc_suspend_or_resume(DWORD pid, int suspend) { - // a huge thanks to http://www.codeproject.com/KB/threads/pausep.aspx - HANDLE hThreadSnap = NULL; - HANDLE hThread; - THREADENTRY32 te32 = {0}; - - if (pid == 0) { - AccessDenied(""); - return FALSE; - } - - hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); - if (hThreadSnap == INVALID_HANDLE_VALUE) { - PyErr_SetFromOSErrnoWithSyscall("CreateToolhelp32Snapshot"); - return FALSE; - } - - // Fill in the size of the structure before using it - te32.dwSize = sizeof(THREADENTRY32); - - if (! Thread32First(hThreadSnap, &te32)) { - PyErr_SetFromOSErrnoWithSyscall("Thread32First"); - CloseHandle(hThreadSnap); - return FALSE; - } - - // Walk the thread snapshot to find all threads of the process. - // If the thread belongs to the process, add its information - // to the display list. - do { - if (te32.th32OwnerProcessID == pid) { - hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, - te32.th32ThreadID); - if (hThread == NULL) { - PyErr_SetFromOSErrnoWithSyscall("OpenThread"); - CloseHandle(hThread); - CloseHandle(hThreadSnap); - return FALSE; - } - if (suspend == 1) { - if (SuspendThread(hThread) == (DWORD) - 1) { - PyErr_SetFromOSErrnoWithSyscall("SuspendThread"); - CloseHandle(hThread); - CloseHandle(hThreadSnap); - return FALSE; - } - } - else { - if (ResumeThread(hThread) == (DWORD) - 1) { - PyErr_SetFromOSErrnoWithSyscall("ResumeThread"); - CloseHandle(hThread); - CloseHandle(hThreadSnap); - return FALSE; - } - } - CloseHandle(hThread); - } - } while (Thread32Next(hThreadSnap, &te32)); - - CloseHandle(hThreadSnap); - return TRUE; -} - - static PyObject * -psutil_proc_suspend(PyObject *self, PyObject *args) { +psutil_proc_suspend_or_resume(PyObject *self, PyObject *args) { long pid; - int suspend = 1; + int ret; + HANDLE hProcess; + PyObject* suspend; - if (! PyArg_ParseTuple(args, "l", &pid)) - return NULL; - if (! psutil_proc_suspend_or_resume(pid, suspend)) + if (! PyArg_ParseTuple(args, "lO", &pid, &suspend)) return NULL; - Py_RETURN_NONE; -} + hProcess = psutil_handle_from_pid(pid, PROCESS_SUSPEND_RESUME); + if (hProcess == NULL) + return NULL; -static PyObject * -psutil_proc_resume(PyObject *self, PyObject *args) { - long pid; - int suspend = 0; + if (PyObject_IsTrue(suspend)) + ret = psutil_NtSuspendProcess(hProcess); + else + ret = psutil_NtResumeProcess(hProcess); - if (! PyArg_ParseTuple(args, "l", &pid)) - return NULL; - if (! psutil_proc_suspend_or_resume(pid, suspend)) + if (ret != 0) { + PyErr_SetFromWindowsErr(0); + CloseHandle(hProcess); return NULL; + } + CloseHandle(hProcess); Py_RETURN_NONE; } @@ -3430,7 +3368,8 @@ psutil_sensors_battery(PyObject *self, PyObject *args) { static PyMethodDef PsutilMethods[] = { // --- per-process functions - {"proc_cmdline", (PyCFunction)(void(*)(void))psutil_proc_cmdline, METH_VARARGS | METH_KEYWORDS, + {"proc_cmdline", (PyCFunction)(void(*)(void))psutil_proc_cmdline, + METH_VARARGS | METH_KEYWORDS, "Return process cmdline as a list of cmdline arguments"}, {"proc_environ", psutil_proc_environ, METH_VARARGS, "Return process environment data"}, @@ -3451,10 +3390,8 @@ PsutilMethods[] = { "Return the USS of the process"}, {"proc_cwd", psutil_proc_cwd, METH_VARARGS, "Return process current working directory"}, - {"proc_suspend", psutil_proc_suspend, METH_VARARGS, - "Suspend a process"}, - {"proc_resume", psutil_proc_resume, METH_VARARGS, - "Resume a process"}, + {"proc_suspend_or_resume", psutil_proc_suspend_or_resume, METH_VARARGS, + "Suspend or resume a process"}, {"proc_open_files", psutil_proc_open_files, METH_VARARGS, "Return files opened by process"}, {"proc_username", psutil_proc_username, METH_VARARGS, diff --git a/psutil/_pswindows.py b/psutil/_pswindows.py index 0441b8a17..0ab8afe4b 100644 --- a/psutil/_pswindows.py +++ b/psutil/_pswindows.py @@ -913,11 +913,11 @@ def cpu_times(self): @wrap_exceptions def suspend(self): - return cext.proc_suspend(self.pid) + cext.proc_suspend_or_resume(self.pid, True) @wrap_exceptions def resume(self): - return cext.proc_resume(self.pid) + cext.proc_suspend_or_resume(self.pid, False) @wrap_exceptions def cwd(self): diff --git a/psutil/arch/windows/global.c b/psutil/arch/windows/global.c index df1c75289..6134687de 100644 --- a/psutil/arch/windows/global.c +++ b/psutil/arch/windows/global.c @@ -117,6 +117,16 @@ psutil_loadlibs() { if (! psutil_RtlGetVersion) return 1; + psutil_NtSuspendProcess = psutil_GetProcAddressFromLib( + "ntdll", "NtSuspendProcess"); + if (! psutil_NtSuspendProcess) + return 1; + + psutil_NtResumeProcess = psutil_GetProcAddressFromLib( + "ntdll", "NtResumeProcess"); + if (! psutil_NtResumeProcess) + return 1; + /* * Optional. */ diff --git a/psutil/arch/windows/global.h b/psutil/arch/windows/global.h index 1f4951faa..1b1d00f81 100644 --- a/psutil/arch/windows/global.h +++ b/psutil/arch/windows/global.h @@ -62,3 +62,9 @@ _GetLogicalProcessorInformationEx \ _RtlGetVersion \ psutil_RtlGetVersion; + +_NtSuspendProcess \ + psutil_NtSuspendProcess; + +_NtResumeProcess \ + psutil_NtResumeProcess; diff --git a/psutil/arch/windows/ntextapi.h b/psutil/arch/windows/ntextapi.h index d6030caa2..8007ec01c 100644 --- a/psutil/arch/windows/ntextapi.h +++ b/psutil/arch/windows/ntextapi.h @@ -455,4 +455,12 @@ typedef NTSTATUS (WINAPI *_RtlGetVersion) ( PRTL_OSVERSIONINFOW lpVersionInformation ); +typedef NTSTATUS (WINAPI *_NtResumeProcess) ( + HANDLE hProcess +); + +typedef NTSTATUS (WINAPI *_NtSuspendProcess) ( + HANDLE hProcess +); + #endif // __NTEXTAPI_H__ diff --git a/psutil/arch/windows/process_info.c b/psutil/arch/windows/process_info.c index 5e0b94217..789e2660e 100644 --- a/psutil/arch/windows/process_info.c +++ b/psutil/arch/windows/process_info.c @@ -244,15 +244,16 @@ psutil_check_phandle(HANDLE hProcess, DWORD pid) { * Return a process handle or NULL. */ HANDLE -psutil_handle_from_pid(DWORD pid, DWORD dwDesiredAccess) { +psutil_handle_from_pid(DWORD pid, DWORD access) { HANDLE hProcess; if (pid == 0) { // otherwise we'd get NoSuchProcess return AccessDenied(""); } - - hProcess = OpenProcess(dwDesiredAccess, FALSE, pid); + // needed for GetExitCodeProcess + access |= PROCESS_QUERY_LIMITED_INFORMATION; + hProcess = OpenProcess(access, FALSE, pid); return psutil_check_phandle(hProcess, pid); } From 2d30ca12aea7aa1926270735349433d448c8f80b Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Mon, 25 Feb 2019 12:15:30 -0800 Subject: [PATCH 2/2] add block parenthesys (damn!) --- psutil/_psutil_windows.c | 6 ++---- psutil/arch/windows/process_info.c | 12 ++++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/psutil/_psutil_windows.c b/psutil/_psutil_windows.c index cc9263b62..e9db62a5a 100644 --- a/psutil/_psutil_windows.c +++ b/psutil/_psutil_windows.c @@ -1998,8 +1998,7 @@ psutil_proc_cpu_affinity_set(PyObject *self, PyObject *args) { /* - * Return True if one of the process threads is in a waiting or - * suspended status. + * Return True if all process threads are in waiting/suspended state. */ static PyObject * psutil_proc_is_suspended(PyObject *self, PyObject *args) { @@ -2010,9 +2009,8 @@ psutil_proc_is_suspended(PyObject *self, PyObject *args) { if (! PyArg_ParseTuple(args, "l", &pid)) return NULL; - if (! psutil_get_proc_info(pid, &process, &buffer)) { + if (! psutil_get_proc_info(pid, &process, &buffer)) return NULL; - } for (i = 0; i < process->NumberOfThreads; i++) { if (process->Threads[i].ThreadState != Waiting || process->Threads[i].WaitReason != Suspended) diff --git a/psutil/arch/windows/process_info.c b/psutil/arch/windows/process_info.c index 789e2660e..1cfd3b5cd 100644 --- a/psutil/arch/windows/process_info.c +++ b/psutil/arch/windows/process_info.c @@ -222,17 +222,21 @@ psutil_is_phandle_running(HANDLE hProcess, DWORD pid) { HANDLE psutil_check_phandle(HANDLE hProcess, DWORD pid) { int ret = psutil_is_phandle_running(hProcess, pid); - if (ret == 1) + if (ret == 1) { return hProcess; - else if (ret == 0) + } + else if (ret == 0) { return NoSuchProcess(""); - else if (ret == -1) + } + else if (ret == -1) { if (GetLastError() == ERROR_ACCESS_DENIED) return PyErr_SetFromWindowsErr(0); else return PyErr_SetFromOSErrnoWithSyscall("OpenProcess"); - else // -2 + } + else { return NULL; + } }