Python pyc文件格式解析(下)
在上篇文章中分析到PyMarshal_ReadLastObjectFromFile内部的一个执行逻辑,主要是通过一个r_object递归解析pyc中的各个对象结构,随后构造成一个PyObject对象并将对象返回。随后一直返回到run_pyc_file中方法。
(https://github.com/python/cpython/blob/08b640e1575b75aeb605e72cb30c337480055b75/Python/pythonrun.c#L1271)
在这篇文章中将分析PyMarshal_ReadLastObjectFromFile之后的代码逻辑,分析python是如何执行PyObject的。
pyc运行环境搭建(下)
1、我们先写一段样例代码把它转成pyc来通过010editor观察:
Test.py
def check_command(cmd):
for char in cmd:
if char in ['"', ''', 'n']:
return False
return True
import os
cmd = input('ping> ')
if check_command(cmd):
os.system("ping "" + cmd + "" -n 1")
else:
print('No No No')
2、随后通过Python3.9将Test.py编译成pyc。
python -m py_compile Test.py
3、随后在__pycache__中找到Test.cpython-39.pyc,将其导入010editor。
来到python源码:
在PyMarshal_ReadLastObjectFromFile紧跟其后的是PyCode_Check的一个检查,随后直接传入run_eval_code_obj方法中。
4、通过定义得知作用是判断参数是不是PyCode类型,也就是说pyc的根对象必须是TYPE_CODE类型。
而我们的pyc中根对象确实是TYPE_CODE对象。
5、一同传入run_eval_code_obj的还有globals和locals,通过向上追溯在pyrun_simple_file函数中。
(https://github.com/python/cpython/blob/08b640e1575b75aeb605e72cb30c337480055b75/Python/pythonrun.c#L441)
发现它们都来自于同一个对象d,并在这之前有一些执行环境的初始化,包括:__file__、__cached__和set_main_loader。这些变量可以通过globals方法查看:
并且此时它们是同一个对象:
6、继续跟进run_eval_code_obj,开头先是设置了globals[‘__builtins__’],之后直接调用PyEval_EvalCode。
PyEval_EvalCode中初始化PyFrameConstructor和参数后调用_PyEval_Vector
在_PyEvalFramePushAndInit中将这些参数打包进frame,并传入_PyEval_EvalFrame
在_PyEval_EvalFrame中通过ThreadState中寻找frame执行函数,如果没有则执行默认的执行函数。我们走默认的frame执行函数。
https://github.com/python/cpython/blob/08b640e1575b75aeb605e72cb30c337480055b75/Python/ceval.c#L920
7、在方法开头有很多宏定义,对应着一些方法中将用到的功能:
8、从此开始是方法代码的开头
https://github.com/python/cpython/blob/08b640e1575b75aeb605e72cb30c337480055b75/Python/ceval.c#L1254C1-L1254C20
一个基础的变量复制:
9、我们的目标是得到python中opcode的结构,所以我们跟进PyBytes_AS_STRING。
不过这个函数并没有解析opcode,而是将对象中的数据提取出来,然后转换成了uint16_t指针。
typedef uint16_t _Py_CODEUNIT;
10、此时first_instr将指向第一条指令,而next_instr自然指向下一条指令。
跳过一系列初始化来到main_loop标签,可以看到是一个大循环。开头是一个全局解释器锁(GIL)用来判断是否需要因一些原因挂起。这部分详情参照Python GIL。
11、跳过一系列无关指令解析无关操作后来到,注释 /* Extract opcode and argument */处。首先通过NEXTOPARG获取将要执行的指令与参数:
12、涉及两个宏_Py_OPCODE与_Py_OPARG。格式很简单,按照小端低字节是opcode高字节是参数。
https://github.com/python/cpython/blob/3.9/Include/cpython/code.h
13、跳过一些宏定义来到switch (opcode),此处是opcode处理的地方了,这里每个case都是一个指令:
定义在opcode.h中
https://github.com/python/cpython/blob/3.9/Include/opcode.h
14、回到switch我们先看NOP指令,这个指令表示什么都不做。直接调用FAST_DISPATCH:
FAST_DISPATCH 是一个goto:
这样就又回到了之前的NEXTOPARG语句上。
我们会发现所有的指令都会在末尾加 FAST_DISPATCH或者DISPATCH。DISPATCH就是直接continue,相当于回到main_loop开头。多做了一个GIL锁的检测。
15、此时我们来看看我们的pyc文件中对应的code都是什么指令:
指令1
第一条指令是0x64也就是100,通过opcode.h可以找到这条指令是LOAD_CONST:
回到switch(opcode)找到LOAD_CONST:
开头有个PREDICTED,通过观察发现实际上是通过宏创建了个标签,格式是PRED_xxx。之后可以通过PREDICT来跳转到指令的位置执行。
之后是一个GETITEM,这个宏是从arg1的元组中获取一个成员,成员的下标来自于oparg。64 00 中的00就是这个下标。
通过010editor模板找到了这个consts对象,它也是一个CODE对象:
回到LOAD_CONST,此时取出的value就是这个TYPE_CODE对象,之后Py_INCREF对这个对象添加了一个引用防止被提前释放,随后PUSH压栈:
至此这个指令就算执行完了,就是将consts[0]的值压栈。
指令2
下一条指令依旧是LOAD_CONST,表示的是PUST(consts[1])。
但consts[1]是一个引用,这个ref_index对应着上篇文章中的ref_flag。上篇文章中没有详细讲解是如何寻找ref的。实际原理是每加载一个带有FLAG_REF的对象时就会将这个对象追加记录在某个数组中。随后ref_index就是这个数组的下标。由于对象内有子对象,子对象也可以携带FLAG_REF,因此必须深度优先遍历整个对象树才能确定引用的是哪个对象,人工搜索太耗时。
为了找到这些引用的具体对象,我完善了模板代码,现在可以找到这个对象。发现是一段字符串:check_command
那么这段代码的含义是PUSH(“check_command”)。
指令3
随后是0x84指令,对应的指令是MAKE_FUNCTION:
通过代码可以看出POP了check_command和consts[0]中的TYPE_CODE对象。分别赋值到了qualname 和codeobj ,最后传入PyFunction_NewWithQualName并返回了一个PyFunctionObject。
这一套流程很容易看出是创建了一个function 名字叫做check_command,并把它压栈了。
刚好对应的是我们Test.py的第一个函数。
指令4
然后是5A指令:
将 5A 00中的00作为下标names获得一个名称,names[0]引用的仍然是check_command。随后POP出了刚才创的函数。随后获得执行环境中的locals,将check_command放入了locals。
指令5&6
随后是紧跟这的两个LOAD_CONST分别加载2和3。
分别是0和None。
指令7
随后是6C指令:
同样的按照指令参数01下标寻找names,找到的是”os”。
然后POP出了刚才一个0和一个None,一并传入import_name。
最后通过SET_TOP将import_name的返回值设置到了栈顶。
这条几条指令相当于Test.py中的import os。
总 结
由于篇幅问题,之后的指令不再继续手动分析。
由于时间问题没有做010editor中pyc3.9模板的指令分析部分,但添加了ref的解析。
这就是从解析pyc到执行opcode的基本流程,包括pyc的文件解析、常见的opcode和python虚拟机的原理与代码实现,关于python的其他特性(如await)需要更加深入的分析。此pyc的文件解析教程到此结束。
向上滑动查看源码
//------------------------------------------------
//--- 010 Editor v3.0.4 Binary Template
//
// File: PYC.bt
// Authors: Kuang-che Wu
// Version: 1.1
// Purpose: Parse python bytecode .pyc and .pyo files,
// support python 2.4 to 2.7, 3.9.
// Category: Programming
// File Mask: *.pyc,*.pyo
// ID Bytes:
// History:
// 1.2 2023-10-17 Srinater:Support 3.9 file format, TODO:3.9 Instructions
// 1.1 2016-02-02 SweetScape: Updated header for repository submission.
// 1.0 2009-04-02 K Wu: Initial release.
//------------------------------------------------
local int ref_objs[100000];
local int ref_index= 0;
local int ref_lock = false;
enumMagicValue {
PY_24a0 = 62041,
PY_24a3 = 62051,
PY_24b1 = 62061,
PY_25a0_1 = 62071,
PY_25a0_2 = 62081,
PY_25a0_3 = 62091,
PY_25a0_4 = 62092,
PY_25b3_1 = 62101,
PY_25b3_2 = 62111,
PY_25c1 = 62121,
PY_25c2 = 62131,
PY_26a0 = 62151,
PY_26a1 = 62161,
PY_27a0_1 = 62171,
PY_27a0_2 = 62181,
PY_39 = 3425,
};
enumObjTypeOrigin {
TYPE_NULL= '0',
TYPE_NONE= 'N',
TYPE_FALSE= 'F',
TYPE_TRUE= 'T',
TYPE_STOPITER= 'S',
TYPE_ELLIPSIS= '.',
TYPE_INT= 'i',
TYPE_INT64= 'I',
TYPE_FLOAT= 'f',
TYPE_BINARY_FLOAT= 'g',
TYPE_COMPLEX= 'x',
TYPE_BINARY_COMPLEX= 'y',
TYPE_LONG= 'l',
TYPE_STRING= 's',
TYPE_INTERNED= 't',
TYPE_STRINGREF= 'R',
TYPE_TUPLE= '(',
TYPE_LIST= '[',
TYPE_DICT= '{',
TYPE_CODE= 'c',
TYPE_UNICODE= 'u',
TYPE_UNKNOWN= '?',
TYPE_SET= '<',
TYPE_FROZENSET= '>',
TYPE_ASCII = 'a',
TYPE_ASCII_INTERNED = 'A',
TYPE_SMALL_TUPLE = ')',
TYPE_SHORT_ASCII = 'z',
TYPE_SHORT_ASCII_INTERNED='Z',
TYPE_REF = 'r',
};
// marshal obj type of version 2
// version 2 is backward compatible to version 1 (for read)
struct ObjType{
ObjTypeOrigin origin_type : 7;
unsigned char ref_flag : 1;
if (ref_flag == 1 && ref_lock == false)
{
ref_objs[ref_index] = FTell() - 1;
ref_index += 1;
}
};
// Python/import.c
struct Magic {
MagicValue magic1;
char magic2[2];
if (magic2 != "x0dx0a") {
Warning("bad magic");
return 0;
}
if (EnumToString(magic1) == "") {
Warning("Unknown magic version");
return 0;
}
};
// opcode.h
// this is opname of python 2.4
// please add new opcode in ReadInstruction()
enumOpCode {
STOP_CODE= 0,
POP_TOP= 1,
ROT_TWO= 2,
ROT_THREE= 3,
DUP_TOP= 4,
ROT_FOUR= 5,
UNARY_POSITIVE= 10,
UNARY_NEGATIVE= 11,
UNARY_NOT= 12,
UNARY_CONVERT= 13,
UNARY_INVERT= 15,
LIST_APPEND= 18,
BINARY_POWER= 19,
BINARY_MULTIPLY= 20,
BINARY_DIVIDE= 21,
BINARY_MODULO= 22,
BINARY_ADD= 23,
BINARY_SUBTRACT= 24,
BINARY_SUBSCR= 25,
BINARY_FLOOR_DIVIDE = 26,
BINARY_TRUE_DIVIDE = 27,
INPLACE_FLOOR_DIVIDE = 28,
INPLACE_TRUE_DIVIDE = 29,
SLICE= 30,
/* Also uses 31-33 */
SLICE_a= 31,
SLICE_b= 32,
SLICE_c= 33,
STORE_SLICE= 40,
/* Also uses 41-43 */
STORE_SLICE_a= 41,
STORE_SLICE_b= 42,
STORE_SLICE_c= 43,
DELETE_SLICE= 50,
/* Also uses 51-53 */
DELETE_SLICE_a= 51,
DELETE_SLICE_b= 52,
DELETE_SLICE_c= 53,
INPLACE_ADD= 55,
INPLACE_SUBTRACT= 56,
INPLACE_MULTIPLY= 57,
INPLACE_DIVIDE= 58,
INPLACE_MODULO= 59,
STORE_SUBSCR= 60,
DELETE_SUBSCR= 61,
BINARY_LSHIFT= 62,
BINARY_RSHIFT= 63,
BINARY_AND= 64,
BINARY_XOR= 65,
BINARY_OR= 66,
INPLACE_POWER= 67,
GET_ITER= 68,
PRINT_EXPR= 70,
PRINT_ITEM= 71,
PRINT_NEWLINE= 72,
PRINT_ITEM_TO = 73,
PRINT_NEWLINE_TO = 74,
INPLACE_LSHIFT= 75,
INPLACE_RSHIFT= 76,
INPLACE_AND= 77,
INPLACE_XOR= 78,
INPLACE_OR= 79,
BREAK_LOOP= 80,
WITH_CLEANUP = 81,
LOAD_LOCALS= 82,
RETURN_VALUE= 83,
IMPORT_STAR= 84,
EXEC_STMT= 85,
YIELD_VALUE= 86,
POP_BLOCK= 87,
END_FINALLY= 88,
BUILD_CLASS= 89,
STORE_NAME= 90,/* Index in name list */
DELETE_NAME= 91,/* "" */
UNPACK_SEQUENCE= 92,/* Number of sequence items */
FOR_ITER= 93,
STORE_ATTR= 95,/* Index in name list */
DELETE_ATTR= 96,/* "" */
STORE_GLOBAL= 97,/* "" */
DELETE_GLOBAL= 98,/* "" */
DUP_TOPX= 99,/* number of items to duplicate */
LOAD_CONST= 100,/* Index in const list */
LOAD_NAME= 101,/* Index in name list */
BUILD_TUPLE= 102,/* Number of tuple items */
BUILD_LIST= 103,/* Number of list items */
BUILD_MAP= 104,/* Always zero for now */
LOAD_ATTR= 105,/* Index in name list */
COMPARE_OP= 106,/* Comparison operator */
IMPORT_NAME= 107,/* Index in name list */
IMPORT_FROM= 108,/* Index in name list */
JUMP_FORWARD= 110,/* Number of bytes to skip */
JUMP_IF_FALSE= 111,/* "" */
JUMP_IF_TRUE= 112,/* "" */
JUMP_ABSOLUTE= 113,/* Target byte offset from beginning of code */
LOAD_GLOBAL= 116,/* Index in name list */
CONTINUE_LOOP= 119,/* Start of loop (absolute) */
SETUP_LOOP= 120,/* Target address (relative) */
SETUP_EXCEPT= 121,/* "" */
SETUP_FINALLY= 122,/* "" */
LOAD_FAST= 124,/* Local variable number */
STORE_FAST= 125,/* Local variable number */
DELETE_FAST= 126,/* Local variable number */
RAISE_VARARGS= 130,/* Number of raise arguments (1, 2 or 3) */
/* CALL_FUNCTION_XXX opcodes defined below depend on this definition */
CALL_FUNCTION= 131,/* #args + (#kwargs<<8) */
MAKE_FUNCTION= 132,/* #defaults */
BUILD_SLICE = 133,/* Number of items */
MAKE_CLOSURE = 134, /* #free vars */
LOAD_CLOSURE = 135, /* Load free variable from closure */
LOAD_DEREF = 136, /* Load and dereference from closure cell */
STORE_DEREF = 137, /* Store into cell */
/* The next 3 opcodes must be contiguous and satisfy
(CALL_FUNCTION_VAR - CALL_FUNCTION) & 3 == 1 */
CALL_FUNCTION_VAR = 140,/* #args + (#kwargs<<8) */
CALL_FUNCTION_KW = 141,/* #args + (#kwargs<<8) */
CALL_FUNCTION_VAR_KW = 142,/* #args + (#kwargs<<8) */
/* Support for opargs more than 16 bits long */
EXTENDED_ARG = 143,
};
// ceval.c
const int HAVE_ARGUMENT = 90;
const int EXTENDED_ARG = 143;
struct Instruction {
if (ReadUByte(FTell()) == EXTENDED_ARG) {
ubyte opcode_extended_arg;
uint16 oparg_hi;
ubyte opcode;
if (opcode >= HAVE_ARGUMENT)
uint16 oparg;
} else {
ubyte opcode;
if (opcode >= HAVE_ARGUMENT)
uint16 oparg;
}
};
typedef int32 r_long;
typedef int64 r_long64;
typedef int16 r_short;
typedef ubyte r_byte;
struct Code {
ObjType type;
if (type.origin_type != TYPE_STRING) {
Warning("code not in string type");
Exit(1);
}
r_long n;
local int remain = n;
local int end = FTell() + n;
/* trick to optimize parse speed */
while (remain >= 6) {
Instruction inst[remain/6];
remain = end - FTell();
}
remain = end - FTell();
while (remain > 0) {
Instruction inst;
remain -= sizeof(inst);
}
};
string Opcode2Opname(OpCode opcode)
{
uint16 magic = file.magic.magic1;
local string opname = EnumToString(opcode);
if (magic >= 0) { // history between python 2.0 and 2.4
// r27197
if (opcode == 114) opname = "";
// r28249
if (opcode == 81) opname = "RETURN_NONE";
// r28494
if (opcode == 81) opname = "";
// r32346
if (opcode == 9) opname = "NOP";
// r32389
if (opcode == 9) opcode = "";
// r35378
if (opcode == 18) opname = "LIST_APPEND";
// r36216
if (opcode == 9) opname = "NOP";
}
// magic 62041 r36242 marshal version 1
// magic 62051 r37112
// magic 62061 r37403
// magic 62071 r38931 marshal version 2
// magic 62081 r39773
if (magic >= 62091) { // r42624
// r42624
if (opcode == 81) opname = "WITH_CLEANUP";
}
// magic 62092 r42952
// magic 62101 r50600
// magic 62111 r50968
// magic 62121 r51082
// magic 62131 r51729
if (magic >= 62151) { // r59548
// r59548
if (opcode == 54) opname = "STORE_MAP";
}
// magic 62161 r61290
if (magic >= 62171) { // r67818
// r67818
if (opcode == 18) opname = "";
if (opcode == 94) opname = "LIST_APPEND";
}
if (magic >= 62181) { // r70071
// r70071
if (opcode == 111) opname = "JUMP_IF_FALSE_OR_POP";
if (opcode == 112) opname = "JUMP_IF_TRUE_OR_POP";
if (opcode == 114) opname = "POP_JUMP_IF_FALSE";
if (opcode == 115) opname = "POP_JUMP_IF_TRUE";
}
return opname;
}
string ReadInstruction(Instruction &ins)
{
string s;
uint16 magic = file.magic.magic1;
OpCode opcode = (OpCode)ins.opcode;
string opname = Opcode2Opname(opcode);
if (exists(ins.oparg)) {
uint32 oparg = ins.oparg;
if (exists(ins.oparg_hi))
oparg += (uint32)ins.oparg_hi << 16;
// Note, COMPARE_OP oparg change name in r24970
if (opname == "COMPARE_OP") {
string cmp_op;
switch (oparg) {
case 0: cmp_op = "<"; break;
case 1: cmp_op = "<="; break;
case 2: cmp_op = "=="; break;
case 3: cmp_op = "!="; break;
case 4: cmp_op = ">"; break;
case 5: cmp_op = ">="; break;
case 6: cmp_op = "in"; break;
case 7: cmp_op = "not in"; break;
case 8: cmp_op = "is"; break;
case 9: cmp_op = "is not"; break;
case 10: cmp_op = "exception match"; break;
case 11: cmp_op = "BAD"; break;
}
SPrintf(s, "%s (%s)", opname, cmp_op);
} else {
SPrintf(s, "%s %d", opname, oparg);
}
} else {
s = opname;
}
return s;
}
struct LnoTab {
ObjType type;
if (type.origin_type != TYPE_STRING) {
Warning("lnotab not in string type");
Exit(1);
}
r_long n;
if (n)
{
struct {
uchar bytecode_offset_diff;
uchar line_diff;
} pair[n/2];
}
};
// Python/marshal.c
typedef struct r_object {
ObjType type;
switch (type.origin_type) {
case TYPE_NULL:
case TYPE_NONE:
case TYPE_STOPITER:
case TYPE_ELLIPSIS:
case TYPE_FALSE:
case TYPE_TRUE:
break;
case TYPE_INT:
r_long value;
break;
case TYPE_INT64:
r_long64 value;
break;
case TYPE_LONG:
r_long n;
local int size = n<0?-n:n;
r_short digit[size];
break;
case TYPE_FLOAT:
r_byte n;
char value[n];
break;
case TYPE_BINARY_FLOAT:
double value;
break;
case TYPE_COMPLEX:
r_byte nr;
char real[nr];
r_byte ni;
char imag[ni];
break;
case TYPE_BINARY_COMPLEX:
double real;
double imag;
break;
case TYPE_INTERNED:
case TYPE_STRING:
r_long n;
if (n)
char str[n];
break;
case TYPE_STRINGREF:
r_long n;
break;
case TYPE_TUPLE:
r_long n;
if (n)
struct r_object elements[n];
break;
case TYPE_LIST:
r_long n;
if (n)
struct r_object elements[n];
break;
case TYPE_DICT:
while (1) {
struct r_object key;
if (key.type == TYPE_NULL)
break;
struct r_object val;
}
break;
case TYPE_SET:
case TYPE_FROZENSET:
r_long n;
if (n)
struct r_object elements[n];
break;
case TYPE_CODE:
r_long argcount;
if (magic.magic1 == PY_39)
{
r_long posonlyargcount;
r_long kwonlyargcount;
}
r_long nlocals;
r_long stacksize;
r_long flags;
//struct r_object code;
Code code;
struct r_object consts;
struct r_object names;
struct r_object varnames;
struct r_object freevars;
struct r_object cellvars;
struct r_object filename;
struct r_object name;
r_long firstlineno;
//struct r_object lnotab;
struct LnoTab lnotab;
break;
case TYPE_ASCII_INTERNED:
case TYPE_ASCII:
r_long n;
if (n)
char str[n];
break;
case TYPE_SMALL_TUPLE:
unsigned char n;
if (n)
struct r_object elements[n];
break;
case TYPE_SHORT_ASCII_INTERNED:
case TYPE_SHORT_ASCII:
unsigned char n;
if (n)
char str[n];
break;
case TYPE_REF:
r_long ref_index;
break;
case TYPE_UNICODE:
r_long n;
if (n)
char unicode[n];
break;
default:
Warning("unknown type code");
Exit(1);
}
};
struct RefShow
{
ref_lock = true;
local int i;
for (i = 0; i < ref_index; ++i)
{
FSeek(ref_objs[i]);
r_object obj;
}
Exit(0);
};
struct {
Magic magic;
if (magic.magic1 == PY_39)
long skip[3];
else
char mtime[4];
r_object data;
if (magic.magic1 == PY_39)
RefShow refs;
} file;
END
原文始发于微信公众号(锋刃科技):Python pyc文件格式解析(下)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论