CTRL_C_EVENT vs CTRL_BREAK_EVENT vs TerminateProcess on Windows

Viewed 94

I have seen plenty of discussions about Windows' TerminateProcess API. Also, I have learned that there are CTRL_C_EVENT and CTRL_BREAK_EVENT events.

My question is, what is the difference between them?

1 Answers

TerminateProcess is insta-death. The process won't see it coming and will just blink out of existence without ever knowing what hit it:

TerminateProcess is the low-level process killing function. It bypasses DLL_PROCESS_DETACH and anything else in the process. Once you kill with TerminateProcess, no more user-mode code will run in that process. It’s gone. Do not pass go. Do not collect $200.

~ Raymond Chen (I updated the links though)

Contrarily, Ctrl+C and Ctrl+Break are handled as signals1 and can be reacted upon. These key combinations trigger the corresponding signals, but there is also a function GenerateConsoleCtrlEvent that can be used to trigger them2.

They are handled a bit differently from each other though. Ctrl+Break is always handled as signal, while Ctrl+C is by default handled as signal but applications may change this behavior to have it treated as regular keyboard input or ignored entirely. The details of how this is achieved are explained in the docs.

"Handled as signals" means the following:

The system creates a new thread in each client process to handle the event. The thread raises an exception if the process is being debugged. The debugger can handle the exception or continue with the exception unhandled.

...and:

[A console control handler is] An application-defined function used with the SetConsoleCtrlHandler function. A console process uses this function to handle control signals received by the process. When the signal is received, the system creates a new thread in the process to execute the function.

There are default console control handlers installed, but you are free to change them. The defaults will react on both Ctrl+C and Ctrl+Break with process termination, but using ExitProcess and not TerminateProcess. Unlike the use of TerminateProcess which constitutes an immediate rugpull, this will give certain cleanup operations in the process a chance to run before fully terminating it, such as DLL_PROCESS_DETACH handlers in DllMain functions and TLS callbacks.

References and further reading:


Footnotes:

1: In a broad sense, "handled as signal" just means "there can be some user-supplied function that gets notified about this event and is allowed to act upon it as desired". Note that signals in a generalized way as you know them from *nixes don't exist as a concept in Windows. In some places docs refer to *nix-like signal names such as SIGINT and SIGBREAK in this case but this is probably intended just to make it easier to understand if you come from a *nix background. There are different events that can happen in different contexts and they have different ways for reacting to them. Here we are talking about console events that a console control handler can receive (those Ctrl+C and Ctrl+Break "signals" would show up in the console control handler's fdwCtrlType argument as CTRL_C_EVENT and CTRL_BREAK_EVENT, but console control handlers can handle other events as well, such as CTRL_CLOSE_EVENT), but other things that in a *nix world would also be a "signal" are handled in entirely different ways - for example an access violation which would be SIGSEGV in *nix would require an UnhandledExceptionFilter to be handled, and there is no concept of a SIGTERM at all (it depends on the type of program you want to ask to terminate - in case of a GUI program the closest thing is sending a WM_CLOSE message to the main window if you can figure out what window that is, in case of a background service that's registered with the Windows service manager there is a dedicated "stop" command, in case of a console program it can be the console close event that's also triggered when you click the close button of the console window, etc.). So don't try to apply your knowledge about what signals do on other OSes here.

2: For this to work, the caller needs to be in the same console process group as the receiver, however. So, if you want to send this from an external process, you first need some trickery such as having a helper process whose sole job is to attach itself to the console of the target and then trigger the event "from the inside".

Related