mcpField
S
symbolize-android-stacktrace

symbolize-android-stacktrace

github.com/chunkytofustudios開発542 回インストール26 週間前に更新
リポジトリ

symbolize-android-stacktrace について

Flutter の Android アプリで、Google Play Console のクラッシュや ANR の難読化されたスタックトレースを、対応する Codemagic のビルド成果物でシンボル化するスキルです。ネイティブと Flutter のシンボルをアップロードするリリース工程向けで、R8 マッピングによる Java フレームの復号にも対応します。

主な機能

  1. 01ネイティブと Flutter のフレームをシンボル化
  2. 02Codemagic からシンボル成果物を取得
  3. 03R8 と ProGuard のマッピングを解決
  4. 04未解決フレーム数を報告

活用シーン

  1. 01Google Play のクラッシュトレースを調査
  2. 02Flutter の ANR フレームをソース行に特定
  3. 03mapping.txt で Java フレームを復号

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/chunkytofustudios/flutter-skills --skill symbolize-android-stacktrace

インストール後、Claude Code で/symbolize-android-stacktraceを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

scripts/
codemagic_fetch_artifacts.py
deobfuscate_r8.py
symbolize_flutter_anr.sh
tests/
classification.bats
help.bats
helpers.bash
metachars.bats
symbolize.bats
test_codemagic_fetch.py
test_deobfuscate_r8.py
tests/fixtures/
mapping.txt
not-a-trace.txt
tests/fixtures/flutter/
app.android-arm64.symbols
tests/stubs/ndk/toolchains/llvm/prebuilt/bats-host/bin/
llvm-addr2line
llvm-readelf

詳細

Android のスタックトレースをシンボル化(Flutter + Codemagic)

難読化された Google Play Console のクラッシュ/ANR スタックトレースを、シンボル化されたトレース(すべてのネイティブフレームと Flutter フレームのファイル名と行番号)に変換し、バグの原因を調査できるようにします。

使用する場面

次の3つすべてに当てはまる場合に使用してください。1つでも当てはまらない場合、このツールは適していません。

  1. アプリが Android を対象とした Flutter アプリである。
  2. トレースが Google Play Console(クラッシュダッシュボードまたは ANR ダッシュボード)からエクスポートされたものである。通常は pc 0x… 形式のフレームを含む .txt/.log ファイルで、# Application:/# Version: ヘッダーが含まれる場合もあります。
  3. リリースビルドが Codemagic CI/CD で作成され、ワークフローによって android_native_debug_symbols.zip と <AppName>_<N>_artifacts.zip(Flutter の split-debug-info .symbols ファイル)の両方がビルド成果物としてアップロードされている。これらがないと、難読化された PC に対応するものがありません。mapping.txt(R8/ProGuard)も存在する場合は取得します。これにより、シンボル化ツールは Ka.n.l+8 のような Java フレームを io.flutter.embedding.engine.FlutterJNI.onSurfaceDestroyed に戻せるため、Java 側が読みやすくなり、難読化された名前ばかりの状態を避けられます。

Codemagic でビルドされたか不明な場合は、リポジトリのルートにある codemagic.yaml を確認するか、ユーザーに尋ねてください。

セットアップ(呼び出し元のマシン)

要件用意する方法
Codemagic API キーCodemagic → User settings → Integrations → Personal API token で生成します。次のいずれかの方法で指定してください。(a)CODEMAGIC_API_KEY 環境変数、または(b)リポジトリのルートに、キーをプレーンテキストで記載した .codemagic-api-key ファイル。スクリプトは CWD から上位ディレクトリをたどり、.git の境界で停止します。プライベートリポジトリに共有キーをコミットしたいチームには、このファイルが便利です。その場合は、リスクを理解したうえで使用してください。
llvm-addr2line を含む Android NDKデフォルトの検索先は ~/Library/Android/sdk/ndk/27.3.13750724 です。ANDROID_NDK_HOME=/path/to/ndk で上書きできます。最近の NDK には llvm-addr2line と llvm-readelf が含まれています。
unzip、python3(3.10 以降)、bash標準的なものです。Python スクリプトは標準ライブラリのみを使用します。

環境変数もファイルもない場合は、推測せずに 停止してユーザーに尋ねてください。Codemagic の呼び出しはすべて 401 になります。

ほかにセットアップはありません。特に、gh は 不要です。手順3 を参照してください。

