Walkthrough of the COBOLworx cbl-gdb source-level debugging package.

December 18, 2021

The COBOLworx cbl-gdb package provides source-level debugging of COBOL programs compiled with the GnuCOBOL compiler. Our goal here is to provide a general understanding of how the package works, and show what it can do.

List of cbl-gdb commands

If you are looking for a summary list of the commands provided by the cbl-gdb source-level debugger, try here.

Design Goals

Our design goal was to provide an easy-to-use source-level debugging package for COBOL programs. We didn’t want to clutter up the COBOL source code with special debugging grammar; we wanted to use the free GnuCOBOL compiler; and we wanted to leverage the GNU GDB debugger for the actual debugging.

The challenge in using the GDB debugger is that it doesn’t understand COBOL, and we have chosen not to embark on the significant effort of creating a COBOL module for GDB.

We have, instead, developed a way to convince GDB that it is debugging a program written in the C Programming Language. The target executable, however, is altered so that the embedded debugging information points back to the original COBOL source code, and we have created an extension to GDB, written in Python, that manages the display of COBOL variables and their contents.

How It Is Used

Ordinarily the GnuCOBOL compiler is invoked from the command line like this:

cobc -x soh.cbl

This compiles the soh.cbl source code, and produces an executable named soh on Linux systems, and soh.exe on Windows.

When debugging information is desired, the cobc command is replaced with cobcd:

cobcd -x soh.cbl

Although GnuCOBOL has many possible command-line options, we have tried to make using the cbl-gdb compilation process as compatible as possible. If you manage to come up with a set of command-line options that works with COBC and that you think ought to work with COBCD, but doesn’t, please let us know about it. (Send such inquiries to support@cobolworx.com )

What Is It Doing?

GnuCOBOL works by translating the COBOL code into C code, which is then fed into the GCC compiler to produce an executable.

The cbl-gdb extensions work by interjecting additional steps that add COBOL variable-name identifier information into the executable, modifies the debugging information to point back to lines of the original COBOL source code, and includes an instruction to load a COBOL-aware Python script that extends GDB.

This process is explained in more detail in Appendix A.

GDB initialization

Some users prefer to configure their systems so that the Python script is always loaded when GDB is started. In a default installation, the Python script is found at /usr/bin/cobcd.py or perhaps at /usr/local/bin/cobcd.py. GDB will load that script if there is a source /usr/bin/cobcd.py instruction in a .gdbinit file along with the executable, or in the user’s ~/.gdbinit.

If GDB was built with the --with-system-gdbinit option, there is a global file where the source ... instruction can go. In GDB, execute show configuration to see if that’s the case on your system.

Leveraging GDB

The GNU GDB debugger is a sophisticated and powerful debugging tool; its documentation is more than a bit daunting. But developers who have used it for actual debugging know that just a few commands are used the most.

From the command line, gdb soh means, “Start debugging the soh program.”

After GDB loads, you are presented with a (gdb) prompt. Here are the commands that are used the most when operating in pure command-line mode:

(gdb) list 1,30        # list source code lines 1 through 30
(gdb) list             # show the next ten lines
(gdb) list -           # show the previous ten lines
(gdb) break 10         # set a breakpoint at line 10
(gdb) run              # start running the program from the beginning
(gdb) frame            # show where the program is stopped, and show the
                       # next line to be executed
(gdb) backtrace        # show the stack
(gdb) continue         # continue from the point where the program is
                       # currently stopped
(gdb) continue N       # when proceeding from a breakpoint, continue
                       # until that same breakpoint is encountered
                       # N times
(gdb) finish           # continue until the current function returns
(gdb) watch <var>      # stop execution when <var> changes
(gdb) next             # Execute a single instruction, stepping OVER
                       # subroutine calls
(gdb) step             # Execute a single instruction, stepping INTO
                       # subroutine calls
(gdb) cprint <var>     # Show the contents of <var>
(gdb) cprint <var>=123 # Alter the contents of <var> to be 123

There are numerous other features, including deleting, disabling, and enabling breakpoints; displaying memory at specific locations in various formats and much more. In addition to the official GNU documentation, there are numerous “cheat sheets” and guides to be found on the internet.

GDB doesn’t require that the entire command be entered; you can use b for break, n for next, and so on. Also, when you are repeating a command – it’s often the case that you will be entering, for example, next repeatedly – you can just keep pressing the Enter key.

