Oracle® Solaris Studio 12.4: 数値計算ガイド

印刷ビューの終了

更新: 2015 年 1 月
 
 

4.4.1 デバッガを使用して例外を特定する

このセクションでは、 dbx を使用して、浮動小数点例外の原因を調査し、例外が発生した命令を特定する方法の例を示します。dbx のソースレベルのデバッグ機能を使用するには、プログラムを –g フラグを指定してコンパイルする必要があります。詳細は、Oracle Solaris Studio 12.4: dbx コマンドによるデバッグ を参照してください。

次の C プログラムについて考察します。

#include <stdio.h>
#include <math.h>

double sqrtm1(double x)
{
   return sqrt(x) - 1.0;
}

int main(void)
{
   double x, y;

   x = -4.2;
   y = sqrtm1(x);
   printf("%g  %g\n", x, y);
   return 0;
}

このプログラムをコンパイルして実行すると、次のように出力されます。

-4.2  NaN

出力に NaN が表示されているのは、無効な演算例外が発生した可能性があることを示しています。それを判別するには、–ftrap オプションを指定して再コンパイルすることによって無効な演算のトラップを有効化し、dbx を使用してプログラムを実行して、SIGFPE シグナルが送信されたときに停止します。または、無効な演算のトラップを有効にする起動ルーチンとリンクするか、手動でトラップを有効にすると、プログラムを再コンパイルすることなく dbx を使用できます。

4.4.1.1 dbx を使用して、例外の原因となっている命令を特定する

浮動小数点例外の原因であるコードを特定するためのもっとも簡単な方法は、–g フラグおよび –ftrap フラグを指定して再コンパイルしてから、dbx を使用して例外が発生している場所を追跡することです。まず、プログラムを次のように再コンパイルします。

example% cc -g -ftrap=invalid ex.c -lm

–g を指定してコンパイルすると、dbx のソースレベルのデバッグ機能を使用できるようになります。–ftrap=invalid を指定すると、無効な演算例外のトラップを有効にしてプログラムが実行されます。次に、dbx を起動して、SIGFPE が発生したときに停止するように catch fpe コマンドを発行し、プログラムを実行します。SPARC ベースのシステムでは、結果は次のようになります。

example% dbx a.out
Reading a.out
Reading ld.so.1
Reading libm.so.2
Reading libc.so.1
(dbx) catch fpe
(dbx) run
Running: a.out 
(process id 2773)
signal FPE (invalid floating point operation) in __sqrt at 0x7fa9839c
0x7fa9839c: __sqrt+0x005c:      srlx     %o1, 63, %l5
Current function is sqrtm1
    5   return sqrt(x) - 1.0;
(dbx) print x
x = -4.2
(dbx)

この出力は、負数の平方根を求めようとした結果、sqrtm1 関数で例外が発生したことを示しています。

dbx を使用すると、ライブラリルーチンなどの –g を指定してコンパイルされていないコードで例外の原因を識別することもできます。この場合、dbx はソースファイルおよび行番号を返すことはできませんが、例外が発生した命令を示すことができます。ここでも、最初の手順では –ftrap を指定して主プログラムを再コンパイルします。

example% cc -ftrap=invalid ex.c -lm

dbx を起動して、catch fpe コマンドを使用し、プログラムを実行します。無効な演算例外が発生すると、dbx は例外の原因となった命令の次の命令で停止します。例外の原因となった命令を特定するには、いくつかの命令を逆アセンブルし、dbx が停止した命令より前にある最後の浮動小数点命令を探します。SPARC ベースのシステムでは、結果は次の出力のようになります。

example% dbx a.out
Reading a.out
Reading ld.so.1
Reading libm.so.2
Reading libc.so.1
(dbx) catch fpe
(dbx) run
Running: a.out 
(process id 2931)
signal FPE (invalid floating point operation) in __sqrt at 0x7fa9839c
0x7fa9839c: __sqrt+0x005c:      srlx     %o1, 63, %l5
(dbx) dis __sqrt+0x50/4
dbx: warning: unknown language, 'c' assumed
0x7fa98390: __sqrt+0x0050:      neg      %o4, %o1
0x7fa98394: __sqrt+0x0054:      srlx     %o2, 63, %l6
0x7fa98398: __sqrt+0x0058:      fsqrtd   %f0, %f2
0x7fa9839c: __sqrt+0x005c:      srlx     %o1, 63, %l5
(dbx) print $f0f1 
$f0f1 = -4.2
(dbx) print $f2f3
$f2f3 = -NaN.0
(dbx) 

この出力は、例外の原因が fsqrtd であったことを示しています。ソースレジスタを検査すると、負数の平方根を求めようとしたためにこの例外が発生したことがわかります。

