z/OS DebuggerとPL/Iコンパイラ:TESTオプション、SYMテーブル生成の深層とモジュール肥大化の罠
基幹システムの現場で、夜間バッチの巨大なロードモジュールや、CICSオンライン領域で突如発生するS0C4やS0C7のアベンド(ABEND)。その解析において、私たちメインフレームアーキテクトが最後に拠り所とするのは、コンパイラが吐き出すシンボル情報、すなわち TESTコンパイラオプションによって生成されるSYMテーブル(またはADATA) である。
現代のオープン系言語(JavaやC#など)に慣れ親しんだエンジニアから見れば、「デバッグ情報を本番ロードモジュールに含めるなんて言語道断、セキュリティと容量の無駄遣いではないか」と映るかもしれない。しかし、極限の可用性が求められるIBMメインフレームの世界において、この「シンボル情報の持ち方」を誤ることは、深夜の障害対応において致命的な遅延を招くことを意味する。
今回は、PL/Iにおける識別子の命名規則の自由度という言語特性を背景に持ちつつ、コンパイラオプションの挙動、動的メモリ操作、そしてモジュールサイズへの影響とマイグレーション時のリスク管理について、骨太なアーキテクチャの視点から紐解いていこう。
—
1. PL/Iの識別子規則とTEST(SYM)が果たす役割
PL/Iという言語の最大の特徴の一つは、「予約語(Keyword)を持たない」という非常にユニークな構文規則にある。
C言語やCOBOLとは異なり、`IF`や`READ`といった言語キーワードであっても、文脈上それが変数名(識別子)として使われているとコンパイラが判断すれば、エラーなくコンパイルが通る。
1
/ PL/Iにおける自由度の高い識別子の例 /
DECLARE IF FIXED BIN(31); / ‘IF’という名前の変数定義 /
DECLARE THEN CHARACTER(10); / ‘THEN’という名前の変数定義 /
IF = 100;
IF IF = 100 THEN
THEN = ‘VALID’;
この驚異的な柔軟性は、コンパイラの字句解析(Lexical Analysis)および構文解析(Parsing)において、非常に高度なシンボルテーブル管理を要求する。コンパイラは、ソースコード上の文字列が変数なのか、キーワードなのかを文脈から完全に追跡し、内部のシンボルテーブル(SYM)へ正確にマッピングしなければならない。
この内部シンボルテーブルを、コンパイル時にロードモジュール(またはSYSDEBUG等の別ファイル)へ出力させるのが `TEST` コンパイラオプション である。
—
2. TESTオプションの解剖:`TEST(SYM)` と `TEST(ALL)` の実務的選択
PL/I(Enterprise PL/I for z/OS)における `TEST` サブオプションの指定は、単なるデバッグの有無にとどまらず、ロードモジュールの構造そのものを変貌させる。
- `NOTEST`
- 本番運用のデフォルト。シンボル情報は一切生成されない。最適化(`OPTIMIZE`)が最大限に効くため、実行速度・メモリ効率ともに最高性能を発揮する。しかし、アベンド時のスナップダンプ(SYSUDUMP/CEEDUMP)では、変数の値がメモリアドレスやオフセットでしか確認できなくなる。
- `TEST(SYM)`
- 識別子名、データ型、構造体のオフセットなどのシンボル情報(SYMテーブル)のみを生成する。フックコード(命令の実行ごとにブレークポイントを挟むための機械語命令)は埋め込まれない。
- アーキテクトの推奨: 本番環境において、万が一の障害解析力を維持しつつ、後述のモジュール肥大化やパフォーマンス劣化を最小限に抑えたい場合の最適解。
- `TEST(ALL)`
- SYMテーブルに加え、コードの各ステートメントにフックコードを挿入する。z/OS Debuggerによる対話型デバッグ(ステップ実行など)に必須であるが、コードサイズが肥大化し、実行時パフォーマンスが明確に低下する。本番環境への適用は厳禁である。
推奨されるコンパイルJCLの構成例
//STEP1 EXEC PGM=IBMZPLI,
// PARM=’RENT,LIB,OPTIMIZE(2),TEST(SYM),NODYNAM’
//SYSIN DD DATA
==============================================================
- 基幹バッチプログラム:動的メモリとポインタを多用するモジュール
==============================================================
PROG01: PROC OPTIONS(MAIN);
…
END PROG01;
/
ここで重要なのは、`OPTIMIZE` と `TEST(SYM)` の共存である。Enterprise PL/Iコンパイラでは、`TEST(SYM)`であれば最適化(`OPTIMIZE(1)`や`OPTIMIZE(2)`)との併用が公式にサポートされている。これにより、実用的なパフォーマンスを維持しつつ、ダンプ解析時に変数名ベースでのデバッグが可能になる。
—
3. ポインタ、ベース変数、およびSYMテーブルの恩恵
基幹システムのPL/Iプログラム、特に大規模なバッチ処理やDB2/CICS連携モジュールでは、ストレージの動的獲得(`ALLOCATE`文)とポインタ(`POINTER`)によるベース変数(Based Variable)の操作が頻繁に行われる。
1
/ ポインタとベース変数を用いた動的ストレージ操作の例 /
DECLARE 1 RECORD_TYPE BASED(P_REC),
5 REC_ID CHAR(4),
5 REC_DATA CHAR(100),
5 REC_AMOUNT FIXED DEC(11,2);
DECLARE P_REC POINTER;
DECLARE 1 WORK_AREA,
3 WS_TOTAL FIXED DEC(15,2) INIT(0);
/ 動的ストレージの獲得 /
ALLOCATE RECORD_TYPE SET(P_REC);
/ 値の設定とパックデシマルの内部表現 /
REC_ID = ‘A001’;
REC_AMOUNT = 12345.67;
このようなコードで `S0C7(データ例外:パックデシマルのフォーマット不正)` や `S0C4(保護例外:無効なメモリアドレスへのアクセス)` が発生した際、`TEST(SYM)` が生成するSYMテーブルが真価を発揮する。
もし `NOTEST` でコンパイルされていた場合、ダンプ上の `P_REC` が指すアドレスから、手動で16進数dumpを1バイトずつ追いかけ、`REC_AMOUNT` のゾーン10進数(Packed Decimal)の符号ニブル(末尾の `C` や `D` など)が反転しているバグや、パディングのズレを目視で計算しなければならない。
しかし、`TEST(SYM)` があれば、z/OS Debugger(あるいはCEEDUMP解析ツール)は以下のように即座に構造体を展開してくれる。
P_REC -> RECORD_TYPE
REC_ID = ‘A001’
REC_DATA = ‘…’
REC_AMOUNT = 12345.67 <-- ここで符号反転バグ(例: 12345.6F など)を即座に特定可能
この「変数名でメモリを覗ける」というアドバンテージは、夜間バッチのランタイムを数十分、数時間短縮させる決定打となる。
---
4. ロードモジュールサイズとストレージへの影響:アーキテクトの懸念
「それならば、すべての本番モジュールに `TEST(SYM)` をつけておけば安心ではないか」と思われるかもしれない。しかし、ここに基幹システムアーキテクトとしてのジレンマが存在する。
1. ロードモジュールおよびPDS/PDSEの容量肥大化
`TEST(SYM)` を指定すると、コンパイラはオブジェクトモジュール内にLLSYM(Language Level Symbol)レコードを出力する。プログラムの規模(特に巨大な構造体や大量の変数を持つデータ定義)によっては、ロードモジュールのサイズが20%から場合によっては50%以上も肥大化するケースがある。
これは、LPA(Link Pack Area)やELPAに常駐させるオンラインモジュール(CICSなど)において、貴重な31ビット(あるいは64ビット)仮想ストレージを圧迫する要因となる。
2. マイグレーション(オープン系移行)時の隠れたリスク
現在、多くの企業がメインフレーム上のPL/I資産を、JavaやC#、あるいはクラウド上の現代的なランタイムへマイグレーション(リライトまたは自動変換)するプロジェクトを推進している。
このレガシー移行の設計フェーズにおいて、元々のPL/Iプログラムが依存していた「PL/I独自のデータ型(`FIXED DECIMAL`、`BIT`ストリング、ポインタベースのポインタ演算)」や、`TEST(SYM)` が前提としていたデバッグ文化をどのようにモダンな環境に翻訳するかが大きな課題となる。
- データ型の差異: パックデシマル(`COMP-3`相当)の内部表現における符号反転やアンダーフローの挙動は、移行先のJava(`BigDecimal`など)では厳密な例外として検知され、仕様の厳格化による予期せぬアベンドを引き起こす。
- シンボル情報の喪失: オープン系への移行ツールが、PL/Iの複雑なスコープ規則や無名構造体を正しく解釈できず、移行後のデバッグで変数名が失われる(あるいは難読化される)トラブルが後を絶たない。
—
5. 現場のプロフェッショナルが導き出すベストプラクティス
これまでの知見とトラブルシューティングの経験から、PL/Iシステムにおける `TEST` オプションおよびデバッグ基盤の設計方針は、以下のように厳格に規定すべきである。
1. 本番バッチ環境の標準:
原則として `OPTIMIZE(2), TEST(SYM)` を採用する。モジュールサイズの肥大化というデメリットよりも、ミッションクリティカルなバッチ異常終了時の解析工数削減(MTTRの短縮)というメリットの方が圧倒的に大きいためだ。ただし、容量制限がシビアな常駐CICSモジュール等では `NOTEST` とのトレードオフを厳密に評価すること。
2. SYM情報の分離(SYSDEBUGファイルの活用):
Enterprise PL/Iでは、`TEST(SYM)` の情報をロードモジュール内に埋め込むのではなく、別ファイルの `SYSDEBUG` DDとして切り出すことが可能である。これにより、本番ロードモジュールのサイズを膨らませることなく、デバッグ時のみシンボル情報をロードする洗練された運用が実現できる。
3. 移行プロジェクトにおける事前のデータプロファイリング:
レガシーマイグレーションを控えたシステムでは、移行前に現行PL/Iプログラムのデータ構造(特にポインタを介した動的ストレージのレイアウト)を `TEST(SYM)` を有効にした検証環境で徹底的にプロファイリングし、暗黙的な仕様依存箇所を洗い出すことが成功の絶対条件となる。
—
結びにかえて
PL/Iという言語は、その歴史の深さゆえに、コンパイラの内臓に至るまで緻密に設計されている。識別子の自由度を支えるシンボル管理の仕組み、そして `TEST(SYM)` がもたらす強力な可観測性(Observability)は、時代遅れの遺物などではなく、現代のオブザーバビリティの概念を数十年前に先取りした高度なアーキテクチャそのものである。
レガシーシステムの維持であれ、次世代への移行であれ、コンパイラの挙動とバイナリの内部構造を熟知した者だけが、システムの命運を握る真のトラブルシューティングを制することができる。背筋がゾッとするような夜間アベンドの現場に直面したとき、あなたを救うのは、こうしたコンパイラの深層に対する確かな理解に他ならない。
