您的当前位置:首页正文

lua虚拟机字节码修改_深入理解Lua虚拟机

2024-11-09 来源:个人技术集锦

为了达到较高的执行效率,lua代码并不是直接被Lua解释器解释执行,而是会先编译为字节码,然后再交给lua虚拟机去执行

lua代码称为chunk,编译成的字节码则称为二进制chunk(Binary chunk)

lua.exe、wlua.exe解释器可直接执行lua代码(解释器内部会先将其编译成字节码),也可执行使用luac.exe将lua代码预编译(Precompiled)为字节码

使用预编译的字节码并不会加快脚本执行的速度,但可以加快脚本加载的速度,并在一定程度上保护源代码

luac.exe可作为编译器,把lua代码编译成字节码,同时可作为反编译器,分析字节码的内容

luac.exe -v  // 显示luac的版本号

luac.exe -s -o d:\Hello.out Hello.lua  // 编译得到Hello.lua的二进制chunk文件d:\Hello.out(去掉调试符号)

luac.exe -p Hello1.lua Hello2.lua  // 对Hello1.lua和Hello2.lua只进行语法检测(注:只会检查语法规则,不会检查变量、函数等是否定义和实现,函数参数返回值是否合法)

lua编译器以函数为单位对源代码进行编译,每个函数会被编译成一个称之为原型(Prototype)的结构

原型主要包含6部分内容:函数基本信息(basic info:含参数数量、局部变量数量等信息)、字节码(bytecodes)、常量(constants)表、upvalue(闭包捕获的非局部变量)表、调试信息(debug info)、子函数原型列表(sub functions)

原型结构使用这种嵌套递归结构,来描述函数中定义的子函数

注:lua允许开发者可将语句写到文件的全局范围中,这是因为lua在编译时会将整个文件放到一个称之为main函数中,并以它为起点进行编译

Hello.lua源代码如下:

1 print ("hello")2

3 functionadd(a, b)4 return a+b5 end

编译得到的Hello.out的二进制为:

二进制chunk(Binary chunk)的格式并没有标准化,也没有任何官方文档对其进行说明,一切以lua官方实现的源代码为准。

其设计并没有考虑跨平台,对于需要超过一个字节表示的数据,必须要考虑大小端(Endianness)问题。

lua官方实现的做法比较简单:编译lua脚本时,直接按照本机的大小端方式生成二进制chunk文件,当加载二进制chunk文件时,会探测被加载文件的大小端方式,如果和本机不匹配,就拒绝加载.

二进制chunk格式设计也没有考虑不同lua版本之间的兼容问题,当加载二进制chunk文件时,会检测其版本号,如果和当前lua版本不匹配,就拒绝加载

另外,二进制chunk格式设计也没有被刻意设计得很紧凑。在某些情况下,一段lua代码编译成二进制chunk后,甚至会被文本形式的源代码还要大。

预编译成二进制chunk主要是为了提升加载速度,因此这也不是很大的问题.

头部字段:

字段

字节数

解释

签名(signature)

byte[4]

0x1B4C7561

二进制chunk文件的魔数,分别是ESC、L、u、a的ASCII码

版本号(version)

byte

0x53

lua语言的版本号由3部分组成:大版本号(Major Version)、小版本号(Minor Version)和发布号(Release Version)

比如:当前lua的版本号为5.3.5,那么大版本号为5,小版本号为3、发布号为5

由于发布号的增加只是为了修复bug,不会对二进制chunk文件格式进行调整,因此版本号(version)只存储了大版本号和小版本号

格式号(format)

byte

0x00

二进制chunk文件的格式号

lua虚拟机在加载二进制chunk时,会检查其格式号,如果和虚拟机本身的格式号不匹配,就会拒绝加载该文件

luacData

byte[6]

0x19930D0A1A0A

其中前两个字节0x1993,是lua1.0发布的年份;后四个字节依次是回车符(0x0D)、换行符(0x0A)、替换符(0x1A)和另一个换行符

cintSize

byte

0x04

cint数据类型size

sizeSize

byte

0x04

size_t数据类型size

instructionSize

byte

0x04

lua虚拟机指令size

luaIntegerSize

byte

0x08

lua整型size

luaNumberSize

byte

0x08

lua浮点数size

lua整数值(luacInt)

int64

0x7856000000000000

加载二进制chunk文件时,会用该字段检测其大小端和本机是否匹配

格式号(luacNum)

float64

0x0000000000287740

浮点数值为370.5

加载二进制chunk文件时,会用该字段检测其使用的浮点数格式(目前主流平台一般都采用IEEE754浮点数格式)

嵌套的函数原型:

0B4048656C6C6F2E6C7561

源文件名Hello.lua;-s去掉调试信息后,该项数值为:00

main

00000000 00000000

main函数起始行列号

00

函数参数个数

01

函数为不定参数

02

寄存器数量

06000000

函数指令数目

06004000

第1条指令

41400000

第2条指令

24400001

第3条指令

2C000000

第4条指令

08000081

第5条指令

26008000

第6条指令

03000000

常量数目

04

第1个常量tag,04表示字符串

067072696E74

第1个常量内容

04

第2个常量tag,04表示字符串

0668656C6C6F

第2个常量内容

04

第3个常量tag,04表示字符串

04616464

第3个常量内容

01000000

Upvalue数目

0100

第1个Upvalue

01000000

子函数原型数目

add

03000000 05000000

add函数起始行列号

02

函数参数个数

00

函数为不定参数

03

寄存器数量

03000000

函数指令数目

8D400000

第1条指令

A6000001

第2条指令

26008000

第3条指令

00000000

常量数目

00000000

Upvalue数目

00000000

子函数原型数目

03000000

行号数目;-s去掉调试信息后,该项数值为:00000000;为0时下面的信息也都没有

04000000

第1条指令行号

04000000

第2条指令行号

05000000

第3条指令行号

02000000

局部变量数目;-s去掉调试信息后,该项数值为:00000000;为0时下面的信息也都没有

0261

第1个局部变量名称

00000000

第1个局部变量起始指令索引

03000000

第1个局部变量终止指令索引

0262

第2个局部变量名称

00000000

第2个局部变量起始指令索引

03000000

第2个局部变量终止指令索引

00000000

Upvalue名称数目;-s去掉调试信息后,该项数值为:00000000;为0时下面的信息也都没有

06000000

行号数目࿱

Top