Python Pdb源码解析
Posted 阿里巴巴淘系技术团队官网博客
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python Pdb源码解析相关的知识,希望对你有一定的参考价值。
经常使用Python的同学一定熟悉pdb模块,它是Python官方标准库提供的交互式代码调试器,和任何一门语言提供的调试能力一样,pdb提供了源代码行级别的设置断点、单步执行等常规调试能力,是Python开发的一个很重要的工具模块。
pdb使用方法见官方文档,本文重点分析官方pdb模块源码,介绍调试功能的实现原理。
原理
从cPython源码中可以看到,pdb模块并非c实现的内置模块,而是纯Python实现和封装的模块。核心文件是pdb.py,它继承自bdb和cmd模块:
class Pdb(bdb.Bdb, cmd.Cmd):
...
基本原理:利用cmd模块定义和实现一系列的调试命令的交互式输入,基于sys.settrace插桩跟踪代码运行的栈帧,针对不同的调试命令控制代码的运行和断点状态,并向控制台输出对应的信息。
cmd模块主要是提供一个控制台的命令交互能力,通过raw_input/readline这些阻塞的方法实现输入等待,然后将命令交给子类处理决定是否继续循环输入下去,就和他主要的方法名runloop一样。
cmd是一个常用的模块,并非为pdb专门设计的,pdb使用了cmd的框架从而实现了交互式自定义调试。
bdb提供了调试的核心框架,依赖sys.settrace进行代码的单步运行跟踪,然后分发对应的事件(call/line/return/exception)交给子类(pdb)处理。bdb的核心逻辑在对于调试命令的中断控制,比如输入一个单步运行的”s“命令,决定是否需要继续跟踪运行还是中断等待交互输入,中断到哪一帧等。
基本流程
pdb启动,当前frame绑定跟踪函数trace_dispatch
def trace_dispatch(self, frame, event, arg):
if self.quitting:
return # None
if event == 'line':
return self.dispatch_line(frame)
if event == 'call':
return self.dispatch_call(frame, arg)
if event == 'return':
return self.dispatch_return(frame, arg)
if event == 'exception':
...
每一帧的不同事件的处理都会经过中断控制逻辑,主要是stop_here(line事件还会经过break_here)函数,处理后决定代码是否中断,需要中断到哪一行。
如需要中断,触发子类方法user_#event,子类通过interaction实现栈帧信息更新,并在控制台打印对应的信息,然后执行cmdloop让控制台处于等待交互输入。
def interaction(self, frame, traceback):
self.setup(frame, traceback) # 当前栈、frame、local vars
self.print_stack_entry(self.stack[self.curindex])
self.cmdloop()
self.forget()
用户输入调试命令如“next”并回车,首先会调用set_#命令,对stopframe、returnframe、stoplineno进行设置,它会影响中断控制```stop_here``的逻辑,从而决定运行到下一帧的中断结果。
def _set_stopinfo(self, stopframe, returnframe, stoplineno=0):
self.stopframe = stopframe
self.returnframe = returnframe
self.quitting = 0
# stoplineno >= 0 means: stop at line >= the stoplineno
# stoplineno -1 means: don't stop at all
self.stoplineno = stoplineno
对于调试过程控制类的命令,一般do_#命令都会返回1,这样本次runloop立马结束,下次运行到某一帧触发中断会再次启动runloop(见第三点);对于信息获取类的命令,do_#命令都没有返回值,保持当前的中断状态。
代码运行到下一帧,重复第三点。
中断控制
中断控制也就是对于不同的调试命令输入后,能让代码执行到正确的位置停止,等待用户输入,比如输入”s”控制台就应该在下一个运行frame的代码处停止,而输出“c”就需要运行到下一个打断点的地方。
中断控制发生在sys.settrace的每一步跟踪的中,是调试运行的核心逻辑。
pdb中主要跟踪了frame的四个事件:
line:同一个frame中的顺序执行事件
call:发生函数调用,跳到下一级的frame中,在函数第一行产生call事件
return:函数执行完最后一行(line),发生结果返回,即将跳出当前frame回到上一级frame,在函数最后一行产生return事件
exception:函数执行中发生异常,在异常行产生exception事件,然后在该行返回(return事件),接下来一级一级向上在frame中产生exception和return事件,直到回到底层frame。
它们是代码跟踪时的不同节点类型,pdb根据用户输入的调试命令,在每一步frame跟踪时都会进行中断控制,决定接下来是否中断,中断到哪一行。中断控制的主要方法是stop_here:
def stop_here(self, frame):
# (CT) stopframe may now also be None, see dispatch_call.
# (CT) the former test for None is therefore removed from here.
if self.skip and \\
self.is_skipped_module(frame.f_globals.get('__name__')):
return False
# next
if frame is self.stopframe:
# stoplineno >= 0 means: stop at line >= the stoplineno
# stoplineno -1 means: don't stop at all
if self.stoplineno == -1:
return False
return frame.f_lineno >= self.stoplineno
# step:当前只要追溯到botframe,就等待执行。
while frame is not None and frame is not self.stopframe:
if frame is self.botframe:
return True
frame = frame.f_back
return False
调试命令大体上分两类:
过程控制:如setp、next、continue等这些执行后马上进入下阶段的代码执行
信息获取/设置:如args、p、list等获取当前信息的,也不会影响cmd状态
以下重点讲解几个最常见的用于过程控制的调试命令的中断控制实现原理:
▐ s(step)
-
命令定义
以上是关于Python Pdb源码解析的主要内容,如果未能解决你的问题,请参考以下文章