Unity资源加载闪退问题深度分析

news/2024/11/28 21:58:55/

游戏线上测试总是有一些很奇怪的crash信息上报,闪退点是Unity引擎C++层的方法GameObject::GetSupportedMessagesRecalculate。我们自己平时跑游戏,偶尔也会在场景切换的时候发生闪退。经过初步分析,确定是同一个crash。虽然收集到的闪退率不高,但既然我们自己人都碰到了,那线上实际情况可能会更容易出。

结论很简单,想看结论,直接跳到末尾即可。分析过程很坎坷,断断续续跨了有两三个月。分析过程分为两个阶段,阶段一主要是围绕崩溃点本身进行的分析,没有得出结论;阶段二,是在编辑器中复现出来的另外一种情况,最终找到了突破点。

阶段一

简略crash堆栈

从名字上猜测,是资源加载出来的时候出了问题,很可能是资源损坏了。

GameObject::GetSupportedMessagesRecalculate()
GameObject::SetSupportedMessagesDirty()
MonoBehaviour::AwakeFromLoad(AwakeFromLoadMode)
AwakeFromLoadQueue::PersistentManagerAwakeSingleObject(Object&, AwakeFromLoadMode)
TimeSliceAwakeFromLoadQueue::IntegrateTimeSliced(int)
PreloadManager::UpdatePreloadingSingleStep(PreloadManager::UpdatePreloadingFlags, int)
PreloadManager::UpdatePreloading()

详细crash信息

所幸在开发环境下,复现了一次,拿到了比较详细的堆栈信息。

E/CRASH: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***Version '2019.4.16f1 (e05b6e02d63e)', Build type 'Development', Scripting Backend 'mono', CPU 'armeabi-v7a'Build fingerprint: 'OPPO/R9s/R9s:6.0.1/MMB29M/1528528402:user/release-keys'Revision: '0'ABI: 'arm'Timestamp: 2021-08-13 12:39:01+0800pid: 18030, tid: 18096, name: UnityMain  >>> com.stormx.test <<<uid: 10458signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x4Cause: null pointer dereferencer0  00000000  r1  00000000  r2  00000003  r3  f36cf930r4  d9668370  r5  00000000  r6  d7ac5b20  r7  d744b760r8  fd3ed0b0  r9  00000003  r10 0001fcf2  r11 00000001
E/CRASH:     ip  f36cfab8  sp  f36cefb8  lr  dacd9ba3  pc  dacda05ebacktrace:#00 pc 0040f05e  /data/app/com.stormx.test-2/lib/arm/libunity.so (GameObject::GetSupportedMessagesRecalculate()+18) (BuildId: 3efcb2d01629f3876c8f81f15aad592efc75b1af)#01 pc 0040eb9f  /data/app/com.stormx.test-2/lib/arm/libunity.so (GameObject::SetSupportedMessagesDirty()+22) (BuildId: 3efcb2d01629f3876c8f81f15aad592efc75b1af)#02 pc 0086b70f  /data/app/com.stormx.test-2/lib/arm/libunity.so (MonoBehaviour::AwakeFromLoad(AwakeFromLoadMode)+14) (BuildId: 3efcb2d01629f3876c8f81f15aad592efc75b1af)#03 pc 008ad579  /data/app/com.stormx.test-2/lib/arm/libunity.so (AwakeFromLoadQueue::PersistentManagerAwakeSingleObject(Object&, AwakeFromLoadMode)+32) (BuildId: 3efcb2d01629f3876c8f81f15aad592efc75b1af)#04 pc 0089ed43  /data/app/com.stormx.test-2/lib/arm/libunity.so (PersistentManager::IntegrateObjectAndUnlockIntegrationMutexInternal(int)+24) (BuildId: 3efcb2d01629f3876c8f81f15aad592efc75b1af)#05 pc 006d3c11  /data/app/com.stormx.test-2/lib/arm/libunity.so (TimeSliceAwakeFromLoadQueue::IntegrateTimeSliced(int)+320) (BuildId: 3efcb2d01629f3876c8f81f15aad592efc75b1af)#06 pc 006d52e9  /data/app/com.stormx.test-2/lib/arm/libunity.so (PreloadManager::UpdatePreloadingSingleStep(PreloadManager::UpdatePreloadingFlags, int)+80) (BuildId: 3efcb2d01629f3876c8f81f15aad592efc75b1af)#07 pc 006d5915  /data/app/com.stormx.test-2/lib/arm/libunity.so (PreloadManager::UpdatePreloading()+180) (BuildId: 3efcb2d01629f3876c8f81f15aad592efc75b1af)
E/CRASH:  #08 pc 006c95bb  /data/app/com.stormx.test-2/lib/arm/libunity.so (InitPlayerLoopCallbacks()::EarlyUpdateUpdatePreloadingRegistrator::Forward()+38) (BuildId: 3efcb2d01629f3876c8f81f15aad592efc75b1af)#09 pc 006c2b13  /data/app/com.stormx.test-2/lib/arm/libunity.so (ExecutePlayerLoop(NativePlayerLoopSystem*)+52) (BuildId: 3efcb2d01629f3876c8f81f15aad592efc75b1af)#10 pc 006c2b47  /data/app/com.stormx.test-2/lib/arm/libunity.so (ExecutePlayerLoop(NativePlayerLoopSystem*)+104) (BuildId: 3efcb2d01629f3876c8f81f15aad592efc75b1af)#11 pc 006c2cf9  /data/app/com.stormx.test-2/lib/arm/libunity.so (PlayerLoop()+264) (BuildId: 3efcb2d01629f3876c8f81f15aad592efc75b1af)#12 pc 008d16a3  /data/app/com.stormx.test-2/lib/arm/libunity.so (UnityPlayerLoop()+490) (BuildId: 3efcb2d01629f3876c8f81f15aad592efc75b1af)#13 pc 008f3fd5  /data/app/com.stormx.test-2/lib/arm/libunity.so (nativeRender(_JNIEnv*, _jobject*)+40) (BuildId: 3efcb2d01629f3876c8f81f15aad592efc75b1af)#14 pc 00592481  /data/app/com.stormx.test-2/oat/arm/base.odex (boolean com.unity3d.player.UnityPlayer.nativeRender()+76)

