发送 Ctrl+C 事件终止 Windows 控制台进程
偶然在 Nuitka 的代码中发现一个未被触发的 BUG 。问题代码位于 OnefileBootstrap.c 的 cleanupChildProcess()
函数中:
c++static void cleanupChildProcess(bool send_sigint) {
// Cause KeyboardInterrupt in the child process.
if (handle_process != 0) {
if (send_sigint) {
#if defined(_NUITKA_EXPERIMENTAL_DEBUG_ONEFILE_HANDLING)
puts("Sending CTRL-C to child\n");
#endif
#if defined(_WIN32)
BOOL res = GenerateConsoleCtrlEvent(CTRL_C_EVENT, GetProcessId(handle_process));
if (res == false) {
printError("Failed to send CTRL-C to child process.", GetLastError());
// No error exit is done, we still want to cleanup when it does exit
}
#else
kill(handle_process, SIGINT);
#endif
}
// TODO: We ought to only need to wait if there is a need to cleanup
// files when we are on Windows, on Linux maybe exec can be used so
// this process to exist anymore if there is nothing to do.
#if _NUITKA_ONEFILE_TEMP_BOOL == 1 || 1
NUITKA_PRINT_TRACE("Waiting for child to exit.\n");
#if defined(_WIN32)
if (WaitForSingleObject(handle_process, _NUITKA_ONEFILE_CHILD_GRACE_TIME_INT) != 0) {
TerminateProcess(handle_process, 0);
}
CloseHandle(handle_process);
#else
waitpid_timeout(handle_process);
kill(handle_process, SIGKILL);
#endif
NUITKA_PRINT_TRACE("Child is exited.\n");
#endif
}
#if _NUITKA_ONEFILE_TEMP_BOOL == 1
if (payload_created) {
#if _NUITKA_EXPERIMENTAL_DEBUG_ONEFILE_HANDLING
wprintf(L"Removing payload path '%lS'\n", payload_path);
#endif
removeDirectory(payload_path);
}
#endif
}
这段代码中第 654 行的 GenerateConsoleCtrlEvent()
总是返回 FALSE
,导致子进程无法收到 CTRL-C 信号。不过由于 Nuitka 的代码中只有 cleanupChildProcess(false)
调用,因此这个 BUG 正常情况下不会被触发。
不幸的是,本人 fork 了 Nuitka 的项目 Nuitka-winsvc 正好触发了这个 BUG 。Nuitka-winsvc 为 Nuitka 增加了编译为 Windows 服务的选项。当停止服务时,需要向子进程发送 CTRL-C 信号来优雅结束子进程。
根据微软的官方文档, GenerateConsoleCtrlEvent()
的第二个参数应该是进程组 ID ,而不是子进程 ID 。要获得进程组 ID 必须在使用 CreateProcess()
创建子进程时设置 CREATE_NEW_PROCESS_GROUP 标志。另一种方法是关联子进程的控制台,并将 GenerateConsoleCtrlEvent()
的第二个参数设置为 0 。具体实现代码如下:
c++AttachConsole(GetProcessId(handle_process));
SetConsoleCtrlHandler(NULL, TRUE); // 阻止当前进程被终止
BOOL res = GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
FreeConsole();