40

Win7 64 ビットで 64 ビット Python 2.7.3 を実行しています。これを行うことで、Python インタープリターを確実にクラッシュさせることができます。

>>> from scipy import stats
>>> import time
>>> time.sleep(3)

スリープ中に Control-C を押します。KeyboardInterrupt は発生しません。インタプリタがクラッシュします。以下が印刷されます。

forrtl: error (200): program aborting due to control-C event
Image              PC                Routine            Line        Source

libifcoremd.dll    00000000045031F8  Unknown               Unknown  Unknown
libifcoremd.dll    00000000044FC789  Unknown               Unknown  Unknown
libifcoremd.dll    00000000044E8583  Unknown               Unknown  Unknown
libifcoremd.dll    000000000445725D  Unknown               Unknown  Unknown
libifcoremd.dll    00000000044672A6  Unknown               Unknown  Unknown
kernel32.dll       0000000077B74AF3  Unknown               Unknown  Unknown
kernel32.dll       0000000077B3F56D  Unknown               Unknown  Unknown
ntdll.dll          0000000077C73281  Unknown               Unknown  Unknown

これにより、長時間実行される scipy 計算を中断することができなくなります。

「forrtl」などをグーグルで検索すると、この種の問題は、Ctrl-C 処理をオーバーライドする Fortran ライブラリの使用が原因であるという提案が見られます。Scipy トラッカーにバグは見当たりませんが、Scipy が Python で使用するためのライブラリであることを考えると、これはバグだと思います。Python の Ctrl-C の処理が壊れます。これに対する回避策はありますか?

編集: @cgohlke の提案に従って、scipy をインポートした後に独自のハンドラーを追加しようとしました。 関連する問題に関するこの質問は、シグナル ハンドラーの追加が機能しないことを示しています。pywin32 経由で Wi​​ndows API SetConsoleCtrlHandler関数を使用してみました:

from scipy import stats
import win32api
def doSaneThing(sig, func=None):
    print "Here I am"
    raise KeyboardInterrupt
win32api.SetConsoleCtrlHandler(doSaneThing, 1)

この後、Ctrl-C を押すと "Here I am" が出力されますが、Python は依然として forrtl エラーでクラッシュします。「ConsoleCtrlHandler 関数が失敗しました」というメッセージが表示されることもありますが、すぐに消えます。

これを IPython で実行すると、forrtl エラーの前に通常の Python KeyboardInterrupt トレースバックが表示されます。また、KeyboardInterrupt の代わりに他のエラー (ValueError など) を発生させると、通常の Python トレースバックの後に forrtl エラーが表示されます。

ValueError                                Traceback (most recent call last)
<ipython-input-1-08defde66fcb> in doSaneThing(sig, func)
      3 def doSaneThing(sig, func=None):
      4     print "Here I am"
----> 5     raise ValueError
      6 win32api.SetConsoleCtrlHandler(doSaneThing, 1)

ValueError:
forrtl: error (200): program aborting due to control-C event
[etc.]

基になるハンドラーが何をしていても、Ctrl-C を直接トラップするだけでなく、エラー状態 (ValueError) に反応してクラッシュしているようです。これをなくす方法はありますか?

4

7 に答える 7

23

これは、投稿されたソリューションのバリエーションであり、機能する可能性があります。この問題を解決するためのより良い方法があるかもしれません。または、ハンドラーのインストールをスキップするように DLL に指示する環境変数を設定することで、問題をすべて回避することもできます。より良い方法が見つかるまで、これが役立つことを願っています。

timeモジュール(行 868-876) と_multiprocessingモジュール(行 312-321)の両方がを呼び出しますSetConsoleCtrlHandler。モジュールの場合time、そのコンソール コントロール ハンドラが Windows イベントを設定しますhInterruptEvent。メイン スレッドの場合、time.sleepを介してこのイベントを待機しますWaitForSingleObject(hInterruptEvent, ul_millis)。ここで、ul_millisは Ctrl+C によって中断されない限りスリープするミリ秒数です。インストールしたハンドラーが を返すTrueため、timeモジュールのハンドラーが sethInterruptEventに呼び出されることはありません。つまり、sleep中断することはできません。

