Lua 5.0 开始,Lua 就从基于栈的虚拟机( stack-based VM )改为了基于寄存器的虚拟机( register-based VM )。基于栈的虚拟机有JVM,.Net CLR,javascript V8。基于寄存器的虚拟机有Android Dalvik VM.
语言的虚拟机是借助于操作系统对物理机器的一种模拟。
虚拟CPU指令流水线一般有5个过程段:
1.取指(Instruction Fetch,IF)
2.译码(ID)
3.执行(EX)
4.访存(MEM)
5.写回寄存器堆(WB)
基于栈的虚拟机:
计算8=3+5,代码必须使用这些指令来移动变量(即push和pop),其操作数都是保存在Stack数据结构中,从栈中取出数据、计算然后再将结果存入栈中(LIFO,Last in first out)。不依赖硬件的寄存器,移植方便。
push 3
push 5 //指令连续把两个常量压入栈
ADD 3,5 result //指令把栈顶的两个值出栈、相加后把结果放回栈顶
pop result //把栈顶值存入局部变量表中
基于寄存器的虚拟机:
没有入栈和出栈的操作,减少了每个函数的指令总数.需要明确的指定操作数R1、R2、R3(这些都是寄存器)的地址。,由于指定了操作数,所以基于寄存器的代码会比基于栈的代码要大,实际指令数量的减少,体积没有大多少。但是带来的性能提升更加突出。
ADD R1, R2, R3 ;
Lua虚拟机指令简介
1.llex.c(lua源码词法分析)、lparser.c(lua源码语法分析),生成bytecode使用了lcode.c完成
2.Lua虚拟机主要的工作(lvm.c)是实现虚拟机的指令分派执行循环( void luaV_execute (lua_State *L)
),大致执行流程如下:
while 取下一条指令 {
switch 指令类型 {
case 指令类型A: 执行A处理逻辑; break;
case 指令类型B: 执行B处理逻辑; break;
......
}
}
这里的指令就是 Instruction,指令类型就是 Instruction 中的 LOPCODE。 如执行一个lua加法操作,使用OP_ADD指令完成。A指向保存结果的寄存器,B和C指向操作操作目标,即寄存器或常量。
name args description
OP_ADD,/* A B C R(A) := RK(B) + RK(C) */
具体的lopcode参见官方代码.
3.为了避免解密luac或luajit烘焙的bytecode,可以打乱lopcodes.c中luaP_opnames和luaP_opmodes顺序。以及调整lvm.c中void luaV_execute (lua_State *L)
的switch顺序。
常见的反向工具如下:
- AndroidKiller 1.3.1(反编译apk,其中apktool.exe是最新版)
- IDA6.8(分析so文件、动态调试so)
- ChunkSpy(用于解析lua bytecode文件结构,方便luac的学习与阅读。)
- luadec51、unluac(反编译luac)
- luajit-decomp(反编译luaJIT)
- ILSpy(反编译c#)
- .NET Reflector + Reflexil(反编译c#,弥补ILSpy功能缺陷,一些dll文件头被修改后ILSpy无法显示。)
Lua的虚拟栈
Lua 使用一个 虚拟栈 来和 C 互传值。
用一个 索引 来指向栈上的任何元素: 正的索引指的是栈上的绝对位置(从1开始); 负的索引则指从栈顶开始的偏移量。 展开来说,如果堆栈有 n 个元素, 那么索引 1 表示第一个元素 (也就是最先被压栈的元素) 而索引 n 则指最后一个元素; 索引 -1 也是指最后一个元素 (即栈顶的元素), 索引 -n 是指第一个元素。
栈上的的每个元素都是一个 Lua 值 (nil,数字,字符串,等等).
TValue 入栈的数据
分析Lua5.3.4源码,入栈的数据都由TValue这种数据类型来维护.TValue对应于lua中的所有数据类型, 是一个{值, 类型} 结构, 这就lua中动态类型的实现, 它把值和类型绑在一起。
/*lobject.h*/
#define TValuefields Value value_; int tt_
typedef struct lua_TValue {
TValuefields;
} TValue;
tt_ 类型定义
/*lua.h*/
/*
** basic types
*/
#define LUA_TNONE (-1)
#define LUA_TNIL 0
#define LUA_TBOOLEAN 1
#define LUA_TLIGHTUSERDATA 2
#define LUA_TNUMBER 3
#define LUA_TSTRING 4
#define LUA_TTABLE 5
#define LUA_TFUNCTION 6
#define LUA_TUSERDATA 7
#define LUA_TTHREAD 8
#define LUA_NUMTAGS 9
Value 值定义, 联合有六个域,
- gc 存其他诸如table, thread, closure, string需要内存管理垃圾回收的类型。
- b 存布尔值
- lua的number是分别由i存整型和n存double
- light userdata类型代表了一个 C 指针
- light C functions的存储
LIGHTUSERDATA
/*lobject.h*/
/*
** Union of all Lua values
*/
typedef union Value {
GCObject *gc; /* collectable objects */
void *p; /* light userdata */
int b; /* booleans */
lua_CFunction f; /* light C functions */
lua_Integer i; /* integer numbers */
lua_Number n; /* float numbers */
} Value;
GCObject 可以被垃圾回收的对象,在 vm 中以引用方式共享.
/*lobject.h*/
/*
** Common type for all collectable objects
*/
typedef struct GCObject GCObject;
/*
** Common Header for all collectable objects (in macro form, to be
** included in other objects)
*/
#define CommonHeader GCObject *next; lu_byte tt; lu_byte marked
/*
** Common type has only the common header
*/
struct GCObject {
CommonHeader;
};
在 CommonHeader 宏里,next 字段说明可回收对象是可以放到链表里的,而 marked 字段是 GC 用来进行标记的,tt 字段表示类型,和上面tt_ 使用的宏定义一致。拥有CommonHeader定义的有:
/*
** Union of all collectable objects (only for conversions)
*/
union GCUnion {
GCObject gc; /* common header */
struct TString ts; /*字符串*/
struct Udata u; /*full userdata*/
union Closure cl; /*闭包*/
struct Proto p; /*lua函数协议描述*/
struct Table h; /*表*/
struct lua_State th; /* thread */
};
/*Closure分二种*/
typedef struct /*luaC函数的闭包*/
typedef struct /*Lua里面原生的函数的闭包*/
/*userdata代表了一块内存,其中的数据对 Lua 不透明,
但是由 Lua 管理生命周期,一般用于实现 C 库*/
从上面结构可以得出结论:
- lua中, number, boolean, nil, light userdata四种类型的值是直接存在虚拟栈上元素里的, 和垃圾回收无关.
- lua中, string, table, closure, full userdata, thread存在虚拟栈上元素里的只是指针, 它们都会在生命周期结束后被垃圾回收.