Eclipseを使用したメインフレーム・アプリケーション開発 - (5)デバッガーの利用

# はじめに
アプリケーションを開発/修正する際には、コードを実際に動かしてみてトライ&エラーを繰り返すということはよくある話です。その際にデバッガーを使ってコードをステップ実行させたり、稼働中の変数の状態を確認したりすることは有用です。
ここでは、Eclipseのツールからデバッガーを使ってz/OS上のアプリケーション(COBOL)を動かしてみます。

当記事ではWazi Developer for Eclipse(V1.1.0)を使ってみた時の内容を記載します。

## 関連記事
[Eclipseを使用したメインフレーム・アプリケーション開発 - (1)概要](https://qiita.com/tomotagwork/items/0a6b273473333f849116)
[Eclipseを使用したメインフレーム・アプリケーション開発 - (2)z/OS基本操作](https://qiita.com/tomotagwork/items/67e7faac4ec30198f1d3)
[Eclipseを使用したメインフレーム・アプリケーション開発 - (3)ソース編集](https://qiita.com/tomotagwork/items/2db56a0981e8dac57a20)
[Eclipseを使用したメインフレーム・アプリケーション開発 - (4)ソース管理ツール/ビルドツール連携](https://qiita.com/tomotagwork/items/da039208be2f77c21285)
[Eclipseを使用したメインフレーム・アプリケーション開発 - (5)デバッガーの利用](https://qiita.com/tomotagwork/items/93457c9ba11870a7c357)

# 全体像
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/31dc0bb2-0842-fe41-6767-44a30abcaffc.png)

ざっくりとしたデバッガーの利用イメージは上の図の通りです。ホスト上でDebugger機能を導入構成しておき、Eclipse側ではデバッグ情報を受け取るデーモン(Debug UI Daemon)を準備しておいて、Eclipse上でデバッグ(ステップ実行など)を行います。

ここでz/OS Debuggerというのはz/OS上で稼働するデバッグ機能を提供するコンポーネントを表しています。IBMさんのプロダクトは相変わらず製品体系や名前が分かりにくいものが多いですね。
IBM z/OS Debugger, IBM Debug for z/OS, IBM Debug for z Systemsなど少しずつ表記にゆらぎがあったりします。この機能は単体製品として提供されているものもありますし、IBM Developer for z/OS や IBM Wazi Developer for Red Hat CodeReady Workspacesなどに含まれて提供されていたりします。Continuous Deliveryなのでまたそれがさらにややこしい...。
ここではz/OS Debuggerと表記しますが、いずれにしても有償製品に付属しているデバッガー機能を指します。
z/OS Debuggerはずいぶん前から提供されている機能で、3270端末エミュレーター(PCOMなど)をUIとしてデバッグするようなこともできますが、ここではEclipseをUIとして考えているので、画面をリモート(ここではEclipse)に飛ばす、"リモート・デバッグ"と呼ばれるやり方にフォーカスします。また、デバッグ対象のアプリケーションも言語環境やミドルウェア(バッチ、CICS、IMSなど)によってもやり方は変わってくるのですが、とりあえずここではシンプルなCOBOLバッチのケースを試してみます。

※ここで注意が必要なのは、z/OS側からPC上のEclipseに対してTCPのコネクションを張る必要があるということです。z/OSのネットワークの構成としてそのような方向のコネクション確立が許されていないケースもあるのでその場合は別途考慮が必要です(後述)。

参考
[IBM z/OS Debugger の概要](https://www.ibm.com/docs/ja/wdfrhcw/1.2.0?topic=overview-zos-debugger)
[What's new in IBM z/OS Debugger V15.0 ](https://community.ibm.com/community/user/ibmz-and-linuxone/blogs/violaine-batthish1/2020/10/30/whats-new-in-ibm-zos-debugger-v150)
[Installation of IBM Developer for z Systems and IBM Debug for z Systems](https://community.ibm.com/community/user/ibmz-and-linuxone/blogs/blog-entry1/2016/12/02/installation-of-ibm-developer-for-z-systems-and-ibm-debug-for-z-systems)
[Announcement Letter - IBM Application Delivery Foundation for z/OS 3.3 and IBM Developer for z/OS Enterprise Edition 15.0 expand IDE choices to include support for Red Hat CodeReady Workspaces and move to Eclipse Photon 4.8 in alignment with IBM Explorer for z/OS Aqua 3.2](https://www-01.ibm.com/common/ssi/cgi-bin/ssialias?subtype=ca&infotype=an&appname=iSource&supplier=897&letternum=ENUS220-425)
[IBM Debug for z/OS](https://www.ibm.com/products/debug-for-zos/details)

# 構成(事前準備)
今回はIBM Wazi Developer for Red Hat CodeReady Workspacesを使った場合の例を示します。この製品はOpenShift上で稼働するz/OSエミュレーターが提供されていますが、はじめからz/OS Debuggerの構成もされた状態となっています。

## z/OS側
z/OS側の構成はここでは詳細は割愛します。基本はドキュメントの記述に従って構成してください。
参考: [z/OS Debugger](https://www.ibm.com/docs/ja/wdfrhcw/1.2.0?topic=components-zos-debugger)

ここでは、IBM Wazi Developer for Red Hat CodeReady WorkspacesV1.1.0のSandboxでデフォルトで提供されているz/OS Debuggerの構成をそのまま使用します。

## Eclipse側

### Debug UI Daemonの有効化
デバッグ・パースペクティブ - デバッグ・ビューから、以下のアイコンをクリックして、"Start listening on port: 8001"を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/3dbc512f-1b95-e2ab-8e3e-34520db2916f.png)

アイコンが緑色に変わればOKです。これで、Debug UI Daemonが有効化され、8001番ポートでListenしている状態になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/7abfca8b-8f0f-8b0b-5576-95975282b3e8.png)

### ビジュアル・デバッグの有効化
ついでにビジュアル・デバッグ機能を有効化しておきます。デバッグ・パースペクティブで、以下のアイコンをクリックし、"ビジュアル・デバッグを有効にする"を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/3a9ebcab-54d7-00d4-c1d2-1ee3b91badd4.png)
アイコン上の斜線が消えればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/e49fa0e8-1777-1238-7485-0b7157f3374e.png)

### SSHポートフォワードの設定
今回、ホストは実際のz/OSではなくWaziというオープンシフト上で稼働するz/OSエミュレーターを使用する想定です。この環境は、z/OSから外部へのTCP/IP接続が制限されているのでそのままではデバッガーのセッションが張れません。そこで、今回はSSHポートフォワードの設定を行っておくことにします。
※z/OSからPCへのTCPコネクション確立が可能な環境であれば当設定は不要です。

イメージとしては以下の通りです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/658ab886-c4bf-f78d-cbdb-e12bb1afe096.png)


事前にSSHのトンネルを作っておくことで、z/OS側のDebuggerからローカルの18001ポートに接続するとそれがPC側の8001番ポートにフォワードされるようにしておきます。
SSHポートフォワードを行うので、当然のことながらz/OS側にはSSHサーバーを立てておく必要があります。
Windowsからsshコネクションを張るにはsshクライアントが必要ですが、WSL(Windows Subsystem for Linux)を使うのが手っ取り早いと思いますので、ここではWSLからsshコマンドを使うことにします。
(teratermなどでも可です。参考: [SSH転送(ポートフォワード)](https://qiita.com/tomotagwork/items/b35aa09c1aa1acb3d554#ssh%E8%BB%A2%E9%80%81%E3%83%9D%E3%83%BC%E3%83%88%E3%83%95%E3%82%A9%E3%83%AF%E3%83%BC%E3%83%89) )

WSLの端末から以下のコマンドを実行しておきます。

```xxx:sshコマンド実行例
$ ssh -p 31175 -N -f -R 18001:localhost:8001 -o 'StrictHostKeyChecking no' -o 'ServerAliveInterval=60' -o 'ServerAliveCountMax=3' TAGUCHI@OCPW01
```

`-R 18001:localhost:8001` という指定が、リモート(z/OS側)の18001ポートをlocalhostの8001ポートにフォワードすることを意味しています。

※複数ユーザーで同一z/OSに接続してデバッグ実行する場合は、z/OS側でListenするポート番号(ここでは18001)がバッティングしないよう、ユーザーごとにポート番号を使い分けるなど考慮が必要です。

# 使用例


## サンプルCOBOLプログラム準備

### COBOLソース作成

単純なCOBOLソースを準備します。

<details><summary><font color="blue">ソース: HELLO1</font></summary><div>

```cbl:HELLO1
      ***************************************************************
       IDENTIFICATION DIVISION.
       PROGRAM-ID. HELLO1.
       ENVIRONMENT DIVISION.

      *****************************************************************
       DATA DIVISION.
      *****************************************************************
       WORKING-STORAGE SECTION.
      *****************************************************************
      *
       01  SYSTEM-DATE-AND-TIME.
           05  CURRENT-DATE.
               10  CURRENT-YEAR            PIC 9(2).
               10  CURRENT-MONTH           PIC 9(2).
               10  CURRENT-DAY             PIC 9(2).
           05  CURRENT-TIME.
               10  CURRENT-HOUR            PIC 9(2).
               10  CURRENT-MINUTE          PIC 9(2).
               10  CURRENT-SECOND          PIC 9(2).
               10  CURRENT-HNDSEC          PIC 9(2).

      *****************************************************************
       PROCEDURE DIVISION.
      *****************************************************************
       000-MAIN.
           DISPLAY "***** Begin *****".
           ACCEPT CURRENT-DATE FROM DATE.
           ACCEPT CURRENT-TIME FROM TIME.
           DISPLAY 'SAM1 STARTED DATE = ' CURRENT-MONTH '/'
                  CURRENT-DAY '/' CURRENT-YEAR  '  (mm/dd/yy)'.
           DISPLAY '             TIME = ' CURRENT-HOUR ':'
                  CURRENT-MINUTE ':' CURRENT-SECOND.
           DISPLAY "HELLO WORLD!".
           DISPLAY "***** End *****".

           GOBACK .
```

</div></details>

### コンパイル/リンク

<details><summary><font color="blue">コンパイル/リンク用JCL</font></summary><div>

```jcl:HELCOMP
//HELCOM  JOB ,NOTIFY=&SYSUID,
// MSGCLASS=H,MSGLEVEL=(1,1),REGION=0M
//*****************************************************************
//    SET HLQ='TAGUCHI'                       *TSO USER ID
//    SET CMPLLIB='IGY630.SIGYCOMP'       *COMPILER LIBRARY
//    SET LINKLIB='CEE.SCEELKED'              *LINK LIBRARY
//    SET SPACE1='SYSALLDA,SPACE=(CYL,(1,1))' *SPACE ALLOCATION
//    SET SPACE2='SYSALLDA,SPACE=(CYL,(1,1))' *SPACE ALLOCATION
//*
//***************************
//*                         *
//*  COMPILE HELLO1        **
//*                         *
//***************************
//*
//CMPLHEL1 EXEC PGM=IGYCRCTL,PARM='LIST,MAP,NODYN,TEST(SOURCE)'
//STEPLIB  DD DISP=SHR,DSN=&CMPLLIB
//SYSIN    DD DISP=SHR,DSN=&HLQ..SAMPLE.COBOL(HELLO1)
//SYSLIB   DD DISP=SHR,DSN=&HLQ..SAMPLE.COBCOPY
//SYSLIN   DD DISP=OLD,DSN=&HLQ..SAMPLE.OBJ(HELLO1)
//SYSPRINT DD SYSOUT=*
//SYSMDECK DD UNIT=&SPACE1
//SYSUT1   DD UNIT=&SPACE1
//SYSUT2   DD UNIT=&SPACE1
//SYSUT3   DD UNIT=&SPACE1
//SYSUT4   DD UNIT=&SPACE1
//SYSUT5   DD UNIT=&SPACE1
//SYSUT6   DD UNIT=&SPACE1
//SYSUT7   DD UNIT=&SPACE1
//SYSUT8   DD UNIT=&SPACE1
//SYSUT9   DD UNIT=&SPACE1
//SYSUT10  DD UNIT=&SPACE1
//SYSUT11  DD UNIT=&SPACE1
//SYSUT12  DD UNIT=&SPACE1
//SYSUT13  DD UNIT=&SPACE1
//SYSUT14  DD UNIT=&SPACE1
//SYSUT15  DD UNIT=&SPACE1
//*
//***************************
//*                         *
//*  LINK HELLO1            *
//*                         *
//***************************
//*
//LINKHEL1 EXEC PGM=IEWL,REGION=3000K
//SYSLMOD  DD  DISP=SHR,DSN=&HLQ..SAMPLE.LOAD
//SYSPRINT DD  SYSOUT=*
//SYSUT1   DD  UNIT=&SPACE2
//SYSLIB   DD  DISP=SHR,DSN=&LINKLIB
//         DD  DISP=SHR,DSN=&HLQ..SAMPLE.LOAD
//OBJ      DD  DISP=SHR,DSN=&HLQ..SAMPLE.OBJ
//SYSLIN   DD *
     INCLUDE OBJ(HELLO1)
     NAME HELLO1(R)
/*
```

</div></details>

デバッグを行うためにコンパイル・オプションとして`TEST(SOURCE)`を追加してコンパイル/リンクします。

参考: [COBOL プログラムでの TEST または NOTEST コンパイラー・サブオプションの選択](https://www.ibm.com/docs/ja/wdfrhcw/1.2.0?topic=ccod-choosing-test-notest-compiler-suboptions-cobol-programs-1)

### 実行用JCL準備

<details><summary><font color="blue">実行用JCL</font></summary><div>

```jcl:HELRUN2
//HELRUN2 JOB ,NOTIFY=&SYSUID,
// MSGCLASS=H,MSGLEVEL=(1,1),REGION=0M
//*****************************************************************
//    SET HLQ='TAGUCHI'                       *TSO USER ID
//    SET CMPLLIB='IGY630.SIGYCOMP'       *COMPILER LIBRARY
//    SET LINKLIB='CEE.SCEELKED'              *LINK LIBRARY
//    SET SPACE1='SYSALLDA,SPACE=(CYL,(1,1))' *SPACE ALLOCATION
//    SET SPACE2='SYSALLDA,SPACE=(CYL,(1,1))' *SPACE ALLOCATION
//*
//*************************
//* RUN HELLO1
//*************************
//HELLO1 EXEC   PGM=HELLO1
//STEPLIB  DD DSN=EQAF00.SEQAMOD,DISP=SHR
//         DD DSN=&HLQ..SAMPLE.LOAD,DISP=SHR
//CEEOPTS DD *
TEST(,,,TCPIP&localhost%18001:*)
//*
//SYSOUT   DD SYSOUT=*
//SYSUDUMP DD SYSOUT=*
//*
```

</div></details>

デバッグを行うために、ランタイム・オプションとしてCEEOPTS DDにて`TEST(,,,TCPIP&localhost%18001:*)`を追加しています。ここで指定している`localhost%18001`はDebug UI DaemonがListenしているアドレス+ポートです。今回はポートフォワード設定をしているので、localhostの18001番ポートを指定しています。
また、STEPLIBにてデバッガーのライブラリ(EQAF00.SEQAMOD)をロードモジュールのライブラリより上位にコンカチしています。

参考:
[z/OS Debugger を開始する方法の選択](https://www.ibm.com/docs/ja/wdfrhcw/1.2.0?topic=session-choosing-method-methods-starting-zos-debugger)
[例: TEST ランタイム・オプション](https://www.ibm.com/docs/ja/wdfrhcw/1.2.0?topic=string-example-test-runtime-options)
[Language Environment®内で始動される プログラムのための z/OS Debugger の始動](https://www.ibm.com/docs/ja/wdfrhcw/1.2.0?topic=szdbtp-starting-zos-debugger-programs-that-start-in-language-environment)

## Debug実行
実際にDebugを行ってみます。

### プログラム実行
上で準備したTESTランタイムオプション付きJCLをSubmitします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/a8da4b90-c884-43c8-96b8-f3f85c1e1743.png)

OK
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/4a3ffbb9-591d-b1c2-b40c-938d8e9e4e45.png)

以下のメッセージが出るので"切り替え"をクリックしてデバッグ・パースペクティブに切り替えます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/e37b0239-05d4-22a5-be10-508d65780c78.png)

### デバッグセッションでの操作
デバッグ・パースペクティブに切り替えると、デバッグ対象のソースが表示されてステップ実行などが行えるようになります。変数の状態や、ビジュアル・デバッグ・ビューにてプログラムの構造などを確認しながらデバッグを行うことができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132331/ade54020-8ad0-9aa0-67df-b91d62bf2716.png)

# おわりに
ここではシンプルなCOBOLバッチをVSCodeでデバッグする例を示しました。言語、コンパイラのバージョン、実行環境(CICS,IMS,...)などによって、準備の仕方などが異なってきますが、ここで示した例をベースとして必要な設定等を拡張していけばよいと思います。