ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
シンボリケーション: 基礎を超えて
Appでパフォーマンスを最大化して洞察的なデバッグを実行する方法を紹介します。シンボリケーションは、InstrumentsやLLDBなどのツールで中核的な役割を果たして、Appのランタイムとソースコード間のレイヤーを橋渡しします。このプロセスの仕組みと、自身のAppを最大限に理解するために実行できる手順を確認しましょう。
リソース
関連ビデオ
WWDC22
-
ダウンロード
♪ (シンボリケーション:基礎を超えて) こんにちはみなさん シンボリケーションに関するこ のセッションにご参加いただきありがとう シンボリケーションは漠然とした 用語のように見えるかもしれませんが バグ クラッシュ パフォーマンス ボトルネックなどの 根本原因を迅速に特定するために 重要な役割を果たしていることを ご紹介します シンボリケーションがどのように 機能するかについて そして直に理解するため自由に 使えるいくつかのツールを扱い より深い直感を得ることが できるようにします 充実したデバッグを 行うために必要な 豊かなシンボリケーション体験に 必要な様々なデバッグ情報源を 活用するための設定方法について 議論します 具体的な定義と象徴の例を よく知ることから始めましょう 基本的に シンボリケーションは変換 または翻訳するメカニズムです デバイスが実行時にAppを どのように認識するか これはメモリアドレスと 命令の観点からです 開発者としての私たちの Appの見方に戻りましょう これは 関数 名前 および ファイルの観点からです このブリッジング層がなければ 数行のコードでもバグの診断が 非常に複雑になります 例としてこのSwiftコードを 考えてみましょう generateMagicNumber という関数がここにあります それは候補者番号のリストから 特定の番号を選択します これを行うには最初に numberChoicesを呼び出します これはランダムに生成された 10個の数値の配列を返します 次にその配列を selectMagicNumberに渡し 特定のインデックスの 数値を返します これは合理的な プログラムのようですが 最初に実行したときに クラッシュが発生しました 私の最初の頼みの綱は クラッシュログのチェックです これはほぼ実りがありません スレッドのバックトレースから わかることのすべては MagicNumbers Appがどこかで クラッシュしたということです ありがとうでも それはすでに知ってます レジスタのいずれかが 参照しているものは 何も思いつきません デバッガでAppを ステップスルーする試みも可能です それでクラッシュを特定しますが 発生したらどうなるでしょう 私が再現できない特定の状況では? デバッガを使用しても 私にとってのその時の問題を 必ずしも正確に 特定できるとは限りません または分解して 見てみることができます しかし物事を追跡することは はるかに困難です 明らかに問題を診断するための 実行可能な方法とは言えません そしてもっと重要なことは シンボリケーションの助けを借りると この開始点からデバッグする 必要はないのです Xcode OrganizerはdSYMを ダウンロードできると言っており このAppの場合 クラッシュログを再処理します そうすることでXcodeは シンボリケーションの概念を適用します より良いクラッシュログで 問題を診断できるように すべての機能を見ることが できるだけではありません 実際に呼びだされていますが コードで参照するファイルと 行番号は私も判断できます この更新されたクラッシュログは 私たちが範囲外のインデックスに アクセスを試したことも 教えてくれます またはすでに dSYMを持っている場合は atosコマンドを使用して 同じ情報を取得できます 私のコードを振り返ると 気付くようになります そのMAGIC_CHOICEは 10要素配列の境界を はるかに超えています おっとっと 別の例で 最速のユーザー エクスペリエンスを提供する Appのプロファイリングに 関心を持ちました ここでInstrumentsは 高使用率と低使用率の期間を Appが循環することを 示しています 使用率の低い期間に焦点を当てると ツールはAppが いくつかのコンテンツを ファイルに書き込んでいることを 示しています ただし使用率の高い期間を調べると まったく同じバックトレースを 取得します どうしてこんなことが 可能なんでしょう? まったく同じコードを 実行していませんか? 後で説明するように このInstrumentsトレース 部分的にのみ シンボリケーションされています たとえば更新クラッシュログで 行ったように ファイル名または バックトレースの 行番号が表示されません その結果 いくつかの情報が欠落しています それを念頭に置いて 同様にInstrumentsでdSYMを 見つけることができます これを行った後 新しいInstrumentsトレースには 高利用領域で確かにファイルに 書き込んでいたことが 表示されており しかしそれらは特に 私がプログラムに残した デバッグコードパス内にありました 使用率の低い領域は これを回避します そして私のAppが本番環境で どのように動作するかを表します XcodeがdSYMを利用して シンボリケーションしたように かなり情報量の少ない クラッシュログで ツールも部分的に シンボリケーションされたトレースを 豊かにするためそして パフォーマンスの問題の 正確な原因を教えるために dSYMを使用しました さて これらのツールがコード内の 問題領域を特定するために シンボリケーションを活用するのは 素晴らしいことですが それは当然いくつかの質問を お願いしなくてはなりません これはどのように機能しますか? 他にどこにこれを適用できますか? そしてこれはすべて dSYMに関するものですか? これらの質問に答えるには シンボリケーションの機能の ロックを解除し 詳細をさらに深く掘り下げる 必要があります これは少し圧倒されるように 思えるかもしれませんが しかしこれらは理解すべき 重要な概念です デバッグとプロファイリングに 役立つツールはたくさんあります それはシンボリケーションに 基づいています atosだけでもクラッシュの 正確な根本原因はわかっています Xcodeにはさらに多くの ツールが組み込まれています さらに atosに `o` `l` そして `i` などのフラグを指定しました しかしそれらはどういう 意味でしょう? 常に同じフラグのセットを 使用しますか? 使用可能な値の1つがない場合は どうなりますか? また ツールの例のように バックトレースが完全に シンボリケーションされていない場合や その理由を理解し それを修正するための基礎知識を 身につけることができます 最後に シンボリケーションの豊かさに 影響を与えるいくつかの ビルド設定を コントロールすることができます これらのビルド設定が どのように活用されているのか しっかりと理解して もらいたいと思います それにむけて シンボリケーションのための段階の プロセスを紹介したいです ステップ1ではファイルに戻り ステップ2ではデバッグ情報を 参照することです これから説明するように ファイルに戻るということは 実行時メモリアドレスを より安定した 使用可能な形式への変換または 翻訳がすべてです これにより生のメモリアドレスと ソースコード間の 意味のあるつながりを作るために デバッグ情報と通信できます まずファイルに戻る ステップ1について説明します このステップの最終的な目標は 実行時メモリアドレスを 元のクラッシュログで見たような ディスク上のバイナリの対応する アドレスに翻訳することです 実行時アドレスがあるのと同様に Appとフレームワーク ディスクにも アドレス空間があります! ディスク上のアドレス空間は アドレス空間とは異なり Appが実行時に占有すること それらの違いを理解するための メカニズムが必要です まず正確にディスク上のアドレスが 何であるかを理解する 必要があります これらのアドレスはAppを ビルドするとき リンカによって割り当てられます 具体的には リンカはバイナリを セグメントにグループ化します 各セグメントには 関連データが含まれ 名前 サイズ および それらに割り当てられた アドレスのような プロパティがあります たとえば記述したすべての関数と メソッドが含まれた __TEXTセグメント グローバル変数などプログラム 全体の状態が含まれた __DATAセグメントなどです これらの各セグメントには それらが重ならないように 異なるアドレスが割り当てられます リンカは実行可能 ファイルの最初に この情報をMach-Oヘッダの 一部として記録します Mach-Oは全実行可能バイナリと ライブラリに使用される形式で そしてシステムは Appを実行するには このヘッダを読み取る必要が あることを知っています もう少し詳しく見てみると Mach-Oヘッダはいくつかの セグメントプロパティを保持する ロードコマンドが含まれています システムはこれらの ロードコマンドをセグメントを メモリにロードするため使用します AppがUniversal2の 場合は注意してください その場合Appには1つの ヘッダと各アーキテクチャの セグメントのセットがあります otool -lコマンドを使用すると 自分で確認でき これは指定されたファイルの ロードコマンドを出力します ここでは LC_SEGMENT_64 で 識別される セグメントロードコマンドを 探しています このロードコマンドは __TEXTセグメントが vmaddrのアドレスから始まり vmsizeバイトの長さです したがってカーネルがこれらの ロードコマンドに従う場合 セグメントをメモリに ロードするには 実行時とリンカアドレスの間の 違いは正確には何でしょう? さてカーネルが実際に セグメントをロードする前に ASLRスライドと呼ばれる ランダムな値を初期化します 次にカーネルはASLRスライドを loadコマンドのアドレスに 追加します アドレスAで__TEXTセグメント やアドレスBの__DATAセグメント をロードするのではなく 代わりにカーネルはそれらを A+S と B+S にロードします ここで S はASLRスライドです A+S と B+S はシステムが 使用する実際のアドレスであるため これらはロードアドレスとも 呼ばれます これを踏まえて ランタイムアドレスと リンカーアドレスの違いが ASLRのスライドであることが わかりました ASLRスライドは 次の式で計算できます S = L-A ここでSはASLRスライド Lはロードアドレス Aはリンカーアドレスです この方程式の例は すぐにわかりますが しかし重要な点は ASLRスライドがわかったら ファイルアドレス空間に いつでも戻ることができることです ASLRスライド方程式に 2つのアドレスが必要でした ロードアドレスとリンカアドレス ではどこから入手するのでしょうか otoolを使用してリンカ アドレスを知るロードコマンドを 照合する方法については すでに説明しました 実行時アドレスを知るには システムはクラッシュ時または ツールによるプロファイリング時に Appにランタイムアドレス空間を 照会します この情報はクラッシュログの バイナリイメージリストに 反映されます ロードアドレスを vmmapツールを使用して 対話形式で確認することもできます これはプログラムのアクティブな メモリ領域を列挙します 元のクラッシュログから ASLRスライド値を 自分で計算してみましょう バイナリイメージリストに __TEXTセグメントの ロードアドレスがあります ロードコマンドを見ると ディスク上の バイナリのリンカアドレスも あります これら2つを引くと 0x45c000 の ASLRスライド値が得られます これは私のプログラムの すべてのアドレスで ランタイム__TEXTセグメントが リンカ__TEXTセグメント アドレスから 0x45c000 バイト 離れていることを意味しています したがってクラッシュログからの バックトレース アアドレスを確認するには ファイル内に対応し それから 0x45c000 を 引くことができ ディスク上のアドレスを取得します このアドレスは現在ディスク上の アドレススペースの一部であるため Appを調べて そこに何があるかを確認できます クラッシュログはこのアドレスに あるものを実行している間 スレッドがクラッシュしたことを 示しています そのため otoolを再度使用して 問題のある命令を確認できます 今回はotoolに -tVフラグを 指定し 逆アセンブリをプリントします アーキテクチャもarm64として 指定しているのに注意してください これはどのMach-Oヘッダと セグメントを考慮するか otoolが分かるように するためです AppはUniversal2として 構築されているからです otoolの出力はその アドレスでbrk命令を 明らかにします brkはAppの例外 または問題を通知します 私たちが一緒に経験したのと 同じテクニックを使用して atosなどのツールも ASLRスライドを計算します atosは -oフラグが示すファイルの ロードコマンドを読み取り 私たちは -lフラグ付きの ロードアドレスを伝えます 私が言ったように vmmapは実行中の Appのロードアドレスについて 私たちにも教えてくれます この計算をもう一度 試してみましょう 今回はASLRスライドの決定のため バイナリイメージリストの代わりに vmmapを使用します MagicNumbersプログラムを 再度実行し プログラムがクラッシュする前に __TEXTセグメントの ロードアドレスを取得しました 前の式を使用して 今回はそれを決定できます ASLRスライド値は 0x104d14000 でした 繰り返しますがファイルに戻るには ASLRスライド値を 差し引く必要があります 4d14000 を引くと 新しいクラッシュログの 一番上のエントリから 以前とまったく同じ ファイルアドレスを取得します そして これは偶然の一致では ありません カーネルが別のASLR値を 選択しましたが クラッシュログ間でロード アドレスが変更されました ただしファイルアドレス 特定することはできます それがクラッシュの原因でした ここで重要なポイントは私たちの Appが何をしていたかを 正確に理解するメカニズムが あるということです 実行時アドレスに関係なく 命令レベルまでです そして そのマッピングを 使用して 命令にコンパイルされた ソースコードの デバッグ情報を 閲覧することができます 先に進む前に私たちが扱ったもの と私たちが使用したツールの 要約を提示したいと思います Appのバイナリとフレームワーク はMach-Oファイルです これは異なるセグメントで 関連するコンテンツが あることを意味します これらのセグメントは リンカによって作成されます Mach-Oヘッダロード コマンドは アドレスなどこれらのセグメントの プロパティを記述します ロードコマンド出力のため -lフラグ指定のotoolを使用しました 次にカーネルがリンカアドレスへの ASLRスライドとして知られる ランダムな値を追加することを 学びました ASLRスライドと リンカアドレスの追加は ロードアドレスとして 知られています クラッシュが発生した場合に ロードアドレスを確認するには クラッシュログでバイナリ イメージリストを確認できます または実行中のAppの場合 vmmapを使用して ロードアドレスを確認できます 最後にいくつかの例を 見ていきました ファイルのアドレス空間に 戻るための ASLRスライドの計算方法です これでファイルアドレスと ソースコード間の 重要なリンクが含まれている デバッグ情報について説明できます XcodeはAppをビルドする時 デバッグ情報を作成します そしてAppのバイナリに 直接埋め込みます またはdSYMなどの別の ファイルとして保存します デバッグ情報にはいくつかの カテゴリまたはタイプがあります 特定のファイルアドレスに 対してそれぞれが 異なるレベルの詳細を提供します 今日は 3種類のデバッグ情報を 見ていきます まず関数の開始について説明します それ自体はあまり価値を 付加しませんが しかしそれは一般的な出発点です 次にnlistシンボルテーブルが 表示されます 関数名とメソッド名を追加します 最後にDWARFについて 見ていきます これはdSYMと 静的ライブラリから来ています DWARFはファイル名 行番号 および最適化レコードを含む 最も詳細な情報を追加します DWARFが最も詳細なものを 提供するので 私たちは本当にいつでも 可能なときこのタイプの デバッグ情報を手に 入れたいと思っています 完全にシンボリケーションされた クラッシュログの作成のため それぞれとその使用方法について 学習します 関数の開始から始めましょう 表で見たように 関数startsは最小限の ソースコードの詳細を提供します またその名前に忠実であり このタイプのデバッグ情報は 最初のアドレス またはリテラルの開始 私たちの機能だけを 教えてくれます たとえばこれで関数が開始し 特定のアドレスに存在する ことを伝えています ただしどの機能かはわかりません それが存在するということだけで そのアドレスから始めてください 関数はデバッグ情報を開始し Appの__LINKEDITセグメント内の アドレスのリストエンコード することでこれを行います これはAppに直接埋め込まれ どこでそれを見つけられるかを 知らせるために Mach-Oヘッダにも ロードコマンドがあります これはLC_FUNCTION_STARTSです あなたはsymbolsコマンドおよび -onlyFuncStartsDataフラグで あなた自身のためにこれらを 見ることができます ここでアドレスとnullプレース ホルダのリストが返されます これらのプレースホルダは 理想的には nullの代わりに関数名と メソッド名を持ちますが 関数はデータを開始し 名前を提供しません 繰り返しますがこれは 最も説明的なデータではありません ただしクラッシュログを わずかに更新することはできます これでファイルアドレスを 関数からの オフセットとして表示できます たとえばASLRスライド値を 差し引くことによって 最初にファイルに戻ります 次にファイルアドレスが 含まれている 可能性がある関数の開始値を 見つけます この場合最初の値のみ アドレスを含めることができます 他のすべての値がアドレスよりも 大きいためです 最後にファイルアドレスを 要求できます この関数には実際には 264バイトです この機能の設定方法の詳細や どのレジスタが変更されたかは 理解できるので 主にデバッガに役立ちます しかし 関数名がない クラッシュログに遭遇した場合は このような最低レベルの デバッグ情報を 扱っていることになります これは良いニュースです クラッシュログを より良いデバッグ情報で 充実させる機会が たくさんあるからです 当然私たちが見たい 次のレベルの詳細は 関数名です これは私たちに クラッシュログまたは ツールトレースを取得するための ソースコードの 問題を追跡するために使用する 最初の本当の機会を与えてくれます これによりnlistシンボル テーブルが表示されます 関数開始のアイデアに基づいて シンボルテーブルは構築され また情報のリストを エンコードします __LINKEDITセグメントにあり 独自のロードコマンドもあります ただしアドレスを エンコードするだけでなく それらはC構造体を エンコードします これにより特定のエントリの詳細を 機能開始と比較して追加できます 具体的にはnlist_64構造体を エンコードします ここにその構造体の定義があります 一目見ただけで名前といくつかの プロパティにアクセス できることがわかります これらの構造体フィールドの値は nlistのn_typeによって 決定されます 私たちが興味を持っている 3つの主要なn_typesがあり 今のところ2つだけに 焦点を当てます 最初のものは直接記号として 知られています これらはAppと フレームワーク内の 完全に定義した関数とメソッドです 直接記号には nlist_64構造体で 名前と住所があります さらにそれらは n_type フィールドの 特定のビットパターンに よって表されます 具体的には n_typeには 2番目 3番目 4番目があり 最下位ビットが設定されています これらのビットはN_SECT とも呼ばれます これらはnmと指定 --defined-onlyフラグと --numeric-sortフラグで 見ることができます ここで nmは定義された MagicNumbersプログラムの シンボルをウォークスルーし そしてそれらをアドレス順に リストしました 返される名前は不可解に見えます それは実際に保存されている 名前だからで シンボルテーブルには わからなくされた名前があります これらのわからなくされた名前は コンパイラとリンカに 機能を一意に識別するために 役立ちます しかしそれらが解放されない 限り理解するのは 簡単ではありません これらの名前のより親しみやすい 型を取得するために 出力をswift-demangleに 渡しました これでmainやnumberChoicesなど おなじみの名前がか取得されます それらは私のAppで直接 定義されているからです 同様にシンボルツールには オプションがあり NListデータを表示するには 名前を自動的に解いてゆきます 関数名をアドレスに関連付ける ことができるようになったので これによりクラッシュログを もう一度更新できます ここで関数開始データから得た オフセット式を見ることができ それは直接記号からの エントリにも一致し そのエントリには名前があります これら2つを組み合わせて メインに264バイトで クラッシュが 発生したと言えます これはまだ詳細が気になりますが 私たちは事実を知っており 関係する機能はその メインだけではありません また正確な行番号を 取得することも役立ちます 私たちはこれに似た 何かにツールトレースの例で 遭遇しました いくつか関数名を使用できましたが 他の関数名がありませんでした この理由の1つは シンボルテーブルが リンクに関係する機能の場合 直接シンボルエントリのみが あるからです これらはモジュール間で 使用する関数で またはフレームワークから エクスポートする関数です これによりAPIの境界を 特定するのに役立ちます またそれがdlsymやdladdrなどの 関数を使用し 動的負荷に電力を供給する必要な データを持っている ことを意味します ただし1つの欠点は ローカル関数または静的関数です シンボルテーブルに表示されていず モジュールの外部で 参照されていないためです これにより実装関数が 省略されてしまいます ここでAppロジックの多くが 存在する可能性があります さらにリリースモードでビルド されたバイナリでは一般的です シンボルテーブルを削除します これは不要なエントリが シンボルテーブルから 削除されたことを意味します これはAppのサイズを 縮小するのに役立ちます 考えてみればAppのプライマリ ドライバがどこかに 機能をエクスポートすることは まずありませんから シンボルのテーブルエントリを 置いておくのは無駄なスペースです フレームワークやライブラリでは クライアントが使用すべき エクスポートされた 関数が必ずありますが ローカルで共有されている関数は 他の場所では使えないので 維持する必要はありません プライマリAppの実行可能 ファイルを削除するとほとんどは シンボルテーブルは 実質的に空のままにします フレームワークと ライブラリの削除は エクスポートされた 関数のみを残します Strip Linked Product ストリップスタイル およびストリップ スウィフトシンボルなど Xcodeでビルド設定に 出くわした可能性があります これらのビルド設定は ビルド中に Appの削除方法を制御します リンクされた製品のストリップが 有効になっている場合 バイナリはストリップスタイルに 従ってストリップされます たとえばすべてのシンボルは 最も侵略的な除去を実行し 必要不可欠なものだけを残します 非グローバルはAppの さまざまなモジュール内で 使用される直接記号を削除します ただし他のAppで使用するために エクスポートされることはないです シンボルをデバッグすると3番目の nlistタイプが削除されます これについては後に DWARFで説明します ただしこのストリップスタイルは 直接記号を保持します たとえば ここに2つの パブリックインターフェイスと 1つの内部共有実装機能を 定義するフレームワークがあります これらの機能はすべてリンクの 役割を果たすため それらはすべて直接シンボル エントリを持っています 非グローバルを削除すると その後自分のインターフェース だけが残ります 共有実装機能のみが使用されました 私のフレームワーク内なので グローバルとは見なされません 同様にすべてのシンボルを削除して もインターフェイスは残ります これらはフレームワークを 使用するため 他のAppに必要なのです シンボル --onlyNListData 出力でも 関数開始アドレスが あることが確認できます 直接シンボルの間に散在しています これらのアドレスは次のいずれか であった関数を表します 直接のシンボルでは決してないか または削除されたかです これらのストリップ設定を シンボルテーブルの可視性の 希望するレベルに調整できます 直接記号を使用しているとき この情報を使用して 次のことを判断できます これのいくつかの明白な兆候は 関数名を持っていますが 行番号やファイル名 関数名の混在はありません フレームワークの例で ここにあるように 関数はアドレスを開始します 分析する2番目のタイプの nlist構造体は 直接記号とは対照的に 間接記号として知られています これはn_typeがN_EXTビット パターンのみに一致する場合で これは印刷などの他のフレーム ワークまたはライブラリから 使用している関数とメソッドです これらはnmで見られますが --defined-onlyの代わりに -undefined-onlyを 今回のみ指定します -mフラグも追加します これでどのフレームワーク みつけられる関数または ライブラリが表示されます たとえば MagicNumbers Appは さまざまな Swift関数に依存します libSwiftCoreで 定義されています これで デバッグ情報 カテゴリの3つのうち 2つについて説明しました それらの特性を 確実に理解しましょう 関数の開始はアドレスのリストです だからそれは名前を欠いている ただしオフセットを 決定することはできます nlistシンボルテーブルは 情報の構造体全体をエンコードし 名前をアドレスに 関連付けることができます これらはAppで定義されている 直接記号をおよび 間接記号表し それは依存関係によって 提供されます 直接記号はリンクに関連する 機能については 通常予約されています ストリップビルド設定が どの直接記号が 利用可能かに影響します 最後に両方の機能が開始され nlistシンボルテーブルは Appに直接埋め込まれています 私たちがまだ見ていないのは ファイル名や行番号などより豊富な レベルの詳細です これはDWARFによって 提供されます DWARFはNListシンボル テーブルの概念を完全に 異なるレベルに取り入れています 関数のサブセットのみを 保持するのではなく DWARFはすべてを説明 するよう努めています nlistシンボルテーブルが対機能が 開始するはるかに多くの情報を 追加することを確認しました それは次元を追加することに よって達成しました してててください 私は関数をされて アドレスのある ただ一つの次元から始めました 次にnlistシンボルテーブル内の 情報でいっぱいの 構造体をエンコード することによって 2次元にアップグレードしました WARFは3番目の次元を追加します それは関係性についてです DWARFは機能が分離されて いないことを認識しています それらは他の関数を呼び出し パラメータを持っています 意味のあるデータを返し 特定のファイルで定義されます これらの関係をエンコードする シンボリケーションの最も 強力な側面のロックを解除します DWARFを分析していると 主にdSYMバンドルを参照します plistなどの他の メタデータに加えて dSYMバンドルにはDWARFを 含むバイナリが含まれています このバイナリが 特別な理由は何でしょう? バイナリにはそのデータが特別な DWARFセグメントに含まれます DWARF仕様では焦点を 当てるセグメント内の 3つのデータストリームに 言及します debug_infoには 生データが含まれ debug_abbrevはデータ に構造を割り当て debug_lineには ファイル名と 行番号が含まれています DWARFは2つの 語彙タイプも定義し 最初に勉強します: コンパイルユニットと サブプログラムです 3つ目は後で紹介します コンパイル単位は単一の ソースファイルを表し それが製品の構築に入りました たとえばプロジェクト内の Swiftファイルごとに コンパイル単位が1つになると 予想できます DWARFはコンパイルユニット ファイル名 SDK に プロパティを割り当て その機能が占める __TEXTセグメントの部分 他にもたくさんあります main.swiftコンパイルユニット にはこれらのプロパティが含まれ 左側のdebug_info ストリームでは debug_abbrevストリームに 対応するエントリがあり 右側には値が何を表しているかが 示されています ここにファイル名 それが 書かれている言語 __TEXTセグメント範囲を表す 低/高ペアが表示されます サブプログラムは 定義された関数を表します すでにnlistシンボルテーブルで 定義された関数を見ましたが ただしサブプログラムは静的関数と ローカル関数も記述できます サブプログラムにも名前と およびその__TEXTセグメント アドレス範囲があります コンパイルユニットと サブプログラム間の 1つの基本的な関係は サブプログラムはコンパイル単位で 定義されているということです DWARFはこれを合わせて ます コンパイル単位はツリーの ルートにあり 子としてサブプログラム エントリがあります 子はアドレス範囲に従って 検索できます これらをより詳細にdwarfdump コマンドを使用して まずコンパイルユニットについて 見ていきます コンパイラは先にたコンパイラ の いくつかのプロパティと一致します dwarfdumpは debug_info および debug_abbrevの 内容便利に組み合わせ あなたのdSYMのデータの 構造と内容を表示します そして出力を下にスクロールすると 1つのサブプログラムの子に遭遇します それが占めるアドレス範囲 コンパイル単位の範囲内にあり 関数の名前も表示されます DWARFはそのデータを非常に 詳細に説明していると述べました このすべての詳細に多くの時間を 費やすことはありませんが 関数のパラメーターなどの詳細を 見るのは楽しいと思います 彼らはパラメータの名前と タイプを説明する 独自の語彙タイプを持っています ツリーモデルに従って パラメータは サブプログラムの子です ここで関数に提供する choicesパラメータの エントリに出くわします 次にファイル名と行番号が debug_lineストリームから 取得されます このストリームにはツリー構造がありません 代わりにラインテーブル プログラムを定義し ここで個々のファイルのアドレスを コードの正確な行にマップして 戻すことができます これでこれを検索して ファイルと行を見つけられる ソースコードの詳細の リストが生成されます debug_infoツリーを解析し debug_lineリストを生成します 最終的には次のような構造になり ファイルアドレスを照合する場合は ツリーを横断することができます まず コンパイル ユニットから始め 枝をたどります 次に一致したdebug_line エントリをすべて取得します これをatosで再び 自動化できます 今回だけ特に -iフラグを 省略しています ここで少し奇妙なことに 気づきましたか? はい関数名と行番号があります 間違いなくDWARFを 使用しているのです それ以外はそれほど nlistシンボルテーブルの 更新とそれほど 違いはありません 実際初めてatosを使用した ときと比較すると まだ私たち非常に多くの 貴重な機能と 見落としがあるようです ここで何が起こったのですか? 唯一変更されたのは 今回は -iをatosに 指定しなかったことです そのフラグは 「インライン関数」を表します インライン化は日常的な最適化で コンパイラが実行します これには関数呼び出しの 置換が含まれます 関数の本体を直接使用します その1つのクールな効果はコードが 一見消えるようにすることです numberChoicesを 呼び出すのではなく numberChoicesの コード全体 所定の位置にドロップされました 突然numberChoicesへの 関数呼び出しがなくなりました! DWARFは、これをインライン サブルーチンで表します これはDWARFの3番目で 最後の語彙タイプです 今日はそれについて話し合います インライン化されたサブルーチンは サブプログラムです だからそれは別のサブプログラムに インライン化された関数です インライン関数が完全に 飲み込まれているため 関係ツリー内の別のノードによって インライン化されたサブルーチンは そのノードの子です この定義は再帰的にも適用されます つまりインラインサブルーチンで 他のインラインの 子供を持つことができます 繰り返しますが dwarfdumpを使用すると インラインサブルーチンを 探すことができます それらは他のノードの 子としてリストされています 名前や住所など サブプログラムと同様の プロパティを持ちます ただしDWARFでは これらのプロパティは 共通ノードを介して 頻繁にアクセスされ 抽象的な起源として 知られています 特定の関数のインライン コピーが多数ある場合 それらの共通の共有プロパティは くれならがますでかも 複製さん贈ないように 抽象的な起源に保たれてます インライン化サブルーチンの固有の プロパティは呼び出しサイトです これはソースコードの場所で 実際の関数呼び出しを書き しかしオプティマイザが それを置き換えました ここではたとえば main.swiftファイルの36行目で generateANumberを 呼び出しました これによりツリーを新しい 子ノードで更新できます そして今私たちの プログラムのはるかに 包括的なビューです インライン関数の最適化の詳細は たんちが私完全にシンボリケーションさん付たん クロスロブログ 私たちが取得する上で 重要な詳細でした atosの-iフラグは シンボリケーションの際に シンボリケーション化の際にそれらを考慮します それらはまたツールトレースから 見落とされいます dSYMが必要な理由は ツールとクラッシュログの両方で このコンテンツをすべて抽出 できるようにするためでした あなたがDWARFを見つける 別の情報源があります それは静的ライブラリと オブジェクトファイルからです dSYMがない場合でも リンクした機能ならば DWARFを静的ライブラリや オブジェクトファイルから 収集できます その場合デバッグシンボルの nlistタイプが見つかります これらは 削除できる シンボルタイプの1つで しかし彼らはDWARF自身を 保持していません むしろそれら は関数を関連付け 元のファイルに戻ります ライブラリがデバッグ情報を 使用して構築されている場合 nlistエントリはそのDWARFを 指すことができます これらのタイプのnlistエントリは dsymutil--dump-debug-mapで 冗長ながら見ることができます ここにかさまざまな 機能のリストとそして それらがどこから引っ張られたが 示されています これらの場所はDWARF用に スキャンおよび処理できます 要約すると DWARFは詳細な記号データの 重要なソースです DWARFは関数とファイルの間の 重要な関係を公開します 関数のインライン化などの最適化は シンボリケーションの質に多大な影響を与え そして DWARF はそれを非常に うまく表現することができます dSYMと静的ライブラリにDWARFが 含まれていることもわかりました ただし他への転送が容易なため dSYMをお勧めします いくつかのツールからの組み込みの サポートがあります 最後にシンボリケーションを容易にするために 使えるさまざまな ツールとヒントを 共有したいと思います ローカル開発ビルドの場合 通常は デバッグモードでビルドすると 大量のデバッグ情報があります リリースモードの場合 デバッグ情報フォーマットの デバッグ情報フォーマットの ビルド設定を確認します リリースがdSYMファイルでDWARFに 設定されてのを確認してください App Storeに投稿された Appの場合 App Store Connectから dSYMをダウンロードできます これにはビットコードが有効に なっているAppも含まれます 特定のdSYMを確認したい場合は すでにデバイス上にある場合は mdfindコマンドを使用できます ここでの英数字の文字列は バイナリのUUIDです これはloadコマンドで 定義された一意の識別子です dSYMのUUIDは 記号 -uuidで確認できます ツールチェーンが無効なDWARFを 生成する場合があります これはdwarfdump--verifyで 確認できます 報告されたエラーを見つけた場合は バグを報告してください DWARFデータにはバイナリ あたり4ギガバイトが上限です dSYMで問題が発生した場合 4ギガバイトを超えている ことを確認し プロジェクトをそれぞれが 独自の小さなdSYMを持つように 個別のコンポーネントに 分割することを検討してください 使用しているdSYMが Appの特定のビルドに 一致すること UUIDを比較して興味のあること を確認できます AppのUUIDはクラッシュ レポートのセクションの バイナリイメージリストにあり symbolsコマンドで確認する こともできます AppとdSYMの両方を 同じUUIDを持つことを 確認する必要があります シンボルツールではAppが 利用できるデバッグ情報の 種類をチェックすることもできます この例はすでに見てきましたが これらのタグが情報源を教える 角かっこで 囲まれていることを 思い出してください あなたが扱っているだろう どのデバッグ情報か わからない場合に便利です dSYMが利用可能であることが 確実な場合でも ツールのトレースでは関数の名前を 取得していません 資格とコード署名を 確認してください 具体的にはcodesign コマンドを使用して 適切なコード署名が あることを確認できます ローカルで構築されたAppを 確認する必要もあります 開発用にはget-task-allow 資格があります この資格はAppを象徴する ツールなどに許可を与えます Xcodeはこの資格を自動的に 設定する必要があります プロファイルアクションを使用し 確認すると便利です 有効なget-task-allow 資格がない場合 Code Signing Inject Base Entitlements ビルド設定を 確認する必要があります 開発中にそれが有効に なっていることを確認してください 最後にUniversal2 Appの場合 ツールに興味を持っている アーキテクチャを 特定のアーキテクチャ スライスでのみ動作する シンボル otool dwarfdumpには すべて -archフラグがあり 指定する必要があります これで「シンボリケーション: 基礎を超えて」は終わりです 他に何もないとしても いくつかの重要なポイントを 強調したいと思います UUIDとファイル アドレスは一貫しており Appが何をしていたかを特定する ための信頼できる方法です ASLRスライドから 独立しているためです これらはデバッグ情報を照会する ための鍵でもあります また可能な限りdSYMを 使用する必要があります dSYMには最も 豊富なデバッグ情報が DWARFの形式でXcodeと Instrumentsによりサポートされて 含まています 最後にいくつかのツール について説明しました これらのツールはXcodeで すぐに利用できます そしてそれらは強力な 診断と洞察を提供します あなたはそれをデバッグと 最適化のためのワークフローに 組み込むよう努めるべきです あなたがもっと学ぶことに 興味があるなら WWDC18からのこれらの 2つのセッションをお勧めします 起動時にAppがどのように 機能するかを学ぶには: 「Appの起動を最適化する」 と「App startup time: Past, present, and future」 シンボリケーションについて学ぶために セッションにご参加いただき ありがとうございました 素晴らしい一週間を お過ごしください ♪
-
-
1:11 - MagicNumbers
func selectMagicNumber(choices: [Int]) -> Int { return choices[MAGIC_CHOICE] } func randomValue() -> Int { return Int.random(in: 1...100) } func numberChoices() -> [Int] { var choices = [Int]() for _ in 1...10 { choices.append(randomValue()) } return choices } func generateMagicNumber() -> Int { let numbers = numberChoices() let magic = selectMagicNumber(choices: numbers) return magic } print("The magic number is: \(generateMagicNumber())")
-
2:51 - atos symbolication
atos -o MagicNumbers.dSYM/Contents/Resources/DWARF/MagicNumbers -arch arm64 -l 0x10045c000 -i 0x10045fb70
-
7:34 - Load commands
otool -l MagicNumbers | grep LC_SEGMENT -A8
-
10:31 - Disassembly
otool -tV MagicNumbers -arch arm64
-
11:32 - vmmap
vmmap MagicNumbers | grep __TEXT
-
15:09 - Function starts
symbols -onlyFuncStartsData -arch arm64 MagicNumbers
-
17:06 - nlist_64
struct nlist_64 { union { uint32_t n_strx; } n_un; uint8_t n_type; uint8_t n_sect; uint16_t n_desc; uint64_t n_value; };
-
17:59 - Direct symbols with nm
nm -arch arm64 —defined-only --numeric-sort MagicNumbers
-
18:30 - Demangled direct symbols with nm
nm -arch arm64 —defined-only --numeric-sort MagicNumbers | xcrun swift-demangle
-
18:43 - Demangled direct symbols with the symbols tool
symbols -arch arm64 -onlyNListData MagicNumbers
-
23:06 - Indirect symbols with nm
nm -m —arch arm64 --undefined-only --numeric-sort MagicNumbers
-
27:16 - Examining dSYMs with dwarfdump
dwarfdump -v -debug-info -arch arm64 MagicNumbers.dSYM
-
29:25 - atos symbolication without inlined functions
atos -o MagicNumbers.dSYM/Contents/Resources/DWARF/MagicNumbers -arch arm64 —l 0x10045c000 0x10045fb70
-
32:29 - Examining debugging symbols
dsymutil --dump-debug-map -arch arm64 MagicNumbers
-
33:59 - Examining dSYM UUIDs
symbols -uuid MagicNumbers.dSYM
-
34:03 - Verifying DWARF
dwarfdump —verify MagicNumbers.dSYM
-
35:09 - Verifying entitlements and codesigning
codesign --display -v --entitlements :- MagicApp.app
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。