如何检查子进程是否在Windows上被信号杀死

时间:2019-01-14 14:02:19

标签: python windows subprocess signals

问题

给出一个从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:是的,我已经检查了GTEST_OS_WINDOWSGTEST_HAS_SEH在C ++代码中是否正确。

1 个答案:

答案 0 :(得分:1)

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

在这种情况下,-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->ExceptionRecord->ExceptionCode)退出。

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


处理未处理的异常

  • 如果将调试器附加到进程调试端口,则内核会向其发送优先机会异常事件。如果它不处理异常,则内核将异常分配到线程的向量异常处理程序和结构化异常处理程序链(基于堆栈,与当前帧相反检查)中。我们将假定该异常未被任何人处理。

  • 最后检查的帧是线程启动功能STATUS_CONTROL_C_EXIT的帧。该帧的处理程序RtlUserThreadStart传递了一个调度记录,使它可以确定作用域的异常过滤器。给定先前通过_C_specific_handler设置的过滤器,该过滤器又调用该进程的未处理异常过滤器。在进程启动时,kernelbase.dll的初始化例程将其设置为恰当命名的函数RtlSetUnhandledExceptionFilter

  • 如果连接了调试器,则UnhandledExceptionFilter返回UnhandledExceptionFilter。随后,内核向调试器发送第二次异常事件。如果调试器没有处理异常,则内核尝试将事件发送到子系统,即Windows会话服务器csrss.exe。服务器继而将故障中继到Windows错误报告(WER)服务,该服务最终将终止该过程。

  • 如果未附加调试器,则EXCEPTION_CONTINUE_SEARCH会调用应用程序的未处理异常过滤器(如果以前是通过SetUnhandledExceptionFilter设置的)。如果应用程序过滤器返回UnhandledExceptionFilterEXCEPTION_CONTINUE_EXECUTION,则EXCEPTION_EXECUTE_HANDLER将此值返回到帧处理程序。

  • 如果应用程序未设置自己的过滤器,或者其过滤器返回UnhandledExceptionFilter,则EXCEPTION_CONTINUE_SEARCH接下来将检查作业{{1}的工作,进程和线程错误模式}}。该标志禁用错误报告,在这种情况下,过滤器仅返回UnhandledExceptionFilter

  • 如果允许错误报告,则SEM_NOGPFAULTERRORBOX调用Windows错误报告(WER)服务。 WER处于循环状态,其行为最终取决于注册表中的配置方式,组策略以及当前Windows版本的默认行为。 WER可以基于系统“ AeDebug”设置创建并附加调试器过程。如果附加了调试器,则EXCEPTION_EXECUTE_HANDLER返回UnhandledExceptionFilter,就像已经附加了调试器一样。否则返回UnhandledExceptionFilter

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