プログラムの誤りを正すための一般的な手順は、概ね以下の通りです。
まずは、モジュール内のどこでプログラムがABENDしたのかを特定します。それから、エラーの原因を究明していきます。S0C4やU4039といったABENDコードは、ABENDさせられた直接の理由を示しますが、それ自体がABENDした原因にはなりません。例えば、S0C4は誤ったアドレスのメモリー領域にアクセスしたことを示しますが、そのような理由がわかっても問題は解決しません。プログラムのどこで不正なアドレスにアクセスしたのか?どうして誤ったアドレスを指し示してしまったのか?などをダンプ・リストを解析することで明らかにしていきます。
PSW
PSWには最後に割り込みを起こした命令の次の命令のアドレスが格納されています。S0C1やS0C4といったプログラム・チェックはプログラム割り込み、ABENDマクロによるUnnnnといったユーザーABENDはSVC割り込み、どちらも割り込みですからPSWの命令アドレス部を見ることでプログラムのどこでABENDしたかを特定することができます。多くの場合、PSWはABENDコードに続いて最初に確認される項目です。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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の内容がベース・アドレスです。
PSWと汎用レジスターの内容はJOBログ上の徴候(SYMPTOM)ダンプに表示されます。ジョブ・ログ消失などで徴候ダンプがない場合はダンプ・リスト上で確認します。PSWはダンプ・リストの冒頭に表示されます。レジスター内容は「GPR VALUES」の文字列をスキャンすれば簡単に見つかります。
一般的なプログラムでは、モジュールの先頭=オフセット=0がベース・アドレスとなるようにUSINGを指定していますから、PSWの命令アドレスからGRnnが示すベース・アドレスを差し引けばいいのですが、例外もあります。例えば、古いアプリケーション・プログラムなどの一部では、モジュールの先頭でBALR命令を使用して自分自身のベース・アドレスを求めるようなものがあります。このようなモジュールではUSINGはBALR命令の後で定義するため、ベース・アドレスはモジュールの先頭にはなりません。
1 2 3 4 5 6 7 8 9 |
----+----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 : |
命令長と割込みコード
多くの場合、PSWの命令アドレスは割込みやエラーが起きたアドレスではなく、次に実行すべき命令のアドレスです。たいていのABENDでは次の命令アドレスが示されても、ダンプやアセンブリー・リストから1つ前の命令が容易に判別できます。しかし、次の命令を探すのではなくあくまでもエラーや割込みを起こしたアドレスをはっきりさせたいという場合には、アドレスから命令長(ILC)を引くことで該当命令のアドレスを求めることができます。命令長は、徴候ダンプやダンプ・リストの冒頭部のPSWにも表示されます。
ABEND時のPSWと最終割込み時のPSW
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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は一致する)。
1 2 3 4 5 6 7 |
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 …;…; ~~~~~~~~~~~~~~~~~~ |
1 2 3 4 |
07500F60 FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 50F0D004 …;…; 07500F80 4510C068 80500FA0 0A1312FF 4770C070 58D0D004 58E0D00C 980CD014 …;…; ~~~~ 07500FA0 00000000 00000000 00000000 00000000 00000000 00000001 00004000 …;…; |
1 2 3 4 5 6 7 8 |
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 ? |
自分のプログラムの外でABENDしたことがはっきりしたからといって、自分のプログラムの問題ではないと決めつけてはいけません。この例では、SVC19つまりOSのOPEN SVCルーチンの中でABENDしていますが、OPEN SVCルーチンの問題やバグである可能性はゼロではないものの限りなくゼロに近いです。マクロ命令などでOSのサービスを要求した後にその処理の中でABENDする場合は、マクロに指定したパラメーターの設定ミスなど99.99%はマクロの書き方やパラメーターの誤りによるものです。まずは、素直に「私がOPENマクロを間違えて書いたからOSの中でABENDしてしまった」と自分のプログラムを疑います。この例では、ABENDの原因はDCBを16MB境界の上に置いていたから(RMODE=ANYのプログラム内にDCBを定義してしまった)でした。
マクロ命令のパラメーター・ミスだけでなく、パラメーターで指定した領域の初期設定ミスなども含めとにかく関連するものについて調べます。うろ覚えで使ったマクロなら改めてマニュアルで確認するなども必要です。マクロ命令のパラメーターとしてレジスター番号を指定するような場合、()で括ることを忘れたためにまったく意図しない命令展開になることがあります。
1 2 3 4 5 6 7 8 9 10 11 |
これが意図している正しい命令列 ============================== 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 |
1 2 3 4 5 6 7 8 9 10 |
レジスター番号を括弧で括り忘れて、意図しない命令列が展開された例 ================================================================ 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 |
プログラム内でのABENDなのに場所がわかりにくい例
誤ったアドレスが指定された分岐命令
OSのマクロ命令などでSVCサービスなどを呼び出したわけではなく、ABEND時のPSWも最終割込みのPSWも同じアドレスを指しているが、ABENDアドレスがプログラム内を示していないという場合があります。例えば、BALR命令やBASR命令などで指定する外部ルーチンのアドレスを誤ったような場合です。分岐命令は分岐先アドレスが誤っているからといってABENDすることはありません。実際にそのアドレスに飛んでいってから、そこが有効な仮想アドレスではないということでS0C4ABENDしたりします。この場合、PSWにはすでに飛び先の不正なアドレスが入ってしまっています。こういうケースはABEND場所が特定しにくいですが、ベース・レジスターや戻りアドレスを指定したレジスターの内容から、どのモジュールのどの分岐命令なのかを調べていきます。BALRやBASRなど戻りアドレスがレジスターに設定される命令では、その内容から見当をつけることが可能です。デバッグをしやすくするためにもプログラム・モジュールのベース・アドレスや戻りアドレスに使用するレジスター番号は統一します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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命令もしくはその次の命令を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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を起こしている : |