.NET MAUIのiOSアプリがTestFlightで落ちる:クラッシュログがシンボリケートされない原因とdSYMでの解決手順(Visual Studio/Xcode)

TestFlightに配布したiOSアプリが「起動直後にクラッシュ」しているのに、App Store Connectから取得したクラッシュログがメモリアドレスばかりで読めない――この症状の多くは、クラッシュしたビルドと一致するdSYMがXcode側で見つからない(または一致していない)ことが原因です。.NET MAUI / Visual Studio(Windows)でも、押さえるポイントは同じです。

目次

この問題の正体:クラッシュログが「アドレスのまま」になる条件

TestFlight(App Store Connect)から落とした crashlog.crash を Xcode の Devices and Simulators などで開いたのに、スタックトレースが 0x0000000100xxxxxx のようなアドレス表示のまま――これは「Xcodeがシンボル情報(dSYM)を当てられていない」状態です。

よくある誤解として、次の2つが混ざりがちです。

  • クラッシュ原因の解析(なぜ落ちたのかを直す)
  • シンボリケート(シンボル化)(アドレスを関数名に戻して「読める状態」にする)

まずは後者を成功させないと、前者の調査効率が極端に落ちます。

用語意味今回の論点
dSYM(MyAppName.app.dSYM)デバッグシンボル(関数名・行情報など)を保持するファイル群クラッシュしたビルドと一致するdSYMが必須
シンボリケートアドレスを関数名(可能なら行番号)に解決することXcodeがdSYMを見つけられると自動で進むことが多い
UUID(Build UUID)ビルドごとに付く識別子クラッシュログのUUIDとdSYMのUUIDが一致しないと失敗
.xcarchiveXcodeのアーカイブ(ビルド成果物の箱)dSYMsフォルダからdSYMを取り出せる

結論:シンボリケートはXcode側で行い、MAUI側は「dSYMが残る設定」と「一致する成果物の保管」が要

質問で挙がっている「WindowsのVisual Studioで何か必要?」「MacのXcode側?」「dSYMはどう使う?」に対する答えを先にまとめると、実務ではこう考えるのが最短です。

やる場所やること目的
Visual Studio / .NET MAUI(ビルド側)dSYMが生成され、不要に消えない設定にする/アーカイブを作る「該当ビルドと一致するdSYM」を確実に残す
Xcode(解析側)クラッシュログにdSYMを当ててシンボリケートするスタックトレースを関数名に戻して読めるようにする

最初に確認:クラッシュログの「Version / Build」と、手元の成果物が一致しているか

シンボリケートができない原因で一番多いのは、設定の問題よりもシンプルに「別ビルドのdSYMを当てようとしている」ケースです。TestFlightの運用では、同日に何回もアップロードしていることが珍しくないため、特に起こりがちです。

  • App Store Connect(TestFlight)側で、該当クラッシュのビルド番号(例:1.2.3 (45))を確認する
  • クラッシュログ(.crash)でも、ヘッダ付近や末尾のBinary Imagesセクションで対象バイナリを確認する
  • 手元の MyAppName.app.dSYM が、そのビルドのものかを後述のUUIDで確認する

.NET MAUI側でやること:シンボルが落ちない(調査に必要な情報が残る)ビルドに寄せる

NoSymbolStrip を設定して、ビルド時のシンボル削除を抑止する

.NET(iOS)では、MSBuildプロパティ NoSymbolStrip で「ビルド時にデバッグシンボルを削らない」指定ができます。クラッシュ調査の初動を速くしたい場合、TestFlight向け(少なくとも社内/検証用)では有効にしておくと、後から困りにくいです。

プロジェクト全体に影響させたくない場合は、iOSのReleaseだけに限定するのが安全です。

<PropertyGroup Condition="'$(TargetFramework)' == 'net8.0-ios' And '$(Configuration)' == 'Release'">
  <NoSymbolStrip>true</NoSymbolStrip>
</PropertyGroup>

マルチターゲット(net8.0-android など)構成で条件を書きにくいときは、TargetFrameworkに -ios が含まれるかで分岐しても構いません。

