在检查/反汇编代码对象时,动态地递归跟随可调用对象

时间:2012-11-13 18:02:18

标签: python

我想对任何函数/方法进行一些内省。对于我的所有示例,我使用的是Python 2.7,但是使用3.3并不是一个问题,如果它使事情变得更容易。

假设我在名为foobar.py的模块中有以下代码:

def foo():
    bar()

我可以动态地看到foo运行的代码:

import inspect
import foobar
inspect.getsource(foobar.foo)

我还可以使用以下函数从该函数的代码对象中获取反汇编的字节码:

import dis
dis.dis(foobar.foo)

有没有办法可以检测到foo方法调用另一个函数(在这种情况下是bar),然后动态地反汇编/检查它?

我知道代码对象本身具有以下所有类型的属性:

>>> dir(foobar.foo.__code__)
['__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_stacksize', 'co_varnames']

我检查过他们中的大多数只是环顾四周,但还没找到我想要的东西。

最终目标只是一个小实验,看看我是否可以在不执行除导入之外的代码的情况下以递归方式打印出可能的调用堆栈。我知道理论上的调用堆栈不能解释运行时的事情,比如特定变量的状态等等。我只想在给定某个调用的情况下打印掉所有嵌套函数的源代码(即使代码永远不会执行基于运行状态)。

另外,我知道一旦进入CPython代码,inspectdis模块就无法提供帮助。最终打印出某种类型的映射可能会很有趣,这种映射显示了inspectdis分解时它到达的CPython代码。但是,我甚至不确定这是否可能。

1 个答案:

答案 0 :(得分:1)

所有编译器/解释器在解析源时都会构建一个抽象语法树。这是基于其上下文无关语法的程序的表示,然后可以递归地遍历该程序以生成可由机器执行的代码。

Python提供对其AST的访问权限,您可以自己遍历此树并查找ast.Call内的ast.FunctionDef个对象。下面粘贴一个简单的例子。请注意,这肯定不会捕获所有可能的调用,因为调用可以嵌入到其他表达式中,由eval表达式隐藏等等。这是一个简单的例子:

import ast

source = """
def foo():
    bar()

def bar():
    baz()

def baz():
    print "hello!"
"""

def get_method_name_for_call(call_obj):
    for fieldname, value in ast.iter_fields(call_obj):
        if fieldname == "func":
            return value.id

def walk_method_calls(node, cur_func):
    if not node:
        return

    for cur_node in ast.iter_child_nodes(node):
        if type(cur_node) == ast.Call:
            method_called = get_method_name_for_call(cur_node)
            print "Found a call to %s in body of %s." % (method_called, cur_func)
        walk_method_calls(cur_node, cur_func)


def walk_function_defs(node):
    if not node:
        return

    for cur_node in ast.iter_child_nodes(node):
        if type(cur_node) == ast.FunctionDef:
            walk_method_calls(cur_node, cur_node.name)

# we pass <string> as a recognizable identifier since
# we have no filename
ast_root = compile(source, "<string>", "exec", ast.PyCF_ONLY_AST)
walk_function_defs(ast_root)

一个执行示例:

$ python abstract_tree.py 
Found a call to bar in body of foo.
Found a call to baz in body of bar.