Apple Developer Connection
高度な検索
Member Login ログイン | ご入会 ADC連絡先

Technical Note TN2123
CrashReporter

CrashReporterはクラッシュしているすべてのプログラムに関する情報を記録する、Mac OS Xのデバッグ機能です。このテクニカルノートではCrashReporterについて詳しく説明します。CrashReporterが生成するクラッシュログと、そのログを利用してプログラムをデバッグする方法について説明します。

このテクニカルノートは、Mac OS Xユーザ空間のソフトウェアを開発する方なら誰にでも役に立ちます。





はじめに

Mac OS XのCrashReporterは、アプリケーションが現場で直面する問題を把握するための便利な機能です。CrashReporterは3つの有用なアクションを実行します。

  • プログラムがクラッシュすると、CrashReporter はクラッシュログ(一般的に、~/Library/Logs/CrashReporter/<プログラム名>.crash.log)を記録し、メッセージをCrashReporterログ(/var/log/crashreporter.log)とシステムログ(/var/log/system.log)に記録することでユーザにそのことを伝えます。

  • さらに、クラッシュしたプログラムがGUIアプリケーションである場合、CrashReporterはアップルにバグレポートを送信するかどうかを尋ねるダイアログをユーザに提示します(図1を参照)。ユーザが「Report(レポートを送信)」ボタンをクリックすると、CrashReporterによってレポートの詳細を示す別のダイアログが表示され(図2を参照)、送信前にコメントを記入できます。

  • デベロッパは、クラッシュしたプログラムとGDBを接続するオプションを提示するようにCrashReporterを設定することができます。この機能については、「CrashReporterPrefs」で詳しく述べます。

図1:CrashReporterの最初のダイアログ

図1 CrashReporterの最初のダイアログ

図2:CrashReporterの2番目のダイアログ

図2 CrashReporterの2番目のダイアログ

注:CrashReporterは通常、前述のように、クラッシュログをユーザのホームディレクトリに置きます。ただし、状況によって、クラッシュログは/Library/Logs/CrashReporter/<プログラム名>.crash.logに置かれることもあります。以下のような場合が考えられます。

  • クラッシュしたプロセスの所有者を判断できない場合

  • クラッシュしたプロセスをrootが所有していた場合

  • ユーザのホームディレクトリが利用可能でないか、書き込み可能でない場合

このテクニカルノートでは、エンドユーザから入手したクラッシュログを解釈する方法について説明します。最初のセクションでは、クラッシュログの各部分を詳しく説明します。その後、プログラムにデバッグシンボルが含まれていない場合でも、クラッシュログから有用な情報を得る方法を示します。次に、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」フィールドの値は次のとおりです。

  • Mac OS X 10.3.2よりも前のバージョンには、このフィールドはありませんでした。そのようなクラッシュログはすべてバージョン1と見なされます。

  • Mac OS X 10.3.2~10.3.9では、バージョン2のクラッシュログが生成されます。

  • バージョン3のクラッシュログはMac OS X 10.4で初めて登場しました。

  • バージョン4のクラッシュログは、インテルベースの最初のMacintoshコンピュータに含まれているMac OS X 10.4.4で初めて登場しました。

このドキュメントでは、各バージョンの大きな違いについて説明します。

注:バージョン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」フィールドを取得します。プロセスがパッケージアプリケーションの場合、バージョンはInfo.plistファイルのCFBundleShortVersionStringおよびCFBundleVersionプロパティで構成されます。プロセスが単一ファイルのアプリケーションの場合、バージョンは'vers' ID=1のリソースから引き出されます。

プロセスがパッケージアプリケーションで、version.plistファイルが含まれている場合は、「Build Version」、「Project Name」、および「Source Version」フィールドがあります。その場合、これらのフィールドはそれぞれ、BuildVersionSourceVersion、およびProjectNameプロパティから取得されます。このファイル、およびこれらのフィールドは通常、アップルのアプリケーションのみに存在します。

「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

例外の最も一般的な形式は次のとおりです。

  • EXC_BAD_ACCESS/KERN_INVALID_ADDRESS - スレッドがマップされていないメモリにアクセスすることで引き起こされます。データアクセスまたは命令フェッチが引き金になります。この違いを見分ける方法については、後のセクションで説明します。

  • EXC_BAD_ACCESS/KERN_PROTECTION_FAILURE - スレッドが読み取り専用メモリに書き込もうとすることで引き起こされます。これは必ずデータアクセスによって引き起こされます。

  • EXC_BAD_INSTRUCTION - スレッドが不正な命令を実行することで引き起こされます。

  • EXC_ARITHMETIC/EXC_I386_DIV — インテルベースのコンピュータで、スレッドが整数のゼロによる除算を実行することで引き起こされます。