可疑日志

崩溃前,一段可疑的日志。说明崩溃前有过资源释放操作。

D/Unity: System memory in use before: 105.3 MB.
D/Unity: System memory in use after: 100.9 MB.Unloading 13317 unused Assets to reduce memory usage. Loaded Objects now: 8653.Total: 205.532813 ms (FindLiveObjects: 6.525573 ms CreateObjectMapping: 6.495416 ms MarkObjects: 159.178958 ms  DeleteObjects: 33.328802 ms)
I/CrashReport-Native: Register backup native handler

源码

不要问我代码是哪里来的,总之有一份旧版的代码可以参考。从源码上看不出任何问题,不知道崩溃的行数,不好定位。只能反编译看看。

void GameObject::SetSupportedMessagesDirty()
{Assert(!IsDestroying());MessageIdentifier::OptimizedMessageMask oldSupportedMessage = m_SupportedMessages;m_SupportedMessages = 0;if (IsDestroying())return;GetSupportedMessagesRecalculate();if (oldSupportedMessage != m_SupportedMessages){for (Container::iterator i = m_Component.begin(); i != m_Component.end(); ++i)if (i->GetComponentPtr())i->GetComponentPtr()->SupportedMessagesDidChange(m_SupportedMessages);}
}
void GameObject::GetSupportedMessagesRecalculate()
{Assert(!IsDestroying());m_SupportedMessages = 0;for (Container::iterator i = m_Component.begin(); i != m_Component.end(); ++i)if (i->GetComponentPtr()) // !crash!m_SupportedMessages |= i->GetComponentPtr()->CalculateSupportedMessages();
}

反汇编

用IDA反编译一下libunity.so。 这个库位于Unity安装目录的Editor\Data\PlaybackEngines\AndroidPlayer\Variations目录中,如果android打包是mono debug模式, 为mono\Development\Libs\armeabi-v7a\libunity.so;如果是il2cpp debug模式,为il2cpp\Development\Libs\armeabi-v7a\libunity.so;如果是release版本,把路径中的Development换成Release;如果是64位模式,把路径中的armeabi-v7a换成arm64-v8a。

对汇编不熟悉,只能边查资料,结合源码来分析。从crash的位置能够定位到发生闪退的指令位置为: #00 pc 0040f05e, 为了方便解读,以下反编译代码顺序略有调整:

