Lua

Lua虚拟栈的实现-数据定义

分析Lua和C如何交互的

Posted by Bob on July 28, 2018

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)

image

基于栈的虚拟机:

计算8=3+5,代码必须使用这些指令来移动变量(即push和pop),其操作数都是保存在Stack数据结构中,从栈中取出数据、计算然后再将结果存入栈中(LIFO,Last in first out)。不依赖硬件的寄存器,移植方便。

 push 3
 push 5  //指令连续把两个常量压入栈
 ADD 3,5 result //指令把栈顶的两个值出栈、相加后把结果放回栈顶
 pop result //把栈顶值存入局部变量表中

image

基于寄存器的虚拟机:

没有入栈和出栈的操作,减少了每个函数的指令总数.需要明确的指定操作数R1、R2、R3(这些都是寄存器)的地址。,由于指定了操作数,所以基于寄存器的代码会比基于栈的代码要大,实际指令数量的减少,体积没有大多少。但是带来的性能提升更加突出。

 ADD R1, R2, R3 ;

image

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 是指第一个元素。

image

栈上的的每个元素都是一个 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 库*/
 
 

从上面结构可以得出结论:

  1. lua中, number, boolean, nil, light userdata四种类型的值是直接存在虚拟栈上元素里的, 和垃圾回收无关.
  2. lua中, string, table, closure, full userdata, thread存在虚拟栈上元素里的只是指针, 它们都会在生命周期结束后被垃圾回收.

参考资料

《虚拟机:系统与进程的通用平台》

《Lua设计与实现》

《Lua设计与实现》ebook》

《Lua5.0实现》