メモリアクセス例外(EXC_BAD_ACCESS)の場合、クラッシュログの例外部分に、例外の引き金となったアドレス(例外アドレス)が含まれています。リスト3では、そのアドレスは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にソケットを組み込むためにスレッドを作成します。

バックトレースでは、各行がネストされた関数呼び出し(フレーム)を示し、最上部には最後に実行した関数、最下部に最初に実行した関数が表示されます。各フレームについて、バックトレース内のカラムは次のとおりです。

  • 最初のカラムはフレーム番号で、0(クラッシュした関数を示す)からスタートして、ネストされた各関数呼び出しごとにインクリメントします。

  • 2番目のカラムは、このフレームで実行するコードを含むライブラリの名前です。これは、(次のカラムの)プログラムカウンタアドレスをロードされたライブラリのリストと照合することで導き出されます。

  • 3番目のカラムは、フレーム内のプログラムカウンタアドレスです。フレーム0の場合、これは一般に例外を引き起こした命令のアドレスです。高位のフレームの場合、これはフレームのリターンアドレスです。つまり、フレームNの場合は、フレームN - 1の参照する関数が戻るときに実行される次の命令を指します。

  • 4番目のカラムは、3番目のカラムに示すプログラムカウンタアドレスのシンボル名です。エンドユーザにアプリケーションを出荷する前にデバッグシンボルをストリップした場合、このカラムには16進の数値だけが含まれています。後述の技法を使えば、対応するシンボル名を知ることが可能です。

最後に、プログラムがマルチスレッドであれば、多くの場合、バックトレースを深く遡ってシンボル名を調べることでスレッドを識別できます。たとえば、リスト4では、フレーム9のシンボルアドレスはNSApplicationMainであり、このスレッドがメインスレッドであることを示しています。対照的に、pthreadの最深部のフレームは必ずルーチン_pthread_bodyになります。

先頭に戻る

スレッドの状態

クラッシュログの次の部分には、クラッシュしたスレッドのプロセッサ状態のダンプが含まれています。リスト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ベースのコンピュータでは、次の点を考慮してください。

  • 3つの値、srr0lr、および例外アドレス(前述)に注目します。

  • srr0は、例外が発生したときのプログラムカウンタです。つまり、例外を引き起こした命令のアドレスです。メモリアクセス例外以外のほとんどの例外(不正な命令の実行によって引き起こされるEXC_BAD_INSTRUCTIONなど)では、これがキー値になります。メモリアクセス例外では、これは式の一部にすぎません。

  • lrは一般的に、関数呼び出しのリターンアドレスを保持するのに使用されます。

  • メモリアクセス例外では:

    • srr0が例外アドレスと同じである場合、例外の原因は命令のフェッチです。通常、これは偽の関数ポインタを呼び出した(言い換えれば、偽のオブジェクト上のメソッドを呼び出した)ということです。この場合、通常はlrにリターンアドレスが入っており、これが偽の関数ポインタを呼び出したコードのアドレスを示します。

    • また、ssr0が例外アドレスと同じlrと等しい場合、プログラムは関数から戻るときにクラッシュしています。一般的にいえば、スタックを破壊した結果(関数の実行時に、リターンアドレスは通常スタック上に保存されます)、偽のアドレスに戻ろうとしてクラッシュしたということです。

    • ssr0が例外アドレスと等しくない場合、例外はメモリアクセス命令によって引き起こされています(C言語の用語で言えば、無効なポインタを逆参照したということです)。

  • 最後に、他のレジスタに証拠となる徴候がないか調べるのも役に立ちます。たとえば、プログラムの一か所のみに現れるASCII文字がレジスタに格納されていれば、最近実行されたコードに関する手掛かりになります。あるいは、レジスタに既知のエラー値(たとえば、dskFulErr。これはCore Services File Managerの「ディスクフルエラー」で、その値は-34、16進数で0xffffffdeです)が含まれていれば、プログラムが機能しなくなった理由の手掛かりになります。

