收藏 分享(赏)

计算机应用自己动手实现Lua虚拟机编译器和标准库张秀宏2018-9-27.pdf

上传人:空登山 文档编号:7027997 上传时间:2022-08-28 格式:PDF 页数:424 大小:209.11MB
下载 相关 举报
计算机应用自己动手实现Lua虚拟机编译器和标准库张秀宏2018-9-27.pdf_第1页
第1页 / 共424页
计算机应用自己动手实现Lua虚拟机编译器和标准库张秀宏2018-9-27.pdf_第2页
第2页 / 共424页
计算机应用自己动手实现Lua虚拟机编译器和标准库张秀宏2018-9-27.pdf_第3页
第3页 / 共424页
计算机应用自己动手实现Lua虚拟机编译器和标准库张秀宏2018-9-27.pdf_第4页
第4页 / 共424页
计算机应用自己动手实现Lua虚拟机编译器和标准库张秀宏2018-9-27.pdf_第5页
第5页 / 共424页
亲,该文档总共424页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述

1、版权相关注意事项:1、书籍版权归著者和出版社所有2、本PDF来自于各个广泛的信息千台,经过整理而成3、本PDF仅限用于非商业用途或者个人交流研究学习使用4、本PDF获得者不得在互联网上以任何目的进行传播5、如果觉得书籍内容很赞,请一定购买正版实体书,多多支持编写高质量的图书的作者和相应的出版社!当然,如果图书内容不堪入目,质量低下,你也可以选择狠狠滴撕裂本PDF6、技术类书籍是拿来获取知识的,不是拿来收藏的,你得到了书籍不意味着你得到了知识,所以请不要得到书籍后就觉得沾沾自喜,要经常翻阅!经常翻阅7、请于下载PDF后24小时内研究使用并删掉本PDF仅供Ii商业用i主或交流学习使用军自己动手写J

2、av剑盟以机作者耗时2年撰写,用Go;fDJava从零开始实现一门完整的脚本语言深入霸述Luai吾言的应当时凡、编译器、标准库以及核心语法的实现院里,全方主展示Lua内部工惘几制M科技依甲运PJ WRT/ Yon OWN LUA VM、CO 问PIL RNp ST口Lua5.3 Reference Manual 口TheEvolution of Lua口TheImplementation of Lua 5.0口ANo-Frills Introduction to Lua 5.1 VM Instructions 口Lua5.3 Bytecode Reference除此之外,笔者在本书的写作过程中

3、还查阅了网络上(特别是StackOver组ow和Wikipedia)的各种相关资料,这里就不一一罗列了。如果读者需要了解Go语法和标准库,请访问https:/ go/ chOl/src/luago/ ch02/src/luago/ ch21/src/luago/ java/ ch02/ 、2. . ch18/ lua/ ch02/ ch21/ VII 其中Go语言部分是Lua解释器实现代码,每章为一个子目录,可以单独编译和运行(详见第1章)。Lua语言部分也是每章一个目录,里面包含每一章的Lua示例代码和测试脚本。Java语言部分是Lua解释器的Java版实现代码,仅供读者参考。Java版实现

4、只提供了前18章的代表,剩下的3章留给读者作为练习。如果读者对Git比较熟悉,希望每次将注意力集中在某一章的代码上,也可以使用gitcheckout命令单独检出某一章的代码。本书为每一章都创建了对应的分支,例如,第1章的代码在chOl分支里,以此类推。勘误和支持受笔者技术水平和表达能力所限,本书并非尽善尽美,如有不合理之处,恳请读者批评指正。由于时间仓促,书中也难免会存在一些疏漏之处,还请读者谅解。本书的勘误将通过https:/ . . 目录C.e11tc1,。目。吕第一部分准备第1章准备工你.3 1.1 准备开发环境.3 1.1.1 操作系统.3 1.1.2 安装Lua41.1 3 安装Go