同梱スクリプト

  • scripts/codemagic_fetch_artifacts.py — 3つのモードで検出とダウンロードを行うツールです。JSON は stdout に、進行状況は stderr に出力します。ビルドで生成されている場合は mapping.txt も取得します。
  • scripts/symbolize_flutter_anr.sh — 順序を問わず、トレースとシンボルをまとめるスクリプトです。どの入力がどれか(トレース、ネイティブ zip、Flutter zip、mapping.txt)を判別します。
  • scripts/deobfuscate_r8.py — R8/ProGuard のマッピングを一括で解決するツールです。bash シンボライザーからトレースごとに1回呼び出されます。通常、直接呼び出すことはありません。

3つとも --help(または先頭の docstring)を備えています。フラグを独自に指定する前に、内容を確認してください。

ワークフロー

1. トレースをファイルに保存する

ユーザーがファイルを添付せず、テキストを貼り付けた場合は、/tmp/play-stacktrace.log に書き込んでください。bash スクリプトは ABI の検出とフレームの抽出のためにファイルを2回 grep するため、実際のファイルパスが必要です。

2. トレースからアプリとバージョンを特定する

トレースには通常、/data/app/.../<applicationId>-…/split_config.<abi>.apk にパッケージ名が含まれています。

/data/app/~~xxxx==/com.example.foo-yyyy==/split_config.arm64_v8a.apk
                   ^^^^^^^^^^^^^^^

バージョンは、次の優先順位で特定してください。

  1. トレース内の # Version: ヘッダー行(Crashlytics 形式のエクスポート)。
  2. トレースを共有したときにユーザーが伝えたバージョン。
  3. それでも不明な場合は、停止してユーザーに尋ねてください。 ユーザーは Google Play Console からトレースをダウンロードしているため、送信したトレースの横にあるクラッシュ詳細ページで versionName を直接確認できます。Codemagic の最新ビルドから 推測してはいけません。Symbol Build ID が解決できるのは、クラッシュしたビルドとバージョンが完全に一致する場合だけです。一致しないと、すべてのアプリフレームで [UNRESOLVED: no symbol file for BuildId …] が表示されます。
3. Codemagic からシンボルを取得する

3 ステップで検出します(不要なステップはスキップできます)。

# (a) List visible apps. Read the JSON to pick the right `appName`,
#     then use that exact string for --app in step (b).
python3 scripts/codemagic_fetch_artifacts.py

# (b) List finished Android builds for the app, to confirm the version exists.
python3 scripts/codemagic_fetch_artifacts.py --app "Pixel Buddy"

# (c) Download both symbol zips for that version. Most-recent build wins on ties.
python3 scripts/codemagic_fetch_artifacts.py --app "Pixel Buddy" --build 1.2.3

--app には Codemagic の表示名を指定します(appName と大文字・小文字を区別しない完全一致で照合します)。これは標準のセレクターで、どのマシンでも機能します。Android の applicationId(例:com.chunkytofustudios.pixel_buddy)を渡したい場合は、gh CLI をインストールして認証してください(gh auth login)。その後、スクリプトがリポジトリの android/app/build.gradle{,.kts} から applicationId を解決します。このスキルだけのために gh をインストールしないでください。まずアプリを一覧表示し、名前で照合してください。

ダウンロードしたファイルは ~/.cache/codemagic-fetch-artifacts/codemagic/<appId>/<buildId>/ にキャッシュされるため、同じビルドで再実行すると即座に完了します。

ステップ(c)の標準出力は JSON です。.cacheDir と .files[].path を解析して、次のステップに渡してください。例:

{
  "cacheDir": "/Users/you/.cache/codemagic-fetch-artifacts/codemagic/<appId>/<buildId>",
  "files": [
    { "name": "android_native_debug_symbols.zip", "path": "…/android_native_debug_symbols.zip", "size": 123456789, "cached": true },
    { "name": "PixelBuddy_42_artifacts.zip",       "path": "…/PixelBuddy_42_artifacts.zip",       "size": 12345678,  "cached": false },
    { "name": "mapping.txt",                       "path": "…/mapping.txt",                       "size": 6543210,   "cached": false }
  ]
}

mapping.txt は任意で、ビルドで R8/ProGuard が有効になっている場合にのみ存在します。難読化を無効にしたアプリにはエントリーがありませんが、問題ありません。ステップ 4 では mapping.txt 引数を省略してください。

アプリまたはバージョンが見つからない場合、スクリプトは利用可能な一覧を表示して、ゼロ以外の終了コードで終了します。繰り返し実行するのではなく、その内容をユーザーに提示してください。

