为什么 sys.exit(4294967295) 将 %ERRORLEVEL% 设置为 -1?

Why does sys.exit(4294967295) set %ERRORLEVEL% to -1?

在处理调用 Python 脚本的批处理脚本时偶然发现了这一点。

在 Python 脚本中,我调用 sys.exit(-1)%ERRORLEVEL% 设置为 4294967295。在阅读 Windows 使用 32 位无符号整数后,我更改了它到 sys.exit(4294967295),但现在 %ERRORLEVEL% 是 -1。

为什么会反过来呢? sys.exit(-1) 是有道理的,因为它的 (2^32) -1 并与回绕一起存储,但为什么使用 32 位无符号整数的最大值会转换为 -1?是不是C下面的东西?

负整数值可以使用基数补码表示。因此,相同的基础 four-byte 值 0xFFFFFFFF 可能表示无符号 32 位值 4294967295 或有符号 32 位 two's complement 值 -1。这只是字节在上下文中如何解释的问题,其中包括使用有符号指令与无符号指令,例如在 x86 ISA 中,JG(如果更大则跳转 - 有符号)与 JA(如果高于 - 无符号则跳转)。

CMD等待进程退出时,通过GetExitCodeProcess获取退出状态,并保存为32位有符号整数cmd!LastRetCode。 (module_name!symbol_name 是 windbg 或 cdb 等调试器在加载模块中引用全局符号名称的方式。)CMD 将退出状态解释为已签名整数,尽管在 API 级别,Windows returns 一个 DWORD,它是 unsigned long.

的 C typedef

通常Windows中的进程退出状态是0-65535范围内的无符号16位值。通常是 pass-fail,即 EXIT_SUCCESS (0) 或 EXIT_FAILURE (1)。但是,如果 Windows 程序异常终止,例如未处理的异常,状态代码通常是 NTSTATUS 值,这是一个 32 位有符号整数,表示警告和失败为负值.例如,访问冲突是 STATUS_ACCESS_VIOLATION(0xC0000005 或 -1073741819)。

当程序异常终止时,批处理脚本可能需要特殊处理。可以使用 errorlevel <number> 表达式测试最后的退出状态,如果最后的退出状态等于或大于指定的数字,则为真。例如:

C:\>cmd /c exit -1073741819
C:\>if errorlevel 0 (echo normal exit) else (echo abnormal exit)
abnormal exit

CMD 本身有特殊的处理来为 STATUS_CONTROL_C_EXIT 打印“^C”(0xC000013A 或 -1073741510),退出状态表示未处理的控制台控制事件,其中包括未处理的 Ctrl+C(取消)和Ctrl+中断。例如:

C:\>cmd /c exit -1073741510
^C

除了errorlevel检查外,CMD还有一个内置的%ERRORLEVEL%环境变量。内置环境变量实际上并未存储在进程环境块中。它们作为默认值提供。在调试器中,您可以观察到 CMD 调用 cmd!GetEnvVar 来获取环境变量的值。此函数首先尝试 WinAPI GetEnvironmentVariableW,它 returns 一个真正的进程环境变量(例如 PATH)或 OS 内置变量之一(例如 __APPDIR__, __CD__).如果 GetEnvironmentVariableW 找不到“ERRORLEVEL”,GetEnvVar 默认为内置值,即 LastRetCode 值通过 StringCchPrintfW 转换为字符串,格式为字符串 "%d"(即带符号的十进制整数)。例如:

C:\>cmd /c exit -1
C:\>echo %errorlevel%
-1

C:\>set errorlevel=foo
C:\>echo %errorlevel%
foo

CMD还将最后的退出状态设置为%=ExitCode%(习惯上是隐藏的,因为名称以“=”开头),这是一个无符号的32位值的退出状态,格式为zero-padded 十六进制数。如果退出状态是 32-126 范围内的可打印 ASCII 序号,则 CMD 还设置 %=ExitCodeAscii%。无论出于何种原因,CMD 将这些存储为由子进程继承的真实环境变量。例如,对于 65 的退出状态(即 0x41,即“A”的 ASCII 序号):

C:\>cmd /c exit 65

C:\>python -q
>>> import win32api
>>> win32api.GetEnvironmentVariable('=ExitCode')
'00000041'
>>> win32api.GetEnvironmentVariable('=ExitCodeAscii')
'A'

关于 Python 中的 sys.exit(4294967295),请注意 sys.exit(status) —— 以及其下方的 raise SystemExit(status) —— 将退出状态处理为带符号的 Python 整数转换为 -2147483648 到 2147483647 范围内的 C long int。使用此范围之外的值会导致转换失败并将退出状态设置为 -1。请参阅 GitHub 上发布的源代码中的 _Py_HandleSystemExit。碰巧 4294967295 (0xFFFFFFFF) 是 -1 的二进制补码表示,但对于任何其他不受支持的值,您会得到相同的结果。例如:

C:\>python -c "raise SystemExit(9876543210)"
C:\>echo %errorlevel%
-1

正常退出状态为 0 表示成功,1 表示失败,包括将错误消息打印到 stderr 时:

C:\>python -c "raise SystemExit"
C:\>echo %errorlevel%
0

C:\>python -c "raise SystemExit('whoops-a-daisy...')"
whoops-a-daisy...
C:\>echo %errorlevel%
1