【実務・中級編】CEE3DMPによるダンプ出力の制御とフォーマット指定 – PL/Iの基本構文とデータ制御実践ガイド

おい、調子はどうだ? 夜間バッチのABEND(異常終了)連絡で叩り起こされる生活、そろそろ抜け出したいところだな。

今日のテーマは、メインフレームの現場で避けて通れない「CEE3DMPによるダンプ出力の制御とフォーマット指定」だ。

オンラインでもバッチでも、システムが予期せぬU4038やS0C4で沈んだとき、頼りになるのはコンソールメッセージとSYSUDUMP、そして何よりこのLanguage Environment(LE)サービス、`CEE3DMP`だ。適当にダンプを出力させて「わけがわからない」と頭を抱えるのは今日で終わりにする。ベテランの俺が、現場で本当に使えるダンプの操り方を叩き込んでやる。

1. なぜ「予約語を持たないPL/I」とCEE3DMPが相性抜群なのか

まず、今回の前提となるPL/Iの言語特性について少し触れておこう。
PL/Iには、他の言語(COBOLやJavaなど)にあるような厳密な「予約語」が存在しない。例えば、`IF`や`READ`といったキーワードですら、コンテキストによっては変数名として定義できてしまうという、懐の深さ(あるいは狂気)を持っている。

識別子(変数名)は最大31文字、英字で始まり、アルファベットと数字、そしてアンダースコア(`_`)が使える。この自由度の高さゆえに、大規模なレガシーシステムでは、可読性を無視した自由奔放な変数名が乱立しがちだ。

そんなカオスなソースコードから展開された変数群がメモリ上でどう配置されているか。それを暴くのが、LEの診断サービスである `CEE3DMP` というわけだ。

2. CEE3DMPの基本とオプション指定の極意

プログラムが異常終了した際、あるいは意図的なロジックの分岐で詳細なメモリ状態をスナップショットしたいとき、俺たちは `CALL CEE3DMP` を埋め込む。

基本構文のイメージはこうだ:

1
DCL CEE3DMP ENTRY(
CHARACTER() VARYING, / ダンプタイトル /
CHARACTER() VARYING, / オプション文字列 /
POINTER, / フィードバックコード(省略時はOMIT) /
) OPTIONS(ASM INTER(RETCODE));

実務で最も頭を悩ませるのが、第2引数の「オプション文字列」だ。ここを適当に `”` (ブランク) にしているエンジニアをよく見かけるが、それではデフォルトのしょぼいダンプしか取れず、肝心な変数の値が省略されてしまう。

現場で推奨する鉄板のオプション文字列はこれだ:
`’TRACE BACK, VARIABLES, STORAGE, BLOCKS’`

各キーワードの意味を整理しておこう:

  • TRACE BACK: 呼び出し履歴(スタックトレース)。どのモジュールの何行目で死んだのかの一覧。
  • VARIABLES: 自動変数や静的変数の値。これがないと、変数の値が分からない。
  • STORAGE: 指定したストレージ領域の16進数ダンプ。
  • BLOCKS: LEの内部制御ブロック(DSAやTCAなど)の情報。

3. 実践:ONユニットとCEE3DMPを組み合わせた堅牢なエラーハンドリング

では、実際のPL/Iソースコードを見ていこう。
ここでは、VSAMファイル(KSDS)の読み込みエラー(典型的なキー重複やレコード不見出しなど)を捕捉する `ON` ユニットの中で、独自のダンプを吐かせてスマートに異常終了させる実装例だ。

1
/ ========================================================== /
/ モジュール名: BATCH001 /
/ 概要: VSAMマスター読み込みとCEE3DMPによる障害解析 /
/ ========================================================== /
BATCH001: PROC OPTIONS(MAIN);

DCL MSTR_FILE FILE RECORD SEQUENTIAL UPDATE
ENV(VSAM);

DCL 1 MSTR_REC,
5 MSTR_KEY CHAR(8),
5 MSTR_DATA CHAR(100);

DCL IO_ERR_FLG CHAR(1) INIT(‘0’);
DCL 99_ABEND_LOG BUILTIN;

/ LEサービス(CEE3DMP)の外部プロシージャ宣言 /
DCL CEE3DMP ENTRY(
CHAR() VARYING,
CHAR() VARYING,
INT4
) LINKAGE(OPLLINK);

