解释运行程序 回忆上次内容 我们这次设置了断点 设置断点的目的是更快地调试 调试的目的是去除 bug 别害怕 bug 一步步地总能找到 bug 这就是程序员基本功 我心中还是有疑问 python3 是怎么解释 hello.py 的? 纯文本 「oeasy」python0010 - python虚拟机解释执行py文件的原理 print("1982------Guido in cwi")
print("1995------Guido in cnri")
print("2000------Guido in beopen")
print("2005------Guido in google")
print("2012------Guido in dropbox")
print("2020------Guido in microsoft")
传统文本 「oeasy」python0010 - python虚拟机解释执行py文件的原理 在字符的基础上组织起篇章结构 字组成词 词组成句 句组成段 段组成章节 最后成书 tokenize 首先把一个个字符组成词 分析一下哪些字可以组成词 术语叫词法分析(lexical analysis) 「oeasy」python0010 - python虚拟机解释执行py文件的原理 「oeasy」python0010 - python虚拟机解释执行py文件的原理 token 「oeasy」python0010 - python虚拟机解释执行py文件的原理 古人说听我号令 怎么把源文件变成一个词(token)流呢? python3模块 帮助手册里面有这个内容 这个tokenize是python3的一个模块(module) 「oeasy」python0010 - python虚拟机解释执行py文件的原理 token流 「oeasy」python0010 - python虚拟机解释执行py文件的原理 我们尝试运行 python3 -m tokenize guido.py 对guido.py进行词法分析 分析出来的词(token)流什么样子呢? 这个词的流怎么理解呢? token流 第0行设置了编码格式 第1行[0,5)字符是第1行第1个token 第1行[5,6)字符是第1行第2个token 第1行[6,30)字符是第1行第3个token "1982------Guido in cwi" 这是一个String(字符串) 第1行[30,31)字符是第1行第4个token 第1行[31,32)字符是第1行第5个token \n \n是一个NewLine(换行符) 换行符意味着第一行结束 「oeasy」python0010 - python虚拟机解释执行py文件的原理 组词 词分析出来就是怎么组词的问题 生成一棵抽象语法树 AST(Abstract Syntax Tree) 「oeasy」python0010 - python虚拟机解释执行py文件的原理 引入ast模块 「oeasy」python0010 - python虚拟机解释执行py文件的原理 流程 先把这个ast模块导入(import)进来 第一句就是import ast 回车之后没有任何报错 那就是执行成功了 后面也一样 没有报错就是执行成功了 「oeasy」python0010 - python虚拟机解释执行py文件的原理 然后读取guido.py并送到s 然后对于s进行语法分析(parse) 「oeasy」python0010 - python虚拟机解释执行py文件的原理 再把分析(parse)的结果进行转储(dump) 看起来有点乱 升级Python 目前lanqiao.cn上面的python是3.8 这个清晰缩进的格式需要在3.9以上完成 需要升级 sudo apt update
sudo apt install python3.9
「oeasy」python0010 - python虚拟机解释执行py文件的原理 缩进换行 「oeasy」python0010 - python虚拟机解释执行py文件的原理 这个就是把词组成语法树的样子 如何理解这棵树呢? 我们看一个例子 表达式运算 「oeasy」python0010 - python虚拟机解释执行py文件的原理 「oeasy」python0010 - python虚拟机解释执行py文件的原理 前两个先结合 得到的结果作为下一个运算的左操作数 然后和第3个结合 结合序 「oeasy」python0010 - python虚拟机解释执行py文件的原理 「oeasy」python0010 - python虚拟机解释执行py文件的原理 后两个会先结合 得到的结果作为下一个运算的右操作数 然后再和1进行加法运算 有了语法树 这棵语法树我们能看懂 但是cpu需要的是能执行的一条条字节码指令 翻译成字节码 要把源程序翻译成字节码才能执行 怎么把ast转化为字节码(指令)呢? 从一种语言到另一种语言 compile 「oeasy」python0010 - python虚拟机解释执行py文件的原理 编译结果 编译(compile)之后得到是字节码指令文件 所以扩展名是pyc 其中c代表compiled pyc是字节码(bytecode)文件 python虚拟机的虚拟cpu就可以直接执行了 「oeasy」python0010 - python虚拟机解释执行py文件的原理 先看看这个pyc文件 注意他在__pycache__文件夹下 cache的意思是缓存 pycache两端各有2条下划线(_) 「oeasy」python0010 - python虚拟机解释执行py文件的原理 进入__pycache__文件夹 「oeasy」python0010 - python虚拟机解释执行py文件的原理 得到的字节码看起来完全是乱码 vi打开这个这个pyc文件 二进制形态 「oeasy」python0010 - python虚拟机解释执行py文件的原理 这样看到了他的字符串形态 可以看到他的二进制字节形态么? 机器语言 「oeasy」python0010 - python虚拟机解释执行py文件的原理 指令 「oeasy」python0010 - python虚拟机解释执行py文件的原理 最早指的是教的行为或者过程 计算机领域里面特指指令 比如加法指令 减法指令 可以让cpu做特定运算的指令 「oeasy」python0010 - python虚拟机解释执行py文件的原理 由于计算机只认识0和1 所以要把这些加加减减的指令 对应到0和1的二进制形态上去 0和1的二进制形态我们记不住 于是有了汇编助记符 助记符告诉我们这条0和1的二进制形态 到底对应什么指令 助记符的语言就是汇编语言 汇编assemble 「oeasy」python0010 - python虚拟机解释执行py文件的原理 assemble指的是收集、集结 在计算机中特指汇编语言 可以让我们把0和1的机器指令 收集起来形成的助记符集合 就是汇编语言指令集 「oeasy」python0010 - python虚拟机解释执行py文件的原理 反编译 disassemble 这个词由两部分组成 dis (反着来的) dislike disgrace disagree assembler (汇编语言) 把py源文件编译成的字节码(指令)我们人类看不明白 把这些字节码(指令)反编译(disassemble)成汇编语言助记符 有了助记符我们就知道指令的含义了 「oeasy」python0010 - python虚拟机解释执行py文件的原理 反编译(dis) python3 -m dis guido.py -m 代表使用模块 dis 代表反编译(disassemble) 「oeasy」python0010 - python虚拟机解释执行py文件的原理 我们可以看见 前面是行号 每行对应4条指令 LOAD_NAME 装载(函数)名字 LOAD_CONST 装载常量 CALL_FUNCTION 调用函数 POP_TOP 弹栈 总共6句 那具体这个 LOAD_NAME 是要做些什么呢? 指令 LOAD_NAME 把一个值压入堆栈co_names 把print这个函数名压入了堆栈 一会儿就要调用这个被压入堆栈的print函数 「oeasy」python0010 - python虚拟机解释执行py文件的原理 python源头 「oeasy」python0010 - python虚拟机解释执行py文件的原理 python 是开源编程语言 整个的源代码都是开放的 我们可以去github找到他的源代码 https://github.com/python/cpython 二进制状态 搜索LOAD_NAME并且排查 指令对应着一个字节码状态值 https://github.com/python/cpython/blob/main/Lib/opcode.py 「oeasy」python0010 - python虚拟机解释执行py文件的原理 4条指令 指令助记符 指令含义 十进制状态 十六进制状态 LOAD_NAME 装载函数名称 101 0x65 LOAD_CONST 装载参数 100 0x64 CALL_FUNCTION 调用函数 142 0x8e POP_TOP 弹栈返回 1 0x01
「oeasy」python0010 - python虚拟机解释执行py文件的原理 好像找到了 64XX 64 00是从表中的00号位置取得字符串"Guido in cwi" 64 01是从表中的01号位置取字符串"Guido in cnri" ... 以此类推,直到05 83取出字符串"Guido in microsoft" 0x83 对应的是 GET_AWAITABLE 那这些二进制代码究竟是什么指令集的呢? 首先我们得弄懂什么是指令集呢? 指令集 「oeasy」python0010 - python虚拟机解释执行py文件的原理 上图是arm的指令集 也常被称作arm架构 那什么又是架构呢? architect 「oeasy」python0010 - python虚拟机解释执行py文件的原理 「oeasy」python0010 - python虚拟机解释执行py文件的原理 架构师 「oeasy」python0010 - python虚拟机解释执行py文件的原理 「oeasy」python0010 - python虚拟机解释执行py文件的原理 虚拟机的虚拟cpu 「oeasy」python0010 - python虚拟机解释执行py文件的原理 总结 我们把python源文件 词法分析 得到 词流(token stream) 语法分析 得到 抽象语法树(Abstract Syntax Tree) 编译 得到 字节码 (bytecode) 字节码我们看不懂 所以反编译 得到 指令文件(opcode) 「oeasy」python0010 - python虚拟机解释执行py文件的原理 指令文件是基于python虚拟机的虚拟cpu的指令集 什么是python虚拟机呢? 我们下次再说 蓝桥->https://www.lanqiao.cn/teacher/3584 github->https://github.com/overmind1980/oeasy-python-tutorial gitee->https://gitee.com/overmind1980/oeasypython 视频->https://www.bilibili.com/video/BV1CU4y1Z7gQ 作者:oeasy