imp.init_builtin('time')モジュールを再初期化するために使用しようとしましtimeたが、明らかSetConsoleCtrlHandlerに2番目の呼び出しを無視します。ハンドラーを削除してから再挿入する必要があるようです。残念ながら、timeモジュールはそのための関数をエクスポートしません。そのため、ハンドラーをインストールした後time、必ずモジュールをインポートしてください。インポートは もインポートするため、適切な順序でハンドラーを取得するためにlibifcoremd.dll を使用してプリロードする必要があります。最後に、呼び出しを追加して、 Python のハンドラーが確実に呼び出されるように[1]します。scipytimectypesthread.interrupt_mainSIGINT

例えば:

import os
import imp
import ctypes
import thread
import win32api

# Load the DLL manually to ensure its handler gets
# set before our handler.
basepath = imp.find_module('numpy')[1]
ctypes.CDLL(os.path.join(basepath, 'core', 'libmmd.dll'))
ctypes.CDLL(os.path.join(basepath, 'core', 'libifcoremd.dll'))

# Now set our handler for CTRL_C_EVENT. Other control event 
# types will chain to the next handler.
def handler(dwCtrlType, hook_sigint=thread.interrupt_main):
    if dwCtrlType == 0: # CTRL_C_EVENT
        hook_sigint()
        return 1 # don't chain to the next handler
    return 0 # chain to the next handler

win32api.SetConsoleCtrlHandler(handler, 1)

