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

-g

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 #line directives, so a debugger shows the COBOL source lines.

-debug (also --debug)

Turns on every run-time check, which is -fstack-check plus -fec=EC-ALL, and enables debugging lines. This is the switch to use for development and for the test run in CI.

-fec=name

Turns on one named exception check, for example -fec=bound for subscripts and reference modification, -fec=size for size errors, -fec=data for invalid numeric data. -fno-ec=name turns one off again. The names are the exception names of the standard, listed in the manual.

-ftrace

Generates code that prints each paragraph and section entry when tracing is switched on at run time.

-ftraceall

As -ftrace, and also each statement.

-fdebugging-line

Compiles debugging lines (those with D in the indicator area) without needing WITH DEBUGGING MODE.

--coverage

Instruments the generated C so that gcov can report which COBOL lines ran. The manual describes it as turned on by -debug and off again by -g.

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

S0C7

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 (IF field IS NUMERIC) at the point of input, or the NUMCHECK option while testing.

S0C4

Protection exception: the program used an address it does not own.

Subscripts or reference modification out of range (SSRANGE finds them), a CALL with fewer parameters than the called program uses, a pointer that was never set, or a file that is not open.

S0CB

Division by zero in a decimal or binary divide.

The divisor; code ON SIZE ERROR on every DIVIDE/COMPUTE that can see a zero.

S322

The job step exceeded the time limit.

A loop that does not end (the PERFORM UNTIL condition can never become true) or a real volume increase.

S806

The load module was not found.

The STEPLIB/JOBLIB concatenation, or a CALL of a literal name that was never link-edited.

S013

An OPEN failed because of a data set attribute mismatch.

RECFM, LRECL or BLKSIZE in the DD statement against the FD.

U4038

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:

  1. 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 OFFSET or LIST option).

  2. The traceback. Every active program, innermost first, with its entry point and offset. The innermost is the program that failed.

  3. The register contents and the program status word (PSW) at the interrupt, for the assembler-level view.

  4. Working storage and local variables. With the MAP listing 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

  1. Reproduce it with the smallest input. Cut the failing run down to the one record that fails (the Circulation LOANTRAN with one line).

  2. Turn on every check. -debug in GnuCOBOL; SSRANGE, NUMCHECK, INITCHECK and TEST on z/OS.

  3. Read the first message, not the last. A later abend is often the consequence of the first wrong value.

  4. Check the data against the copybook. Wrong PICTURE`s, record lengths and unset fields explain most `S0C7 and S0C4 cases; the FILE STATUS item (see File Status Codes) explains the rest.

  5. Add a test that fails before the fix and passes after it (Testing COBOL Programs).

Common mistakes

  • Trusting a clean compile. cobc -Wall printed no warning for LOANCHK; the defect only shows at run time with -debug.

  • Shipping a build made with -debug or TEST. 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 DISPLAY alone on z/OS and forgetting SYSOUT. Output of DISPLAY goes to SYSOUT, which a job may route elsewhere.

  • Fixing the abend site, not the cause. An S0C7 on the 400th statement is usually a layout error in the file read earlier.