日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發布時間:2024/8/1 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Unity资源加载闪退问题深度分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

游戲線上測試總是有一些很奇怪的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:

  • 假設m_Component.begin()為空,則迭代器i會是空。此時m_Component.size()也應該是0,則for循環壓根就不會進入。說明假設不成立;
  • 假設m_Component.begin()不為空,則迭代器i不會是空,i只有++操作,不可能變成空。
  • 也就是說,i無論如何都不可能是空值。那就說名有可能出現了內存錯誤:

  • 當前的GameObject已經被銷毀了!此時this指針就是非法地址,理論上說,
    執行this->m_SupportedMessages = 0這一步時就會出現崩潰。當然,崩潰信息也不一定完全準確,而且兩行條指令相鄰,極有可能發生。
  • 多線程問題。指令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被釋放了。

    總結

    以上是生活随笔為你收集整理的Unity资源加载闪退问题深度分析的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。