ダンプ・リスト解析入門 ②:ABEND箇所を特定する

プログラムの誤りを正すための一般的な手順は、概ね以下の通りです。

  • どこでABENDしたのか? →ABEND(エラーの)箇所を特定する
  • どうしてABENDしたのか?→ABEND(エラーの)原因を調べる
  • どうすればいいのか?   →プログラムを正しく修正する
  • まずは、モジュール内のどこでプログラムがABENDしたのかを特定します。それから、エラーの原因を究明していきます。S0C4やU4039といったABENDコードは、ABENDさせられた直接の理由を示しますが、それ自体がABENDした原因にはなりません。例えば、S0C4は誤ったアドレスのメモリー領域にアクセスしたことを示しますが、そのような理由がわかっても問題は解決しません。プログラムのどこで不正なアドレスにアクセスしたのか?どうして誤ったアドレスを指し示してしまったのか?などをダンプ・リストを解析することで明らかにしていきます。

    PSW

    PSWには最後に割り込みを起こした命令の次の命令のアドレスが格納されています。S0C1やS0C4といったプログラム・チェックはプログラム割り込み、ABENDマクロによるUnnnnといったユーザーABENDはSVC割り込み、どちらも割り込みですからPSWの命令アドレス部を見ることでプログラムのどこでABENDしたかを特定することができます。多くの場合、PSWはABENDコードに続いて最初に確認される項目です。

    ※PSWの命令アドレスは多くの場合、次の命令アドレスを指すがS0CxABENDの場合はプログラム割込みコードによってはABENDした命令そのものを指す場合がある。例えばS0C4ABENDの場合、仮想アドレスは正しいが記憶キーが異なるために書き込みできない場合(記憶保護例外)は命令は抑止されPSWは次の命令アドレスを指すが、仮想アドレスそのものが誤りの場合(セグメント変換例外)は命令は取消(無効化)されPSWはABENDした命令そのもののアドレスを指す。

    PSWが示す命令アドレスからプログラム・モジュールのベース・アドレスを引けば、モジュール内オフセットがわかります。オフセットはプログラム内のどこでABENDしたかを示します。オフセットはアセンブリー・リストではロケーション・カウンターとして表示されています。
    ベース・アドレスは汎用レジスターの内容から求めます。自分で作ったプログラムであれば、ABENDしたモジュールがどのレジスターをベース・レジスターにしているかはすぐわかるでしょう。例えば、GR12をベースレジスターにしているならABEND時のGR12の内容がベース・アドレスです。
    PSWと汎用レジスターの内容はJOBログ上の徴候(SYMPTOM)ダンプに表示されます。ジョブ・ログ消失などで徴候ダンプがない場合はダンプ・リスト上で確認します。PSWはダンプ・リストの冒頭に表示されます。レジスター内容は「GPR VALUES」の文字列をスキャンすれば簡単に見つかります。
    一般的なプログラムでは、モジュールの先頭=オフセット=0がベース・アドレスとなるようにUSINGを指定していますから、PSWの命令アドレスからGRnnが示すベース・アドレスを差し引けばいいのですが、例外もあります。例えば、古いアプリケーション・プログラムなどの一部では、モジュールの先頭でBALR命令を使用して自分自身のベース・アドレスを求めるようなものがあります。このようなモジュールではUSINGはBALR命令の後で定義するため、ベース・アドレスはモジュールの先頭にはなりません。

    上記のようなモジュールでは、USINGで定義したベース・アドレスがモジュールの先頭ではないため、アセンブリー・リスト上のロケーション・カウンターとベース・アドレスからの相対アドレスであるオフセットは一致しない。この例ではSAVEAREAのロケーション・カウンターはx28だが、GR12をベースにしたオフセット・アドレスはx26となる(BALR命令長が2バイトなので2ずれてしまう)

    命令長と割込みコード

    多くの場合、PSWの命令アドレスは割込みやエラーが起きたアドレスではなく、次に実行すべき命令のアドレスです。たいていのABENDでは次の命令アドレスが示されても、ダンプやアセンブリー・リストから1つ前の命令が容易に判別できます。しかし、次の命令を探すのではなくあくまでもエラーや割込みを起こしたアドレスをはっきりさせたいという場合には、アドレスから命令長(ILC)を引くことで該当命令のアドレスを求めることができます。命令長は、徴候ダンプやダンプ・リストの冒頭部のPSWにも表示されます。

    ABEND時のPSWと最終割込み時のPSW

    ABENDしたアドレスが自分のプログラムではない場合があります。この例ではS0C4でABENDしたアドレスは00EA0C76ですが、このプログラムではベース・レジスターにGR12を使用しており、そのGR12が示すベース・アドレスは07500F20です。ぱっと見ても自分のプログラムとはかけ離れた場所でABENDしています。また、徴候ダンプでもモジュール名はUNKNOWN、NO ACTIVE MODULE FOUNDとなっており、JCLのEXECステートメントで指定したプログラム・モジュールやこのプログラムからLINKやLOADマクロによってロードされたモジュールでもなさそうです。
    このような場合は、ダンプ・リストから文字列「PRB:」を探します。PRBはタスクで実行されるプログラムを管理するOSの制御表で、ジョブ・ステップで最初に実行されるプログラムもPRBによって管理されます。PRBにはこのプログラムが最後に起こした割込み時のPSWが格納されており、ダンプ上ではOPSWの見出しで表示されます。このPSWと徴候ダンプやダンプ・リストの冒頭部に表示されたPSWが違っていれば、ABENDは直接プログラム内で起きたのではなくプログラム内で起こした割込みの延長で起きたものと考えられます。そこで、PRBで示されるOPSWを見て最後に起こした割込み箇所を調べます(プログラム内でS0C1やS0C4などのプログラム・チェックが起きれば、それ自体がプログラム割込みになり、その割込み発生アドレスがABENDアドレスとなるためPRB内の最終割込みPSWとABEND時のPSWは一致する)。

    PRBのOPSWが示す最終割込みアドレスは7500F8Aで、ベース・アドレス7500F20からプログラム・モジュール内のオフセットはx6Aであることがわかる。また、命令長は2バイト、割込みコードはx13と示されている。したがって、割込みはオフセットx68(x6A-2)で起きたことがわかる。

    割込みを起こした命令はx0A(SVC命令)、つまりSVC19を発行したことによるSVC割込みであることがわかる。したがって、このS0C4ABENDはプログラムが発行したSVC19命令によって実行されたSVCルーチンの延長で起きたものと考えることができる。

    アセンブリー・リストを見れば、SVC19はOPENマクロの展開命令列の中で発行されていることがわかる。PRINT NOGEN指定のアセンブル・リストがデバッグの役に立たないのは、こういう場合にリスト上で展開された命令列が確認できないからである。

    自分のプログラムの外でABENDしたことがはっきりしたからといって、自分のプログラムの問題ではないと決めつけてはいけません。この例では、SVC19つまりOSのOPEN SVCルーチンの中でABENDしていますが、OPEN SVCルーチンの問題やバグである可能性はゼロではないものの限りなくゼロに近いです。マクロ命令などでOSのサービスを要求した後にその処理の中でABENDする場合は、マクロに指定したパラメーターの設定ミスなど99.99%はマクロの書き方やパラメーターの誤りによるものです。まずは、素直に「私がOPENマクロを間違えて書いたからOSの中でABENDしてしまった」と自分のプログラムを疑います。この例では、ABENDの原因はDCBを16MB境界の上に置いていたから(RMODE=ANYのプログラム内にDCBを定義してしまった)でした。
    マクロ命令のパラメーター・ミスだけでなく、パラメーターで指定した領域の初期設定ミスなども含めとにかく関連するものについて調べます。うろ覚えで使ったマクロなら改めてマニュアルで確認するなども必要です。マクロ命令のパラメーターとしてレジスター番号を指定するような場合、()で括ることを忘れたためにまったく意図しない命令展開になることがあります。

    レジスター番号を正しく括弧で括っているので、レジスターの内容がパラメーター・リスト内にストアーされている。

    レジスター番号を括弧でくくり忘れたので、レジスターの内容がパラメーター・リスト内にストアーされるのではなく、アドレス2番地がパラメーター・リスト内に設定されてしまった。元々複数のサブ・パラメーターを括弧で括るようなパラメーターは、一見ではミスに気づきにくい。

    プログラム内でのABENDなのに場所がわかりにくい例

    誤ったアドレスが指定された分岐命令

    OSのマクロ命令などでSVCサービスなどを呼び出したわけではなく、ABEND時のPSWも最終割込みのPSWも同じアドレスを指しているが、ABENDアドレスがプログラム内を示していないという場合があります。例えば、BALR命令やBASR命令などで指定する外部ルーチンのアドレスを誤ったような場合です。分岐命令は分岐先アドレスが誤っているからといってABENDすることはありません。実際にそのアドレスに飛んでいってから、そこが有効な仮想アドレスではないということでS0C4ABENDしたりします。この場合、PSWにはすでに飛び先の不正なアドレスが入ってしまっています。こういうケースはABEND場所が特定しにくいですが、ベース・レジスターや戻りアドレスを指定したレジスターの内容から、どのモジュールのどの分岐命令なのかを調べていきます。BALRやBASRなど戻りアドレスがレジスターに設定される命令では、その内容から見当をつけることが可能です。デバッグをしやすくするためにもプログラム・モジュールのベース・アドレスや戻りアドレスに使用するレジスター番号は統一します。

    ~~~~~部分がプログラムのローディングされた近辺のアドレス。ベース・アドレスだけではなく、近辺のアドレスからどのあたりの命令まで処理されていたかを調べる。

    戻りアドレスが格納されないB、BH、BNEといった単純な分岐命令では飛び先アドレスを誤るとどの命令かを特定するのはかなり大変です。しかし、これらの命令では分岐先の指定にレジスターを使うといったことは少なく、たいていはラベル名で分岐先を指定します。BALRやBASRといった外部ルーチン呼び出しに使う命令のようにプログラム内で誤った分岐先アドレスを指定するといったことは少ないでしょう。

    EXECUTE命令

    EXECUTE命令でABENDした場合は注意が必要です。EXECUTE命令自身がABENDしても、EXECUTE命令によって実行した命令がABENDしても、PSWはEXECUTE命令を示します。EXECUTE命令自身がABENDするのは、EXECUTE命令で奇数アドレスを指定するとかEXECUTEで別のEXECUTE命令を示すなど例外的なものですが、EXECUTE命令が参照する命令でS0C4ABENDするなどはよくあることです。そのような場合でもPSWは直接S0C4を起こした命令ではなく、EXECUTE命令もしくはその次の命令を示します。