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!