【问题标题】:How to check if subprocess was killed by signal on Windows如何检查子进程是否被Windows上的信号杀死
【发布时间】:2019-01-14 14:02:19
【问题描述】:

问题

给定一个在 python 中启动的子进程,其代码类似于:

import subprocess
p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p.communicate()
print('Return code: {}'.format(p.returncode))

根据official documentation,可以检查子进程是否被信号终止:

负值 -N 表示子进程被信号 N 终止(仅限 POSIX)。

但仅限于 POSIX 平台。

有没有办法在 Windows 平台上检查进程是否被信号终止(不管是哪一个)?

背景

我在运行 googletest 的测试时遇到了这个问题。 break-on-failure CLI flag test 在 Windows 平台(VC14、VS2017)上失败,但在 POSIX 平台(2x Ubuntu、2x macOS)上运行良好。

在命令行上手动得到以下结果:

> .\googletest-break-on-failure-unittest_.exe --gtest_break_on_failure
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from Foo
[ RUN      ] Foo.Bar
<some path>\googletest\test\googletest-break-on-failure-unittest_.cc(52): error: Expected equality of these values:
  2
  3

> echo %ERRORLEVEL%
-2147483645

但是,调用此测试的 python 包装器会收到 2147483651(正数)。

(我刚刚在this line之前添加了一个打印)

请注意,这些是指十六进制的数字0xFFFFFFFF80000003(负数)和0x‭80000003‬(正数),并且返回代码没有进一步处理。 (见here

返回码为什么会这样改?

PS:是的,我检查了 C++ 代码中的 GTEST_OS_WINDOWSGTEST_HAS_SEH 是否正确。

【问题讨论】:

    标签: python windows subprocess signals


    【解决方案1】:

    CMD 的 ERRORLEVEL 环境变量是一个 32 位有符号值。正值范围是 0x00000000-0x7FFFFFFF(0 到 2147483647),负值范围是 0x80000000-0xFFFFFFFF(-2147483648 到 -1)。当进程由于未处理的异常而退出时,状态通常是异常代码,通常是一个 32 位,签名的 NTSTATUS 值。也就是说,没有什么能阻止我们使用超过 0x7FFFFFFF 的无符号退出代码调用 ExitProcessTerminateProcess,因此 CMD 中的负数 ERRORLEVEL 本身并不意味着进程异常退出。

    在这种情况下,-2147483645 是 NTSTATUS 代码 STATUS_BREAKPOINT (0x80000003)。这源自 Google Test 的 gtest_break_on_failure 选项,它调用 WinAPI DebugBreak。对于 x86 架构,后一个函数只是简单地执行一条int 3 指令(软件中断 3),该指令被困在内核中以引发断点异常。

    通常没有附加调试器,这将执行默认的 Windows 未处理异常处理程序,该处理程序调用 Windows Error Reporting(见下文)。但是问题中链接的代码将ExitWithExceptionCode 设置为应用程序的未处理异常过滤器。这个过滤器只是通过exit(exception_pointers-&gt;ExceptionRecord-&gt;ExceptionCode) 退出。

    对于 Unix 信号,Windows 内核没有实现它们。 C 运行时实现了标准 C 所需的六个。在控制台应用程序中,标准SIGINT 信号与控制台的CTRL_C_EVENT 相关联。非标准的SIGBREAK 信号用于其他控制台控制事件,包括CTRL_BREAK_EVENTCTRL_CLOSE_EVENT。这些事件的默认处理程序调用ExitProcess(STATUS_CONTROL_C_EXIT)。因此,要从字面上回答这个问题,要确定一个进程是否被控制台“信号”杀死,请检查 STATUS_CONTROL_C_EXIT(0xC000013A 或 -1073741510)。


    处理未处理的异常

    • 如果调试器连接到进程调试端口,内核会向其发送第一次机会异常事件。如果它不处理异常,内核将异常分派到线程的向量异常处理程序链和结构化异常处理程序(基于堆栈,从当前帧反向检查)。我们将假设异常没有被它们中的任何一个处理。

    • 检查的最后一帧是线程启动函数的帧,RtlUserThreadStart。此帧的处理程序_C_specific_handler 传递了一个调度记录,允许它确定范围的异常过滤器。这个过滤器反过来调用进程的未处理异常过滤器,假设它之前是通过RtlSetUnhandledExceptionFilter 设置的。在进程启动时,kernelbase.dll 的初始化例程将其设置为恰当命名的函数UnhandledExceptionFilter

    • 如果附加了调试器,UnhandledExceptionFilter 返回EXCEPTION_CONTINUE_SEARCH。随后,内核向调试器发送第二次机会异常事件。如果调试器不处理异常,内核会尝试将事件发送到子系统,即 Windows 会话服务器 csrss.exe。服务器依次将故障转发给 Windows 错误报告 (WER) 服务,该服务最终将终止该进程。

    • 如果没有附加调试器,UnhandledExceptionFilter 会调用应用程序的未处理异常过滤器,如果它之前是通过 SetUnhandledExceptionFilter 设置的。如果应用程序过滤器返回EXCEPTION_CONTINUE_EXECUTIONEXCEPTION_EXECUTE_HANDLER,则UnhandledExceptionFilter 将此值返回给帧处理程序。

    • 1234563此标志禁用错误报告,在这种情况下过滤器仅返回 EXCEPTION_EXECUTE_HANDLER
    • 如果允许错误报告,UnhandledExceptionFilter 将调用 Windows 错误报告 (WER) 服务。在循环中使用 WER,行为最终取决于它在注册表中的配置方式、组策略以及当前 Windows 版本的默认行为。 WER 可以根据系统“AeDebug”设置创建和附加调试器进程。如果它附加了一个调试器,UnhandledExceptionFilter 返回EXCEPTION_CONTINUE_SEARCH,就像它已经附加了一个调试器一样。否则返回EXCEPTION_EXECUTE_HANDLER

    • 最终,如果它为“未处理”异常执行异常处理程序,堆栈首先通过RtlUnwindEx 展开到RtlUserThreadStart 帧,这将调用中间帧中的任何finally 终止处理程序。异常处理程序的执行上下文通过RtlRestoreContext 恢复,异常代码在整数返回寄存器中设置(例如,x64 中的rax)。最后,处理程序通过NtTerminateProcess(NtCurrentProcess(), exceptionCode)自行终止。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-08
      • 2011-09-23
      • 1970-01-01
      • 1970-01-01
      • 2010-09-28
      相关资源
      最近更新 更多