リスト5の例(メモリアクセス例外のスレッドの状態)では、srr0は0x00000000であり、例外アドレス(「例外情報」を参照)とは等しいのに、lrとは等しくないことが分かります。つまり、プログラムはNULL関数ポインタを呼び出すことでクラッシュし、呼び出し側のアドレスはlrにあります。

注:バージョン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

インテルベースのコンピュータでは、次の点を考慮する必要があります。

  • 2つの値、eipと例外アドレス(前述)に注目します。

  • eipは、例外が発生したときのプログラムカウンタです。つまり、例外を引き起こした命令のアドレスです。メモリアクセス例外以外のほとんどの例外(整数のゼロによる除算によって引き起こされるEXC_ARITHMETIC/EXC_I386_DIVなど)では、これがキー値になります。

  • メモリアクセス例外では:

    • eipが例外アドレスと同じである場合、例外の原因は命令のフェッチです。通常、これは次のことを意味します。

      • 偽の関数ポインタを呼び出した(言い換えれば、偽のオブジェクト上のメソッドを呼び出した)

      • 不適切なアドレスに戻った。これはつまり、スタックを破壊したことを意味します。

    • eipが例外アドレスと等しくない場合、例外はメモリアクセス命令によって引き起こされています(C言語の用語で言えば、無効なポインタを逆参照したということです)。

  • 最後に、PowerPCの場合と同様に、他のレジスタに証拠となる徴候がないか調べるのも役に立ちます。

注:インテルアーキテクチャの仕組みのため、スレッドの状態から有用な結果を得るのはPowerPCの場合よりも困難です。たとえば、PowerPCアーキテクチャではリターンアドレスがレジスタ(lr)に格納されますが、インテルではスタックに格納されます。

先頭に戻る

ライブラリ

クラッシュログの次の部分は、プロセスにロードされたすべてのライブラリに関する記述です。リスト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つのレベルがあります。

  • フルデバッグシンボル - これは開発時に使用するもので、デバッガでコードを1ステップずつ実行できます。

  • 関数別シンボル - デバッガシンボルなしビルドした場合でも、リンカが各関数のシンボルをプログラムに含めます。

  • エクスポート専用シンボル - プログラムをストリップした場合、エクスポートされる(つまり、他のプログラムまたはライブラリにインポート可能な)関数のシンボルのみが含まれます。

注:状況はPEF (CFM)プログラムの場合とよく似ています。この場合、フルデバッグシンボルは別の.xSYMファイルに置かれ、関数別シンボルはトレースバックテーブルに保存され、トレースバックテーブルなしで出荷するのは、Mach-Oのエクスポート専用シンボル付きで出荷するのと同じです。

フルデバッグシンボルを含めた状態でビルドされたプログラムは巨大になります。この方法は開発時にのみ使用します。エンドユーザへの出荷時には、通常、関数別シンボルとエクスポート専用シンボルのどちらかを選びます。関数別シンボルを使用すると、バイナリのサイズが数パーセントだけ増加しますが、クラッシュログの解釈が楽になります。他方で、エクスポート専用シンボルを使用するとバイナリが小さくなりますが、クラッシュログには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

重要:エクスポートファイルは、リンカから見た場合と同様の形式で、エクスポートした関数の名前をリストアップする必要があります。つまり、次のことを意味します。

  • C関数をエクスポートする場合は、C識別子の前にアンダースコアを追加する必要があります。

  • C++関数をエクスポートする場合は、マングル名を使用する必要があります。

先頭に戻る

アプリケーション

シンボルがストリップされているプログラムのクラッシュログでは、アドレスがプレーンな16進数で出力されています。元のストリップされていないプログラムを使用して、これらのアドレスのシンボル名を知ることができます。このプロセスは、コードが必ず同じアドレス(一般的には、0x0001000)にロードされるMach-Oアプリケーションでは特に簡単です。atosコマンドラインツール(またはGDB)に対して、元のストリップされていないバイナリを指すように指示するだけです。リスト13に、atosを使用してこれを行う方法の一例を示します。-o original/MyApp引数は、atosに対してoriginal/MyAppバイナリを対象に作動するように指示します。残りの各引数は、シンボル名にマップする16進数のアドレスです。各アドレスについて、atosは、そのアドレスに対応するシンボル名を含む1行を出力します。

リスト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)

atosの詳細については、man ページを参照してください。

重要: atosは現在のところ、-archコマンドラインオプションをサポートしていませんが、Universal Binaryの場合は、常に現行のアーキテクチャと連携します(r. 4086505)。この制限が取り除かれるまで、クロスアーキテクチャのアドレスからシンボルへのマッピングを行う必要がある場合、最善の方法はlipoを使用して、関連のあるアーキテクチャのコードの一時的なコピーを取り出し、それに対してatosを実行することです。

