最近在看skynet的代码,总体感觉跟 erlang很像,理念都是基于Actor模型,即万物皆Actor,Actor之间通过发送消息进行通信。(这里说的“万物”倾向于表示有能动作用,有独立行为的个体。)

不同的是, skynet使用 c 和 lua实现,这两个语言相较erlang比较流行。 skynet的Actor 是指skynet服务,skynet服务 类似erlang进程,调度方式也有点接近,skynet启动时会初始化多个调度线程,用于同时处理多个服务,每个服务都有一定的调度次数约束(处理消息条数),挂起或达到约束就失去调度权。不过skynet服务的调度不是轮转,而是消息驱动方式(先来消息先调度)。实现上,每个服务都是一个lua state,也就是一个lua虚拟机,效果等同沙箱,实现隔离。另外,lua state本身没有多线程支持的,skynet实现上在一个线程运行多个lua state实例。

好了,切入今天的主题吧,skynet项目lua代码加密。文章利用lua编译成二进制实现加密,方法比较简单。

lua编译

lua代码可以被编译二进制文件,就是lua可以同时支持源代码加载和二进制加载两种方式。

但是,lua编译成二进制有一点使用限制,lua二进制支持跨平台,跨版本,但必须在相同字长的机器上。就是说,32位lua编译的二进制不能在64位下使用。另外,虽然官方表示可跨版本,但也声明了跨版本可能存在不兼容的情况,具体情况没有说明。

另一点,lua代码编译成二进制,不意味着执行更快,这个过程只是预编译,将代码编译成字节码(bytecodes)。因为lua源代码执行前也要生成字节码,所以二进制方式在一定程度上提高了代码加载的速度。

lua编译器

]

options说明:

options

说明

-l

生成lua编译后字节码的可视化数据,这对于学习lua虚拟机很有帮助

-o file

编译lua代码,输出文件为file。默认生成 luac.out

-s

写入输出文件时去掉调试信息。可以减少输出文件的大小,但错误信息就比较简单,例如,少了行号和局部变量名

-v

显示版本信息

编译skynet项目的lua脚本

skynet没有提供工具来生成,所以这里简单写个脚本演示下。目前skynet没有支持luajit

#!/bin/bash





LUAC=./3rd/lua/luac


mkdir -p bin





Luas=`find . -name "*.lua"`


for file in $Luas


do


 filename = `basename $file`


 $LUAC -o bin/$filename $file


done

为了代码能够被执行到,还需要修改配置文件,所有的lua文件都要从bin路径查找。

以 examples/config 为例

root = "./"


thread = 8


logger = nil


logpath = "."


harbor = 1


address = "127.0.0.1:2526"


master = "127.0.0.1:2013"


start = "main" -- main script


bootstrap = "snlua bootstrap" -- The service for bootstrap


standalone = "0.0.0.0:2013"


luaservice = root.."bin/?.lua;"


lualoader = root.."bin/loader.lua"


snax = root.."bin/?.lua"


-- snax_interface_g = "snax_g"


cpath = root.."cservice/?.so"


-- daemon = "./skynet.pid"

lua代码加密的另一种方式

前面介绍了把lua代码编译成二进制达到加密目的,这已经足够了。如果想进一步加密代码,可以使用下面的方法。

这里以异或加密为例说明。

// luac.c       





        static int writer(lua_State* L, const void* p, size_t size, void* u)       


        {       


int ret = !0;


size_t i =0;


char* pp = (char *)malloc(sizeof(char*)*size);


        UNUSED(L);       


memcpy(pp, p, size);


for(;i<size;i++) pp[i] ^=250;


(void*)pp,size,1,(FILE*)u)!=1) && (size!=0);       


free(pp);


        return ret;       


        }

lua执行代码也要相应做修改,否则无法执行。

//lauxlib.c





static const char *getF (lua_State *L, void *ud, size_t *size) {


 LoadF *lf = (LoadF *)ud;


 (void)L; /* not used */


 if (lf->n > 0) { /* are there pre-read characters to be read? */


 *size = lf->n; /* return them (chars already in buffer) */


 lf->n = 0; /* no more pre-read characters */


 }


 else { /* read a block from file */


 /* 'fread' can return > 0 *and* set the EOF flag. If next call to


 'getF' called 'fread', it might still wait for user input.


 The next check avoids this problem. */


 if (feof(lf->f)) return NULL;


 *size = fread(lf->buff, 1, sizeof(lf->buff), lf->f); /* read block */


 }


do{size_t i=0; for(;i<*size;) lf->buff[i++] ^= 250;}while(0);


 return lf->buff;


}

这样就可以了,但是要注意了,不能同时修改以上两处内容,编译和执行要分开处理,否则无法编译lua代码。(skynet版本不同,代码可以不同,仅参考)

luajit编译

按照skynet的计划,lua有可能换上luajit。luajit也支持二进制编译,lua代码加密方式类似。

luajit -b filename newfile

luajit -b test.lua test.out # Save bytecode to test.out


luajit -bs test.lua test.out # As above, Strip debug info (default).


luajit -bg test.lua test.out # Keep debug info


luajit -be "print('hello world')" test.out # Save cmdline script





luajit -bl test.lua # List to stdout


luajit -bl test.lua test.txt # List to test.txt


luajit -ble "print('hello world')" # List cmdline script

最后语

最后回顾下,文章利用lua编译成二进制实现加密,编译成字节码,可直接被lua加载,比较实用。比较之下,改编译器代码不是很适用,这里只是提供可能的思路。

好了,今天就到这里。再研究下skynet的实现,继续做分享。