4. シンボル化する
bash scripts/symbolize_flutter_anr.sh \
  /tmp/play-stacktrace.log \
  ~/.cache/codemagic-fetch-artifacts/codemagic/<appId>/<buildId>/android_native_debug_symbols.zip \
  ~/.cache/codemagic-fetch-artifacts/codemagic/<appId>/<buildId>/<AppName>_<N>_artifacts.zip \
  ~/.cache/codemagic-fetch-artifacts/codemagic/<appId>/<buildId>/mapping.txt   # optional

引数の順序は問いません。スクリプトが各入力を判別し、トレース、ネイティブ zip、Flutter シンボル zip、または R8 の mapping.txt に分類します。出力先は、入力ファイルと同じ場所にある <trace>.symbolized.txt がデフォルトです。-o で変更できます。機械で読み取り可能な概要を標準出力に表示するには、--json を使用します。

mapping.txt を指定すると、ユーザーアプリの Java フレーム(<userPackage>/base.apk、<userPackage>/oat/.../base.odex、または /memfd:jit-cache のようなパス)に、難読化解除された <class>.<method> を含む [JAVA: …] 注釈が付きます。クラスがマッピングに含まれていないフレーム(R8 が難読化しなかった Flutter SDK のコードや、古いマッピングでよく見られます)には、[JAVA: not in mapping.txt] が付きます。mapping.txt を省略した場合、同じフレームには [JAVA: pass mapping.txt to deobfuscate] が付き、次に何をすればよいかが明確になります。

5. シンボル化された出力を読み、デバッグする

出力には、元の行と解決済みフレームが交互に表示されます。

  #07  pc 0x00000000004bfb78  /data/app/.../split_config.arm64_v8a.apk (flutter::SurfaceDestroyed(_JNIEnv*, _jobject*, long)+…) (BuildId: d73e2148…)
    -> /Users/.../libflutter.so
flutter::SurfaceDestroyed(_JNIEnv*, _jobject*, long)
shell/platform/android/platform_view_android_jni_impl.cc:1234

スクリプトで解決できなかったフレームには [UNRESOLVED] が付きます。末尾の概要には、Frames と Unresolved の件数が表示されます。

信頼モデル

このスキルはエージェントのプロセス外からファイルを取り込むため、「デフォルトで信頼しない」という基準で取り扱います。

  • Codemagic のアーティファクトは、任意の第三者ではなく、ユーザー自身が認証した CI ビルド(個人用 API トークンで解決)から取得します。codemagic_fetch_artifacts.py は小規模な許可リスト(API 用の api.codemagic.io と、Codemagic の署名付き URL CDN 用の storage.googleapis.com/codemagic-build-artifacts/)に TLS 経由で接続先を限定し、リダイレクトのたびに再検証します。また、ホストをまたぐリクエストでは x-auth-token ヘッダーを削除するため、Codemagic トークンが CDN に送信されることはありません。
  • mapping.txt は deobfuscate_r8.py が 1 行ずつ解析します。内容が eval/exec されることも、インポートされることも、ファイルシステムのパスの生成に使われることもありません。ファイルサイズと 1 行ごとの上限により、過大なファイルでパーサーがメモリ不足になるのを防ぎます。
  • symbolize_flutter_anr.sh が処理するトレースと mapping.txt のフィールド(バイナリのパス、括弧で囲まれたメソッド名)は、パターン照合(case)に使うか、リテラルのフィールド値として awk -v に渡すだけです。シェルコマンドや eval、ファイルシステムのパスに埋め込まれることはありません。

