サンプルプログラムとトレースバック情報

このセクションでは、トレースバックを使用してエラーの原因を特定する方法を示すサンプルプログラムについて説明します。

これらのプログラム出力で表示される 16 進の PC とレジスターの内容は、一般的な出力の代表例です。PC は、実行ファイルの作成に使用したライブラリーとほかのツールの変更に伴って変更されます。

例: ファイル終了条件 (プログラム teof)

次の 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

例: マシン例外条件 (プログラム ovf)

次のプログラムは、-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

このプログラムは、以下のオプションを指定してコンパイルされると仮定しています。

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 および CPONG)

次の例では、Fortran と C が混在したアプリケーションでトレースバック出力を使用する方法を示します。メインプログラムは FPING という名前の Fortran プログラムです。プログラム FPING は、一連の関数呼び出しで、Fortran と C のコードを交互に呼び出します。そして、Unlucky という名前の C のルーチンが呼び出され、ゼロによる浮動小数点除算エラーが発生します。

ソースモジュール FPING.FOR には、それぞれソースモジュール CPONG.C の C ルーチンを呼び出す Fortran 関数定義が含まれています。プログラム FPING.FOR は、以下のオプションを指定してコンパイルされます。

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