Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#1379 - Windows: suspend / resume process by using native APIs #1435

Merged
merged 2 commits into from
Feb 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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