思路梳理
基本上lj_meta_cache造成的崩溃,是没有复现的希望了,毕竟luajit都好几年的代码了,而且使用的人非常多,所以问题不在luajit,请不要怀疑luajit作者的能力,大概率是项目的使用方式不当导致的。
所以排查方向也要发生变化,我们不能盯着luajit的源码,阅读理解源码细节解决问题基本不现实,毕竟luajit用来学习还行,但是要看明白,不是一时半会的事情,我简单看了看都涉及到lua语法分析、反汇编等各种非常基础的知识点,另外jit也是一门非常深奥的学科,基本上像c#,java,js都有类似的技术。
既然无法找到原因,那么只能考虑从调用信息上下手,统计出来都是谁在调用这个函数,然后尝试从lua代码层反推可能得原因,目前看来这是一个非常靠谱的解决办法。
将编译好的luajit移植到cocos2dx
我自己将luajit通过gitaction自动打包了一个版本,看luajit源码一时半会也找不到原因,考虑直接将luajit搞到项目里面
cocos2dx在window上默认使用的也是luajit
这样子问题就非常棘手了,我们编译出来的luajit仅仅是和cocos2dx自带的不同而已,但项目在windows上开发过程中,从来没有遇到luajit相关的任何崩溃,说明可能是平台差异导致的问题。
这里我们注意到了,luajit的编译过程,有生成对应平台的反汇编代码的步骤,所以这可能也是导致这个崩溃问题无法在windows上出现。
具体的集成过程不再赘述,简单来说我们只需要将debug版本的lua51.dll替换即可。
需要注意的是,为了得到更精确的调试信息,我们需要将/O2
改为/Od
bash 复制代码 cl /nologo /c /O2 /W3 /D_CRT_SECURE_NO_DEPRECATE cl /nologo /c /Od /W3 /D_CRT_SECURE_NO_DEPRECATE
这是cl的编译选项,对应的vs项目属性是
调用信息
将debug版本集成到项目中后,运行调试看到了更深层次调用lj_meta_cache的逻辑
因为通过反汇编我们是观察到52
行更加容易崩溃,所以我们在此处下断点,观察整个调用栈
竟然还能看到lua的相关变量
在目标函数设置钩子
因为调用量非常大,可以考虑将经过该函数的调用栈整理出来,然后统计汇总,再回过头review相关的游戏代码,进行排查,具体做法如下:
- 在luajit中定义一个全局的函数指针,项目中我们会实际设置这个回调函数,用来做相关的堆栈收集等处理
1. c 2. 复制代码 #ifndef __LIBHOOK__ #define __LIBHOOK__ #ifdef __cplusplus extern "C" { #endif void (*hookFunction)(); extern __declspec(dllexport) void setHookFunction(void(*func)); void emitHookFunction(); #ifdef __cplusplus } #endif #endif
- 在目标函数lj_meta_chche中调用这个回调,这样当执行到lj_meta_chche时,就会触发回调
1. c 2. 复制代码 /* Negative caching of a few fast metamethods. See the lj_meta_fast() macro. */ cTValue *lj_meta_cache(GCtab *mt, MMS mm, GCstr *name) { cTValue *mo = lj_tab_getstr(mt, name); lj_assertX(mm <= MM_FAST, "bad metamethod %d", mm); if (!mo || tvisnil(mo)) { /* No metamethod? */ emitHookFunction(); // 钩子函数在这里 mt->nomm |= (uint8_t)(1u<<mm); /* Set negative cache flag. */ return NULL; } return mo; }
这样对luajit的破坏是最小的,修改完毕后,需要重新编译下luajit。
将调用信息保存为log
cpp堆栈
c++的堆栈获取网上有很多例子,使用到的相关函数如下:
- SymFromAddr检索指定地址的符号信息,成功返回TRUE,失败返回FALSE,并调用GetLastError获取错误码
- SymGetLineFromAddr64查找指定地址的源行。
- StackWalk,这个函数可以参考下
lua堆栈
注意,以上方式我们只能获取到c、c++层的调用堆栈,对于lua层的我们是拿不到的,在lj_meta_cache下断点,在vs自带的调用堆栈可以看到整个调用栈,不仅有c++ lua51.dll的堆栈信息, 还有lua_vm的堆栈
使用SymInitialize
、SymFromAddr
、SymGetLineFromAddr64
,发现我是无法捕获到lua_vm层的堆栈信息的,CaptureStackBackTrace
显示的堆栈数量比vs调用堆栈显示的少,怎么样才能捕获到lua_vm层堆栈
各种折腾,最后我发现是调用lua_getstack
拿到的lua堆栈,正好和vs的对上了,这么看来vs ide底层在捕获堆栈,也有类似的处理,vs的确非常强。
完结
到这里,基本上我们已经尽可能的得到lj_meta_cache的相关信息了,剩下就是配套工具的开发去快速生成这个log文件。
反正最终也没有解决这个问题,只是尽可能的从代码层拿到了相关有用的信息,后续工作就是对log的分析和对相关lua代码定位的工作了。