专栏名称: Python之美
《Python web开发实战》作者的公众号。发现Python之美,主要包含Web开发、Python进阶、架构设计、Python开发招聘信息等方面内容
目录
相关文章推荐
51好读  ›  专栏  ›  Python之美

一次调试段错误(segmentation fault)的经验

Python之美  · 公众号  · Python  · 2019-12-11 17:47

正文

请到「今天看啥」查看全文


段错误(segmentation fault)的发生是由于C模块试图访问无法访问的内存。如果没有尝鲜最新的CPython或者类库或者编写C/C++扩展,段错误对Python开发者来说可以说可遇不可求,因为CPython和主流第三方类库的测试完善且社区活跃所以很难看到,即便看到了往往也已经被修复了。

昨天恰好遇到一个,所以把整个调试解决过程整理成本文。

问题

我准备在博客应用lyanna的v3.0版本时支持Python 3.8最新的海象运算符,所以拉取了最新的CPython源码并编译,在lyanna项目中使用它创建虚拟环境并安装依赖。问题就出在安装依赖过程中:

  1. virtualenv venv --python=python3.8

  2. source venv/bin/activate

  3. pip install -r requirements.txt

  4. ...

  5. Installing collected packages: aiomcache

  6. Running setup.py develop for aiomcache

  7. ERROR: Command errored out with exit status -11:

  8. command: /Users/dongweiming/lyanna/venv/bin/python3.8 -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'/Users/dongweiming/lyanna/venv/src/aiomcache/setup.py'"'"'; __file__='"'"'/Users/dongweiming/lyanna/venv/src/aiomcache/setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' develop --no-deps

  9. cwd: / Users/dongweiming/lyanna/venv/src/aiomcache/

  10. Complete output (10 lines):

  11. running develop

  12. running egg_info

  13. writing aiomcache.egg-info/PKG-INFO

  14. writing dependency_links to aiomcache.egg-info/dependency_links.txt

  15. writing top-level names to aiomcache.egg-info/top_level.txt

  16. reading manifest file 'aiomcache.egg-info/SOURCES.txt'

  17. reading manifest template 'MANIFEST.in'

  18. warning: no previously-included files matching '*.pyc' found anywhere in distribution

  19. warning: no previously-included files matching '*.swp' found anywhere in distribution

  20. writing manifest file 'aiomcache.egg-info/SOURCES.txt'

  21. ...

这里并没有抛出段错误,输出还是很让人困惑的,我查了下退出码11(延伸阅读链接1)表示段错误,当然还可以直接执行复现:

  1. python /Users/dongweiming/lyanna/venv/src/aiomcache/setup.py develop

  2. ...

  3. [1] 93545 segmentation fault python3.8 /Users/dongweiming/lyanna/venv/src/aiomcache/setup.py develop

和Python代码报错有详细的堆栈不一样,段错误的输出不带任何有帮助的信息,不能确定是哪行代码引起的。

faulthandler模块

看到错误立刻想起来之前看过的faulthandler模块,它是Python 3.3加入的,我尝试通过它获得堆栈。首先把下面代码写入setup.py头部:

  1. import faulthandler; faulthandler.enable()

  2. # 下面是原来的代码

  3. import codecs

  4. ...

然后运行它:

  1. python -Xfaulthandler /Users/dongweiming/ lyanna/venv/src/aiomcache/setup.py develop

  2. # 或者: PYTHONFAULTHANDLER=1 /Users/dongweiming/lyanna/venv/src/aiomcache/setup.py develop

  3. ...

  4. Fatal Python error: Segmentation fault


  5. Current thread 0x000000010fe73dc0 (most recent call first):

  6. File "", line 219 in _call_with_frames_removed

  7. File "", line 1109 in exec_module

  8. File "", line 671 in _load_unlocked

  9. File "", line 975 in _find_and_load_unlocked

  10. File "", line 991 in _find_and_load

  11. File "/Users/dongweiming/lyanna/venv/lib/python3.8/site-packages/Cython/Compiler/Main.py", line 28 in <module>

  12. File "", line 219 in _call_with_frames_removed

  13. File "", line 783 in exec_module

  14. File "", line 671 in _load_unlocked

  15. File "", line 975 in _find_and_load_unlocked

  16. ...

这次的输出可谓详细了很多,注意堆栈顺序和Python代码的相反: 引起错误的在顶部。

