Mercurial > hg > xemacs-beta
diff src/process-nt.c @ 280:7df0dd720c89 r21-0b38
Import from CVS: tag r21-0b38
author | cvs |
---|---|
date | Mon, 13 Aug 2007 10:32:22 +0200 |
parents | 90d73dddcdc4 |
children | c42ec1d1cded |
line wrap: on
line diff
--- a/src/process-nt.c Mon Aug 13 10:31:30 2007 +0200 +++ b/src/process-nt.c Mon Aug 13 10:32:22 2007 +0200 @@ -34,6 +34,7 @@ #include <windows.h> #include <shellapi.h> +#include <signal.h> /* Implemenation-specific data. Pointed to by Lisp_Process->process_data */ struct nt_process_data @@ -54,7 +55,315 @@ { return (NT_DATA (p)->h_process); } + +/*-----------------------------------------------------------------------*/ +/* Running remote threads. See Microsoft Systems Journal 1994 Number 5 */ +/* Jeffrey Richter, Load Your 32-bit DLL into Another Process's Address..*/ +/*-----------------------------------------------------------------------*/ +typedef struct +{ + HANDLE h_process; + HANDLE h_thread; + LPVOID address; +} process_memory; + +/* + * Allocate SIZE bytes in H_PROCESS address space. Fill in PMC used + * further by other routines. Return nonzero if successful. + * + * The memory in other process is allocated by creating a suspended + * thread. Initial stack of that thread is used as the memory + * block. The thread entry point is the routine ExitThread in + * kernel32.dll, so the allocated memory is freed just by resuming the + * thread, which immediately terminates after that. + */ + +static int +alloc_process_memory (HANDLE h_process, size_t size, + process_memory* pmc) +{ + LPTHREAD_START_ROUTINE adr_ExitThread = + (LPTHREAD_START_ROUTINE) + GetProcAddress (GetModuleHandle ("kernel32"), "ExitThread"); + DWORD dw_unused; + CONTEXT context; + MEMORY_BASIC_INFORMATION mbi; + + pmc->h_process = h_process; + pmc->h_thread = CreateRemoteThread (h_process, NULL, size, + adr_ExitThread, NULL, + CREATE_SUSPENDED, &dw_unused); + if (pmc->h_thread == NULL) + return 0; + + /* Get context, for thread's stack pointer */ + context.ContextFlags = CONTEXT_CONTROL; + if (!GetThreadContext (pmc->h_thread, &context)) + goto failure; + + /* Determine base address of the committed range */ + if (sizeof(mbi) != VirtualQueryEx (h_process, +#if defined (_X86_) + (LPDWORD)context.Esp - 1, +#elif defined (_ALPHA_) + (LPDWORD)context.IntSp - 1, +#else +#error Unknown processor architecture +#endif + &mbi, sizeof(mbi))) + goto failure; + + /* Change the page protection of the allocated memory to executable, + read, and write. */ + if (!VirtualProtectEx (h_process, mbi.BaseAddress, size, + PAGE_EXECUTE_READWRITE, &dw_unused)) + goto failure; + + pmc->address = mbi.BaseAddress; + return 1; + + failure: + ResumeThread (pmc->h_thread); + pmc->address = 0; + return 0; +} + +static void +free_process_memory (process_memory* pmc) +{ + ResumeThread (pmc->h_thread); +} + +/* + * Run ROUTINE in the context of process determined by H_PROCESS. The + * routine is passed the address of DATA as parameter. CODE_END is the + * address immediately after ROUTINE's code. DATA_SIZE is the size of + * DATA structure. + * + * Note that the code must be positionally independent, and compiled + * without stack checks (they cause implicit calls into CRT so will + * fail). DATA should not refer any data in calling process, as both + * routine and its data are copied into remote process. Size of data + * and code together should not exceed one page (4K on x86 systems). + * + * Return the value returned by ROUTINE, or (DWORD)-1 if call failed. + */ +static DWORD +run_in_other_process (HANDLE h_process, + LPTHREAD_START_ROUTINE routine, LPVOID code_end, + LPVOID data, size_t data_size) +{ + process_memory pm; + size_t code_size = (LPBYTE)code_end - (LPBYTE)routine; + /* Need at most 3 extra bytes of memory, for data alignment */ + size_t total_size = code_size + data_size + 3; + LPVOID remote_data; + HANDLE h_thread; + DWORD dw_unused; + + /* Allocate memory */ + if (!alloc_process_memory (h_process, total_size, &pm)) + return (DWORD)-1; + + /* Copy code */ + if (!WriteProcessMemory (h_process, pm.address, (LPVOID)routine, + code_size, NULL)) + goto failure; + + /* Copy data */ + if (data_size) + { + remote_data = (LPBYTE)pm.address + ((code_size + 4) & ~3); + if (!WriteProcessMemory (h_process, remote_data, data, data_size, NULL)) + goto failure; + } + else + remote_data = NULL; + + /* Execute the remote copy of code, passing it remote data */ + h_thread = CreateRemoteThread (h_process, NULL, 0, + (LPTHREAD_START_ROUTINE) pm.address, + remote_data, 0, &dw_unused); + if (h_thread == NULL) + goto failure; + + /* Wait till thread finishes */ + WaitForSingleObject (h_thread, INFINITE); + + /* Free remote memory */ + free_process_memory (&pm); + + /* Return thread's exit code */ + { + DWORD exit_code; + GetExitCodeThread (h_thread, &exit_code); + CloseHandle (h_thread); + return exit_code; + } + + failure: + free_process_memory (&pm); + return (DWORD)-1; +} + +/*-----------------------------------------------------------------------*/ +/* Sending signals */ +/*-----------------------------------------------------------------------*/ + +/* + * We handle the following signals: + * + * SIGKILL, SIGTERM, SIGQUIT - These three translate to ExitProcess + * executed by the remote process + * SIGINT - The remote process is sent CTRL_BREAK_EVENT + */ + +/* + * Sending SIGKILL + */ +typedef struct +{ + void (WINAPI *adr_ExitProcess) (UINT); +} sigkill_data; + +static DWORD WINAPI +sigkill_proc (sigkill_data* data) +{ + (*data->adr_ExitProcess)(255); + return 1; +} + +/* Watermark in code space */ +static void +sigkill_code_end (void) +{ +} + +/* + * Sending break or control c + */ +typedef struct +{ + BOOL (WINAPI *adr_GenerateConsoleCtrlEvent) (DWORD, DWORD); + DWORD event; +} sigint_data; + +static DWORD WINAPI +sigint_proc (sigint_data* data) +{ + return (*data->adr_GenerateConsoleCtrlEvent) (data->event, 0); +} + +/* Watermark in code space */ +static void +sigint_code_end (void) +{ +} + +/* + * Enabling signals + */ +typedef struct +{ + BOOL (WINAPI *adr_SetConsoleCtrlHandler) (LPVOID, BOOL); +} sig_enable_data; + +static DWORD WINAPI +sig_enable_proc (sig_enable_data* data) +{ + (*data->adr_SetConsoleCtrlHandler) (NULL, FALSE); + return 1; +} + +/* Watermark in code space */ +static void +sig_enable_code_end (void) +{ +} + +/* + * Send signal SIGNO to process H_PROCESS. + * Return nonzero if successful. + */ + +/* This code assigns a return value of GetProcAddress to function pointers + of many different types. Instead of heavy obscure casts, we just disable + warnings about assignments to different function pointer types. */ +#pragma warning (disable : 4113) + +static int +send_signal (HANDLE h_process, int signo) +{ + HMODULE h_kernel = GetModuleHandle ("kernel32"); + DWORD retval; + + assert (h_kernel != NULL); + + switch (signo) + { + case SIGKILL: + case SIGTERM: + case SIGQUIT: + { + sigkill_data d; + d.adr_ExitProcess = GetProcAddress (h_kernel, "ExitProcess"); + assert (d.adr_ExitProcess); + retval = run_in_other_process (h_process, + sigkill_proc, sigkill_code_end, + &d, sizeof (d)); + break; + } + case SIGINT: + { + sigint_data d; + d.adr_GenerateConsoleCtrlEvent = + GetProcAddress (h_kernel, "GenerateConsoleCtrlEvent"); + assert (d.adr_GenerateConsoleCtrlEvent); + d.event = CTRL_C_EVENT; + retval = run_in_other_process (h_process, + sigint_proc, sigint_code_end, + &d, sizeof (d)); + break; + } + default: + assert (0); + } + + return (int)retval > 0 ? 1 : 0; +} + +/* + * Enable CTRL_C_EVENT handling in a new child process + */ +static void +enable_child_signals (HANDLE h_process) +{ + HMODULE h_kernel = GetModuleHandle ("kernel32"); + sig_enable_data d; + + assert (h_kernel != NULL); + d.adr_SetConsoleCtrlHandler = + GetProcAddress (h_kernel, "SetConsoleCtrlHandler"); + assert (d.adr_SetConsoleCtrlHandler); + run_in_other_process (h_process, + sig_enable_proc, sig_enable_code_end, + &d, sizeof (d)); +} + +#pragma warning (default : 4113) + +/* + * Signal error if SIGNO is not supported + */ +static void +validate_signal_number (int signo) +{ + if (signo != SIGKILL && signo != SIGTERM + && signo != SIGQUIT && signo != SIGINT) + error ("Signal number %d not supported", signo); +} + /*-----------------------------------------------------------------------*/ /* Process methods */ /*-----------------------------------------------------------------------*/ @@ -226,7 +535,8 @@ } err = (CreateProcess (NULL, command_line, NULL, NULL, TRUE, - CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP, + CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP + | CREATE_SUSPENDED, NULL, current_dir, &si, &pi) ? 0 : GetLastError ()); @@ -261,6 +571,10 @@ CloseHandle (pi.hProcess); } + if (!windowed) + enable_child_signals (pi.hProcess); + + ResumeThread (pi.hThread); CloseHandle (pi.hThread); /* Hack to support Windows 95 negative pids */ @@ -357,6 +671,67 @@ Lstream_flush (XLSTREAM (DATA_OUTSTREAM(p))); } +/* + * Send a signal number SIGNO to PROCESS. + * CURRENT_GROUP means send to the process group that currently owns + * the terminal being used to communicate with PROCESS. + * This is used for various commands in shell mode. + * If NOMSG is zero, insert signal-announcements into process's buffers + * right away. + * + * If we can, we try to signal PROCESS by sending control characters + * down the pty. This allows us to signal inferiors who have changed + * their uid, for which killpg would return an EPERM error. + * + * The method signals an error if the given SIGNO is not valid + */ + +static void +nt_kill_child_process (Lisp_Object proc, int signo, + int current_group, int nomsg) +{ + struct Lisp_Process *p = XPROCESS (proc); + + /* Signal error if SIGNO cannot be sent */ + validate_signal_number (signo); + + /* Send signal */ + if (!send_signal (NT_DATA(p)->h_process, signo)) + error ("Cannot send signal to process"); +} + +/* + * Kill any process in the system given its PID. + * + * Returns zero if a signal successfully sent, or + * negative number upon failure + */ +static int +nt_kill_process_by_pid (int pid, int signo) +{ + HANDLE h_process; + int send_result; + + /* Signal error if SIGNO cannot be sent */ + validate_signal_number (signo); + + /* Try to open the process with required privileges */ + h_process = OpenProcess (PROCESS_CREATE_THREAD + | PROCESS_QUERY_INFORMATION + | PROCESS_VM_OPERATION + | PROCESS_VM_WRITE, + FALSE, pid); + if (h_process == NULL) + return -1; + + send_result = send_signal (h_process, signo); + + CloseHandle (h_process); + + return send_result ? 0 : -1; +} + + /*-----------------------------------------------------------------------*/ /* Initialization */ /*-----------------------------------------------------------------------*/ @@ -372,8 +747,8 @@ PROCESS_HAS_METHOD (nt, create_process); PROCESS_HAS_METHOD (nt, update_status_if_terminated); PROCESS_HAS_METHOD (nt, send_process); - /* PROCESS_HAS_METHOD (nt, kill_child_process); */ - /* PROCESS_HAS_METHOD (nt, kill_process_by_pid); */ + PROCESS_HAS_METHOD (nt, kill_child_process); + PROCESS_HAS_METHOD (nt, kill_process_by_pid); #if 0 /* Yet todo */ #ifdef HAVE_SOCKETS PROCESS_HAS_METHOD (nt, canonicalize_host_name);