以简单的方式使用GDB调试Python程序

先决条件

本文演示环境

# python版本: 2.7
# python内带GDB版本: 2.7  影响使用自定义的Python命令
$ gdb  # 进入GDB环境
(gdb) python-interactive  # 进入GDB内带的Python环境
>>> import sys
>>> sys.version  # 查看内带Python版本 如果需要使用其它Python版本需要自行编译GDB

# 或者
$ readelf -d $(which gdb) | grep python
 0x0000000000000001 (NEEDED)             Shared library: [libpython3.5m.so.1.0]  # Python3.5
  1. 安装 Python debugging extensions:
    apt-get install python2.7-dbg
  2. 将下面代码保存为 pyfields.py (如果是Python3需要自行修改), 之后将使用py-fields命令查看成员变量
    class PyPrintFields(gdb.Command):
        'Look up the given python variable name, and print it'
        def __init__(self):
            gdb.Command.__init__ (self, "py-fields", gdb.COMMAND_DATA, gdb.COMPLETE_NONE)
    
        def invoke(self, args, from_tty):
            name = str(args)
    
            frame = Frame.get_selected_python_frame()
            if not frame:
                print 'Unable to locate python frame'
                return
    
            pyop_frame = frame.get_pyop()
            if not pyop_frame:
                print 'Unable to read information on python frame'
                return
    
            def get_var_dict(v):
                if isinstance(v, PyInstanceObjectPtr):
                    return v.pyop_field('in_dict')
                elif isinstance(v, HeapTypeObjectPtr):
                    return v.get_attr_dict()
                else:
                    return {}
            parent = None
            fields = name.split('.')
            for name in fields:
                if parent is None:
                    pyop_var, scope = pyop_frame.get_var_by_name(name)
                else:
                    for k, pyop_var in get_var_dict(parent).iteritems():
                        if str(k) == name:
                            break
                    else:
                        print "Can't find field %s of %s" % (name, parent)
                        return
                parent = pyop_var
            if pyop_var:
                print '%s %r = %s' % (scope, name, pyop_var.get_truncated_repr(MAX_OUTPUT_LEN))
            else:
                print '%r not found' % name
    
    PyPrintFields()
    

调试 Python 进程

  • 测试Python代码test.py
    # -*- coding:utf-8 -*-
    
    class C(object):
        def __init__(self):
            super(C, self).__init__()
            self.var1 = 123
            self.var2 = 'abc'
            self.var3 = ['A', 1, 'B', 2]
            self.var4 = {'A': 1, 'B': 2}
    
        def func(self, value):
            while True:
                pass
    
    
    def func():
        c = C()
        c.func('value')
    
    
    if __name__ == '__main__':
        func()
    
  1. 通过 GDB 启动 Python 后再运行待调试 Python 代码
    $ gdb python
    (gdb) run test.py  # 运行后, 按Ctrl+C将停在当前执行位置
    
  2. (或)通过 GDB 调试已在运行中的进程
    $ gdb python
    (gdb) attach PID  # 运行中的进程将暂停, 按c继续
    
  3. 查找当前 GDB 所支持的 Python 调试命令: 输入 py 后双击 Tab
    (gdb) py
    py-bt               py-down             py-locals           py-up               python-interactive
    py-bt-full          py-list             py-print            python
    
  4. 查看当前的 Python 执行栈: py-bt / py-bt-full
    (gdb) py-bt
    Traceback (most recent call first):
        File "test.py", line 12, in func
            while True:
        File "test.py", line 18, in func
            c.func('value')
        File "test.py", line 22, in <module>
            func()
    
    (gdb) py-bt-full
    #1 Frame 0x7ffc4bc02aa0, for file test.py, line 12, in func (self=<C(var4={'A': 1, 'B': 2}, var1=123, var3=['A', 1, 'B', 2], var2='abc') at remote 0x7ffc4bbbff10>, value='value')
        while True:
    #4 Frame 0x7ffc4bcf73c0, for file test.py, line 18, in func (c=<C(var4={'A': 1, 'B': 2}, var1=123, var3=['A', 1, 'B', 2], var2='abc') at remote 0x7ffc4bbbff10>)
        c.func('value')
    #7 Frame 0x7ffc4bcf7050, for file test.py, line 22, in <module> ()
        func()
    
  5. 在 Python 栈间移动: py-up / py-down

  6. 查看当前正在执行的 Python 源码: py-list (行号前以 > 标识)

    (gdb) py-list
    7            self.var2 = 'abc'
    8            self.var3 = ['A', 1, 'B', 2]
    9            self.var4 = {'A': 1, 'B': 2}
    10
    11        def func(self, value):
    >12            while True:
    13                pass
    14
    15
    16    def func():
    17        c = C()
    
  7. 查看当前作用域的变量: py-locals / py-print
    (gdb) py-locals
    self = <C(var4={'A': 1, 'B': 2}, var1=123, var3=['A', 1, 'B', 2], var2='abc') at remote 0x7ffc4bbbff10>
    value = 'value'
    
    (gdb) py-print self
    local 'self' = <C(var4={'A': 1, 'B': 2}, var1=123, var3=['A', 1, 'B', 2], var2='abc') at remote 0x7ffc4bbbff10>
    
  8. 尝试查看实例的成员py-print 无法正确识别 实例的成员变量
    (gdb) py-print self.var3
    'self.var3' not found
    
  9. 引入自己实现的命令 py-fields(gdb) source pyfields.py, py-fields 查看成员
    (gdb) py-fields self.var3
    local 'var3' = ['A', 1, 'B', 2]
    