看了下 importlib . _bootstrap 的代码没看出来有问题,而且堆栈提到的这些是Python代码的应该不是它。不过这次的堆栈之后要考,大家看看能不能联想到什么?其实这里已经隐隐的显示了问题。

怀疑aiomcache代码兼容性问题

接着想到的第一个搜索点就是aiomcache,但是翻了下aiomcache代码,它和其依赖中没有C扩展,那应该不是,另外试了下 install 没有问题:

  1. python /Users/dongweiming/lyanna/venv/src/aiomcache/ setup.py install

  2. ... # 正常

  3. Installed /Users/dongweiming/lyanna/venv/lib/python3.8/site-packages/aiomcache-0.6.0-py3.8.egg

  4. Processing dependencies for aiomcache==0.6.0

  5. Finished processing dependencies for aiomcache==0.6.0

那可以确定不是aiomemcache,这个时候我想到:

  • 是setuptools(在setup.py中会用到)及其依赖中的C扩展(在Python3.8下)问题

  • 最新的CPython代码问题

怀疑最新的CPython代码问题

等大家看完全文会发现这里偏了,因为按照几率来说肯定是第三方的库更不靠谱一点... 不过也是合理的,想尝鲜就是非常可能遇到各种还没有人踩到的坑。此时我心中一怔,顺便给CPython改C代码的机会来的,激动!!

由于这种心情的撺掇,调试方向的天平倾向了CPython而不是看setuptools依赖链。

接着我加了DEBUG标记重新编译了CPython:

  1. ./configure CFLAGS='-DPy_DEBUG' --with-pydebug && make && make install

这次最终没有抛段错误,执行正常,很奇怪。接着我不带标记重新编译,上面的错误又重现了,不过我在调试过程发现直接用CPython源码目录下编译好的python执行正常:

  1. ~/cpython/python .exe /Users/dongweiming/lyanna/venv/src/aiomcache/setup.py develop

也就是说 用什么都没做的裸Python正常,但是在虚拟环境中的Python本来也正常,但是安装了一堆依赖后就不正常

