このセクションでは、トレースバックを使用してエラーの原因を特定する方法を示すサンプルプログラムについて説明します。
これらのプログラム出力で表示される 16 進の PC とレジスターの内容は、一般的な出力の代表例です。PC は、実行ファイルの作成に使用したライブラリーとほかのツールの変更に伴って変更されます。
次の READ 文は、アプリケーションが処理しないファイル終了エラーを発生させます。
program teof integer*4 i,res i=here( ) end integer*4 function here( ) here = we( ) end integer*4 function we( ) we = go( ) end integer*4 function go( ) go = again( ) end integer*4 function again( ) integer*4 a open(10,file='xxx.dat',form='unformatted',status='unknown') read(10) a again=a end
このプログラムを IA-32 アーキテクチャー・ベースのプラットフォームでトレースバックを有効にしてビルドし、シングルスレッドの Fortran 共有ランタイム・ライブラリーとリンクすると、診断出力は次のようになります。
forrtl: 致命的なエラー (24): 読み取り中にファイルの最後に達しました。ユニット 10、ファイル E:\USERS\xxx.dat Image PC Routine Line Source libifcorert.dll 1000A3B2 Unknown Unknown Unknown libifcorert.dll 1000A184 Unknown Unknown Unknown libifcorert.dll 10009324 Unknown Unknown Unknown libifcorert.dll 10009596 Unknown Unknown Unknown libifcorert.dll 10024193 Unknown Unknown Unknown teof.exe 004011A9 AGAIN 21 teof.for teof.exe 004010DD GO 15 teof.for teof.exe 004010A7 WE 11 teof.for teof.exe 00401071 HERE 7 teof.for teof.exe 00401035 TEOF 3 teof.for teof.exe 004013D9 Unknown Unknown Unknown teof.exe 004012DF Unknown Unknown Unknown KERNEL32.dll 77F1B304 Unknown Unknown Unknown
出力の最初の行は、標準の Fortran ランタイム・エラー・メッセージです。その後には、エラーの発生場所を特定するために、コールスタックを逆向きに巡回した結果が表示されています。個々の出力行は、スタック上の 1 つのコールフレームを表しています。アプリケーションは -traceback (Linux* および Mac OS* X) または /traceback (Windows*) を指定してコンパイルされているため、Fortran コード内の PC は、対応するルーチン名、行番号、およびソースモジュールと関係付けられています。Fortran コード中にない PC は関係付けられずに、"Unknown" とレポートされています。
最初の 5 つのフレームは、Fortran ランタイム・ライブラリー中のルーチンへの呼び出しを (逆順で) 示しています。アプリケーションはシングルスレッドの共有ライブラリーにリンクされているため、レポートされる実行ファイルの名前は libifcore.so (Linux および Mac OS X) または libifcorert.dll (Windows) となっています。これらは、READ を実行するために、およびファイル終了 (EOF) 条件の検出時にエラーをレポートするのに呼び出されたランタイムルーチンです。未処理の I/O のプログラミング・エラーでは、コールスタック上にはこのようなランタイムコードのフレームが常にいくつか存在します。
Fortran 開発者にとって真に重要なスタックフレームは、実行ファイル teof.exe 内の最初のフレームです。ここでは、ソースモジュール teof.for の 21 行目の AGAIN という名前のルーチンでエラーが発生したことが示されています。ソースコードの 21 行目で、ファイル終了条件を引き起こした Fortran の READ 文があることがわかります。
次の 4 つのフレームは、エラーを発生させたルーチンに至る、Fortran コード内の一連の呼び出しを示しています (TEOF→HERE→WE→GO→AGAIN)。
最後の 3 つのフレームは、プログラムの起動と初期化を処理したルーチンです。
このプログラムが、シングルスレッドのスタティック Fortran ランタイム・ライブラリーにリンクされていた場合、出力は次のようになります。
forrtl: 致命的なエラー (24): 読み取り中にファイルの最後に達しました。ユニット 10、ファイル E:\USERS\xxx.dat Image PC Routine Line Source teof.exe 004067D2 Unknown Unknown Unknown teof.exe 0040659F Unknown Unknown Unknown teof.exe 00405754 Unknown Unknown Unknown teof.exe 004059C5 Unknown Unknown Unknown teof.exe 00403543 Unknown Unknown Unknown teof.exe 004011A9 AGAIN 21 teof.for teof.exe 004010DD GO 15 teof.for teof.exe 004010A7 WE 11 teof.for teof.exe 00401071 HERE 7 teof.for teof.exe 00401035 TEOF 3 teof.for teof.exe 004202F9 Unknown Unknown Unknown teof.exe 00416063 Unknown Unknown Unknown KERNEL32.dll 77F1B304 Unknown Unknown Unknown
最初の 5 つのスタックフレームは、libifcore.so (Linux および Mac OS X) または libifcorert.dll (Windows) ではなく実行ファイル teof.exe 中のルーチンを示しています。ルーチンは共有ライブラリーの場合にレポートされた 5 つのランタイムルーチンと同じものですが、アプリケーションはアーカイブ・ライブラリー libifcore.a (Linux および Mac OS X) またはスタティック Fortran ランタイム・ライブラリー libifcore.lib (Windows) にリンクされたので、これらのルーチンを含むオブジェクト・モジュールはアプリケーション・イメージ (teof.exe) にリンクされています。マップファイルを使用して、関係付けられていない PC の場所を決定することができます。
アプリケーションがトレースバックを有効にしないでコンパイルされ、シングルスレッドのスタティック Fortran ライブラリーにリンクされた場合、診断出力は次のようになります。
forrtl: 致命的なエラー (24): 読み取り中にファイルの最後に達しました。ユニット 10、ファイル E:\USERS\xxx.dat Image PC Routine Line Source teof.exe 00406792 Unknown Unknown Unknown teof.exe 0040655F Unknown Unknown Unknown teof.exe 00405714 Unknown Unknown Unknown teof.exe 00405985 Unknown Unknown Unknown teof.exe 00403503 Unknown Unknown Unknown teof.exe 00401169 Unknown Unknown Unknown teof.exe 004010A8 Unknown Unknown Unknown teof.exe 00401078 Unknown Unknown Unknown teof.exe 00401048 Unknown Unknown Unknown teof.exe 0040102F Unknown Unknown Unknown teof.exe 004202B9 Unknown Unknown Unknown teof.exe 00416023 Unknown Unknown Unknown KERNEL32.dll 77F1B304 Unknown Unknown Unknown
イメージに -traceback (Linux および Mac OS X) または /traceback (Windows) によって提供された関係情報がないため、Fortran ランタイムシステムは PC をルーチン名、行番号、およびソースファイルに関係付けることができません。この場合でも、マップファイルを使用すれば、少なくともルーチン名と、それが含まれているモジュールを決定することはできます。
-traceback または /traceback を指定してコンパイルを行うと、実行ファイルに追加の PC の関係情報が含まれるので、実行ファイルのサイズが大きくなることに注意してください。実行ファイルに追加のトレースバック情報が含まれているかどうか (.trace セクションが存在するかどうか) を調べるには、次のように入力してください。
objdump -h your_app.exe (Linux) otool -l your_app.exe (Mac OS X) link -dump -summary your_app.exe (Windows)
traceback を指定した場合と指定しない場合でアプリケーションをビルドして、生成される実行ファイルのサイズを比較してみてください。単純なディレクトリー・コマンドでファイルのサイズを確認できます。
この teof.exe の例では、トレースバックの関係情報が含まれることで実行ファイルのサイズが 512 バイト大きくなっています。実際のアプリケーションでは、これよりもはるかに大きくなるでしょう。開発者は、個々のアプリケーションについて、実行ファイルのサイズが大きくなっても PC の関係付けを自動的に行うほうが良いのか、それとも PC とマップファイルの関係付けを手作業で行うほうが良いのかを判断しなければなりません。
コンパイル時にトレースバックを有効にした場合、エラーが発生すると、ランタイム・ライブラリーは関係情報を含むコールスタック表示を出力します。
コンパイル時にトレースバックを無効にした場合、エラーが発生すると、ランタイム・ライブラリーは関係情報を含まないコールスタック表示を出力します。
コールスタック情報を表示したくない場合、環境変数 FOR_DISABLE_STACK_TRACE を true に設定します。ただし、この場合でも次の Fortran のランタイム・エラー・メッセージは表示されます。
forrtl: severe (24): end-of-file during read, unit 10, file E:\USERS\xxx.dat
次のプログラムは、-fpe 0 (Linux および Mac OS X) または /fpe:0 (Windows) を指定してコンパイルすると、浮動小数点オーバーフロー例外が生成されます。
program ovf real*4 a a=1e37 do i=1,10 a=hey(a) end do print *, 'a= ', a end real*4 function hey(b) real*4 b hey = watch(b) end real*4 function watch(b) real*4 b watch = out(b) end real*4 function out(b) real*4 b out = below(b) end real*4 function below(b) real*4 b below = b*10.0e0 end
このプログラムは、以下のオプションを指定してコンパイルされると仮定しています。
-fpe 0 (Linux および Mac OS X) または /fpe:0 (Windows)
-traceback (Linux および Mac OS X) または /traceback (Windows)
-O0 (Linux および Mac OS X) または /Od (Windows)
IA-32 アーキテクチャー・ベースのシステムでは、トレースバック出力は次のようになります。
forrtl: エラー (72): 浮動小数点オーバーフロー Image PC Routine Line Source ovf.exe 00401161 BELOW 29 ovf.f90 ovf.exe 0040113C OUT 24 ovf.f90 ovf.exe 0040111B WATCH 19 ovf.f90 ovf.exe 004010FA HEY 14 ovf.f90 ovf.exe 0040105B OVF 7 ovf.f90 ovf.exe 00432429 Unknown Unknown Unknown ovf.exe 00426C74 Unknown Unknown Unknown KERNEL32.dll 77F1B9EA Unknown Unknown Unknown
前に示した、処理されない I/O のプログラミング・エラーの例と同様に、例外が発生した場所からスタック巡回を開始することができます。ランタイムルーチンはコールスタック上にはありません。オーバーフローは、ソースファイル ovf.f90 の 29 行目に関係付けられている BELOW ルーチンの PC 00401161 で発生しています。
これよりも高い最適化レベル O2 で -fpe 0 と -traceback (Linux および Mac OS X) または /fpe:0 と /traceback (Windows) を指定してプログラムをコンパイルすると、トレースバック出力は次のようになります。
forrtl: エラー (72): 浮動小数点オーバーフロー Image PC Routine Line Source ovf.exe 00401070 OVF 29 ovf.f90 ovf.exe 004323E9 Unknown Unknown Unknown ovf.exe 00426C34 Unknown Unknown Unknown KERNEL32.dll 77F1B9EA Unknown Unknown Unknown
/O2 では、プログラム全体がインライン展開されます。
メインプログラム OVF は、HEY ルーチンを呼び出さなくなっています。この出力は直観的に予想されるものとはかなり異なりますが、依然として完全に正しいものです。リリース版実行ファイルでの実行の失敗に関する診断情報を解釈するときには、コンパイラーの最適化の効果を念頭に置く必要があります。
環境変数 TBK_ENABLE_VERBOSE_STACK_TRACE を true に設定して、同じ実行ファイルをもう一度実行すると、エラーの発生時に例外コンテキスト・レコードのダンプも表示されます。IA-32 アーキテクチャー・ベースのシステムではどのように表示されるかを次に抜粋します。
forrtl: エラー (72): 浮動小数点オーバーフロー Hex Dump Of Exception Record Context Information: Exception Context: Processor Control and Status Registers. EFlags: 00010212 CS: 0000001B EIP: 00401161 SS: 00000023 ESP: 0012FE38 EBP: 0012FE60 Exception Context: Processor Integer Registers. EAX: 00444488 EBX: 00000009 ECX: 00444488 EDX: 00000002 ESI: 0012FBBC EDI: F9A70030 Exception Context: Processor Segment Registers. DS: 00000023 ES: 00000023 FS: 00000038 GS: 00000000 Exception Context: Floating Point Control and Status Registers. ControlWord: FFFF0262 ErrorOffset: 0040115E DataOffset: 0012FE5C StatusWord: FFFFF8A8 ErrorSelector: 015D001B DataSelector: FFFF0023 TagWord: FFFF3FFF Cr0NpxState: 00000000 Exception Context: Floating Point RegisterArea. RegisterArea[00]: 4080BC143F4000000000 RegisterArea[10]: F7A0FFFFFFFF77F9D860 RegisterArea[20]: 00131EF0000800060012 RegisterArea[30]: 00000012F7C002080006 RegisterArea[40]: 02080006000000000000 RegisterArea[50]: 0000000000000012F7D0 RegisterArea[60]: 00000000000000300000 RegisterArea[70]: FBBC000000300137D9EF ...
次の例では、Fortran と C が混在したアプリケーションでトレースバック出力を使用する方法を示します。メインプログラムは FPING という名前の Fortran プログラムです。プログラム FPING は、一連の関数呼び出しで、Fortran と C のコードを交互に呼び出します。そして、Unlucky という名前の C のルーチンが呼び出され、ゼロによる浮動小数点除算エラーが発生します。
ソースモジュール FPING.FOR には、それぞれソースモジュール CPONG.C の C ルーチンを呼び出す Fortran 関数定義が含まれています。プログラム FPING.FOR は、以下のオプションを指定してコンパイルされます。
-fpe 0 (Linux および Mac OS X) または /fpe:0 (Windows)
-traceback (Linux および Mac OS X) または /traceback (Windows)
-O0 (Linux および Mac OS X) または /Od (Windows)
IA-32 アーキテクチャー・ベースのシステムでは、トレースバック出力は次のようになります。
forrtl: エラー (73): 浮動小数点ゼロ除算 Image PC Routine Line Source fping.exe 00401161 Unknown Unknown Unknown fping.exe 004010DC DOWN4 58 fping.for fping.exe 0040118F Unknown Unknown Unknown fping.exe 004010B6 DOWN3 44 fping.for fping.exe 00401181 Unknown Unknown Unknown fping.exe 00401094 DOWN2 31 fping.for fping.exe 00401173 Unknown Unknown Unknown fping.exe 00401072 DOWN1 18 fping.for fping.exe 0040104B FPING 5 fping.for fping.exe 004013B9 Unknown Unknown Unknown fping.exe 004012AF Unknown Unknown Unknown KERNEL32.dll 77F1B304 Unknown Unknown Unknown
Fortran ルーチンに対応するスタックフレームは、ルーチン名、行番号、およびソースモジュールと関係付けられていますが、C ルーチンに対応するフレームは関係付けができないことに注目してください。スタックを逆向きに巡回し、PC をレポートすることはできても、PC をルーチン名や行番号などに関係付けるために必要な情報は、Fortran コンパイラーが生成した実行ファイルから得られます。C コンパイラーにはこの機能はありません。また、Fortran のコンパイルの際にも、-traceback または /traceback オプションを指定しないと、関係情報は得られないということに注意してください。
一番上のスタックフレームは、C コードに含まれているので、ルーチン名に関係付けることはできません。アプリケーションのマップファイルを見ると、レポートされた PC、00401161 はルーチン _Unlucky の始点よりも大きく、ルーチン _down1_C の始点よりも小さいことがわかります。これは、エラーがルーチン _Unlucky で発生したことを意味します。
同じように、"Unknown" としてレポートされた他の PC も、マップファイルを使用してルーチン名に関係付けることができます。
トレースバック出力 (また、その意味ではあらゆる形式の診断出力) を調べるときには、コンパイラーの最適化の影響を考慮に入れることが重要です。上記の例の Fortran ソースモジュールは、最適化をオフにしてビルドされていました。次に、-O2 (Linux および Mac OS X) または /O2 (Windows) を指定し、最適化を有効にしてビルドしたときの出力を示します。
forrtl: エラー (73): 浮動小数点ゼロ除算 Image PC Routine Line Source fping.exe 00401111 Unknown Unknown Unknown fping.exe 0040109D DOWN4 58 fping.for fping.exe 0040113F Unknown Unknown Unknown fping.exe 00401082 DOWN3 44 fping.for fping.exe 00401131 Unknown Unknown Unknown fping.exe 0040106B DOWN2 31 fping.for fping.exe 00401123 Unknown Unknown Unknown fping.exe 00401032 FPING 18 fping.for fping.exe 00401369 Unknown Unknown Unknown fping.exe 0040125F Unknown Unknown Unknown KERNEL32.dll 77F1B304 Unknown Unknown Unknown
トレースバック出力からは、ルーチン DOWN1 が一度も呼び出されなかったように見えます。実際、このルーチンは呼び出されていません。高度な最適化レベルでは、コンパイラーは関数 DOWN1 をインライン展開するので、FPING からルーチン down1_C への呼び出しが行われるようになっています。関係付けられた行番号は、依然としてソースコード内の正しい行を指しています。
最後に、例の Fortran コードの設計を変更し、個々の Fortran ルーチンを独立したソースモジュールに分割したとします。再設計したコードのトレースバック出力は次のようになります。
forrtl: エラー (73): 浮動小数点ゼロ除算 Image PC Routine Line Source fpingmain.exe 00401171 Unknown Unknown Unknown fpingmain.exe 004010ED DOWN4 12 fping4.for fpingmain.exe 0040119F Unknown Unknown Unknown fpingmain.exe 004010C1 DOWN3 11 fping3.for fpingmain.exe 00401191 Unknown Unknown Unknown fpingmain.exe 00401099 DOWN2 11 fping2.for fpingmain.exe 00401183 Unknown Unknown Unknown fpingmain.exe 00401073 DOWN1 11 fping1.for fpingmain.exe 0040104B FPING 5 fpingmain.for fpingmain.exe 004013C9 Unknown Unknown Unknown fpingmain.exe 004012BF Unknown Unknown Unknown KERNEL32.dll 77F1B304 Unknown Unknown Unknown
コードの新しい設計が反映され、行番号とソースファイルの関係情報が変わっていることに注意してください。
次に、上記の例で使用したソースを示します。
************************************ FPING.FOR ************************************ program fping real*4 a,b a=-10.0 b=down1(a) end real*4 function down1(b) real*4 b !DEC$ IF DEFINED(_X86_) INTERFACE TO REAL*4 FUNCTION down1_C [C,ALIAS:'_down1_C'] (n) !DEC$ ELSE INTERFACE TO REAL*4 FUNCTION down1_C [C,ALIAS:'down1_C'] (n) !DEC$ ENDIF REAL*4 n [VALUE] END real*4 down1_C down1 = down1_C(b) end real*4 function down2(b) real*4 b [VALUE] !DEC$ IF DEFINED(_X86_) INTERFACE TO REAL*4 FUNCTION down2_C [C,ALIAS:'_down2_C'] (n) !DEC$ ELSE INTERFACE TO REAL*4 FUNCTION down2_C [C,ALIAS:'down2_C'] (n) !DEC$ ENDIF REAL*4 n [VALUE] END real*4 down2_C down2 = down2_C(b) end real*4 function down3(b) real*4 b [VALUE] !DEC$ IF DEFINED(_X86_) INTERFACE TO REAL*4 FUNCTION down3_C [C,ALIAS:'_down3_C'] (n) !DEC$ ELSE INTERFACE TO REAL*4 FUNCTION down3_C [C,ALIAS:'down3_C'] (n) !DEC$ ENDIF REAL*4 n [VALUE] END real*4 down3_C down3 = down3_C(b) end real*4 function down4(b) real*4 b [VALUE] !DEC$ IF DEFINED(_X86_) INTERFACE TO SUBROUTINE Unlucky [C,ALIAS:'_Unlucky'] (a,c) !DEC$ ELSE INTERFACE TO SUBROUTINE Unlucky [C,ALIAS:'Unlucky'] (a,c) !DEC$ ENDIF REAL*4 a [VALUE] REAL*4 c [REFERENCE] END real*4 a call Unlucky(b,a) down4 = a end ************************************ CPONG.C ************************************ #include <math.h> extern float __stdcall DOWN2 (float n); extern float __stdcall DOWN3 (float n); extern float __stdcall DOWN4 (float n); int Fact( int n ) { if (n > 1) return( n * Fact( n - 1 )); return 1; } void Pythagoras( float a, float b, float *c) { *c = sqrt( a * a + b * b ); } void Unlucky( float a, float *c) { float b=0.0; *c = a/b; } float down1_C( float a ) { return( DOWN2( a )); } float down2_C( float a ) { return( DOWN3( a )); } float down3_C( float a ) { return( DOWN4( a )); } ************************************ FPINGMAIN.FOR ************************************ program fping real*4 a,b a=-10.0 b=down1(a) end ************************************ FPING1.FOR ************************************ real*4 function down1(b) real*4 b !DEC$ IF DEFINED(_X86_) INTERFACE TO REAL*4 FUNCTION down1_C [C,ALIAS:'_down1_C'] (n) !DEC$ ELSE INTERFACE TO REAL*4 FUNCTION down1_C [C,ALIAS:'down1_C'] (n) !DEC$ ENDIF REAL*4 n [VALUE] END real*4 down1_C down1 = down1_C(b) end ************************************ FPING2.FOR ************************************ real*4 function down2(b) real*4 b [VALUE] !DEC$ IF DEFINED(_X86_) INTERFACE TO REAL*4 FUNCTION down2_C [C,ALIAS:'_down2_C'] (n) !DEC$ ELSE INTERFACE TO REAL*4 FUNCTION down2_C [C,ALIAS:'down2_C'] (n) !DEC$ ENDIF REAL*4 n [VALUE] END real*4 down2_C down2 = down2_C(b) end ************************************ FPING3.FOR ************************************ real*4 function down3(b) real*4 b [VALUE] !DEC$ IF DEFINED(_X86_) INTERFACE TO REAL*4 FUNCTION down3_C [C,ALIAS:'_down3_C'] (n) !DEC$ ELSE INTERFACE TO REAL*4 FUNCTION down3_C [C,ALIAS:'down3_C'] (n) !DEC$ ENDIF REAL*4 n [VALUE] END real*4 down3_C down3 = down3_C(b) end ************************************ FPING4.FOR ************************************ real*4 function down4(b) real*4 b [VALUE] !DEC$ IF DEFINED(_X86_) INTERFACE TO SUBROUTINE Unlucky [C,ALIAS:'_Unlucky'] (a,c) !DEC$ ELSE INTERFACE TO SUBROUTINE Unlucky [C,ALIAS:'Unlucky'] (a,c) !DEC$ ENDIF REAL*4 a [VALUE] REAL*4 c [REFERENCE] END real*4 a call Unlucky(b,a) down4 = a end