Python 代码先被编译为字节码后,再由Python虚拟机来执行字节码, Python的字节码是一种类似汇编指令的中间语言, 一个Python语句会对应若干字节码指令,虚拟机一条一条执行字节码指令, 从而完成程序执行。
Python dis 模块支持对Python代码进行反汇编, 生成字节码指令。
dis.dis()
将CPython字节码转为可读的伪代码(类似于汇编代码)。结构如下:
其实就是这样的结构:
LOAD_CONST
加载const
变量,比如数值、字符串等等,一般用于传给函数的参数
转为python代码就是:
LOAD_FAST
一般加载局部变量的值,也就是读取值,用于计算或者函数调用传参等。
STORE_FAST
一般用于保存值到局部变量。
这段bytecode转为python就是:
函数的形参也是局部变量,如何区分出是函数形参还是其他局部变量呢?
形参没有初始化,也就是从函数开始到LOAD_FAST
该变量的位置,如果没有看到STORE_FAST
,那么该变量就是函数形参。
而其他局部变量在使用之前肯定会使用STORE_FAST
进行初始化。
具体看下面的实例:
对应的python代码如下,对比一下就一目了然。
LOAD_GLOBAL
用来加载全局变量,包括指定函数名,类名,模块名等全局符号。
STORE_GLOBAL
用来给全局变量赋值。
对应的python代码
BUILD_LIST
用于创建一个list结构。
对应python代码是:
另外再看看一种常见的创建list的方式如下:
一个实例bytecode如下:
转为python代码是:
BUILD_MAP
用于创建一个空的dict。STORE_MAP
用于初始化dict的内容。
对应的python代码是:
再看看修改dict的bytecode:
对应的python代码是:
BUILD_SLICE
用于创建slice。对于list、元组、字符串都可以使用slice的方式进行访问。
但是要注意BUILD_SLICE
用于[x:y:z]这种类型的slice,结合BINARY_SUBSCR
读取slice的值,结合STORE_SUBSCR
用于修改slice的值。
另外SLICE+n
用于[a:b]类型的访问,STORE_SLICE+n
用于[a:b]类型的修改,其中n
表示如下:
下面看具体实例:
SETUP_LOOP
用于开始一个循环。SETUP_LOOP 26 (to 35)
中35
表示循环退出点。
对应python代码是:
这是典型的for+in结构,转为python代码就是:
POP_JUMP_IF_FALSE
和JUMP_FORWARD
一般用于分支判断跳转。POP_JUMP_IF_FALSE
表示条件结果为FALSE
就跳转到目标偏移指令。JUMP_FORWARD
直接跳转到目标偏移指令。
转为python代码是:
前面介绍第二列表示指令在函数中的偏移地址,所以看到0就是函数开始,下一个0前一条指令就是函数结束位置,当然也可以通过RETURN_VALUE
来确定函数结尾
函数调用类似于push+call
的汇编结构,压栈参数从左到右依次压入(当然不是push
,而是读取指令LOAD_xxxx
来指定参数)。
函数名一般通过LOAD_GLOBAL
指令指定,如果是模块函数或者类成员函数通过LOAD_GLOBAL
+LOAD_ATTR
来指定。
先指定要调用的函数,然后压参数,最后通过CALL_FUNCTION
调用。
CALL_FUNCTION
后面的值表示有几个参数。
支持嵌套调用:
这段bytecode
转换成python
代码就是
其他常见指令,一看就明白,就不具体分析了,更多详细内容请看官方文档。
基础运算还有一套对应的BINARY_xxxx
指令,两者区别很简单。
欢迎有兴趣的朋友关注公众号:汉客儿
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)