TSO「TEST」コマンドによるデバッグ

TSOには、TESTコマンドというMVSが標準で提供するコマンド・ベースの対話型デバッガーがあります(*1)。MVSでは、バッチ処理プログラムであってもそのままTSOで実行することができます。プログラムの実行中は端末からのキー入力ができませんが、TSOのTMPであってもイニシエーターであっても、プログラム側で特別な対応をすることなくバッチ処理プログラムのロード・モジュールをそのままTSOで実行できます。I/Oを行うプログラムの場合は、JCLのDDステートメントに定義するデータセットをDDステートメントの代わりにTSOのALLOCATEコマンドを使用して割り振ります。
なお、TESTコマンドはISPFのコマンド・シェルでは実行できないので一旦READYプロンプトに戻る必要があります(*2)。そのため、対話型でデバッグするには事前に対象プログラムのアセンブリー・リストを出力して手元に用意しておかないと効率的な作業が行えません。確認すべき点が予めわかっているのであれば、対話ではなくバッチ処理での実行も便利です。

*1 HLASM(高水準アセンブラー)のToolkit Featureには、IDF(Interactive Debug Facility)というフル・スクリーンによる対話型デバッガーもあり、z/OSでは標準的にインストールされIFAPRDxxメンバーで有効にできるが、有償オプションなので使用するには追加の契約が必要になる。(zD&TであればADCDに含まれる)
*2 REXXからであればISPF内からでもTESTコマンドを実行できる。但し、画面はフル・スクリーン・モードに切り替わってしまうのでPF2キーで画面を分割してPF9キーでSDSFパネルを見ながらのような作業はできない。

実行準備:アセンブルとバインド時にTESTオプションを追加する

TESTコマンドを使用すれば、わざわざダンプを取らなくてもプログラムの実行中にレジスターやデータ領域の内容を簡単なコマンドでディスプレイに表示させて確認することができます。その際、表示させたい命令やデータ領域をアドレスではなくプログラムのソース・コードに定義した名前で指定することもできますが、そのためにはロード・モジュールにSYMレコードと呼ばれるプログラム内に定義した領域などの名前とアドレスの対応表であるSymbolテーブルが格納されたデータ・レコードを出力しておく必要があります。このSYMレコードを作成するのがTESTオプションです。
SYMレコードはアセンブラーによってオブジェクト・モジュール内に書き出され、さらにそれがバインダーによってロード・モジュールに書き出されます。したがって、TESTオプションはアセンブラーとバインダーの両方に指定する必要があります。

デバッグするプログラムをTESTコマンドによって実行する

プログラムがアクセスするデータセットの割り振り

TSOで対話型デバッグを行うのであれば、TESTコマンド実行前にALLOCATEコマンドによってデータセットを割り振ります。バッチ処理で実行するのであれば、JCLにDDステートメントを追加します。データセットをアクセスしないのであれば追加で割り振る必要はありません。

TESTコマンドの実行

最初のパラメーターで実行するプログラムのロード・モジュール・ライブラリーとモジュール・メンバー名を指定します。デフォルトDSNは userid.LOAD です。DSNを省略して(メンバー名)だけを括弧で括って指定した場合は userid.LOAD が暗黙のDSNです。DSNを指定する場合は完全修飾名で指定します。クォーテーション記号を省略した場合、プレフィックス userid、サフィックス LOADが付加されます。LOAD(member) と指定した場合、userid.LOAD.LOAD となってしまいます。
バッチ・セッションで実行する場合は *(member) と指定して、STEPLIB/JOBLIBライブラリーからmemberが探されるようにすればいいでしょう。

HLASM標準提供のアセンブル・プロシージャーASMACLでは、ロード・モジュールはDSN=&&GOSET(GO)という一時データセットに出力されます。その場合は、STEPLIBにDSN=&&GOSETを定義し、TESTコマンドのプログラム名にGOを指定します。

REXXでのTESTコマンドの実行