>>> import time
>>> from scipy import stats
>>> time.sleep(10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyboardInterrupt

[1]interrupt_main呼び出しますPyErr_SetInterrupt。これはトリップして addHandlers[SIGINT]を呼び出します。次に、これは を呼び出します。がトリップしたので、これは を呼び出します。最後に、isの場合、例外が発生します。Py_AddPendingCallchecksignals_withargPyErr_CheckSignalsHandlers[SIGINT]Handlers[SIGINT].funcfuncsignal.default_int_handlerKeyboardInterrupt

于 2013-03-18T08:56:12.087 に答える
4

これを行うことで、半分の回避策を得ることができました:

from scipy import stats
import win32api
def doSaneThing(sig, func=None):
    return True
win32api.SetConsoleCtrlHandler(doSaneThing, 1)

ハンドラーで true を返すと、ハンドラーのチェーンが停止し、干渉する Fortran ハンドラーが呼び出されなくなります。ただし、次の 2 つの理由から、この回避策は部分的なものにすぎません。

  1. 実際には KeyboardInterrupt を発生させません。つまり、Python コードでそれに反応することはできません。プロンプトに戻るだけです。
  2. Python で Ctrl-C が通常行うように、物事を完全に中断することはありません。新しい Python セッションで a を実行してtime.sleep(3)Ctrl-C を押すと、スリープはすぐに中止され、KeyboardInterrupt が発生します。上記の回避策では、スリープは中止されず、スリープ時間が終了した後にのみ制御がプロンプトに戻ります。

それでも、これはセッション全体をクラッシュさせるよりはましです。私にとって、これは、なぜ SciPy (およびこれらの Intel ライブラリに依存する他の Python ライブラリ) がこれを行わないのかという疑問を提起します。

誰かが実際の解決策または回避策を提供できることを期待して、この回答は受け入れられません。「本当の」とは、長時間の SciPy 計算中に Ctrl-C を押すと、SciPy がロードされていないときと同じように機能することを意味します。(これは、すぐに動作する必要があるという意味ではないことに注意してください。プレーンな Python のような SciPy 以外の計算はsum(xrange(100000000))、Ctrl-C ですぐに中止されない場合がありますが、少なくとも中止された場合は、KeyboardInterrupt が発生します。)

于 2013-03-17T21:01:02.660 に答える
3

dll にパッチを適用して、Ctrl-C ハンドラーをインストールする呼び出しを削除するコードを次に示します。

import os
import os.path
import imp
import hashlib

basepath = imp.find_module('numpy')[1]
ifcoremd = os.path.join(basepath, 'core', 'libifcoremd.dll')
with open(ifcoremd, 'rb') as dll:
    contents = dll.read()

m = hashlib.md5()
m.update(contents)

patch = {'7cae928b035bbdb90e4bfa725da59188': (0x317FC, '\xeb\x0b'),
  '0f86dcd44a1c2e217054c50262f727bf': (0x3fdd9, '\xeb\x10')}[m.hexdigest()]
if patch:
    contents = bytearray(contents)
    contents[patch[0]:patch[0] + len(patch[1])] = patch[1]
    with open(ifcoremd, 'wb') as dll:
        dll.write(contents)
else:
    print 'Unknown dll version'

編集: x64 用のパッチを追加する方法は次のとおりです。デバッガーで python.exe を実行し、SetConsoleCtrlHandlerパッチを適用する呼び出しに到達するまでブレークポイントを設定します。

Microsoft (R) Windows Debugger Version 6.12.0002.633 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.

CommandLine: .\venv\Scripts\python.exe
...
0:000> .symfix
0:000> bp kernel32!SetConsoleCtrlHandler
0:000> g
Breakpoint 0 hit
KERNEL32!SetConsoleCtrlHandler:
00007ffc`c25742f0 ff252af00400    jmp     qword ptr [KERNEL32!_imp_SetConsoleCtrlHandler (00007ffc`c25c3320)] ds:00007ffc`c25c3320={KERNELBASE!SetConsoleCtrlHandler (00007ffc`bfa12e10)}
0:000> k 5
Child-SP          RetAddr           Call Site
00000000`007ef7a8 00000000`71415bb4 KERNEL32!SetConsoleCtrlHandler
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\WINDOWS\SYSTEM32\python27.dll -
00000000`007ef7b0 00000000`7035779f MSVCR90!signal+0x17c
00000000`007ef800 00000000`70237ea7 python27!PyOS_getsig+0x3f
00000000`007ef830 00000000`703546cc python27!Py_Main+0x21ce7
00000000`007ef880 00000000`7021698c python27!Py_InitializeEx+0x40c
0:000> g
Python 2.7.11 (v2.7.11:6d1b6a68f775, Dec  5 2015, 20:40:30) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy
...
Breakpoint 0 hit
KERNEL32!SetConsoleCtrlHandler:
00007ffc`c25742f0 ff252af00400    jmp     qword ptr [KERNEL32!_imp_SetConsoleCtrlHandler (00007ffc`c25c3320)] ds:00007ffc`c25c3320={KERNELBASE!SetConsoleCtrlHandler (00007ffc`bfa12e10)}
0:000> k 5
Child-SP          RetAddr           Call Site
00000000`007ec308 00000000`7023df6e KERNEL32!SetConsoleCtrlHandler
00000000`007ec310 00000000`70337877 python27!PyTime_DoubleToTimet+0x10ee
00000000`007ec350 00000000`7033766d python27!PyImport_IsScript+0x4f7
00000000`007ec380 00000000`70338bf2 python27!PyImport_IsScript+0x2ed
00000000`007ec3b0 00000000`703385a9 python27!PyImport_ImportModuleLevel+0xc82
0:000> g
...
>>> import scipy.stats
...
Breakpoint 0 hit
KERNEL32!SetConsoleCtrlHandler:
00007ffc`c25742f0 ff252af00400    jmp     qword ptr [KERNEL32!_imp_SetConsoleCtrlHandler (00007ffc`c25c3320)] ds:00007ffc`c25c3320={KERNELBASE!SetConsoleCtrlHandler (00007ffc`bfa12e10)}
0:000> k 5
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Users\kevin\Documents\\venv\lib\site-packages\numpy\core\libifcoremd.dll -
Child-SP          RetAddr           Call Site
00000000`007ed818 00007ffc`828309eb KERNEL32!SetConsoleCtrlHandler
00000000`007ed820 00007ffc`828dfa44 libifcoremd!GETEXCEPTIONPTRSQQ+0xdb
00000000`007ed880 00007ffc`828e59d7 libifcoremd!for_lt_ne+0xc274
00000000`007ed8b0 00007ffc`828e5aff libifcoremd!for_lt_ne+0x12207
00000000`007ed8e0 00007ffc`c292ddc7 libifcoremd!for_lt_ne+0x1232f
0:000> ub  00007ffc`828309eb
libifcoremd!GETEXCEPTIONPTRSQQ+0xbb:
00007ffc`828309cb 00e8            add     al,ch
00007ffc`828309cd df040b          fild    word ptr [rbx+rcx]
00007ffc`828309d0 0033            add     byte ptr [rbx],dh
00007ffc`828309d2 c9              leave
00007ffc`828309d3 ff15bf390e00    call    qword ptr [libifcoremd!for_lt_ne+0x40bc8 (00007ffc`82914398)]
00007ffc`828309d9 488d0d00efffff  lea     rcx,[libifcoremd!for_rtl_finish_+0x20 (00007ffc`8282f8e0)]
00007ffc`828309e0 ba01000000      mov     edx,1
00007ffc`828309e5 ff158d390e00    call    qword ptr [libifcoremd!for_lt_ne+0x40ba8 (00007ffc`82914378)]

lea命令を相対値でパッチしますjmp(0xebその後にジャンプするバイト数が続きます)。

0:000> ? 00007ffc`828309eb - 00007ffc`828309d9
Evaluate expression: 18 = 00000000`00000012
0:000> f 00007ffc`828309d9 L2 eb 10
Filled 0x2 bytes
0:000> ub  00007ffc`828309eb
libifcoremd!GETEXCEPTIONPTRSQQ+0xbe:
00007ffc`828309ce 040b            add     al,0Bh
00007ffc`828309d0 0033            add     byte ptr [rbx],dh
00007ffc`828309d2 c9              leave
00007ffc`828309d3 ff15bf390e00    call    qword ptr [libifcoremd!for_lt_ne+0x40bc8 (00007ffc`82914398)]
00007ffc`828309d9 eb10            jmp     libifcoremd!GETEXCEPTIONPTRSQQ+0xdb (00007ffc`828309eb)
00007ffc`828309db 0d00efffff      or      eax,0FFFFEF00h
00007ffc`828309e0 ba01000000      mov     edx,1
00007ffc`828309e5 ff158d390e00    call    qword ptr [libifcoremd!for_lt_ne+0x40ba8 (00007ffc`82914378)]

