Skip to content

Commit

Permalink
#1379 - Windows: suspend / resume process by using native APIs (#1435)
Browse files Browse the repository at this point in the history
  • Loading branch information
giampaolo authored Feb 25, 2019
1 parent 842a505 commit 32c1a0d
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 97 deletions.
3 changes: 3 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
111 changes: 23 additions & 88 deletions psutil/_psutil_windows.c
Original file line number Diff line number Diff line change
Expand Up @@ -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))
if (! PyArg_ParseTuple(args, "lO", &pid, &suspend))
return NULL;
if (! psutil_proc_suspend_or_resume(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;
}

Expand Down Expand Up @@ -2060,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) {
Expand All @@ -2072,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)
Expand Down Expand Up @@ -3430,7 +3366,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"},
Expand All @@ -3451,10 +3388,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,
Expand Down
4 changes: 2 additions & 2 deletions psutil/_pswindows.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
10 changes: 10 additions & 0 deletions psutil/arch/windows/global.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
6 changes: 6 additions & 0 deletions psutil/arch/windows/global.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,9 @@ _GetLogicalProcessorInformationEx \

_RtlGetVersion \
psutil_RtlGetVersion;

_NtSuspendProcess \
psutil_NtSuspendProcess;

_NtResumeProcess \
psutil_NtResumeProcess;
8 changes: 8 additions & 0 deletions psutil/arch/windows/ntextapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -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__
19 changes: 12 additions & 7 deletions psutil/arch/windows/process_info.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}


Expand All @@ -244,15 +248,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);
}

Expand Down

0 comments on commit 32c1a0d

Please sign in to comment.