.text:0040F04C ; _DWORD GameObject::GetSupportedMessagesRecalculate(GameObject *__hidden this)
.text:0040F04C _ZN10GameObject31GetSupportedMessagesRecalculateEv
.text:0040F04C                                         ; CODE XREF: GameObject::SetSupportedMessagesDirty(void)+16↑p
.text:0040F04C ; __unwind {
.text:0040F04C                 PUSH            {R4,R5,R7,LR}
.text:0040F04E                 LDR             R2, [R0,#0x3C] // r2 = m_Component.size(). r2 == 3, 有三个组件
.text:0040F052                 LDR             R1, [R0,#0x2C] // r1 = m_Component.begin()
.text:0040F050                 MOV             R4, R0 // r4 = r0 = this
.text:0040F054                 MOVS            R0, #0
.text:0040F058                 STR             R0, [R4,#0x50] // m_SupportedMessages = 0;
.text:0040F056                 CMP             R2, #0 // 判断m_Component.size() 是否等于 0
.text:0040F05A                 BEQ             locret_40F07C // if == 0 goto locret_40F07C
.text:0040F05C                 MOV             R5, R1 // Container::iterator i = m_Component.begin()
.text:0040F05E
.text:0040F05E loc_40F05E                              ; CODE XREF: GameObject::GetSupportedMessagesRecalculate(void)+2E↓j
.text:0040F05E !crash!         LDR             R0, [R5,#4] // component = i->GetComponentPtr()
.text:0040F060                 CBZ             R0, loc_40F072 //if (i == nullptr) goto loc_40F072
.text:0040F062                 LDR             R1, [R0]
.text:0040F064                 LDR             R1, [R1,#0x58] // r1 = i->GetComponentPtr()->CalculateSupportedMessages
.text:0040F066                 BLX             R1       // call CalculateSupportedMessages()
.text:0040F068                 LDR             R1, [R4,#0x2C] // r1 = this->m_Component.begin()
.text:0040F06A                 LDR             R2, [R4,#0x3C] // r2 = this->m_Component.size()
.text:0040F06C                 LDR             R3, [R4,#0x50] // r3 = this->m_SupportedMessages
.text:0040F06E                 ORRS            R0, R3 // ret |= this->m_SupportedMessages
.text:0040F070                 STR             R0, [R4,#0x50] // this->m_SupportedMessages = ret
.text:0040F072
.text:0040F072 loc_40F072                              ; CODE XREF: GameObject::GetSupportedMessagesRecalculate(void)+14↑j
.text:0040F072                 ADD.W           R0, R1, R2,LSL#3 // r0 = r1 + r2 << 3 = end = begin + size * 8
.text:0040F076                 ADDS            R5, #8 // ++i
.text:0040F078                 CMP             R5, R0
.text:0040F07A                 BNE             loc_40F05E
.text:0040F07C
.text:0040F07C locret_40F07C                           ; CODE XREF: GameObject::GetSupportedMessagesRecalculate(void)+E↑j
.text:0040F07C                 POP             {R4,R5,R7,PC}
.text:0040F07C ; } // starts at 40F04C

主要指令说明:

指令名字英文解释描述
LDRload memory data into register.把内存数据加载到寄存器中
STRstore register into memory.把寄存器的数据,写入到内存中
CMPcompare比较两个操作数,将结果写到状态寄存器的标记位中
Bbranch(jump)跳转到目标地址
BEQbranch(jump) if equal.如果状态寄存器的比较标志位的值是0,则跳转
BNEbranch(jump) if not equa.与BEQ相反
CBZcompare branch(jump) if zero.如果寄存器的值为零,则跳转。不修改状态寄存器。
BLbranch with link用于函数调用的跳转
BLXBranch with Link and exchange instruction set用于函数调用的跳转,并且切换指令集

分析

崩溃位置是对迭代器解引用(component = i->GetComponentPtr())的时候发生的,根据寄存器r5的值来看,此时i为NULL。有下面两种情况,会导致i为NULL:

  1. 假设m_Component.begin()为空,则迭代器i会是空。此时m_Component.size()也应该是0,则for循环压根就不会进入。说明假设不成立;
  2. 假设m_Component.begin()不为空,则迭代器i不会是空,i只有++操作,不可能变成空。

也就是说,i无论如何都不可能是空值。那就说名有可能出现了内存错误:

  1. 当前的GameObject已经被销毁了!此时this指针就是非法地址,理论上说,
    执行this->m_SupportedMessages = 0这一步时就会出现崩溃。当然,崩溃信息也不一定完全准确,而且两行条指令相邻,极有可能发生。
  2. 多线程问题。指令0040F04E和0040F052之间被多线程操作打断,别的地方销毁了m_Components。
    中间就隔了一条之类,这种情况理论上概率极低。

分析到此为止,陷入了僵局,无法继续推进。只能猜测是某个资源损坏了,但是一直没发定位到是哪个资源。在网上搜索了下,也没有太多案例可以参考。

阶段二

很长一段时间后,就想着用编辑来模拟一下bundle的运行情况,看看能不能获得更详细的报错信息。经过若干次测试,终于在某个特定的情况下切换场景,碰到了大量的错误日志。并且编辑器停止游戏运行的时候,编辑器发生了闪退。

编辑器闪退堆栈:

========== OUTPUTTING STACK TRACE ==================0x00007FF7A53FE8A4 (Unity) GameObject::GetComponentIndex
0x00007FF7A5C8804E (Unity) CanReplaceComponent
0x00007FF7A5C87B50 (Unity) CanDestroyObject
0x00007FF7A5C8ADDF (Unity) DestroyObjectHighLevel
0x00007FF7A5CA08D3 (Unity) DestroyWorldObjects
0x00007FF7A45992ED (Unity) EditorSceneManager::RestoreSceneBackups
0x00007FF7A3FEE82E (Unity) PlayerLoopController::ExitPlayMode
0x00007FF7A4000CCF (Unity) PlayerLoopController::SetIsPlaying
0x00007FF7A40039A2 (Unity) Application::TickTimer
0x00007FF7A49874E5 (Unity) MainMessageLoop
0x00007FF7A49916C8 (Unity) WinMain
0x00007FF7A7A06962 (Unity) __scrt_common_main_seh
0x00007FFB875F7034 (KERNEL32) BaseThreadInitThunk
0x00007FFB88642651 (ntdll) RtlUserThreadStart========== END OF STACKTRACE ===========

编辑器的闪退堆栈没有太大价值,因为是在停止播放时发生的,而不是在出错位置。但是从堆栈上可以猜测出是某个GameObject或Component发生了野指针,导致销毁的时候引起了闪退。

编辑器使用bundle模式运行,收集到的错误日志:

Component at index 0 could not be loaded when loading game object 'Bip001'. Removing it! 
(Filename: C:\buildslave\unity\build\Runtime/BaseClasses/GameObject.cpp Line: 811)Transform component could not be found on game object. Adding one! 
(Filename: C:\buildslave\unity\build\Runtime/BaseClasses/GameObject.cpp Line: 741)Prefab has multiple Transform components! Removing them automatically would not be safe. 
(Filename: C:\buildslave\unity\build\Runtime/BaseClasses/GameObject.cpp Line: 890)CheckConsistency: GameObject does not reference component Transform. Fixing. 
(Filename: C:\buildslave\unity\build\Runtime/BaseClasses/GameObject.cpp Line: 1394)

而错误日志也是让人很困惑,没有指明是哪个资源出了问题。即便我把含有’Bip001’的所有结点全部删掉,又会出现另外一些结点出错。在网上查了一下,有相似的问题,都是资源损坏引起的:

  • prefab在版本合并时,出现了合并混乱,导致prefab格式被破坏;
  • 资源是旧版Unity生成的,升级Unity后资源格式需要升级,或者bundle需要重新生成;
  • prefab中含有丢失的内嵌预设(Missing Prefab);
  • 资源中含有丢失的脚本(Missing Script);
  • CacheServer中资源发生了损坏;
  • Library缓存目录中的资源发生了损坏。

用脚本扫描了所有的资源,确实出现很多损坏问题。把资源问题逐一修复后,删除了所有缓存,重新打bundle,结果还是一样,失望ing。

不过,至此可以排除是资源损坏的问题。回到出问题的地方,刚好是切换场景,那最有可能的就是某个资源正在异步加载或对象在创建的过程中,被切换场景给销毁了。Unity创建对象的接口只有Instantiate,而且实例化对象是同步的。那就只可能资源在异步加载的过程中,bundle被Unload引起了异常。查了下资源加载器代码,果然在异步加载资源的时候,没有对bundle增加引用计数,导致切换场景的时候被释放掉了。至于Unity为何没有拦截掉这种错误的用法,就不得而知了。

清除Missing Script

GameObjectUtility.RemoveMonoBehavioursWithMissingScript(GameObject go);

查找内嵌的Missing Prefab

static void FindMissingPrefab(GameObject go, string name, bool isRoot, bool recursive = true)
{if (go.name.Contains("Missing Prefab")){Debug.LogError($"1. {name} has missing prefab {go.name}", go);return;}if (PrefabUtility.IsPrefabAssetMissing(go)){Debug.LogError($"2. {name} has missing prefab {go.name}", go);return;}if (PrefabUtility.IsDisconnectedFromPrefabAsset(go)){Debug.LogError($"3. {name} has missing prefab {go.name}", go);return;}if (!isRoot){if (PrefabUtility.IsAnyPrefabInstanceRoot(go)){return;}GameObject prefabRoot = PrefabUtility.GetNearestPrefabInstanceRoot(go);if (prefabRoot == go){return;}}if (recursive){name = name + "/" + go.name;foreach (Transform child in go.transform){FindMissingPrefab(child.gameObject, name, false, recursive);}}
}

总结

卸载正在异步加载资源的AssetBundle,会导致Unity引擎内部出现指针错误,引发一些奇怪的闪退问题。
经过此次闪退分析,基本上可以确定,堆栈含有MonoBehaviour::AwakeFromLoad(AwakeFromLoadMode),都是资源损坏引起的。可能是资源真的有问题,或AssetBundle损坏了,或资源正在加载过程中AssetBundle被释放了。


http://www.ppmy.cn/news/241521.html

相关文章

Android 购物车实现(思路+步骤+源码)

购物车 Android 购物车 购物车前言正文一、准备数据源二、绘制界面布局三、配置项目四、渲染数据五、功能实现① 商品、店铺选中② 单选、多选、全选③ 价格控制④ 编辑商品⑤ 细节优化 六、源码 总结 前言 购物车作为电商APP来说&#xff0c;是必备的。并且很多公司面试初级An…

cocos creator个人笔记-切换场景闪退(龙骨问题)

项目场景&#xff1a; creator 2.2.2 场景加载报错&#xff1a; 安卓原生打包后 测试切换关卡场景时候 有的关卡卡死有的没问题 其他端无法重现 问题描述&#xff1a; 这里是引用2021-01-11 15:10:25.496 2335-2335/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** …

Android兼容之libc++_shared.so库冲突方案

提供解决libc_shared.so库冲突方案 背景 随着App功能增多&#xff0c;集成的so库也会增多&#xff0c;如果系统中多个so库都使用系统自动生成的libc_shared.so库&#xff0c;如果不兼容时&#xff0c;会导致某个服务调用时crash 说明 当存在不兼容问题时&#xff0c;app会在…

springboot项目外卖管理 day02-新增员工以及员工信息编辑

文章目录 一、新增员工1.1、需求分析1.2、数据模型1.3、代码开发 二、员工信息分页查询2.1、需求分析 2.2、代码开发2.3、分页插件2.4、员工信息分页查询 三、启用/禁用员工账号3.1、需求分析3.2、代码开发3.3、根据id修改员工信息3.4、代码修复 4、编辑员工信息4.1、功能需求4…

第十四届蓝桥杯大赛软件赛省赛(C/C++ 研究生组)

蓝桥杯 2023年省赛真题 C/C 大学G组 试题 A: 工作时长  试题 B: 与或异或  试题 C: 翻转  试题 D: 阶乘的和  试题 E: 公因数匹配  试题 F: 奇怪的数  试题 G: 太阳  试题 H: 子树的大小  试题 I: 高塔  试题 J: 反异或 01 串 除去第 F \rm F F 题&#xff0c;其他题…

Java教程【01.02】Java引用类型数组和继承的意义

Java引用类型数组和继承的意义 Java引用类型数组和继承是Java中常用的两个概念,它们在编程中起到重要的作用。在本教程中,我们将讨论Java引用类型数组的使用以及继承的意义,并提供相关的示例。 步骤1:创建引用类型数组 Java中的引用类型数组允许我们在单个变量中存储多个…

RSSI 异常分类

2.2.1 工程质量不好导致 RSSI 异常 指工程质量不好引起的 RSSI 异常&#xff0c;比如接头制作不符合规范和接头松动等原因导致主 集或分集产生自激、天线进水、设备老化等都会引起 RSSI 值异常。 由自激产生的 RSSI 异常通常表现为主集或分集过大、主分集差值过大。在已发…

win10 高内存、CPU占用(接近百分之百)

问题描述&#xff1a; 1. 电脑内存8g&#xff0c;CPU为 i5 6300HQ 2. 有时用着用着电脑的内存cpu就占用的90%以上。打开任务管理器&#xff0c;有个system进程经常CPU占用约30% 3. 开机有时占用会降下来&#xff0c;有时有cpu、内存一直接近100%的占用&#xff0c;而且不会随着…