【入門編】CEE3201S(ゼロ除算)のダンプ解析とPSWの特定 – PL/Iの基本構文とデータ制御実践ガイド

こんにちは!IBMメインフレームの世界へようこそ。
JavaやCOBOLといったモダン、あるいはビジネスで主流の言語をバリバリ書いてきた方にとって、レガシーの代表格である「PL/I(ピーエルワン)」や、画面いっぱいに広がる文字と数字の羅列――いわゆる「システムダンプ」は、ちょっと近寄りがたいモンスターのように見えるかもしれません。

特に、夜間バッチの実行中に突然ジョブが異常終了し、Sysout(ログ出力先)に吐き出された「CEE3201S THE SYSTEM ISSUED AN ABEND AND THE PROBLEM WAS IDENTIFIED IN…」なんていうメッセージを見た日には、冷や汗が止まらなくなりますよね。

でも、安心してください。怖がる必要はまったくありません。
今回は、PL/Iプログラムで最も遭遇しやすいエラーの一つである「ゼロ除算(割り算の分母がゼロになってしまった状態)」をテーマに、ダンプから犯人(異常発生箇所)を特定する手順を、優しく紐解いていきましょう。

1. なぜPL/Iには「予約語」がないのか?(ちょっとした前提知識)

本題に入る前に、JavaやCOBOL経験者が最初に戸惑う、PL/Iのちょっと変わった性格についてお話しさせてください。

多くのプログラミング言語には、「if」や「while」、「display」といった予約語(キーワード)が存在し、これらを変数名として使うことはできませんよね。しかし、PL/Iの大きな特徴(そして初心者を少し混乱させる原因)として、「PL/Iには厳密な意味での予約語が存在しない」という仕様があります。

どういうことかと言うと、極端な話、`IF`という名前の変数を作ることさえできてしまうのです(コンパイラは前後の文脈で「これは命令語だな」「ここは変数だな」と空気を読んで判断しています)。

この設計思想の裏返しとして、PL/Iの構文は非常に自由度が高く、データ宣言や計算式も独特のノリで記述されます。例えば、後述するゼロ除算を引き起こすようなコードも、一見すると何気ない四則演算に見えてしまいます。だからこそ、エラーが起きたときに「どこで何が起きたのか」を正確に突き止めるダンプ解析のスキルが、私たちエンジニアの強力な武器になるのです。

2. 犯人はコイツだ!CEE3201S(ゼロ除算)の正体

夜間バッチが突然落ちて、ジョブの実行結果(JESログ)を確認すると、以下のようなメッセージが鎮座しています。

CEE3201S THE SYSTEM ISSUED AN ABEND AND THE PROBLEM WAS IDENTIFIED IN…
…OFFSET +0000A2C6

この `CEE3201S` こが、Language Environment(LE)環境下で発生した例外(この場合はシステムABEND、コードとしてはS0C7やS0C9などの算術例外)を検知したよ、という合図です。

Javaで言えば `java.lang.ArithmeticException: / by zero` のようなものですが、メインフレームの世界では、スタックトレースが親切に出ない古い構成や最適化されたロードモジュールに直面することが多々あります。そこで頼りになるのが「ダンプ(CEEDUMP)」です。

ゼロ除算を引き起こすPL/Iコードのイメージ

まずは、どんなコードがこのエラーを招くのか、実務を模したシンプルなPL/Iプログラムを見てみましょう。大文字で書かれた重厚な雰囲気に怯む必要はありません。

1
DIV_TEST: PROC OPTIONS(MAIN);

/ 変数の宣言:固定小数点数(DECIMAL FIXED)として定義 /
DCL TOTAL_SALES DEC FIXED(9,2) INIT(50000.00);
DCL MEMBER_COUNT DEC FIXED(5,0) INIT(0); / ← ここがゼロ! /
DCL AVERAGE_SAL DEC FIXED(9,2);

ON ERROR BEGIN;
DISPLAY(‘ 算術エラーを検知しました ‘);
GOTO ERROR_RTN;
END;

/ ゼロによる割り算の実行 /
AVERAGE_SAL = TOTAL_SALES / MEMBER_COUNT;

DISPLAY(‘平均売上: ‘ || AVERAGE_SAL);
RETURN;

ERROR_RTN:
DISPLAY(‘異常終了回避ルートを通りました。’);

END DIV_TEST;

`MEMBER_COUNT`(会員数)の初期値が誤って `0` になっており、その状態で割り算を行っているため、当然ゼロ除算が発生します。上記のコードでは `ON ERROR`(例外処理)を書いていますが、もしこれが未定義であれば、容赦なくジョブは異常終了し、CEEDUMPが生成されます。

3. ダンプからPSWを特定し、異常箇所を暴く手順

さて、ここからが本番です。CEEDUMPが出力されたとき、私たちはどこを見るべきなのでしょうか?
「難解な16進数の海」に見えますが、見るべきポイントは実は一本道です。

ステップ1:CEEDUMPから「PSW」を見つけ出す

ダンプリストを開き、「PSW at entry to abend」(または類似の文言)を探してください。
PSW(Program Status Word:プログラム状態語)とは、CPUが「今、プログラムのどこを実行していて、どんな状態か」を指し示している、いわば飛行機のフライトレコーダーのようなものです。

そこに記載されている16進数のアドレス(例:`078D1000 800A2C6` のような形式)を確認します。この後半部分にあるアドレス(例: `800A2C6` や、オフセットアドレス `+0000A2C6`)が、まさにエラーが発生したその瞬間の命令のアドレスです。

ステップ2:ロードモジュールのロードマップ(コンパイラリスト)と突合する

次に、コンパイル時に出力された「リスト(Listing / 魔法の紙)」を取り出します。
PL/Iのコンパイラリストには、ソースコードの各行が「ロードモジュールの先頭から何バイト目の位置(オフセット)にあるか」がずらりと記載されています。

1. ダンプで見つけたオフセット(例:`+0000A2C6`)を確認する。
2. コンパイラリストの「OFFSET」列を上から下へ目で追い、そのアドレスに最も近い行を探す。
3. 「あ、ここだ!」。リストのその行には、まさに `AVERAGE_SAL = TOTAL_SALES / MEMBER_COUNT;` と書かれたソースコードの行番号がポツンと表示されています。

これで、何万行もある巨大な基幹システムの中から、「どの変数がゼロで、どの計算式のせいでプログラムが息絶えたのか」を完璧に特定できたというわけです。

4. レガシー移行期を生き抜くアーキテクトからのアドバイス

JavaやCOBOLから来た皆さんにとって、PL/Iのダンプ解析は最初は黒魔術のように感じられるかもしれません。「なぜソースコードの行数が直接ログに出ないんだ!」とイライラすることもあるでしょう。

しかし、構造を理解してしまえば、メインフレームほど「過去に何が起きたのか」を正確に、嘘偽りなく教えてくれる環境はほかにありません。CPUのレジスタやPSWは、決して嘘をつきませんからね。

もし次に `CEE3201S` に遭遇したら、深呼吸をして以下の3ステップを思い出してください。
1. ジョブログでメッセージとオフセットを確認する
2. CEEDUMPからPSWのアドレスを特定する
3. コンパイラリストのオフセットと照らし合わせて、該当行を特定する

「怖くないですよ、一つずつ紐解けば簡単です」。
このアプローチさえ身につければ、どんな巨大なレガシーシステムのトラブルも、きっとスマートに解決できるようになりますよ。それでは、次回のメインフレーム談義もお楽しみに!

タイトルとURLをコピーしました