今日ではISPFを使わずにTSO端末を利用することはほぼありません。対話型でTESTコマンドを使う場合、TESTコマンドを実行するためにいちいちISPFを終わらせてREADYプロンプトに戻るのは面倒なので、下記のようなREXX execを作ってTESTコマンドを起動する方が便利です。データセットをアクセスするプログラムであれば、READYプロンプトから個々にALLOCATEなどのコマンドを実行する必要も無くなります。
REXXからであれば、ISPF内からでもTESTコマンドを実行できます。但し、画面はフル・スクリーン・モードに切り替わるのでPF2キーで画面を分割してPF9キーでSDSFパネルを見ながらのような作業はできません。円滑な作業の為には、予めアセンブリー・リストをプリントしておくかダウンロードしておくといいです。

汎用的なものなので、プログラム・モジュールの格納データセット名やメンバー名は実行時に入力するようにしてあります。決まった名前であれば適宜修正して下さい。また、データセットをアクセスするプログラムであれば、ALLOCATEコマンドなどを適宜追加して下さい。
ISPFコマンド・シェルもしくは任意のISPFパネルでのTSOコマンドによってTSOのEXコマンドを実行できます。以下に任意のパネルからの実行例を示します(REXX execの格納DSNはuserid.CLIST、メンバー名はDOTESTとします)

TSO△EX△(DOTEST)
TSO△EX△'dsname(DOTEST)'

REXXによってTESTコマンドが起動されると、「ENTER MODULE LIBRARY DSNAME(MEMBER)」と「ENTER PARM TEXT PASSING TO THE PROGRAM」の2つのプロンプト・メッセージが出力されます。それぞれ、テストするプログラムの格納DSN(メンバー名)とプログラムに渡すパラメーター文字列を応答します。ロード・モジュールの格納先DSNがuserid.LOADであれば、(メンバー名)だけを入力できます。その他のDSNであれば’DSN(MEMBER)’のように全体を引用符で囲みます。

REXXからのプロンプトへの応答が終わると、TESTコマンドが起動されて指定したプログラム・モジュールが読み込まれ、プログラムの最初の命令の直前で停まります。「TEST」プロンプトに対して各種のTESTサブコマンドを入力してプログラムの実行(デバッグ作業)を進めて行きます。

主なサブ・コマンド

TESTコマンドには数多くのサブ・コマンドがありますが、覚えておくとよい基本的なものは数種類です。プログラムの実行を開始するGOとRUN、現在のPSW内容を表示するLISTPSW、レジスターや記憶域の内容を表示するLIST、それらの内容を変更する値割り当て機能、ブレーク・ポイントの設定と解除を行うATとOFFなどを使えば基本的なデバッグを行うことができます。

コマンド一覧

サブ・コマンド 機能 備考
RUN プログラムの実行を開始して最後まで実行してTESTセッションを終了する。ブレーク・ポイントが設定されていても停止しない。
END TESTコマンド・セッションを終了する。(実行の打ち切り)
AT ブレーク・ポイントを設定する。
OFF ブレーク・ポイントを解除する。
GO プログラムの実行を開始/再開する。 ブレーク・ポイントがあればそこで停止する。
LIST レジスターや記憶域の内容を表示する。
COPY レジスターや記憶域の内容を別のレジスターや記憶域にコピーする。
LISTPSW 現PSWの内容を編集して表示する。
WHERE 指定したアドレスやシンボルもしくはCSECTが、プログラム内のどの位置にあるかを表示する。 プログラムのABEND時などに使用すれば、どの命令でエラーを起こしたかがわかる。
LISTMAP テスト中プログラムの仮想記憶マップを表示する。 REGION開始アドレスとサイズ、TCBアドレスとRBタイプ、モジュール名と長さ、ロード・アドレス、割当て済み記憶域ページのサブプール番号、開始アドレスとサイズ。
※TESTコマンドと各サブコマンドの詳細は「z/OS TSO/Eコマンド解説書」(SA88-7049)を参照。

ATサブコマンド

