|
最大的坑应该是暗坑,就是在大部分情况下正常,在小部分情况下不正常,一旦掉进去就很难爬出来的那种。
从业这几年,机缘巧合,还真遇到过几件,就拿记忆比较深刻的来说吧
被大V点赞了,我觉得应该认真严肃一点,去修改一下错别字
IL2CPP
想听故事的可以接着看,想看问题和结果的可以直接跳到最后
故事发生在16年的冬天,那天晚上我们准备提交iOS审核版本。
晚上10点的时候差不多测试妥当了(我们是先在编辑器上测试,然后在android上测试)。我们准备在iOS上跑一下功能就提审。
等第一个iOS版本出来之后,我们发现没办法登陆服务器了,下面就开始排查问题,前端后端一起上,最后定位到是玩家上线时的初始化消息包没有解开。那就从网络通讯开始查起吧。
这里要简单介绍一下我们用的通讯协议,这是个在protobuff基础上修改的二进制协议,前两位是包长,接着两位是消息id,后面是压缩标记,最后面是消息体。在unity这一端,我们用工具生成每个消息对应的消息类,包括解码,生成unity能用的c#结构等等。解码的过程可以认为是反序列化的过程。
首先从后端抓数据包,用工具解码,没有任何问题,那就是前端的问题了。虽然问题范围缩小了,同时问题也变得棘手了。编辑器和android上都没有问题,这些是比较好调试的平台,但是在iOS上调试就变得相当困难了。
iOS上为了支持64位的设备必须把代码从C#转换为cpp,这个过程就是IL2CPP。unity自带的有工具,但是生成的cpp代码简直不是给人看的,在这些代码上单步调试是个巨恶心的过程。还有我们的打包机器也不太给力,一台低配的二手mac mini,编译一次unity项目工程是相当的酸爽,这也是我们最后才测试iOS版本的原因,严重影响工作效率。(好在我们现在已经更换成了mac pro垃圾桶,快的让人不太习惯)。
我们先用最土最笨的方式进行排查,就是插桩打印log,然后从“海量“的日志中去分析是哪条消息包出的问题,最后定位出来是最近新添加的消息。找到了问题下面就是怎么办的问题了,当时已经凌晨两点了,小伙伴们又饿又困,就想耍小聪明,有人提议既然不是逻辑上有问题,那就先调整一下消息的编号,将出问题的消息的编号往前放一放。服务端迅速的做了调整,我们测试了一下,天呀,能进游戏了,还没来得及高兴,就发现有功能用不了,用不了的功能就是刚才交换消息号的功能。也就是几个消息号排在最后的消息对应的功能。难道是消息号太大的问题?当时我们已经有1000多条消息了,编号已经排到了3000多。难道要收缩消息编号,排列的更紧密一些?这显然是个馊主意,暂且不说问题是不是消息号太大,就算排列的再紧密随着我们后续功能的开发,总会到达3000多号的,这不是正解。直觉告诉我,问题应该出在那些生成的cpp代码上,于是我就开始去排查和单步相关的代码。
单步是个相当痛苦的过程,这里暂且不表,反正最后我定位到了一段神奇的代码里
在c#这边它是这样的,一段根据消息id找解码器的逻辑代码
public static Decoder GetDecoder(uint msgid)
{
Decoder _decoder = null;
switch (msgid)
{
case 1:
_decoder = Msg_1.decoder;
break;
case 2:
_decoder = Msg_2.decoder;
break;
............
case 10086:
_decoder = Msg_10086.decoder;
break;
............
}
return _decoder;
}
对应到IL2CPP那里,它是这样的
V_0 = (Decoder_t_1323225970_0 *)NULL;
uint32_t L_0 = ___msgid;
V_1 = L_0;
uint32_t L_1 = V_1;
if (((int32_t)((int32_t)L_1-(int32_t)3)) == 0)
{
goto IL_01ff;
}
if (((int32_t)((int32_t)L_1-(int32_t)3)) == 1)
{
goto IL_00c0;
}
if (((int32_t)((int32_t)L_1-(int32_t)3)) == 2)
{
goto IL_0211;
}
if (((int32_t)((int32_t)L_1-(int32_t)3)) == 4)
{
goto IL_abcd;
}
...
IL_01ff:
{
IntPtr_t L_23 = { (void*)Msg_1_decoder_m_428528862_0_MethodInfo_var };
Decoder_t_1323225970_0 * L_24 = (Decoder_t_1323225970_0 *)il2cpp_codegen_object_new (Decoder_t_1323225970_0_il2cpp_TypeInfo_var);
Decoder__ctor_m2046815770_0(L_24, NULL, L_23, /*hidden argument*/NULL);
V_0 = L_24;
goto IL_bcef;
}
IL_00c0:
{
IntPtr_t L_25 = { (void*)Msg_2_decoder_m_428528862_0_MethodInfo_var };
Decoder_t_1323225970_0 * L_26 = (Decoder_t_1323225970_0 *)il2cpp_codegen_object_new (Decoder_t_1323225970_0_il2cpp_TypeInfo_var);
Decoder__ctor_m2046815770_0(L_26, NULL, L_25, /*hidden argument*/NULL);
V_0 = L_26;
goto IL_bcef;
}
.....
上边那段c#代码switch里有1000多个分支,对应的就是我们的每一条消息。
当我在cpp代码里单步到出问题的消息id时,发现它没办法跳转到正确的逻辑分支里,我忍着眼疼看了这个上万行的函数,发现出问题的消息id直接跳转到默认的分支里去了,就相当于去直接执行了switch里的default分支。至今这个问题我也一直没时间去找原因,也没给unity官方提bug,因为可能只有我们才会傻到在一个switch里写1000多个分支加逻辑,而且后来我用简单的switch加好几万个分支也没办法复现,只有我们的这段用工具生成的消息解码函数才会有问题。
这时已经是凌晨6点多了,我没时间去深究问题,立即去改消息解码生成工具,把这个恶心的switch改掉。然后问题解决了,天亮了。 |
|