5、4 1.2 准备目录结构.4 1.3 本章小结.6 第二部分Lua虚拟机和LuaAPI第2章二进制chunk. 9 2.1 什么是二进制chunk. 10 2.2 luac命令介绍.11 2.2. l 编译Lua源文件.11 2.2.2 查看二进制chunk. 13 2.3 二进制chunk格式152.3.l 数据类型16 2.3.2 总体结构.17 2.3.3 头部.18 2.3.4 函数原型.22 2.4解析二进制chur业272.4.l 读取基本数据类型.28 2.4.2 检查头部292.4.3 读取函数原型302.5 测试本章代码.33 2.6 本章小结36第3章指令集.37 3.1

6、指令集介绍3.2 指令编码格式.38 3.2.l 编码模式383.2.2 操作码393.2.3 操作数403.2.4 指令表413.3 指令解码423.4测试本章代码443.5 本章小结47第4章LuaAPI494.1 LuaAPI介绍.49 4.2 Lua战514.2.l Lua数据类型和值.51 4.2.2战索引544.2.3 定义luaStack结构体.54 4.3 Lua State 57 4.3.l 定义LuaState接口.57 4.3.2 定义luaState结构体584.3.3 基础桔操纵方法.59 4.3.4 Push方法.64 4.3.5 Access方法.65 4.4 测

7、试本章代码.69 4.5 本章小结70第5章Lua运算符715.1 Lua运算符介绍.71 5.2 自动类型转换.75 5.3扩展LuaState接口.79 5.3. l Arith()方法.80 5.3.2 Compare()方法.83 5.3.3 Len()方法855.3.4 Concat()方法865.4测试本章代码.87 日本章小结m第6章虚拟机雏形四6.1 添加LuaVM接口四6.1.1 定义LuaVM接口.91 6.1.2 改造JuaState结构体926.1.3 实现LuaVM接口.93 6.2 实现Lua虚拟机指令946.2.I 移动和跳转指令946.2.2 加载指令96| 6

8、.2.3 算术运算指令.101 6.2.4 长度和拼接指令1036.2.5 比较指令1066.2.6 逻辑运算指令.107 6.2.7 for循环指令l10 6.3 指令分派.113 6.4测试本章代码.115 6.5 本章小结.118 第7章表川7.1 表介绍.119 7.2 表内部实现1217.3 表相关API1257 3.1 Get方法.126 7.3.2 Set方法1297.4 表相关指令m7.4.1 NEWTABLE131 7.4.2 GETTABLE. 133 7.4.3 SETTABLE. 135 7.4.4 SETLIST136 7.5 测试本章代码1387.6本章小结.140

9、 第8章函数调用.141 8.1 函数调用介绍1418.2 函数调用枝1438.2.1 调用帧实现144 8.2.2 调用战实现1458.3 函数调用API1478 3.1 Load()148 x 8 3.2 Call()149 8.4 函数调用指令1528.4.1 CLOSURE152 8.4.2 CALL153 8.4.3 RETURN157 8.4.4 VARARG158 8.4.5 TAILCALL. 159 8.4.6 SELF. 160 8.4.7扩展LuaVM接口.162 8.4.8 改进SETLIST指令1638.5 测试本章代码1648.6本章小结.166 第9章Go函数调用

10、.167 9.1 Go函数登场.167 9.1.l 添加Go函数类型.168 9.1.2 扩展LuaAPI.169 9.1.3 调用Go函数.170 9.2 Lua注册表1729.2.l 添加注册表172 9.2.2 操作注册表.173 9.3 全局环境.175 9.3.1 使用API操作全局环境1759.3.2 在Lua里访问全局环境1789.4 测试本章代码1799.5 本章小结.181 第10章闭包和Upvalue.183 10.l 闭包和Upvalue介绍.183 10. l.l 背景知识18310.1.2 Upvalue介绍18510.1.3 全局变量18710.2 Upvalue底

11、层支持.189 10.2.1 修改closure结构体18910.2.2 Lua闭包支持.191 10.2.3 Go闭包支持19210.3 Upvalue相关指令.195 10.3.l GETUPVAL. 195 10.3.2 SETUPVAL. 196 10.3.3 GETTABUP. 197 10.3.4 SETTABUP. 199 10.3.5 JMP. 200 10.4测试本章代码.202 10.5 本章小结.203 第11章元编程20511.l 元表和元方法介绍205元表.206 11.1.2 元方法.206 11.2 支持元表.207 11.3 调用元方法.208 11.3.1 算

