IBM Disassemblerでアセンブラー・プログラムのソース・コードを復元する
はじめに
1980年代に開発されたアセンブラーのプログラムのソース・コードとロード・モジュールの内容が一致しない、つまりソース・コードが行方不明になってしまった状況に遭遇してしまいました。開発されたのは古いですが、今でも使用しているツールであり、今後のためにソース・コードをなんとか復元しなくてはなりません。そこではじめてDisassemblerを使ってソースコードの復元にチャレンジしました。
(おわかりだと思いますがIBMのメインフレームでz/OSの下で稼働するプログラムです。)
Disassemblerのマニュアルは使用例の解説がなく、実際の動作のイメージがなかなかわきません。また、Disassemblerの日本語情報が見つけられなかったので、この記事にまとめました。Disassemblerのマニュアルは以下を参照してください
まず一度Disasmしてみる
とりあえずまず一度Disassembler(長いので以後Disasmと表記)してみます。サンプルJCLは以下のとおりです。
//DISASM JOB MSGCLASS=H,CLASS=A,NOTIFY=&SYSUID
//ST1 EXEC PGM=ASMDASM
//STEPLIB DD DISP=SHR,DSN=hlq.sasmmod ①
//SYSLIB DD DISP=SHR,DSN=hlq.loadlib ②
//SYSPUNCH DD DISP=SHR,DSN=hlq.src ③
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
member csect ④
/*
① Disassemblerのモジュールが導入されているライブラリー
② Disasmの対象となるロードモジュール・ライブラリー
③ Disasmしたソース・コードを収納するファイル
④ 1桁目からロード・モジュール名、続いて1桁以上のブランク、次にCSECT名
ジョブが正常終了すると以下の情報がSYSPRINTに出力されます。また、SYSPUNCHに指定したデータセットにソース・コードが出力されます。
- ディレクトリ情報
- DisasmするCSECTを含むモジュールのディレクトリ・エントリ
- ESD テーブル
- モジュールで見つかったすべての外部シンボル・エントリーのフォーマットされたリスト
- RLD テーブル
- このCSECTに関連するすべての再配置辞書エントリのフォーマットされたリスト。
- V-CONやマルチCSECTのA-CONがリストされる
- ユーザー入力レコード
- SYSINに入力した内容
- ラベル表
- ESDエントリ、RLDエントリ、およびUSINGステートメント処理の結果生成された名前から作成されたものを含む、Disasmに使用されるすべてのラベルのリスト。
- プログラムのベースレジスターやDSECT情報を指定するとラベルを生成し、その一覧がここにリストされる
- テキスト
- DisasmされるCSECTを構成するテキストのストレージ・ダンプ形式のリスト。
- ソース・リスト
- 命令が生成された 16進値を含む、生成されたソース・コード
- CSECTの先頭からのオフセット、オブジェクト・コード、ソース・コードが表示される
Disasmのソース・リストの出力例を以下に示します。
SAMPLE CSECT 00000030
00000000 90EC D00C STM R14,R12,12(R13) Save regs 00000040
00000004 05B0 BALR R11,0 Address set 00000050
00000006 41AB 07D0 LA R10,2000(R11) 00000060
0000000A 41AA 0830 LA R10,2096(R10) 00000070
0000000E 18FD LR R15,R13 00000080
00000010 41D0 B6FE LA R13,1790(,R11) 00000090
00000014 50DF 0008 ST R13,8(R15) 00000100
00000018 50FD 0004 ST R15,4(R13) 00000110
0000001C 1BFF SR R15,R15 00000120
0000001E 50FD 0048 ST R15,72(R13) 00000130
00000022 50FD 004C ST R15,76(R13) 00000140
00000026 50FD 0050 ST R15,80(R13) 00000150
0000002A 5890 A65A L R9,1626(,R10) 00000160
0000002E D207 9820 A5FA MVC 2080(8,R9),1530(R10) 00000170
プログラムのベース・レジスターを指定する
ソース・コードは生成できたものの、参照先や分岐先がすべてベース・レジスターとディスプレースメントの形式なのでトレースするにはかなりたいへんです。そこで、Disasmにプログラムのベース・レジスターを指定するとCSECT内の参照先と分岐先のラベルを生成し、そのラベルを使用したコードを作ってくれます。
//DISASM JOB MSGCLASS=H,CLASS=A,NOTIFY=&SYSUID
//ST1 EXEC PGM=ASMDASM
//STEPLIB DD DISP=SHR,DSN=hlq.sasmmod
//SYSLIB DD DISP=SHR,DSN=hlq.loadlib
//SYSPUNCH DD DISP=SHR,DSN=hlq.src
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
member csect
USING start end basereg P initvalue
/*
SYSINのUSINGで始まる行は、プログラムのベース・レジスターを指定する制御ステートメントです。指定例と意味は以下のとおりです。
USING 6 1006 B P 6
① ② ③ ④⑤⑥
① リテラル'USING'を指定する
② USING範囲の開始位置のオフセット(16進数)
USINGステートメントを記述する位置
③ USING範囲の終了位置のオフセット(16進数)
DROPステートメントを記述する位置
④ ベース・レジスター 16進数で1-F
⑤ プログラムのベースレジスターの場合は、リテラル'P'を指定する
DSECTのベース・レジスターの場合は'D'を指定する
⑥ ベース・レジスターの初期値(16進数)
CSECTの先頭からのオフセットで指定する
4Kを超えているプログラムでベース・レジスターを複数使用している場合は例えば次のように指定します。
USING 6 2000 B P 6
USING 6 2000 A P 1006
上の例では、プログラムのベース・レジスターとして汎用レジスター(GPR)の11と10を使用し、GPR 11はオフセット6,GPR 10はオフセット1006(最初のベース・レジスターであるGPR 11から4K離れたところ)を指定しています。
3つ目のパラメーターであるUSING範囲の終了位置にはDropステートメントが挿入されます。DROPする位置を意識しない場合はプログラムの最後を指定しておくとよいです。
プログラムのベース・レジスターを指定してDisasmすると以下のような結果が得られます。差がわかるように指定前後で比較しています。左が指定前、右が指定後です。ラベルが生成されていることがわかります。
ACONはAで始まるラベル、VCONはVで始まるラベル、EX命令の参照先はXで始まるラベルを生成するようです。先頭の文字に続く数字はCSECTの先頭からのオフセットです。ラベルを使って該当箇所が検索できるのでトレースが少し楽になると思います。
プログラム内のラベルを指定する
プログラムのベース・レジスターを指定することでラベルが自動生成されます。でも意味がわかるものではないです。該当フィールドの意味が分かる場合は、ラベルはやっぱり意味があるものにした方がわかりやすくなります。Disasmにラベルの情報を与えるとその名前でソースコードを生成してくれます。
//DISASM JOB MSGCLASS=H,CLASS=A,NOTIFY=&SYSUID
//ST1 EXEC PGM=ASMDASM
//STEPLIB DD DISP=SHR,DSN=hlq.sasmmod
//SYSLIB DD DISP=SHR,DSN=hlq.loadlib
//SYSPUNCH DD DISP=SHR,DSN=hlq.src
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
member csect
ULABL fieldname offset length
USING start end basereg P initvalue
/*
ULABLは、プログラム内のフィールド名を指定する制御ステートメントです。指定例と意味は以下のとおりです。
ULABL SAVEAREA 0704 120
① ② ③ ④
① リテラル'ULABL'を指定する
② フィールド名
③ CSECTの先頭からのオフセット(16進数)
④ フィールド長(10進数)
指定をして実行すると以下のような結果が得られます。これも差がわかるように指定の前後で比較しています。左が指定前、右が指定後です。制御ステートメントで指定したラベル名が使用されていることがわかります。自動生成されるラベルよりも意味が想像できるようになるのでわかりやすくなります。
DSECTラベルを指定する
プログラムで使用しているDSECTがわかっているのであれば、その情報を与えることでDSECTに定義したラベルを使ってDisasmすることができます。
//DISASM JOB MSGCLASS=H,CLASS=A,NOTIFY=&SYSUID
//ST1 EXEC PGM=ASMDASM
//STEPLIB DD DISP=SHR,DSN=hlq.sasmmod
//SYSLIB DD DISP=SHR,DSN=hlq.loadlib
//SYSPUNCH DD DISP=SHR,DSN=hlq.src
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
member csect
dsectname DSECT fieldcount
fieldname offset length
USING start end basereg D dsectname
USING start end basereg P initvalue
/*
DSECT情報の指定例と意味は以下のとおりです。DSECT情報を指定する場合はUSINGステートメントでベース・レジスターとDSECT名を指定します。
SAMPLE DSECT 5
① ② ③
PARM1 2080 8
④ ⑤ ⑥
USING 10 2000 9 D SAMPLE
⑦ ⑧ ⑨ ⑩⑪⑫
① DSECT名
② リテラル'DSECT'
③ フィールド数
④ フィールド名
⑤ オフセット(10進数)
⑥ フィールド長(10進数)
⑦ リテラル'USING'
⑧ USING範囲の開始位置のオフセット(16進数)
USINGステートメントを記述する位置
⑨ USING範囲の終了位置のオフセット(16進数)
DROPステートメントを記述する位置
⑩ ベース・レジスター 16進数で1-F
⑪ DSECTのベースレジスターの場合は、リテラル'D'を指定する
⑫ DSECT名
DSECT情報を指定をして実行すると以下のような結果が得られます。これも差がわかるように指定の前後で比較しています。左が指定前、右が指定後です。制御ステートメントで指定したDSECTラベルが使用されています
おわりに
以上のようにベース・レジスターやDSECTの情報を追加していくことで人が読んでわかるソース・コードに近づけていくことができます。SYSPUNCHに指定したデータセットにソース・コードが生成されます。これを、人が最終的に確認と手直ししてソース・コードを復元することができます。
ソース・コードがきちんと管理されていればよいのですが、古いシステムだと行方不明になることもよくあります。保守を続けるためやモダナイズするために失われたソース・コードの復元が必要になることもあると思います。そうしたときにDisassemblerは使えるツールのひとつだと思います。
Discussion