阅读“每个程序员应该知道的97件事”我发现了有趣的关于代码分析工具的文章。
作者声称,Python标准库中的反汇编程序对调试您的每日代码非常有用
这里有: “这个库(Python标准库反汇编程序)有一件事可以反汇编 是您的最后一个堆栈跟踪,为您提供有关哪个字节码指令引发最后一次未捕获异常的反馈。“
但书中没有对此作出解释
那么有人知道上面的模块如何对调试有用吗?
答案 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,但它使得你不必知道很多关于发生了什么。但我不认为知道指令的作用或说明的顺序是什么很痛苦。