COBOLWorx GnuCOBOL and Debugger

The next step in delving back into COBOL is to get a working debugger. While using DISPLAY debug lines was the only debugging I had available when I did this for a living, there should be no reason to have to do that these days!

After going thru the gnuCOBOL FAQ, I found there is a way to make use of the GNU Debugger (gdb). Unfortunately the link to the debugger in the FAQ is dead. After a little digging I found cobcd, the COBOL debugger pre-processor  on the COBOLWorx site.

I spent quite some time getting cobcd to ‘make’. This is because if you want to run it on Windows you must compile using MinGW/MSYS2 and I have been using the older MSYS.

Even after I got it to compile under MSYS2, it failed to work with Arnold Trembley’s version of the gnuCOBOL I’ve been using. After several days of hair pulling, I finally decided to contact COBOLWorx directly. Bob from COBOLWorx responded quickly and confirmed that cobcd doesn’t play well with Anold Trembley’s version of gnuCOBOL.

Download and Install COBOLWorx GnuCOBOL and the Cobc Pre-Processor

If you are using Windows, the easiest way to get the cobcd pre-processor to work is to download the COBOLWorx’s Windows version of the compiler and debugger. In fact, their build of the compiler is so easy to install, I’d recommending using it even if you aren’t planning to use the cobcd debugger.

The download page for the COBOLWorx versions of gnuCOBOL is here.

Instead of messing with MinGW, I just downloaded the self-extracting installer for Windows found at the top of this page.

If, for some reason, the above link fails, the copy I downloaded is here. However, expect this  version to go out of date quickly.

Once you have the self-extracting EXE file downloaded, double-click on it and let it install. The program is a bit slow to install.

I installed the compiler into the default c:\gnucobol directory.

As the install completes you will see this dialog box:

I recommend you say yes and let it add these environment variables. Beats having to mess with the environment when you want to run the compiler.

Now reboot the PC to make sure everything gets setup properly.

Dealing with Microsoft Python

To test the install, type cobcd and you should see:

C:\cobol>cobcd
This is the cobc debugging wrapper [Version 3.17]
Use it as you would 'cobc'
C:\cobol>

If you instead only get a DOS prompt and no version information, chances are the MicroSoft Python is before gnuCOBOL’s python in your path. The following change will correct this problem.

Microsoft decided to put a stub program into your WindowsApps directory that does nothing except invoke the Microsoft Store. This breaks cobcd. Correcting the problem involves putting the gnuCOBOL path ahead of WindowsApps.

If you look at the path for your user, you will find WindowsApps is searched before gnucobol:

Simply move gnucobol above windowsapps:

The Test Program

Before using the debugger, we need a program to compile. Here is a sample program I’ll be using:

       >>source free
identification division.
program-id.
    testgdb.

data division.
working-storage section.

01  dst-rec.
    03  lastname                        pic x(20).
    03  firstname                       pic x(20).
    03  birthdate                       pic 999999.
    03  age                             pic zz9. 
    03  salary                          pic $$$$,$$9.99.

01  kount                               pic s9(4), comp.

01  src-rec.
    03  lastname                        pic x(20),          value "SMITH".
    03  firstname                       pic x(20),          value "JOHN".
    03  birthdate                       pic 999999,         value 800715.
    03  age                             pic s9(4), comp,    value 35.
    03  salary                          pic s9(6)v99, comp-3, value 123456.78.

procedure division.

    move "TEST"                         to lastname of dst-rec.
    move 20                             to age      of dst-rec.
    move 50000                          to salary   of dst-rec.

    perform varying kount from 1 by 1 until kount > 10,
        add 1                           to age of src-rec;
        end-perform.

    move corresponding src-rec          to dst-rec.

    display "dst-rec:".
    display dst-rec.

    stop run.

Setting Up GDB

The first time you attempt to run GDB, you will get a warning:

