This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Author eryksun
Recipients Akos Kiss, eryksun, paul.moore, steve.dower, tim.golden, zach.ware
Date 2017-10-27.04:18:16
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1509077899.89.0.213398074469.issue31863@psf.upfronthosting.co.za>
In-reply-to
Content
A C/C++ program returns EXIT_FAILURE for a generic failure. Microsoft defines this macro value as 1. Most tools that a user might use to forcibly terminate a process don't allow specifying the reason; they just use the generic value of 1. This includes Task Manager, taskkill.exe /f, the WDK's kill.exe -f, and Sysinternals pskill.exe and Process Explorer. subprocess and multiprocessing should also use 1 to be consistent.

The system itself doesn't distinguish a forced termination from a normal exit. Ultimately every thread and process gets terminated by the system calls NtTerminateThread and NtTerminateProcess (or the equivalent Process Manager private functions PspTerminateThreadByPointer, PspTerminateProcess, etc). Windows API TerminateThread and TerminateProcess are light wrappers around the corresponding system calls.

ExitThread and ExitProcess (actually implemented as RtlExitUserThread and RtlExitUserProcess in ntdll.dll) are within-process calls that integrate with the loader's LdrShutdownThread and LdrShutdownProcess routines. This allows the loader to call the entry points for loaded DLLs with DLL_THREAD_DETACH or DLL_PROCESS_DETACH, respectively. ExitThread also handles deallocating the thread's stack. Beyond that, the bulk of the work is handled by NtTerminateThread and NtTerminateProcess. For ExitProcess, NtTerminateProcess is actually called twice -- the first time it's called with a NULL process handle to kill the other threads in the current process. After LdrShutdownProcess returns, NtTerminateProcess is called again to truly terminate the process.

> PowerShell and .NET ... `System.Diagnostics.Process.Kill()` ... 
> `TerminateProcess` is called with -1

.NET is in its own (cross-platform) managed-code universe. I don't know why the developers decided to make Kill() use -1 (0xFFFFFFFF) as the exit code. I can guess that they negated the conventional EXIT_FAILURE value to indicate a signal-like kill. I think it's an odd decision, and I'm not inclined to favor it over behaviors that predate the existence of .NET. 

Making the ExitCode property a signed integer in .NET is easy to understand, and not a cause for concern since it's only a matter of interpretation. Note that the return value from wmain() or wWinMain() is a signed integer. Also, the two fundamental status result types in Windows -- NTSTATUS [1] and HRESULT [2] -- are 32-bit signed integers (warnings and errors are negative). Internally, the NT Process object's EPROCESS structure defines ExitStatus as an NTSTATUS value. You can see in a kernel debugger that it's a 32-bit signed integer (Int4B):

    lkd> dt nt!_eprocess ExitStatus
       +0x624 ExitStatus : Int4B

Python also wants the exit code to be a signed value. If we try to exit with an unsigned value that exceeds 0x7FFF_FFFF, it instead uses a default code of -1 (0xFFFF_FFFF). For example:

    >>> hex(subprocess.call('python -c "raise SystemExit(0x8000_0000)"'))
    '0xffffffff'

Using the corresponding signed integer works fine:

    >>> 0x8000_0000 - 2**32
    -2147483648
    >>> hex(subprocess.call('python -c "raise SystemExit(-2_147_483_648)"'))
    '0x80000000'

[1]: https://msdn.microsoft.com/en-us/library/cc231200
[2]: https://msdn.microsoft.com/en-us/library/cc231198


> termination by a signal "terminates the calling program with 
> exit code 3"

MS C raise() defaults to calling exit(3). I don't know why it uses the value 3; it's a legacy value from the MS-DOS era. Python doesn't directly expose C raise(), so this exit code only occurs in rare circumstances.

Note that SIGINT and SIGBREAK are based on console control events, and in this case the default behavior (i.e. SIG_DFL) is not to call exit(3) but rather to continue to the next registered console control handler. This is normally the Windows default handler (i.e. kernelbase!DefaultHandler), which calls ExitProcess with STATUS_CONTROL_C_EXIT. When closing the console itself (i.e. CTRL_CLOSE_EVENT), if a control handler in a console client returns TRUE, the default handler doesn't get called, but (starting with NT 6.0) the process still has to be terminated. In this case the session server, csrss.exe, calls NtTerminateProcess with STATUS_CONTROL_C_EXIT.

The exit code also isn't normally 3 for SIGABRT when abort() (i.e. os.abort in Python) gets called. In a release build, abort() defaults to using the __fastfail intrinsic (i.e. INT 0x29 on x64 systems) with the code FAST_FAIL_FATAL_APP_EXIT. This terminates the process with a STATUS_STACK_BUFFER_OVERRUN exception. By design, a __fastfail exception cannot be handled. An attached debugger only sees it as a second-chance exception. (Ideally they should have split this functionality into multiple status codes, since a __fastfail isn't necessarily due to a stack buffer overrun.) The error-reporting dialog may change the exit status to 255 in this case, but you can suppress this dialog via SetErrorMode(SEM_NOGPFAULTERRORBOX) or by using a Job object that's flagged to suppress it. You can also override the CRT's default abort() behavior to skip __fastfail. Either set a SIGABRT handler that exits the process. Or call _set_abort_behavior to unset _CALL_REPORTFAULT, in which case the exit code will be 3.
History
Date User Action Args
2017-10-27 04:18:20eryksunsetrecipients: + eryksun, paul.moore, tim.golden, zach.ware, steve.dower, Akos Kiss
2017-10-27 04:18:19eryksunsetmessageid: <1509077899.89.0.213398074469.issue31863@psf.upfronthosting.co.za>
2017-10-27 04:18:19eryksunlinkissue31863 messages
2017-10-27 04:18:16eryksuncreate