更多

  • 基于 gdb.Command 可以构建更多自己的调试命令
  • 使用 (gdb) python-interactive 进入交互后可以查看 gdb / gdb.Command 下的更多信息

  • 如果使用--with-pydebug编译的带Debug信息的Python,那直接使用bt命令就可以查看较详细的栈信息

    (gdb) bt
    #0  0x00000000004585f3 in lookdict_string (mp=0x7fffff71ebe8, key='True', hash=-1504860438477502276) at Objects/dictobject.c:414
    #1  0x00000000004d68a8 in PyEval_EvalFrameEx (
        f=Frame 0x7fffff521060, for file test.py, line 12, in func (self=<C(var4={'A': 1, 'B': 2}, var1=123, var3=['A', 1, 'B', 2], var2='abc') at remote 0x7fffff523220>, value='value'), throwflag=0) at Python/ceval.c:2362
    #2  0x00000000004dfdf6 in fast_function (func=<function at remote 0x7fffff5916f0>, pp_stack=0x7ffffffddad0, n=2, na=2, nk=0) at Python/ceval.c:4442
    #3  0x00000000004dfb01 in call_function (pp_stack=0x7ffffffddad0, oparg=1) at Python/ceval.c:4377
    #4  0x00000000004da194 in PyEval_EvalFrameEx (
        f=Frame 0x7fffff6dc430, for file test.py, line 18, in func (c=<C(var4={'A': 1, 'B': 2}, var1=123, var3=['A', 1, 'B', 2], var2='abc') at remote 0x7fffff523220>), throwflag=0) at Python/ceval.c:2994
    #5  0x00000000004dfdf6 in fast_function (func=<function at remote 0x7fffff5915a0>, pp_stack=0x7ffffffddf40, n=0, na=0, nk=0) at Python/ceval.c:4442
    #6  0x00000000004dfb01 in call_function (pp_stack=0x7ffffffddf40, oparg=0) at Python/ceval.c:4377
    #7  0x00000000004da194 in PyEval_EvalFrameEx (f=Frame 0x7fffff6dc060, for file test.py, line 22, in <module> (), throwflag=0)
        at Python/ceval.c:2994
    #8  0x00000000004dd070 in PyEval_EvalCodeEx (co=0x7fffff578ca0,
        globals={'C': <type at remote 0x8b6720>, '__builtins__': <module at remote 0x7fffff7114d8>, '__file__': test.py', '__package__': None, 'func': <function at remote 0x7fffff5915a0>, '__name__': '__main__', '__doc__': None},
        locals={'C': <type at remote 0x8b6720>, '__builtins__': <module at remote 0x7fffff7114d8>, '__file__': 'test.py', '__package__': None, 'func': <function at remote 0x7fffff5915a0>, '__name__': '__main__', '__doc__': None}, args=0x0, argcount=0, kws=0x0, kwcount=0, defs=0x0, defcount=0,
        closure=0x0) at Python/ceval.c:3589
    #9  0x00000000004cf66f in PyEval_EvalCode (co=0x7fffff578ca0,
        globals={'C': <type at remote 0x8b6720>, '__builtins__': <module at remote 0x7fffff7114d8>, '__file__': 'test.py', '__package__': None, 'func': <function at remote 0x7fffff5915a0>, '__name__': '__main__', '__doc__': None},
        locals={'C': <type at remote 0x8b6720>, '__builtins__': <module at remote 0x7fffff7114d8>, '__file__': 'test.py', '__package__': None, 'func': <function at remote 0x7fffff5915a0>, '__name__': '__main__', '__doc__': None}) at Python/ceval.c:669
    #10 0x00000000005110e9 in run_mod (mod=0x902eb0, filename=0x7ffffffde894 "test.py",
        globals={'C': <type at remote 0x8b6720>, '__builtins__': <module at remote 0x7fffff7114d8>, '__file__': 'test.py', '__package__': None, 'func': <function at remote 0x7fffff5915a0>, '__name__': '__main__', '__doc__': None},
    ---Type <return> to continue, or q <return> to quit---
    

参考

发表评论

电子邮件地址不会被公开。