【UE5】Android環境でハングアップしたときの調査方法
今回はUnreal Engineでビルドした APK ファイルがAndroid環境でクラッシュしたときの調査方法について書きます。
ログの回収方法
クラッシュの原因調査方法として、まずは Unreal Engineが出力しているログを回収します。
基本的には以下のコマンドを使って ADB 経由でログをコピーします。
adb pull /sdcard/Android/data/<pkg>/files/UnrealGame/<Project>/Saved/Logs .ログのパスを調べる方法
ログのパスである <pkg> (パッケージ) と、<Project> (プロジェクト名) は adb shell コマンドで調べると良さそうです。
例えば「adb shell ls sdcard/Android/data」でインストールされているパッケージ名が取得できます

プロジェクト設定に記載されているものと同じなので、わざわざコマンドで調べる必要はないかもしれませんが、この一覧から探すこともできます。
次にプロジェクト名を確認する必要があるので「adb shell ls sdcard/Android/[パッケージ名]/files/UnrealGame」を実行。
これで目的のログファイルにたどり着ければそのパスを使ってログを取り出せます。(場合によって [プロジェクト名]/[プロジェクト名] )というパスが作られることもあるので、直接ストレージを確認すると確実です)
バグレポートの取得
次にバグレポートの取得方法についてです。Androidでクラッシュが発生すると「bugreport.zip」というファイルでバグ情報が取り出せます。
こちらはログ取得よりも簡単で、以下のコマンドを実行するだけです。
adb bugreport bugreport.zipこれを実行すると、現在のフォルダに "bugreport.zip" がコピーされます。

クラッシュの原因とコールスタックを確認する
クラッシュ情報は「bugreport/FS/data/tombstones」というパスにファイルがいくつか配置されています。

ここには、これまでに発生したクラッシュ情報すべてを保持しています。そのため基本的には最新のファイルを見ることになると思います。
最新の「tombstone_##」の拡張子がないファイルをテキストエディタで開きます。

