2.3.4 用gdb除错段错误(segmentation faults)

如果有段错误,你不可以用pdb对它进行除错,因为在进入除错器之前,它会让Python解释器崩溃。同样的,如果在嵌入Python的C代码中有一个bug,pdb也是没用的。对于这种情况,我们可以转用gnu除错器,gdb,在Linux可用。

在我们开始使用gdb之前,让我们为它添加一些Python专有的工具。对于这个情况我们可以添加一些宏到我们的~/.gbdinit。宏的最优的选择取决于你的Python版本和gdb版本。我在gdbint添加了一个简单的版本,但是别客气读一下DebuggingWithGdb

要用gdb来除错Python脚本segfault.py, 我们可以想如下这样在gdb中运行这个脚本

  1. $ gdb python
  2. ...
  3. (gdb) run segfault.py
  4. Starting program: /usr/bin/python segfault.py
  5. [Thread debugging using libthread_db enabled]
  6. Program received signal SIGSEGV, Segmentation fault.
  7. _strided_byte_copy (dst=0x8537478 "\360\343G", outstrides=4, src=
  8. 0x86c0690 <Address 0x86c0690 out of bounds>, instrides=32, N=3,
  9. elsize=4)
  10. at numpy/core/src/multiarray/ctors.c:365
  11. 365 _FAST_MOVE(Int32);
  12. (gdb)

我们得到了一个segfault, gdb捕捉到它在C级栈(并不是Python调用栈)中进行事后剖析除错。我们可以用gdb的命令来对C调用栈进行除错:

  1. (gdb) up
  2. #1 0x004af4f5 in _copy_from_same_shape (dest=<value optimized out>,
  3. src=<value optimized out>, myfunc=0x496780 <_strided_byte_copy>,
  4. swap=0)
  5. at numpy/core/src/multiarray/ctors.c:748
  6. 748 myfunc(dit->dataptr, dest->strides[maxaxis],

如你所见,现在,我们numpy的C代码中。我们想要知道哪个Python代码触发了这个segfault,因此,在栈上向上搜寻,直到我们达到Python执行循环:

  1. (gdb) up
  2. #8 0x080ddd23 in call_function (f=
  3. Frame 0x85371ec, for file /home/varoquau/usr/lib/python2.6/site-packages/numpy/core/arrayprint.py, line 156, in _leading_trailing (a=<numpy.ndarray at remote 0x85371b0>, _nc=<module at remote 0xb7f93a64>), throwflag=0)
  4. at ../Python/ceval.c:3750
  5. 3750 ../Python/ceval.c: No such file or directory.
  6. in ../Python/ceval.c
  7. (gdb) up
  8. #9 PyEval_EvalFrameEx (f=
  9. Frame 0x85371ec, for file /home/varoquau/usr/lib/python2.6/site-packages/numpy/core/arrayprint.py, line 156, in _leading_trailing (a=<numpy.ndarray at remote 0x85371b0>, _nc=<module at remote 0xb7f93a64>), throwflag=0)
  10. at ../Python/ceval.c:2412
  11. 2412 in ../Python/ceval.c
  12. (gdb)

一旦我们进入了Python执行循环,我们可以使用特殊的Python帮助函数。例如我们可以找到对应的Python代码:

  1. (gdb) pyframe
  2. /home/varoquau/usr/lib/python2.6/site-packages/numpy/core/arrayprint.py (158): _leading_trailing
  3. (gdb)

这是numpy代码,我们需要向上走直到找到我们写的代码:

  1. (gdb) up
  2. ...
  3. (gdb) up
  4. #34 0x080dc97a in PyEval_EvalFrameEx (f=
  5. Frame 0x82f064c, for file segfault.py, line 11, in print_big_array (small_array=<numpy.ndarray at remote 0x853ecf0>, big_array=<numpy.ndarray at remote 0x853ed20>), throwflag=0) at ../Python/ceval.c:1630
  6. 1630 ../Python/ceval.c: No such file or directory.
  7. in ../Python/ceval.c
  8. (gdb) pyframe
  9. segfault.py (12): print_big_array

对应代码是:

In [1]:

  1. def make_big_array(small_array):
  2. big_array = stride_tricks.as_strided(small_array,
  3. shape=(2e6, 2e6), strides=(32, 32))
  4. return big_array
  5. def print_big_array(small_array):
  6. big_array = make_big_array(small_array)

这样segfault在打印big_array[-10:]时发生。原因非常简单,big_array被分配到程序内存以外。

笔记:对于在gdbinit中定义的Python特有命令,读一下这个文件的源代码。

总结练习

下面的脚本是详细而清晰的。它想要回答一个实际的有趣数值计算,但是,它并不起作用…你可以为它除错吗?

Python源代码:to_debug.py

In [ ]:

  1. """
  2. A script to compare different root-finding algorithms.
  3. This version of the script is buggy and does not execute. It is your task
  4. to find an fix these bugs.
  5. The output of the script sould look like:
  6. Benching 1D root-finder optimizers from scipy.optimize:
  7. brenth: 604678 total function calls
  8. brentq: 594454 total function calls
  9. ridder: 778394 total function calls
  10. bisect: 2148380 total function calls
  11. """
  12. from itertools import product
  13. import numpy as np
  14. from scipy import optimize
  15. FUNCTIONS = (np.tan, # Dilating map
  16. np.tanh, # Contracting map
  17. lambda x: x**3 + 1e-4*x, # Almost null gradient at the root
  18. lambda x: x+np.sin(2*x), # Non monotonous function
  19. lambda x: 1.1*x+np.sin(4*x), # Fonction with several local maxima
  20. )
  21. OPTIMIZERS = (optimize.brenth, optimize.brentq, optimize.ridder,
  22. optimize.bisect)
  23. def apply_optimizer(optimizer, func, a, b):
  24. """ Return the number of function calls given an root-finding optimizer,
  25. a function and upper and lower bounds.
  26. """
  27. return optimizer(func, a, b, full_output=True)[1].function_calls,
  28. def bench_optimizer(optimizer, param_grid):
  29. """ Find roots for all the functions, and upper and lower bounds
  30. given and return the total number of function calls.
  31. """
  32. return sum(apply_optimizer(optimizer, func, a, b)
  33. for func, a, b in param_grid)
  34. def compare_optimizers(optimizers):
  35. """ Compare all the optimizers given on a grid of a few different
  36. functions all admitting a signle root in zero and a upper and
  37. lower bounds.
  38. """
  39. random_a = -1.3 + np.random.random(size=100)
  40. random_b = .3 + np.random.random(size=100)
  41. param_grid = product(FUNCTIONS, random_a, random_b)
  42. print "Benching 1D root-finder optimizers from scipy.optimize:"
  43. for optimizer in OPTIMIZERS:
  44. print '% 20s: % 8i total function calls' % (
  45. optimizer.__name__,
  46. bench_optimizer(optimizer, param_grid)
  47. )
  48. if __name__ == '__main__':
  49. compare_optimizers(OPTIMIZERS)

In [1]:

  1. %matplotlib inline
  2. import numpy as np