Debugging and Troubleshooting
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against ISO/IEC 1989:2023 (edition 3, January 2023; an upcoming 4th edition is in ballot), in which debugging lines and the DEBUGGING MODE switch come from COBOL 85 and the exception conditions that run-time checks report come from COBOL 2002; against IBM Enterprise COBOL for z/OS 6.5 (PTFs through September 2026) on z/OS 3.2; and against GnuCOBOL 3.2 (28 July 2023, the latest release), with which every program on it was compiled with cobc -x -Wall and run on 2026-10-20 (and again with -std=ibm where it differs). z/OS examples and abend descriptions are labelled "z/OS; not run here" and were checked against the Programming Guide; no dump listing is reproduced because none could be produced without a mainframe.
Debugging a batch program means answering three questions in this order: what did the program do (trace), where did the first wrong thing happen (run-time checks) and which paths did my test data never reach (coverage). The page answers them first for GnuCOBOL, where everything can be tried on a laptop, then for z/OS, where the same questions are answered with abend codes, the Language Environment dump and the TEST compiler option. It builds on Error Handling and Declaratives (what a program does about errors on purpose) and Compiling, Linking and Language Environment (the options named below). The tools that surround the debugger are on Modern Tooling: VS Code and Zowe, and the tests that stop most defects before they need debugging are on Testing COBOL Programs.
Where the book is outdated: Taulli’s Modern Mainframe Development (2022, chapter 3) presents the debugger as one feature of a full-screen IDE and the abend as something to read from the job log. That is still how a z/OS shop works, but the book is written around the IDE tool of its time; the paths to a debugger today also include the Zowe-based VS Code route described on the tooling page, and on a laptop a free GnuCOBOL with -debug finds most of the defects that on z/OS surface as S0C4 or S0C7.
The sample defect
The Circulation LOANCHK program counts loans per copy slot. A book has three copies, so WS-SLOT has three entries, but the loop runs while the index is not greater than 4: a classic off-by-one.
* LOANCHK - counts loans per copy slot; has a deliberate bug.
IDENTIFICATION DIVISION.
PROGRAM-ID. LOANCHK.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-COPIES.
05 WS-SLOT PIC 9(3) OCCURS 3 TIMES.
01 WS-IDX PIC 9(2) VALUE 0.
01 WS-TOTAL PIC 9(4) VALUE 0.
PROCEDURE DIVISION.
MAIN-PARA.
MOVE ZEROS TO WS-COPIES
PERFORM VARYING WS-IDX FROM 1 BY 1 UNTIL WS-IDX > 4
ADD 10 TO WS-SLOT (WS-IDX)
ADD WS-SLOT (WS-IDX) TO WS-TOTAL
END-PERFORM
DISPLAY "TOTAL " WS-TOTAL
STOP RUN.
Compiled with cobc -x -Wall LOANCHK.cob, with no warnings, the program runs to the end and prints a wrong answer without any complaint, which is the dangerous outcome:
TOTAL 1020
The fourth slot is the first three bytes after the table, which here is WS-IDX and the start of WS-TOTAL; 10 was added to storage that belongs to other items, and the printed total is no longer meaningful. The page uses this program for every tool below.
GnuCOBOL: the compiler switches
| Switch | Effect |
|---|---|
|
Passes debug information to the C compiler, turns on debugging lines and stack checks, and produces a program that a C-level debugger such as GDB can step through. In GnuCOBOL 3.2 the generated C carries |
|
Turns on every run-time check, which is |
|
Turns on one named exception check, for example |
|
Generates code that prints each paragraph and section entry when tracing is switched on at run time. |
|
As |
|
Compiles debugging lines (those with |
|
Instruments the generated C so that |
Run-time checks: -debug and -fec
Recompiling the sample with -debug stops it at the first bad subscript and says where:
libcob: LOANCHK.cob:14: error: subscript of 'WS-SLOT' out of bounds: 4
note: maximum subscript for 'WS-SLOT': 3
Last statement of "LOANCHK" was ADD
MAIN-PARA at LOANCHK.cob:14
ENTRY LOANCHK at LOANCHK.cob:11
Started by ./LOANCHK
The program ends with exit status 1. The message gives the source line, the item, the bad value, the legal maximum and a call-stack style listing of paragraphs. Compiling with -g -fec=bound produced the same first line and the same exit status, with a shorter call listing (no ENTRY line).
A second common defect is invalid numeric data. The program BADNUM redefines three characters as a packed-decimal field and adds it to a total, which is what happens when a record with text in a numeric column is read:
* BADNUM - a packed field holding invalid data (S0C7 on z/OS).
IDENTIFICATION DIVISION.
PROGRAM-ID. BADNUM.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-RAW PIC X(3) VALUE "ABC".
01 WS-FINES REDEFINES WS-RAW PIC S9(5) COMP-3.
01 WS-TOTAL PIC S9(5)V99 COMP-3 VALUE 0.
01 WS-SMALL PIC 99 VALUE 0.
PROCEDURE DIVISION.
MAIN-PARA.
ADD WS-FINES TO WS-TOTAL
DISPLAY "TOTAL " WS-TOTAL
COMPUTE WS-SMALL = 150 + WS-SMALL
DISPLAY "SMALL " WS-SMALL
STOP RUN.
Compiled with cobc -x -Wall BADNUM.cob, no warnings, the unchecked program prints garbage and truncates the second result silently:
TOTAL +41424.00
SMALL 50
Compiled with -debug it stops before the first DISPLAY:
libcob: BADNUM.cob:12: error: 'WS-FINES' (Type: PACKED-DECIMAL) not numeric: '0x414243'
Last statement of "BADNUM" was ADD
MAIN-PARA at BADNUM.cob:12
ENTRY BADNUM at BADNUM.cob:11
Started by ./BADNUM
The message shows the bytes found (41 42 43 are ABC in ASCII; on z/OS they would be EBCDIC C1 C2 C3). The COMPUTE that truncated 150 to 50 is not an error in either case because no ON SIZE ERROR phrase was coded and the standard says the result is then undefined; -fec=size did not report it in this run, so the dependable cure is to code the phrase, as Arithmetic and COMPUTE shows. On Enterprise COBOL the equivalent checks are the compiler options SSRANGE and NUMCHECK (see the performance page for their cost).
Tracing: -ftrace, -ftraceall and COB_SET_TRACE
Trace code is generated at compile time but printed only when the environment variable COB_SET_TRACE is set to Y (or COB_TRACE_FILE names a file). With -ftrace the output is the program, entry and paragraph:
Source: 'COV.cob'
Program-Id: COV
Program-Id: COV Entry: COV Line: 11
Program-Id: COV Paragraph: MAIN-PARA Line: 11
(That output is from the COV program of the coverage section; LOANCHK has a single paragraph and prints the same shape.) With -ftraceall every statement is shown with its line, which for the first iterations of the sample is:
Program-Id: LOANCHK MOVE Line: 12
Program-Id: LOANCHK PERFORM Line: 13
Program-Id: LOANCHK UNTIL Line: 13
Program-Id: LOANCHK ADD Line: 14
Program-Id: LOANCHK ADD Line: 15
Program-Id: LOANCHK VARYING Line: 13
Setting COB_TRACE_FILE=trace.log sends the trace to a file instead of the screen, which keeps a long batch run readable. A trace answers "which path did it take" and is the first thing to try when the data is correct but the result is not; for a wrong value, the next section is faster.
Debugging lines and DISPLAY tracing
A line with D in column 7 is a debugging line: it is compiled only when the program says WITH DEBUGGING MODE on SOURCE-COMPUTER (or the compiler is given -fdebugging-line), and is a comment otherwise. That allows DISPLAY statements that cost nothing in production to stay in the source.
* DBGLINE - debugging lines (D in column 7) and DISPLAY tracing.
IDENTIFICATION DIVISION.
PROGRAM-ID. DBGLINE.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
SOURCE-COMPUTER. CIRC-HOST WITH DEBUGGING MODE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-COPIES PIC 9(3) VALUE 3.
01 WS-DAYS PIC 9(3) VALUE 12.
01 WS-FINE PIC 9(3)V99 VALUE 0.
PROCEDURE DIVISION.
MAIN-PARA.
D DISPLAY "DEBUG: copies=" WS-COPIES " days=" WS-DAYS
COMPUTE WS-FINE = WS-DAYS * 0.35
D DISPLAY "DEBUG: fine=" WS-FINE
DISPLAY "FINE " WS-FINE
STOP RUN.
Compiled with cobc -x -Wall DBGLINE.cob, no warnings, it prints:
DEBUG: copies=003 days=012
DEBUG: fine=004.20
FINE 004.20
Without WITH DEBUGGING MODE only the last line is printed; adding -fdebugging-line brings the first two back. With -std=ibm the same program prints fine=00420 and FINE 00420, because the IBM dialect does not insert the decimal point when displaying an item with an implied decimal point. On Enterprise COBOL the WITH DEBUGGING MODE clause works the same way, and the DEBUGGING declarative in the Language Reference is a separate, older facility that runs a section before each paragraph.
Coverage with --coverage
Coverage answers "which statements did my test data run". The COV program is a corrected LOANCHK that adds a branch the test data never takes:
* COV - sums loans per copy slot; used to show coverage.
IDENTIFICATION DIVISION.
PROGRAM-ID. COV.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-COPIES.
05 WS-SLOT PIC 9(3) OCCURS 3 TIMES.
01 WS-IDX PIC 9(2) VALUE 0.
01 WS-TOTAL PIC 9(4) VALUE 0.
PROCEDURE DIVISION.
MAIN-PARA.
MOVE ZEROS TO WS-COPIES
PERFORM VARYING WS-IDX FROM 1 BY 1 UNTIL WS-IDX > 3
ADD 10 TO WS-SLOT (WS-IDX)
ADD WS-SLOT (WS-IDX) TO WS-TOTAL
END-PERFORM
IF WS-TOTAL > 50
DISPLAY "TOO BIG"
END-IF
DISPLAY "TOTAL " WS-TOTAL
STOP RUN.
Compiled with cobc -x -Wall --coverage COV.cob, no warnings, run once (it prints TOTAL 0030), and then processed with gcov -o . COV, the program produces a COV.cob.gcov file whose lines are the COBOL source with counts:
8*: 13: PERFORM VARYING WS-IDX FROM 1 BY 1 UNTIL WS-IDX > 3
3*: 14: ADD 10 TO WS-SLOT (WS-IDX)
3*: 15: ADD WS-SLOT (WS-IDX) TO WS-TOTAL
-: 16: END-PERFORM
1*: 17: IF WS-TOTAL > 50
#####: 18: DISPLAY "TOO BIG"
##### marks the line that never ran, and gcov reported 91.67 percent of the 12 COBOL lines executed. gcov also prints messages about the generated C file when it is not at hand ("Cannot open source file"), which can be ignored when only the .cob.gcov file is used. The counts are per line of the generated C that maps to the source line, so a line with several statements shows one number.
GnuCOBOL: stepping with GDB
A program compiled with -g can be stepped in GDB, and the manual and release notes of 3.2 describe improved support for stepping through the COBOL source rather than the generated C. A typical session sets a breakpoint on a source line (break LOANCHK.cob:14), runs, prints COBOL items through the generated C fields and uses bt for the stack. GDB was not available in the container used for this page, so no session is shown, and the exact printing of COBOL data items is left to the manual.
z/OS: reading an abend
A program that fails on z/OS ends with a system completion code (an abend such as S0C7; S means system) or a user completion code (U4038; U means the program or Language Environment asked for it). Language Environment may convert a program interrupt into its own condition first; the completion code on the job log then comes with a message CEE3207S or CEE3250C style line from LE, and the message identifies the failing condition and program. The common ones for COBOL programs are these (z/OS; not run here).
| Code | Usual meaning | First thing to look at |
|---|---|---|
|
Data exception: an arithmetic or compare instruction used a packed, zoned or binary field with invalid contents. |
The field that was read just before the failing statement, usually from a file whose layout does not match the copybook; use the numeric class test ( |
|
Protection exception: the program used an address it does not own. |
Subscripts or reference modification out of range ( |
|
Division by zero in a decimal or binary divide. |
The divisor; code |
|
The job step exceeded the time limit. |
A loop that does not end (the |
|
The load module was not found. |
The |
|
An |
|
|
A Language Environment abnormal termination: LE raised a condition that the program did not handle. |
The LE message printed before it, and the traceback; the details are in the CEEDUMP. |
The meaning of each code is documented in the z/OS messages and codes volumes and in the Language Environment debugging guide; the S0Cn rows follow the long-standing IBM definitions, while the "first thing to look at" column is practical advice, not a quotation.
z/OS: the Language Environment dump
When a condition is not handled, Language Environment writes a CEEDUMP to the CEEDUMP DD (or SYSOUT) if the run-time option TERMTHDACT asks for a dump (TRACE, DUMP or UADUMP). Its parts, in the order they appear, are these, and reading them in this order finds the failing statement:
-
The condition and message. The first lines name the LE message, the failing program and the offset in the program (the statement number as well when the program was compiled with the
OFFSETorLISToption). -
The traceback. Every active program, innermost first, with its entry point and offset. The innermost is the program that failed.
-
The register contents and the program status word (PSW) at the interrupt, for the assembler-level view.
-
Working storage and local variables. With the
MAPlisting of the Data Division the storage can be mapped back to COBOL data names; without it it is a raw hex dump.
To go from the offset to the COBOL statement, compile with OFFSET or LIST (and MAP) and look the offset up in the compiler listing: the listing shows each COBOL statement with its generated instructions and the offset where they start. With the TEST option the debugger tool can do the same step automatically. The tools that format dumps from a job (IBM Fault Analyzer, Broadcom Abend-AID and similar) are separate products; they are named here only so that a reader recognises the names at work.
z/OS: the TEST option and the debugger
The TEST compiler option adds debugging information to the load module: the data names, the statement table and (optionally) the hooks that let a debugger stop before each statement or only at paragraph starts. It makes the module larger and slower, so it is used on test builds only. The debug tool that reads it (IBM z/OS Debugger, or the VS Code debug extensions described on the tooling page) lets the programmer set breakpoints on a COBOL line, step, inspect and change data items by name and see the call stack. Enterprise COBOL 6 uses separate debug information (the SEPARATE suboption, kept in a file next to the listing) so the production module need not carry it; the suboptions and their default are in the TEST option page.
A short method that works on both platforms
-
Reproduce it with the smallest input. Cut the failing run down to the one record that fails (the Circulation
LOANTRANwith one line). -
Turn on every check.
-debugin GnuCOBOL;SSRANGE,NUMCHECK,INITCHECKandTESTon z/OS. -
Read the first message, not the last. A later abend is often the consequence of the first wrong value.
-
Check the data against the copybook. Wrong
PICTURE`s, record lengths and unset fields explain most `S0C7andS0C4cases; theFILE STATUSitem (see File Status Codes) explains the rest. -
Add a test that fails before the fix and passes after it (Testing COBOL Programs).
Common mistakes
-
Trusting a clean compile.
cobc -Wallprinted no warning forLOANCHK; the defect only shows at run time with-debug. -
Shipping a build made with
-debugorTEST. Both add a cost on every statement; use them for development and test runs and a normal build for production (see Performance and Compiler Options). -
Leaving a debugging line with no
WITH DEBUGGING MODE. It is silently a comment, so the line that was supposed to show the problem prints nothing. -
Debugging with
DISPLAYalone on z/OS and forgettingSYSOUT. Output ofDISPLAYgoes toSYSOUT, which a job may route elsewhere. -
Fixing the abend site, not the cause. An
S0C7on the 400th statement is usually a layout error in the file read earlier.