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:
- The COBC compiler converts the COBOL source into the intermediate .C file.
- The COBCD-ST program scans the .C file and develops a cross-reference table matching the C variable names to the COBOL variable names.
- 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.
- The GCC compiler then compiles the .C file into a .S assembly language file.
- The COBCD-SFIX program modifies the .S file, rearranging the line references so that GDB cleanly refers back to the original COBOL source.
- COBC assembles the modified .S file into a .O object.
- 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.