使用反汇编程序调试错误Python

时间:2015-02-10 15:48:39

标签: python testing disassembly

阅读“每个程序员应该知道的97件事”我发现了有趣的关于代码分析工具的文章。

作者声称,Python标准库中的反汇编程序对调试您的每日代码非常有用

这里有: “这个库(Python标准库反汇编程序)有一件事可以反汇编 是您的最后一个堆栈跟踪,为您提供有关哪个字节码指令引发最后一次未捕获异常的反馈。“

但书中没有对此作出解释

那么有人知道上面的模块如何对调试有用吗?

1 个答案:

答案 0 :(得分:2)

虽然反汇编程序可以帮助您了解Python如何理解您所编写的内容,但它并不是唯一的工具。还有其他工具也可以提供帮助。正如我们将看到其中一些可以一起工作。

所以这里有一小段Python:

def five():
    return 5
print(five())

这里使用我编写的跨平台反汇编程序对其进行反汇编的一部分,称为xdis

# Python bytecode 3.4 (3310)
# Disassembled from Python 3.4.2 (default, May 17 2015, 22:17:04) 
# [GCC 4.8.2]
# Timestamp in code: 1499405520 (2017-07-07 01:32:00)
# Source code size mod 2**32: 39 bytes

# Method Name:       <module>
# Filename:          five.py
# Argument count:    0
# Kw-only arguments: 0
# Number of locals:  0
# Stack size:        2
# Flags:             0x00000040 (NOFREE)
# First Line:        1
# Constants:
#    0: <code object five at 0x7f99dd4e88a0, file "five.py", line 1>
#    1: 'five'
#    2: None
# Names:
#    0: five
#    1: print
  1:
        LOAD_CONST                0 (<code object five at 0x7f99dd4e88a0, file "five.py", line 1>)
        LOAD_CONST                1 ('five')
        MAKE_FUNCTION             0 (0 positional, 0 name and default, 0 annotations)
        STORE_NAME                0 (five)

  3:
        LOAD_NAME                 1 (print)
        LOAD_NAME                 0 (five)
        CALL_FUNCTION             0 (0 positional, 0 keyword pair)
        CALL_FUNCTION             1 (1 positional, 0 keyword pair)
        POP_TOP
        LOAD_CONST                2 (None)
        RETURN_VALUE
...

(这是Python 3.4,其他版本稍微改变了一些细节。)

首先要注意的是,python认为此代码来自路径名为five.py的文件。如果你碰巧重命名了文件而不是python代码,这可能会让Python感到困惑。或者文件名可以是tmp/five.py,然后你应该寻找它。此外,在Python版本3及更高版本中,文件的 size (模2 ** 32)作为检查,以查看文件系统上的five.py是否与文件系统上的MAKE_FUNCTION相同Python在编译文件时看到了。

我提醒您注意代码的开头:我们正在加载一个常量对象,它恰好是函数的代码!然后是函数的名称,最后调用x = five() # five is hasn't been defined here! def five(): ... 并将其存储在名为 five 的变量中。

如果您习惯使用C ++,Go或Java这样的编译语言,那么有点不寻常的是,当您运行程序时,该函数会立即在那里创建。如果我的程序之前有另一条指令而是:

if 1:
   y = 5

这会失败,因为MAKE_FUNCTION尚未运行,因此在开始时尚未定义五个。

现在我也建议您也可以使用调试器来学习这一点,我再次建议 trepan2 trepan3 ,它们内置了一个反汇编命令他们甚至是那个集会的后裔。

反汇编可以解释的另一个地方是Python在代码上进行优化的极少数情况。

考虑这个Python源代码:

if 1:

在这里,在大约2.3之后的Python版本中,只会注意到x = 1 if x: y = 5 是多余的并删除该代码。但如果你说的话:

prev = [100] + range(3)
x = prev[prev[prev[0]]]

这足以让Python混淆以保持测试。反汇编是我认为你能够知道的唯一方法。

最后一个方面是,当您在调试器中停止或遇到错误时,完全理解 where 。你经常(但不总是)得到你有错误的那一行,但有时这可能会令人困惑。正常 Python屏蔽了这里有用的信息,指令偏移量,但是我将告诉你如何得到它以及你有错误的指令。

假设我的代码是:

trepan2 /tmp/boom.py
-> 2 prev = [100] + range(3)
(trepan2) next
(/tmp/boom.py:3 @19): <module>
-- 3 x = prev[prev[prev[0]]]
(trepan2) next
(/tmp/boom.py:3 @32): <module>
!! 3 x = prev[prev[prev[0]]]
R=> (<type 'exceptions.IndexError'>, 'list index out of range', <traceback object at
(trepan2) info pc
PC offset is 32.
  2        0 LOAD_CONST          0          100
           3 BUILD_LIST          1
           6 LOAD_NAME           0          0
           9 LOAD_CONST          1          3
          12 CALL_FUNCTION       1          1 positional, 0 keyword pair
          15 BINARY_ADD          None
          16 STORE_NAME          1          1

  3       19 LOAD_NAME           1          1
          22 LOAD_NAME           1          1
          25 LOAD_NAME           1          1
          28 LOAD_CONST          2          0
          31 BINARY_SUBSCR       None
   -->    32 BINARY_SUBSCR       None
          33 BINARY_SUBSCR       None
          34 STORE_NAME          2          2
          37 LOAD_CONST          3          None
          40 RETURN_VALUE        None

如果我运行此命令,我将收到 IndexError 异常。但是哪个“上一个”呢?

trepan2(或trepan3k)在此公开指令指针。它还允许访问反汇编程序和deparser。那么让我们看看如何在这里使用它:

(trepan2) deparse -p
instruction: 32 BINARY_SUBSCR
x = prev[prev[prev[0]]]
         -------------
Contained in...
Grammar Symbol: binary_subscr
x = prev[prev[prev[0]]]
    -------------------
(trepan2) prev
[100, 0, 1, 2]

确定。所以我们看到其中到底是什么,偏移32(之前在偏移@ 19处停止后是@32),但这是什么意思? trepan调试器会将其转换回Python,因此您不必自己执行此操作:

prev[0]

然后,上面显示您处于偏移32(不是31或33),并且特定的上一步访问权限不是第一次访问prev[prev[0]],而是{{1}}之后的访问{{1}} 1}}。

尽管在调试器中同时拥有一个disasmbler,deparser,但它使得你不必知道很多关于发生了什么。但我不认为知道指令的作用或说明的顺序是什么很痛苦。