x86 ベースのシステムでは、命令が固定長ではないため、コードの逆アセンブルを開始する正しいアドレスを見つけるには試行錯誤が必要になることがあります。この例では、関数の先頭付近で例外が発生しているので、ここから逆アセンブルできます。この出力は、–xlibmil フラグを指定してプログラムがコンパイルされていることを想定しています。一般的な結果は次の出力のようになります。

example% dbx a.out
Reading a.out
Reading ld.so.1
Reading libc.so.1
(dbx) catch fpe
(dbx) run
Running: a.out 
(process id 18566)
signal FPE (invalid floating point operation) in sqrtm1 at 0x80509ab
0x080509ab: sqrtm1+0x001b:      fstpl    0xffffffe0(%ebp)
(dbx) dis sqrtm1+0x16/5
dbx: warning: unknown language, 'c' assumed
0x080509a6: sqrtm1+0x0016:      fsqrt    
0x080509a8: sqrtm1+0x0018:      addl     $0x00000008,%esp
0x080509ab: sqrtm1+0x001b:      fstpl    0xffffffe0(%ebp)
0x080509ae: sqrtm1+0x001e:      fwait    
0x080509af: sqrtm1+0x001f:      movsd    0xffffffe0(%ebp),%xmm0
(dbx) print $st0
$st0 = -4.20000000000000017763568394002504647e+00
(dbx) 

この出力は、例外の原因が fsqrt 命令であったことを示しています。浮動小数点レジスタを調べると、負数の平方根を求めようとしたためにこの例外が発生したことがわかります。

4.4.1.2 再コンパイルせずにトラップを有効にする

上記の例では、–ftrap フラグを指定して主サブプログラムを再コンパイルすることによって、無効な演算例外のトラップを有効にしました。場合によっては、主プログラムの再コンパイルを行うことができず、ほかの方法でトラップを有効にする必要があることがあります。これを行うにはいくつかの方法があります。

dbx を使用しているときに、浮動小数点ステータスレジスタを直接変更することによって、トラップを手動で有効にできます。オペレーティングシステムはプログラム内で浮動小数点ユニットが最初に使用されるまで浮動小数点ユニットを使用可能にせず、その時点で浮動小数点の状態が初期化され、すべてのトラップが無効になるため、これには注多少の注意が必要です。したがって、プログラムが少なくとも 1 つの浮動小数点命令を実行するまでは、トラップを手動で有効にできません。この例では、sqrtm1 関数が呼び出されるまでに浮動小数点ユニットはアクセスされているので、この関数への入口にブレークポイントを設定して、無効な演算例外のトラップを有効化し、SIGFPE シグナルの受信時に停止するように dbx に指示して、実行を継続できます。SPARC ベースのシステムでの手順は次のようになります。無効な演算例外のトラップを有効にするために、assign コマンドを使用して %fsr を変更しています。

example% dbx a.out
Reading a.out
... etc.
(dbx) stop in sqrtm1
dbx: warning: 'sqrtm1' has no debugger info -- will trigger on first instruction
(2) stop in sqrtm1
(dbx) run
Running: a.out 
(process id 23086)
stopped in sqrtm1 at 0x106d8
0x000106d8: sqrtm1       :      save    %sp, -0x70, %sp
(dbx) assign $fsr=0x08000000
dbx: warning: unknown language, 'c' assumed
(dbx) catch fpe
(dbx) cont
signal FPE (invalid floating point operation) in __sqrt at 0xff36b3c4
0xff36b3c4: __sqrt+0x003c:      be      __sqrt+0x98
(dbx) 

x86 ベースのシステムでは、同じ手順は次のようになります。

example% dbx a.out
Reading a.out
... etc.
(dbx) stop in sqrtm1 
dbx: warning: 'sqrtm1' has no debugger info -- will trigger on first instruction
(2) stop in sqrtm1
(dbx) run    
Running: a.out 
(process id 25055)
stopped in sqrtm1 at 0x80506b0
0x080506b0: sqrtm1     :        pushl  %ebp
(dbx) assign $fctrl=0x137e
dbx: warning: unknown language, 'c' assumed
(dbx) catch fpe
(dbx) cont 
signal FPE (invalid floating point operation) in sqrtm1 at 0x8050696
0x08050696: sqrtm1+0x0016:      fstpl  -16(%ebp)
(dbx)

上記の例では、assign コマンドによって、浮動小数点制御ワード内の無効な演算例外がアンマスクされています (つまり、トラップを有効にしています)。プログラムで SSE2 命令を使用している場合は、MXCSR レジスタの例外をアンマスクして、それらの命令によって発生した例外のトラップを有効にする必要があります。

