はじめにMac OS XのCrashReporterは、アプリケーションが現場で直面する問題を把握するための便利な機能です。CrashReporterは3つの有用なアクションを実行します。
図1:CrashReporterの最初のダイアログ
図2:CrashReporterの2番目のダイアログ
注:CrashReporterは通常、前述のように、クラッシュログをユーザのホームディレクトリに置きます。ただし、状況によって、クラッシュログは
このテクニカルノートでは、エンドユーザから入手したクラッシュログを解釈する方法について説明します。最初のセクションでは、クラッシュログの各部分を詳しく説明します。その後、プログラムにデバッグシンボルが含まれていない場合でも、クラッシュログから有用な情報を得る方法を示します。次に、CrashReporterを使って、コンピュータにおける偶発的なクラッシュをデバッグする方法について説明します。最後に、現在の実装に関するいくつかの制限について説明します。 重要:このテクニカルノートでは、Mac OS X 10.4.4に実装されているCrashReporterについて述べます。CrashReporterは長い間に進化してきたため、現行バージョンと以前のバージョンの間にはわずかな違いがたくさんあります。重要な違いがある場合には、その点を言及しています。 クラッシュログの構造クラッシュログは、いくつかの部分に分かれています。以降のセクションでは、各部分について詳細に説明します。 基本情報クラッシュログの最初の部分には、クラッシュログ自体に関する情報が含まれています。一例をリスト1に示します。 リスト1:基本情報 Date/Time: 2006-01-16 16:56:44.755 -0800 OS Version: 10.4.4 (Build 8G1165) Report Version: 4 ここで最も重要な情報はOSのバージョンです。ビルド番号には特に注意する必要があります。Mac OS Xの各バージョンには、ビルド番号によってのみ識別できる複数のバリエーションが存在します(これは一般に、アップルが新しいハードウェアをリリースするときに発生します)。また、時間と日付を調べて、疑わしいパターンがないか確認します。すべて12:00に発生しているクラッシュログが多数ある場合には、おそらく時間処理コードを調べる必要があります。 「ReportVersion」フィールドの値は次のとおりです。
このドキュメントでは、各バージョンの大きな違いについて説明します。 注:バージョン2のクラッシュログには、このセクションに「Host Name」フィールドが含まれていました。その後のバージョンには、このフィールドはありません。 プロセス情報クラッシュログの次の部分には、リスト2に示すように、クラッシュしたプロセスに関する情報が含まれています。 リスト2:プロセス情報 Command: TextEdit Path: /Applications/TextEdit.app/Contents/MacOS/TextEdit Parent: WindowServer [354] Version: 1.4 (220) Build Version: 418 Project Name: TextEdit Source Version: 2200000 PID: 628 Thread: 0 ここで注目すべき最も重要な情報は、クラッシュしたプロセスの名前です。場合によっては、実際にクラッシュしたプロセスは考えていたプロセスでないことがあります。たとえば、アプリケーションがヘルパーツールを利用してある作業を実行する際に、そのヘルパーツールがクラッシュした場合、アプリケーションコードのデバッグで時間を無駄にせずに、ヘルパーツールのコードに焦点を合わせる必要があります。 「Parent」フィールドには、親プロセスの名前とPID(角括弧内)が含まれています。このフィールドが期待どおりであるかチェックする価値はあります。 CrashReporterはプロセスの実行ファイルから「Version」フィールドを取得します。プロセスがパッケージアプリケーションの場合、バージョンは プロセスがパッケージアプリケーションで、 「PID」フィールドはクラッシュしたプロセスのプロセスIDです。 「Thread」フィールドにはクラッシュしたスレッドが表示されます。バックトレースセクションにクラッシュしたスレッドが強調表示されるため、このフィールドは冗長です。 注:「Path」、「Version」、および「Thread」フィールドはバージョン2のクラッシュログで追加されました。 注:「Parent」、「Build Version」、「Project Name」、および「Source Version」フィールドはバージョン3のクラッシュログで追加されました。また、バージョン3のクラッシュログでは、以前のバージョンのフィールドのいくつかが並べ替えられています。 例外情報クラッシュログの3番目の部分は、クラッシュの直接の原因になったプロセッサ例外に関する情報を示します。リスト3に典型的な例を示します。 リスト3:例外情報 Exception: EXC_BAD_ACCESS (0x0001) Codes: KERN_PROTECTION_FAILURE (0x0002) at 0x00000000 例外の最も一般的な形式は次のとおりです。
メモリアクセス例外( バックトレース情報クラッシュログの4つ目の部分には、クラッシュしたプロセスのすべてのスレッドに関するバックトレースが含まれており、一般的に最も興味深い部分です。リスト4に例を示します。 リスト4:バックトレース情報 Thread 0 Crashed: 0 <<00000000>> 0x00000000 0 + 0 1 com.apple.CoreFoundation 0x9075d114 __CFRunLoopRun + 832 2 com.apple.CoreFoundation 0x9075ca18 CFRunLoopRunSpecific + 268 3 com.apple.HIToolbox 0x9318e1e0 RunCurrentEventLoopInMode + … 4 com.apple.HIToolbox 0x9318d874 ReceiveNextEventCommon + 380 5 com.apple.HIToolbox 0x9318d6e0 BlockUntilNextEventMatchingL… 6 com.apple.AppKit 0x9368c104 _DPSNextEvent + 384 7 com.apple.AppKit 0x9368bdc8 -[NSApplication nextEventMat… 8 com.apple.AppKit 0x9368830c -[NSApplication run] + 472 9 com.apple.AppKit 0x93778e68 NSApplicationMain + 452 10 com.apple.TextEdit 0x000020e8 0x1000 + 4328 11 com.apple.TextEdit 0x0000b298 0x1000 + 41624 この例では、スレッドが1つのみのため、バックトレースも1つのみです。マルチスレッドプロセスでは、1つのスレッドごとに1つのバックトレースがあります。そのため、クラッシュしたスレッドを識別することが重要です。CrashReporterでは、そのバックトレースに "Thread <スレッド番号> Crashed:" というテキストのタグを付けることで、スレッドの識別が簡単になっています。しかし、このテキストは見落とされやすく、Thread 0がクラッシュしたと勘違いされがちです。 注:明示的にスレッドを作成しなくても、プロセスがマルチスレッドになる場合があります。さまざまなフレームワークが、開発者に代わってスレッドを作成する可能性があるからです。たとえば、CFSocketはrunloopにソケットを組み込むためにスレッドを作成します。 バックトレースでは、各行がネストされた関数呼び出し(フレーム)を示し、最上部には最後に実行した関数、最下部に最初に実行した関数が表示されます。各フレームについて、バックトレース内のカラムは次のとおりです。
最後に、プログラムがマルチスレッドであれば、多くの場合、バックトレースを深く遡ってシンボル名を調べることでスレッドを識別できます。たとえば、リスト4では、フレーム9のシンボルアドレスは スレッドの状態クラッシュログの次の部分には、クラッシュしたスレッドのプロセッサ状態のダンプが含まれています。リスト5に、PowerPCの場合の例を示します。 リスト5:PowerPCのスレッドの状態
Thread 0 crashed with PPC Thread State 64:
srr0: 0x0000000000000000 srr1: 0x000000004000d030 …
cr: 0x44022282 … lr: 0x000000009000a6bc…
r0: 0x00000000ffffffe1 r1: 0x00000000bfffeb10 r2: 0x00000000a…
r4: 0x0000000003000006 r5: 0x0000000000000000 r6: 0x000000000…
r8: 0x0000000000000000 r9: 0x0000000000000000 r10: 0x000000000…
r12: 0x000000009000a770 r13: 0x0000000000000000 r14: 0x000000000…
r16: 0x0000000000000000 r17: 0x0000000000000000 r18: 0x000000000…
r20: 0x00000000101a7026 r21: 0x00000000be5b19d8 r22: 0x000000000…
r24: 0x0000000000000450 r25: 0x0000000000001203 r26: 0x000000000…
r28: 0x0000000000000000 r29: 0x0000000003000006 r30: 0x000000000…
この情報を最大限に生かすには、プロセッサのMac OS Xアプリケーションバイナリインターフェイス(ABI)について十分に理解する必要があります。詳細については、『Mac OS X ABI Function Call Guide』を参照してください。ただし、ABIについて十分に理解していなくても、有用な結果を得る簡単な方法がいくつかあります。 PowerPCアーキテクチャPowerPCベースのコンピュータでは、次の点を考慮してください。
リスト5の例(メモリアクセス例外のスレッドの状態)では、 注:バージョン3より前のクラッシュログには、各PowerPCレジスタの下位32ビットのみが含まれます。 インテルアーキテクチャリスト6に、インテルベースのコンピュータのスレッドの状態を示します。 リスト6:インテルのスレッドの状態 Thread 0 crashed with i386 Thread State: eax: 0x10004005 ebx: 0x90822191 ecx:0x00001203 edx: 0x00000450 edi: 0x00000000 esi: 0x00000000 ebp:0xbffff058 esp: 0xbffff01c ss: 0x0000002f efl: 0x00010206 eip:0x00000000 cs: 0x00000027 ds: 0x0000002f es: 0x0000002f fs:0x00000000 gs: 0x00000037 インテルベースのコンピュータでは、次の点を考慮する必要があります。
注:インテルアーキテクチャの仕組みのため、スレッドの状態から有用な結果を得るのはPowerPCの場合よりも困難です。たとえば、PowerPCアーキテクチャではリターンアドレスがレジスタ( ライブラリクラッシュログの次の部分は、プロセスにロードされたすべてのライブラリに関する記述です。リスト7にその一例を示します。 リスト7:ライブラリ
Binary Images Description:
0x1000 - 0x1bfff com.apple.TextEdit 1.4 (220) /Applications/Te…
0x8fe00000 - 0x8fe4bfff dyld 44.17 /usr/lib/dyld
0x90000000 - 0x9016efff libSystem.B.dylib /usr/lib/libSystem.B.dylib
0x901be000 - 0x901c0fff libmathCommon.A.dylib /usr/lib/system/libmathC…
0x90225000 - 0x902fafff ATS /System/Library/Frameworks/ApplicationSe…
0x9031a000 - 0x90769fff com.apple.CoreGraphics 1.258.20 (???) /System/…
0x90800000 - 0x908c8fff com.apple.CoreFoundation 6.4.5 (368.26) /System/…
0x90906000 - 0x90906fff com.apple.CoreServices 10.4 (???) /System/Libr…
0x90908000 - 0x909fbfff libicucore.A.dylib /usr/lib/libicucore.A.dylib
0x90a4b000 - 0x90acafff libobjc.A.dylib /usr/lib/libobjc.A.dylib
[…]
このリストは、シンボルのないプログラムでシンボリックバックトレースを知るのに使用できるので特に役立ちます。また、プロセスにロードされたプラグインを正確に示すので、プログラムでプラグインを多用している場合にも有用です。最後に、一般的なアプリケーションパッチ(すなわち「拡張」)技術で使用するライブラリのように、プロセスにロードされると予想していないライブラリがないか、このリストを調べることもできます。 注:このセクションはバージョン2のクラッシュログで追加されました。 マシン情報このセクションは、リスト8に一例を示すように、クラッシュが発生したハードウェアについて記述されています。小さな「システムプロファイラ」レポートと考えることができます。 リスト8:マシン情報 Model: iMac4,1, BootROM IM41.0039.B00, 2 processors, Intel Core Duo, … Graphics: ATI Radeon X1600, ATY,RadeonX1600, PCIe, 128 MB Memory Module: DIMM1/BANK 1, 512 MB, DDR2 SDRAM, 667 MHz AirPort: spairport_wireless_card_type_airport_extreme (0x14E4, 0x89),… Bluetooth: Version 1.7.1f14, 2 service, 1 devices, 1 incoming serial … Network Service: AirPort, AirPort, en1 Serial ATA Device: Maxtor 6L160M0, 152.67 GB Parallel ATA Device: MATSHITADVD-R UJ-846 USB Device: Built-in iSight, Micron, Up to 480 Mb/sec, 500 mA USB Device: Hub in Apple USB Keyboard, Alps Electric, Up to 12 Mb/sec… USB Device: M4848, Logitech, Up to 1.5 Mb/sec, 100 mA USB Device: Apple USB Keyboard, Alps Electric, Up to 1.5 Mb/sec, 250 … USB Device: Bluetooth HCI, Up to 12 Mb/sec, 500 mA USB Device: IR Receiver, Apple Computer, Inc., Up to 12 Mb/sec, 500 m… 重要:このセクションには、コンピュータのシリアル番号またはMACアドレス、その他、特定のユーザやマシンを追跡できるような情報は含まれていません。 注:このセクションはバージョン4のクラッシュログで追加されました。 Rosettaに関する追加情報クラッシュしたプロセスがRosettaで実行していた場合は、追加情報がクラッシュログに追加されます。まず、プロセス情報セクションに使用されていたRosettaが表示されます。この例をリスト9に示します。 リスト9:Rosettaに関するプロセス情報 Command: TextEdit Path: /Applications/TextEdit.app/Contents/MacOS/TextEdit Parent: WindowServer [95] Rosetta: Yes また、ライブラリセクションとマシン情報セクションの間に新しく「変換されたコード情報」のセクションが追加されます。このセクションには以下の情報が含まれています。
リスト10に、この情報の例を示します。 リスト10:変換されたコードの情報 Translated Code Information: Rosetta Version: 14.05 Args: /Applications/TextEdit.app/Contents/MacOS/TextEdit -psn_0_39452673 Exception: EXC_BAD_ACCESS (0x0001) Thread 0: Crashed (0xb7fff9d0, 0xb8080bcf) 0x0000000e: /Applications/TextEdit.app/Contents/MacOS/TextEdit … 0x000046d0: /Applications/TextEdit.app/Contents/MacOS/TextEdit … […] PPC Thread State srr0: 0x00000000 srr1: 0x00000000 vrsave… cr: 0xXXXXXXXX xer: 0x00000000 lr: 0x000046d0 ctr… r00: 0x000046d0 r01: 0xbfffe3b0 r02: 0x00000000 r03… r04: 0xbfffe450 r05: 0x0000000e r06: 0x00000000 r07… r08: 0x00000000 r09: 0x00000000 r10: 0x00000000 r11… r12: 0x90131cc0 r13: 0xffffd96e r14: 0x0033fdd0 r15… r16: 0xbfffe4c0 r17: 0x00314690 r18: 0x00000000 r19… r20: 0x00000001 r21: 0x00000003 r22: 0xa3132618 r23… r24: 0x00000000 r25: 0x00000000 r26: 0x00000000 r27… r28: 0x00328b60 r29: 0x00000000 r30: 0xbfffe3b0 r31… シンボルなしのデバッグMach-Oデバッグシンボルには、3つのレベルがあります。
注:状況はPEF (CFM)プログラムの場合とよく似ています。この場合、フルデバッグシンボルは別の フルデバッグシンボルを含めた状態でビルドされたプログラムは巨大になります。この方法は開発時にのみ使用します。エンドユーザへの出荷時には、通常、関数別シンボルとエクスポート専用シンボルのどちらかを選びます。関数別シンボルを使用すると、バイナリのサイズが数パーセントだけ増加しますが、クラッシュログの解釈が楽になります。他方で、エクスポート専用シンボルを使用するとバイナリが小さくなりますが、クラッシュログには16進数のアドレスだけが含まれることになります。 幸いなことに、これらは両立できます。ビルド環境を正しくセットアップすれば、エクスポート専用シンボルを備えたプログラムをユーザに出荷する一方で、クラッシュログのアドレスをシンボリック名にマップすることができます。次のセクションでは、その方法を説明します。 ビルドシステムのセットアップその第一歩は、シンボル付きのバイナリを生成し、そのバイナリからシンボルをストリップして、シンボルなしの独立のバイナリを生成するようにビルドシステムをセットアップすることです。実際の方法は、ビルドシステムがどのようにセットアップされているかによって決まります。基本となるメカニズムは、stripコマンドラインツールです。リスト11に、このツールを使用してアプリケーションからシンボルをストリップする方法を示します。リスト12には、共有ライブラリまたはバンドルで同じことを行う方法を示します。この場合は、保持したいエクスポートシンボルをリストアップするエクスポートファイルを用意する必要があります。これらのシンボルを保持しないと、ライブラリをインポートする(または、バンドルをロードする)プログラムがエクスポートされたシンボルを見つられません。 リスト11:アプリケーションのストリップ $ strip -u -r original/MyApp -o stripped/MyApp リスト12:ライブラリのストリップ $ strip -u -r -s MyLib.exp original/MyLib -o stripped/MyLib 重要:エクスポートファイルは、リンカから見た場合と同様の形式で、エクスポートした関数の名前をリストアップする必要があります。つまり、次のことを意味します。
アプリケーションシンボルがストリップされているプログラムのクラッシュログでは、アドレスがプレーンな16進数で出力されています。元のストリップされていないプログラムを使用して、これらのアドレスのシンボル名を知ることができます。このプロセスは、コードが必ず同じアドレス(一般的には、0x0001000)にロードされるMach-Oアプリケーションでは特に簡単です。 リスト13:アプリケーションのアドレスのシンボルへのマッピング $ atos -o original/MyApp 0x00003fbc 0x000045a4 0x00004cb8 0x000035c0 _CopyBundleDevelopmentRegionPretty (BundleInfo.c:335) _PrintBundleInfo (BundleInfo.c:643) _main (BundleInfo.c:828) __start (crt.c:267)
重要: ライブラリ共有ライブラリまたはバンドルの場合は、コードを任意のアドレスでロードできるため、プロセスが若干複雑になります。この場合は、ライブラリのロードアドレスに基づいてアドレスをスライドする必要があります。その第一歩は、ライブラリが実際にロードされたアドレス(Areal)から、ライブラリがロードされるはずだったアドレス(Aideal)を引き算して、スライドを計算することです。ほとんどのサードパーティ製のライブラリ(およびすべてのバンドル)ではAidealが0です。この場合、スライドはArealと等しくなります。ライブラリが特定のアドレスに事前結合されていて、そのアドレスに問題なくロードされると、AidealはArealと等しく、スライドはゼロになります。 スライドを計算したら、アドレス(X)からそれを引き算して、スライドアドレス(Xslid)を決定できます。このアドレスは、ライブラリがスライドしなかった場合のアドレスです。 リスト14に、このプロセスの一例を示します。この例では、クラッシュログには、アドレス x00080f70 (X)でクラッシュしたこと、ライブラリがアドレス0x00080000 (Areal)でロードされたことが分かります。ライブラリはバンドルなので、Aidealはゼロです。そのため、スライド(Areal - Aideal)は0x00080000です。これにより、スライドアドレス(Xslid)は0x00000f70 (X - slide)となります。このアドレスは、 リスト14:アプリケーションのアドレスのシンボルへのマッピング -- クラッシュログの抜粋 Thread 0 Crashed: 0 com.apple.carbonbundletemplate 0x00080f70 0x80000 + 0xf70 […] Binary Images Description: […] 0x80000 - 0x80fff com.apple.carbonbundletemplate 1.0 /Volumes/Guy… -- コマンドライン $ atos -o original/MyBundle 0x00000f70 _Nested2 (main.c:3) 注:数式を使って説明すると次のようになります。
-- slideはArealとAidealの差
slide = Areal - Aideal
-- XはArealの先頭からのオフセット
X = Areal + offset
offset = X - Areal
-- Xslidはライブラリの先頭からのオフセットと等しい
Xslid = Aideal + offset
= Aideal + X - Areal
= X + (Aideal - Areal)
= X - (Areal - Aideal)
= X - slide
-- Xslidは単にXからslideを差し引いた値
CrashReporterPrefsCrashReporterPrefsアプリケーション(Xcodeデベロッパツールの一部としてインストールされます)では、CrashReporterの動作を制御できます。図3に、そのプライマリユーザインターフェイスを示します。 図3:CrashReporterPrefsのユーザインターフェイス
3つのモードがあります。
図4:DeveloperモードのCrashReporter
Developerモードでは、コンピュータで発生する偶発的なクラッシュをデバッグできます。毎日使用していても、めったにクラッシュしないプログラムがあるとします。毎日使用しているプログラムなので、GDBの下で実行するのは実用的ではありません。しかし、プログラムがクラッシュしたらそのクラッシュをデバッグできればと思うでしょう。CrashReporterのDeveloperモードでは、これが簡単にできます。 重要:CrashReporterPrefsはMac OS X 10.4 (Xcode 2.0)で導入されました。Mac OS X 10.4より前のバージョンでは、Developerモードに相当するものはありませんでした。ただし、テクニカルQ&A QA1288「Suppressing the "unexpectedly quit" alert」で述べているように、隠し環境設定を使用すると、CrashReporterの動作を変更できます。 CrashReporterの制限CrashReporterには現在、いくつかの制限があります。
参考資料ドキュメント改訂履歴
掲載日: 2006-02-28 | ||||||||||||
|