我将这个问题发布在了stackoverflow上(虽然太旧了,无法迁移)。

说我在python解释器中,并定义了如下函数:

def h(a):
  return a


如果我想查看字节码(不是使用dis进行反汇编),通常可以使用h.func_code.co_code。还有其他方法可以查看字节码吗?这个特定的应用程序与一个自定义的python解释器打包在一起(可能使用py2exe),该解释器删除了对co_code的访问。我不能仅仅查看pyc文件,因为它们已加密。

例如,在解释器中,如果我直接输入h而不使它成为函数调用,那么我将获得函数的地址。我可以使用该地址获取字节码吗?还有其他方法吗?

P.S.当时我这样做的最初目标是使用pyREtic(称为co_code)进行反编译。由于它调用了co_code,因此它将无法正常工作。我想出了一种方法来做,最终将作为答案发布。想看看别人做了什么或想出什么。

评论

现在无法访问计算机,但是您是否尝试过研究python源代码以查看co_code的作用?

是的,co_code只是一个存储给定函数字节码的缓冲区。

#1 楼

首先,仅提醒一下“什么是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字节码。另一种形式是函数/方法的即时编译。

但是,有一种方法可以直接访问函数/方法的定义,从而直接访问字节码。关键是要用gdb拦截Python进程并进行分析。网络上有一些有关此的教程(请参见此处,此处,此处或此处)。但是,这是一个简单的示例(您需要首先安装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
 = {_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
 = 'foo'
(gdb) print (*(PyCodeObject*)0x187aca0)
 = {_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
 = 'd\x01\x00GHd\x00\x00S'


因此,在给定函数地址的情况下,这是直接访问字节码的方法。

尝试一下为了完整起见,我在Python字节码(以及如何访问它)上找到的最好的文档是Python代码本身,尤其是inspect模块(2.7、3.2)。尝试看一下,它很有启发性。

您可以使用的另一种帮助是dis模块,该模块为Python字节码提供了反汇编程序。这是此反汇编程序的示例。

$> 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 


评论


太酷了!我不是100%肯定这将在我正在处理的特定项目中工作,因为它具有自定义python解释器,我可能无法为其安装gdb绑定。仍然是一项伟大的技术。感谢分享!

–mikeazo
13年4月27日在19:14

#2 楼

我认为恐怖的答案是正确的方法。我想发布我最终为他人着想的方式,以防万一我在对perror的回答的评论中提到的问题是正确的。我现在没有所有注释,如果需要的话会进行更新。我在函数的末尾附近设置了断点,以便创建对象。从那里,我能够查看内存中的对象以提取字节码。

要将此信息返回给pyREtic,我将函数名和字节码从GDB中转储到文件中,并修改了pyREtic,就像我说的那样,它已经有一段时间了(stackoverflow问题来自2012年9月),而不是调用co_code来获取字节码,而是从文件中提取字节码。我将回顾一下笔记并填写详细信息。