目录
前提概要:
本篇内容都是在理解了博客Python逆向(一)—— 前言及Python运行原理 - Blili - 博客园的基础上完成的,只是加上了自己的理解而已,有需要的自己看上面博客内容。
运行原理和目录基础理解:
Python运行原理概述:
Python是解释型语言,没有严格意义上的编译和汇编过程。但是一般可以认为编写好的python源文件,由python解释器翻译成以.pyc为结尾的字节码文件。pyc文件是二进制文件,可以由python虚拟机直接运行。
注:为什么运行的python,有时候生成pyc文件,有时候没有呢?
Python在执行import语句时,将会到已设定的path中寻找对应的模块。并且把对应的模块编译成相应的PyCodeObject(python中的一个类)中间结果,然后创建pyc文件,并将中间结果写入该文件。然后,Python会import这个pyc文件,实际上也就是将pyc文件中的PyCodeObject重新复制到内存中。而被直接运行的python代码一般不会生成pyc文件。
总结:
python在整个运行过程中主要涉及源码xxx.py,编译好的文件xxx.pyc两类文件。其中xxx.pyc是可以由虚拟机直接执行的,是python将目标源码编译成字节码以后在磁盘上的文件形式。
python目录“__pycache__”:
在这个目录下还有一个名为“module_test.cpython-36.pyc”的文件。
1:这是Python将文件module_test.py编译成字节码后的文件,Python可以将程序编译成字节码的形式。对于import导入的外部模块文件来说,Python总是在第一次调用后将其编译成字节码的形式,以提高程序的启动速度。Python程序在导入外部模块文件时会查找模块的字节码文件,如果存在,则将编译版后的模块的修改时间同模块的修改时间进行比较。如果两者的修改时间不同,Python会重新编译这个模块,目的是确保两者的内容相符。
2:在Python程序开发过程中,如果不想将某个源文件发布,可以发布编译后的程序(例如上面的文件module_test.cpython-36.pyc),这样可以起到一定的保护源文件的作用。对于不作为模块来使用的Python程序来说,Python不会在运行脚本后将其编译成字节码的形式。如果想将其编译,可以使用compile模块实现。在下面的实例代码中,将文件mokuai.py进行了编译操作。
应用举例:
import py_compile #调用系统内置模块py_compile
py_compile.compile('mokuai.py','mokuai.pyc'); #调用内置库函数compile()
3:执行后将会在同目录下生成一个名为“mokuai.pyc”的文件,在Python 3语法规范中规定,如果在方法py_compile.compile中不指定第2个参数,则会在当前目录中新建一个名为“__pycache__”的目录,并在这个目录中生成如下格式的pyc字节码文件。被编译模块名.cpython-32.-pyc。
pyc文件结构分析:
Python代码的编译结果就是PyCodeObject对象。PyCodeObject对象可以由虚拟机加载后直接运行,而pyc文件就是PyCodeObject对象在硬盘上的保存形式。因此我们先分析PyCodeObject对象的结构,随后再涉及pyc文件的二进制结构。
PyCodeObject对象结构分析:
typedef struct {
PyObject_HEAD
int co_argcount; /* 位置参数个数 */
int co_nlocals; /* 局部变量个数 */
int co_stacksize; /* 栈大小 */
int co_flags;
PyObject *co_code; /* 字节码指令序列 */
PyObject *co_consts; /* 所有常量集合 */
PyObject *co_names; /* 所有符号名称集合 */
PyObject *co_varnames; /* 局部变量名称集合 */
PyObject *co_freevars; /* 闭包用的的变量名集合 */
PyObject *co_cellvars; /* 内部嵌套函数引用的变量名集合 */
/* The rest doesn’t count for hash/cmp */
PyObject *co_filename; /* 代码所在文件名 */
PyObject *co_name; /* 模块名|函数名|类名 */
int co_firstlineno; /* 代码块在文件中的起始行号 */
PyObject *co_lnotab; /* 字节码指令和行号的对应关系 */
void *co_zombieframe; /* for optimization only (see frameobject.c) */
} PyCodeObject;
Pyc文件结构分析:
pyc文件结构主要包括两部分:
pyc文件头部表示和PyCodeObject对象部分。
pyc文件头部比较简单,在python2中只占用4个字节,包含两个字段magic和mtime。
python中使用marshal.dump的方法将PyCodeObject对象转化为对应的二进制文件结构。每个字段在二进制文件中的结构如下图:
Pyc文件生成和分析:
源文件test.py:通过执行python2 -m py_compile test.py 可以生成编译好的pyc文件test.pyc
s = "hello"
def func():
a = 3
print s
func()
使用二进制编辑器打开test.pyc:
pyc文件头部:
前4个字节:03f3 0d0a,表示python版本
5-8个字节:0e6b 905d,表示pyc文件修改时间
PyCodeObject对象二进制编译结果:
第9字节:63,TYPE_CODE字段,也就是字符c,值为99,即0x63,表示接下为是一个PyCodeObject对象
PyCodeObject对象----全局参数:
然后4个字节是0x00 0000 00,code block的位置参数个数co_argument,这里是0;
再接着4个字节是0x00 0000 00, code block中的局部变量个数co_nlocals,这里是0;
再接着4个字节是0x01 0000 00, code block需要的栈空间co_stacksize,这里是1;
再接着4个字节是0x40 0000 00, co_flags,这里是64;
PyCodeObject对象----code block:
1个字节0x73为TYPE_CODE字段, 表示该字段为string格式;
4个字节0x1a00 0000表示code block段的数据部分占用0x1a个字节,即长度为26;
接下来26个字节6400 ...... 6402 0053为该TYPE_CODE字段(数据类型string)部分,也就是pyc文件中包含的字节码指令。
再往下的逐个TYPE_CODE字段都是重复结构的,用来表示PyCodeObject对象中的一些其他参数
Python编译运行及反汇编:
Pyc文件手动生成:
命令行模式:
python -m py_compile file.py # 生成单个pyc文件
python -m py_compile /dir/{file1,file2}.py # 生成多个pyc文件
python -m compileall /dir/ # 生成目录下所有py文件对应的pyc文件
交互shell模式:
>>> import py_compile # 相当于命令行中的“-m py_compile”
>>> py_compile.compile('py file path')
>>> import compileall
>>> compileall.compile_dir("py files dir")
Pyo文件手动生成:
pyo文件是源代码文件经过优化编译后生成的文件,是pyc文件的优化版本。编译时需要使用-O和-OO选项来生成pyo文件。在Python3.5之后,不再使用.pyo文件名,而是生成文件名类似“test.opt-n.pyc的文件。
python -O -m py_compile file.py
python -O -m py_compile /dir/{file1,file2}.py
python -O -m compileall /dir/
可以直接运行编译好的pyc文件和pyo文件:
字节码反编译:
前面说过pyc文件的结构,其实就是pyc文件头部加上PyCodeObject对象。文件头部的信息在python2中只占用固定8字节,用来携带一些版本类的信息,不是我们做反编译的重点,因此通过提取8字节之后的部门做反编译处理就可以了。
PyCodeObjectData就是我们需要提取的数据,根据python的编译原理我们知道PyCodeObjectData是python源文件作为一个实例化的类,通过python内置库函数marshal.dumps生成的二进制数据段,因此通过marshal.loads(PyCodeObjectData) ,我们可以得到PyCodeObjectData反序列化的对象。(dir函数返回参数的属性、方法、列表)
可以看到PyObj对象包含了很多内置方法和属性,这些属性在第二节中我们已经有过介绍,各个字段的含义都已经知道了。通过对这些方法的引用可以直接看到相关字段反序列后的具体值。
使用python内置模块dis可以对PyCodeObject进行反编译,从而获取到python二进制字节码代码段的“汇编形式”。这样可以便于对字节码进行阅读。dis模块也可以单独对PyCodeObject中的co_data模块进行反编译,但是这样得到的是单纯的代码段字节码,缺少很多代码段中涉及的变量名字。如下图所示。
Python内置模块dis.py源码详解:
dis反汇编源码文件:
将内存中的类、函数,甚至时普通的变量作为参数传递给dis模块中的dis函数,也可以返回该类对应的编译后的字节码形式:
dis反汇编PyCodeObject对象:
这一类情况是我们在做python逆向或者pyc文件分析时常用到的形式。
dis无参数:
如果dis.dis无参数传入,该方法默认会返回当前python shell上次报错时堆栈中储存的内存信息的字节码形式。
Python字节码解读:
python逆向或者反汇编的目的就是在没有源码的基础上,通过字节码来理解源代码的运行内容,并且进一步对源码的远行进行调试。因此本次我们尝试对python字节码进行解读。
字节码结构:
源码行号 | 跳转注释符 | 指令在函数中的偏移 | 指令符号(助记符) | 指令参数(索引) | 实际参数值
上图解析如下:
该字节码指令在源码中对应59行
此处是跳转的目的地址
82是该字节指令的字节码偏移
操作指令对应的助记符为LOAD_GLOBAL
操作参数(索引)为6
操作参数对应的实际值为disassemble
字节码中常量分析:
加载常量只有一行LOAD_CONST,对应源码第1行,字节码偏移地址0字节,常量数组中索引0,实际常量值‘123’。
字节码中局部变量分析:
加载局部变量a:LOAD_CONST加载常量1,调用STORE_NAME(参数a),并将变量a存储为1
同理加载局部变量b
字节码中全局变量分析:
加载全局变量a,与加载局部变量不同的是通过STORE_GLOBAL在存储变量。
字节码中数据类型list分析:
先将所有的list元素加载,调用BUILD_LIST方法生成list于内存中,通过STORE_NAME将堆栈中的list存储于局部变量a中。
字节码中数据类型dist分析:
BUILD_MAP声明字典元素数量,通过两次LOAD_CONST后,调用STORE_MAP生成键值对存于堆栈,最终通过STORE_NAME将堆栈中长度为2的两个键值对最为字典数据类型存储在a中。
字节码中数学运算分析:
字节码中显示先对局部变量a、b赋值,通过LOAD_NAME加载局部变量,调用加法BINARY_ADD,生成结果存储与堆栈中,使用STORE_NAME将堆栈中的计算结果存储与局部变量c。
下图中为对a、b做加减乘除的字节码,因为没有存储计算结果,所以每次运算完没有使用STORE_NAME方法存储,解释器默认调用POP_TOP方法将计算结果从堆栈顶部弹出,以保证堆栈平衡。
字节码中循环For语句分析:
SETUP_LOOP表明循环开始,参数说明此循环知道字节码偏移28字节的指令结束(也就是28字节开始不是循环)。调用range方法生成generator存于堆栈。FOR_ITER调用堆栈,声明generator作用到字节码偏移位置27字节。从第16字节起到27为generator迭代作用域。其中为一个print函数。
字节码中循环IF语句分析:
以一个简单的IF判断为例,先加载需要比较的常量,调用COMPARE_OP指令对堆栈中两个常量进行比较,将结果存入堆栈。调用POP_JUMP_IF_FALSE指令,判断栈顶值来决定程序运行顺序实现判断功能。
附上反汇编dis模块:(python2文档)
dis模块通过反汇编来支持对python字节码形式的分析。dis模块可以将编译好的二进制数据或者python源码当作模块的输入源。
CPython 实现细节:
字节码是 CPython 解释器的一个实现细节!不保证不会在 Python 版本之间添加、删除或更改字节码。不应考虑使用此模块跨 Python VM 或 Python 版本工作。
例如,给定函数myfunc():
def myfunc(alist):
return len(alist)
可以使用以下命令来反汇编myfunc():(“2”是行号)
>>> dis.dis(myfunc)
2 0 LOAD_GLOBAL 0 (len)
3 LOAD_FAST 0 (alist)
6 CALL_FUNCTION 1
9 RETURN_VALUE
dis模块定义的函数和常量:
dis.dis([bytesource]):
反汇编bytesource对象。bytesource可以表示模块、类、方法、函数或代码对象。对于模块,它分解所有功能。对于一个类,它分解所有方法。对于单个代码序列,它为每个字节码指令打印一行。如果没有提供对象,它将分解最后一次回溯。
dis.distb([tb]):
反汇编回溯的堆栈顶部函数,如果没有传递回溯,则使用最后一个回溯。指示导致异常的指令。
dis.disassemble(code[, lasti]):
反汇编代码对象,如果提供了lasti,则指示最后一条指令。输出分为以下列:
1:每行的第一条指令的行号
2:当前指令,指示为-->,
3:带标签的指令,用>>>表示,
4:指示的地址,
5:操作代码名称,
6:操作参数,以及
7:括号中参数的解释。
参数解释可识别局部和全局变量名、常量值、分支目标和比较运算符。
dis.disco(code[, lasti]):
反汇编()的同义词。它更便于键入,并且与早期Python版本兼容。
dis.findlinestarts(code):
此生成器函数使用代码目标代码的co_firstlineno和co_lnotab属性查找源代码中行的起始偏移量。它们生成为(偏移、线号)对。
dis.findlabels(code):
检测代码对象代码中作为跳转目标的所有偏移,并返回这些偏移的列表。
dis.opname:
操作名序列,可使用字节码进行索引。
dis.opmap:
字典将操作名称映射到字节码。
dis.cmp_op:
所有比较操作名称的顺序。
dis.hasconst:
访问常量的字节码序列。
dis.hasfree:
访问自由变量的字节码序列。
dis.hasname:
按名称访问属性的字节码序列。
dis.hasjrel:
具有相对跳转目标的字节码序列。
dis.hasjabs:
具有绝对跳转目标的字节码序列。
dis.haslocal:
访问局部变量的字节码序列。
dis.hascompare:
布尔运算的字节码序列。
dis模块调用解读:
1:dis模块主函数为dis,所有对dis模块的调用默认都会将参数传送给dis.dis(不排除进阶玩家直接调用dis.disb等其他模块来完成特定功能)
2:dis.dis先进行参数检查,根据无参数、字典、PyCodeObject实例化对象,代码段等不同类型参数调用不同的方法。如果提交的参数是字典,dis模块会通过迭代,将字典中的每个键值作为参数传递给dis.dis
3:经过dis方法的处理,最终参数会被交给disassemble或者disassemble_string方法处理,disassemble方法负责对提交的对象进行反汇编,disassemble_string方法负责对代码段进行反汇编,因为disassemble_string方法代码类似于disassemble,不对disassemble_string进行解读。
4:disassemble方法用来将PyCodeObject实例化对象翻译为可读字节码。首先调用findlabels和findlinestarts。findlabels将所有字节码跳转指向目的字节码地址存入堆栈。findlinestarts用来标记字节码对应的源码位置,官方注释说明findlinestarts会生成(offset, lineno)元组,其中offset为字节码偏移地址,lineno为源码偏移地址。
5:disassemble方法对字节码代码部分逐行翻译,并且添加必要变量及标志注释。
Python 字节码指令:
STOP_CODE()
向编译器指示代码结束,不被解释器使用。
NOP()
什么都不做代码。被字节码优化器用作占位符。
POP_TOP()
移除栈顶 (TOS) 项。
ROT_TWO()
交换最顶部的两个堆栈项数据。
ROT_THREE()
将第二个和第三个堆叠项目向上提升一个位置,自上而下移动到第三个位置。
ROT_FOUR()
将第二个、第三个和第四个堆叠项目向上提升一个位置,自上而下移动到第四个位置。
DUP_TOP()
复制堆栈顶部的引用。
一元运算取栈顶,应用运算,并将结果压回到栈中。
UNARY_POSITIVE()
实施.TOS = +TOS
UNARY_NEGATIVE()
实施.TOS = -TOS
UNARY_NOT()
实施.TOS = not TOS
UNARY_CONVERT()
实施.TOS = `TOS`
UNARY_INVERT()
实施.TOS = ~TOS
GET_ITER()
实施.TOS = iter(TOS)
二元运算从堆栈中删除堆栈顶部 (TOS) 和第二个最顶部堆栈项 (TOS1)。他们执行操作,并将结果放回堆栈中。
BINARY_POWER()
实施.TOS = TOS1 ** TOS
BINARY_MULTIPLY()
实施.TOS = TOS1 * TOS
BINARY_DIVIDE()
工具时未生效。TOS = TOS1 / TOSfrom __future__ import division
BINARY_FLOOR_DIVIDE()
实施.TOS = TOS1 // TOS
BINARY_TRUE_DIVIDE()
器具时生效。TOS = TOS1 / TOSfrom __future__ import division
BINARY_MODULO()
实施.TOS = TOS1 % TOS
BINARY_ADD()
实施.TOS = TOS1 + TOS
BINARY_SUBTRACT()
实施.TOS = TOS1 - TOS
BINARY_SUBSCR()
实施.TOS = TOS1[TOS]
BINARY_LSHIFT()
实施.TOS = TOS1 << TOS
BINARY_RSHIFT()
实施.TOS = TOS1 >> TOS
BINARY_AND()
实施.TOS = TOS1 & TOS
BINARY_XOR()
实施.TOS = TOS1 ^ TOS
BINARY_OR()
实施.TOS = TOS1 | TOS
就地操作类似于二元操作,因为它们删除 TOS 和 TOS1,并将结果推回到堆栈上,但是当 TOS1 支持时,操作就地完成,结果 TOS 可能(但没有是)原来的TOS1。
INPLACE_POWER()
就地实施。TOS = TOS1 ** TOS
INPLACE_MULTIPLY()
就地实施。TOS = TOS1 * TOS
INPLACE_DIVIDE()
在无效时就地实施。TOS = TOS1 / TOSfrom __future__ import division
INPLACE_FLOOR_DIVIDE()
就地实施。TOS = TOS1 // TOS
INPLACE_TRUE_DIVIDE()
生效时就地实施。TOS = TOS1 / TOSfrom __future__ import division
INPLACE_MODULO()
就地实施。TOS = TOS1 % TOS
INPLACE_ADD()
就地实施。TOS = TOS1 + TOS
INPLACE_SUBTRACT()
就地实施。TOS = TOS1 - TOS
INPLACE_LSHIFT()
就地实施。TOS = TOS1 << TOS
INPLACE_RSHIFT()
就地实施。TOS = TOS1 >> TOS
INPLACE_AND()
就地实施。TOS = TOS1 & TOS
INPLACE_XOR()
就地实施。TOS = TOS1 ^ TOS
INPLACE_OR()
就地实施。TOS = TOS1 | TOS
切片操作码最多采用三个参数。
SLICE+0()
实施.TOS = TOS[:]
SLICE+1()
实施.TOS = TOS1[TOS:]
SLICE+2()
实施.TOS = TOS1[:TOS]
SLICE+3()
实施.TOS = TOS2[TOS1:TOS]
切片分配甚至需要一个额外的参数。与任何语句一样,它们没有在堆栈中放置任何内容。
STORE_SLICE+0()
实施.TOS[:] = TOS1
STORE_SLICE+1()
实施.TOS1[TOS:] = TOS2
STORE_SLICE+2()
实施.TOS1[:TOS] = TOS2
STORE_SLICE+3()
实施.TOS2[TOS1:TOS] = TOS3
DELETE_SLICE+0()
实施.del TOS[:]
DELETE_SLICE+1()
实施.del TOS1[TOS:]
DELETE_SLICE+2()
实施.del TOS1[:TOS]
DELETE_SLICE+3()
实施.del TOS2[TOS1:TOS]
STORE_SUBSCR()
实施.TOS1[TOS] = TOS2
DELETE_SUBSCR()
实施.del TOS1[TOS]
PRINT_EXPR()
实现交互模式的表达式语句。TOS 从堆栈中移除并打印。在非交互模式下,表达式语句以POP_TOP.
PRINT_ITEM()
将 TOS 打印到绑定到sys.stdout. print语句中的每一项都有一个这样的指令。
PRINT_ITEM_TO()
Like PRINT_ITEM,但将第二个项目从 TOS 打印到 TOS 处的类文件对象。这是由扩展打印语句使用的。
PRINT_NEWLINE()
在 上打印一个新行sys.stdout。这是作为print语句的最后一个操作生成的,除非语句以逗号结尾。
PRINT_NEWLINE_TO()
Like PRINT_NEWLINE,但在 TOS 上的类文件对象上打印新行。这是由扩展打印语句使用的。
BREAK_LOOP()
由于break语句而终止循环。
CONTINUE_LOOP(target)
由于continue语句而继续循环。 target是要跳转到的地址(应该是一条FOR_ITER指令)。
LIST_APPEND(/)
调用。用于实现列表推导式。当附加值被弹出时,列表对象仍保留在堆栈中,以便它可用于循环的进一步迭代。list.append(TOS[-i], TOS)
LOAD_LOCALS()
在堆栈上推送对当前作用域的局部变量的引用。这在类定义的代码中使用:在评估类主体后,将局部变量传递给类定义。
RETURN_VALUE()
与 TOS 一起返回给函数的调用者。
YIELD_VALUE()
TOS从generator弹出并产生它。
IMPORT_STAR()
将所有不'_'直接从模块 TOS开始的符号加载到本地命名空间。加载所有名称后弹出模块。此操作码实现.from module import *
EXEC_STMT()
实施. 编译器用.exec TOS2,TOS1,TOSNone
POP_BLOCK()
从块堆栈中移除一个块。每帧都有一堆块,表示嵌套循环、try 语句等。
END_FINALLY()
终止一个finally条款。解释器会回想是否必须重新引发异常,或者函数是否返回,并继续外部下一个块。
BUILD_CLASS()
创建一个新的类对象。TOS 是方法字典,TOS1 是基类名称的元组,TOS2 是类名称。
SETUP_WITH(delta)
这个操作码在 with 块开始之前执行几个操作。首先,它__exit__()从上下文管理器加载并将其推送到堆栈上供WITH_CLEANUP. 然后, __enter__()被调用,并 推送指向delta的 finally 块。最后,调用 enter 方法的结果被压入堆栈。下一个操作码将忽略它 ( POP_TOP),或将其存储在 (a) 变量中 ( STORE_FAST, STORE_NAME, 或 UNPACK_SEQUENCE)。
WITH_CLEANUP()
当with语句块退出时清理堆栈。堆栈顶部有1–3个值,指示如何/为什么输入finally子句:
TOP = None
(TOP, SECOND) = (WHY_{RETURN,CONTINUE}), retval
TOP = WHY_*; no retval below it
(TOP, SECOND, THIRD) = exc_info()
在它们下面是EXIT,上下文管理器的_EXIT__()绑定方法。
在最后一种情况下,调用EXIT(TOP、SECOND、THIRD),否则调用EXIT(None、None、None)。
EXIT从堆栈中移除,使其上的值保持相同的顺序。此外,如果堆栈表示异常,并且函数调用返回“true”值,则此信息将被“zapped”,以防止END_最终重新引发异常。(但仍应恢复非本地GOTO。)
以下所有操作码都需要参数。一个参数是两个字节,最后是一个更重要的字节。
STORE_NAME( namei )
实施. namei是代码对象属性 中名称的索引。如果可能,编译器会尝试使用 或。name = TOSco_namesSTORE_FASTSTORE_GLOBAL
DELETE_NAME( namei )
实现,其中namei是 代码对象属性的索引。del nameco_names
UNPACK_SEQUENCE(计数)
将 TOS 解包为count 个单独的值,这些值从右到左放入堆栈。
DUP_TOPX(计数)
重复计数项目,使它们保持相同的顺序。由于实施限制,计数应介于 1 和 5 之间(包括 1 和 5)。
STORE_ATTR( namei )
实现,其中namei是 name 在 中的索引 。TOS.name = TOS1co_names
DELETE_ATTR( namei )
实现,使用namei作为索引到.del TOS.nameco_names
STORE_GLOBAL( namei )
用作STORE_NAME,但将名称存储为全局。
DELETE_GLOBAL( namei )
用作DELETE_NAME,但会删除全局名称。
LOAD_CONST( consti )
压co_consts[consti]入堆栈。
LOAD_NAME( namei )
将与关联的值压co_names[namei]入堆栈。
BUILD_TUPLE(count)
创建一个消耗堆栈中count 个项目的元组,并将生成的元组推送到堆栈上。
BUILD_LIST(count)
用作BUILD_TUPLE,但会创建一个列表。
BUILD_SET(count)
用作BUILD_TUPLE,但会创建一个集合。
2.7 版中的新功能。
BUILD_MAP(count)
将一个新的字典对象压入堆栈。字典预先调整大小以保存计数条目。
LOAD_ATTR( namei )
将 TOS 替换为.getattr(TOS, co_names[namei])
COMPARE_OP( opname)
执行布尔运算。操作名称可以在 cmp_op[opname].
IMPORT_NAME( namei )
导入模块co_names[namei]。TOS 和 TOS1 被弹出并提供 的fromlist和level参数__import__()。模块对象被压入堆栈。当前命名空间不受影响:对于正确的导入语句,后续STORE_FAST指令会修改命名空间。
IMPORT_FROM( namei )
co_names[namei]从 TOS 中找到的模块加载属性。结果对象被压入堆栈,随后由STORE_FAST指令存储 。
JUMP_FORWARD(delta)
按delta递增字节码计数器。
POP_JUMP_IF_TRUE(target)
如果 TOS 为真,则将字节码计数器设置为target。TOS 弹出。
POP_JUMP_IF_FALSE(target)
如果 TOS 为 false,则将字节码计数器设置为target。TOS 弹出。
JUMP_IF_TRUE_OR_POP(target)
如果 TOS 为真,则将字节码计数器设置为target并将 TOS 留在堆栈上。否则(TOS 为假),TOS 被弹出。
JUMP_IF_FALSE_OR_POP(target)
如果 TOS 为 false,则将字节码计数器设置为target并将 TOS 留在堆栈中。否则(TOS 为真),TOS 被弹出。
JUMP_ABSOLUTE(target)
将字节码计数器设置为target。
FOR_ITER(delta)
TOS是一个迭代器。调用它的next()方法。如果这产生了一个新值,则将其压入堆栈(将迭代器留在其下方)。如果迭代器指示它已耗尽,TOS则弹出,并且字节码计数器增加delta。
LOAD_GLOBAL( namei )
将全局变量co_names[namei]加载到堆栈上。
SETUP_LOOP(delta)
将一个循环块推送到块堆栈上。该块从具有增量字节大小的当前指令跨越。
SETUP_EXCEPT(delta)
将 try-except 子句中的 try 块压入块堆栈。delta 指向第一个 except 块。
SETUP_FINALLY(delta)
将 try-except 子句中的 try 块压入块堆栈。delta 指向 finally 块。
STORE_MAP()
将键值对存储在字典中。弹出键和值,同时将字典留在堆栈中。
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中的单元格的引用和自由变量存储。变量的名称是co_cellvars[i]如果i小于co_cellvars的长度。否则就是。co_freevars[i - len(co_cellvars)]
LOAD_DEREF(/)
加载包含在单元格的插槽i中的单元格和自由变量存储。将一个对单元格包含的对象的引用推送到堆栈中。
STORE_DEREF(/)
将 TOS 存储到包含在单元格的插槽i中的单元格和自由变量存储中。
SET_LINENO(lineno)
此操作码已过时。
RAISE_VARARGS( argc )
引发异常。argc表示 raise 语句的参数数量,范围从 0 到 3。处理程序将查找回溯为 TOS2,参数为 TOS1,异常为 TOS。
CALL_FUNCTION( argc )
调用可调用对象。argc的低字节表示位置参数的数量,高字节表示关键字参数的数量。堆栈包含顶部的关键字参数(如果有),然后是其下方的位置参数(如果有),然后是要在其下方调用的可调用对象。每个关键字参数在堆栈上用两个值表示:参数的名称和它的值,参数的值在堆栈上的名称上方。位置参数按照它们被传递到可调用对象的顺序推送,最右边的位置参数在顶部。 CALL_FUNCTION从堆栈中弹出所有参数和可调用对象,使用这些参数调用可调用对象,并推送可调用对象返回的返回值。
MAKE_FUNCTION( argc )
将一个新的函数对象压入堆栈。TOS 是与函数关联的代码。函数对象定义为具有argc默认参数,可在 TOS 下方找到。
MAKE_CLOSURE( argc )
创建一个新的函数对象,设置其func_closure槽,并将其压入堆栈。TOS 是与函数关联的代码,TOS1 是包含闭包自由变量单元格的元组。该函数还具有 argc默认参数,可在单元格下方找到。
BUILD_SLICE( argc )
将切片对象压入堆栈。 argc必须是 2 或 3。如果是 2, 则压入;如果是 3,则被推送。有关更多信息,请参阅内置函数。slice(TOS1, TOS)slice(TOS2, TOS1, TOS)slice()
EXTENDED_ARG(ext)
为任何参数太大而无法放入默认的两个字节的操作码添加前缀。 ext包含两个附加字节,它们与后续操作码的参数一起构成一个四字节参数,ext是两个最重要的字节。
CALL_FUNCTION_VAR( argc )
调用可调用对象,类似于CALL_FUNCTION。 argc表示关键字和位置参数的数量,与CALL_FUNCTION. 堆栈的顶部包含一个包含附加位置参数的可迭代对象。下面是关键字参数(如果有)、位置参数(如果有)和一个可调用对象,与CALL_FUNCTION. 在调用可调用对象之前,可迭代对象被“解包”,并将其内容附加到传入的位置参数。在计算 的值时忽略可迭代对象argc。
CALL_FUNCTION_KW( argc )
调用可调用对象,类似于CALL_FUNCTION。 argc表示关键字和位置参数的数量,与CALL_FUNCTION. 堆栈的顶部包含一个包含附加关键字参数的映射对象。下面是关键字参数(如果有)、位置参数(如果有)和一个可调用对象,与CALL_FUNCTION. 在调用 callable 之前,栈顶的映射对象被“解包”,其内容被附加到传入的关键字参数中。 在计算 的值时,栈顶的映射对象被忽略argc。
CALL_FUNCTION_VAR_KW( argc )
调用可调用对象,类似于CALL_FUNCTION_VAR和 CALL_FUNCTION_KW。 argc表示关键字和位置参数的数量,与CALL_FUNCTION. 堆栈的顶部包含一个映射对象,如 CALL_FUNCTION_KW。下面是一个可迭代对象,如 CALL_FUNCTION_VAR. 下面是关键字参数(如果有)、位置参数(如果有)和一个可调用对象,与CALL_FUNCTION. 在调用 callable 之前,映射对象和可迭代对象都被“解包”,它们的内容分别作为关键字和位置参数传入,与CALL_FUNCTION_VAR和相同CALL_FUNCTION_KW。在计算 的值时,映射对象和可迭代对象都被忽略argc。
HAVE_ARGUMENT()
这不是一个真正的操作码。它标识了不带参数的操作码和 带参数的操作码之间的分界线。< HAVE_ARGUMENT>= HAVE_ARGUMENT