在windows console app模擬sigterm

posix相容的系統上,想要把一個程式用正常的流程關掉可以透過 `kill -SIGTERM $pid` 來傳送signal,然後在目標程式上trap signal來進行收尾

但在windows上不管是用 `taskkill /F` 還是 `wmic call terminate` 或 `wmic delete`,程式都會被強制關閉(相當於sigkill),沒辦法執行收尾的流程

`taskkill` 不加F的話是正常關閉,但如果對象是console app時,他會說這個程式只能使用F的方式關閉

查了半天,最接近sigterm的操作是interrupt(ctrl+c)或是break(ctrl+break)

但這個event必須透過win32 api來傳送,因此只能用powershell或是做exe來達成

最後我選了用c的方式實作

#include <windows.h>
#include <errhandlingapi.h>
#include <stdio.h>

void printSystemError();

int main(int argc, char** argv) {
	if (argc <= 1) {
		wprintf(L"PID argument needed\n");
		return 1;
	}

	int targetPid = atoi(argv[1]);

	// Detach from current console
	if (!FreeConsole()) {
		wprintf(L"FreeConsole failed\n");
		printSystemError();
		return 1;
	}

	// Attach to console where target process resides
	if (!AttachConsole(targetPid)) {
		wprintf(L"AttachConsole failed\n");
		printSystemError();
		return 1;
	}

	// Ignore interrupt signal we later send
	if (!SetConsoleCtrlHandler(NULL, TRUE)) {
		wprintf(L"SetConsoleCtrlHandler failed\n");
		printSystemError();
		return 1;
	}

	// Send interrupt signal to every process in current console
	if (!GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)) {
		wprintf(L"GenerateConsoleCtrlEvent failed\n");
		printSystemError();
		return 1;
	}

	return 0;
}

void printSystemError() {
	DWORD errCode = GetLastError();
	LPVOID lpMsgBuf;

	FormatMessage(
		FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL,
		errCode,
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPTSTR)&lpMsgBuf,
		0,
		NULL
	);

	wprintf(L"Error Code: %d\n", errCode);
	wprintf(L"Message: %s\n", (LPTSTR)&lpMsgBuf);
	wprintf(L"More info: https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes\n");

	LocalFree(lpMsgBuf);
}

原本想要直接傳送event給指定process,但GenerateConsoleCtrlEvent只能送給process group id,而process group id無從查起,直接用pid也沒辦法,只好設定成0讓他傳給當下process group中所有程式

但這樣想要關閉的程式還是會收不到,所以還需要FreeConsole和AttachConsole讓執行當下可以跟目標process掛在同一個process group底下,這樣event就可以收到了

這時會發現送event的程式執行到一半反而因為自己收到ctrl+c而中斷,為了讓他能正常結束要先對ctrl+c免疫,因此要加上一個假的event handler

到這邊這隻程式就能正確發送ctrl+c給指定的pid,唯一需要注意的是整個process group都會收到,地圖砲範圍太大,發動之前要先提醒友軍(不想被關掉的程式)迴避,不然大家都一起被關掉了

留言

粗體斜體刪除線連結引用圖片程式碼

注意:您的電子信箱將不會被公開,且網站連結不會被搜尋引擎採計