DCL 01 FB_CODE,
05 FC_SEV BIN FIXED(15),
05 FC_MSG_NO BIN FIXED(15),
05 FC_FLAGS BIT(32),
05 FC_I_INFO BIN FIXED(32);

DCL DUMP_TITLE CHAR(80) VARYING;
DCL DUMP_OPTS CHAR(80) VARYING;

/ — 条件(ONユニット)の定義 — /
/ VSAM等の入出力エラーを捕捉する /
ON CONDITION(ERROR)
BEGIN;
PUT SKIP EDIT (‘ UNEXPECTED ERROR OCCURRED. PROCESSING CEE3DMP… ‘) (A);

/ ダンプタイトルの動的生成 /
DUMP_TITLE = ‘FATAL ERROR IN BATCH001: KEY = ‘ || MSTR_KEY;
/ スタックトレースと変数情報を確実に出力するオプション /
DUMP_OPTS = ‘TRACE BACK, VARIABLES, STORAGE’;

/ CEE3DMPの呼び出し /
CALL CEE3DMP(DUMP_TITLE, DUMP_OPTS, FB_CODE);

/ 異常終了コードを返してジョブを落とす /
SIGNAL ERROR;
END;

/ — メイン処理ループ — /
OPEN FILE(MSTR_FILE);

READ FILE(MSTR_FILE) INTO(MSTR_REC);

DO WHILE(^ENDFILE(MSTR_FILE));

/ 例として特定の条件で強制的に異常を発生させる /
IF MSTR_KEY = ‘99999999’ THEN
SIGNAL CONDITION(ERROR);

/ 通常処理の継続 /
READ FILE(MSTR_FILE) INTO(MSTR_REC);
END;

CLOSE FILE(MSTR_FILE);

RETURN;

END BATCH001;

このコードのポイントは、`ON CONDITION(ERROR)` の中で単に異常終了させるのではなく、`CEE3DMP` を挟んで当時のキー値や制御フラグをダンプのタイトルにねじ込んでいる点だ。これにより、SYSOUTの海から該当ジョブのログを探すとき、タイトルを見ただけでどのキーの処理で逝ったのかが一発で判別できるようになる。

4. ダンプの読み解き方:どこを見るべきか?

`CEE3DMP` がSYSOUT(あるいは指定したDD名)に出力されたら、以下の順番でスキャンしていくのがプロのやり方だ。全文を上から順に追っていたのでは、定時までに仕事が終わらない。

① どこで死んだか(Traceback)

ダンプの最初の方にある `Traceback` セクションを見る。
モジュール名、コンパイル日時、そしてオフセット(Offset)を確認する。リンケージエディタ(BIND)のMAPリストと突き合わせれば、ソースコードの何行目に該当するか秒速で特定できる。

② 変数の値はどうなっていたか(Variables)

`VARIABLES` オプションを指定していれば、そのルーチン内の自動変数の値がダンプされる。
PL/Iの構造体(ストラクチャー)の場合、アライメント(境界調整)によって予期せぬパディング(空白バイト)が入ることがある。変数のメモリアドレスを追いかけながら、「あ、ここで文字化けを起こしているな」とか「ポインタがヌル(NULL)を指しているな」という当たりをつけていく。

③ フィードバックコード(FB_CODE)の確認

もし `CEE3DMP` 自体が失敗したり、LEが内部エラーを抱えていたりする場合は、第3引数で受け取った `FB_CODE` の中身(特に `FC_SEV` と `FC_MSG_NO`)をCICSやIMSのメッセージマニュアルと照らし合わせる。大抵の場合、セビリティ(重度)が 2 や 3 のエラーがヒントになっている。

5. シニアアーキテクトからの現場の助言

バッチ改修のテスト中や本番稼働後に障害が発生したとき、慌ててソースコードをいじり倒すのは素人のやることだ。まずは確実なダンプを取る仕組み(すなわち今回解説した `CEE3DMP` の適切なオプション運用)をコードベースに組み込んでおくこと。

「エラーが起きたら、まずはダンプに語らせる」。これができるエンジニアは、どんなに巨大なレガシーシステムが相手でも決してビビらない。

次のプロジェクトやバッチの単体テストで、ぜひこのコードスニペットを試してみてくれ。君のデバッグ効率が劇的に跳ね上がることを、俺が保証しよう。

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