トラップを有効にする初期化ルーチンを設定すると、主プログラムを再コンパイルしたり、dbx を使用したりすることなくトラップを有効にできます。この方法は、デバッガで実行せずに、例外が発生したときにプログラムを中止する場合などに便利です。このようなルーチンを作成する方法は 2 つあります。

プログラムを構成するオブジェクトファイルとライブラリが使用可能な場合は、プログラムを適切な初期化ルーチンに再リンクすることによってトラップを有効にできます。最初に、次のような C のソースファイルを作成します。

#include <ieeefp.h>
 
#pragma init (trapinvalid)
 
void trapinvalid()
{
     /* FP_X_INV et al are defined in ieeefp.h */
     fpsetmask(FP_X_INV);
}

このファイルをコンパイルしてオブジェクトファイルを作成し、元のプログラムをこのオブジェクトファイルにリンクします。

example% cc -c init.c
example% cc ex.o init.o -lm
example% a.out
Arithmetic Exception

再リンクを行うことはできないが、プログラムが動的にリンクされている場合は、実行時リンカーの、共有オブジェクトを事前ロードする機能を使用するとトラップを有効にすることができます。SPARC ベースのシステムでこれを行うには、上記と同じ C ソースファイルを作成して、次のようにコンパイルします。

example% cc -Kpic -G -ztext init.c -o init.so -lc

トラップを有効にするには、init.so オブジェクトのパス名を、環境変数 LD_PRELOAD によって指定される事前ロードする共有オブジェクトのリストに追加します。

example% env LD_PRELOAD=./init.so a.out
Arithmetic Exception

共有オブジェクトの作成および事前ロードについては、Oracle Solaris 11.2 リンカーとライブラリガイド を参照してください。

前述したように、浮動小数点の制御モードの初期化方法は、共有オブジェクトを事前ロードすることによって原則として変更できます。ただし、共有オブジェクト内の初期化ルーチンは、事前ロードされる場合も明示的にリンクされる場合も、メインの実行可能ファイルの一部である起動コードに制御を渡す前に、実行時リンカーによって実行されます。起動コードは、–ftrap–fround–fns (SPARC)、または –fprecision (x86) コンパイラフラグを介して選択される非デフォルトモードを設定し、メインの実行可能ファイルの一部である初期化ルーチン (静的にリンクされているものを含む) を実行して、最後に制御を主プログラムに渡します。このため、SPARC では次のことに留意してください。

  • 上記の例で有効にされたトラップのような、共有オブジェクト内の初期化ルーチンによって設定される浮動小数点制御モードはすべて、オーバーライドされないかぎりプログラムの実行中に有効な状態のままになります。

  • コンパイラフラグを介して選択された非デフォルトモードは、共有オブジェクト内の初期化ルーチンによって設定されるモードを無効にします (ただし、コンパイラフラグを介して選択されたデフォルトモードは、以前に設定されているモードを無効にしません)。

  • メインの実行可能ファイルの一部である初期化ルーチン、または主プログラム自体によって設定されるモードは、両方とも無効にします。

x86 ベースのシステムでは状況はもう少し複雑です。通常、コンパイラによって自動的に提供される起動コードは、__fpstart ルーチン (標準 C ライブラリ libc にあります) を呼び出すことによってすべての浮動小数点モードをデフォルトにリセットしてから、–fround–ftrap、または –fprecision フラグによって選択された非デフォルトモードを設定して、主プログラムに制御を渡します。そのため、初期化ルーチンを持つ共有オブジェクトを事前ロードすることによって x86 ベースのシステムでトラップを有効にしたり、ほかのデフォルトの浮動小数点モードを変更したりするには、デフォルトの浮動小数点モードを __fpstart ルーチンがリセットしないように、このルーチンを無効にする必要があります。ただし、代替の __fpstart ルーチンは、標準のルーチンが行う初期化機能の残りの部分を実行する必要があります。次のコードは、これを行うための 1 つの方法を示しています。このコードは、ホスト プラットフォームで Oracle Solaris 10 OS 以降のリリースが実行されていることを想定しています。

#include <ieeefp.h>
#include <sys/sysi86.h>
 
#pragma init (trapinvalid)
 
void trapinvalid()
{
     /* FP_X_INV et al are defined in ieeefp.h */
     fpsetmask(FP_X_INV);
}
 
extern int  __fltrounds(), __flt_rounds;
extern int  _fp_hw, _sse_hw;
 
void __fpstart()
{
    /* perform the same floating point initializations as
       the standard __fpstart() function but leave all
       floating point modes as is */
    __flt_rounds = __fltrounds();
    (void) sysi86(SI86FPHW, &_fp_hw);
 
    /* set the following variable to 0 instead if the host
       platform does not support SSE2 instructions */
    _sse_hw = 1;
}