直观上,我已经觉得和CPython 3.8的源码无关了(# ̄~ ̄#),但谁引起的段错误还不清楚。


GDB大法

对于调试Python代码,我基本上都会用print,连pdb也用的很少。如果是C接口有问题,就需要使用gdb了。另外gdb也可以用于调试正在运行的Python进程。

在macOS下使用Homebrew安装gdb:

  1. brew install gdb

不过此时GDB只有基本的Python支持,如果你自己编译GDB记得加上 -- with - python 选项。接着我们要把CPython项目中的libpython.py里面提供的命令加进来:

  1. gdb python3

  2. GNU gdb (GDB) 8.3

  3. ...

  4. (gdb) python # Python解释器

  5. >import sys # 和写Python代码一样

  6. >sys.path.insert(0, '/Users/dongweiming/cpython/Tools/gdb') # 把包含libpython.py文件的目录加到sys.path里

  7. >import libpython # 导入

  8. >end # 结束Python

  9. (gdb) py # 按Tab就可以看到支持多个py-开头的命令了

  10. py-bt py-down py-locals py-up python-interactive

  11. py-bt-full py-list py-print python

导入libpython的缺点是每次进入gdb都得执行一次,另外一个方法是写入到GDB初始化文件 . gdbinit 中:

  1. source ~/cpython/Tools/gdb/libpython.py

libpython的作用是让 PyObject * 的输出更容易理解,另外新加了上面看到的命令具体用法可以看延伸阅读链接2。

另外CPython官方也维护了一个 Misc / gdbinit 文件(见延伸阅读链接3),其中提供了pystack、pyframe、lineno等命令,它们是针对gdb 7之前的版本,我安装的是当前最新的GDB 8.3,就不需要了

这里要注意一个细节,加了配置后抛段错误前会卡住,需要在 . gdbinit 里面加这么一句:

  1. set startup-with-shell off

Ok, 配置已经完成了,现在运行这个有问题的命令:

  1. gdb python3

  2. (gdb) run /Users/dongweiming/lyanna/venv/src/aiomcache/setup.py develop

  3. Starting program: /Users/dongweiming/lyanna/venv/bin/python3 /Users/dongweiming/lyanna/venv/src/aiomcache/setup.py develop

  4. Unable to find Mach task port for process-id 8888: (os/kern) failure (0x5).

  5. (please check gdb is codesigned - see taskgated(8))

这是由于权限问题,需要给gdb签一个证书。步骤如下(也可以参考延伸阅读链接5里的内容):

  1. 启动Keychain Access应用程序

  2. 从菜单选择Certificate Assistant -> Create a Certificate

  3. 新加一个证书,名字就叫gdbcert(之后还要用到),身份类型self-signed root,证书类型Code Signing,「Let me override defaults」不用勾,然后create

  4. 创建一个XML文件(gdb.xml),内容如下:

  1. xml version="1.0" encoding="UTF-8"?>

  2. version="1.0">

  3. com.apple.security.cs.debugger

  1. 更新代码签名: codesign -- entitlements gdb . xml - fs gdbcert / usr / local / bin / gdb

现在就可以正常使用了:

  1. cd ~/cpython

  2. (gdb) run /Users/dongweiming/lyanna/venv/src/aiomcache /setup.py develop

  3. Starting program: /Users/dongweiming/lyanna/venv/bin/python3 /Users/dongweiming/lyanna/venv/src/aiomcache/setup.py develop

  4. [New Thread 0x1a03 of process 35316]

  5. [New Thread 0x2603 of process 35316]

  6. warning: unhandled dyld version (16)

  7. ...


  8. Thread 2 received signal SIGSEGV, Segmentation fault.

  9. 0x0000000100026c04 in PyCode_NewWithPosOnlyArgs (argcount=1, posonlyargcount=0, kwonlyargcount=0,

  10. nlocals =0, stacksize=1, flags=0, code=<unknown at remote 0x3>, consts=b'', names=(), varnames=(),

  11. freevars=('self',), cellvars=(), filename=(), name='Cython/Compiler/Scanning.py',

  12. firstlineno=28646256, lnotab=<unknown at remote 0x42>) at Objects/codeobject.c:121

  13. 121 code == NULL || !PyBytes_Check(code) || # 可以看到是这里引起的段错误

  14. (gdb) bt # 回溯堆栈。frame0是栈顶,id越大顺序越靠前,也就是说PyCode_New里面调用了PyCode_NewWithPosOnlyArgs最终引发段错误

  15. #0 0x0000000100026c04 in PyCode_NewWithPosOnlyArgs (argcount=1, posonlyargcount=0, kwonlyargcount=0,

  16. nlocals=0, stacksize=1, flags=0, code=< unknown at remote 0x3>, consts=b'', names=(), varnames=(),

  17. freevars=('self',), cellvars=(), filename=(), name='Cython/Compiler/Scanning.py',

  18. firstlineno=28646256, lnotab=<unknown at remote 0x42>) at Objects/codeobject.c:121

  19. #1 0x00000001000273f7 in PyCode_New (argcount=1, kwonlyargcount=3, nlocals=25499040, stacksize=1,

  20. flags=0, code=0x0, consts=b'', names=(), varnames=(), freevars=('self',), cellvars=(), filename=(),

  21. name='Cython/Compiler/Scanning.py', firstlineno=28646256, lnotab=<unknown at remote 0x42>)

  22. at Objects/codeobject.c:253

  23. #2 0x0000000103b84fe2 in ?? ()

  24. #3 0x00000001018515a0 in ?? ()

  25. #4 0x00000001004b6040 in ?? ()

  26. #5 0x00000001004b6040 in ?? ()

  27. #6 0x0000000103b4c070 in ?? ()

  28. ...

  29. (gdb) frame 0 # 切换到栈顶

  30. ...

  31. (gdb) list # 查看栈顶的代码,注意121行

  32. 116

  33. 117 /* Check argument types */

  34. 118 if (argcount < posonlyargcount || posonlyargcount < 0 ||

  35. 119 kwonlyargcount < 0 || nlocals < 0 ||

  36. 120 stacksize < 0 || flags < 0 ||

  37. 121 code == NULL || !PyBytes_Check(code) || 相关的是code这个参数

  38. 122 consts == NULL || !PyTuple_Check(consts) ||

  39. 123 names == NULL || !PyTuple_Check(names) ||

  40. 124 varnames == NULL || !PyTuple_Check(varnames) ||

  41. 125 freevars == NULL || !PyTuple_Check(freevars) ||

  42. (gdb) frame 0 # 切回栈顶,这个输出内容很多,由于libpython的作用,参数的值更明确了

  43. #0 0x0000000100026c04 in PyCode_NewWithPosOnlyArgs (argcount=1, posonlyargcount=0, kwonlyargcount=0,

  44. nlocals=0, stacksize=1, flags= 0, code=<unknown at remote 0x3>, consts=b'', names=(), varnames=(),

  45. freevars=('self',), cellvars=(), filename=(), name='Cython/Compiler/Scanning.py',

  46. firstlineno=28646256, lnotab=<unknown at remote 0x42>) at Objects/codeobject.c:121

  47. 121 code == NULL || !PyBytes_Check(code) ||

  48. (gdb) p code

  49. $1 = <unknown at remote 0x3>

  50. # 可以看到大部分参数是Python对象,但是要注意其中code的参数看起来是一个很怪的内存地址,看一下下个栈:

  51. (gdb) up

  52. #1 0x00000001000273f7 in PyCode_New (argcount=1, kwonlyargcount=3, nlocals=25499040, stacksize=1,

  53. flags=0, code=0x0 , consts=b'', names=(), varnames=(), freevars=('self',), cellvars=(), filename=(),

  54. name='Cython/Compiler/Scanning.py', firstlineno=28646256, lnotab=<unknown at remote 0x42>)

  55. at Objects/codeobject.c:253

  56. 253 return PyCode_NewWithPosOnlyArgs(argcount, 0, kwonlyargcount, nlocals,

  57. # 注意code的值0x0,应该是这个内存地址有问题。

注意code的值0x0,应该是这个内存地址有问题,因为它不像stacksize、flags、consts等参数那样可读。不过到这里卡住了,我循着frame又向前看了一遍,没看出来CPython的问题

sys.settrace

既然CPython这方面没有收获,换个思路,用 sys . settrace 试试把整个执行代码的过程都打印出来。在setup.py里面加入下面代码:

  1. import sys


  2. def trace(frame, event, arg):

  3. print("%s, %s:%d" % (event, frame.f_code.co_filename, frame.f_lineno))

  4. return trace


  5. sys.settrace(trace)

  6. # 下面是原来的代码

  7. import codecs #

  8. ...

然后执行:

  1. python /Users/dongweiming/lyanna/venv/src/aiomcache/setup.py develop # 执行时间略长,等1分钟

  2. ... # 输出非常长,省略

  3. line, /Users/dongweiming/lyanna/venv/lib/python3.8/site-packages/Cython/Compiler/Main.py:28

  4. ...

  5. line, <frozen importlib._bootstrap>:671

  6. call, <frozen importlib._bootstrap_external>:1107

  7. line, <frozen importlib._bootstrap_external>:1109

  8. call, <frozen importlib._bootstrap>:211

  9. line, <frozen importlib._bootstrap>:219

  10. [1] 7125 segmentation fault python /Users/dongweiming/lyanna/venv/src/aiomcache/setup.py develop

大家还记得一开始用faulthandler模块输出的堆栈么?这个要具体的多,不过我由捕捉到了Cython这部分,这是出现错误前最后一个出现的和C有关的模块。

我突然来了灵感: 「是不是什么Python 3.8的修改对Cython的逻辑有影响?」Python3.8已经上了一段时间,相信有别人已经用了一段时间了,不过去Cython项目下没找到对应issue,因为这个不能明确的对应到是Cython引起的。我就顺手升级下Cython试试:

  1. pip freeze |grep -i cython

  2. Cython== 0.29.13


  3. pip install -U cython

  4. ...

  5. Successfully installed cython-0.29.14

0.29 . 13 升到了 0.29 . 14 ,去掉 sys . settrace 部分代码再试一下:

  1. python /Users/dongweiming/lyanna/venv/src/aiomcache/setup.py develop

  2. ...

  3. Finished processing dependencies for aiomcache==0.6.0

Emm, 正常了 O(∩_∩)O!之后我翻了下这次cython更新的改变,但由于改动太多还是没有找到具体的问题,另外一个让我疑惑的是,用另外一个电脑没有过问题。不过问题算是解决了,先这样。

别急,还有后续

现在依赖都装好了,启动应用:

  1. python app.py

  2. [1] 10396 segmentation fault python app.py

心情down到了谷底。用gdb看还是类似的错误,不过换用faulthandler后看到了新的问题:

  1. python app.py

  2. Fatal Python error: Segmentation fault


  3. Current thread 0x000000010b4e3dc0 (most recent call first):

  4. ...

  5. File "/Users/dongweiming/lyanna/venv/lib/python3.8/site-packages/yaml/cyaml.py", line 7 in <module>

  6. ...

  7. [1] 12363 segmentation fault python app.py

也就是yaml有问题了。按之前的套路,先升级看看:

  1. pip freeze |grep -i yaml

  2. PyYAML==5.1.2


  3. pip install -U PyYAML

  4. ...

  5. Successfully installed PyYAML-5.2

再启动:

  1. python app.py

  2. [2019-12-11 13:19:03 +0800] [14690] [DEBUG]


  3. Sanic

  4. Build Fast. Run Fast.



  5. [2019-12-11 13:19:03 +0800] [14690] [INFO] Goin' Fast @ http://0.0.0.0:8000

还没有结束

2个段错误都以升级包而解决,不过这也太巧了吧?此时我怀疑是pip cache引起的,很早前我就在编译安装Python3.8,所以我在想是不是由于pip缓存的缘故,使用了之前编译的包,而距离上次编译之后Python3.8又做了不兼容的修改?所以我做了如下尝试:

  1. pip install cython==0.29.13 # 安装回之前有问题的包

  2. Processing /Users/dongweiming/Library/Caches/pip/wheels/5b/1e/17/15dd6d435ec0644967eb8e1493af09ae28cd94184c6f416d02/Cython-0.29.13-cp38-cp38-macosx_10_14_x86_64.whl # 注意这句,用了缓存

  3. python /Users/dongweiming/lyanna/venv/src/aiomcache/setup.py develop # 还是会抛出段错误

  4. ...

  5. [1] 33909 segmentation fault python /Users/dongweiming/lyanna/venv/src/aiomcache/setup.py develop

  6. rm /Users/dongweiming/Library/Caches/pip/wheels/5b/1e/17/ 15dd6d435ec0644967eb8e1493af09ae28cd94184c6f416d02/Cython-0.29.13-cp38-cp38-macosx_10_14_x86_64.whl # 删除本地编译好的包

  7. pip uninstall cython # 卸载Cython-0.29.13

  8. pip install cython==0.29.13 # 重新安装

  9. python /Users/dongweiming/lyanna/venv/src/aiomcache/setup.py develop

  10. ...

  11. Finished processing dependencies for aiomcache==0.6.0

这次一切正常,猜对了,这也解释了为什么用另外一个电脑不能复现。不过等等,我突然意识到了问题:

  1. sw_vers -productVersion

  2. 10.15.1

什么意思呢? 我最近升级了系统到Catalina 10.15.1,但是安装包时会使用10.14时编译的包

搜了下本地有错误的whl包:

  1. find /Users/dongweiming/Library/Caches/pip/wheels -name "*cp38-cp38-macosx_10_14_x86_64.whl"

  2. /Users/dongweiming/Library/Caches/pip/wheels/3b/13/4a/e6b972b23b1d7fca074f2ef681b3c819123b842cf1b4de8627/ciso8601-2.1.2-cp38-cp38-macosx_10_14_x86_64.whl

  3. /Users/dongweiming/Library/Caches/pip/wheels/f2/aa/04/0edf07a1b8a5f5f1aed7580fffb69ce8972edc16a505916a77/MarkupSafe-1.1.1-cp38-cp38-macosx_10_14_x86_64.whl

  4. /Users/dongweiming/Library/Caches/pip/wheels/28/77/e4/0311145b9c2e2f01470e744855131f9e34d6919687550f87d1/ujson-1.35-cp38-cp38-macosx_10_14_x86_64.whl

虽然这次没因为他们抛出段错误,不过正好可以当做调试pip的badcase 罒ω罒

虽然没给CPython贡献代码,给pip贡献一下也好,具体PR是: https://github.com/pypa/pip/pull/7466

延伸阅读

  1. https://en.wikipedia.org/wiki/Segmentation_fault

  2. https://devguide.python.org/gdb/

  3. https://github.com/python/cpython/blob/master/Misc/gdbinit

  4. https://sourceware.org/gdb/wiki/BuildingOnDarwin

  5. https://sourceware.org/gdb/wiki/PermissionsDarwin

  6. https://github.com/pypa/pip/pull/7466








请到「今天看啥」查看全文


推荐文章
GQ实验室  ·  进击!未来清尘新英雄!
7 年前
红楼梦学刊  ·  《红楼梦学刊》2017年第4辑目录
7 年前
一本财经  ·  “任性王子”李彦宏的救赎
7 年前