请教新天龙八部lua源码 lua源码hook几个Lua函数

随笔 - 17&
文章 - 48&
trackbacks - 0
242526272829301234567891011121314151617182021222324252627282930311234
阅读排行榜
评论排行榜
很早就想读lua的源码,也曾很多次浏览过大概。不过我一直没有深入去读,一是想自己在读lua源码之前,仅凭自己对lua使用的理解自己先实现一个简单的lua子集,二是我觉得自己实现过lua的子集之后也能帮助自己更容易的理解lua源码。前段时间,花了几个月的业余时间,实现了一个简单粗糙的lua子集()之后,我觉得现在可以开始读lua的源码了。从lua.c的main函数开始,lua.c是一个stand-alone的解释器,编译完就是一个交互式命令行解释器,输入一段lua代码,然后执行并返回结果,也可以执行一个lua文件。main:&&/*&call&'pmain'&in&protected&mode&*/&&lua_pushcfunction(L,&&pmain);&&lua_pushinteger(L,&argc);&&/*&1st&argument&*/&&lua_pushlightuserdata(L,&argv);&/*&2nd&argument&*/&&status&=&lua_pcall(L,&2,&1,&0);&&result&=&lua_toboolean(L,&-1);&&/*&get&result&*/main函数创建了lua_State之后就按照调用C导出给lua函数的方式调用了pmain函数。pmain函数中通过lua栈获取到命令行的argc和argv参数之后,对参数进行分析后,主要可以分为两个分支,一个处理交互命令行,一个处理文件。dotty出来交互命令行,handle_script处理lua文件。handle_script:& status&=&luaL_loadfile(L,&fname);&&lua_insert(L,&-(narg+1));&&if&(status&==&LUA_OK)&&&&status&=&docall(L,&narg,&LUA_MULTRET);&&else&&&&lua_pop(L,&narg);在handle_script中先loadfile,然后docall。loadfile会产生一个什么东西在栈上呢?写过lua的程序的人估计都会了解到下面这段lua代码:local f = load(filename)f()load会将文件chunk编译成一个function,然后我们就可以对它调用。如果我们详细看lua文档的话,这个函数可以带有upvalues,也就是这个函数其实是一个闭包(closure)。按照我自己实现的那个粗糙的lua子集的方式的话,每个运行时期的可调用的lua函数都是闭包。#define&luaL_loadfile(L,f)&&&&&luaL_loadfilex(L,f,NULL)luaL_loadfilex:&&if&(filename&==&NULL)&{&&&&lua_pushliteral(L,&"=stdin");&&&&lf.f&=&&&}&&else&{&&&&lua_pushfstring(L,&"@%s",&filename);&&&&lf.f&=&fopen(filename,&"r");&&&&if&(lf.f&==&NULL)&return&errfile(L,&"open",&fnameindex);&&}&&if&(skipcomment(&lf,&&c))&&/*&read&initial&portion&*/&&&&lf.buff[lf.n++]&=&'\n';&&/*&add&line&to&correct&line&numbers&*/&&if&(c&==&LUA_SIGNATURE[0]&&&&filename)&{&&/*&binary&file?&*/&&&&lf.f&=&freopen(filename,&"rb",&lf.f);&&/*&reopen&in&binary&mode&*/&&&&if&(lf.f&==&NULL)&return&errfile(L,&"reopen",&fnameindex);&&&&skipcomment(&lf,&&c);&&/*&re-read&initial&portion&*/&&}&&if&(c&!=&EOF)&&&&lf.buff[lf.n++]&=&c;&&/*&'c'&is&the&first&character&of&the&stream&*/&&status&=&lua_load(L,&getF,&&lf,&lua_tostring(L,&-1),&mode);luaL_loadfile是一个宏,实际是luaL_loadfilex函数,在luaL_loadfilex函数中,我们发现是通过调用lua_load函数实现,lua_load的函数原型是:LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data,&const char *chunkname, const char *mode);定义在lapi.c中,它接受一个lua_Reader的函数并把data作为这个reader的参数。在luaL_loadfilex函数中传给lua_load作为reader是一个static函数getF,getF通过fread读取文件。lua_load:& ZIO&z;&&int&&&lua_lock(L);&&if&(!chunkname)&chunkname&=&"?";&&luaZ_init(L,&&z,&reader,&data);&&status&=&luaD_protectedparser(L,&&z,&chunkname,&mode);在函数lua_load中,又将lua_Reader和data通过luaZ_init函数把数据绑定到ZIO的结构中,ZIO是buffered streams。之后调用luaD_protectedparser,此函数定义在ldo.c中,在这个函数中,我们发现它使用了构造lua_Reader和data的方式构造了调用函数f_parser和它的数据SParser,并将它们传给luaD_pcall,luaD_pcall的功能是在protected模式下用SParser数据调用f_parser函数,因此我们只需追踪f_parser函数即可。luaD_protectedparser:status&=&luaD_pcall(L,&f_parser,&&p,&savestack(L,&L-&top),&L-&errfunc);f_parser:&&if&(c&==&LUA_SIGNATURE[0])&{&&&&checkmode(L,&p-&mode,&"binary");&&&&cl&=&luaU_undump(L,&p-&z,&&p-&buff,&p-&name);&&}&&else&{&&&&checkmode(L,&p-&mode,&"text");&&&&cl&=&luaY_parser(L,&p-&z,&&p-&buff,&&p-&dyd,&p-&name,&c);&&}&f_parser通过数据头的signature来判断读取的数据是binary还是text的,如果是binary的数据,则调用luaU_undump来读取预编译好的lua chunks,如果是text数据,则调用luaY_parser来parse lua代码。我们发现luaU_undump和luaY_parser函数的返回值都是Closure *类型,这个刚好就和我们前面预计的一样,一个chunk load之后返回一个闭包。进入luaY_parser函数后,就调用了一个static的mainfunc开始parse lua代码。仔细回顾上面看过的函数,我们会发现每个C文件的导出函数都会使用lua开头,如果没有lua开头的函数都是static函数。并且我们会发现lua后的大写前缀可以标识这个函数所属的文件:luaL_loadfile&luaL_loadfilex&L应该是library的意思,属于lauxlibluaD_protectedparser&luaD_pcall&D是do的意思,属于ldoluaU_undump U 是undump的意思,属于lundumpluaY_parser Y 是代表yacc的意思,lua的parser最早是用过yacc生成的,后来改成手写,名字也保留下来,属于lparser其它的lua函数也都有这个规律。
阅读(7818)
&re: lua源码剖析(一)& 21:14&
无意中看到你的文章,感到好惭愧,我不知道该拿出什么勇气继续走下去---------爱上C++的菜鸟&&&&&&
&re: lua源码剖析(一)& 16:14&
佩服...&&&&&&
&re: lua源码剖析(一)& 10:02&
分析的优点基础了,要是能深入一点就好了..&&&&&&您的浏览器已经禁用了脚本,这可能会影响您正常使用本站的功能。
学习Lua源代码的一些经验和参考资料推荐
断断续续在博客更新Lua5.1.4分析的一些文章,当前写的还是很杂乱。前面大体分析了一些数据结构(通用数据结构,表),一些流程的处理(赋值,逻辑跳转),还有函数相关的代码。后面还有不少没有分析到,目测还有表相关的操作,GC,Lua调试器等等,内容还是不少。
今天暂时打住,记录一下我看Lua代码这段历程来用到的一些知识点,经验和参考资料。
起初决定看Lua代码的理由很简单。每个对代码有点喜欢的人,都会觉得能创造一门语言是很酷而且充满神秘感的事情,尤其对我这样非科班出身的人。龙书很早就买了,一直搁着,上面布满了灰尘。中间也接触过一些编译类的书籍,比如编译原理与实践就实现了一个Toy级别的脚本语言,虽然五脏俱全,但是毕竟离正经产品级的代码还是有距离。
Lua也是一早就听说过,但是一直没有太多了解。直到2011年换工作之后,使用C++ + Lua做游戏服务器。这个服务器大体的框架是C++层做为所谓的引擎层,实现网络,数据库查询,AOI等,C++提供一个逻辑线程,所有网络收报之后的处理将数据放到Lua层来做逻辑处理。看上去没有什么太特别的。而惊艳的地方在于,在引入了Lua脚本层之后,一般情况下对逻辑的调试修改都是在Lua层进行,由于脚本可以实现热更新,加断点调试,这样在修改调试的时候大部分时间不用再重启服务器了。游戏服务器不同于其他本身不需要保存状态的服务器,启动的时候需要加载数据,所以启动会比较慢,调试不用重启服务器这对开发效率是极大的提高。
这是Lua给我带来最爽的快感。Lua不是一个自己能跑起来的脚本语言,不像Python,Ruby那样,它需要一个宿主环境,从诞生起它就做好了给别人当配角的打算,对于CC++这样的静态编译语言来说,需要一些动态特性的时候搭配上Lua是绝好的搭配。
于是,怀着对创造一门语言的神秘感,和Lua带来的极大方便,我开始打算好好看Lua的代码。这各时间点,大概是2012年初了吧。
我最开始的计划,是自上而下的,也就是从常规的词法分析,语法分析到语义分析。然则这里遇到了比较大的问题。在编译原理与实践中的教学语言,使用的非常传统的方式,词法分析,语法分析之后生成语法树,然后再到语义分析。这是大概两遍扫描文件的流程,第一遍先生成语法树,第二遍再语义分析。然而Lua中并没有生成语法树,而是一边分析一边进行语义分析,一遍分析就搞定。由于省略了这一个中间步骤,给我阅读Lua代码带来极大困难,因为我之前并没有正经学过编译原理,只是大概知道怎么回事。所以开始的方式,是先去补编译原理的一些知识,这个过程大概持续了几个月,到现在,我仍然不认为我把这套流程完全明白了。
由于在前面遇到太多挫折,我后来反思了一下最开始的计划。可能最开始想的太完美,反而会进步缓慢,于是开始从Lua的opcode下手。提起这里参考最多的资料,应该是《A No Frills Intro To Lua51 VM Instructions》。这个资料最好的一点是,它将Lua中每个opcode基本过了一个遍,对每个指令给出例子场景和相应生成的opcode。我个人在阅读Lua代码的过程,将opcode像它那样分为几种类型,比如函数类,赋值类,表操作类,一个一个写示例代码调试。我写的demo例子代码是这样的:
int main(int argc, char *argv[]) {
char * = NULL;
if (argc == 1) {
file = &my.lua&;
file = argv[1];
lua_State *L = lua_open();
luaL_openlibs(L);
luaL_dofile(L, file);
很简单,加载一个叫my.lua的文件来执行。需要注意的一点的是,生成任何Lua opcode的时候,最后都会调用函数luaK_code。那么这个事情就很简单了,你按照你想分析的Lua执行写一段代码,然后开,在这个函数下断点,然后开始执行,它中间会产生什么opcode就一目了然了,慢慢分析吧。另外一个重要的函数就是lvm.c中的luaX_execute函数,这是Lua虚拟机开始执行指令的入口。你把握住了这两个入口点,生成什么指令和如何执行指令,就把握住了,剩下的就是耐心和毅力。所以我这时候的学习方式,就是不停的在my.lua中加入我想分析的Lua指令,在这两个重要的函数上下断点,分析其流程。这段时间是我对Lua了解进步很快的时间。
至于其他的资料,老实说很多都是看了一遍不会再看的(当然讲解Lua语法的书得准备一下随时查阅)。只有这份资料是每次开始看Lua代码时都会打开的。
回头看学习Lua代码的这一段历程,最开始学习编译原理以分析Lua语义分析的流程是最痛苦的。而当我现在对Lua源码有一定了解之后,我觉得如果你不是对编译特别感兴趣,只想弄明白Lua本身,实际上可以不必在这里耗费太多时间,直接按照我前面说的那样,把Lua的opcode过一遍收获可能会更大。而当你搞定了这些,再回头啃这块硬骨头的时候,心态也会不一样。做为一个过来人,我建议就是先以了解Lua本身为主,顺带学习编译为锦上添花的点。
将阅读Lua代码本身能学习和涉及到的一些知识点罗列如下,我看分为几大块:
1)编译原理相关的,如果你要学习如何创造一门语言这部分可以看看,包括词法分析,语法分析,语义分析。
2)Lua本身相关的,你要对Lua本身有个通透的了解,最好把它的opcode全部过一遍,Lua的opcode不算多,30多个吧,上面提到的参考资料每个都分析了,可以按照我的方法也每种指令都写Lua代码来调试跟踪整个流程。这个过程走下来之后,对Lua的了解整个就上了一个档次。
另外需要提一个点,Lua是使用Lua栈与C进行交互的,把这个点理解好可以理解Lua执行流程的关键点,网上有一篇文章分析的不错,在。
3) 表是Lua最重要的数据结构,可以说无处不在都是表,所以我把表相关知识点单独拿出来算一项以提高它的重要性。与它相关的知识点出了Lua表的实现,相关的opcode(表相关的只有三个),还包括如何使用Lua表实现面向对象,meta表,registry表,G表。
4)GC,Lua中使用的垃圾收集机制。
5)Lua如何进行热更新,如何实现一个Lua调试器。
6)其他的杂项,比如Lua中使用的一些编码技巧,如何用C实现异常处理,Lua代码优雅的点,等等。
从前面的说法来看,我觉得1)不是最重要的,能完整的把2)走完,对Lua的了解会倍增同时也会得到很多信心,这样就可以再按照其他知识点依次看下去。
最后的最后,做为一门生命期已经超过15年,而且流行程度能在世界排前20的语言,Lua只有不到2W行代码,而再把一些不重要的地方去掉,代码量还会更少,可能核心的代码不到1W行吧。另外做为一门纯C语言编写的项目,我认为其代码质量比大家都评价很高的还要高,这个问题我以后会专门聊一聊。我说了这些,是想说从学习优秀开源项目代码的角度来说,学习Lua的性价比很高,坚持下去收获很大。
收藏Ctrl+D
关注Linux/Unix应用技术、业界新闻,同时也发布开源、移动互联网等新鲜资讯!
—— Powered ——运行在随笔 - 17&
文章 - 48&
trackbacks - 0
242526272829301234567891011121314151617182021222324252627282930311234
阅读排行榜
评论排行榜
lua的VM执行代码是从lvm.c中的void luaV_execute(lua_State *L)开始:void&luaV_execute&(lua_State&*L)&{&&CallInfo&*ci&=&L-&&&LClosure&*&&TValue&*k;&&StkId&base;&newframe:&&/*&reentry&point&when&frame&changes&(call/return)&*/&&lua_assert(ci&==&L-&ci);&&cl&=&clLvalue(ci-&func);&&k&=&cl-&p-&k;&&base&=&ci-&u.l.base;&&/*&main&loop&of&interpreter&*/&&for&(;;)&{&&&&Instruction&i&=&*(ci-&u.l.savedpc++);&&&&StkId&&&&&if&((L-&hookmask&&&(LUA_MASKLINE&|&LUA_MASKCOUNT))&&&&&&&&&&&(--L-&hookcount&==&0&||&L-&hookmask&&&LUA_MASKLINE))&{&&&&&&Protect(traceexec(L));&&&&}&&&&/*&WARNING:&several&calls&may&realloc&the&stack&and&invalidate&`ra'&*/&&&&ra&=&RA(i);&&&&lua_assert(base&==&ci-&u.l.base);&&&&lua_assert(base&&=&L-&top&&&&L-&top&&&L-&stack&+&L-&stacksize);&&&&vmdispatch&(GET_OPCODE(i))&{&&&&&&vmcase(OP_MOVE,&&&&&&&&setobjs2s(L,&ra,&RB(i));&&&&&&)&&&&&&vmcase(OP_LOADK,&&&&&&&&TValue&*rb&=&k&+&GETARG_Bx(i);&&&&&&&&setobj2s(L,&ra,&rb);&&&&&&)&&&&&&vmcase(OP_LOADKX,&&&&&&&&TValue&*&&&&&&&&lua_assert(GET_OPCODE(*ci-&u.l.savedpc)&==&OP_EXTRAARG);&&&&&&&&rb&=&k&+&GETARG_Ax(*ci-&u.l.savedpc++);&&&&&&&&setobj2s(L,&ra,&rb);&&&&&&)&&&&&&vmcase(OP_LOADBOOL,&&&&&&&&setbvalue(ra,&GETARG_B(i));&&&&&&&&if&(GETARG_C(i))&ci-&u.l.savedpc++;&&/*&skip&next&instruction&(if&C)&*/&&&&&&)&&&&&&vmcase(OP_LOADNIL,&&&&&&&&int&b&=&GETARG_B(i);&&&&&&&&do&{&&&&&&&&&&setnilvalue(ra++);&&&&&&&&}&while&(b--);&&&&&&)&&&&&&vmcase(OP_GETUPVAL,&&&&&&&&int&b&=&GETARG_B(i);&&&&&&&&setobj2s(L,&ra,&cl-&upvals[b]-&v);&&&&&&)&&&&&&vmcase(OP_GETTABUP,&&&&&&&&int&b&=&GETARG_B(i);&&&&&&&&Protect(luaV_gettable(L,&cl-&upvals[b]-&v,&RKC(i),&ra));&&&&&&)&&&&&&vmcase(OP_GETTABLE,&&&&&&&&Protect(luaV_gettable(L,&RB(i),&RKC(i),&ra));&&&&&&)&&&&&&vmcase(OP_SETTABUP,&&&&&&&&int&a&=&GETARG_A(i);&&&&&&&&Protect(luaV_settable(L,&cl-&upvals[a]-&v,&RKB(i),&RKC(i)));&&&&&&)&&&&&&vmcase(OP_SETUPVAL,&&&&&&&&UpVal&*uv&=&cl-&upvals[GETARG_B(i)];&&&&&&&&setobj(L,&uv-&v,&ra);&&&&&&&&luaC_barrier(L,&uv,&ra);&&&&&&)&&&&&&vmcase(OP_SETTABLE,&&&&&&&&Protect(luaV_settable(L,&ra,&RKB(i),&RKC(i)));&&&&&&)&&&&&&vmcase(OP_NEWTABLE,&&&&&&&&int&b&=&GETARG_B(i);&&&&&&&&int&c&=&GETARG_C(i);&&&&&&&&Table&*t&=&luaH_new(L);&&&&&&&&sethvalue(L,&ra,&t);&&&&&&&&if&(b&!=&0&||&c&!=&0)&&&&&&&&&&luaH_resize(L,&t,&luaO_fb2int(b),&luaO_fb2int(c));&&&&&&&&checkGC(L,&ra&+&1);&&&&&&)&&&&&&vmcase(OP_SELF,&&&&&&&&StkId&rb&=&RB(i);&&&&&&&&setobjs2s(L,&ra+1,&rb);&&&&&&&&Protect(luaV_gettable(L,&rb,&RKC(i),&ra));&&&&&&)&&&&&&vmcase(OP_ADD,&&&&&&&&arith_op(luai_numadd,&TM_ADD);&&&&&&)&&&&&&vmcase(OP_SUB,&&&&&&&&arith_op(luai_numsub,&TM_SUB);&&&&&&)&&&&&&vmcase(OP_MUL,&&&&&&&&arith_op(luai_nummul,&TM_MUL);&&&&&&)&&&&&&vmcase(OP_DIV,&&&&&&&&arith_op(luai_numdiv,&TM_DIV);&&&&&&)&&&&&&vmcase(OP_MOD,&&&&&&&&arith_op(luai_nummod,&TM_MOD);&&&&&&)&&&&&&vmcase(OP_POW,&&&&&&&&arith_op(luai_numpow,&TM_POW);&&&&&&)&&&&&&vmcase(OP_UNM,&&&&&&&&TValue&*rb&=&RB(i);&&&&&&&&if&(ttisnumber(rb))&{&&&&&&&&&&lua_Number&nb&=&nvalue(rb);&&&&&&&&&&setnvalue(ra,&luai_numunm(L,&nb));&&&&&&&&}&&&&&&&&else&{&&&&&&&&&&Protect(luaV_arith(L,&ra,&rb,&rb,&TM_UNM));&&&&&&&&}&&&&&&)&&&&&&vmcase(OP_NOT,&&&&&&&&TValue&*rb&=&RB(i);&&&&&&&&int&res&=&l_isfalse(rb);&&/*&next&assignment&may&change&this&value&*/&&&&&&&&setbvalue(ra,&res);&&&&&&)&&&&&&vmcase(OP_LEN,&&&&&&&&Protect(luaV_objlen(L,&ra,&RB(i)));&&&&&&)&&&&&&vmcase(OP_CONCAT,&&&&&&&&int&b&=&GETARG_B(i);&&&&&&&&int&c&=&GETARG_C(i);&&&&&&&&StkId&&&&&&&&&L-&top&=&base&+&c&+&1;&&/*&mark&the&end&of&concat&operands&*/&&&&&&&&Protect(luaV_concat(L,&c&-&b&+&1));&&&&&&&&ra&=&RA(i);&&/*&'luav_concat'&may&invoke&TMs&and&move&the&stack&*/&&&&&&&&rb&=&b&+&base;&&&&&&&&setobjs2s(L,&ra,&rb);&&&&&&&&checkGC(L,&(ra&&=&rb&?&ra&+&1&:&rb));&&&&&&&&L-&top&=&ci-&&&/*&restore&top&*/&&&&&&)&&&&&&vmcase(OP_JMP,&&&&&&&&dojump(ci,&i,&0);&&&&&&)&&&&&&vmcase(OP_EQ,&&&&&&&&TValue&*rb&=&RKB(i);&&&&&&&&TValue&*rc&=&RKC(i);&&&&&&&&Protect(&&&&&&&&&&if&(cast_int(equalobj(L,&rb,&rc))&!=&GETARG_A(i))&&&&&&&&&&&&ci-&u.l.savedpc++;&&&&&&&&&&else&&&&&&&&&&&&donextjump(ci);&&&&&&&&)&&&&&&)&&&&&&vmcase(OP_LT,&&&&&&&&Protect(&&&&&&&&&&if&(luaV_lessthan(L,&RKB(i),&RKC(i))&!=&GETARG_A(i))&&&&&&&&&&&&ci-&u.l.savedpc++;&&&&&&&&&&else&&&&&&&&&&&&donextjump(ci);&&&&&&&&)&&&&&&)&&&&&&vmcase(OP_LE,&&&&&&&&Protect(&&&&&&&&&&if&(luaV_lessequal(L,&RKB(i),&RKC(i))&!=&GETARG_A(i))&&&&&&&&&&&&ci-&u.l.savedpc++;&&&&&&&&&&else&&&&&&&&&&&&donextjump(ci);&&&&&&&&)&&&&&&)&&&&&&vmcase(OP_TEST,&&&&&&&&if&(GETARG_C(i)&?&l_isfalse(ra)&:&!l_isfalse(ra))&&&&&&&&&&&&ci-&u.l.savedpc++;&&&&&&&&&&else&&&&&&&&&&donextjump(ci);&&&&&&)&&&&&&vmcase(OP_TESTSET,&&&&&&&&TValue&*rb&=&RB(i);&&&&&&&&if&(GETARG_C(i)&?&l_isfalse(rb)&:&!l_isfalse(rb))&&&&&&&&&&ci-&u.l.savedpc++;&&&&&&&&else&{&&&&&&&&&&setobjs2s(L,&ra,&rb);&&&&&&&&&&donextjump(ci);&&&&&&&&}&&&&&&)&&&&&&vmcase(OP_CALL,&&&&&&&&int&b&=&GETARG_B(i);&&&&&&&&int&nresults&=&GETARG_C(i)&-&1;&&&&&&&&if&(b&!=&0)&L-&top&=&ra+b;&&/*&else&previous&instruction&set&top&*/&&&&&&&&if&(luaD_precall(L,&ra,&nresults))&{&&/*&C&function?&*/&&&&&&&&&&if&(nresults&&=&0)&L-&top&=&ci-&&&/*&adjust&results&*/&&&&&&&&&&base&=&ci-&u.l.base;&&&&&&&&}&&&&&&&&else&{&&/*&Lua&function&*/&&&&&&&&&&ci&=&L-&&&&&&&&&&&ci-&callstatus&|=&CIST_REENTRY;&&&&&&&&&&goto&&&/*&restart&luaV_execute&over&new&Lua&function&*/&&&&&&&&}&&&&&&)&&&&&&vmcase(OP_TAILCALL,&&&&&&&&int&b&=&GETARG_B(i);&&&&&&&&if&(b&!=&0)&L-&top&=&ra+b;&&/*&else&previous&instruction&set&top&*/&&&&&&&&lua_assert(GETARG_C(i)&-&1&==&LUA_MULTRET);&&&&&&&&if&(luaD_precall(L,&ra,&LUA_MULTRET))&&/*&C&function?&*/&&&&&&&&&&base&=&ci-&u.l.base;&&&&&&&&else&{&&&&&&&&&&/*&tail&call:&put&called&frame&(n)&in&place&of&caller&one&(o)&*/&&&&&&&&&&CallInfo&*nci&=&L-&&&/*&called&frame&*/&&&&&&&&&&CallInfo&*oci&=&nci-&&&/*&caller&frame&*/&&&&&&&&&&StkId&nfunc&=&nci-&&&/*&called&function&*/&&&&&&&&&&StkId&ofunc&=&oci-&&&/*&caller&function&*/&&&&&&&&&&/*&last&stack&slot&filled&by&'precall'&*/&&&&&&&&&&StkId&lim&=&nci-&u.l.base&+&getproto(nfunc)-&&&&&&&&&&&int&&&&&&&&&&&/*&close&all&upvalues&from&previous&call&*/&&&&&&&&&&if&(cl-&p-&sizep&&&0)&luaF_close(L,&oci-&u.l.base);&&&&&&&&&&/*&move&new&frame&into&old&one&*/&&&&&&&&&&for&(aux&=&0;&nfunc&+&aux&&&&aux++)&&&&&&&&&&&&setobjs2s(L,&ofunc&+&aux,&nfunc&+&aux);&&&&&&&&&&oci-&u.l.base&=&ofunc&+&(nci-&u.l.base&-&nfunc);&&/*&correct&base&*/&&&&&&&&&&oci-&top&=&L-&top&=&ofunc&+&(L-&top&-&nfunc);&&/*&correct&top&*/&&&&&&&&&&oci-&u.l.savedpc&=&nci-&u.l.&&&&&&&&&&oci-&callstatus&|=&CIST_TAIL;&&/*&function&was&tail&called&*/&&&&&&&&&&ci&=&L-&ci&=&&&/*&remove&new&frame&*/&&&&&&&&&&lua_assert(L-&top&==&oci-&u.l.base&+&getproto(ofunc)-&maxstacksize);&&&&&&&&&&goto&&&/*&restart&luaV_execute&over&new&Lua&function&*/&&&&&&&&}&&&&&&)&&&&&&vmcasenb(OP_RETURN,&&&&&&&&int&b&=&GETARG_B(i);&&&&&&&&if&(b&!=&0)&L-&top&=&ra+b-1;&&&&&&&&if&(cl-&p-&sizep&&&0)&luaF_close(L,&base);&&&&&&&&b&=&luaD_poscall(L,&ra);&&&&&&&&if&(!(ci-&callstatus&&&CIST_REENTRY))&&/*&'ci'&still&the&called&one&*/&&&&&&&&&&return;&&/*&external&invocation:&return&*/&&&&&&&&else&{&&/*&invocation&via&reentry:&continue&execution&*/&&&&&&&&&&ci&=&L-&&&&&&&&&&&if&(b)&L-&top&=&ci-&&&&&&&&&&&lua_assert(isLua(ci));&&&&&&&&&&lua_assert(GET_OPCODE(*((ci)-&u.l.savedpc&-&1))&==&OP_CALL);&&&&&&&&&&goto&&&/*&restart&luaV_execute&over&new&Lua&function&*/&&&&&&&&}&&&&&&)&&&&&&vmcase(OP_FORLOOP,&&&&&&&&lua_Number&step&=&nvalue(ra+2);&&&&&&&&lua_Number&idx&=&luai_numadd(L,&nvalue(ra),&step);&/*&increment&index&*/&&&&&&&&lua_Number&limit&=&nvalue(ra+1);&&&&&&&&if&(luai_numlt(L,&0,&step)&?&luai_numle(L,&idx,&limit)&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&:&luai_numle(L,&limit,&idx))&{&&&&&&&&&&ci-&u.l.savedpc&+=&GETARG_sBx(i);&&/*&jump&back&*/&&&&&&&&&&setnvalue(ra,&idx);&&/*&update&internal&index&*/&&&&&&&&&&setnvalue(ra+3,&idx);&&/*&and&external&index&*/&&&&&&&&}&&&&&&)&&&&&&vmcase(OP_FORPREP,&&&&&&&&const&TValue&*init&=&&&&&&&&&const&TValue&*plimit&=&ra+1;&&&&&&&&const&TValue&*pstep&=&ra+2;&&&&&&&&if&(!tonumber(init,&ra))&&&&&&&&&&luaG_runerror(L,&LUA_QL("for")&"&initial&value&must&be&a&number");&&&&&&&&else&if&(!tonumber(plimit,&ra+1))&&&&&&&&&&luaG_runerror(L,&LUA_QL("for")&"&limit&must&be&a&number");&&&&&&&&else&if&(!tonumber(pstep,&ra+2))&&&&&&&&&&luaG_runerror(L,&LUA_QL("for")&"&step&must&be&a&number");&&&&&&&&setnvalue(ra,&luai_numsub(L,&nvalue(ra),&nvalue(pstep)));&&&&&&&&ci-&u.l.savedpc&+=&GETARG_sBx(i);&&&&&&)&&&&&&vmcasenb(OP_TFORCALL,&&&&&&&&StkId&cb&=&ra&+&3;&&/*&call&base&*/&&&&&&&&setobjs2s(L,&cb+2,&ra+2);&&&&&&&&setobjs2s(L,&cb+1,&ra+1);&&&&&&&&setobjs2s(L,&cb,&ra);&&&&&&&&L-&top&=&cb&+&3;&&/*&func.&+&2&args&(state&and&index)&*/&&&&&&&&Protect(luaD_call(L,&cb,&GETARG_C(i),&1));&&&&&&&&L-&top&=&ci-&&&&&&&&&i&=&*(ci-&u.l.savedpc++);&&/*&go&to&next&instruction&*/&&&&&&&&ra&=&RA(i);&&&&&&&&lua_assert(GET_OPCODE(i)&==&OP_TFORLOOP);&&&&&&&&goto&l_&&&&&&)&&&&&&vmcase(OP_TFORLOOP,&&&&&&&&l_tforloop:&&&&&&&&if&(!ttisnil(ra&+&1))&{&&/*&continue&loop?&*/&&&&&&&&&&setobjs2s(L,&ra,&ra&+&1);&&/*&save&control&variable&*/&&&&&&&&&&&ci-&u.l.savedpc&+=&GETARG_sBx(i);&&/*&jump&back&*/&&&&&&&&}&&&&&&)&&&&&&vmcase(OP_SETLIST,&&&&&&&&int&n&=&GETARG_B(i);&&&&&&&&int&c&=&GETARG_C(i);&&&&&&&&int&&&&&&&&&Table&*h;&&&&&&&&if&(n&==&0)&n&=&cast_int(L-&top&-&ra)&-&1;&&&&&&&&if&(c&==&0)&{&&&&&&&&&&lua_assert(GET_OPCODE(*ci-&u.l.savedpc)&==&OP_EXTRAARG);&&&&&&&&&&c&=&GETARG_Ax(*ci-&u.l.savedpc++);&&&&&&&&}&&&&&&&&luai_runtimecheck(L,&ttistable(ra));&&&&&&&&h&=&hvalue(ra);&&&&&&&&last&=&((c-1)*LFIELDS_PER_FLUSH)&+&n;&&&&&&&&if&(last&&&h-&sizearray)&&/*&needs&more&space?&*/&&&&&&&&&&luaH_resizearray(L,&h,&last);&&/*&pre-allocate&it&at&once&*/&&&&&&&&for&(;&n&&&0;&n--)&{&&&&&&&&&&TValue&*val&=&ra+n;&&&&&&&&&&luaH_setint(L,&h,&last--,&val);&&&&&&&&&&luaC_barrierback(L,&obj2gco(h),&val);&&&&&&&&}&&&&&&&&L-&top&=&ci-&&&/*&correct&top&(in&case&of&previous&open&call)&*/&&&&&&)&&&&&&vmcase(OP_CLOSURE,&&&&&&&&Proto&*p&=&cl-&p-&p[GETARG_Bx(i)];&&&&&&&&Closure&*ncl&=&getcached(p,&cl-&upvals,&base);&&/*&cached&closure&*/&&&&&&&&if&(ncl&==&NULL)&&/*&no&match?&*/&&&&&&&&&&pushclosure(L,&p,&cl-&upvals,&base,&ra);&&/*&create&a&new&one&*/&&&&&&&&else&&&&&&&&&&setclLvalue(L,&ra,&ncl);&&/*&push&cashed&closure&*/&&&&&&&&checkGC(L,&ra&+&1);&&&&&&)&&&&&&vmcase(OP_VARARG,&&&&&&&&int&b&=&GETARG_B(i)&-&1;&&&&&&&&int&j;&&&&&&&&int&n&=&cast_int(base&-&ci-&func)&-&cl-&p-&numparams&-&1;&&&&&&&&if&(b&&&0)&{&&/*&B&==&0?&*/&&&&&&&&&&b&=&n;&&/*&get&all&var.&arguments&*/&&&&&&&&&&Protect(luaD_checkstack(L,&n));&&&&&&&&&&ra&=&RA(i);&&/*&previous&call&may&change&the&stack&*/&&&&&&&&&&L-&top&=&ra&+&n;&&&&&&&&}&&&&&&&&for&(j&=&0;&j&&&b;&j++)&{&&&&&&&&&&if&(j&&&n)&{&&&&&&&&&&&&setobjs2s(L,&ra&+&j,&base&-&n&+&j);&&&&&&&&&&}&&&&&&&&&&else&{&&&&&&&&&&&&setnilvalue(ra&+&j);&&&&&&&&&&}&&&&&&&&}&&&&&&)&&&&&&vmcase(OP_EXTRAARG,&&&&&&&&lua_assert(0);&&&&&&)&&&&}&&}}此函数先从CallInfo中取出运行的lua closure,取出这个closure的寄存器的base指针和closure的函数Proto的常量列表k。debug hook进入for循环开始执行代码,先取出当前指令Instruction,根据Lua State的hook mask来判断是否需要hook代码执行,这个hook代码执行就是lua提供给外界调试代码的库,我们可以使用这个debug库实现自己的调试器,两年前我使用这个debug实现过一个简单的lua调试器。(博客:&代码放在github上:)lua提供了四种hookmask,分别是:#define&LUA_MASKCALL&&&&&(1&&&&LUA_HOOKCALL)#define&LUA_MASKRET&&&&&(1&&&&LUA_HOOKRET)#define&LUA_MASKLINE&&&&&(1&&&&LUA_HOOKLINE)#define&LUA_MASKCOUNT&&&&&(1&&&&LUA_HOOKCOUNT)LUA_MASKCALL表示每次调用函数的时候hook;LUA_MASKRET表示每次函数返回的时候hook;LUA_MASKLINE表示每行执行的时候hook;LUA_MASKCOUNT表示每执行count条lua指令hook一次,这里的count是debug.sethook ([thread,] hook, mask [, count])中传递的。LUA_MASKLINE和LUA_MASKCOUNT类型的hook是在函数的开头这段代码里hook:& &&if&((L-&hookmask&&&(LUA_MASKLINE&|&LUA_MASKCOUNT))&&&&&&&&&&&(--L-&hookcount&==&0&||&L-&hookmask&&&LUA_MASKLINE))&{&&&&&&Protect(traceexec(L));&&&&}而LUA_MASKCALL和LUA_MASKRET类型的hook则分别在call和return的时候hook,具体是在ldo.c中的luaD_precall和luaD_poscall中hook。如果设置了debug hook,那执行指令的时候就会检测一下是否需要调用hook函数。若需要LUA_MASKLINE或LUA_MASKCOUNT的hook则调用lvm.c中的traceexec函数,而traceexec函数通过调用ldo.c中的luaD_hook函数完成;若需要LUA_MASKCALL或LUA_MASKRET的hook则ldo.c中的luaD_precall和luaD_poscall会对hook进行检测,最终还是调用到ldo.c中的luaD_hook函数完成。void&luaD_hook&(lua_State&*L,&int&event,&int&line)&{&&lua_Hook&hook&=&L-&&&if&(hook&&&&L-&allowhook)&{&&&&CallInfo&*ci&=&L-&&&&&ptrdiff_t&top&=&savestack(L,&L-&top);&&&&ptrdiff_t&ci_top&=&savestack(L,&ci-&top);&&&&lua_Debug&&&&&ar.event&=&event;&&&&ar.currentline&=&&&&&ar.i_ci&=&&&&&luaD_checkstack(L,&LUA_MINSTACK);&&/*&ensure&minimum&stack&size&*/&&&&ci-&top&=&L-&top&+&LUA_MINSTACK;&&&&lua_assert(ci-&top&&=&L-&stack_last);&&&&L-&allowhook&=&0;&&/*&cannot&call&hooks&inside&a&hook&*/&&&&ci-&callstatus&|=&CIST_HOOKED;&&&&lua_unlock(L);&&&&(*hook)(L,&&ar);&&&&lua_lock(L);&&&&lua_assert(!L-&allowhook);&&&&L-&allowhook&=&1;&&&&ci-&top&=&restorestack(L,&ci_top);&&&&L-&top&=&restorestack(L,&top);&&&&ci-&callstatus&&=&~CIST_HOOKED;&&}}我们发现这个调用的hook函数是注册在L-&hook中的C函数指针,我们通过debug.sethook注册的hook函数是lua的函数,那这个注册的C函数肯定是用来完成lua函数与C函数之间的转换。L-&hook这个函数指针的注册是通过ldebug.c中的lua_sethook函数完成:LUA_API&int&lua_sethook&(lua_State&*L,&lua_Hook&func,&int&mask,&int&count)&{&&if&(func&==&NULL&||&mask&==&0)&{&&/*&turn&off&hooks?&*/&&&&mask&=&0;&&&&func&=&NULL;&&}&&if&(isLua(L-&ci))&&&&L-&oldpc&=&L-&ci-&u.l.&&L-&hook&=&&&L-&basehookcount&=&&&resethookcount(L);&&L-&hookmask&=&cast_byte(mask);&&return&1;}在ldblib.c中的db_sethook中调用了lua_sethook函数,这个hook函数是ldblib.c中的hookf:static&void&hookf&(lua_State&*L,&lua_Debug&*ar)&{&&static&const&char&*const&hooknames[]&=&&&&{"call",&"return",&"line",&"count",&"tail&call"};&&gethooktable(L);&&lua_pushthread(L);&&lua_rawget(L,&-2);&&if&(lua_isfunction(L,&-1))&{&&&&lua_pushstring(L,&hooknames[(int)ar-&event]);&&&&if&(ar-&currentline&&=&0)&&&&&&lua_pushinteger(L,&ar-&currentline);&&&&else&lua_pushnil(L);&&&&lua_assert(lua_getinfo(L,&"lS",&ar));&&&&lua_call(L,&2,&0);&&}}这个函数就把注册的lua hook函数取出来然后调用,传递的hook类型作为hook函数的第一参数,分别是{"call", "return", "line", "count", "tail call"}。寄存器结构lua是寄存器虚拟机,它为每个函数在运行时期最多分配250个寄存器。函数运行时都是通过这些寄存器来操作数据,指令操作寄存器的参数都是记录着相应寄存器的下标。在for循环中,通过RA(i)获取到指令i的参数A的寄存器,lua指令格式在上一篇中有介绍(),RA宏获得A参数的寄存器下标,再加上当前运行函数的base指针,就可以得出相应的寄存器。再之后通过GET_OPCODE(i)获得opcode并进入switch-case,分别针对每条指令类型取出相应的其它指令参数并执行。lua寄存器结构如图:对每条指令分别根据指令类型操作A、B、C、Ax、Bx、sBx参数,参数可以是寄存器的下标,也可以是Proto的常量列表k的下标。case的第一条指令OP_MOVE就是最简单的指令,从指令i中取出参数B,然后把B指向的TValue赋值给A指向的TValue。从常量列表中把TValue load到寄存器中的指令有两种,分别是OP_LOADK和OP_LOADKX。在OP_LOADK中,参数Bx就是Proto的常量列表的下标,然后简单的将这个TValue load到寄存器RA(i)中,如果一个函数的常量很多,个数超过了,参数Bx(14~31bits,共18位)的表示范围,这时候就要使用OP_LOADKX指令表示。在OP_LOADKX指令中,会继续读取下一条指令,下一条指令的类型是OP_EXTRAARG,它的参数是Ax(6~31bits,共26位)来表示Proto的常量列表的下标,这样常量的个数就扩大到了26位的表示范围。函数调用的栈结构lua的函数调用指令是OP_CALL和OP_TAILCALL,实际上函数调用是通过luaD_precall完成,这个函数判断被调用的函数是否是C函数,如果是C函数的话那就将函数执行完返回,如果不是则准备好一些基本数据,并把指令切换到被调用的lua函数的指令地址上,然后执行被调用函数的指令。int&luaD_precall&(lua_State&*L,&StkId&func,&int&nresults)&{&&lua_CFunction&f;&&CallInfo&*&&int&n;&&/*&number&of&arguments&(Lua)&or&returns&(C)&*/&&ptrdiff_t&funcr&=&savestack(L,&func);&&switch&(ttype(func))&{&&&&case&LUA_TLCF:&&/*&light&C&function&*/&&&&&&f&=&fvalue(func);&&&&&&goto&C&&&&case&LUA_TCCL:&{&&/*&C&closure&*/&&&&&&f&=&clCvalue(func)-&f;&&&&&Cfunc:&&&&&&luaD_checkstack(L,&LUA_MINSTACK);&&/*&ensure&minimum&stack&size&*/&&&&&&ci&=&next_ci(L);&&/*&now&'enter'&new&function&*/&&&&&&ci-&nresults&=&&&&&&&ci-&func&=&restorestack(L,&funcr);&&&&&&ci-&top&=&L-&top&+&LUA_MINSTACK;&&&&&&lua_assert(ci-&top&&=&L-&stack_last);&&&&&&ci-&callstatus&=&0;&&&&&&if&(L-&hookmask&&&LUA_MASKCALL)&&&&&&&&luaD_hook(L,&LUA_HOOKCALL,&-1);&&&&&&lua_unlock(L);&&&&&&n&=&(*f)(L);&&/*&do&the&actual&call&*/&&&&&&lua_lock(L);&&&&&&api_checknelems(L,&n);&&&&&&luaD_poscall(L,&L-&top&-&n);&&&&&&return&1;&&&&}&&&&case&LUA_TLCL:&{&&/*&Lua&function:&prepare&its&call&*/&&&&&&StkId&base;&&&&&&Proto&*p&=&clLvalue(func)-&p;&&&&&&luaD_checkstack(L,&p-&maxstacksize);&&&&&&func&=&restorestack(L,&funcr);&&&&&&n&=&cast_int(L-&top&-&func)&-&1;&&/*&number&of&real&arguments&*/&&&&&&for&(;&n&&&p-&&n++)&&&&&&&&setnilvalue(L-&top++);&&/*&complete&missing&arguments&*/&&&&&&base&=&(!p-&is_vararg)&?&func&+&1&:&adjust_varargs(L,&p,&n);&&&&&&ci&=&next_ci(L);&&/*&now&'enter'&new&function&*/&&&&&&ci-&nresults&=&&&&&&&ci-&func&=&&&&&&&ci-&u.l.base&=&base;&&&&&&ci-&top&=&base&+&p-&&&&&&&lua_assert(ci-&top&&=&L-&stack_last);&&&&&&ci-&u.l.savedpc&=&p-&&&/*&starting&point&*/&&&&&&ci-&callstatus&=&CIST_LUA;&&&&&&L-&top&=&ci-&&&&&&&if&(L-&hookmask&&&LUA_MASKCALL)&&&&&&&&callhook(L,&ci);&&&&&&return&0;&&&&}&&&&default:&{&&/*&not&a&function&*/&&&&&&func&=&tryfuncTM(L,&func);&&/*&retry&with&'function'&tag&method&*/&&&&&&return&luaD_precall(L,&func,&nresults);&&/*&now&it&must&be&a&function&*/&&&&}&&}}lua的函数调用栈是通过一个CallInfo的链表来表示,每一个CallInfo链表元素表示一层函数调用,每个CallInfo通过prev和next指针分别指向前面的函数和后面的函数。CallInfo中的base和top分别指向这个调用栈帧的起始地址和结束地址,base到top这些栈空间在函数运行内部就是可用的寄存器。func则指向这个被调用函数的closure所在lua栈中的地址。函数CallInfo链表结构与lua的栈的格式的关系有如下3种:1.被调用函数为普通lua函数时,调用者把被调用函数的closure放到栈中,然后把传入函数的参数依次放入栈中。被调用者的CallInfo中的func指针指向它所属的closure,并把这个运行时期的base指针指向传进来的第一参数,如下图:2.被调用函数是vararg(变参)的lua函数时,被调用者的CallInfo的func还是指向相应的closure,固定参数则会复制一份,并把原来的设置为nil,而多出的参数则保留在原始位置,并将base指针指向复制的第一个实参。这样,base指针前面的就是多出的参数,即固定的参数是从base指针指向的地方开始,而变参数则在base指针前面,这样可以保证后续的指令访问固定的参数跟非可变参数函数(第一种情况)时一致。例如:一个变参函数function f(a, b, ...) end,这样调用f(1, 2, 3, 4),那么会把1和2复制一份,分别作为a和b的实参,3和4则保留在原始位置,也就是在base指针之前。3.被调用的函数是C函数的时候,CallIInfo的top指向L-&top + LUA_MINSTACK(20),为C函数操作lua栈预留的最小stack空间,在被调用的C函数中若使用的lua栈空间比较多时,需要调用lua_checkstack来向lua申请保证有足够的栈空间使用,不然就会出现lua stack overflow的错误。函数调用完成后,lua通过指令OP_RETURN返回,这时候,最后一个CallIInfo就回收了。在回收之前,通过luaD_poscall来将函数的返回值复制到相应的位置,函数返回值复制到的位置的起点就是closure的位置,把closure覆盖掉。若调用的CallInfo表示的是C函数时,也是通过luaD_poscall完成返回值的复制。int&luaD_poscall&(lua_State&*L,&StkId&firstResult)&{&&StkId&&&int&wanted,&i;&&CallInfo&*ci&=&L-&&&if&(L-&hookmask&&&(LUA_MASKRET&|&LUA_MASKLINE))&{&&&&if&(L-&hookmask&&&LUA_MASKRET)&{&&&&&&ptrdiff_t&fr&=&savestack(L,&firstResult);&&/*&hook&may&change&stack&*/&&&&&&luaD_hook(L,&LUA_HOOKRET,&-1);&&&&&&firstResult&=&restorestack(L,&fr);&&&&}&&&&L-&oldpc&=&ci-&previous-&u.l.&&/*&'oldpc'&for&caller&function&*/&&}&&res&=&ci-&&&/*&res&==&final&position&of&1st&result&*/&&wanted&=&ci-&&&L-&ci&=&ci&=&ci-&&&/*&back&to&caller&*/&&/*&move&results&to&correct&place&*/&&for&(i&=&&i&!=&0&&&&firstResult&&&L-&&i--)&&&&setobjs2s(L,&res++,&firstResult++);&&while&(i--&&&0)&&&&setnilvalue(res++);&&L-&top&=&&&return&(wanted&-&LUA_MULTRET);&&/*&0&iff&wanted&==&LUA_MULTRET&*/}尾递归lua对于递归会有尾递归优化,如果一个函数调用是尾递归的话,那么函数的调用栈是不会增长的。lua通过OP_TAILCALL指令完成尾递归调用,这条指令的前面一段跟OP_CALL相似,通过luaD_precall增加函数调用栈信息CallInfo。当luaD_precall返回时,调用的不是C函数,则会将新增的CallInfo与上一个CallInfo栈帧合并,然后把新增的CallInfo移除掉,这样的尾递归调用就不会导致栈帧增长了。lua的其它指令就是很明确的操作一些寄存器和常量来完成代码执行。
阅读(4804)
&re: lua源码剖析(三):VM& 13:42&
lz的分析很赞请问一下:变参数的函数被调用时,参数为啥需要复制一份,base指针则指向复制的那些实参的第一个地址。&&&&&&
&re: lua源码剖析(三):VM& 15:56&
@zenk我对这段描述不够清楚,现在已经补上了。复制的只是固定的那些参数,&...&所代表的变参并没有复制,保留在base指针前面,这样可以保证后续指令访问固定的那些参数和非可变参数函数是一致。&&&&&&
&re: lua源码剖析(三):VM& 08:06&
@airtrack按照你说的意思访问变参数的寄存器应该是小于0的,但是看了一下源代码没发现lua是如何处理,估计是OP_VARARG这个没理解透彻想请教一下,lua如何处理访问变参数的,比如说可以通过arg这个本地变量访问,那lua什么时候创建这个本地变量(看了一下午源码没明白)谢了&&&&&&
&re: lua源码剖析(三):VM& 09:13&
@zenkOP_VARARG这条指令就是访问...参数的,可以看到用int n = cast_int(base - ci-&func) - cl-&p-&numparams - 1;来计算可变参数变参的个数(cl-&p-&numparams是这个函数的固定参数的个数),接下来在判断B参数是否为0,如果为0,那就有多少个变参就复制多少个,不然就复制B - 1个变参。在lua中如下代码会生成OP_VARARG指令:function f(...)
print(...)end至于arg变量,在lua 5.2中已经不会自动打包可变参数...为一个arg变量,在lua 5.2中要使用arg变量可以这样写:function f(...)
local arg = table.pack(...)end&&&&&&
&re: lua源码剖析(三):VM& 13:45&
@airtrack谢谢,受教了&&&&&&

我要回帖

更多关于 lua hook 的文章

 

随机推荐