<PropertyGroup Condition="$([System.String]::Copy('$(TargetFramework)').Contains('-ios')) And '$(Configuration)' == 'Release'">
  <NoSymbolStrip>true</NoSymbolStrip>
</PropertyGroup>

注意点として、NoSymbolStrip=true はアプリサイズ増の要因になります。最終的に本番配信でサイズを最適化したい場合は、「本番はstripするが、dSYMは必ず保管する」運用に切り替えるのが一般的です(後述)。

「dSYMが生成されない」系の設定も念のため把握しておく

シンボリケートに必要なのは基本的にdSYMなので、「そもそもdSYM生成が無効になっていないか」も確認ポイントです。.NET(iOS)には NoDSymUtil というプロパティがあり、これが有効だとdSYM生成が抑止されます。デフォルト挙動も条件によって変わるため、特殊なビルド設定を入れている場合は要注意です。

ArchiveOnBuild=true を使って「.xcarchive」を確実に残す

調査で一番強いのは、該当ビルドの .xcarchive を残すことです。なぜなら、アーカイブの中に dSYMs がまとまっており、後から「このビルドのdSYMどれ?」で迷いにくいからです。

Microsoftの手順でも、iOS配布用の dotnet publish で -p:ArchiveOnBuild=true を指定する例が紹介されています。Mac上で実行する場合だけでなく、Windowsからリモートビルド(Pair to Mac相当)する場合も同様のパラメータを渡せます。

dotnet publish -f net8.0-ios -c Release -p:ArchiveOnBuild=true -p:RuntimeIdentifier=ios-arm64

Visual StudioのGUI操作でIPAを作っている場合でも、裏側ではMac側にアーカイブ相当が残ることがあります。大事なのは「IPAだけを成果物として扱わず、アーカイブ(またはdSYM一式)をビルド番号付きで保管する」運用にすることです。

成果物用途紛失すると困る度おすすめの保管単位
.ipa配布(TestFlight/AdHocなど)中ビルド番号つきで保管(再配布用)
.xcarchive調査(dSYMの供給源)最重要ビルドごとに丸ごと保管(推奨)
MyAppName.app.dSYMシンボリケート最重要最低限これだけはZIPで保管

dSYMの入手:XcodeのArchive(.xcarchive)から取り出す手順

すでに MyAppName.app.dSYM を入手できている場合でも、今後のために「どこにあるのか」を押さえておくと安心です。

  1. MacでXcodeを起動
  2. Window → Organizer を開く
  3. Archives を選択し、該当アプリの該当ビルド(Version/Build)を選ぶ
  4. Show in Finder(Finderで表示) などでアーカイブの場所へ移動
  5. xxxx.xcarchive を右クリック → パッケージの内容を表示
  6. dSYMs 配下に MyAppName.app.dSYM がある

重要なのは、クラッシュを起こしたビルドと同一のアーカイブ由来のdSYMを使うことです。ビルドが1つ違うだけで、シンボリケートは失敗します。

シンボリケートの前にやるべき最重要チェック:UUIDが一致しているか

「Xcodeで開いてもアドレスのまま」のとき、経験上ほぼこれです。

  • クラッシュログが要求しているUUID
  • dSYMが持っているUUID

この2つが一致しない限り、どれだけ手順をやっても“それっぽく”シンボル化されません。

dSYM側のUUIDを確認する(dwarfdump)

Macのターミナルで、dSYMのUUIDを確認します。Crashlyticsのドキュメントでも、dSYMのUUID確認に dwarfdump を使う例が案内されています。

dwarfdump --uuid MyAppName.app.dSYM

出力例(イメージ):

UUID: 01234567-89AB-CDEF-0123-456789ABCDEF (arm64) MyAppName.app.dSYM

クラッシュログ側のUUIDを確認する(Binary Images)

.crashファイルの末尾付近にある Binary Images セクションで、MyAppName(またはBundle名)に該当する行を探します。そこにUUIDが載っています(表示形式はログの種類やXcodeのバージョンで多少異なります)。

見るものどこを見る一致させるもの
クラッシュログBinary Imagesの MyAppName 行UUID
dSYMdwarfdump --uuid の出力UUID

