【実務・中級編】アセンブラレベルでのデータ例外(S0C7)の発生原因 – PL/Iの基本構文とデータ制御実践ガイド

伝説の例外「S0C7」とPL/Iの世界へようこそ

おい、そこの画面を睨みつけている若手くん。また夜間バッチで夜間運用オペレータから叩き起こされたか?
「シスログに SYSTEM COMPLETION CODE=0C7 REASON CODE=00000007 って出て、基幹の月次集計バッチが盛大に落ちました」って顔をしてやがる。

……よし、落ち着け。メインフレームのエンジニアとして避けて通れない、そして最も恐れられ、同時に最もメカニズムが美しい悪夢――アセンブラレベルのデータ例外「S0C7(System Abend 0C7)」だ。

COBOLだろうがPL/Iだろうが、根本の原因は同じだ。「計算に使おうとしたパック10進数(COMP-3)の領域に、まともな数字以外のゴミが入っていた」これに尽きる。しかし、自由度が高すぎるが故に「何でもできてしまう」PL/Iの世界では、このS0C7がCOBOLとはちょっと違った、エンジニアの意表を突くタイミングで牙をむく。

今日は、なぜS0C7が発生するのかというハードウェアレベルのメカニズムから、我らがPL/Iにおいてどうやってこれを防ぎ、どうやってスマートにハンドリングすべきか、現場の血と汗が染み込んだノウハウを叩き込んでやる。耳の穴をかっぽじってよく聞きな。

—

1. なぜS0C7は起きるのか?(S370/zアーキテクチャの泥臭い真実)

まず大前提として、S0C7の正体を知る必要がある。これはPL/Iのコンパイラが吐くエラーじゃない。IBMのプロセッサ(System/370アーキテクチャおよびz/Architecture)が、「おい、俺に意味不明なデータを計算させようとするんじゃねぇ!」とハードウェア割り込みを起こして自爆する現象だ。

パック10進数(Packed Decimal / PL/Iでの `PICTURE` 項目の多く)の構造

メインフレームの世界では、数値を1バイトに2桁詰め込む「パック10進数」をゴリゴリ使う。
1バイトのうしろ4ビット(ゾーン部)には符号(正なら `C` や `F`、負なら `D` など)が入り、それ以外の場所には `0` から `9` までの数字がビチッと詰まっていることが絶対のルールだ。

例えば、`+123` という値が2バイトの領域にあったとする。
HEXで見れば `12 3C` だ。完璧だな。

ところが、外部ファイル(VSAMや順編成ファイル)のレイアウト変更漏れ、あるいは画面からの誤入力、C言語やJavaで作られた他システムからの行儀の悪い連携データによって、この領域にスペース(`40`)や文字の「A」(`C1`)、あるいは初期化漏れのゴミ(`00` や `FF`)が混入したとする。

この状態で、CPUが次のような演算命令(例えば `AP`: Add Packed や `SP`: Subtract Packed)を実行した瞬間:
> 「ハードウェア: おい、右側のバイトのうしろ4ビットに符号の代わりに `4` が入ってるぞ!これじゃ計算できねぇ!」 -> 【ドン!S0C7アベンド発生】

これが、S0C7のメカニズムのすべてだ。CPUレベルでデータ検証例外(Data Exception)がトリガーされる。

—

2. PL/Iにおけるデータ定義とS0C7の罠

COBOLであれば、`USAGE IS COMPUTED-3` と定義された項目に英字をMOVしようとすると、コンパイル時やMOVE時の暗黙のデータ変換で気づくか、あるいは有効数字チェック(NUMERICテスト)を挟む文化が定着している。

しかし、PL/Iはどうだ? PL/Iは非常に強力な型変換(プロモーション)を裏で勝手に行う。
例えば、以下のようなコードを書いていないか?

DCL WK_AMT FIXED DECIMAL(7,2);
DCL IN_REC CHAR(100);

/ 外部から読み込んだ文字データをそのまま代入 /
WK_AMT = SUBSTR(IN_REC, 1, 7);

この瞬間、コンパイラは「文字列からパック10進数への変換コード」を自動生成する。
もし `IN_REC` の先頭7バイトに数字以外の文字(例えばスペースやハイフン)が混ざっていたらどうなるか?
変換の瞬間に、あるいはその後の四則演算の瞬間に、容赦なくS0C7が飛ぶ。

さらに厄介なのは、「読み込んだ瞬間」ではなく「その変数を演算に使った瞬間」にアベンドするという点だ。エラー発生個所(PSWが指すアドレス)と、バグの原因となったデータが入り込んだ場所が、プログラムの中で何十行も離れていることが珍しくない。これがデバッグを地獄絵図にする理由だ。

—

3. 実践:VSAM入出力とONユニットによるスマートな防御

では、ベテランのPL/IアーキテクトはどうやってこのS0C7からシステムを守っているのか?
答えは2つある。「ONユニットによる例外捕捉」 と 「BUILTIN関数を使った事前のバリデーション」 だ。

実際のバッチプログラムを想定したサンプルコードを見てみよう。大文字で美しく書かれた、実務でそのまま使えるコードだ。