12、术元方法20911.3.2 长度元方法.211 11.3.3 拼接元方法.211 11.3.4 比较元方法.212 11.3.5 索引元方法.214 11.3.6 函数调用元方法21611.4 扩展LuaAPI川11.4.1 GetMetatable()218 11.4.2 SetMetatable(). 218 11.5 测试本章代码.219 11.6本章小结.222 第12章迭代器22312.1 迭代器介绍.223 12.2 next()函数.226 12.2.1 修改luaTable结构体.227 12.2.2 扩展LuaAPI22812.2.3 实现next()函数22912.3 通用

13、for循环指令.229 12.4 测试本章代码.232 12.5 本章小结.234 第13章异常和错误处理.235 13.1 异常和错误处理介绍.235 13.2 异常和错误处理API.237 13.2.1 E口or()23813.2 2 PCall()239 13.3 eor()和pcall()函数.240 13.4 测试本章代码.241 13.5 本章小结.242 第二部分Lua语法和编译器第14章词法分析24514.1 编译器介绍.245 14.2 Lua词法介绍.247 14.3 实现词法分析器25114.3.l 定义Token类型25214.3.2 空白字符25414.3.3 注释.

14、256 14.3.4 分隔符和运算符256XI 14.3.5 长字符串字面量25814.3.6 短字符串字面量25914.3.7数字字面量.262 14.3.8 标识符和关键字26314.4 LookAhead()和其他方法26414.5 测试本章代码.265 14.6 本章小结.267 第15章抽象语法树.269 15.1 抽象语法树介绍26915.2 Chunk和块.270 15.3 语句15.4 15.3.1 简单语句27315.3.2 while和repeat语句27315.3.3 if语句27415.3.4 数值for循环语句.275 15.3.5 通用for循环语句.275 15.

15、3.6 局部变量声明语句.276 15.3.7 赋值语句27715.3.8 非局部函数定义语句.278 15.3.9 局部函数定义语句.279 表达式.280 15.4.l 简单表达式.280 15.4.2运算符表达式.281 15.4.3 表构造表达式.281 15.4.4 函数定义表达式28215.4.5 前缀表达式.283 15.4.6 圆括号表达式.284 15.4.7 表访问表达式.284 15.4.8 函数调用表达式285XII 15.5 本章小结.286 第16章语法分析28716.1 语法分析介绍.287 16.1.l 歧义.288 16.1.2前瞻和回溯28916.1.3 解

16、析方式29016.2解析块29016.3 解析语句.293 16.3.1 简单语句29416.3.2 if语句29616.3.3 for循环语句29716.3.4 局部变量声明和函数定义语句.299 16.3.5 赋值和函数调用语句.300 16.3.6非局部函数定义语句.302 16.4解析表达式.303 16.4.1 运算符表达式.304 16.4.2非运算符表达式30616.4.3 函数定义表达式 30716.4.4表构造表达式. .308 16.4.5 前缀表达式31016.4.6 圆括号表达式.311 16.4.7 函数调用表达式31216.4.8 表达式优化31316.5 测试本章

17、代码. .315 16.6 本章小结.316 第17章代码生成31717.1 定义也nclnfo结构体 .317 17.1.l 常量表31817.1.2 寄存器分配31917.1.3 局部变量表32017 .1.4 Break表32317 .1.5 Upvalue表 .324 17 .1.6 字节码.325 17.1.7 其他信息32717.2 编译块32717.3 编译语句32917.3.1 简单语句33017.3.2 while和repeat语句33117.3.3 if语句33317.3.4 for循环语句33417.3.5 局部变量声明语句33517.3.6赋值语句33717.4 编译表

18、达式.339 17.4.1 函数定义表达式34017.4.2表构造表达式.341 17.4.3 运算符表达式.343 17.4.4 名字和表访问表达式.345 17.4.5 函数调用表达式34617.5 生成函数原型.347 17.6使用编译器34917.7 测试本章代码.350 17.8 本章结350第四部分Lua标准库第18章辅助API和基础库.353 18.1 Lua标准库介绍35318.2辅助API.355 18.2.1 增强版方法.357 18.2.2 加载方法35818.2.3 参数检查方法.359 18.2.4标准库开启方怯36018.3 基础库36118.3.1 基础库介绍.3

