Eryk Sun <eryk...@gmail.com> added the comment:

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.

----------

_______________________________________
Python tracker <rep...@bugs.python.org>
<https://bugs.python.org/issue31863>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to