🚀

IBM Disassemblerでアセンブラー・プログラムのソース・コードを復元する

2024/02/19に公開

はじめに

1980年代に開発されたアセンブラーのプログラムのソース・コードとロード・モジュールの内容が一致しない、つまりソース・コードが行方不明になってしまった状況に遭遇してしまいました。開発されたのは古いですが、今でも使用しているツールであり、今後のためにソース・コードをなんとか復元しなくてはなりません。そこではじめてDisassemblerを使ってソースコードの復元にチャレンジしました。

(おわかりだと思いますがIBMのメインフレームでz/OSの下で稼働するプログラムです。)

Disassemblerのマニュアルは使用例の解説がなく、実際の動作のイメージがなかなかわきません。また、Disassemblerの日本語情報が見つけられなかったので、この記事にまとめました。Disassemblerのマニュアルは以下を参照してください

https://www.ibm.com/docs/en/hla-and-tf/1.6?topic=guide-using-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に指定したデータセットにソース・コードが出力されます。

  1. ディレクトリ情報
  • DisasmするCSECTを含むモジュールのディレクトリ・エントリ
  1. ESD テーブル
  • モジュールで見つかったすべての外部シンボル・エントリーのフォーマットされたリスト
  1. RLD テーブル
  • このCSECTに関連するすべての再配置辞書エントリのフォーマットされたリスト。
  • V-CONやマルチCSECTのA-CONがリストされる
  1. ユーザー入力レコード
  • SYSINに入力した内容
  1. ラベル表
  • ESDエントリ、RLDエントリ、およびUSINGステートメント処理の結果生成された名前から作成されたものを含む、Disasmに使用されるすべてのラベルのリスト。
  • プログラムのベースレジスターやDSECT情報を指定するとラベルを生成し、その一覧がここにリストされる
  1. テキスト
  • DisasmされるCSECTを構成するテキストのストレージ・ダンプ形式のリスト。
  1. ソース・リスト
  • 命令が生成された 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指定例

おわりに

以上のようにベース・レジスターやDSECTの情報を追加していくことで人が読んでわかるソース・コードに近づけていくことができます。SYSPUNCHに指定したデータセットにソース・コードが生成されます。これを、人が最終的に確認と手直ししてソース・コードを復元することができます。

ソース・コードがきちんと管理されていればよいのですが、古いシステムだと行方不明になることもよくあります。保守を続けるためやモダナイズするために失われたソース・コードの復元が必要になることもあると思います。そうしたときにDisassemblerは使えるツールのひとつだと思います。

Discussion