直接的に重要なのは「Cause」という部分です。
Cause: null pointer dereference
x0 000000741e6abe30 x1 0000000000000000 x2 0000000000000001 x3 0000000000000004
x4 0000007409ad6c00 x5 0000000000000002 x6 0000000000000000 x7 000000741e4c55c0
x8 0000000000000001 x9 0000000040040008 x10 b4000075660245c8 x11 0000000000000000
x12 000000002f46b850 x13 000000000000007f x14 0000000000000000 x15 00000000000083d5
x16 00000074ec899bc0 x17 000000761b32dd00 x18 00000000fffffe80 x19 000000741e6abe30
x20 00000074f2285d00 x21 00000074f2285d00 x22 00000074f2285d00 x23 0000000000000030
x24 0000000000000050 x25 00000074ecc89bd8 x26 0000000000000100 x27 000000742f499160
x28 0000000000001400 x29 00000074f227f500
lr 00000074e89a6c40 sp 00000074f227f470 pc 00000074e89a72c0 pst 0000000000001000
例えば上記ログであれば、"null pointer dereference" となっているので、NULL参照がエラーの原因となります。
もう1つ重要なのがコールスタックです。
22 total frames
backtrace:
#00 pc 00000000140752c0 ==/lib/arm64/libUnreal.so (ACPP_GrabActorBase::UpdateRotation(UMeshComponent*, float)+40) (BuildId: 47ad9d8b85d0a9586137f91ee040952f1e6277d1)
#01 pc 0000000014074c3c ==/lib/arm64/libUnreal.so (ACPP_GrabActorBase::UpdateTimerTick()+104) (BuildId: 47ad9d8b85d0a9586137f91ee040952f1e6277d1)
#02 pc 0000000014078ac4 ==/lib/arm64/libUnreal.so (TBaseUObjectMethodDelegateInstance<false, ACPP_GrabActorBase, void (), FDefaultDelegateUserPolicy>::ExecuteIfSafe() const+120) (BuildId: 47ad9d8b85d0a9586137f91ee040952f1e6277d1)
#03 pc 0000000014046c58 ==/lib/arm64/libUnreal.so (ACPP_GameTimer::_ExecuteTimerEvent(FTimerEvent&)+220) (BuildId: 47ad9d8b85d0a9586137f91ee040952f1e6277d1)
#04 pc 000000001404591c ==/lib/arm64/libUnreal.so (ACPP_GameTimer::_ProcessTimerEvents(float)+288) (BuildId: 47ad9d8b85d0a9586137f91ee040952f1e6277d1)
#05 pc 0000000010bafa7c ==/lib/arm64/libUnreal.so (FActorTickFunction::ExecuteTick(float, ELevelTick, ENamedThreads::Type, TRefCountPtr<FBaseGraphTask> const&)+148) (BuildId: 47ad9d8b85d0a9586137f91ee040952f1e6277d1)
#06 pc 0000000012225cb4 ==/lib/arm64/libUnreal.so (FTickFunctionTask::DoTask(ENamedThreads::Type, TRefCountPtr<FBaseGraphTask> const&)+288) (BuildId: 47ad9d8b85d0a9586137f91ee040952f1e6277d1)
#07 pc 0000000012225b40 ==/lib/arm64/libUnreal.so (TGraphTask<FTickFunctionTask>::ExecuteTask()+60) (BuildId: 47ad9d8b85d0a9586137f91ee040952f1e6277d1)
#08 pc 000000000b0f1e18 ==/lib/arm64/libUnreal.so (UE::Tasks::Private::FTaskBase::TryExecuteTask()+348) (BuildId: 47ad9d8b85d0a9586137f91ee040952f1e6277d1)
#09 pc 000000000b0fc1a4 ==/lib/arm64/libUnreal.so (FNamedTaskThread::ProcessTasksNamedThread(int, bool)+1988) (BuildId: 47ad9d8b85d0a9586137f91ee040952f1e6277d1)
#10 pc 000000000b0fb078 ==/lib/arm64/libUnreal.so (FNamedTaskThread::ProcessTasksUntilQuit(int)+244) (BuildId: 47ad9d8b85d0a9586137f91ee040952f1e6277d1)
#11 pc 000000000b0fa8ec ==/lib/arm64/libUnreal.so (FTaskGraphCompatibilityImplementation::WaitUntilTasksComplete(TArray<TRefCountPtr<FBaseGraphTask>, TSizedInlineAllocator<4u, 32, TSizedDefaultAllocator<32> > > const&, ENamedThreads::Type)+1868) (BuildId: 47ad9d8b85d0a9586137f91ee040952f1e6277d1)
#12 pc 0000000012221330 ==/lib/arm64/libUnreal.so (FTickTaskSequencer::ReleaseTickGroup(ETickingGroup, bool)+2064) (BuildId: 47ad9d8b85d0a9586137f91ee040952f1e6277d1)
#13 pc 000000001221ce98 ==/lib/arm64/libUnreal.so (FTickTaskManager::RunTickGroup(ETickingGroup, bool)+176) (BuildId: 47ad9d8b85d0a9586137f91ee040952f1e6277d1)
#14 pc 000000001171dee8 ==/lib/arm64/libUnreal.so (UWorld::Tick(ELevelTick, float)+4784) (BuildId: 47ad9d8b85d0a9586137f91ee040952f1e6277d1)
#15 pc 0000000011424a7c ==/lib/arm64/libUnreal.so (UGameEngine::Tick(float, bool)+1692) (BuildId: 47ad9d8b85d0a9586137f91ee040952f1e6277d1)
#16 pc 000000001293994c ==/lib/arm64/libUnreal.so (FEngineLoop::Tick()+12468) (BuildId: 47ad9d8b85d0a9586137f91ee040952f1e6277d1)
#17 pc 00000000129351b0 ==/lib/arm64/libUnreal.so (AndroidMain(android_app*)+4272) (BuildId: 47ad9d8b85d0a9586137f91ee040952f1e6277d1)
#18 pc 000000001293d994 ==/lib/arm64/libUnreal.so (android_main+200) (BuildId: 47ad9d8b85d0a9586137f91ee040952f1e6277d1)
#19 pc 000000001295e38c ==/lib/arm64/libUnreal.so (BuildId: 47ad9d8b85d0a9586137f91ee040952f1e6277d1)
#20 pc 00000000000fd0ec /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+204) (BuildId: dc4c39cee8280a6712ff2d9e4968d431)
#21 pc 0000000000094fb0 /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64) (BuildId: dc4c39cee8280a6712ff2d9e4968d431)上記ログであれば「ACPP_GrabActorBase::UpdateRotation」で何らかの NULL参照が発生したこととなります。
ただ、このバグレポートにはシンボル情報がないためわかるのは呼び出し関数名のみです。
シンボルと tombstone を連携して場所を特定する方法
Androidビルド時には、通常 "Binaries/Android/<Project>_Symbols_v1/<Project>arm64" に "libUnreal.so" というシンボルファイルが作成されます。
"libUnreal.so" ファイルが見つかれば以下のコマンドでクラッシュダンプを書き出せます。
:: NDK内の ndk-stack.cmd
set TOOL=%NDK_ROOT%\ndk-stack.cmd
:: libUnreal.soがあるフォルダ
set SYM_DIR=[プロジェクトのパス]\Binaries\Android\<Project>_Symbols_v1\<Project>arm64
:: bugreport内のtombstoneファイル。拡張子がない tombstone_## を指定.
set TOMBSTONE=bugreport\FS\data\tombstones\tombstone_##
:: dump.txtにクラッシュダンプを書き出し.
%TOOL% -sym %SYM_DIR% -dump %TOMBSTONE% >> dump.txtおしまい
Androidでのクラッシュが発生したときの調査方法でした。
今回はログ取得と例外の種類、コールスタックを調べる方法のみですが、何かもっと良い方法が見つかったら別途記事を書こうと思っています。
以上、Unreal EngineでのAndroidのデバッグに役立てれば幸いです。

