Python文件结构与逆向分析

目录

前提概要:

运行原理和目录基础理解:

pyc文件结构分析:

Python编译运行及反汇编:

Python内置模块dis.py源码详解:

Python字节码解读:

反汇编dis模块:(python2文档)

dis模块定义的函数和常量:

dis模块调用解读:

Python 字节码指令:


前提概要:

本篇内容都是在理解了博客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

  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沐一 · 林

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值