源码赏析Lua钩子函数的原理和应用!
Posted 西山居质量
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了源码赏析Lua钩子函数的原理和应用!相关的知识,希望对你有一定的参考价值。
Lua钩子函数
钩子的API
为了方便调试,Lua提供的几个关于钩子的API。
typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar);
LUA_API void (lua_sethook) (lua_State *L, lua_Hook func, int mask, int count);
LUA_API lua_Hook (lua_gethook) (lua_State *L);
LUA_API int (lua_gethookmask) (lua_State *L);
LUA_API int (lua_gethookcount) (lua_State *L);
1 lua_Hook()是钩子的回调函数,lua_Debug中存储的是当前函数的状态信息。其中包括hook事件类型、函数名、函数类型、函数的定义行号、当前调用代码的行号等等。
struct lua_Debug
{
int event;
const char *name;/* (n) */
const char *namewhat;/* (n) 'global', 'local', 'field', 'method' */
const char *what;/* (S) 'Lua', 'C', 'main', 'tail' */
const char *source;/* (S) */
int currentline;/* (l) */
int linedefined;/* (S) */
int lastlinedefined;/* (S) */
unsigned char nups;/* (u) number of upvalues */
unsigned char nparams;/* (u) number of parameters */
char isvararg; /* (u) */
char istailcall;/* (t) */
char short_src[LUA_IDSIZE]; /* (S) */
/* private part */
struct CallInfo *i_ci; /* active function */
};
2 lua_sethook(),用于安装钩子,func是我们定义的回调函数,mask是hook的事件类型。事件类型有以下几种,call是函数调用,ret函数返回,line是执行每行代码,count是记录执行了多少条指令。
#define LUA_MASKCALL(1 << LUA_HOOKCALL)
#define LUA_MASKRET(1 << LUA_HOOKRET)
#define LUA_MASKLINE(1 << LUA_HOOKLINE)
#define LUA_MASKCOUNT(1 << LUA_HOOKCOUNT)
3 其他三个函数是获取刚刚设置的参数。
钩子的实现
钩子的内部实现是在luaD_hook()这个函数里,这里主要有几个操作,保存堆栈,构造调试信息,把allowhook改为0禁止钩子的递归调用,然后执行hook回调函数,最后恢复堆栈和allowhook。
void luaD_hook (lua_State *L, int event, int line)
{
lua_Hook hook = L->hook;
if (hook && L->allowhook)
{
/* make sure there is a hook */
CallInfo *ci = L->ci;
ptrdiff_t top = savestack(L, L->top);
ptrdiff_t ci_top = savestack(L, ci->top);
lua_Debug ar;
ar.event = event;
ar.currentline = line;
ar.i_ci = 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;
}
}
Lua Hook 获取 lua性能
背景
对Lua源码的调用有一定的了解之后,需要在不修改源码的情况下,获取Lua的调用情况的数据。为了实现这个需求,需要用到Hook的技术。
实现原理
Lua在执行lua代码,解析call和ret的luacode指令时,利用lua的hook api可以回调到我们设定的hook函数中,然后我们可以计算函数从开始调用到函数退出的耗时情况。
实现步骤
1
利用funchook实现hook lua的功能,在这里我们需要hook的是luaL_newState()和lua_close()这两个函数。luaL_newState的作用是返回一个新创建的lua虚拟机,lua_close是关闭lua虚拟机。
/*
利用__attribute__((constructor))实现在main函数执行前进入installhook函数加载hook
*/
__attribute__((constructor))
void installhook(void)
{
int rv;
funchook_t *funchook = funchook_create();
luaL_newstate_func = luaL_newstate;
rv = funchook_prepare(funchook, (void**)&luaL_newstate_func, luaL_newstate_hook);
if (rv != 0)
{
printf("error prepare
");
}
lua_close_func = lua_close;
rv = funchook_prepare(funchook, (void**)&lua_close_func, lua_close_hook);
if (rv != 0)
{
printf("error prepare
");
}
rv = funchook_install(funchook, 0); if (rv != 0)
{
printf("error install
");
}
}
2
在luaL_newState的hook回调中,利用lua_sethook给此虚拟机添加hook。lua的hook有几种方式,包括call、return、line等,这里我们用到的是call和return。
钩子的内部实现是在luaD_hook()这个函数里,这里主要有几个操作,保存堆栈,构造调试信息,把allowhook改为0禁止钩子的递归调用,然后执行hook回调函数,最后恢复堆栈和allowhook。
3
这是lua的hook回调函数,通过lua_getinfo获取lua函数的信息,包括lua文件名,函数名,函数定义行号,当前行号,调用栈等信息。利用这些可以记录lua函数从开始调用到退出的耗时情况。
void lua_hookcall(lua_State *L, lua_Debug *ar)
{
// 获取函数信息
lua_getinfo(L, "Sln", ar);
// 这里只关心Lua的函数调用
if (strcmp(ar->what, "Lua") == 0)
{
if (LUA_HOOKCALL == ar->event)
{
/* function call */
} else
{
/* function return */
}
}
}
Lua Hook 获取 lua性能
好啦~本期只是展示简单的实现步骤,有兴趣的同学可以自行了解一下具体的实现或留言和我们一起讨论哦。
精彩回顾 :
西山居质量Test+团队是金山西山居工作室的专业质量团队,对于游戏领域有超过18年的测试经验积累,专注于为手游、端游提供各类自动化测试等全方位质量保障。
以上是关于源码赏析Lua钩子函数的原理和应用!的主要内容,如果未能解决你的问题,请参考以下文章