—————————————————————-

  • モジュール名: S0C7PREV
  • 概要: VSAMファイルから読み込んだデータの数値妥当性検証と
  • ONユニットによるデータ例外(S0C7)の安全な捕捉

—————————————————————-
S0C7PREV: PROC OPTIONS(MAIN);

/ 宣言部 /
DCL 1 ACCOUNT_REC,
5 ACC_ID CHAR(6), 口座番号
5 ACC_BALANCE CHAR(9); 外部定義: 実際はパックに変換予定の文字列

DCL W_BALANCE FIXED DECIMAL(11,2) INIT(0);
DCL ERR_FLG BIT(1) INIT(‘0’B);

/ VSAMファイルのオープン(説明のため簡易記述) /
OPEN FILE(ACC_VSAM) INPUT;

/ データ例外(CONVERSION)のONユニット定義 /
ON CONVERSION BEGIN;
DISPLAY(‘【警告】データ変換エラーを検出しました。不正データが含まれています。’);
DISPLAY(‘問題の口座ID: ‘ || ACC_ID);
DISPLAY(‘問題の残高生データ (HEX): ‘ || HEX(ACC_BALANCE));
ERR_FLG = ‘1’B;
GOTO NEXT_REC;
END;

READ_LOOP: DO WHILE(‘1’B);
READ FILE(ACC_VSAM) INTO(ACCOUNT_REC);
IF ENDFILE(ACC_VSAM) THEN LEAVE READ_LOOP;

ERR_FLG = ‘0’B;

——————————————————–

  • 手法1: 事前バリデーション(VERIFYとBUILTINの活用)
  • ‘0123456789’ 以外の文字が含まれていないかを厳密にチェック

——————————————————–
IF VERIFY(ACC_BALANCE, ‘0123456789’) > 0 THEN DO;
DISPLAY(‘【事前検知】口座ID: ‘ || ACC_ID || ‘ の残高項目に数値以外の文字があります。’);
ITERATE READ_LOOP;
END;

——————————————————–

  • 手法2: 安全な代入と演算
  • ONユニットが有効な状態で変換を伴う代入を行う

——————————————————–
W_BALANCE = ACC_BALANCE; ここでCONVERSIONが発生する可能性あり

IF ERR_FLG THEN DO;

  • ONユニット側でエラーフラグが立った場合の処理

ITERATE READ_LOOP;
END;

  • 正常系の処理(例:残高に利息を加算)

W_BALANCE = W_BALANCE 1.015;

NEXT_REC:
END;

CLOSE FILE(ACC_VSAM);
RETURN;

END S0C7PREV;

コードの解説:ここがプロの技だ

1. `ON CONVERSION BEGIN; … END;` の活用
PL/Iには強力な条件(Condition)ハンドリング機構がある。データ属性が一致しない、あるいは数値変換できない文字が飛び込んできたとき、システムが即座にS0C7で異常終了するのを防ぎ、この `ON CONVERSION` ユニットに処理をトラップさせることができる。
コード内では、エラーが発生した瞬間にフラグを立て、`GOTO` で安全に次のレコードへバイパスしている(ONユニット内からの制御移動には `GOTO` が唯一の確実な手段だ)。

2. `HEX` バルトイン関数の凄み
デバッグ時に最も役立つのが `HEX(ACC_BALANCE)` だ。ダンプリストを何枚も捲らなくても、コンソールログに「どのバイトに何の16進数が入っているか」が一発で出力される。`40`(スペース)が入っているのか、`FF` が入っているのかが即座に判明する。

3. `VERIFY` ビルトイン関数による事前防御
わざわざアベンドさせてONユニットで拾うまでもなく、`VERIFY(string, match)` を使えば、「指定した文字リスト以外の文字が最初に出現する位置」を瞬時に返してくれる。`0` が返れば、その文字列は完全に数字だけで構成されている証拠だ。実務では、入出力境界(Boundary)でこの `VERIFY` チェックをくぐらせるのが、夜間バッチを絶対に落とさないための最も確実な防衛ラインとなる。

—

4. 先輩からの現場の教訓(まとめ)

いいか、よく覚えておけ。
メインフレームの歴史において、S0C7は幾千人ものプログラマーたちの胃を痛めてきた最大の宿敵だ。しかし、恐れる必要はない。敵の正体(パック10進数の不正データ)と、武器(`VERIFY`、`HEX`、そして `ON CONVERSION`)の使い所を正確に把握していれば、怖じ気づくことは何もない。

マイグレーションやリビルドの案件で、古いデータ構造をそのまま新しいオープン系やリレーショナルデータベースに流し込むとき、この手の「ゴミデータ」が必ずと言っていいほどゾンビのように蘇ってくる。

「動けばいいや」ではなく、「異常なデータが来ても、システムが自壊せず、優しく、かつ正確にログを残して弾く」――これこそが、世界最高峰のメインフレームシステムを支えるプロフェッショナルのコードだ。

さあ、コーヒーでも飲んで、ログの解析に戻るとしようか。何か詰まったら、いつでも俺のところに来い。

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