一致していれば、Xcodeが正しく参照できる状態に置くことでシンボリケートできる可能性が一気に上がります。逆に一致していないなら、今持っているdSYMはそのクラッシュに使えません。この場合は「該当ビルドのアーカイブから取り直す」「App Store Connect/Xcodeから該当ビルドのdSYMを入手する」へ進みます。

シンボリケートの実行:Xcodeで自動、ダメなら手動

方法A:Xcode Organizerの「Crashes」で見る(自動シンボリケート)

XcodeはOrganizer経由でクラッシュを閲覧すると、自動でシンボリケートされることが多いです。特に「該当ビルドのアーカイブがMac内に残っている」場合、ここが最短ルートになりやすいです。

  1. Xcode → Window → Organizer
  2. Crashes(またはそれに相当するクラッシュ表示)を開く
  3. 対象アプリを選択
  4. 対象のクラッシュを開き、関数名が出るか確認

ここでシンボル化されない場合は、ほぼ「一致するdSYMが見つからない」か「見つけられる場所にない」です。

方法B:クラッシュログ(.crash)をXcodeで開き、dSYMを“見つけられる場所”に置く

クラッシュログをXcodeで開いたとき、Xcodeが参照できる場所にdSYMが存在し、かつUUIDが一致していれば、勝手にメソッド名が出ることがあります。ポイントは「Xcodeが普段見る場所(Archiveや既知のdSYMキャッシュ)に置く」です。

  • 該当ビルドの .xcarchive を Xcode Organizer の Archives に残す(最優先)
  • 取り出した MyAppName.app.dSYM を、アーカイブの dSYMs 配下に戻しておく(構造を崩さない)

最近のXcodeでは「dSYMが足りない」ことを警告として出すこともあり、逆に言えば“足りていない”ことが判定しやすくなっています。

方法C:ターミナルで手動シンボリケート(最後の手段として覚えておく)

自動でうまくいかないときでも、UUIDが合っているなら手動で前に進めます。代表的なのは symbolicatecrash を使うやり方です(Xcodeの内部に含まれるスクリプトを利用します)。

環境によってパスが変わるため、まずは見つけます。

xcrun --find symbolicatecrash

見つかったパスを使って実行します(例)。

export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
SYMBOLICATE=$(xcrun --find symbolicatecrash)

# 例:同じフォルダに crashlog.crash と MyAppName.app.dSYM を置いて実行
"$SYMBOLICATE" crashlog.crash > symbolicated.crash

この方法は、Xcodeのバージョン差や環境変数で躓きやすいので、まずは「Archives/Crashesで自動」を優先し、どうしても必要なときだけ使うのがおすすめです。

App Store ConnectからdSYMを取るべきケースと、取れないときの代替

運用次第では「ローカルのアーカイブが残っていない」「ビルドマシンが入れ替わって古いdSYMが消えた」ということが起こります。その場合、App Store ConnectやXcode経由でdSYMを取得できるかが鍵になります。

状況第一候補ダメなとき
ビルドマシンに該当アーカイブが残っているXcode Organizer → Archives → .xcarchive から取り出すアーカイブ保管運用に切り替える
ローカルに何も残っていないApp Store Connectのビルド詳細からdSYMをダウンロード(表示される場合)Xcode Organizerの「Download dSYMs」相当の導線を試す
dSYMダウンロード導線が見当たらない(環境/時期/ビルド形式によって見え方が変わる)ローカルにアーカイブを残す運用が結局確実

App Store Connectで「Build Metadata → Download dSYM」のような導線が案内されている例もありますが、時期やBitcode事情などで画面や提供範囲が変わることがあります。見えない場合は、Xcode Organizer側で取得できるかを当たる、または最初からアーカイブを保管する運用に寄せるのが現実的です。

「MoveNext」「AsyncMethodBuilderCore_Start」など見慣れない名前が出ても慌てない

シンボリケートが成功すると、...MoveNext のような見慣れないメソッド名が出ることがあります。これは多くの場合、C#の async/await がコンパイル時に生成する「状態マシン(state machine)」由来のメソッドで、プロジェクト内で MoveNext を検索しても出てこないのは正常です。