19、62 18.3.2 基础库实现.362 18.4 测试本章代码.365 18.5 本章小结.366 第19章工具库36719. l 数学库19.2 表库19.3 字符串库19.4 UTF-8库.374 19.5 OS库.376 19.6本章小结.379 XIII 第20章包和模块38120.l 包和模块介绍38120.2 实现包库.386 20.3 测试本章代码.391 20.4 本章小结.392 第21章协程21.1 协程介绍.393 21.2 协程API.396 21.2.1 支持线程类型.396 21.2.2 支持协程操作.398 21.3 实现协程库.400 21.4测试本章代码.40

20、3 21.5 本章小结.404 附录ALua虚拟机指令集405附录BLua语法EBNF描述407后记, b 乱,也mMY 分RV 4A口t弟. . 回. . 跚. . . . mm- . 翻. am-. am圃,. 翻Eg跚. 植. . 瞌Easm Bam- , . BE翻翻翻翻翻aaa- 黯EE- ”aa- . 跚跚sa. . 翻蟹. 黯噩画ES11. . 回准备国第l章准备工作第1章准备工作子曰:工欲善其事,必先利其器。本书的目标是带领读者从零开始自己动手学会Lua语言,所以我们要做的第一件事就是把开发环境准备好。本章主要分为两部分内容:第一部分介绍读者跟随本书编写代码所需的环境和工具;第

