こんにちは!メインフレームの世界へようこそ。
JavaやCOBOLといった他のモダンな言語や基幹言語の経験がある方にとって、突然目の前に現れるPL/I(ピーエルアイ)のソースコードや、突如として夜間バッチを止めてくれる「S0C4アベンド」という不気味なエラーコードは、なかなかのインパクトですよね。
「なんだか難しそう……」「変なところで落とされたらどうしよう……」と、不安になっていませんか?
大丈夫ですよ。安心してください。PL/Iは歴史のある言語ですが、その挙動にはきちんとした「理由」があります。一つひとつ仕組みを紐解いていけば、決して怖いものではありません。
今回は、PL/Iの文字操作の花形でありながら、一歩間違えるとシステムをクラッシュさせる魔物に変貌する「SUBSTR(サブストリング)関数によるS0C4アベンド」について、実務の現場の視点を交えながら優しく丁寧に解説していきますね。
—
1. 他言語とはちょっと違う?PL/Iの「文字列操作」と怖〜いS0C4の正体
Javaの `substring()` やCOBOLの `MOVE … (X:Y)` などを使ったことがある方なら、文字列の一部を切り出す処理はお手のものだと思います。PL/Iでも、文字列の切り出しには `SUBSTR` という組み込み関数を使います。
例えば、こんなコードです。
1
DCL WK-IN-AREA CHAR(10) INIT(‘IBM-MAINF’);
DCL WK-OUT-STR CHAR(5);
/ 先頭から5文字を切り出す /
WK-OUT-STR = SUBSTR(WK-IN-AREA, 1, 5);
ここまでは平和です。「WK-IN-AREA」という全10バイトの箱から、1文字目から5文字分(`IBM-M`)を切り出して別の箱に入れています。
しかし、この `SUBSTR` の第2引数(開始位置)や第3引数(長さ)の指定を少しでもミスすると、メインフレームの代名詞とも言える「S0C4アベンド(システム異常終了コード:S0C4)」があなたを襲います。
S0C4アベンドって何?
一言で言うと、「プログラムがアクセスしてはいけないメモリ領域(番地)の壁をぶち破って、外側の世界を覗き見ようとした(あるいは書き込もうとした)」というハードウェアからの警告です。
C言語でいうところの「セグメンテーション違反(Segmentation Fault)」ですね。
Javaであれば `StringIndexOutOfBoundsException` という親切な例外クラスが飛んできて、「おいおい、範囲外だぜ」と教えてくれます。しかし、鉄火場のメインフレームはそんなに優しくありません。エラーを検知した瞬間、OS(z/OS)が容赦なくそのタスクを強制終了させ、ドカンと巨大なメモリダンプ(SYSUDUMP)を残して夜間バッチを異常終了させるのです。
—
2. なぜ起こる?SUBSTRでS0C4を招く2大パターン
実務のマイグレーションや保守現場でよく見かける、S0C4を引き起こす典型的なシチュエーションを見てみましょう。
パターンA:変数や計算結果の勘違いによる「境界外アクセス」
文字列の長さが「10バイト」しかないのに、うっかり「15文字目から5文字くれ」と頼んでしまった場合です。
1
DCL WK-ID CHAR(10) INIT(‘1234567890’);
DCL WK-SUB CHAR(5);
DCL WK-POS FIXED BIN(31) INIT(12); / うわっ、10文字超えてる! /
/ 12文字目から5文字取ろうとする /
WK-SUB = SUBSTR(WK-ID, WK-POS, 5);
PL/Iのコンパイラや実行時は、他のモダン言語ほど厳密に実行時範囲チェックをしてくれないことが多く(オプションによりますが)、指定されたメモリのアドレスをそのまま信じて読みに行きます。結果、WK-IDのメモリ領域の「向こう側」にある、別のプログラムやOSの大切な領域に触れてしまい、S0C4が発動します。
パターンB:ポインタ(POINTER)やベース変数との組み合わせ地獄(これが本命)
PL/Iの真骨頂であり、初学者が最もビビるのが「POINTER(ポインタ)」と「Based(ベース)変数」の組み合わせです。メモリのアドレスを直接指し示すため、少しでも計算を誤ると即座にS0C4の餌食になります。
1
DCL MY-PTR POINTER;
DCL BASED-BUF CHAR(100) BASED(MY-PTR);
DCL WK-DATA CHAR(10);
/ MY-PTR がまだ何も指していない(NULLの状態)、または不正なアドレスを指している状態で… /
WK-DATA = SUBSTR(BASED-BUF, 1, 10); / -> ここで一発でS0C4アベンド! /
ポインタが指し示す実体がないのに、そこにあるものとして `SUBSTR` で切り出そうとした結果、存在しない宇宙の果てにアクセスしようとして撃墜されるパターンです。レガシーシステムの改修では、この手のポインタ操作が絡む複雑なレコードレイアウト解析でよく涙を流すことになります。
—
3. ダンプから真犯人を暴け!オフセット特定の手法
もし夜間バッチがS0C4で落ちてしまったら……?焦る必要はありません。ベテランのシステムアーキテクトは、出力されたSYSOUT(スプール)のダンプリストから、次のような手順で犯人を特定します。
1. PSW(Program Status Word)のアドレスを確認する
ダンプリストの冒頭にあるPSW(プログラムステータスワード)のインストラクション・アドレス(右側の8桁の16進数、例: `000E34A0`)を探します。これが「まさにエラーが発生したその瞬間の命令の場所」です。
2. ロードマップ(セクションマップ)と突き合わせる
リンク・エディット時のマップリストを見て、そのアドレスがどのモジュール、どのCSECTのどのオフセットにあるかを特定します。
3. リスト(Listing)の機械語命令を確認する
コンパイル時に出力されたリスト(PRTLISTなど)を開き、先ほどのオフセット位置(例: `+00012A`)に対応するステートメント番号を見つけます。
「あ、ここで `SUBSTR` を使っているあの行だな!」とピンポイントで特定できるわけです。
—
4. アベンドを未然に防ぐ!安全なPL/Iコードの書き方
「じゃあ、どう書けば安全なんだよ!」という声が聞こえてんきそうですね。実務では、以下のような「お守り」をコードに仕込んでおくことが大切です。
1
DCL WK-IN-A CHAR(20) INIT(‘HELLO PL/I’);
DCL WK-LEN FIXED BIN(15);
DCL WK-RESULT CHAR(10);
/ 安全策1: あらかじめLENGTH関数で実際の長さを確認してから処理する /
WK-LEN = LENGTH(RTRIM(WK-IN-A)); / トリム後の有効長を取得 /
IF WK-LEN >= 5 THEN
DO;
WK-RESULT = SUBSTR(WK-IN-A, 1, 5);
END;
ELSE
DO;
/ 長さが足りない場合のフォールバック処理 /
WK-RESULT = ‘SHORT’;
END;
このように、「いきなりSUBSTRに飛び込むな、長さを測ってから条件分岐を挟め」という防衛的プログラミング(Defensive Programming)の精神が、メインフレームの荒波を生き抜く最大のコツです。
—
おわりに
PL/Iの `SUBSTR` とS0C4アベンドの関係、少しイメージが湧いてきたでしょうか?
「メモリの境界を跨いでしまうかもしれない」というレガシーならではの緊張感はありますが、データの長さやポインタの指し先をしっかりとコントロールしてあげれば、PLIは非常にパワフルで頼りになる相棒です。
「変数の箱の大きさと、切り出す範囲のサイズ感」をいつも頭の片隅に置きながら、怖がらずに向き合っていきましょうね。あなたのメインフレームライフが実りあるものになるよう、これからも応援しています!