既知の注意点

  • ビルド ID が照合の基準です。 シンボルファイルがスタックフレームと一致するのは、そのファイルの BuildId:(llvm-readelf --notes から取得)がトレース行の BuildId: と等しい場合だけです。バージョンが違うと、一致するものがなくなり、すべてのフレームが [UNRESOLVED] になります。ほとんどのフレームが解決できない場合、スクリプトは最初に「wrong build artifacts (common)」と警告します。
  • バージョン文字列は、Codemagic ビルドの version フィールドと完全に一致する必要があります。 Codemagic には、たとえば "2.3.1" のように保存されます。v プレフィックスは許容されますが、ビルド番号を付加した値(2.3.1+42)は使えません。不明な場合は、codemagic_fetch_artifacts.py --app …(--build なし)を実行して、利用可能なバージョンを確認してください。
  • ABI は、トレース内の arm64、armeabi、x86_64、x86 を grep して検出します。 99%のケースでは arm64-v8a です。トレースに ABI のヒントが本当にない場合、スクリプトはデフォルトで arm64-v8a を使用します。
  • Flutter シンボル ZIP の名前は <AppName>_<N>_artifacts.zip です。 <N> は Codemagic のビルド連番であり、バージョンではありません。取得スクリプトは正規表現 .+_\d+_artifacts\.zip でこのファイルを検出します。Codemagic ワークフローでアーティファクト名を変更した場合は、scripts/codemagic_fetch_artifacts.py の FLUTTER_ARTIFACTS_RE を更新してください。
  • システムライブラリのフレームは意図的に未解決のままになります。 libc.so、libart.so、com.google.android.gms、com.google.android.webview、/data/misc/apexdata/com.android.art/.../boot.oat(Android Runtime のプリコンパイル済みブートイメージ)、system_server などのフレームは、ビルドに由来するものではありません。これらの PC は Android のシステムバイナリに一致しますが、Codemagic のアーティファクトには含まれません。スクリプトはこれらに [SYSTEM: …] のタグを付けます。追跡せず、自分のパッケージの split_config.<abi>.apk を指すフレームに注目してください。
  • R8 で難読化された Java フレームには mapping.txt が必要です。 <userPackage>/base.apk のフレームにある Ka.n.l+8 のような名前は、自分のコードが難読化されているだけです。Codemagic は mapping.txt をシンボル ZIP とともにアップロードし、4 番目の引数として渡すとシンボライザーが自動的に適用します。これを渡さない場合、Java フレームは [SYSTEM] に黙ってまとめられるのではなく、[JAVA: pass mapping.txt to deobfuscate] というヒントが付きます。<userPackage>/oat/.../base.odex および /memfd:jit-cache にあるフレーム(AOT および JIT コンパイルされたユーザー Java)も同様に処理されます。一方、boot.oat と framework.jar は正しくシステムとして分類されます。
  • Flutter エンジンのフレーム(libflutter.so)は、シンボル情報が削除された状態で提供されます。 android_native_debug_symbols.zip に含まれる libflutter.so にはビルド ID がありますが、DWARF デバッグ情報がないため、正しいビルド ID が一致してもソース位置情報は解決できません。スクリプトはこれらに [UNRESOLVED: BuildId … matched but PC has no debug info (Flutter engine binaries ship stripped)] のタグを付けます。エンジンのフレームについては、Play Console のトレースに含まれる (flutter::Foo()+offset) というテキストが、得られる情報として最善です。このスキルの付加価値は、app.android-<abi>.symbols ファイルで正しく解決できる libapp.so(Dart AOT)のフレームにあります。
  • Codemagic 以外でビルドした場合、ステップ 3 で失敗します。 クラッシュしたバージョンで Codemagic ワークフローが実行されていない場合(たとえば、ローカルで flutter build appbundle を実行してアップロードした場合)、API から取得できるものはありません。そこで処理を中止し、ユーザーに伝えてください。
  • symbolize_flutter_anr.sh の --json フラグは、すべてのステータスログを stderr に出力し、{"frames", "resolved", "unresolved", "unresolved_nomatch", "unresolved_stripped", "java_deobfuscated", "java_not_deobfuscated", "system", "abi", "output"} を stdout に出力します。シンボライザーを別のスクリプトに連結する場合に使用してください。unresolved は unresolved_nomatch(ビルド ID がアーカイブ内にない → ビルドが違う)と unresolved_stripped(ビルド ID は一致したがバイナリにデバッグ情報がない → 通常は libflutter.so)の合計です。java_deobfuscated と java_not_deobfuscated は、ユーザーアプリの Java フレームを網羅します。後者は、ファイルが渡されたかどうかによって、「mapping.txt が指定されていない」と「クラスが mapping.txt に含まれていない」に意味上分かれます。

クイックリファレンス

# End-to-end, when you already know the display name + version:
export CODEMAGIC_API_KEY=...   # or drop a `.codemagic-api-key` file at the repo root
python3 scripts/codemagic_fetch_artifacts.py --app "Pixel Buddy" --build 1.2.3 \
  | tee /tmp/cm.json
CACHE=$(jq -r .cacheDir /tmp/cm.json)
bash scripts/symbolize_flutter_anr.sh \
  /tmp/play-stacktrace.log \
  "$CACHE/android_native_debug_symbols.zip" \
  "$CACHE"/*_artifacts.zip \
  "$CACHE/mapping.txt"        # omit if the build didn't run R8/ProGuard
# → /tmp/play-stacktrace.symbolized.txt

コメント