And, finally, although we won’t be using it here, you might want to see if $ gdb -tui soh works on your system. If it does, some users might find GDB’s Text User Interface presentation to be useful. On the other hand, it requires that NCURSES be configured properly, which is often a headache.

Enough Talk. Let’s Look at a Program

For a sample, we are going to look at the SOH program. (“SOH” means “Sack Of Hammers”, as in “Dumb as a sack of hammers.”)

        IDENTIFICATION DIVISION.
        PROGRAM-ID. SOH.

        DATA DIVISION.
        WORKING-STORAGE SECTION.
        77 HEADER PICTURE X(64) VALUE SPACE.
        77 ONE    PICTURE 9999  VALUE 0.
        77 TWO    PICTURE 9999  VALUE 0.
        77 THREE  PICTURE 9999  VALUE 0.

        PROCEDURE DIVISION.
        MOVE  "Demonstration of addition" to HEADER.
        DISPLAY HEADER.
        MOVE 1 TO ONE.
        MOVE 2 TO TWO.
        ADD ONE TO TWO GIVING THREE.
        DISPLAY ONE " plus " TWO " equals " THREE.
        END PROGRAM SOH.

For anybody new to COBOL: Every COBOL program has to have an IDENTIFICATION division. The WORKING-STORAGE section is where variable definitions go. This program defines four variables: one 64-character alphanumeric string, and three four-digit numeric variables.

The PROCEDURE division is where executable code goes. I will refrain from explaining what SOH does. (If you can’t figure it out, I suggest that you are in the wrong place.)

Let’s compile and run that program on an Ubuntu 18.04 Linux system:

$ cobcd -x soh.cbl
$ ./soh
Demonstration of addition
0001 plus 0002 equals 0003
$

No surprises there.

We compiled it with COBCD, so we are ready to use GDB to debug it. We’ll launch GDB with the -q command line option to avoid a lot of boilerplate:

$ gdb -q rtest
Reading symbols from test...
Registering the CBL-GDB debugger [Version 4.28].  Help is available for
   cprint cwatch cbreak ctbreak ccondition add-symbol-file-cobol cstart cnext
   cup cup-silently cdown cdown-silently cfinish
   finish-out-of-line-perform finish-module until-cobol list-section local-backtrace cbacktrace auto-step
(gdb)

The feedback from GDB shows that it was able to load SOH and its debugging symbols, and that the Python debugging extension was loaded.

We see also that the Python has created five new commands to extend GDB’s capabilities. You can get a quick reminder of each by entering, for example, help cprint.

We can now use the GDB list command to see the program. I am going to ask to see lines 1 through 100 because I know that will show me the whole listing of this short program.

(gdb) list 1,100
1               IDENTIFICATION DIVISION.
2               PROGRAM-ID. SOH.
3
4               DATA DIVISION.
5               WORKING-STORAGE SECTION.
6               77 HEADER PICTURE X(64) VALUE SPACE.
7               77 ONE    PICTURE 9999  VALUE 0.
8               77 TWO    PICTURE 9999  VALUE 0.
9               77 THREE  PICTURE 9999  VALUE 0.
10
11              PROCEDURE DIVISION.
12              MOVE  "Demonstration of addition" to HEADER.
13              DISPLAY HEADER.
14              MOVE 1 TO ONE.
15              MOVE 2 TO TWO.
16              ADD ONE TO TWO GIVING THREE.
17              DISPLAY ONE " plus " TWO " equals " THREE.
18              END PROGRAM SOH.
(gdb)

The General Case of Setting Breakpoints and Looking at Variables

Line 12 is the first executable line, so let’s set a breakpoint there:

(gdb) b 12
Breakpoint 1 at 0x1045: file soh.cbl, line 12.
(gdb)

And then use the run command to launch the program

(gdb) r
Starting program: /home/bob/projects/soh/soh
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, SOH_ (entry=0) at soh.cbl:12
12              MOVE  "Demonstration of addition" to HEADER.
(gdb)

Just to show that we can, indeed, single-step through a COBOL program, I am going to enter the next command, and then repeatedly press Enter until the program finishes running:

(gdb) next
13              DISPLAY HEADER.
(gdb)
Demonstration of addition
14              MOVE 1 TO ONE.
(gdb)
15              MOVE 2 TO TWO.
(gdb)
16              ADD ONE TO TWO GIVING THREE.
(gdb)
17              DISPLAY ONE " plus " TWO " equals " THREE.
(gdb)
0001 plus 0002 equals 0003
[Inferior 1 (process 21007) exited normally]
(gdb)
The program is not being run.
(gdb)