21、二部分介绍本书源代码目录结构。1.1 准备开发环境读者眼随本书编写代码所需的开发环境非常简单,只需要一台安装着现代操作系统(比如Windows、Linux、macOS等)的电脑,以及文本编辑器、Lua语言编译器和Go语言编译器。1.1.1 操作系统由于操作系统一般都自带了简单的文本编辑器(比如Windows下的记事本),且Lua语言和Go语言也都是跨平台的,所以读者可以选择自己喜欢的操作系统。不过由于笔者是使用MacBook笔记本编写本书代码和文字的,所以书中出现的命令和路径等都是Unix形式。下面是一个例子。$ ls /dev/*random /dev/random /dev/urandom

22、 4 !第一部分准备命令行以“$”开头,后跟输出结果;路径分隔符是“” 。如果读者使用Windows操作系统进行编写,需要对命令和路径做出相应的调整。另外,虽然任何文本编辑器都可以满足我们的需要,但是最好选择可以对Lua语言和Go语言进行语法着色的编辑器,这里推荐使用SublimeText。1.1.2 安装LuaLua虽然是解释型语言,但实际上Lua解释器会先把Lua脚本编译成字节码,然后在虚拟机中解释执行字节码,这一点和Java语言很像。本书的第一部分(第213章)主要围绕Lua字节码的解释执行展开讨论,并初步实现我们自己的Lua虚拟机。在这一部分,我们需要通过官方Lua编译器来将Lua脚本

23、编译成字节码,因此需要安装Lua。在本书的第二部分(第1417章),我们将实现自己的Lua编译器。Lua的安装比较简单,读者可以从http:/www.lua.org/download.html下载最新版(本书编写时,Lua的最新版本是5.3.4)源代码自行编译,或者直接下载已经编译好的发行版。安装完毕后,在命令行里执行luac-v命令,如果看到类似下面的输出,就表示安装成功了。$ l uac - v Lua 5.3.4 Copyright (C) 1994- 2017 Lua.erg, PUC-Rio 1.1.3安装Go本书将带领读者使用Go语言编写Lua虚拟机、Lua编译器以及Lua标准库,

24、因此需要安装Go。Go的安装也比较简单,读者可以从https:/ ro d m , n .l m a -q q, - nu 咱4咱4o nqd o ln so rl es vr e ov qd o 户hnuJ1.2 准备目录结构如果读者跟着本书一起编写代码,那么在每一章的最后,都会提供一份完整的源代码,可以独立编译为可执行程序。除第1章之外,每一章的代码都以前一章代码为基础,逐渐添加功能,最终实现一个完整的Lua解释器。建议读者跟随本书的每一章,自己输入第才重准备工作!5 代码,循序渐进完成Lua解释器的开发。当然直接从GitHub上下载源代码,只编写自己感兴趣的部分也是完全可以的。作为开始,

25、我们需要创建一个根目录,然后在里面创建go和lua子目录。其中go目录里存放每一章的Go语言源代码,lua目录里存放每一章的Lua语言示例和测试代码。读者可以在任何位置创建根目录,在本书后面的内容里,我们将使用“$LUAGO”来表示这个目录,出现的路径也都是相对于该目录的相对路径。$LUAGO的目录结构如下所示。$LUAGO/ go/ chOl/src/ ch02/src/ lua/ chOl/ ch02/ 万事开头难。作为一本介绍编程语言实现的书,按照惯例,当然也要从“Hello,World !”程序开始。请读者打开命令行窗口,执行下面的命令。cd $LUAGO/go/ mkdir -p c

26、hOl/src/luago $ export GOPATH=$PWD/ch01 上面的命令创建了本章的目录结构,并且设置好了GOPATH环境变量(关于GO PATH的介绍,请参考https:/ luago目录下面创建main.go文件,现在完整的目录结构如下所示。$LUAGO/ go/chOl/src/luago/main.go lua/ 打开main.go文件,在里面输入如下代码。package main func main() println(”Hello, World !”) 在命令行里执行下面的命令编译“Hello,World !”程序。6 !第一部分准备$ go install lu

27、ago 命令执行完毕,如果没有看到任何输出,那么就表示编译成功了。go/chOl/bin目录下会出现luago可执行文件,直接运行就可以看到“Hello,World !”输出。$ ./chOl/bin/luago Hello, World! 1.3 本章小结千里之行,始于足下。本章我们准备好了开发环境,包括操作系统、文本编辑器以及Lua语言和Go语言编译器。我们还创建了代码的目录结构,并且编写和运行了“Hello,World !”程序。从第2章开始,我们将正式进入Lua语言的学习之旅。J r 龟,冯分RV 立口第. . . . Ba- . mm- . ” . EE- . . . . . . .

28、 . . . . E E. ,. . . . . . . . . Lua虚拟机和LuaAPI 第2章二进制chunk第3章指令集第4章LuaAPI第5章Lua运算符第6章虚拟机雏形第7章表第8章函数调用第9章Go函数调用第10章闭包和Upvalue元编程迭代器异常和错误处理第11章第12章第13章. . 第2章二进制chunkLua是一门以高效著称的脚本语言,为了达到较高的执行效率,Lua从1.0版(1993 年发布)开始就内置了虚拟机。也就是说,Lua脚本并不是直接被Lua解释器解释执行,而是类似Java语言那样,先由Lua编译器编译为字节码,然后再交给Lua虚拟机去执行。相比较而言,诞生时

29、间比Lua稍晚一些的脚本语言Ruby在出现以来的很长一段时间里一直是直接解释执行Ruby脚本,直到1.9版(2007年底发布)才引入了YARV虚拟机。Lua字节码需要一个载体,这个载体就是二进制chunk,对Java虚拟机比较熟悉的读者可以把二进制chunk看作Lua版的class文件。本章会首先对二进制chunk进行一个简单的介绍,然后详细讨论Lua编译器的用法和二进制chunk格式,最后编写代码实现二进制chunk解析,为后续章节做准备。在继续阅读本章内容之前,请读者执行如下命令,把本章所需的目录结构和编译环境准备好。$ cd $LUAGO/go/ $ cp -r chOl/ ch02 $

30、 mkdir ch02/src/luago/binchunk $ export GOPATH=$PWD/ch02 $ mkdir $LUAGO/lua/ch02 10 !第二部分Lua虚拟机和LuaAPI 2.1 什么是二进制chunk在Lua的行话里,一段可以被Lua解释器解释执行的代码就叫作chunk。chunk可以很小,小到只有一两条语句;也可以很大,大到包含成千上万条语句和复杂的函数定义。前面也提到过,为了获得较高的执行效率,Lua并不是直接解释执行chunk,而是先由编译器编译成内部结构(其中包含字节码等信息),然后再由虚拟机执行字节码。这种内部结构在Lua里就叫作预编译(Preco

31、mpiled) chunk,由于采用了二进制格式,所以也叫二进制(Bina)chunk。我们仍然以Java虚拟机作为对照,存放chunk的文件(一般以.lua为后缀)对应扣va源文件,二进制chunk则对应编译好的class文件。Java的class文件里除了字节码外,还有常量池、行号表等信息,类似地,二进制chunk里也有这些信息。然而和Java不同的是,Lua程序员一般不需要关心二进制chunk,因为Lua解释器会在内部进行编译,如图2-1所示。lua 图2-1隐式调用Lua编译器Java提供了命令行工具jav饵,用来把Java源文件编译成class文件,类似地,Lua也提供了命令行工具l

32、uac,可以把Lua源代码编译成二进制chunk,并且保存成文件(默认文件名为luac.out)。Lua解释器可以直接加载并执行二进制chunk文件,如图2-2所示。图2-2显式调用Lua编译器如前所述,Lua解释器会在内部编译Lua脚本,所以预编译并不会加快脚本执行的速度,但是预编译可以加快脚本加载的速度,并可以在一定程度上保护源代码。另外,luac第2章二进制chunk! 11 还提供了反编译功能,方便我们查看二进制chunk内容和Lua虚拟机指令。下面详细介绍luac的用法。2.2 luac命令介绍luac命令主要有两个用途:第一,作为编译器,把Lua源文件编译成二进制chunk文件:第

33、二,作为反编译器,分析二进制chunk,将信息输出到控制台。这里仍然以Java为对照,JDK提供了单独的命令行工具javap,用来反编译class文件,而Lua则是将编译命令和反编译命令整合在了一起。在命令行里直接执行luac命令(不带任何参数)可以看到luac命令的完整用法。$ luac luac: no input files given usage: luac (options filen缸nesAvailable options are: -1 list (use -1 -1 for full listing) -o name output to file name(default i

34、s ”luac.out” -p parse only -s strip debug information -v show version information stop handling options stop handling options and process stdin 本节主要以“Hello,World!”程序为例讨论luac命令的两种用法。请读者在$LUAGO/lua/ch02目录下创建helloworld.lua文件并且在里面输人如下代码。print(”Hello, World l”) 为了便于讨论,我们暂时将当前路径切换到$LUAGO/lua/ch02目录。$ cd L

35、UAGO/lua/ch02 2.2.1 编译Lua源文件将一个或者多个文件名作为参数调用luac命令就可以编译指定的Lua源文件,如果编译成功,在当前目录下会出现luac.out文件,里面的内容就是对应的二进制chunk。如果不想使用默认的输出文件,可以使用“。”选项对输出文件进行明确指定。编译生成的二进制chunk默认包含调试信息、(行号、变量名等),可以使用“s”选项告诉luac去 . . 才2令第二部分L崎虚拟机和LuaAPI 掉调试信息。另外,如果仅仅想检查语法是否正确,不想产生输出文件,可以使用“?”选项进行编译。下面是luac的一些用法示例。$ luac hello world.l

36、ua 生成luac.out$ luac -o hw.luac hello world.lua 生成hw.luac$ luac -s hello world.lua 不包含调试信息$ luac -p hello_world.lua 只进行语法检查为了方便后面的讨论,本节还会简单介绍一下Lua编译器的内部工作原理,本书第二部分(第1417章)会详细介绍Lua编译器的实现细节。Lua编译器以函数为单位进行编译,每一个函数都会被Lua编译器编译为一个内部结构,这个结构叫作“原型”(Prototype)。原型主要包含6部分内容,分别是:函数基本信息(包括参数数量、局部变量数量等)、字节码、常量表、Upv

37、alue表、调式信息、子函数原型列表。由此可知,函数原型是一种递归结构,并且Lua源码中函数的嵌套关系会直接反映在编译后的原型里。细心的读者一定会想到这样一个问题:前面我们写的“Hello,World!”程序里面只有一条打印语句,并没有定义函数,那么Lua编译器是怎么编译这个文件的呢?由于Lua是脚本语言,如果我们每执行一段脚本都必须要定义一个函数(就像Java那样),岂不是很麻烦?所以这个吃力不讨好的工作就由Lua编译器代劳了。Lua编译器会自动为我们的脚本添加一个main函数(后文称其为主函数),并且把整个程序都放进这个函数里,然后再以它为起点进行编译,那么自然就把整个程序都编译出来了。这

38、个主函数不仅是编译的起点,也是未来Lua虚拟机解释执行程序时的人口。我们写的“Hello,World!”程序被Lua编译器加工之后,就变成了下面这个样子。function main( . ) print(”Hello, World!) return end 把主函数编译成函数原型后,Lua编译器会给它再添加一个头部(Header,详见2.3.3节),然后一起dump成luac.out文件,这样,一份热乎的二进制chunk文件就新鲜出炉了。综上所述,函数原型和二进制chunk的内部结构如图2-3所示。 . . 第2章二进制chunk! 13 binary chunk header prototy

39、pe basic info bytecodes constants sub functions upvalues functionl debug info function2 sub functions function3 图2-3二进制ch山J.k.内部结构2.2.2 查看二进制chunk二进制chunk之所以使用二进制格式,是为了方便虚拟机加载,然而对人类却不够友好,因为其很难直接阅读。如前所述,luac命令兼具编译和反编译功能,使用“1”选项可以将luac切换到反编译模式。正如javap命令是查看class文件的利器,luac命令搭配“1”选项则是查看二进制chunk的利器。本节的目标是

40、学会阅读luac的反编译输出。在2.3节,我们将深入到二进制chunk的内部来研究其格式。以前面编译出来的helloworld.luac文件为例,其反编译输出如下。$ luac - 1 hello world.luac main hello world.lua:O,。(4 instructions at Ox7fb4dbc030f0) 。params, 2 slots, 1 upvalue, 0 locals, 2 constants, 0 functions 1 l GETTABUP 0 0 -1 ; _ENV”print” 2 lLOADK 1 -2; ”Hello, World l” 3

41、 l CALL 0 2 1 4 lRETURN 0 1 上面的例子以二进制chunk文件为参数,实际上也可以直接以Lua源文件为参数,luac会先编译源文件,生成二进制chunk文件,然后再进行反编译,产生输出。由于“Hello, World!”程序只有一条打印语句,所以编译出来的二进制chunk里也只有一个主函仅供非商业用途或交流学习使用 . . 14 !第二部分Lua虚拟机和LuaAPI 数原型(没有子函数),因此反编译输出里也只有主函数信息。如果我们的Lua程序里有函数定义,那么luac反编译器会按顺序依次输出这些函数原型的信息,例如如下的Lua程序(请读者将其保存在$LUAGO/lua

42、/ch02/foobar.lua文件中)。function foo ( ) function bar() end end 反编译输出中会依次包含main、foo和bar函数的信息,如下所示。$ luac -1 foo bar.lua main foo bar.lua:O,。(3 instructions at Ox7fc43fc02b20) 。params, 2 slots, 1 upvalue, 0 locals, 1 constant , 1 function 1 4 CLOSURE 0 0 ; Ox7fc43fc02cc0 2 l SETTABUP 0 -1 0 ; ENV”fo。”3

43、4 RETURN 0 1 function (3 instructions at Ox7fc43fc02cc0) 0 params, 2 slots, 1 upvalue, 0 locals, 1 constant , 1 function 1 3 CLOSURE 0 0 ; Ox7fc43fc02e 40 2 2 SETTABUP 0 -1 0 ; ENV”bar” 3 4 RETURN 0 1 function (1 instruction at Ox7fc43fc02e40) 0 par缸ns,2 slots, 0 upvalues, 0 locals, 0 constants, 0 f

44、unctions 1 3 RETURN 0 1 反编译打印出的函数信息包含两个部分:前面两行是函数基本信息,后面是指令列表。第一行如果以main开头,说明这是编译器为我们生成的主函数;以function开头,说明这是一个普通函数。接着是定义函数的源文件名和函数在文件里的起止行号(对于主函数,起止行号都是0),然后是指令数量和函数地址。第二行依次给出函数的固定参数数量(如果有号,表示这是一个vararg函数)、运行函数所必要的寄存器数量、upvalue数量、局部变量数量、常量数量、子函数数量。如果读者看不懂这些信息也没有关系,我们在后面的章节中会陆续介绍这些信息。指令列表里的每一条指令都包含指令

45、序号、对应行号、操作码和操作数。分号后面是luac根据指令操作数生成的注释,以便于我们理解指令。第3章会详细介绍Lua虚拟机指令。仅供非商业用途或交流学习使用 . . 第2童二进制chunk令15以上看到的是luac反编译器精简模式的输出内容,如果使用两个“1”选项,则可以进入详细模式,这样,luac会把常量表、局部变量表和upvalue表的信息也打印出来。$ luac -1 -1 hello world.lua main hello_world.lua:O,。(4 instructions at Ox7fbcb5401c00) 。params, 2 slots, 1 upvalue, 0 l

46、ocals, 2 constants, O functions 1 l GETTABUP 0 0 -1 ; _ENV print” 2 l LOADK 1 -2 ; Hello, World! 3 l CALL 0 2 1 4 l RETURN 0 1 constants (2) for Ox7fbcb5401c00: 1”print” 2”Hello, World l” locals (0) for Ox7fbcb5401c00: upvalues (1) for Ox7fbcb5401c00: 0 ENV 1 0 到这里luac命令反编译模式的基本用法和阅读方法就介绍完毕了,如果读者觉得一

47、头雾水也不要担心,暂时只要对二进制chunk有一个粗略的认识就可以了,在2.3节我们会详细地讨论二进制chunk格式。2.3 二进制chunk格式和Java的class文件类似,Lua的二进制chunk本质上也是一个字节流。不过class文件的格式设计相当紧凑,并且在Java虚拟机规范里给出了严格的规定,二进制chunk则不然。1 )二进制chunk格式(包括Lua虚拟机指令)属于Lua虚拟机内部实现细节,并没有标准化,也没有任何官方文档对其进行说明,一切以Lua官方实现的源代码为准。在写作本书的过程中,笔者参考了一些关于二进制chunk格式和Lua虚拟机指令的非官方说明文档,具体见本书参考资

48、料。2)二进制chunk格式的设计没有考虑跨平台的需求。对于需要使用超过一个字节表示的数据,必须要考虑大小端(Endianness)问题。Lua官方实现的做法比较简单:编译Lua脚本时,直接按照本机的大小端方式生成二进制chunk文件,当加载二进制chunk文件时,会探测被加载文件的大小端方式,如果和本机不匹配,就拒绝加载。仅供非商业用途或交流学习使用 . . 16令第二部分Lua虚拟机和LuaAPI 3)二进制chunk格式的设计也没有考虑不同Lua版本之间的兼容问题。和大小端问题一样,Lua官方实现的做法也比较简单:编译Lua脚本时,直接按照当时的Lua版本生成二进制chunk文件,当加载

49、二进制chunk文件时,会检测被加载文件的版本号,如果和当前Lua版本不匹配,则拒绝加载。4)二进制chunk格式并没有被刻意设计得很紧凑。在某些情况下,一段Lua脚本被编译成二进制chunk之后,甚至会比文本形式的源文件还要大。不过如前所述,由于把Lua脚本预编译成二进制chunk的主要目的是为了获得更快的加载速度,所以这也不是什么大问题。本节主要讨论二进制chunk格式与如何将其编码成Go语言结构体。在2.4节,我们会进一步编写二进制chunk解析代码。2.3.1 数据类型前文提到过,二进制chunk本质上来说是一个字节流。大家都知道,一个字节能够表示的信息是非常有限的,比如说一个ASCI

50、I码或者一个很小的整数可以放进一个字节内,但是更复杂的信息就必须通过某种编码方式编码成多个字节。在讨论二进制chunk格式时,我们称这种被编码为一个或多个字节的信息单位为数据类型。请读者注意,由于Lua官方实现是用C语言编写的,所以C语言的一些数据类型(比如sizet)会直接反映在二进制chunk的格式里,千万不要将这两个概念混淆。二进制chunk内部使用的数据类型大致可以分为数字、字符串和列表三种。1.数字数字类型主要包括字节、C语言整型(后文简称cint)、C语言sizet类型(简称sizet)、Lua整数、Lua浮点数五种。其中,字节类型用来存放一些比较小的整数值,比如Lua版本号、函数

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 网络技术 > 热门技术

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:文库网官方知乎号:文库网

经营许可证编号: 粤ICP备2021046453号世界地图

文库网官网©版权所有2025营业执照举报