Ctrl-C crashes Python after importing scipy.stats

后端 未结 7 2171
孤街浪徒
孤街浪徒 2020-12-03 02:38

I\'m running 64-bit Python 2.7.3 on Win7 64-bit. I can reliably crash the Python interpreter by doing this:

>>> from scipy import stats
>>>         


        
相关标签:
7条回答
  • 2020-12-03 03:00

    I have been able to get a half-workaround by doing this:

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

    Returning true in the handler stops the chain of handlers so that the meddling Fortran handler is no longer called. However, this workaround is only partial, for two reasons:

    1. It does not actually raise a KeyboardInterrupt, meaning that I can't react to it in Python code. It just drops me back to the prompt.
    2. It doesn't fully interrupt things in the way that Ctrl-C normally does in Python. If in a fresh Python session I do a time.sleep(3) and hit Ctrl-C, the sleep is immediately aborted and I get a KeyboardInterrupt. With the above workaround, the sleep is not aborted, and control returns to the prompt only after the sleep time is up.

    Nonetheless, this is still better than crashing the whole session. To me this raises the question of why SciPy (and any other Python libraries that rely on these Intel libraries) don't do this themselves.

    I'm leaving this answer unaccepted in the hope that someone can provide a real solution or workaround. By "real" I mean that pressing Ctrl-C during a long-running SciPy calculation should work just like it does when SciPy is not loaded. (Note that this doesn't mean it has to work immediately. Non-SciPy calculations like plain Python sum(xrange(100000000)) may not immediately abort on Ctrl-C, but at least when they do, they raise a KeyboardInterrupt.)

    0 讨论(0)
  • 2020-12-03 03:01

    Try

    import os
    os.environ['FOR_IGNORE_EXCEPTIONS'] = '1'
    import scipy.stats
    
    0 讨论(0)
  • 2020-12-03 03:09

    This worked for me:

    import os
    os.environ['FOR_DISABLE_CONSOLE_CTRL_HANDLER'] = '1'
    from scipy.stats import zscore
    
    0 讨论(0)
  • 2020-12-03 03:10

    Setting the environment variable FOR_DISABLE_CONSOLE_CTRL_HANDLER to 1 seems to fix the issue.

    import os
    os.environ['FOR_DISABLE_CONSOLE_CTRL_HANDLER'] = '1'
    
    [...]
    

    EDIT: While Ctrl+C doesn't crash python anymore, it also fails to stop the current calculation.

    0 讨论(0)
  • 2020-12-03 03:12

    Workaround: patch 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
    
    0 讨论(0)
  • 2020-12-03 03:14

    Here's code to patch the dll to remove the call that installs the Ctrl-C handler:

    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'
    

    EDIT: Here's how I added a patch for the x64. Run python.exe in the debugger, and set a breakpoint for SetConsoleCtrlHandler until you get to the call you want to patch out:

    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)]
    

    We'll patch out the lea instruction with a relative jmp (which is 0xeb followed by the number of bytes to jump)

    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)]
    

    I don't know how the .dll file is mapped in this process, so I'll just search for 0d 00 ef ff ff in the file with a hex editor. It is a unique hit, so we can calculate the location in the .dll to patch.

    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>
    

    Ok, I've patched the dll at 0x3fdd9. Let's see what it looks like now:

    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>
    

    So now were are jmping over pushing the arguments on the stack and the function call. So its Ctrl-C handler will not be installed.

    0 讨论(0)
提交回复
热议问题