LuaJit执行文件过程分析
通过之前对luajit -b命令的分析可知,在luajit.c文件的runargs函数中,用于手机参数,对相应的参数调用对应的函数,若返回LUA_OK则执行handle_script函数,该函数用于执行一个lua脚本文件,该函数如下:
cpp
static int handle_script(lua_State *L, char **argx)
{
int status;
const char *fname = argx[0];
if (strcmp(fname, "-") == 0 && strcmp(argx[-1], "--") != 0)
fname = NULL; /* stdin */
status = luaL_loadfile(L, fname);
if (status == LUA_OK) {
/* Fetch args from arg table. LUA_INIT or -e might have changed them. */
int narg = 0;
lua_getglobal(L, "arg");
if (lua_istable(L, -1)) {
do {
narg++;
lua_rawgeti(L, -narg, narg);
} while (!lua_isnil(L, -1));
lua_pop(L, 1);
lua_remove(L, -narg);
narg--;
} else {
lua_pop(L, 1);
}
status = docall(L, narg, 0);
}
return report(L, status);
}
参数argx包含了执行的lua脚本文件名,luaL_loadfile加载了一个lua脚本,通过之前的分析可知,luaL_loadfile会加载一个字节码文件或者转换一个源码文件,最终都会得到转换好的原型结构。在进行一些参数设置后,调用docall函数,执行字节码,该函数如下:
cpp
static int docall(lua_State *L, int narg, int clear)
{
int status;
int base = lua_gettop(L) - narg; /* function index */
lua_pushcfunction(L, traceback); /* push traceback function */
lua_insert(L, base); /* put it under chunk and args */
#if !LJ_TARGET_CONSOLE
signal(SIGINT, laction);
#endif
status = lua_pcall(L, narg, (clear ? 0 : LUA_MULTRET), base);
#if !LJ_TARGET_CONSOLE
signal(SIGINT, SIG_DFL);
#endif
lua_remove(L, base); /* remove traceback function */
/* force a complete garbage collection in case of errors */
if (status != LUA_OK) lua_gc(L, LUA_GCCOLLECT, 0);
return status;
}
Docall函数继续调用lua_pcall函数,该函数在lua_api.c中如下:
cpp
LUA_API int lua_pcall(lua_State *L, int nargs, int nresults, int errfunc)
{
global_State *g = G(L);
uint8_t oldh = hook_save(g);
ptrdiff_t ef;
int status;
api_check(L, L->status == LUA_OK || L->status == LUA_ERRERR);
api_checknelems(L, nargs+1);
if (errfunc == 0) {
ef = 0;
} else {
cTValue *o = stkindex2adr(L, errfunc);
api_checkvalidindex(L, o);
ef = savestack(L, o);
}
status = lj_vm_pcall(L, api_call_base(L, nargs), nresults+1, ef);
if (status) hook_restore(g, oldh);
return status;
}
在进行一些安全性的检查和处理后,调用lj_vm_pcall执行函数,lj_vm_pcall使用汇编实现,头文件如下:
cpp
LJ_ASMF int lj_vm_pcall(lua_State *L, TValue *base, int nres1, ptrdiff_t ef);
X86版本的调用入口简化后如下:
Lua
|->vm_pcall: // Setup protected C frame and enter VM.
| // (lua_State *L, TValue *base, int nres1, ptrdiff_t ef)
| saveregs
| mov PC, FRAME_CP
| jmp >1
|1: // Entry point for vm_pcall above (PC = ftype).
| mov L:RB, SAVE_L
| mov RA, INARG_BASE // Caveat: overlaps SAVE_CFRAME!
| mov DISPATCH, L:RB->glref // Setup pointer to dispatch table.
| mov KBASEa, L:RB->cframe // Add our C frame to cframe chain.
| mov SAVE_CFRAME, KBASEa
| mov SAVE_PC, L:RB // Any value outside of bytecode is ok.
| add DISPATCH, GG_G2DISP
| mov L:RB->cframe, esp
|2: // Entry point for vm_resume/vm_cpcall (RA = base, RB = L, PC = ftype).
| mov [DISPATCH+DISPATCH_GL(cur_L)], L:RB
| set_vmstate INTERP
| mov BASE, L:RB->base // BASE = old base (used in vmeta_call).
| add PC, RA
| sub PC, BASE // PC = frame delta + frame type
| mov RD, L:RB->top
| sub RD, RA
| shr NARGS:RD, 3
| add NARGS:RD, 1 // RD = nargs+1
|->vm_call_dispatch:
| mov LFUNC:RB, [RA-8]
| cmp dword [RA-4], LJ_TFUNC
| jne ->vmeta_call // Ensure KBASE defined and != BASE.
|->vm_call_dispatch_f:
| mov BASE, RA
| ins_call
| // BASE = new base, RB = func, RD = nargs+1, PC = caller PC
在还原指针和参数栈后,ins_call开始执行解释器的call指令