C:\cobol>gdb -q testgdb.exe
Reading symbols from optfde01.exe...
warning: File "C:\cobol\optfde01-gdb.py" auto-loading has been declined by your `auto-load safe-path' set to "$debugdir:$datadir/auto-load".
To enable execution of this file add
        add-auto-load-safe-path C:\cobol\optfde01-gdb.py
line to your configuration file "C:\Users\me/.gdbinit".
To completely disable this security protection add
        set auto-load safe-path /
line to your configuration file "C:\Users\me/.gdbinit".
For more information about this security protection see the
"Auto-loading safe path" section in the GDB manual.  E.g., run from the shell:
        info "(gdb)Auto-loading safe path"
(gdb)

To fix this, we need to add set auto-load safe-path / to the file .gdbinit.

It easy enough to create c:\users\<username>\.gdbinit with the proper contents:

C:\Users\me>type \users\me\.gdbinit
set auto-load safe-path /

Compiling the Test Program

Here is an example of compiling the program with debugging and running it w/o invoking the debugger:

C:\cobol\gdbDebugging>cobcd -x testgdb.cbl
C:\cobol\gdbDebugging>testgdb.exe
dst-rec:
SMITH               JOHN                800715 45$123,456.78

C:\cobol\gdbDebugging>

Setting the Initial Breakpoint

To invoke the debugger (-q omits printing the header):

C:\cobol\gdbDebugging>gdb -q testgdb.exe
Reading symbols from testgdb.exe...
registering CPrint (Usage is "print  ") [Version 3.17]
registering CWatch (Usage is "cwatch ")
(gdb)

If you start execution of the program, it will run what I assume is part of the run-time library. I’ve found the best way to bypass this code is to manually set a breakpoint on the first executable line.

First use the search command to find the procedure division and then list to see the lines around it:

(gdb) search procedure
25      procedure division.
(gdb) l
20          03  firstname                       pic x(20),          value "JOHN".
21          03  birthdate                       pic 999999,         value 800715.
22          03  age                             pic s9(4), comp,    value 35.
23          03  salary                          pic s9(6)v99, comp-3, value 123456.78.
24
25      procedure division.
26
27          move "TEST"                         to lastname of dst-rec.
28          move 20                             to age      of dst-rec.
29          move 50000                          to salary   of dst-rec.

Line 27 is the first executable line, so set a breakpoint there and use continue to let the program run until it hits the breakpoint:

gdb) break 27
Breakpoint 1 at 0x4016e3: file testgdb.cbl, line 27.
(gdb) run
Starting program: C:\cobol\gdbDebugging\testgdb.exe
[New Thread 5136.0xc98]
[New Thread 5136.0x18a8]
[New Thread 5136.0x16e8]

Thread 1 hit Breakpoint 1, testgdb_ (entry=0) at testgdb.cbl:27
27          move "TEST"                         to lastname of dst-rec.
(gdb)

Use Continue to Execute Until Next Breakpoing

Once the program is executing, you use continue, rather than start, to execute code until the next breakpoint is encountered:

(gdb) break 28
Breakpoint 2 at 0x401713: file testgdb.cbl, line 28.
(gdb) continue
Continuing.

Thread 1 hit Breakpoint 2, testgdb_ (entry=0) at testgdb.cbl:28
28          move 20                             to age      of dst-rec.
(gdb)

Using Step and Next

Step executes the next line and stop, but if a procedure is called, it will stop inside of the called procedure.

Next executes the next line without stopping inside of any called procedures. This is typically what I want to have happen.

After you type either, simply pressing enter will execute the command again (this is true of any command).

(gdb) next
29          move 50000                          to salary   of dst-rec.
(gdb)<cr>
31          perform varying kount from 1 by 1 until kount > 10,
(gdb)

Examining Contents of Variables

Typing p* will display all variables:

(gdb) p*
 1 : 01 dst-rec/testgdb [W-S] : "TEST", ' ' , "000000 20 $50,000.00"
 2 : 03 lastname/dst-rec/testgdb [W-S] : "TEST                "
 3 : 03 firstname/dst-rec/testgdb [W-S] : "                    "
 4 : 03 birthdate/dst-rec/testgdb [W-S] : "000000"
 5 : 03 age/dst-rec/testgdb [W-S] :  20
 6 : 03 salary/dst-rec/testgdb [W-S] :  $50,000.00
 7 : 01 kount/testgdb [W-S] : +0000
 8 : 01 src-rec/testgdb [W-S] : [534d495448", 20 , 4a4f484e", 20 , 3830303731350023012345678c]
 9 : 03 lastname/src-rec/testgdb [W-S] : "SMITH               "
10 : 03 firstname/src-rec/testgdb [W-S] : "JOHN                "
11 : 03 birthdate/src-rec/testgdb [W-S] : "800715"
12 : 03 age/src-rec/testgdb [W-S] : +0035
13 : 03 salary/src-rec/testgdb [W-S] : [012345678c]

To see the value of only kount, type:

(gdb) p kount
01 kount/testgdb [W-S]  :  +0000

I don’t make a practice of using MOVE CORRESPONDING so I don’t normally need to qualify variables with OF, but I wanted to see how they work in the debugger. To see the value of LASTNAME OF SRC-REC:

(gdb) p lastname/src-rec
03 lastname/src-rec/testgdb [W-S]  :  "SMITH               "

Using cwatch to Display Variable When It Changes

Here I ‘watch’ the value of kount change in the PERFORM loop. Every time the value changes, the program pauses execution:

(gdb) l
26
27          move "TEST"                         to lastname of dst-rec.
28          move 20                             to age      of dst-rec.
29          move 50000                          to salary   of dst-rec.
30
31          perform varying kount from 1 by 1 until kount > 10,
32              add 1                           to age of src-rec;
33              end-perform.
34
35          move corresponding src-rec          to dst-rec.
(gdb) break 35
Breakpoint 3 at 0x40189d: file testgdb.cbl, line 35.
(gdb) cwatch kount
Hardware watchpoint 4: *(char(*)[2])(0x406060)

(gdb) continue
Continuing.

Thread 1 hit Hardware watchpoint 4: *(char(*)[2])(0x406060)

Old value = "\000"
New value = "\000\001"
0x630cf909 in ?? () from c:\gnucobol\bin\libcob-4.dll
(gdb)<cr>
Continuing.

Thread 1 hit Hardware watchpoint 4: *(char(*)[2])(0x406060)

Old value = "\000\001"
New value = "\000\002"
0x0040188a in cob_addswp_s16 (val=, p=0x406060 ) at testgdb.cbl:32
32              add 1                           to age of src-rec;
(gdb) info break
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x004016e3 in testgdb_ at testgdb.cbl:27
        breakpoint already hit 1 time
2       breakpoint     keep y   0x00401713 in testgdb_ at testgdb.cbl:28
        breakpoint already hit 1 time
3       breakpoint     keep y   0x0040189d in testgdb_ at testgdb.cbl:35
4       hw watchpoint  keep y              *(char(*)[2])(0x406060)
        breakpoint already hit 2 times
(gdb) disable 4
(gdb) continue
Continuing.

Thread 1 hit Breakpoint 3, testgdb_ (entry=0) at testgdb.cbl:35
35          move corresponding src-rec          to dst-rec.
(gdb)

Finally, let the MOVE CORRESPONDING execute and compare records:

gdb) n
37          display "dst-rec:".
(gdb) p *
 1 : 01 dst-rec/testgdb [W-S] : "SMITH               JOHN                800715 45$123,456.78"
 2 : 03 lastname/dst-rec/testgdb [W-S] : "SMITH               "
 3 : 03 firstname/dst-rec/testgdb [W-S] : "JOHN                "
 4 : 03 birthdate/dst-rec/testgdb [W-S] : "800715"
 5 : 03 age/dst-rec/testgdb [W-S] :  45
 6 : 03 salary/dst-rec/testgdb [W-S] : $123,456.78
 7 : 01 kount/testgdb [W-S] : +0011
 8 : 01 src-rec/testgdb [W-S] : [534d495448", 20 , 4a4f484e", 20 , 383030373135002d012345678c]
 9 : 03 lastname/src-rec/testgdb [W-S] : "SMITH               "
10 : 03 firstname/src-rec/testgdb [W-S] : "JOHN                "
11 : 03 birthdate/src-rec/testgdb [W-S] : "800715"
12 : 03 age/src-rec/testgdb [W-S] : +0045
13 : 03 salary/src-rec/testgdb [W-S] : [012345678c]
(gdb) c
Continuing.
dst-rec:
SMITH JOHN 800715 45$123,456.78
[Thread 8156.0x11d8 exited with code 0]
[Thread 8156.0xf14 exited with code 0]
[Thread 8156.0x96c exited with code 0]
[Inferior 1 (process 8156) exited normally]
(gdb)

There you go, gdb for gnuCOBOL. Pretty slick!

This entry was posted in c-gnuCOBOL and tagged , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.