先頭に戻る

ライブラリ

共有ライブラリまたはバンドルの場合は、コードを任意のアドレスでロードできるため、プロセスが若干複雑になります。この場合は、ライブラリのロードアドレスに基づいてアドレスをスライドする必要があります。その第一歩は、ライブラリが実際にロードされたアドレス(Areal)から、ライブラリがロードされるはずだったアドレス(Aideal)を引き算して、スライドを計算することです。ほとんどのサードパーティ製のライブラリ(およびすべてのバンドル)ではAidealが0です。この場合、スライドはArealと等しくなります。ライブラリが特定のアドレスに事前結合されていて、そのアドレスに問題なくロードされると、AidealはArealと等しく、スライドはゼロになります。

スライドを計算したら、アドレス(X)からそれを引き算して、スライドアドレス(Xslid)を決定できます。このアドレスは、ライブラリがスライドしなかった場合のアドレスです。atosまたはGDBを使用して、このアドレスをシンボル名にマップできます。

リスト14に、このプロセスの一例を示します。この例では、クラッシュログには、アドレス x00080f70 (X)でクラッシュしたこと、ライブラリがアドレス0x00080000 (Areal)でロードされたことが分かります。ライブラリはバンドルなので、Aidealはゼロです。そのため、スライド(Areal - Aideal)は0x00080000です。これにより、スライドアドレス(Xslid)は0x00000f70 (X - slide)となります。このアドレスは、atosを使用して、ストリップされていないバイナリ内でマッピングできます。

リスト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を差し引いた値

先頭に戻る

CrashReporterPrefs

CrashReporterPrefsアプリケーション(Xcodeデベロッパツールの一部としてインストールされます)では、CrashReporterの動作を制御できます。図3に、そのプライマリユーザインターフェイスを示します。

図3:CrashReporterPrefsのユーザインターフェイス

図3 CrashReporterPrefsのユーザインターフェイス

3つのモードがあります。

  • Basic — これはデフォルトのモードです(前述)。

  • Developer — このモードでは、クラッシュしたプログラムをGDBにアタッチするオプションが与えられます。図4に、クラッシュ後に表示されるダイアログを示します。「Attach Debugger」ボタンをクリックすると、CrashReporterは(「ターミナル」内で)GDBを起動し、クラッシュしたプロセスに接続するようGDBに指示します。

  • Server — このモードは無人のサーバ用に設計されています。CrashReporterのユーザインターフェイスは表示されませんが、クラッシュログは作成されます。

図4:DeveloperモードのCrashReporter

図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には現在、いくつかの制限があります。

  • 現在のところ、サードパーティデベロッパがCrashReporter経由で送信されるレポートにアクセスする方法はありません。アップルでは、このような機能に対する強い要望があることは認識しています(r. 3356232)。実際、さまざまなサードパーティデベロッパが独自のクラッシュレポートメカニズムを実装しています。それらのメカニズムは単純なもの(起動時にアプリケーションに独自のクラッシュログを確認させて、変更されていればデベロッパにレポートを送信するもの)から、非常に複雑なもの(CrashReporterを完全に再実装したもの)まで多岐にわたっています。

  • abortシステムコールが原因でプログラムが終了した場合、CrashReporterはクラッシュログを生成しません(r. 3291139)。

  • 例外を引き起こすけれども、その例外をシグナルハンドラで処理するようなプログラムを記述すると、CrashReporterは間違ってプログラムのクラッシュログを生成します(r. 2941263)

  • クラッシュログにスタック情報を追加すると、特定のタイプのクラッシュをデバッグするときに役立ちます(r. 3310695)

先頭に戻る

参考資料

先頭に戻る

ドキュメント改訂履歴

日付メモ
2006-02-28PowerPCおよびインテルベースのコンピュータにおけるMac OS X 10.4.4に合わせて更新。バージョン3とバージョン4のクラッシュログ、およびCrashReporterPrefsに関する説明を追加。
2004-09-09CrashReporterについてと、クラッシュログを使ったデバッグ方法について説明。

掲載日: 2006-02-28




Did this document help you?
Yes: Tell us what works for you.

It’s good, but: Report typos, inaccuracies, and so forth.

It wasn’t helpful: Tell us what would have helped.