【问题标题】:Why does sys.exit(4294967295) set %ERRORLEVEL% to -1?为什么 sys.exit(4294967295) 将 %ERRORLEVEL% 设置为 -1?
【发布时间】:2020-12-01 10:34:25
【问题描述】:

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

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

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

【问题讨论】:

  • 来自documentation of sys.exit()大多数系统要求它在 0-127 范围内,否则会产生未定义的结果
  • @EugeneSh。但是 Windows 文档处理 0-[(2^32)-1] 的范围。所以 C 实现中一定有一些东西。
  • @pstatix 该范围没有在任何地方记录。我为各种平台的应用程序的退出值阅读了各种建议,但我没有在任何地方阅读真正的规范。仅根据返回类型为 intunsigned int 的编译器/库/解释器定义了可以使用函数 main 的哪些变体,但是据我所知,Windows 没有通用规范。在 C 和 C++ 标准中,int 被定义为能够包含至少 -32,767 到 +32,767 的范围(我没记错时从 C99 开始)。
  • @pstatix 在 Linux 上,仅使用 0 到 127 的值作为应用程序特定返回码到父进程是安全的,因为 Linux 内核的各种系统函数已经解释了具有特殊含义的更高值,如 @ 987654322@、wait 等。像SIGTERMSIGINT 这样的Linux 信号应该导致退出应用程序,信号值+ 128 导致“用户”退出范围限制为0 到127。
  • Python 在 Modules/main.c 中以 Py_Main 开头。初始化由几个 C 文件实现,例如 Python/pylifecycle.c、Python/preconfig.c 和 Python/initconfig.c 以及 Modules/getpath.c(或 PC/getpathp.c)。最终它会得到一个运行函数,例如 Python/pythonrun.c 中的PyRun_FileExFlags。该文件通过PyParser_ASTFromFileObjectPyAST_CompileObject 进行解析和编译。有了代码对象,它在 Python/ceval.c 中调用PyEval_EvalCode,它创建一个框架对象来评估代码,最后在PyEval_EvalFrame 中执行,即执行字节码的循环。

标签: python c windows cmd error-code


【解决方案1】:

负整数值可以使用基数补码表示。因此,相同的底层四字节值 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 返回DWORD,这是 unsigned long 的 C 类型定义。

通常,Windows 中的进程退出状态是 0-65535 范围内的无符号 16 位值。通常是通过失败,即 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(0xC000013A 或 -1073741510)打印“^C”,退出状态表示未处理的控制台控制事件,包括未处理的 Ctrl+C(取消)和 Ctrl+Break。例如:

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

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

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

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

CMD 还将最后一个退出状态设置为%=ExitCode%(通常是隐藏的,因为名称以“=”开头),这是一个无符号 32 位值的退出状态,格式为填充零的十六进制数字。如果退出状态是 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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-12
    • 1970-01-01
    相关资源
    最近更新 更多