167 lines
4.7 KiB
C
167 lines
4.7 KiB
C
#ifndef EXIT_PROCESS_H
|
|
#define EXIT_PROCESS_H
|
|
|
|
/*
|
|
* This file contains functions to terminate a Win32 process, as gently as
|
|
* possible.
|
|
*
|
|
* At first, we will attempt to inject a thread that calls ExitProcess(). If
|
|
* that fails, we will fall back to terminating the entire process tree.
|
|
*
|
|
* For simplicity, these functions are marked as file-local.
|
|
*/
|
|
|
|
#include <tlhelp32.h>
|
|
|
|
/*
|
|
* Terminates the process corresponding to the process ID and all of its
|
|
* directly and indirectly spawned subprocesses.
|
|
*
|
|
* This way of terminating the processes is not gentle: the processes get
|
|
* no chance of cleaning up after themselves (closing file handles, removing
|
|
* .lock files, terminating spawned processes (if any), etc).
|
|
*/
|
|
static int terminate_process_tree(HANDLE main_process, int exit_status)
|
|
{
|
|
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
|
PROCESSENTRY32 entry;
|
|
DWORD pids[16384];
|
|
int max_len = sizeof(pids) / sizeof(*pids), i, len, ret = 0;
|
|
pid_t pid = GetProcessId(main_process);
|
|
|
|
pids[0] = (DWORD)pid;
|
|
len = 1;
|
|
|
|
/*
|
|
* Even if Process32First()/Process32Next() seem to traverse the
|
|
* processes in topological order (i.e. parent processes before
|
|
* child processes), there is nothing in the Win32 API documentation
|
|
* suggesting that this is guaranteed.
|
|
*
|
|
* Therefore, run through them at least twice and stop when no more
|
|
* process IDs were added to the list.
|
|
*/
|
|
for (;;) {
|
|
int orig_len = len;
|
|
|
|
memset(&entry, 0, sizeof(entry));
|
|
entry.dwSize = sizeof(entry);
|
|
|
|
if (!Process32First(snapshot, &entry))
|
|
break;
|
|
|
|
do {
|
|
for (i = len - 1; i >= 0; i--) {
|
|
if (pids[i] == entry.th32ProcessID)
|
|
break;
|
|
if (pids[i] == entry.th32ParentProcessID)
|
|
pids[len++] = entry.th32ProcessID;
|
|
}
|
|
} while (len < max_len && Process32Next(snapshot, &entry));
|
|
|
|
if (orig_len == len || len >= max_len)
|
|
break;
|
|
}
|
|
|
|
for (i = len - 1; i > 0; i--) {
|
|
HANDLE process = OpenProcess(PROCESS_TERMINATE, FALSE, pids[i]);
|
|
|
|
if (process) {
|
|
if (!TerminateProcess(process, exit_status))
|
|
ret = -1;
|
|
CloseHandle(process);
|
|
}
|
|
}
|
|
if (!TerminateProcess(main_process, exit_status))
|
|
ret = -1;
|
|
CloseHandle(main_process);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Determine whether a process runs in the same architecture as the current
|
|
* one. That test is required before we assume that GetProcAddress() returns
|
|
* a valid address *for the target process*.
|
|
*/
|
|
static inline int process_architecture_matches_current(HANDLE process)
|
|
{
|
|
static BOOL current_is_wow = -1;
|
|
BOOL is_wow;
|
|
|
|
if (current_is_wow == -1 &&
|
|
!IsWow64Process (GetCurrentProcess(), ¤t_is_wow))
|
|
current_is_wow = -2;
|
|
if (current_is_wow == -2)
|
|
return 0; /* could not determine current process' WoW-ness */
|
|
if (!IsWow64Process (process, &is_wow))
|
|
return 0; /* cannot determine */
|
|
return is_wow == current_is_wow;
|
|
}
|
|
|
|
/**
|
|
* Inject a thread into the given process that runs ExitProcess().
|
|
*
|
|
* Note: as kernel32.dll is loaded before any process, the other process and
|
|
* this process will have ExitProcess() at the same address.
|
|
*
|
|
* This function expects the process handle to have the access rights for
|
|
* CreateRemoteThread(): PROCESS_CREATE_THREAD, PROCESS_QUERY_INFORMATION,
|
|
* PROCESS_VM_OPERATION, PROCESS_VM_WRITE, and PROCESS_VM_READ.
|
|
*
|
|
* The idea comes from the Dr Dobb's article "A Safer Alternative to
|
|
* TerminateProcess()" by Andrew Tucker (July 1, 1999),
|
|
* http://www.drdobbs.com/a-safer-alternative-to-terminateprocess/184416547
|
|
*
|
|
* If this method fails, we fall back to running terminate_process_tree().
|
|
*/
|
|
static int exit_process(HANDLE process, int exit_code)
|
|
{
|
|
DWORD code;
|
|
|
|
if (GetExitCodeProcess(process, &code) && code == STILL_ACTIVE) {
|
|
static int initialized;
|
|
static LPTHREAD_START_ROUTINE exit_process_address;
|
|
PVOID arg = (PVOID)(intptr_t)exit_code;
|
|
DWORD thread_id;
|
|
HANDLE thread = NULL;
|
|
|
|
if (!initialized) {
|
|
HINSTANCE kernel32 = GetModuleHandleA("kernel32");
|
|
if (!kernel32)
|
|
die("BUG: cannot find kernel32");
|
|
exit_process_address =
|
|
(LPTHREAD_START_ROUTINE)(void (*)(void))
|
|
GetProcAddress(kernel32, "ExitProcess");
|
|
initialized = 1;
|
|
}
|
|
if (!exit_process_address ||
|
|
!process_architecture_matches_current(process))
|
|
return terminate_process_tree(process, exit_code);
|
|
|
|
thread = CreateRemoteThread(process, NULL, 0,
|
|
exit_process_address,
|
|
arg, 0, &thread_id);
|
|
if (thread) {
|
|
CloseHandle(thread);
|
|
/*
|
|
* If the process survives for 10 seconds (a completely
|
|
* arbitrary value picked from thin air), fall back to
|
|
* killing the process tree via TerminateProcess().
|
|
*/
|
|
if (WaitForSingleObject(process, 10000) ==
|
|
WAIT_OBJECT_0) {
|
|
CloseHandle(process);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return terminate_process_tree(process, exit_code);
|
|
}
|
|
|
|
CloseHandle(process);
|
|
return 0;
|
|
}
|
|
|
|
#endif
|