ここでやるべきことは「名前に惑わされる」よりも、次の観点でログを読むことです。

  • クラッシュしたスレッド(Crashed Thread)はどれか
  • 自分のアセンブリ(あなたの名前空間)に入っているフレームはどこか
  • クラッシュ直前に呼ばれているOS API / フレームワークは何か

よくある詰まりポイント:症状→原因→対処(チェック表)

症状よくある原因対処
ずっとアドレス表示のままdSYMが別ビルド(UUID不一致)dwarfdump --uuid でUUIDを突き合わせ、該当ビルドのアーカイブから取り直す
一部だけシンボル化され、他は「???」アプリ本体はOKだが、組み込みフレームワークのdSYMが不足アーカイブ内の dSYMs を丸ごと扱う/依存関係のdSYMも揃える
Xcode Organizerでクラッシュは見えるが詳細が薄い対応するアーカイブがMacにない/dSYM未取得該当ビルドの .xcarchive を復元するか、dSYMを取得してXcodeが参照できる場所に置く
Visual StudioでIPAはあるが、dSYMが見つからない成果物としてIPAしか保管していない今後は .xcarchive(またはdSYM一式)をビルド番号付きで保管する
TestFlightでだけ落ちる(DebugはOK)Release特有のリンク/最適化、権限、構成差まずシンボリケートして原因箇所を特定。別問題として切り分けて調査する

運用の話:dSYMを「探す」のではなく「必ず残す」に変える

シンボリケート問題は、技術的にはUUID一致の話ですが、実務での勝敗は運用で決まります。おすすめは「ビルドごとにdSYM(できればxcarchive)を必ず保管する」ことです。

おすすめ運用やることメリット
ビルドごとにxcarchive保管MyApp_1.2.3(45).xcarchive を丸ごと保存dSYMだけでなく依存フレームワークのdSYMも揃いやすい
dSYM ZIP保管dSYMs をZIP化して保存容量を抑えつつ最重要情報を保持できる
命名規則を固定「アプリ名_バージョン(ビルド番号)」で統一後から照合が速い(人間のミスを減らす)

例えば、アーカイブからdSYMsだけ取り出してZIP化するなら次のような形です。

cd /path/to/YourApp.xcarchive
zip -r YourApp_1.2.3_45_dSYMs.zip dSYMs

シンボリケート後の「読み方」だけは押さえる(起動直後クラッシュ編)

シンボリケートに成功しても、クラッシュログは情報量が多く、見る場所を間違えると迷子になります。起動直後クラッシュでは、まずここだけ確認すると効率が上がります。

見る項目よく書かれている内容読み取りのコツ
Exception Type / Exception CodesEXC_BAD_ACCESS、SIGABRT などメモリアクセス違反か、abort系かで調査の方向が変わる
Termination Reason / Termination SignalOS側で殺された理由が出ることもWatchdog(起動時間)や権限/署名系の可能性も疑う
Crashed Thread落ちたスレッド番号まずそのスレッドのバックトレースを最優先で読む
Backtrace(スタックトレース)関数が並ぶ自分の名前空間に入った瞬間を起点に、前後のOS呼び出しも見る
Binary ImagesロードされたバイナリとUUIDシンボリケート不調時はここが“正解合わせ”の場所

なお、シンボリケート後に「push通知許可ダイアログの直後に落ちる」など、原因の輪郭が見えたら、それはもうシンボル化の問題ではなく別の不具合解析です。ログから得た手掛かりを元に、再現条件(端末種別・iOSバージョン・初回起動か・権限状態など)を整理して切り分けるのが定石です。

まとめ:迷ったら「UUID一致」「アーカイブ保管」「Xcode側でシンボリケート」の順に戻る

TestFlightのクラッシュログがシンボリケートされないときは、技術的には複雑に見えても、実際は次の3点に収束します。

  • dSYMはクラッシュしたビルドと一致しているか(UUID一致)
  • そのビルドのdSYM/xcarchiveを手元に残しているか
  • シンボリケート作業はXcode側の流儀で進めているか

この土台が整うと、スタックトレースが一気に読めるようになり、起動直後クラッシュの原因特定までの時間が大幅に短縮できます。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次