ダンプリスト解析入門②
ダンプリスト解析入門②: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=078D0000 80007FEA アドレスx7FEAがABENDした次の命令 (ABENDしたのはこの1つ前の命令) bit 0 8 16 24 32 63 +----+----+----+----+-------------+ I 07 I 8D I 00 I 00 I 80 00 7F EA I +----+-++-+----+----+-++----------+ II II II I+-> 命令アドレス(bit33-63) II +-> アドレスモード(bit32が1なら31ビットモード) II I+-> 奇数なら問題プログラム状態(通常はD:正確にはbit15が1かどうか) +-> PSWキー(一般のアプリケーション・プログラムでは8となる)
PSWが示す命令アドレスから、プログラム・モジュールのベースアドレスを引けば、モジュール内オフセットがわかります。オフセットはプログラム内のどこでABENDしたかを示します。オフセットはアセンブル・リストではロケーション・カウンターとして表示されています。
ベースアドレスは汎用レジスターの内容から求めます。自分で作ったプログラムであれば、ABENDしたモジュールがどのレジスターをベースレジスターにしているかはすぐわかるでしょう。例えばGR12をベースレジスターにしているなら、ABEND時のGR12の内容がベースアドレスとなります。
MVSであれば、PSWと汎用レジスターの内容はJOBログ上の徴候(SYMPTOM)ダンプに表示されます。MSPやVOS3で徴候ダンプがない場合は、ダンプリスト上で確認します。PSWは、ダンプリストの冒頭に表示されます。レジスター内容は、「REGS AT ENTRY TO ABEND」の文字列をスキャンすれば簡単に見つかります。(MVSのダンプでは、「REGS AT ENTRY TO ABEND」または「GPR VALUES」の文字列をスキャンする)
一般的なプログラムでは、モジュールの先頭=オフセット=0がベースアドレスとなるようにUSINGを指定していますから、PSWの命令アドレスからGRnnが示すベースアドレスを差し引けばいいのですが、例外もあります。例えば一部のOSモジュールなど、MVSの標準リンケージ規約が使われず、GR15が入口点アドレスを指さないようなモジュールでは、モジュールの先頭でBALR命令を使用し自分自身ベースアドレスを求めるようなものがあります。このようなモジュールではUSINGはBALR命令の後で定義するため、ベースアドレスはモジュールの先頭にはなりません。
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- 000000 MODULE1 CSECT , 000000 BALR 12,0 LOAD OUR BASE ADDRESS USING *,12 ADDRESS IT 000002 STM 0,1,SAVEAREA SAVE GR0 AND GR1 000006 : : 000028 SAVEAREA DC 2F'0' TEMP SAVEAREA : このようなモジュールでは、USINGで定義したベースアドレスがモジュールの先頭では ないため、アセンブル・リスト上のロケーション・カウンターとベースアドレスからの 相対アドレスであるオフセットは一致しない。 この例ではSAVEAREAのロケーション・カウンターはx28だが、GR12をベースにした オフセット・アドレスはx26となる。(BALR命令長が2バイトなので2ずれてしまう)
命令長と割込みコード
多くの場合、PSWの命令アドレスは、割込みやエラーが起きたアドレスではなく、次に実行すべき命令のアドレスです。たいていのABENDでは次の命令アドレスが示されても、ダンプやリストから1つ前の命令が容易に判別できます。しかし次の命令を探すのではなく、あくまでもエラーや割込みを起こしたアドレスをはっきりさせたい、という場合には、アドレスから命令長(ILC)を引くことで該当命令のアドレスを求めることができます。命令長は、徴候ダンプやダンプリストの冒頭部のPSWにも表示されます。
ABEND時のPSWと最終割込み時のPSW
SYSTEM COMPLETION CODE=0C4 REASON CODE=00000010 TIME=15.18.06 SEQ=00025 CPU=0000 ASID=0020 PSW AT TIME OF ERROR 078C0000 00EA0C76 ILC 4 INTC 10 NO ACTIVE MODULE FOUND NAME=UNKNOWN DATA AT PSW 00EA0C70 - 1B224111 0000BF27 10014770 GR 0: FD000008 1: 00500F84 2: 00000000 3: 00EA030A 4: 009D0E88 5: 009FDB90 6: 87500F84 7: 00F99880 8: 00000000 9: 80FFF01C A: 009FDC50 B: 009D0E88 C: 07500F20 D: 87500F30 E: 90EA0350 F: 009D0E88 END OF SYMPTOM DUMP
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: 009EC9B8 -0020 XSB...... 7FFFEF08 FLAGS2... 00 RTPSW1... 00000000 …… -000C 00000000 FLAGS1... 00000000 WLIC..... 00020013 ~~~~~~~~ +0000 RSV...... 00000000 00000000 SZSTAB... 00110082 …… +0010 OPSW..... 078D0000 87500F8A SQE...... 00000000 …… ~~~~~~~~~~~~~~~~~~ PRBのOPSWが示す最終割込みアドレスは7500F8Aで、ベースアドレス7500F20から プログラム・モジュール内のオフセットx6Aであることがわかる。 また、命令長は2バイト、割込みコードはx13と示されている。 したがって割込みはオフセットx68(x6A-2)で起きたことがわかる。 07500F60 FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 50F0D004 …… 07500F80 4510C068 80500FA0 0A1312FF 4770C070 58D0D004 58E0D00C 980CD014 …… ~~~~ 07500FA0 00000000 00000000 00000000 00000000 00000000 00000001 00004000 …… 割込みを起こした命令は0A(SVC)、つまりSVC19を発行したことによるSVC割込みで あることがわかる。したがってこのS0C4ABENDは、プログラムが発行したSVC19命令 によって実行されたSVCルーチンの延長で起きたものと考えることができる。 30 OPEN (SYSUT1,INPUT) OPEN INPUT DATASET 000060 31+ CNOP 0,4 ALIGN LIST TO 000060 4510 C068 32+ BAL 1,*+8 LOAD REG1 W/LIST 000064 80 33+ DC AL1(128) OPTION BYTE 000065 000080 34+ DC AL3(SYSUT1) DCB ADDRESS 000068 0A13 35+ SVC 19 ISSUE OPEN SVC ~~~~~~~~~~~ ~~~~~~~~ 00006A 12FF 36 LTR RF,RF SUCCESSFUL ? アセンブル・リストを見れば、SVC19はOPENマクロの展開命令列の中で発行されて いることがわかる。PRINT NOGEN指定のアセンブル・リストがデバッグの役に立た ないのは、こういう場合にリスト上で展開された命令列が確認できないからである。
自分のプログラムの外でABENDしたことがはっきりしたからといって、自分のプログラムの問題ではないと決めつけてはいけません。この例では、SVC19つまりOSのOPEN SVCルーチンの中でABENDしていますが、OPEN SVCルーチンの問題やバグである可能性は0ではないものの、限りなく0に近いです。マクロ命令などでOSのサービス(API)を要求して、その処理の中でABENDするのは、マクロに指定したパラメーターの設定ミスなど、99.99%はマクロの書き方やパラメーターの誤りによるものです。まずは素直に、私がOPENマクロを間違えて書いたからOSの中でABENDしてしまった、と自分のプログラムを疑います。この例では、ABENDの原因はDCBを16MB境界の上に置いていたから(RMODE=ANYのプログラム内にDCBを定義してしまった)でした。
マクロ命令のパラメーター・ミスだけでなく、パラメーターで指定した領域の初期設定ミスなど、とにかく関連するものについて調べます。うろ覚えで使ったマクロなら改めてマニュアルで確認するなども必要です。マクロ命令のパラメーターとしてレジスター番号を指定するような場合、()で括ることを忘れたためにまったく意図しない命令展開になることがあります。
これが意図している正しい命令列 ============================== 000060 4120 C08C 31 LA R2,SYSUT1 LOAD DCB ADDRESS 32 OPEN ((2),INPUT) OPEN INPUT DATASET 000064 33+ CNOP 0,4 ALIGN LIST TO 000064 4510 C06C 34+ BAL 1,*+8 LOAD REG1 W/LI 000068 00000000 35+ DC A(0) OPT BYTE AND D 00006C 5021 0000 36+ ST 2,0(1,0) STORE INTO LIS ~~~~~~~~~ ~~~~~~~~~~~~~~ 000070 9280 1000 37+ MVI 0(1),128 MOVE IN OPTION 000074 0A13 38+ SVC 19 ISSUE OPEN SVC レジスター番号を正しく括弧で括っているので、レジスターの内容がパラメーター・ リスト内にストアーされている。 レジスター番号を括弧で括り忘れて、意図しない命令列が展開された例 ================================================================ 000060 4120 C084 31 LA R2,SYSUT1 LOAD DCB ADDRESS 32 OPEN (2,INPUT) OPEN INPUT DATASET 000064 33+ CNOP 0,4 ALIGN LIST TO 000064 4510 C06C 34+ BAL 1,*+8 LOAD REG1 W/LI 000068 80 35+ DC AL1(128) OPTION BYTE 000069 000002 36+ DC AL3(2) DCB ADDRESS ~~~~~~ ~~~~~~~~~~~~ 00006C 0A13 37+ SVC 19 ISSUE OPEN SVC レジスター番号を括弧でくくり忘れたので、レジスターの内容がパラメーター・ リスト内にストアーされるのではなく、アドレス2番地がパラメーター・リスト内 に設定されてしまった。 元々複数のサブ・パラメーターを括弧で括るようなパラメーターでは、 一見ではミスに気づきにくい。
プログラム内でのABENDなのに場所がわかりにくい例
誤ったアドレスが指定された分岐命令
OSのマクロ命令などでSVCサービスなどを呼び出したわけではなく、ABEND時のPSWも最終割込みのPSWも同じアドレスを指しているが、ABENDアドレスがプログラム内を示していない、という場合があります。例えば、BALR命令やBASR命令などで指定する外部ルーチンのアドレスを誤ったような場合です。分岐命令は分岐先アドレスが誤っているからといってABENDすることはありません。実際にそのアドレスに飛んでいってから、そこが有効な仮想アドレスでない、ということでS0C4ABENDしたりします。この場合、PSWにはすでに飛び先の不正なアドレスが入ってしまっています。こういうケースはABEND場所が特定しにくいですが、ベースレジスターや戻りアドレスを指定したレジスターの内容から、どのモジュールのどの分岐命令なのかを調べていきます。BALRやBASRなど戻りアドレスがレジスターに設定される命令では、その内容からあたりをつけることが可能です。デバッグをしやすくするためにもプログラム・モジュールのベースアドレスや戻りアドレスに使用するレジスター番号は統一します。
SYSTEM COMPLETION CODE=0C4 REASON CODE=00000010 TIME=15.56.39 SEQ=00032 CPU=0000 ASID=0020 PSW AT TIME OF ERROR 078D0000 876FF758 ILC 4 INTC 10 NO ACTIVE MODULE FOUND NAME=UNKNOWN DATA AT PSW IS UNAVAILABLE AT THIS TIME GR 0: FD000008 1: 00006FF8 2: 00000040 3: 009D29D4 4: 009D29B0 5: 009EC818 6: 009C1FE0 7: FD000000 8: 009EC600 9: 009ECAD8 A: 00000000 B: 009EC818 C: 00007F10 D: 80007F20 ~~~~~~~~~~~~~~~~~~~~~~~~~ E: 80007F76 F: 076FF758 ~~~~~~~~~~~ END OF SYMPTOM DUMP ~~~~~部分がプログラムのローディングされた近辺のアドレス。 ベースアドレスだけでなく、近辺のアドレスからどのあたりの 命令まで処理されていたかを調べる。
戻りアドレスが格納されない、B、BH、BNEといった単純な分岐命令では飛び先アドレスを誤ると、どの命令かを特定するのはかなり大変です。しかしこれらの命令では分岐先の指定にレジスターを使うといったことは少なく、たいていはラベル名で分岐先を指定します。したがってBALRやBASRといった外部ルーチン呼び出しに使う命令のようにプログラム内で誤った分岐先アドレスを指定する、といったことは少ないでしょう。
EXECUTE命令
EXECUTE命令でABENDした場合は注意が必要です。EXECUTE命令自身がABENDしてもEXECUTE命令によって実行した命令がABENDしても、PSWはEXECUTE命令を示します。EXECUTE命令自身がABENDするのは、EXECUTE命令で奇数アドレスを指定するとかEXECUTEで別のEXECUTE命令を示すなど、例外的なものですが、EXECUTE命令が参照する命令でS0C4ABENDするなどはよくあることです。そのような場合でもPSWは直接S0C4を起こした命令ではなく、EXECUTE命令もしくはその次の命令を示します。
SYSTEM COMPLETION CODE=0C4 REASON CODE=00000004 TIME=17.37.54 SEQ=00061 CPU=0000 ASID=0020 PSW AT TIME OF ERROR 078D0000 80007F2E ILC 4 INTC 04 ACTIVE LOAD MODULE ADDRESS=00007EC0 OFFSET=0000006E NAME=TEMPNAM0 DATA AT PSW 00007F28 - 06F044F0 C08658D0 D00458E0 GR 0: FD000008 1: 00000000 2: 00000040 3: 009D29D4 4: 009D29B0 5: 009EC818 6: 009C1FE0 7: FD000000 8: 009EC600 9: 009ECAD8 A: 00000000 B: 009EC818 C: 00007EC0 D: 80007ED0 E: 80FC8308 F: 00000009 END OF SYMPTOM DUMP : : 000064 41F0 000A 33 LA RF,10 000068 06F0 34 BCTR RF,0 00006A 44F0 C086 35 EX RF,DOMOVE <== PSWが示すのはここ 00006E 58D0 D004 37 L RD,4(,RD) : 000086 D200 1000 C09C 48 DOMOVE MVC 0(0,R1),AREAB <== 実際はこの命令が : S0C4を起こしている