有时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 代码的段错误?
最简单的方法是有一个“父”进程来启动您的应用程序进程,并检查其退出值。
-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
。
但正如马克在评论中指出的那样,你不应该太相信这个太多,如果程序被杀是因为它做了坏事。
为了补充 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)