Now, let’s use the run command to start the program again. It’ll stop at line 12 again (the breakpoint is still there), and then we’ll use the cprint command to look at the HEADER variable:

(gdb) r
Starting program: /home/bob/projects/soh/soh
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, SOH_ (entry=0) at soh.cbl:12
12              MOVE  "Demonstration of addition" to HEADER.
(gdb) cprint header
77 HEADER/SOH [W-S]  :  ' ' <repeats 64 times>
(gdb)

A few things to note there.

First, COBOL variable identifiers are not case-sensitive, so it doesn’t matter if you ask for “header” or “HEADER”.

Second, even though GDB shows line 12 as the instruction MOVE "Demonstration of addition" to HEADER., the cprint command reports that the HEADER string is 64 spaces.

This is because GDB shows you the instruction that is going to be executed when the program resumes. That instruction hasn’t happened yet.

Let’s do a next followed by another cprint HEADER:

(gdb) next
13              DISPLAY HEADER.
(gdb) cprint header
77 HEADER/SOH [W-S]  :  "Demonstration of addition", ' ' <repeats 39 times>
(gdb)

We now see that the MOVE has taken place.

You have control over the repeat-count formatting. The threshold level defaults to a count of eleven characters. You can change that to another value, making it larger or smaller. You can also set it to zero, which defeats the counting:

(gdb) cprint set repeat 0
(gdb) cprint header
77 HEADER/SOH [W-S]  :  "Demonstration of addition                                       "
(gdb)

In a program with so few variables, it can be useful to simply look at all of them with the cprint * command:

(gdb) cp *
 1 : 77 HEADER/SOH [W-S] : "Demonstration of addition                                       "
 2 : 77 ONE/SOH [W-S] : 0000
 3 : 77 TWO/SOH [W-S] : 0000
 4 : 77 THREE/SOH [W-S] : 0000
(gdb)

The first column is an ordering number. Then comes the COBOL variable Level and name along with the program it came from. That’s followed by an indication that the variable is in WORKING-STORAGE ([W-S]), LOCAL-STORAGE ([L-S]), LINKAGE ([LNK]), or in a FILE DESCRIPTOR ([I/O]). After that is the variable’s value.

The ordering number can be used in a subsequent cprint command:

(gdb) cp #1
77 HEADER/SOH [W-S]  :  "Demonstration of addition                                       "
(gdb)

This can be used to change the way the a variable is displayed without entering the name again. The way we most frequently use this is when we want to see a variable in the cprint/d “debugging” format (which is used a lot when debugging the debugger).

(gdb) cp/d #1
Name:        77 HEADER/SOH [W-S]
Field:       f_8    ( 0x555555756020 )
Base:        b_8
Attr:        a_2    ( 0x555555555670 )
Offset:      0
location:    0x5555557561a0
Length:      64
AttrType:    33 (0x21) ALPHANUMERIC
AttrDigits:  0
AttrScale:   0
AttrFlags:   0x0
data: (HEX)   [ 44 65 6d 6f 6e 73 74 72 61 74 69 6f 6e 20 6f 66 20 61 64
64 69 74 69 6f 6e 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 ]
data: (ASCII) "Demonstration of addition                                       "
(gdb)

What you see here might make sense when considering that the COBOL variables are actually implemented as data structures in the C code generated by the GnuCOBOL compiler. Those interested in more information can look at the generated C code as well as the source code for GnuCOBOL’s libcob shared library.

Moving on: Using the frame command reminds us that the program is trapped just before line 13:

(gdb) frame
#0  SOH_ (entry=0) at soh.cbl:13
13              DISPLAY HEADER.

Let’s single-step ahead a couple of lines:

(gdb) next
Demonstration of addition
14              MOVE 1 TO ONE.
(gdb) next
15              MOVE 2 TO TWO.
(gdb)

We just executed line 14, MOVE 1 TO ONE, and are about to execute line 15.

Let’s check the value of ONE…

(gdb) cp ONE
77 ONE/SOH [W-S]  :  0001
(gdb)

…and change it to 1,001 using the cprint <var>= construction:

(gdb) cp ONE=1001
77 ONE/SOH [W-S]  :  1001
(gdb)