addressはブレーク・ポイントを設定する命令アドレス。ラベル名、CSECT先頭からの変位等で指定できる。変位で指定する場合、+16進数(例:+6C、+86)のみを指定すればモジュールの先頭CSECT内の指定された変位の命令を示す。:記号で命令範囲を指定すればその間をステップ走行させることができる(GOコマンドを実行する毎に1命令ずつ実行される)。設定するブレーク・ポイントには有効な命令コードが含まれている必要がある。範囲指定する場合、指定範囲でマクロ命令が使われていると、そのマクロ命令内でパラメーター用データなどが展開されることがある。その場合、そのデータ部分以降にはブレーク・ポイントは設定されない(その旨のエラー・メッセージが出力される)
COUNTは、その命令の実行が指定された回数に達するまでブレークしないことを示す。ループ内にブレーク・ポイントを設定するような場合に利用できる。COUNT(100)と指定した場合、ループ内で命令が100回実行される毎に停止する。
subcmdはブレークした際に自動実行させるサブコマンドの定義。(LISTPSW;L 0R:15R)と指定すれば、ブレーク時にPSWとGPR0~15の内容が自動表示される。
TITLEは、そのブレーク・ポイントで停止する際に端末に出力するメッセージ・テキストを指定するもの。省略時は、addressで指定した名前や値が示される。

ブレーク時に実行するサブコマンドの最後にGOを指定すると、subcmdで指定したサブコマンドを実行して停まらずに実行を再開する。実行中のレジスターや決まった記憶域内容の変化を実行を停めずに確認したい場合に利用できる。バッチTSOのTESTコマンドでステップ走行する場合は、ATサブコマンド内でGOサブコマンドを実行させればダイナミック・ステップ数に合わせたGOサブコマンドを並べる必要が無くなる。

設定したブレーク・ポイントを解除するにはOFFサブコマンドを使用する。ATサブコマンドで指定したブレーク・ポイントを指定したOFFサブコマンドを実行すればよい。

解除するブレーク・ポイントは必ずしもATサブコマンドの指定と合わせる必要はなく、順不同で指定してもいいし、1部のブレーク・ポイントだけを指定して解除してもいい。

LISTサブコマンド

nRとnR:mRはレジスター内容の表示。nRで特定のレジスター番号を指定するかnR:mRで範囲指定のレジスター番号を指定する。記憶域は、絶対アドレス(末尾に.ピリオド記号を付ける)、±オフセット値(モジュールの先頭CSECT内の指定された変位)、シンボル名、シンボル名±オフセット値で表示開始アドレスを指定するか記憶域アドレスを:記号で繋げて範囲で指定する。記憶域データに応じた表示形式(type)も指定できる。
開始アドレスだけを指定した場合、表示する長さはL[ENGTH](長さ)で示すかM[ULTIPLE](個数)で、もしくはLENGTHとMULTIPLEを組み合わせて示す。MULTIPLEを併用すれば同じ型のデータを並べて表示できる。

表示形式

type 最大長 表示形式
C 256 文字
X 256 16進数
B 256 2進数
H 8 ハーフワード整数
F 8 フルワード整数
P 16 パック10進数
Z 16 ゾーン10進数
E 8 浮動小数点(単精度)
D 8 浮動小数点(倍精度)
A 4 アドレス定数(フルワード)
Y 2 アドレス定数(ハーフワード)
S 2 相対アドレス(基底+変位)
I 256 命令(逆アセンブル)
XC 256 ダンプ(16進数+文字)

WHEREサブコマンド

値の割当て

レジスターや記憶域の内容は、レジスターやアドレスを示すシンボルに値を割り当てれば変更することができる。レジスターや記憶域内容を変更すれば、「もし、ここの値が〇〇〇だったら動きはどう変わるのか?」といったテストが簡単に行える。

左辺にnRで特定のレジスター番号を指定するか、記憶域を絶対アドレス(末尾に.ピリオド記号を付ける)、±オフセット値(モジュールの先頭CSECT内の指定された変位)、シンボル名、シンボル名±オフセット値で指定し、右辺にデータ型と値を type文字’値’の形式で指定すれば値を割り当てられる。
複数のレジスターに値を割り当てる場合は、左辺に開始レジスター番号を指定し、右辺に開始レジスターから順に割り当てる型と値の組み合わせをカンマで区切って指定し右辺全体を括弧で括る。
値の代入ではなく、COPYコマンドを使用して別のレジスターや記憶域内の値をコピーすることもできる。COPYコマンドのorg_addrにコピー元のレジスターや記憶域を、targ_addrにコピー先のレジスターや記憶域を指定する。レジスターと記憶域の指定方法はLISTサブコマンドと同様である。コピーするデータの長さはLENGTHパラメーターで指定できる。POINTERオプションを付けると、org_addrにアドレスではなく即値を指定することができる。

