Luajit 字节码分析之 KSTR

用户头像
whosemario
关注
发布于: 2020 年 08 月 28 日
Luajit字节码分析之KSTR

luajit的解析器是通过汇编代码实现的,代码晦涩难懂,但是我还是想尝试对一些OPCode进行解析,比如下面的lua代码:

-- file:code.lua
local s1 = "123"
local s2 = "1234"

通过下面的指令可以获得对应的opcode:

> luajit -bl code.lua # code.lua 即为上面lua代码的文件名
KGC 0 "123"
KGC 1 "1234"
0001 KSTR 0 0 ; "123"
0002 KSTR 1 1 ; "1234"
0003 RET0 0 1

那么KSTR这个OPCode在Luajit里面的实现是怎样的呢?可以在vm_x64.dasc文件里面找到:

case BC_KSTR:
| ins_AND // RA = dst, RD = str const (~)
| mov RD, [KBASE+RD*8]
| settp RD, LJ_TSTR
| mov [BASE+RA*8], RD
| ins_next
break;

这几行汇编代码都分别代表什么意思,我们一个一个的来解析。

首先假设我们在执行第一个指令 :

KSTR 0 0 ; "123"

那么RA=0,RD=0。

ins_AND宏定义

|.macro ins_AND; not RD; .endmacro

ins_AND宏的实现很简单,就是对RD取反,取反后RD=-1。

关于GCProto的内存布局

// @file:lj_obj.h
typedef struct GCproto {
GCHeader;
...
MRef k;
...
} GCproto;

我们这里只列出了与KSTR指令相关的数据。MRef k指向的地址是存放全局常量的基地址。GCProto的内存布局如下:



那么在上面的汇编代码里面,KBASE就是global consts内存区域的高位地址。为何KBASE即为global consts的高位地址呢?

case BC_IFUNCV:
...
} else {
| mov KBASE, [PC-4+PC2PROTO(k)]
| ins_next
}
...

在执行BC_KSTR之前会先执行BC_IFUNCV,其中会计算KBASE的值,他的计算方法是[PC-4+PC2PROTO(k)]。PC2PROTO宏又是什么?

#define PC2PROTO(field) ((int)offsetof(GCproto, field)-(int)sizeof(GCproto))

不难看出PC2PROTO(k)是计算GCproto->k到GCproto结构体尾部的字节长度,然后取负。PC寄存器存储的是当前OPCode运行的地址的后4字节,BC_IFUNCV又是第一个运行的OPCode,因此PC-4代表的意义就是GCProto结构体底部的绝对地址。因此可以想到PC-4+PC2PROTO(k)即为GCproto->k所在的地址。因此:

KBASE = GCproto->k->ptr64

我们回来再看下面这条汇编指令:

| mov RD, [KBASE+RD*8]

因此,[KBASE+RD*8]计算的就是第一个常量字符串"123"的地址,即:

RD = GCproto->k->ptr64 - 8



然后进行第二条汇编指令:

|.macro settp, reg, tp
| mov64 ITYPE, ((uint64_t)tp<<47)
| or reg, ITYPE
|.endmacro
|settp RD, LJ_TSTR

我个人感觉这种设计很巧妙。x64的线性地址48-63位是保留的,因此luajit利用这几位做类型信息的保存,settp是将LJ_TSTR左移47位,然后与RD做或运算。

最后一个汇编指令:

| mov [BASE+RA*8], RD

有了上面的基础,这个指令比较好理解了,它将RD的数据放到栈(BASE)的第一个位置上。



发布于: 2020 年 08 月 28 日 阅读数: 284
用户头像

whosemario

关注

还未添加个人签名 2017.11.01 加入

游戏开发

评论

发布
暂无评论
Luajit字节码分析之KSTR