By stepping ahead another couple of lines and doing another cprint *, we can see the effects of changing ONE to 1,001:

(gdb) next
16              ADD ONE TO TWO GIVING THREE.
(gdb) next
17              DISPLAY ONE " plus " TWO " equals " THREE.
(gdb) cp *
 1 : 77 HEADER/SOH [W-S] : "Demonstration of addition                                       "
 2 : 77 ONE/SOH [W-S] : 1001
 3 : 77 TWO/SOH [W-S] : 0002
 4 : 77 THREE/SOH [W-S] : 1003
(gdb)

Let’s take a look at GDB’s “watchpoint” capability, which we invoke with the cwatch command.

We start the program again, then set a watchpoint on the variable TWO, and then continue from there:

(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/bob/projects/soh/soh
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, SOH_ (entry=0) at soh.cbl:12
12              MOVE  "Demonstration of addition" to HEADER.
(gdb) cwatch TWO
Hardware watchpoint 2: *(char(*)[4])(0x555555756190)

(gdb) continue
Continuing.
Demonstration of addition

Hardware watchpoint 2: *(char(*)[4])(0x555555756190)

Old value = "0000"
New value = "0002"
0x000055555555513f in SOH_ (entry=0) at soh.cbl:15
15              MOVE 2 TO TWO.
(gdb)

You can see that the program halted when the value of TWO changed from “0000” to “0002”.

That should give you a pretty good idea of what you can do with the cbl-gdb package.

Slightly more advanced topics:

The SOH program has only a few variables and only a few instructions. Ordinary COBOL programs often have many different variables. Between them and their related REDEFINES, it’s not unusual for a program to have thousands of variables. When there are dozens of variables the cprint * display can become oppressive; with hundreds or thousands, it can be useless.

We have a feature for coping with that. The cprint ? command looks at a range of lines around the current trap point, and tries to find things that look like variable names, and show only those. By default, the range is set to +/- six lines. The range is adjusted using the cprint/r <range> switch. I am going to trap the SOH program in the middle, set the range to +/- zero lines, and we’ll see what happens:

Breakpoint 1, SOH_ (entry=0) at soh.cbl:12
12              MOVE  "Demonstration of addition" to HEADER.
(gdb) n
13              DISPLAY HEADER.
(gdb) n
Demonstration of addition
14              MOVE 1 TO ONE.
(gdb) n
15              MOVE 2 TO TWO.
(gdb) cprint/r
The 'cprint ?' range is currently +/- 6 lines
(gdb) cprint/r 0
The 'cprint ?' range has been set to +/- 0 lines
77 TWO/SOH [W-S]  :  0000
(gdb) cp *
 1 : 77 HEADER/SOH [W-S] : "Demonstration of addition", ' ' <repeats 39 times>
 2 : 77 ONE/SOH [W-S] : 0001
 3 : 77 TWO/SOH [W-S] : 0000
 4 : 77 THREE/SOH [W-S] : 0000
(gdb) cprint ?
77 TWO/SOH [W-S]  :  0000
(gdb)

You can see how the command cprint/r, without a parameter, simply reported the current range setting. cprint/r 0 changed the range to +/- zero lines, meaning that only the current line is checked for possible variables. That explains why cprint ? only reported the value for TWO; TWO is the only variable referenced in line 15.

Now that you know about the /d and /r switches, you might suspect that there are others. Find them with help cprint:

(gdb) help cprint
Prints COBOL-NAMED variables when debugging cobcd (via cobst) processed object files.

    COBOL cprint command usage:
    cprint VAR          displays VAR using session-default formatting
    cprint VAR!         when VAR is ambiguous, all the possibilities are listed instead of counted
    cprint VAR(N)       displays the Nth element of table <arg>. Multiple subscripts are allowed
    cprint VAR(S:R)     reference modification: displays R characters starting at S in <arg>
    cprint/v0           session formatting is single line, with LEVEL & NAME and VALUE
    cprint/v1           session formatting is single line, just the value
    cprint/v2           session formatting two lines: LEVEL & NAME followed by VALUE
    cprint/v3           like /v0, but with LEVEL & NAME/PROGRAM-ID [STORAGE] and VALUE
    cprint/v4           same as /v1
    cprint/v5           like /v2, but with LEVEL & NAME/PROGRAM-ID [STORAGE] then VALUE
    cprint/v6           like /v0, but with just NAME and VALUE
    cprint/v7           same as /v1
    cprint/v8           like /v2, but with just NAME then VALUE
    cprint/v<n> <arg>   displays <arg> with a one-time formatting override
    cprint/p            enable  'pretty print' (one-time override if there is an argument)
    cprint/P            disable 'pretty print'
    cprint/x            show numeric value in hexadecimal
    cprint/b            show numeric value in binary
    cprint/d            show expanded information
    cprint/v<n>         with no arguments sets the session formatting
    cprint/m            generate output compatible with '-stack-list-variables --simple-values
    cprint *            display all variables that are currently in context
    cprint #N           display the Nth variable in the context list
    cprint/r N          set the range for 'cprint ?' to plus/minus N (default 6; -1 means 'all'; -2 'none')
    cprint/r            show the current value of N
    cprint ?            display in-context variables within +/- N lines of current line
    cprint ? R          display in-context variables within +/- R lines of current line
    cprint VARIABLE="STRING"    set variable to string
    cprint VARIABLE='STRING'    set variable to string
    cprint VARIABLE=NUMERIC     set variable to numeric
    cprint VARIABLE=VARIABLE2   set variable to variable2

    cprint set repeat N sets the display <repeats> threshold to N characters

    The default v-level can be set with the environment variable CPRINT_V=<n>
    The default 'pretty print' mode can be turned on with        CPRINT_P=p
    The default r-level can be set with the environment variable CPRINT_R=<n>
(gdb)

Let’s check out the /v formatting switches:

(gdb) p/v0 header
77 HEADER/SOH [W-S]  :  "Demonstration of addition", ' ' <repeats 39 times>
(gdb) cp/v1 header
"Demonstration of addition", ' ' <repeats 39 times>
(gdb) cp/v2 header
77 HEADER/SOH [W-S]
"Demonstration of addition", ' ' <repeats 39 times>
(gdb)

Which one is chosen is a matter of personal taste. I hardly ever change away from the default of cprint/v0. As the print/h screen describes, you can change the session default to v2 with cprint/v2

(gdb) cprint header
77 HEADER/SOH [W-S]  :  "Demonstration of addition", ' ' <repeats 39 times>
(gdb) cprint/v2
(gdb) cprint header
77 HEADER/SOH [W-S]
"Demonstration of addition", ' ' <repeats 39 times>
(gdb)

You can, if you prefer that GDB start with a different /v default, set that with the environment variable CPRINT_V=n

There is one switch that’s documented in help cprint for completeness, but which you almost certainly won’t find a need for. cprint/m creates an output similar to that generated by GDB in machine-interface mode. It’s used for program-to-program communication when COBCD is combined with Microsoft’s Visual Studio Code:

(gdb) cp/m *
variables=[{name="HEADER/SOH",value="\"\\\"Demonstration of addition\\\"
, ' ' <repeats 39 times>\""},{name="ONE/SOH",value="\"0001\""},{name="TW
O/SOH",value="\"0002\""},{name="THREE/SOH",value="\"0003\""}]
(gdb)

Printing From a More Complex List of Variables

Let’s switch to a different program. I am not going to bother with the source code. But it has a more complicated set of variables, and will allow more of a real-world discussion about using cprint.

At this point I have loaded the program, set a breakpoint at the end of it, and run it. The cprint * command shows that there are a lot of variables:

(gdb) cp *
 1 : 01 input-record/optfde01 [I/O] : "1234560601194501L021234567900000123", ' ' <repeats 83 times>
 2 : 01 output-record/optfde01 [I/O] : "1234561945060101L021234567900000123", ' ' <repeats 83 times>
 3 : 01 file-status/optfde01 [W-S] : "."
 4 : 01 working-data/optfde01 [W-S] : "         0601194519450601Records modified: 00003"
 5 : 05 pgm-name/working-data/optfde01 [W-S] : "         "
 6 : 05 old-date/working-data/optfde01 [W-S] : 06011945
 7 : 05 old-dat1/working-data/optfde01 [W-S] : "06011945"
 8 : 10 datmo/old-dat1/working-data/optfde01 [W-S] : 06
 9 : 10 datda/old-dat1/working-data/optfde01 [W-S] : 01
10 : 10 datyr/old-dat1/working-data/optfde01 [W-S] : 1945
11 : 05 new-date/working-data/optfde01 [W-S] : 19450601
12 : 05 new-dat1/working-data/optfde01 [W-S] : "19450601"
13 : 10 datyr/new-dat1/working-data/optfde01 [W-S] : 1945
14 : 10 datmo/new-dat1/working-data/optfde01 [W-S] : 06
15 : 10 datda/new-dat1/working-data/optfde01 [W-S] : 01
16 : 05 my-counters/working-data/optfde01 [W-S] : "Records modified: 00003"
17 : 10 custmas-ctr/my-counters/working-data/optfde01 [W-S] : 00003
18 : 01 oeit/optfde01 [L-S] : "1234561945060101L021234567900000123"
19 : 05 oeit-custnum/oeit/optfde01 [L-S] : 123456
20 : 05 oeit-orddate/oeit/optfde01 [L-S] : 19450601
21 : 05 oeit-ordnum/oeit/optfde01 [L-S] : 01
22 : 05 oeit-rectype/oeit/optfde01 [L-S] : "L"
23 : 05 oeit-lineno/oeit/optfde01 [L-S] : 02
24 : 05 oeit-ponum/oeit/optfde01 [L-S] : "12345679"
25 : 05 oeit-filler/oeit/optfde01 [L-S] : "00000123"
26 : 01 oeitl/optfde01 [L-S] : '0' <repeats 16 times>, " ", '0' <repeats 18 times>
27 : 05 oeitl-custnum/oeitl/optfde01 [L-S] : 000000
28 : 05 oeitl-orddate/oeitl/optfde01 [L-S] : 00000000
29 : 05 oeitl-ordnum/oeitl/optfde01 [L-S] : 00
30 : 05 oeitl-rectype/oeitl/optfde01 [L-S] : " "
31 : 05 oeitl-lineno/oeitl/optfde01 [L-S] : 00
32 : 05 oeitl-prodnum/oeitl/optfde01 [L-S] : 00000000
33 : 05 oeitl-qty/oeitl/optfde01 [L-S] : 00000000
34 : 01 ordmast/optfde01 [L-S] : '0' <repeats 16 times>, "        "
35 : 05 oeit-custnum/ordmast/optfde01 [L-S] : 000000
36 : 05 oeit-orddate/ordmast/optfde01 [L-S] : 00000000
37 : 05 oeit-ordnum/ordmast/optfde01 [L-S] : 00
38 : 05 oeit-ponum/ordmast/optfde01 [L-S] : "        "
39 : 01 ordline/optfde01 [L-S] : '0' <repeats 18 times>
40 : 05 oeitl-lineno/ordline/optfde01 [L-S] : 00
41 : 05 oeitl-prodnum/ordline/optfde01 [L-S] : 00000000
42 : 05 oeitl-qty/ordline/optfde01 [L-S] : 00000000
(gdb

The cbl-gdb cprint command attempts to be as helpful as it can by not forcing you to type entire variable names. For example there are two variables that contain the letters “counter”. So, cprint counter results in:

(gdb) cprint counter
 1 : 05 my-counters/working-data/optfde01 [W-S] : "Records modified: 00003"
 2 : 10 custmas-ctr/my-counters/working-data/optfde01 [W-S] : 00003

You’ll note that those are two lines in the same working-data variable structure. But because the string “counter” was ambiguous, cprint showed both of them.

However, cprint my-counters does this:

(gdb) cprint my-counters
05 my-counters/working-data/optfde01 [W-S]  :  "Records modified: 00003"
(gdb)

Because there actual is only one variable with a named piece that is exactly “my-counters”, cprint shows just that one.

Similarly, of all the variables, only one contains the substring “ctr”:

(gdb) cp ctr
10 custmas-ctr/my-counters/working-data/optfde01 [W-S]  :  00003

So, even though “ctr” doesn’t specify an entire piece of the name, it’s the only possibility, and is shown as such. And because it’s the only possibility, it can be used in a cp ctr=3333 alter command:

(gdb) cp ctr=3333
10 custmas-ctr/my-counters/working-data/optfde01 [W-S]  :  03333

Conclusion

You now have a general understanding of what the cbl-gdb package is doing to create a COBOL executable that can be source-level debugged by GDB.

You have also seen many of the commands available in the Python extension to GDB for the display of COBOL variables.

If you have any questions, or problems, or comments, please let us know at support@cobolworx.com.

Appendix A – the CBL-GDB compiliation process.

The GnuCOBOL compiler operates by translating the COBOL source code into the C programming language. The GCC compiler then goes through its normal behavior, which is to convert the C code to assembly language (which has a .S file extension). That assembly language is then compiled (or assembled, if you prefer) into machine language, which is packaged by the loader (along with debugging information, if any) into an executable.

(I am not unaware that entire college courses are spent expanding on what I described there so cavalierly, and experts spend entire careers implementing it.)

The COBCD script breaks down the process into its components steps, and interjects additional processing to create the debuggable executable:

  1. The COBC compiler converts the COBOL source into the intermediate .C file.
  2. The COBCD-ST program scans the .C file and develops a cross-reference table matching the C variable names to the COBOL variable names.
  3. The COBCD-ST program appends that information to the end of the .C file in the form of a triplet of character strings with the names VARIABLE_STRING_A_xxx, VARIABLE_STRING_B_xxx, and VARIABLE_STRING_C_xxx. Those names end with a suffix that indicates the name of the source file they are found in.
  4. The GCC compiler then compiles the .C file into a .S assembly language file.
  5. The COBCD-SFIX program modifies the .S file, rearranging the line references so that GDB cleanly refers back to the original COBOL source.
  6. COBC assembles the modified .S file into a .O object.
  7. GCC combines the two .O files into a single executable. If you care to see this process unfold, you can set the ECHO=1 environment variable:
$ ECHO=1 cobcd -x soh.cbl
COBC_GEN_DUMP_COMMENTS=1 cobc -### -save-temps -A -ggdb -fgen-c-line-directives -fgen-c-labels -x soh.cbl

    loading standard configuration file 'default.conf'
    command line:       cobc -### -save-temps -A -ggdb -fgen-c-line-directives -fgen-c-labels -x soh.cbl
    preprocessing:      soh.cbl -> soh.i
    return status:      0
    parsing:    soh.i (soh.cbl)
    return status:      0
    translating:        soh.i -> soh.c (soh.cbl)
    to be executed:     gcc -c -pipe -I/usr/local/include -Wno-unused -fsigned-char -Wno-pointer-sign -fcf-protection=none -ggdb -o soh.o soh.c
    to be executed:     gcc -Wl,--export-dynamic -o soh soh.o -L/usr/local/lib -lcob -lm

/usr/local/bin/cobcd-st -q -m1 soh soh.cbl

   to_be_executed: gcc -c -pipe -I/usr/local/include -Wno-unused -fsigned-char -Wno-pointer-sign -fcf-protection=none -ggdb -o soh.o soh.c
   cbl_file... soh.cbl
   i_file..... soh.i
   c_file..... soh.c
   s_file..... soh.s

gcc -pipe -I/usr/local/include -Wno-unused -fsigned-char -Wno-pointer-sign -fcf-protection=none -ggdb -o soh.s soh.c -S
/usr/local/bin/cobcd-sfix -q soh.s soh.s soh.c soh.cbl
gcc -c -pipe -I/usr/local/include -Wno-unused -fsigned-char -Wno-pointer-sign -fcf-protection=none -ggdb -o soh.o soh.s
gcc -Wl,--export-dynamic -o soh soh.o -L/usr/local/lib -lcob -lm

$

And if you are interested enough, you can use the COBCDNOCLEAN=1 environment variable to tell the COBCD command not to delete the intermediate files:

$ COBCDNOCLEAN=1 cobcd -x soh.cbl
$ ls -al
total 176
drwxrwxr-x  2 bob bob  4096 2020-08-31 10:09 .
drwxrwxr-x 15 bob bob  4096 2020-08-26 09:06 ..
-rwxrwxr-x  1 bob bob 23320 2020-08-31 10:09 soh
-rw-rw-r--  1 bob bob  5837 2020-08-31 10:09 soh.c
-rw-rw-r--  1 bob bob   566 2020-08-26 09:13 soh.cbl
-rw-rw-r--  1 bob bob  1157 2020-08-31 10:09 soh.c.h
-rw-rw-r--  1 bob bob  1451 2020-08-31 10:09 soh.c.l.h
-rw-rw-r--  1 bob bob   443 2020-08-31 10:09 soh.i
-rw-rw-r--  1 bob bob 25224 2020-08-31 10:09 soh.o
-rw-rw-r--  1 bob bob 52727 2020-08-31 10:09 soh.s

You almost certainly don’t actually need to know all of that; all you need to do is invoke COBCD and all should be well. But at least now you know what you are ignoring.