12号玩了玩VNCTF2022,感觉自己又菜了114514倍。以前学习的时候都是每个小模块稍微学习一下就浅尝辄止了,现在决心死磕一个板块,一个一个搞透了。
Pyc字节码分析——虚拟机指令集分析
注意
关于本博客主旨
dis — Python 字节码反汇编器 — Python 3.10.2 文档
- 作为官方文档的补充,在此基础上提供一些例子。冷门指令(也就是我搞不懂的)不讲
- 老版本中被抛弃的指令显然是因为不好用且很少用,所以我直接选取最高版本3.10.2的指令集分析。
提示
- Py36以上,每条指令使用2个字节。
dis
模块通过反汇编支持CPython
的 bytecode 分析。该模块作为输入的CPython
字节码在文件Include/opcode.h
中定义,并由编译器和解释器使用。
环境
1 | Python3.10.2 |
dis模块
1 | def myfunc(alist): |
dis.dis()
显示反汇编
1 | dis.dis(myfunc) |
Bytecode
对象
1 | bytecode = dis.Bytecode(myfunc) |
from_traceback(tb)
从给定traceback
构造一个 Bytecode
实例,将设置 current_offset
为异常负责的指令。
codeobj
已编译的代码对象。
first_line
代码对象的第一个源代码行(如果可用)
dis
()
返回字节码操作的格式化视图(与 dis.dis()
打印相同,但作为字符串返回)。
info
()
返回带有关于代码对象的详细信息的格式化多行字符串,如 code_info()
。
1 | bytecode.dis() |
函数
我常用的是
1 | dis.dis() |
1 | dis.dis(myfunc) |
其他的省略。
指令集分析
TOS
:”Top of Stack”,栈顶里面有一些指令我没有设置成小标题,因为我不清楚其意思和用法。
无参指令
NOP
什么都不做。 用作字节码优化器的占位符。
1 | 'NOP'] dis.opmap[ |
POP_TOP
删除堆栈顶部(TOS)项。
1 | def func(): |
常用于抛弃函数的返回值。
ROT_TWO
交换两个最顶层的堆栈项。
1 | def func(): |
交换值时会遇到。
ROT_THREE
将第二个和第三个堆栈项向上提升一个位置,顶项移动到位置三。
1 | def func(): |
ROT_FOUR
(Py38)
将第二个、第三个和第四个堆栈项向上提升一个位置,将顶项移动到第四个位置。
大于4个参数的相互交换就使用元组(
BUILD_TUPLE
)指令了,故暂时找不到例子
DUP_TOP
(Py32)
复制堆栈顶部的引用。
1 | def func(): |
链式赋值时会用上。
作用
- 复制栈顶元素
- 然后push进栈中
这样栈顶就有两个一样的”对象”了。但是,似乎栈上的都是对象指针,所以实际上指向的是同一个对象。
且Python的链式赋值和C的不一样。Python中的链式赋值似乎是从左往右走的。
1 | def func(): |
可以发现a, b, c
指向的是同一个地址空间下的对象。
1 | def func(): |
由于从左到右,所以i
先变成了3,进而对s[3]
进行了修改,而不是s[0]
。
1 | //取自 python/ceval.c |
DUP_TOP_TWO
(Py32)
复制堆栈顶部的两个引用,使它们保持相同的顺序。
没怎么见过。
一元操作
偷懒,能用
lambda
表达式的就用。
UNARY_POSITIVE
实现 TOS = +TOS
。
1 | lambda x : +x) dis.dis( |
UNARY_NEGATIVE
实现 TOS = -TOS
。
1 | lambda x : -x) dis.dis( |
UNARY_NOT
实现 TOS = not TOS
。
1 | lambda x : not x) dis.dis( |
UNARY_INVERT
实现 TOS = ~TOS
。
1 | lambda x : ~x y = |
GET_ITER
实现 TOS = iter(TOS)
。
原地将原来的对象引用变成迭代器对象。
1 | def func(): |
在使用for i in xxx
遍历数组,range()
对象等可遍历对象时,会调用此指令。但是显性地使用iter()
函数,则会编译成函数调用。
1 | lambda x: iter(x)) dis.dis( |
GET_YIELD_FROM_ITER
如果 TOS
是一个 generator iterator 或 coroutine 对象则保持原样。否则实现 TOS = iter(TOS)
。
没找到用法。
二元操作
二元操作从堆栈中删除堆栈顶部(TOS)和第二个最顶层堆栈项(TOS1)。 它们执行操作,并将结果放回堆栈。
BINARY_POWER
实现 TOS = TOS1 ** TOS
。
1 | lambda x,y : x**y) dis.dis( |
BINARY_MULTIPLY
实现 TOS = TOS1 * TOS
。
1 | lambda x,y : x*y) dis.dis( |
BINARY_MATRIX_MULTIPLY
(Py35)
实现 TOS = TOS1 @ TOS
。
本操作需要(大概率)外部导入模块比如
numpy
。
1 | def func(): |
BINARY_FLOOR_DIVIDE
实现 TOS = TOS1 // TOS
。
1 | lambda x,y : x//y) dis.dis( |
BINARY_TRUE_DIVIDE
实现 TOS = TOS1 / TOS
。
1 | lambda x,y : x/y) dis.dis( |
BINARY_MODULO
实现 TOS = TOS1 % TOS
。
1 | lambda x,y : x%y) dis.dis( |
BINARY_ADD
实现 TOS = TOS1 + TOS
。
1 | lambda x,y : x+y) dis.dis( |
BINARY_SUBTRACT
实现 TOS = TOS1 - TOS
。
1 | lambda x,y : x-y) dis.dis( |
BINARY_SUBSCR
实现 TOS = TOS1[TOS]
。
1 | lambda s,i : s[i]) dis.dis( |
BINARY_LSHIFT
实现 TOS = TOS1 << TOS
。
1 | lambda x,y : x << y) dis.dis( |
BINARY_RSHIFT
实现 TOS = TOS1 >> TOS
。
1 | lambda x,y : x >> y) dis.dis( |
BINARY_AND
实现 TOS = TOS1 & TOS
。
1 | lambda x,y : x & y) dis.dis( |
BINARY_XOR
实现 TOS = TOS1 ^ TOS
。
1 | lambda x,y : x | y) dis.dis( |
BINARY_OR
实现 TOS = TOS1 | TOS
。
1 | lambda x,y : x | y) dis.dis( |
就地操作
就地操作就像二元操作,因为它们删除了TOS和TOS1,并将结果推回到堆栈上,但是当TOS1支持它时,操作就地完成,并且产生的TOS可能是(但不一定) 原来的TOS1。
INPLACE_POWER
就地实现 TOS **= TOS1
。
1 | def func(): |
INPLACE_MULTIPLY
就地实现 TOS *= TOS1
。
INPLACE_MATRIX_MULTIPLY
就地实现 TOS @= TOS1
。(Py35)
INPLACE_FLOOR_DIVIDE
就地实现 TOS //= TOS1
。
INPLACE_TRUE_DIVIDE
就地实现 TOS /= TOS1
。
INPLACE_MODULO
就地实现 TOS %= TOS1
。
INPLACE_ADD
就地实现 TOS += TOS1
。
INPLACE_SUBTRACT
就地实现 TOS -= TOS1
。
1 | def func(): |
INPLACE_LSHIFT
就地实现 TOS <<= TOS1
。
INPLACE_RSHIFT
就地实现 TOS >>= TOS1
。
INPLACE_AND
就地实现 TOS &= TOS1
。
INPLACE_XOR
就地实现 TOS ^= TOS1
。
INPLACE_OR
就地实现 TOS |= TOS1
。
STORE_SUBSCR
实现 TOS1[TOS] = TOS2
。
1 | def func(): |
DELETE_SUBSCR
实现 del TOS1[TOS]
。
1 | def func(): |
协程操作码(暂不分析)
GET_AWAITABLE
(Py35)
实现 TOS = get_awaitable(TOS)
,其中 get_awaitable(o)
返回 o
如果 o
是一个有 CO_ITERABLE_COROUTINE 标志的协程对象或生成器对象,否则解析 o.__await__
。
GET_AITER
实现 TOS = TOS.__aiter__()
。(Py35)
在 3.7 版更改:已经不再支持从
__aiter__
返回可等待对象。
GET_ANEXT
实现 PUSH(get_awaitable(TOS.__anext__()))
。参见 GET_AWAITABLE
获取更多 get_awaitable
的细节(Py35)
END_ASYNC_FOR
终止一个 async for
循环。处理等待下一个项目时引发的异常。如果 TOS 是 StopAsyncIteration
, 从堆栈弹出7个值,并使用后三个恢复异常状态。否则,使用堆栈中的三个值重新引发异常。从块堆栈中删除异常处理程序块。(Py38)
BEFORE_ASYNC_WITH
从栈顶对象解析 __aenter__
和 __aexit__
。将 __aexit__
和 __aenter__()
的结果推入堆栈。(Py35)
SETUP_ASYNC_WITH
创建一个新的帧对象。(Py35)
其他操作码
PRINT_EXPR
实现交互模式的表达式语句。TOS从堆栈中被移除并打印。在非交互模式下,表达式语句以 POP_TOP
终止。
一般情况下遇不到。
SET_ADD(i)
调用 set.add(TOS1[-i], TOS)
。 用于实现集合推导。
1 | def func(): |
推导式的本质是内部嵌套实现了一个函数代码对象,然后再去调用。
1 | def func(): |
看一下这个内嵌的函数有啥。
1 | def func(): |
.0
应该就是外面函数传入的参数,是一个iter()
对象。
LIST_APPEND(i)
调用 list.append(TOS1[-i], TOS)
。 用于实现列表推导式。
1 | def func(): |
MAP_ADD(i)
调用 dict.__setitem__(TOS1[-i], TOS1, TOS)
。 用于实现字典推导。
在 3.8 版更改:映射值为 TOS ,映射键为 TOS1 。之前,它们被颠倒了。
1 | # Py310版本测试 |
对于所有 SET_ADD
、 LIST_APPEND
和 MAP_ADD
指令,当弹出添加的值或键值对时,容器对象保留在堆栈上,以便它可用于循环的进一步迭代。
RETURN_VALUE
返回 TOS 到函数的调用者。
1 | def func(): |
单返回值就返回一个值,多返回值返回一个元组。
YIELD_VALUE
弹出 TOS 并从一个 generator 生成它。
1 | def func(): |
可见这是
yield
的汇编形式。
YIELD_FROM
弹出 TOS 并将其委托给它作为 generator 的子迭代器。(Py33)
这个是
yield from
的汇编形式,没怎么研究过。
SETUP_ANNOTATIONS
检查 __annotations__
是否在 locals()
中定义,如果没有,它被设置为空 dict
。只有在类或模块体静态地包含 variable annotations 时才会发出此操作码。(Py36)
IMPORT_STAR
将所有不以 '_'
开头的符号直接从模块 TOS 加载到局部命名空间。加载所有名称后弹出该模块。这个操作码实现了 from module import *
。
from module import *
只能全局调用,无法在函数中使用,故暂时无法演示。
POP_BLOCK
从块堆栈中删除一个块。有一块堆栈,每帧用于表示 try
语句等。
1 | def func(): |
通常在try块的结尾,弹出整个块。
POP_EXCEPT
从块堆栈中删除一个块。 弹出的块必须是异常处理程序块,在进入 except
处理程序时隐式创建。除了从帧堆栈弹出无关值之外,最后三个弹出值还用于恢复异常状态。
上面一个例子有
RERAISE
重新引发当前位于栈顶的异常。 如果 oparg
为非零值,则将当前帧的 f_lasti
恢复为异常被引发时的值。(Py39)
WITH_EXCEPT_START
调用堆栈中 7 号位置上的函数并附带栈顶位置的三项作为参数。 用来在 with
语句内发生异常时实现调用 context_manager.__exit__(*exc_info())
。(Py39)
LOAD_ASSERTION_ERROR
将 AssertionError
推入栈顶。 由 assert
语句使用。(Py39)
1 | def func(): |
没成功。等价于
LOAD_GLOBAL
和RAISE_VARARGS
配合,还更灵活
LOAD_BUILD_CLASS
将 builtins .__ build_class__()
推到堆栈上。它之后被 CALL_FUNCTION
调用来构造一个类。
SETUP_WITH(delta)
此操作码会在 with 代码块开始之前执行多个操作。 首先,它从上下文管理器加载 __exit__()
并将其推入栈顶以供 WITH_EXCEPT_START
后续使用。 然后,调用 __enter__()
,并推入一个指向 delta 的 finally 代码块。 最后,将调用 __enter__()
方法的结果推入栈顶。 下一个操作码将忽略它 (POP_TOP
),或将其存储在一个或多个变量 (STORE_FAST
, STORE_NAME
或 UNPACK_SEQUENCE
) 中。3.2 新版功能.
COPY_DICT_WITHOUT_KEYS
TOS 是一个映射键的元组,而 TOS1 是匹配目标。 将 TOS 替换为由 TOS1 条目组成的 dict
,但不包含任何 TOS 中的键。(Py310)
GET_LEN
将 len(TOS)
推入栈顶。(Py310)
直接调用
len()
函数无法触发这个指令。需要其他方法。
MATCH_MAPPING
如果 TOS 是 collections.abc.Mapping
的实例(或者更准确地说:如果在它的 tp_flags
中设置了 Py_TPFLAGS_MAPPING
旗标),则将 True
推入栈顶。 否则,推入 False
。(Py310)
MATCH_SEQUENCE
如果 TOS 是 collections.abc.Sequence
的实例而 不是 str
/bytes
/bytearray
的实例(或者更准确地说:如果在它的 tp_flags
中设置了 Py_TPFLAGS_SEQUENCE
旗标),则将 True
推入栈顶。 否则 ,推入 False
。(Py310)
MATCH_KEYS
TOS 是一个映射键的元组,而 TOS1 是匹配目标。 如果 TOS1 包含 TOS 中的所有键,则推入一个包含对应值的 tuple
,再推入 True
。 否则,推入 None
,再推入 False
。(Py310)
以下所有操作码均使用其参数。
STORE_NAME(*namei*)
实现 name = TOS
。 namei
是 name
在代码对象的 co_names
属性中的索引。 在可能的情况下,编译器会尝试使用 STORE_FAST
或 STORE_GLOBAL
。
DELETE_NAME(*namei*)
实现 del name
,其中 namei 是代码对象的 co_names
属性的索引。
UNPACK_SEQUENCE(*count*)
将 TOS 解包为 count 个单独的值,它们将按从右至左的顺序被放入堆栈。
UNPACK_EX(*counts*)
实现使用带星号的目标进行赋值:将 TOS 中的可迭代对象解包为单独的值,其中值的总数可以小于可迭代对象中的项数:新值之一将是由所有剩余项构成的列表。counts 的低字节是列表值之前的值的数量,counts 中的高字节则是之后的值的数量。 结果值会按从右至左的顺序入栈。
STORE_ATTR(*namei*)
实现 TOS.name = TOS1
,其中 namei 是 name 在 co_names
中的索引号。
1 | class myobj: |
DELETE_ATTR(*namei*)
实现 del TOS.name
,使用 namei
作为 co_names
中的索引号。
见上
STORE_GLOBAL(*namei*)
类似于 STORE_NAME
但会将 name 存储为全局变量。
1 | def func(): |
DELETE_GLOBAL(*namei*)
类似于 DELETE_NAME
但会删除一个全局变量。
1 | def func(): |
LOAD_CONST(*consti*)
将 co_consts[consti]
推入栈顶。
LOAD_NAME(*namei*)
将与 co_names[namei]
相关联的值推入栈顶。
BUILD_TUPLE(*count*)
创建一个使用了来自栈的 count 个项的元组,并将结果元组推入栈顶。
BUILD_LIST(*count*)
类似于 BUILD_TUPLE
但会创建一个列表。
1 | def func(): |
其他几个
BUILD_
指令都一样的。
BUILD_SET(*count*)
类似于 BUILD_TUPLE
但会创建一个集合。
BUILD_MAP
(count)
将一个新字典对象推入栈顶。 弹出 2 * count
项使得字典包含 count 个条目: {..., TOS3: TOS2, TOS1: TOS}
。在 3.5 版更改: 字典是根据栈中的项创建而不是创建一个预设大小包含 count 项的空字典。
BUILD_CONST_KEY_MAP(*count*)
BUILD_MAP
版本专用于常量键。 弹出的栈顶元素包含一个由键构成的元组,然后从 TOS1
开始从构建字典的值中弹出 count 个值。(Py36)
BUILD_STRING(*count*)
拼接 count
个来自栈的字符串并将结果字符串推入栈顶。(Py36)
LIST_TO_TUPLE
从堆栈中弹出一个列表并推入一个包含相同值的元组。(Py39)
LIST_EXTEND(*i*)
调用 list.extend(TOS1[-i], TOS)
。 用于构建列表。(Py39)
SET_UPDATE
(i)
调用 set.update(TOS1[-i], TOS)
。 用于构建集合。(Py39)
DICT_UPDATE
(i)
调用 dict.update(TOS1[-i], TOS)
。 用于构建字典。(Py39)
DICT_MERGE
类似于 DICT_UPDATE
但对于重复的键会引发异常。(Py39)
LOAD_ATTR(*namei*)
将 TOS 替换为 getattr(TOS, co_names[namei])
。
COMPARE_OP(*opname*)
执行布尔运算操作。 操作名称可在 cmp_op[opname]
中找到。
1 | def func(): |
IS_OP(*invert*)
执行 is
比较,或者如果 invert
为 1 则执行 is not
。(Py39)
CONTAINS_OP
(invert)
执行 in
比较,或者如果 invert
为 1 则执行 not in
。(Py39)
上面几个似乎都能用
COMPARE_OP
一个应对。
IMPORT_NAME
(namei)
导入模块 co_names[namei]
。 会弹出 TOS 和 TOS1 以提供 fromlist 和 level 参数给 __import__()
。 模块对象会被推入栈顶。 当前命名空间不受影响:对于一条标准 import 语句,会执行后续的 STORE_FAST
指令来修改命名空间。
1 | def func(): |
IMPORT_FROM(*namei*)
从在 TOS 内找到的模块中加载属性 co_names[namei]
。 结果对象会被推入栈顶,以便由后续的 STORE_FAST
指令来保存。
JUMP_FORWARD(*delta*)
将字节码计数器的值增加 delta
。
我混淆最喜欢用的指令。但是只能往后跳转。
POP_JUMP_IF_TRUE(*target*)
如果 TOS 为真值,则将字节码计数器的值设为 target。 TOS 会被弹出。(Py31)
POP_JUMP_IF_FALSE(*target*)
如果 TOS 为假值,则将字节码计数器的值设为 target。 TOS 会被弹出。(Py31)
JUMP_IF_NOT_EXC_MATCH(*target*)
检测堆栈中的第二个值是否为匹配 TOS 的异常,如果不是则会跳转。 从堆栈中弹出两个值。(Py39)
JUMP_IF_TRUE_OR_POP(*target*)
如果 TOS 为真值,则将字节码计数器的值设为 target 并将 TOS 留在栈顶。 否则(如 TOS 为假值),TOS 会被弹出。(Py31)
JUMP_IF_FALSE_OR_POP(*target*)
如果 TOS 为假值,则将字节码计数器的值设为 target 并将 TOS 留在栈顶。 否则(如 TOS 为真值),TOS 会被弹出。(Py31)
JUMP_ABSOLUTE(*target*)
将字节码计数器的值设为 target。
FOR_ITER(*delta*)
TOS 是一个 iterator。 请调用其 __next__()
方法。 如果此操作产生了一个新值,则将其推入栈顶(将迭代器留在其下方)。 如果迭代器提示已耗尽,TOS 会被弹出,并且字节码计数器将增加 delta。
用伪指令描述一下:
1 | # FOR_ITER delta |
此指令在循环,遍历列表,构建列表表达式时都会被用上。
LOAD_GLOBAL(*namei*)
加载名称为 co_names[namei]
的全局对象推入栈顶。
1 | global glob |
SETUP_FINALLY(*delta*)
将一个来自 try-finally
或 try-except
子句的 try
代码块推入代码块栈顶。 相对 finally
代码块或第一个 except 代码块 delta
个点数。
LOAD_FAST(*var_num*)
将指向局部对象 co_varnames[var_num]
的引用推入栈顶。
STORE_FAST(*var_num*)
将 TOS 存放到局部对象 co_varnames[var_num]
。
DELETE_FAST(*var_num*)
移除局部对象 co_varnames[var_num]
。
这几个太常用了,不搞了
LOAD_CLOSURE(*i*)
将一个包含在单元的第 i 个空位中的对单元的引用推入栈顶并释放可用的存储空间。 如果 i 小于 co_cellvars 的长度则变量的名称为 co_cellvars[i]
。 否则为 co_freevars[i len(co_cellvars)]
。
LOAD_DEREF(*i*)
加载包含在单元的第 i 个空位中的单元并释放可用的存储空间。 将一个对单元所包含对象的引用推入栈顶。
LOAD_CLASSDEREF(*i*)
类似于 LOAD_DEREF
但在查询单元之前会首先检查局部对象字典。 这被用于加载类语句体中的自由变量。3.4 新版功能.
STORE_DEREF(*i*)
将 TOS 存放到包含在单元的第 i 个空位中的单元内并释放可用存储空间。
DELETE_DEREF(*i*)
清空包含在单元的第 i 个空位中的单元并释放可用存储空间。 被用于 del
语句。3.2 新版功能.
RAISE_VARARGS(*argc*)
使用 raise
语句的 3 种形式之一引发异常,具体形式取决于 argc 的值:0: raise
(重新引发之前的异常)1: raise TOS
(在 TOS
上引发异常实例或类型)2: raise TOS1 from TOS
(在 TOS1
上引发异常实例或类型并将 __cause__
设为 TOS
)
1 | def func(): |
异常触发
CALL_FUNCTION(*argc*)
调用一个可调用对象并传入位置参数。 argc 指明位置参数的数量。 栈顶包含位置参数,其中最右边的参数在最顶端。 在参数之下是一个待调用的可调用对象。 CALL_FUNCTION
会从栈中弹出所有参数以及可调用对象,附带这些参数调用该可调用对象,并将可调用对象所返回的返回值推入栈顶。在 3.6 版更改: 此操作码仅用于附带位置参数的调用。
是个函数调用都用这个。
CALL_FUNCTION_KW
(argc)
调用一个可调用对象并传入位置参数(如果有的话)和关键字参数。 argc 指明位置参数和关键字参数的总数量。 栈顶元素包含一个关键字参数名称的元组,名称必须为字符串。 在元组之下是与元组顺序相对应的关键字参数值。 在它之下则是位置参数,其中最右边的参数在最顶端。 在参数之下是要调用的可调用对象。 CALL_FUNCTION_KW
会从栈中弹出所有参数及可调用对象,附带这些参数调用该可调用对象,并将可调用对象所返回的返回值推入栈顶。在 3.6 版更改: 关键字参数会被打包为一个元组而非字典,argc 指明参数的总数量。
CALL_FUNCTION_EX
(flags)
调用一个可调用对象并附带位置参数和关键字参数变量集合。 如果设置了 flags 的最低位,则栈顶包含一个由额外关键字参数组成的映射对象。 在调用该可调用对象之前,映射对象和可迭代对象会被分别“解包”并将它们的内容分别作为关键字参数和位置参数传入。 CALL_FUNCTION_EX
会中栈中弹出所有参数及可调用对象,附带这些参数调用该可调用对象,并将可调用对象所返回的返回值推入栈顶。(Py36)
LOAD_METHOD(namei)
从 TOS 对象加载一个名为 co_names[namei]
的方法。 TOS 将被弹出。 此字节码可区分两种情况:如果 TOS 有一个名称正确的方法,字节码会将未绑定方法和 TOS 推入栈顶。 TOS 将在调用未绑定方法时被用作 CALL_METHOD
的第一个参数 (self
)。 否则会将 NULL
和属性查找所返回的对象推入栈顶。(Py37)
CALL_METHOD(argc)
调用一个方法。 argc
是位置参数的数量。 关键字参数不受支持。 此操作码被设计用于配合 LOAD_METHOD
使用。 位置参数放在栈顶。 在它们之下放在栈中的是由 LOAD_METHOD
所描述的两个条目(或者是 self
和一个未绑定方法对象,或者是 NULL
和一个任意可调用对象)。 它们会被全部弹出并将返回值推入栈顶。(Py37)
1 | def func(): |
MAKE_FUNCTION(flags)
将一个新函数对象推入栈顶。 从底端到顶端,如果参数带有指定的旗标值则所使用的栈必须由这些值组成。0x01
一个默认值的元组,用于按位置排序的仅限位置形参以及位置或关键字形参0x02
一个仅限关键字形参的默认值的字典0x04
一个包含形参标注的字符串元组。0x08
一个包含用于自由变量的单元的元组,生成一个闭包与函数相关联的代码 (在 TOS1)函数的 qualified name (在 TOS)在 3.10 版更改: 旗标值 0x04
是一个字符串元组而非字典。
https://stackoverflow.com/questions/54877888/python-3-7-bytecode-make-function-how-to-interpret-such-disassembled-function-d
https://docs.python.org/3/library/dis.htmlMAKE_FUNCTION(argc)
Pushes a new function object on the stack. From bottom to top, the consumed stack must consist of values if the argument carries a specified flag value:0x01 a tuple of default values for positional-only and positional-or-keyword parameters in positional order
0x02 a dictionary of keyword-only parameters’ default values
0x04 an annotation dictionary
0x08 a tuple containing cells for free variables, making a closure
the code associated with the function (at TOS1)
the qualified name of the function (at TOS)
1 | def outf(): |
BUILD_SLICE(*argc*)
将一个切片对象推入栈顶。 argc
必须为 2 或 3。 如果为 2,则推入 slice(TOS1, TOS)
;如果为 3,则推入 slice(TOS2, TOS1, TOS)
。 请参阅 slice()
内置函数了解详细信息。
1 | def func(): |
EXTENDED_ARG(ext)
为任意带有大到无法放入默认的单字节的参数的操作码添加前缀。 ext
存放一个附加字节作为参数中的高比特位。 对于每个操作码,最多允许三个 EXTENDED_ARG
前缀,构成两字节到三字节的参数。
FORMAT_VALUE(flags)
用于实现格式化字面值字符串(f-字符串)。 从栈中弹出一个可选的 fmt_spec
,然后是一个必须的 value
。 flags
的解读方式如下:(flags & 0x03) == 0x00
: value 按原样格式化。(flags & 0x03) == 0x01
: 在格式化 value 之前调用其 str()
。(flags & 0x03) == 0x02
: 在格式化 value 之前调用其 repr()
。(flags & 0x03) == 0x03
: 在格式化 value 之前调用其 ascii()
。(flags & 0x04) == 0x04
: 从栈中弹出 fmt_spec
并使用它,否则使用空的 fmt_spec
。使用 PyObject_Format()
执行格式化。 结果会被推入栈顶。(Py36)
MATCH_CLASS(count)
TOS 是一个包含关键字属性名称的元组,TOS1 是要匹配的类,而 TOS2 是匹配目标。 count 是位置子模式的数量。弹出 TOS。 如果 TOS2 是 TOS1 的一个实例且具有 count 和 TOS 所要求的位置和关键字属性,则将 TOS 设为 True
并将 TOS1 设为包含已提取属性的元组。 否则,将 TOS 设为 False
。(Py310)
GEN_START(kind)
Pops TOS. The kind
operand corresponds to the type of generator or coroutine. The legal kinds are 0 for generator, 1 for coroutine, and 2 for async generator.(Py310)
ROT_N(count)
将栈的前 count
项向上提升一个位置,并将 TOS 下移至位置 count
。(Py310)
HAVE_ARGUMENT
这不是一个真正的操作码。 它用于标明使用参数和不使用参数的操作码 (分别为 < HAVE_ARGUMENT
和 >= HAVE_ARGUMENT
) 之间的分隔线。
在 3.6 版更改: 现在每条指令都带有参数,但操作码
< HAVE_ARGUMENT
会忽略它。 之前仅限操作码>= HAVE_ARGUMENT
带有参数。
- 本文作者: Taardis
- 本文链接: https://taardisaa.github.io/2022/02/13/Pyc Reverse Opcode/
- 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!