崩溃代码
lua 复制代码 -- 创建一个node,但是并不加入到scene中,在下一帧就会被回收 local wildPointer = cc.Label:create(); wildPointer:setString("123"); -- 在下一帧,此时的wildPointer已经是野指针了,再次调用就会触发崩溃 wildPointer:setString("");
这种lua代码非常容易出现,比如一个lua变量持有了一个node,但是这个node被remove了,此时lua层再次操作这个node,就会直接出问题。
崩溃日志
less 复制代码 com.game.test A Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0 in tid 17314 (GLThread 4719), pid 17231 (com.game.test) pid-18570 A pid: 17231, tid: 17314, name: GLThread 4719 >>> com.game.test <<< pid-18570 A #00 pc 005b9674 /data/app/~~2dSXjw-Pwc5t1uuaelP0sA==/com.game.test-HVaqUS1t_su3ikG9VESEIQ==/lib/arm/libcocos2dlua.so (lua_cocos2dx_Label_setString(lua_State*)+180) (BuildId: 2198ce3de806dfb68c9d6346167edc344b301702) pid-18570 A #01 pc 009165cc /data/app/~~2dSXjw-Pwc5t1uuaelP0sA==/com.game.test-HVaqUS1t_su3ikG9VESEIQ==/lib/arm/libcocos2dlua.so (BuildId: 2198ce3de806dfb68c9d6346167edc344b301702) pid-18570 A #02 pc 00909055 /data/app/~~2dSXjw-Pwc5t1uuaelP0sA==/com.game.test-HVaqUS1t_su3ikG9VESEIQ==/lib/arm/libcocos2dlua.so (lua_pcall+20) (BuildId: 2198ce3de806dfb68c9d6346167edc344b301702)
通过ida,005b9674
反汇编结果
对应的代码如下,可以看到就是转换之后的cobj出现了问题
cobj此时是null,因为COCOS2D_DEBUG
的原因,这个问题在release模式下直接崩溃闪退,debug版本还能提示异常。
解决办法
造成这个问题的根本原因是这种引用持有野指针的情况非常极限,想要复现都非常困难。
可以通过bugly后台,看到哪个函数崩溃,就在哪个函数里面将
if(!cobj){return 0;}
的逻辑打开,避免崩溃
但是这样要不停地强更新,而且频繁更新app主包,会造成用户流失的,所以这种办法治标不治本,在c++层做各种保护根本起不到任何作用,问题根源还是lua脚本的逻辑,所以只能在lua层修复了这个问题。
要修复问题必须复现才行,但是这种情况又非常极限难以复现,所以只能尽可能的还原现场,根据收集到的信息进行排查,顺着这个思路,我们首先想到的是堆栈,是tolua_tousertype
转为null导致的崩溃,那么我们可以增加一个机制,发现转换类型指针失败,,立即收集堆栈相关的数据并上报到后台。
这对于解决问题非常有帮助,当收到这些数据后,可以尝试复现下现场,这样才能从根源上解决问题,并且修复的肯定是lua代码,直接热更就行了,根本不需要更新app。
具体实现思路
在函数tolua_tousertype
下个钩子
- tolua++.h
c++
复制代码 TOLUA_API void set_tousertype_nullhandler(void(*func)); // 设置钩子函数
- tolua++.c
c++ 复制代码 void (*_tousertype_nullhandler)() = NULL; // 钩子函数 TOLUA_API void set_tousertype_nullhandler(void(* func)) { _tousertype_nullhandler = func; } TOLUA_API void* tolua_tousertype (lua_State* L, int narg, void* def) { if (lua_gettop(L)<abs(narg)) return def; else { void* u; if (!lua_isuserdata(L, narg)) { if (!push_table_instance(L, narg)) return NULL; }; u = lua_touserdata(L,narg); void* ret = (u==NULL) ? NULL : *((void**)u); /* nil represents NULL */ if (ret == NULL && _tousertype_nullhandler != NULL) { _tousertype_nullhandler(); // 如果发现类型转换失败了,触发钩子函数 } return ret; } }
然后在钩子函数里面进行lua堆栈数据的收集工作,这里面用到了lua_getstack
,lua_getinfo
获取每一层堆栈,通过lua_getlocal
获取每层堆栈的变量情况,这里就不再贴代码了。
堆栈数据格式
示例
ini 复制代码 <setString> at =[C]:-1 # 第1层 <func> at .\crash-lua/index.lua:82 # 第2层 a:10 # 第2层的相关变量 b:{a=1,c="c",d={d1=1,d2="d2",},} c:<function>0F5F5C78 [Lua] at .\core/testLayer.lua:122 # 第3层 ref:<userdata> type:0
不过考虑到网络传输带宽,需要将数据压缩下再发送到服务器,格式就不需要精简了,没有必要。
这里就要开发配套的后台日志系统,又是一个新坑。