このプロセスで .dll ファイルがどのようにマップされるかはわかりません。そのため0d 00 ef ff ff、16 進エディターでファイル内を検索するだけです。これは固有のヒットであるため、パッチを適用する .dll 内の場所を計算できます。

0:000> db  00007ffc`828309d0
00007ffc`828309d0  00 33 c9 ff 15 bf 39 0e-00 eb 10 0d 00 ef ff ff  .3....9.........
00007ffc`828309e0  ba 01 00 00 00 ff 15 8d-39 0e 00 48 8d 0d 0e 9c  ........9..H....
00007ffc`828309f0  09 00 e8 09 2e 0a 00 48-8d 0d 32 9f 09 00 e8 fd  .......H..2.....
00007ffc`82830a00  2d 0a 00 48 8d 0d ca ee-0e 00 e8 51 90 00 00 85  -..H.......Q....
00007ffc`82830a10  c0 0f 85 88 02 00 00 e8-38 fa 0a 00 ff 15 4e 39  ........8.....N9
00007ffc`82830a20  0e 00 89 c1 e8 d7 2d 0a-00 48 8d 05 f8 be 11 00  ......-..H......
00007ffc`82830a30  45 32 e4 c7 05 0b 4a 13-00 00 00 00 00 41 bd 01  E2....J......A..
00007ffc`82830a40  00 00 00 48 89 05 06 4a-13 00 ff 15 30 39 0e 00  ...H...J....09..
0:000> ? 00007ffc`828309d9 -  00007ffc`828309d0
Evaluate expression: 9 = 00000000`00000009
0:000> ? 00007ffc`828309d9 -  00007ffc`828309d0 + 3FDD0
Evaluate expression: 261593 = 00000000`0003fdd9
0:000>

わかりました。の dll にパッチを適用しました0x3fdd9。それが今どのように見えるか見てみましょう:

Microsoft (R) Windows Debugger Version 6.12.0002.633 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.

CommandLine: .\venv\Scripts\python.exe
...
0:000> bp libifcoremd!GETEXCEPTIONPTRSQQ+c9
Bp expression 'libifcoremd!GETEXCEPTIONPTRSQQ+c9' could not be resolved, adding deferred bp
0:000> g
Python 2.7.11 (v2.7.11:6d1b6a68f775, Dec  5 2015, 20:40:30) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import scipy.stats
...
Breakpoint 0 hit
libifcoremd!GETEXCEPTIONPTRSQQ+0xc9:
00007ffc`845909d9 eb10            jmp     libifcoremd!GETEXCEPTIONPTRSQQ+0xdb (00007ffc`845909eb)
0:000> u
libifcoremd!GETEXCEPTIONPTRSQQ+0xc9:
00007ffc`845909d9 eb10            jmp     libifcoremd!GETEXCEPTIONPTRSQQ+0xdb (00007ffc`845909eb)
00007ffc`845909db 0d00efffff      or      eax,0FFFFEF00h
00007ffc`845909e0 ba01000000      mov     edx,1
00007ffc`845909e5 ff158d390e00    call    qword ptr [libifcoremd!for_lt_ne+0x40ba8 (00007ffc`84674378)]
00007ffc`845909eb 488d0d0e9c0900  lea     rcx,[libifcoremd!GETHANDLEQQ (00007ffc`8462a600)]
00007ffc`845909f2 e8092e0a00      call    libifcoremd!for_lt_ne+0x30 (00007ffc`84633800)
00007ffc`845909f7 488d0d329f0900  lea     rcx,[libifcoremd!GETUNITQQ (00007ffc`8462a930)]
00007ffc`845909fe e8fd2d0a00      call    libifcoremd!for_lt_ne+0x30 (00007ffc`84633800)
0:000>

そのjmpため、スタックと関数呼び出しに引数をプッシュする必要があります。したがって、その Ctrl-C ハンドラーはインストールされません。

于 2016-06-30T21:47:47.323 に答える
2

回避策: パッチSetControlCtrlHandler

import ctypes
SetConsoleCtrlHandler_body_new = b'\xC2\x08\x00' if ctypes.sizeof(ctypes.c_void_p) == 4 else b'\xC3'
try: SetConsoleCtrlHandler_body = (lambda kernel32: (lambda pSetConsoleCtrlHandler:
    kernel32.VirtualProtect(pSetConsoleCtrlHandler, ctypes.c_size_t(1), 0x40, ctypes.byref(ctypes.c_uint32(0)))
    and (ctypes.c_char * 3).from_address(pSetConsoleCtrlHandler.value)
)(ctypes.cast(kernel32.SetConsoleCtrlHandler, ctypes.c_void_p)))(ctypes.windll.kernel32)
except: SetConsoleCtrlHandler_body = None
if SetConsoleCtrlHandler_body:
    SetConsoleCtrlHandler_body_old = SetConsoleCtrlHandler_body[0:len(SetConsoleCtrlHandler_body_new)]
    SetConsoleCtrlHandler_body[0:len(SetConsoleCtrlHandler_body_new)] = SetConsoleCtrlHandler_body_new
try:
    import scipy.stats
finally:
    if SetConsoleCtrlHandler_body:
        SetConsoleCtrlHandler_body[0:len(SetConsoleCtrlHandler_body_new)] = SetConsoleCtrlHandler_body_old
于 2016-08-18T14:37:38.833 に答える
1

これは私のために働いた:

import os
os.environ['FOR_DISABLE_CONSOLE_CTRL_HANDLER'] = '1'
from scipy.stats import zscore
于 2020-07-15T14:58:30.613 に答える
1

試す

import os
os.environ['FOR_IGNORE_EXCEPTIONS'] = '1'
import scipy.stats
于 2013-03-18T09:46:10.497 に答える