TESTコマンドによるデバッグ例

ブレーク・ポイントで実行を停める

最後のブレーク・ポイントでサブコマンドを使った後、再度GOまたはRUNコマンドを実行しないとプログラムの最後まで実行されません。ENDサブコマンドはTESTコマンドの終了なのでブレークした状態でプログラム実行が打ち切られます(バッチ処理でTESTコマンドを実行する際に最後まで実行させるGOやRUNを書かずにSYSTSINを//で終了させた場合も同様で、ブレークした状態でプログラム実行が打ち切られる)。デバッグなので実行を途中で打ち切っても(恐らく)実害は無いでしょうが、そのような動きをすることは覚えておきます。

ステップ走行を行う

ATで指定した範囲の命令が、GOが入力される毎にレジスター2~9とWORKAREA領域の内容を表示しながら1命令ずつ実行されます。

プログラム実行中にABENDする

GOで実行させます(RUNではデバッグできないので、GOで実行します)。ABENDした状態で停止するので、その状態のままLISTPSWやWHEREでABENDした命令を確認して、LISTを使用して関連するレジスターや記憶域の内容を表示します。

【PSWがABENDした次の命令を指すケース】

WHEREでABENDした時のPSWが示す命令アドレスを表示させ、その16バイト前から表示させています(PSWは基本的にABENDした命令の次の命令を指す)。さらに同じ範囲を逆アセンブルして具体的な命令も表示させています(デバッグ時に具体的な命令を確認する場合は、アセンブリー・リストを見るか機械語コードを解読することになるが、TESTコマンドのLISTサブコマンドには逆アセンブルの機能もあるので必要に応じて利用することができる)
ABENDした命令はCVB命令。第2オペランドが示す記憶域内容は、x0000000000000000。正しい10進数ではないことがわかります。

【PSWがABENDした命令を指すケース】

期待した実行結果にならない

ATでブレーク・ポイントを設定してGOで実行します。ブレークしたら、関連するレジスターや記憶域内容を表示して、プログラム・ロジックで想定している内容通りかを調べます。条件分岐命令の動きが想定通りでないような場合は、その条件分岐命令でブレークさせてLISTPSWでCC(条件コード)の現在値を確認します。

ループあるいはウェイト(ハングアップ、フリーズ)してしまう

TESTコマンド実行中に、ブレークを設定していない範囲でループしてしまった場合やPOSTされないWAITで停まってしまった場合、バッチ・ジョブと違って自分で自分をキャンセルすることはできません。その場合は、慌てずにATTNキーを押下します。アテンション割込みが受け付けられると、TESTコマンドはプロンプトを出して次のサブコマンド入力待ちになります。PSWやレジスターの内容などを表示してデバッグを進めます。あるいはENDサブコマンドで一旦処理を打ち切って、再度TESTコマンドを起動してから改めてブレーク・ポイントを設定し直します。
※STIMERのWAIT時間誤りなど、MVSのサービス・ルーチン側で停止しているような場合はATTN割込みは効きません。(ATTNは受け付けられるがTESTのプロンプトは表示されない)

アテンション割込みを行うには、SNA端末ではATTNキーを、非SNA端末ではRESETキーでキーボード・ロックを解除してからPA1キーを押します。今日では、TN3270接続ではSNA端末として設定されていることが多いです。ATTNキーは普段あまり使わないので、エミュレーターのキーボード設定を事前に確認しておくかキーパッドを表示させるといいでしょう。なお、むやみにATTNやPAnキーを連打すると無用なアテンションが何度も通知されTESTコマンド自体が終了してしまうことがあります。