首先,只是一个关于“什么是co_code
”的小提醒。
在 Python 中,语言的每个元素(函数、方法、类等)都被定义并存储在一个对象中。的co_code
是连接到用于表示函数或方法的类中的一个字段。让我们用 Python 2.7 练习一下。
$> python2.7
Python 2.7.3 (default, Mar 4 2013, 14:57:34)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> def foo():
... print('Hello World!')
...
>>> dir(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']
>>> foo.__code__.co_code
'd\x01\x00GHd\x00\x00S'
因此,您可以看到该co_code
字段包含我们之前定义的函数的编译字节码。事实上,它似乎co_code
只是一个缓冲区,以懒惰的方式存储编译后的字节码。只有在第一次访问时才编译它。
假设这样,这co_code
只是一个统一的帮助程序来访问可能以多种形式存储的字节码。一种形式是*.pyc
存储整个文件的已编译 Python 字节码的文件。另一种形式只是函数/方法的即时编译。
然而,有一种方法可以直接访问函数/方法定义,从而访问字节码。关键是拦截 Python 进程gdb
并对其进行分析。网络上有一些关于此的教程(请参阅此处、此处、此处或此处)。但是,这是一个快速示例(您需要先安装python-gdb
软件包):
$> python2.7-dbg
Python 2.7.3 (default, Mar 4 2013, 14:27:19)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> def foo():
... print('Hello World!')
...
[40809 refs]
>>> foo
<function foo at 0x1a5e1b0>
[40811 refs]
>>> foo.__code__.co_code
'd\x01\x00GHd\x00\x00S'
[40811 refs]
>>>
[1]+ Stopped python2.7-dbg
然后,您需要获取 Python 进程的 PID 并附加gdb
在其上。
$ gdb -p 5164
GNU gdb (GDB) 7.4.1-debian
...
Attaching to process 5164
Program received signal SIGTSTP, Stopped (user).
Reading symbols from /usr/bin/python2.7-dbg...done.
Reading symbols from /lib/x86_64-linux-gnu/libpthread.so.0...
Reading symbols from /usr/lib/debug/lib/x86_64-linux-gnu/libpthread-2.13.so...done.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".done.
...
(gdb) print *(PyFunctionObject*)0x1a5e1b0
$1 = {_ob_next = 0x187aca0, _ob_prev = 0x189dd08, ob_refcnt = 2,
ob_type = 0x87ce00, func_code = <code at remote 0x187aca0>,
func_globals = {'__builtins__': <module at remote 0x7f5ebcb5e470>,
'__name__': '__main__', 'foo': <function at remote 0x1a5e1b0>, '__doc__': None,
'__package__': None}, func_defaults = 0x0, func_closure = 0x0, func_doc = None,
func_name = 'foo', func_dict = 0x0, func_weakreflist = 0x0,
func_module = '__main__'}
(gdb) print (*(PyFunctionObject*)0x1a5e1b0)->func_name
$2 = 'foo'
(gdb) print (*(PyCodeObject*)0x187aca0)
$3 = {_ob_next = 0x18983a8, _ob_prev = 0x1a5e1b0, ob_refcnt = 1, ob_type = 0x872680,
co_argcount = 0, co_nlocals = 0, co_stacksize = 1, co_flags = 67,
co_code = 'd\x01\x00GHd\x00\x00S', co_consts = (None, 'Hello World!'),
co_names = (), co_varnames = (), co_freevars = (), co_cellvars = (),
co_filename = '<stdin>', co_name = 'foo', co_firstlineno = 1,
co_lnotab = '\x00\x01', co_zombieframe = 0x0, co_weakreflist = 0x0}
(gdb) print (*(PyCodeObject*)0x187aca0)->co_code
$4 = 'd\x01\x00GHd\x00\x00S'
所以,这里是直接访问字节码的方法,给定函数的地址。
为了完整起见,我在 Python 字节码(以及如何访问它)上找到的最好的文档是 Python 代码本身,尤其是inspect
模块(2.7、3.2)。试着看看它,它很有启发性。
您可以使用的另一个帮助是为 Python 字节码提供反汇编程序的dis
模块。这是一个可以执行此反汇编程序的示例。
$> python2.7
Python 2.7.3 (default, Mar 4 2013, 14:57:34)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> def foo():
... print("Hello World!")
...
>>> import dis
>>> dis.dis(foo)
2 0 LOAD_CONST 1 ('Hello World!')
3 PRINT_ITEM
4 PRINT_NEWLINE
5 LOAD_CONST 0 (None)
8 RETURN_VALUE