私は64ビットPython 2.7.3 on Win764ビットを実行しています。これを行うことで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経由でWindowsAPI SetConsoleCtrlHandler 関数を使用してみました:
from scipy import stats
import win32api
def doSaneThing(sig, func=None):
print "Here I am"
raise KeyboardInterrupt
win32api.SetConsoleCtrlHandler(doSaneThing, 1)
この後、Ctrl-Cを押すと「HereI am」と出力されますが、Pythonは引き続きforrtlエラーでクラッシュします。「ConsoleCtrlHandler関数が失敗しました」というメッセージが表示され、すぐに消えてしまうことがあります。
これをIPythonで実行すると、forrtlエラーの前に通常のPython KeyboardInterruptトレースバックが表示されます。また、通常のPython tracebackの後にforrtlエラーが表示されます。 KeyboardInterruptの代わりに他のエラー(ValueErrorなど)を発生させた場合:
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)に反応してクラッシュしているようです。これを排除する方法はありますか?
投稿されたソリューションのバリエーションが機能する可能性があります。この問題を解決するためのより良い方法があるかもしれません-あるいは、DLLハンドラーのインストールをスキップするように指示する環境変数を設定することによって、すべてを一緒に回避することさえできます。うまくいけば、これがより良いものを見つけるまで役立つでしょう仕方。
time
module (行868-876)と __multiprocessing
_ module (行312-321)の両方が SetConsoleCtrlHandler
。 time
モジュールの場合、そのコンソールコントロールハンドラーはWindowsイベントhInterruptEvent
を設定します。メインスレッドの場合、_time.sleep
_はWaitForSingleObject(hInterruptEvent, ul_millis)
を介してこのイベントを待機します。ここで、_ul_millis
_は、Ctrl + Cによって中断されない限りスリープするミリ秒数です。インストールしたハンドラーはTrue
を返すため、time
モジュールのハンドラーがhInterruptEvent
を設定するために呼び出されることはありません。つまり、sleep
は中断できません。
imp.init_builtin('time')
を使用してtime
モジュールを再初期化しようとしましたが、どうやらSetConsoleCtrlHandler
は2番目の呼び出しを無視しているようです。ハンドラーを削除してから再挿入する必要があるようです。残念ながら、time
モジュールはそのための関数をエクスポートしません。したがって、応急修理として、ハンドラーをインストールした後にtime
モジュールをインポートするようにしてください。 scipy
をインポートするとtime
もインポートされるため、ハンドラーを正しい順序で取得するには、ctypes
を使用してlibifcoremd.dllをプリロードする必要があります。最後に、_thread.interrupt_main
_への呼び出しを追加して、PythonのSIGINT
ハンドラーが呼び出されるようにします[1]。
例えば:
_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
_を呼び出します。これは_Handlers[SIGINT]
_をトリップし、_Py_AddPendingCall
_を呼び出して_checksignals_witharg
_を追加します。次に、これは_PyErr_CheckSignals
_を呼び出します。 _Handlers[SIGINT]
_がトリップされるため、これは_Handlers[SIGINT].func
_を呼び出します。最後に、func
が_signal.default_int_handler
_の場合、KeyboardInterrupt
例外が発生します。
環境変数の設定FOR_DISABLE_CONSOLE_CTRL_HANDLER
から1
問題を修正しているようです 。
[〜#〜]編集[〜#〜]:ながら Ctrl+C クラッシュしませんpythonもう、現在の計算を停止することもできません。
Ctrl-Cハンドラーをインストールする呼び出しを削除するためにdllにパッチを適用するコードは次のとおりです。
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ファイルがどのようにマップされるかわからないので、16進エディターを使用してファイル内の0d 00 ef ff ff
を検索します。これはユニークなヒットであるため、パッチを適用する.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>
わかりました。0x3fdd9
でdllにパッチを適用しました。今の様子を見てみましょう:
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ハンドラーはインストールされません。
これを行うことで、半分の回避策を得ることができました。
_from scipy import stats
import win32api
def doSaneThing(sig, func=None):
return True
win32api.SetConsoleCtrlHandler(doSaneThing, 1)
_
ハンドラーでtrueを返すと、ハンドラーのチェーンが停止し、干渉しているFortranハンドラーが呼び出されなくなります。ただし、この回避策は2つの理由から、部分的なものにすぎません。
time.sleep(3)
を実行してCtrl-Cを押すと、スリープはすぐに中止され、KeyboardInterruptが発生します。上記の回避策を使用すると、スリープは次のようになります。中止されず、スリープ時間が経過した後にのみ制御がプロンプトに戻ります。それでも、これはセッション全体をクラッシュさせるよりも優れています。私にとって、これはなぜSciPy(およびこれらのIntelライブラリに依存する他のPythonライブラリ))がこれを自分で行わないのかという疑問を提起します。
誰かが本当の解決策や回避策を提供できることを期待して、私はこの答えを受け入れないままにしておきます。 「実際の」とは、長時間実行されるSciPy計算中にCtrl-Cを押すと、SciPyがロードされていないときと同じように機能することを意味します。 (これは、すぐに機能する必要があるという意味ではないことに注意してください。プレーンPython sum(xrange(100000000))
のような非SciPy計算は、Ctrl-Cですぐに中止されない場合がありますが、少なくとも彼らはそうします、彼らはKeyboardInterruptを上げます。)
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
試してみてください
import os
os.environ['FOR_IGNORE_EXCEPTIONS'] = '1'
import scipy.stats