如何在Python中捕获SegFault异常?

问题描述 投票:0回答:2

有时Python不仅会抛出异常,还会抛出段错误。

根据我多年使用 Python 的经验,我看到了许多段错误,其中一半位于二进制模块(C 库,即

.so
/
.pyd
文件)内部,其中一半位于 CPython 二进制本身内部。

当发出段错误时,整个 Python 程序会以崩溃转储(或静默)结束。我的问题是,如果段错误发生在某些代码块或线程中,是否有机会通过

except
将其捕获为常规异常,从而防止整个程序崩溃?

众所周知,您可以使用faulthandler,例如通过

python -q -X faulthandler
。然后,当出现段错误时,它会创建以下转储:

>>> import ctypes
>>> ctypes.string_at(0)
Fatal Python error: Segmentation fault

Current thread 0x00007fb899f39700 (most recent call first):
  File "/home/python/cpython/Lib/ctypes/__init__.py", line 486 in string_at
  File "<stdin>", line 1 in <module>
Segmentation fault

但是上面的转储完全完成了程序。相反,我想捕获这个回溯作为一些标准异常。


另一个问题是我是否可以在 PyRun_SimpleString() 函数的 C API 中捕获 Python 代码的段错误?

python segmentation-fault
2个回答
8
投票

最简单的方法是有一个“父”进程来启动您的应用程序进程,并检查其退出值。

-11
表示进程收到信号
11
,即
SEGFAULTV
(cf)

import subprocess

SEGFAULT_PROCESS_RETURNCODE = -11


segfaulting_code = "import ctypes ; ctypes.string_at(0)"  # https://codegolf.stackexchange.com/a/4694/115779
try:
    subprocess.run(["python3", "-c", segfaulting_code],
                   check=True)
except subprocess.CalledProcessError as err:
    if err.returncode == SEGFAULT_PROCESS_RETURNCODE:
        print("probably segfaulted")
    else:
        print(f"crashed for other reasons: {err.returncode}")
else:
    print("ok")

编辑:这是一个使用 内置

faulthandler
进行 Python 转储的可重现示例:

# file: parent_handler.py
import subprocess

SEGFAULT_PROCESS_RETURNCODE = -11


try:
    subprocess.run(["python3", "-m", "dangerous_child.py"],
                   check=True)
except subprocess.CalledProcessError as err:
    if err.returncode == SEGFAULT_PROCESS_RETURNCODE:
        print("probably segfaulted")
    else:
        print(f"crashed for other reasons: {err.returncode}")
else:
    print("ok")
# file: dangerous_child.py

import faulthandler
import time

faulthandler.enable()  # by default will dump on sys.stderr, but can also print to a regular file


def cause_segfault():  # https://codegolf.stackexchange.com/a/4694/115779
    import ctypes
    ctypes.string_at(0)


i = 0
while True:
    print("everything is fine ...")
    time.sleep(1)
    i += 1
    if i == 5:
        print("going to segfault!")
        cause_segfault()
everything is fine ...
everything is fine ...
everything is fine ...
everything is fine ...
everything is fine ...
going to segfault!

Fatal Python error: Segmentation fault

Current thread 0x00007f7a9ab35740 (most recent call first):
  File "/usr/lib/python3.8/ctypes/__init__.py", line 514 in string_at
  File "/home/stack_overflow/dangerous_child.py", line 9 in cause_segfault
  File "/home/stack_overflow/dangerous_child.py", line 19 in <module>
  File "<frozen importlib._bootstrap>", line 219 in _call_with_frames_removed
  File "<frozen importlib._bootstrap_external>", line 848 in exec_module
  File "<frozen importlib._bootstrap>", line 671 in _load_unlocked
  File "<frozen importlib._bootstrap>", line 975 in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 991 in _find_and_load
  File "/usr/lib/python3.8/runpy.py", line 111 in _get_module_details
  File "/usr/lib/python3.8/runpy.py", line 185 in _run_module_as_main

probably segfaulted

(两个进程的输出在我的终端中混合在一起,但您可以根据需要将它们分开)

这样你就可以查明问题是由 Python 代码引起的

ctypes.string_at

但正如马克在评论中指出的那样,你不应该太相信这个太多,如果程序被杀是因为它做了坏事。


0
投票

为了补充 Lenormju 的答案,也可以在子进程中运行代码,而无需显式调用 python 解释器。我们可以使用

multiprocessing.Process
来创建子流程。以下代码演示了如何执行此操作。它可以打印段错误和异常的回溯。例如,这在单元测试中可能很有用。

import multiprocessing
import ctypes
import faulthandler

faulthandler.enable()

def test_code1():
    # segfault
    ctypes.string_at(0)

def test_code2():
    # throw exception
    print(0/0)

def test_code3():
    # normal
    print("Hello")

def run_in_subprocess(func):
    process = multiprocessing.Process(target=func)
    process.start()
    process.join()
    exitcode = process.exitcode
    process.close()
    if exitcode == 0:
        print(func, "no error")
    elif exitcode < 0:
        print(func, f"killed by signal {-exitcode}")
    else:
        print(func, f"exited with code {exitcode}")
    return exitcode

if __name__ == "__main__":
    run_in_subprocess(test_code1)
    print("======================")
    run_in_subprocess(test_code2)
    print("======================")
    run_in_subprocess(test_code3)
© www.soinside.com 2019 - 2024. All rights reserved.