はじめに:なぜ今、PL/Iの「死に様」を極める必要があるのか
基幹システムの現場において、夜間バッチが突如としてシステムアベンド(ABEND)を引き起こし、オペレーターからの冷ややかなコールで叩き起こされる――。レガシーシステムの運用保守に携わるエンジニアであれば、誰もが一度は冷や汗をかいた経験があるはずだ。
JavaやC#といったモダンな言語の世界であれば、例外発生時にはスタックトレースが親切に出力され、ローカル変数の値までIDEで手に取るように確認できる。しかし、我々が愛し、時に呪うIBMメインフレームの世界、すなわちPL/I(Programming Language One)で書かれた巨大なバッチ群においては、そうはいかない。
特に、近年盛んに行われているJava等へのマイグレーションプロジェクトにおいて、「現行のブラックボックス化した処理が、なぜその異常終了を引き起こしたのか」を正確に突き止めることは、移行先の設計品質を左右する極めて重要なフェーズである。仕様書は当てにならない。信用できるのは、コンパイラが吐き出したコードと、ハードウェアが切り取った瞬間のメモリダンプだけだ。
今回は、PL/Iプログラムの異常終了時にその真価を発揮する`CEE3DMP`(Language Environment デンプサービス)の制御と、出力されたダンプからベース変数、ポインタ、さらにはパックデシマルの内部符号に至るまでを完璧に読み解くための実践的知見を、システムアーキテクトの視点から徹底的に解説しよう。
—
1. 予約語を持たないPL/Iの懐の深さと、それがもたらすダンプ解析の罠
PL/Iの言語仕様における最大の特異性は、「言語としての厳密な予約語(Reserved Words)を持たない」という点にある。COBOLやC言語とは異なり、`IF`や`READ`といったキーワードであっても、コンテキスト(文脈)によっては変数名として宣言して使用することが理論上可能となっている。
この設計哲学はプログラミングの自由度を爆発的に高めた一方で、ダンプ解析を行う際には極めて厄介な副作用をもたらす。コンパイラは文脈から型やシンボルを解決するため、最適化レベル(`OPTIMIZE`)が上がると、ソースコード上の変数名が機械語命令の最適化の過程で消去されたり、レジスタへ完全にインライン展開されたりするのだ。
したがって、`CEE3DMP`を用いて確実に対象変数の内容を追跡するためには、「ポインタとベース変数(Based Variables)」を用いた厳密なメモリ管理と、コンパイラオプションの適切な制御が不可欠となる。
—
2. CEE3DMPの呼び出しと実用的なオプション指定
Language Environment(LE)環境下において、プログラムが予期せぬ例外(S0C4やS0C7など)に直面した際、あるいはアプリケーション側で意図的に異常を検知した際に、詳細な診断情報を得るためには`CEE3DMP`を動的に呼び出す。
以下に、実務のバッチプログラムで即座に使える、`CEE3DMP`の呼び出しコード例を示す。
DCL CEE3DMP ENTRY (
CHAR(), / タイトル文字列(ダンプのヘッダに出力) /
CHAR(), / オプション文字列(出力先の制御など) /
FEEDBACK / フィードバック・コード(結果受取り) /
) LINKAGE(OPLLINK);
DCL 01 V_DUMP_TITLE CHAR(80) VARYING;
DCL 01 V_DUMP_OPTS CHAR(255) VARYING;
DCL 01 FB_CODE FEDBACK;
/ ダンプタイトルの設定 /
V_DUMP_TITLE = ‘ FATAL ERROR: ACCOUNTS BATCH ABEND AT STEP010 ‘;
/ オプション指定:
FCH = フライパンのように致命的なエラー箇所、
STG = ストレージ(メモリ)内容のダンプ出力、
TRACE = サブルーチン呼び出しの履歴(スタックトレース)
/
V_DUMP_OPTS = ‘FCH STG TRACE BLKS(64) ‘
|| ‘HEADER(”PL/I Production Diagnostic Dump”)’;
/ CEE3DMPの実行 /
CALL CEE3DMP(V_DUMP_TITLE, V_DUMP_OPTS, FB_CODE);
IF FB_CODE.Condition_Token_Value(1) ^= 0 THEN
DO;
/ ダンプ生成自体に失敗した場合のフォールバック処理 /
DISPLAY(‘CRITICAL: CEE3DMP FAILED WITH RC = ‘ || FB_CODE.I_V_Value);
END;
アーキテクトが教えるコンパイラオプションの鉄則
`CEE3DMP`で変数の値を正確に追いかけたい場合、コンパイル時には以下のオプションが必須となる。
- `TEST(ALL,SYM)` または `NOTEST(SYM)`: シンボルテーブルをロードモジュールに埋め込む。これをケチると、ダンプ上のストレージアドレスと変数名のマッピングが失われ、単なる16進数の海と格闘することになる。
- `OPTIMIZE(0)` または `NOOPTIMIZE`: 調査フェーズにおいては最適化を完全に切るべきだ。レジスタ割り付けの最適化が入ると、メモリ上の変数の値が古いまま放置されたり、変数が実在しなくなったりするため、ダンプ解析の難易度が跳ね上がることになる。
—
3. ダンプの読み解き方:S0C7(パックデシマル異常)とポインタの追跡
メインフレームの夜間バッチを最も震撼させるエラー、それがS0C7(データ例外:Data Exception)である。これは、演算命令(ZAPやAPなど)の対象となったゾーン10進数やパックデシマル(`FIXED DECIMAL`)の領域に、有効な数値データ(`0`~`9`のゾーン/ニブル)以外のゴミや非数(パディングミスなど)が混入している場合に発生する。
ケーススタディ:パックデシマルの内部符号反転バグ
例えば、外部ファイル(VSAMや順編成ファイル)から読み込んだレガシーな電文フォーマットや固定長レコードのレイアウト定義がズレており、本来文字(`CHAR`)であるべき領域を算術演算項目(`FIXED DECIMAL`)として読み込んでしまった場合、最下位ニブル(右側の4ビット)にあるはずの符号(`C`, `D`, `F`など)の位置に文字コードのビットパターンが入り込む。
`CEE3DMP`が出力する「Traceback」と「Storage」セクションを突き合わせることで、以下の手順で原因を特定する。
1. 異常発生アドレスの特定:
ダンプ内の `Program Interruption` が発生した PSW(Program Status Word)のアドレスから、機械語命令のオフセットを割り出す。
2. レジスタとベース変数の照合:
PL/Iのベース変数(`BASED`属性を持つ変数)を指しているポインタ変数の値(16進数アドレス)をレジスタから読み取る。
3. ストレージ・ダンプの目視確認:
該当アドレス周辺の16進数ダンプを確認し、問題のパックデシマル変数がメモリ上でどのようなバイト列になっているかを検証する。
ベース変数を用いた動的ストレージ操作のサンプル
マイグレーションやリバースエンジニアリングの現場では、巨大な生データ(バイナリ領域)を効率的に解析するために、以下のようなベース変数とポインタを駆使したコードが多用される。
/ ポインタ変数とベース変数の宣言 /
DCL P_RECORD_PTR POINTER;
DCL 01 RAW_RECORD_MAP BASED(P_RECORD_PTR),
03 REC_ID CHAR(4),
03 REC_AMT_PACK FIXED DEC(11,2), / ここがS0C7の温床になりやすい /
03 REC_FILLER CHAR(50);
/ 任意のメモリ領域(例えばGET DATAやストレージプール)のアドレスをセット /
P_RECORD_PTR = ADDR(WORK_AREA_BUFFER);
/ この状態でアクセスを行うと、WORK_RECORD_MAPのレイアウトに沿って
メモリが直接解釈される。CEE3DMP出力のストレージダンプと
このレイアウトを突き合わせることで、どのオフセットでズレが生じているかが一目瞭然となる。 /
もし `REC_AMT_PACK` の領域に `C1 C2 C3 C4 C5` のような文字コード(EBCDICの ‘A’, ‘B’, ‘C’, ‘D’, ‘E’)が入り込んでいれば、算術演算を行った瞬間に容赦なくS0C7アベンドが発生する。ダンプ上の当該アドレスを直接目視することで、「どの外部入力ファイル・どのレコードが不正なデータを持ち込んだか」を物理レベルで特定できるのだ。
—
4. 埋め込みSQL(DB2)およびCICS環境におけるエッジケース対策
バッチ処理だけでなく、CICSオンラインやDB2(SQL)が絡む環境下での `CEE3DMP` の利用には、さらなる高度な配慮が必要となる。
データベースアクセス中のアベンド
DB2のプリコンパイルを経たPL/Iプログラムにおいて、SQL文の実行失敗(SQLCODE < 0)のハンドリングを誤ると、意図しないタイミングでアプリケーションが強制終了する。 このようなケースでは、エラーハンドラ(`ON ERROR` や `ON UNDEFINEDFILE` など)の内部で `CEE3DMP` を呼ぶように設計しておくことが、システム運用の信頼性を担保する上で極めて有効である。 ON ERROR DO; CALL CEE3DMP('DB2 BATCH ERROR ENCOUNTERED', 'STG TRACE', FB_CODE); / 必要に応じてダンプ取得後に異常終了コードをOSに返却 / CALL PLIRETC(16); STOP; END; ここで注意すべきは、CICSオンライン環境である。CICSタスク内で無闇に `CEE3DMP` を呼び出すと、CICSの領域保護機構やトランザクション管理とバッティングし、最悪の場合はCICSリージョン全体を巻き込んだアベンド(S0C3やドメインエラー)を引き起こすリスクがある。CICS環境下では、言語環境(LE)のCICS用ダンプオプション(`CEEDUMP` 出力先の設定など)をあらかじめ `CEEOPTS` などのランタイムオプションで適切にルーティングしておくことが、シニアアーキテクトとしての必須の作法となる。 ---
5. マイグレーション(レガシーモダナイゼーション)への示唆
我々アーキテクトが、これほどまでに泥臭いPL/Iのダンプ解析手法や言語仕様を深く理解していなければならない本当の理由は、「レガシーシステムを安全に現代のアーキテクチャ(Java / Cloud)へ移行するため」に他ならない。
現行のPL/Iプログラムが抱えている「暗黙のデータ補正」「パックデシマルの符号処理の癖」「ポインタによるメモリ上書きのロジック」などは、仕様書に一言も書かれていないことが多い。仕様書が嘘をつくとき、最後に真実を語るのは「アベンド時のメモリダンプ」だけなのだ。
- 型安全性の担保: Javaなどのモダン言語へ移行する際は、PL/Iの曖昧なベース変数やオーバーレイ構造(`DEFINED`属性など)を完全に解体し、厳密なオブジェクト指向のドメインモデルへと再設計しなければならない。
- 例外ハンドリングの近代化: `CEE3DMP`による事後解析に頼っていた世界から、構造化された例外処理(`try-catch` とロギングフレームワーク)へのシームレスな移行を実現するためには、「現行システムがどのような前提条件(メモリ状態)でエラーを検知していたか」をダンプから完全に見極める必要がある。
—
おわりに
PL/Iは、その誕生から半世紀以上が経過した現在でも、金融、航空、公共といった社会の根幹を支える巨大システムの中で静かに、そして力強く稼働し続けている。
「古い言語だから」「もうすぐ移行するから」と言って、その挙動のメカニズムやダンプの読み方を軽視するアーキテクトに、堅牢な新システムを設計することはできない。機械語とメモリの息吹を感じ取り、ダンプの16進数の羅列からバグの足音を聞き分ける――。この生粋のエンジニアリング精神こそが、時代がどのように変わろうとも、我々システムスペシャリストの最大の武器であり続けるのだ。
