23 min read

Debugging COBOL programs is a skill that separates productive programmers from those who spend days chasing problems that experienced developers resolve in minutes. The techniques differ dramatically from debugging in modern languages with...

Chapter 20: Debugging Techniques and Tools

Introduction: The Reality of COBOL Debugging

Debugging COBOL programs is a skill that separates productive programmers from those who spend days chasing problems that experienced developers resolve in minutes. The techniques differ dramatically from debugging in modern languages with integrated development environments, step-through debuggers, and interactive REPLs. On a mainframe, your program may have run as a batch job at 2:00 AM, processing ten million records before failing at record 7,342,891. You have a dump, a sysout listing, and possibly a cryptic abend code. Your job is to figure out what went wrong, fix it, and resubmit before the morning production deadline.

This chapter covers the full spectrum of COBOL debugging, from simple DISPLAY-based techniques that work everywhere, through compiler-generated listings that reveal data layouts and cross-references, to interactive debuggers on both IBM mainframes and GnuCOBOL environments. It also covers the critical skill of dump analysis -- reading the hexadecimal memory contents that the operating system produces when a program abends. Finally, it addresses the modern practice of unit testing COBOL programs, which represents a shift from reactive debugging to proactive defect prevention.

Whether you are working on a z/OS mainframe with IBM Debug Tool, on a Linux workstation with GnuCOBOL and GDB, or analyzing a production dump at 3:00 AM with a cup of coffee and a hex conversion chart, this chapter gives you the tools and techniques you need.


20.1 DISPLAY-Based Debugging

The DISPLAY statement is the oldest, simplest, and most universally available debugging technique in COBOL. It writes output to the system console (SYSOUT on z/OS, standard output on GnuCOBOL). Despite its simplicity, experienced COBOL programmers use DISPLAY-based debugging extensively because it works in every environment, requires no special tools, and can be added to production programs with minimal risk.

Basic DISPLAY Debugging

       2000-PROCESS-RECORD.
           DISPLAY '>>> ENTERING 2000-PROCESS-RECORD'
           DISPLAY '    RECORD KEY: ' WS-RECORD-KEY
           DISPLAY '    AMOUNT:     ' WS-AMOUNT
           DISPLAY '    COUNTER:    ' WS-RECORD-COUNT

           PERFORM 2100-VALIDATE
           DISPLAY '    AFTER VALIDATE, STATUS: '
               WS-VALID-FLAG

           PERFORM 2200-CALCULATE
           DISPLAY '    AFTER CALCULATE, RESULT: '
               WS-CALC-RESULT

           DISPLAY '<<< EXITING 2000-PROCESS-RECORD'
           .

Strategic DISPLAY Placement

Effective DISPLAY debugging requires strategic placement. Do not scatter DISPLAY statements randomly; instead, place them at decision points where the program's behavior might diverge from expectations:

      * 1. At the start of every major paragraph
           DISPLAY '=== 2000-PROCESS-TRANSACTION ==='

      * 2. Before and after file I/O operations
           DISPLAY 'ABOUT TO READ MASTER FILE'
           READ MASTER-FILE INTO WS-MASTER-REC
           DISPLAY 'READ STATUS: ' WS-MASTER-STATUS

      * 3. Before and after CALL statements
           DISPLAY 'CALLING EDITMOD WITH: ' WS-INPUT-DATA
           CALL 'EDITMOD' USING WS-INPUT-DATA WS-OUTPUT-DATA
           DISPLAY 'EDITMOD RETURNED RC=' RETURN-CODE

      * 4. At conditional branch points
           DISPLAY 'TRANS-TYPE=' WS-TRANS-TYPE
           EVALUATE WS-TRANS-TYPE
               WHEN 'A'
                   DISPLAY 'TAKING ADD PATH'
                   PERFORM 3100-ADD-RECORD
               WHEN 'C'
                   DISPLAY 'TAKING CHANGE PATH'
                   PERFORM 3200-CHANGE-RECORD
               WHEN 'D'
                   DISPLAY 'TAKING DELETE PATH'
                   PERFORM 3300-DELETE-RECORD
               WHEN OTHER
                   DISPLAY 'UNEXPECTED TYPE: [' WS-TRANS-TYPE
                       ']'
           END-EVALUATE

      * 5. Inside loops, with iteration count
           DISPLAY 'LOOP ITERATION: ' WS-LOOP-COUNT
               ' KEY=' WS-CURRENT-KEY

Displaying Data in Multiple Formats

Sometimes you need to see the hexadecimal representation of a field to diagnose data corruption or encoding issues:

       01  WS-DEBUG-HEX        PIC X(40).
       01  WS-HEX-DISPLAY.
           05  WS-HEX-CHAR    PIC X(02) OCCURS 20 TIMES.

      * Show the raw content of a field
           DISPLAY 'AMOUNT DISPLAY: [' WS-AMOUNT ']'
           DISPLAY 'AMOUNT LENGTH:  '
               FUNCTION LENGTH(WS-AMOUNT)

      * For IBM Enterprise COBOL, you can inspect individual
      * bytes using reference modification:
           DISPLAY 'BYTE 1: ' WS-AMOUNT(1:1)
           DISPLAY 'BYTE 2: ' WS-AMOUNT(2:1)

Displaying Numeric Data for Debugging

Numeric fields in COBOL can behave unexpectedly when displayed, especially COMP-3 (packed decimal) and COMP (binary) fields. When debugging, you often need to see both the formatted value and the raw content:

      * COMP-3 field: DISPLAY shows the numeric value
       01  WS-AMOUNT       PIC S9(7)V99 COMP-3 VALUE 12345.67.

           DISPLAY 'AMOUNT (FORMATTED): ' WS-AMOUNT
      *    Shows: AMOUNT (FORMATTED): +0012345.67

      * To see the raw bytes, use reference modification
      * COMP-3 PIC S9(7)V99 occupies 5 bytes
           DISPLAY 'AMOUNT (BYTE 1): '
               WS-AMOUNT(1:1)
           DISPLAY 'AMOUNT (BYTE 2): '
               WS-AMOUNT(2:1)

      * For binary (COMP) fields, DISPLAY converts to readable form
       01  WS-COUNTER      PIC S9(9) COMP VALUE 42.
           DISPLAY 'COUNTER: ' WS-COUNTER
      *    Shows: COUNTER: +000000042

      * When a numeric field contains invalid data (pre-S0C7),
      * DISPLAYing it may itself cause an abend. To safely
      * inspect suspicious data, MOVE it to an alphanumeric
      * field first:
       01  WS-SUSPECT-AMOUNT PIC S9(7)V99 COMP-3.
       01  WS-RAW-BYTES      PIC X(5).
       01  WS-RAW-REDEF REDEFINES WS-RAW-BYTES
                             PIC S9(7)V99 COMP-3.

           MOVE WS-SUSPECT-AMOUNT TO WS-RAW-BYTES
           DISPLAY 'RAW CONTENT: [' WS-RAW-BYTES ']'
      *    This safely displays the bytes without interpreting
      *    them as packed decimal

Trace Table Technique

For programs where the sequence of paragraph execution matters, maintain a trace table in WORKING-STORAGE that records the last N paragraphs executed:

       01  WS-TRACE-TABLE.
           05  WS-TRACE-INDEX    PIC 99 VALUE 0.
           05  WS-TRACE-MAX      PIC 99 VALUE 20.
           05  WS-TRACE-ENTRY    PIC X(30) OCCURS 20 TIMES
                                 VALUE SPACES.

       9500-TRACE-PARAGRAPH.
           ADD 1 TO WS-TRACE-INDEX
           IF WS-TRACE-INDEX > WS-TRACE-MAX
               MOVE 1 TO WS-TRACE-INDEX
           END-IF
           MOVE WS-CURRENT-PARAGRAPH
               TO WS-TRACE-ENTRY(WS-TRACE-INDEX)
           .

       9510-DUMP-TRACE.
           DISPLAY '=== PARAGRAPH TRACE ==='
           PERFORM VARYING WS-DUMP-IDX FROM 1 BY 1
               UNTIL WS-DUMP-IDX > WS-TRACE-MAX
               IF WS-TRACE-ENTRY(WS-DUMP-IDX) NOT = SPACES
                   DISPLAY WS-DUMP-IDX ': '
                       WS-TRACE-ENTRY(WS-DUMP-IDX)
               END-IF
           END-PERFORM
           DISPLAY '=== END TRACE ==='
           .

Call 9500-TRACE-PARAGRAPH at the entry of each major paragraph (with WS-CURRENT-PARAGRAPH set to the paragraph name). If the program abends or produces wrong results, the trace table shows the sequence of paragraphs that led to the problem. This is the COBOL equivalent of a stack trace in modern languages.

Conditional Debug Displays

In production programs, you often want debug displays that can be turned on and off without recompiling:

       WORKING-STORAGE SECTION.
       01  WS-DEBUG-MODE       PIC 9 VALUE 0.
           88  WS-DEBUG-ON     VALUE 1.
           88  WS-DEBUG-OFF    VALUE 0.
       01  WS-DEBUG-LEVEL      PIC 9 VALUE 0.
           88  WS-DEBUG-NONE   VALUE 0.
           88  WS-DEBUG-ERROR  VALUE 1.
           88  WS-DEBUG-INFO   VALUE 2.
           88  WS-DEBUG-TRACE  VALUE 3.

       PROCEDURE DIVISION.
       0000-MAIN.
      *    Read debug level from a control card or parameter
           ACCEPT WS-DEBUG-LEVEL FROM ENVIRONMENT
               'DEBUG_LEVEL'
           END-ACCEPT

           IF WS-DEBUG-TRACE
               DISPLAY 'DEBUG: TRACE MODE ENABLED'
           END-IF
           .

       2000-PROCESS.
           IF WS-DEBUG-TRACE
               DISPLAY 'TRACE: ENTERING 2000-PROCESS'
               DISPLAY 'TRACE: KEY=' WS-RECORD-KEY
                   ' AMT=' WS-AMOUNT
           END-IF

      *    ... processing logic ...

           IF WS-DEBUG-INFO
               DISPLAY 'INFO: PROCESSED RECORD '
                   WS-RECORD-COUNT
           END-IF
           .

20.2 Debugging Lines: D in Column 7

COBOL provides a built-in mechanism for debugging code that can be activated or deactivated at compile time without removing or commenting out the debug statements. Any line with the letter D in column 7 is treated as a debugging line.

How Debugging Lines Work

       WORKING-STORAGE SECTION.
      D 01  WS-DEBUG-COUNTER    PIC 9(7) VALUE 0.

       PROCEDURE DIVISION.
       2000-PROCESS.
      D    ADD 1 TO WS-DEBUG-COUNTER
      D    DISPLAY 'DEBUG: RECORD ' WS-DEBUG-COUNTER
      D        ' KEY=' WS-RECORD-KEY
      D        ' AMOUNT=' WS-AMOUNT

           PERFORM 2100-VALIDATE-RECORD

      D    DISPLAY 'DEBUG: AFTER VALIDATE, FLAG='
      D        WS-VALID-FLAG

           IF WS-RECORD-VALID
               PERFORM 2200-UPDATE-MASTER
           END-IF

      D    DISPLAY 'DEBUG: EXITING 2000-PROCESS'
           .

Activating Debugging Lines

On IBM Enterprise COBOL, debugging lines are controlled by the WITH DEBUGGING MODE clause in the SOURCE-COMPUTER paragraph:

       ENVIRONMENT DIVISION.
       CONFIGURATION SECTION.
       SOURCE-COMPUTER. IBM-370 WITH DEBUGGING MODE.

When WITH DEBUGGING MODE is specified, lines with D in column 7 are compiled as regular source code. When it is removed, those lines are treated as comments and generate no object code.

On GnuCOBOL, debugging lines are activated with the -fdebugging-line compiler option:

cobc -fdebugging-line -x program.cob

Limitations of Column 7 Debugging

The D-in-column-7 mechanism is part of the COBOL-85 standard but is considered archaic in COBOL 2002 and later. Modern COBOL standards prefer the >>D directive for debugging lines in free-format source:

      >>D DISPLAY 'DEBUG: Processing record ' WS-COUNT

The mechanism has a significant limitation: it is an all-or-nothing switch. You cannot selectively enable some debugging lines and disable others. For more granular control, use the conditional DISPLAY approach described in Section 20.1.


20.3 Compiler Listing Analysis

The compiler listing is one of the most powerful debugging tools available to a COBOL programmer. When you compile a COBOL program, the compiler can produce a listing that contains the source code annotated with data definitions, cross-references, offset maps, and diagnostic messages. Learning to read this listing is essential for effective debugging.

Requesting a Listing

On IBM Enterprise COBOL, the listing is controlled by compiler options:

//COBOL  EXEC PGM=IGYCRCTL,
//       PARM='LIST,MAP,XREF,OFFSET,VBREF'
Option What It Produces
LIST Assembler expansion and object code listing
MAP Data Division map showing offsets and lengths
XREF Cross-reference of data names and procedure names
OFFSET Condensed verb listing with statement offsets
VBREF Verb cross-reference (which verbs are used where)

On GnuCOBOL, use the -t option for a listing file:

cobc -x -t listing.txt program.cob

The DATA MAP

The DATA MAP section of the listing shows every data item in your program with its offset from the start of its section, its length, its format, and its level number. This is critical for dump analysis because dump contents are displayed by offset from the start of the data area.

Example DATA MAP output:

DATA DIVISION MAP

DEFN  DATA NAME               LVL  SOURCE   HEX-DISP  DEFINITION
                                   NAME

0045  WS-COUNTERS              01  WS       00000000   GROUP
0046  WS-READ-COUNT            05  WS       00000000   BINARY    PIC S9(9)
0047  WS-WRITE-COUNT           05  WS       00000004   BINARY    PIC S9(9)
0048  WS-ERROR-COUNT           05  WS       00000008   BINARY    PIC S9(9)
0050  WS-RECORD-AREA           01  WS       0000000C   GROUP
0051  WS-CUST-ID               05  WS       0000000C   DISPLAY   PIC X(10)
0052  WS-CUST-NAME             05  WS       00000016   DISPLAY   PIC X(30)
0053  WS-CUST-BALANCE          05  WS       00000034   COMP-3    PIC S9(7)V99

When you have a dump showing that the field at WORKING-STORAGE offset X'00000034' contains X'0000000C' (which is not valid COMP-3 data), you can use the DATA MAP to identify that field as WS-CUST-BALANCE and know immediately that an invalid numeric value caused an S0C7 abend.

The Cross-Reference (XREF)

The XREF listing shows every data name and procedure name with every line number where it is referenced, and whether each reference is a definition (D), a modification (M), or a reference (R):

CROSS-REFERENCE OF DATA NAMES

DATA NAME                    DEFN   REFERENCE
WS-CUST-BALANCE             0053   0120M  0135  0148  0162M  0175
WS-CUST-ID                  0051   0118   0122  0130  0155M
WS-CUST-NAME                0052   0119   0131  0156M
WS-EOF-FLAG                 0040   0112M  0125  0128M
WS-READ-COUNT               0046   0115M  0170

The XREF is invaluable for several debugging tasks:

  • Finding where a variable is modified: Look for references marked with M.
  • Finding unused variables: Variables with only a D (definition) reference are never used.
  • Tracing data flow: Follow a variable through all its references to understand how it gets its value.

The OFFSET Listing

The OFFSET listing shows the hexadecimal offset of each COBOL verb in the generated object code. This is critical for dump analysis because the Program Status Word (PSW) in a dump contains the instruction address where the abend occurred. By subtracting the program load point from the PSW address, you get the offset, which you can look up in the OFFSET listing to find the exact COBOL statement that caused the abend.

OFFSET LISTING

LINE #   HEXLOC   VERB
000120   000A2E   MOVE
000121   000A36   READ
000122   000A54   IF
000125   000A78   ADD
000128   000A86   MOVE
000130   000A8E   PERFORM
000135   000A96   COMPUTE

If the dump shows that the abend occurred at offset X'000A96', you look up this offset in the OFFSET listing and find that it corresponds to line 135, which is a COMPUTE statement. You then look at line 135 in the source listing and examine the variables involved.


20.4 Common COBOL Abends: Diagnosis and Resolution

Understanding common abend codes is fundamental to COBOL debugging. Each abend code tells a specific story about what went wrong. Here is a comprehensive guide to the abend codes most frequently encountered in COBOL programs.

S0C7: Data Exception

What it means: A decimal arithmetic or comparison instruction encountered data that is not in valid packed decimal (COMP-3) or zoned decimal format.

How to diagnose:

  1. Find the failing instruction address in the PSW (Program Status Word) from the dump.
  2. Subtract the program load point to get the offset.
  3. Look up the offset in the OFFSET listing to find the COBOL statement.
  4. Examine the variables used in that statement.
  5. In the dump, locate those variables using the DATA MAP offsets and examine their hexadecimal content.

Common scenarios and fixes:

      * SCENARIO 1: Uninitialized COMP-3 variable
      * The field contains X'0000' instead of X'0000000C'
       01  WS-TOTAL  PIC S9(5)V99 COMP-3.
      * FIX: Always initialize
       01  WS-TOTAL  PIC S9(5)V99 COMP-3 VALUE 0.

      * SCENARIO 2: Moving alphanumeric to numeric without check
           MOVE WS-INPUT-FIELD TO WS-NUMERIC-FIELD
           ADD WS-NUMERIC-FIELD TO WS-TOTAL
      * FIX: Validate before using
           IF WS-INPUT-FIELD IS NUMERIC
               MOVE WS-INPUT-FIELD TO WS-NUMERIC-FIELD
               ADD WS-NUMERIC-FIELD TO WS-TOTAL
           ELSE
               DISPLAY 'INVALID DATA: ' WS-INPUT-FIELD
           END-IF

      * SCENARIO 3: Record area reused after EOF
           READ INPUT-FILE INTO WS-RECORD
               AT END SET WS-EOF TO TRUE
           END-READ
      * If WS-EOF, WS-RECORD may contain partial/invalid data
      * FIX: Only use WS-RECORD when read was successful
           IF WS-NOT-EOF
               ADD WS-RECORD-AMOUNT TO WS-TOTAL
           END-IF

      * SCENARIO 4: REDEFINES mismatch
       01  WS-DATA-AREA.
           05  WS-TEXT-FIELD  PIC X(10) VALUE 'HELLO'.
           05  WS-NUM-FIELD   REDEFINES WS-TEXT-FIELD
                              PIC S9(7)V99 COMP-3.
      * Using WS-NUM-FIELD in arithmetic will S0C7 because
      * 'HELLO' is not valid COMP-3 data

S0C4: Protection Exception

What it means: The program attempted to access a memory address that is not allocated to the program or is outside the addressable range.

How to diagnose:

  1. Check the PSW for the failing instruction address.
  2. Check register contents in the dump -- a register used as a base address may contain an invalid value (X'00000000' is a common culprit indicating an uninitialized pointer).
  3. Check subscript/index values for table references.
  4. Check LINKAGE SECTION references to ensure data was actually passed.

Common scenarios and fixes:

      * SCENARIO 1: Table subscript out of range
       01  WS-TABLE.
           05  WS-ITEM  PIC X(20) OCCURS 100 TIMES.
       01  WS-SUB      PIC 9(4) VALUE 0.

      * If WS-SUB = 0 or > 100, S0C4:
           MOVE 'DATA' TO WS-ITEM(WS-SUB)
      * FIX: Validate subscript
           IF WS-SUB >= 1 AND WS-SUB <= 100
               MOVE 'DATA' TO WS-ITEM(WS-SUB)
           END-IF

      * SCENARIO 2: LINKAGE SECTION reference without data
       LINKAGE SECTION.
       01  LS-PARAMETER  PIC X(100).
       PROCEDURE DIVISION USING LS-PARAMETER.
      * If caller did not pass a parameter, accessing
      * LS-PARAMETER will S0C4

      * SCENARIO 3: Reference modification out of range
           MOVE WS-DATA(WS-START:WS-LENGTH)
               TO WS-OUTPUT
      * If WS-START = 0 or WS-START + WS-LENGTH - 1 > length
      * of WS-DATA, S0C4
      * FIX: Validate reference modification values
           IF WS-START >= 1 AND
              (WS-START + WS-LENGTH - 1) <=
                  FUNCTION LENGTH(WS-DATA)
               MOVE WS-DATA(WS-START:WS-LENGTH)
                   TO WS-OUTPUT
           END-IF

      * SCENARIO 4: CALL with wrong number of parameters
           CALL 'SUBPROG' USING WS-PARM1
      *    But SUBPROG expects USING LS-P1 LS-P2 LS-P3
      *    Accessing LS-P2 or LS-P3 will likely S0C4

S0C1: Operation Exception

What it means: The CPU encountered an invalid machine instruction.

Common causes and fixes:

      * SCENARIO 1: Falling through end of program
       PROCEDURE DIVISION.
       0000-MAIN.
           PERFORM 1000-PROCESS
      *    Missing STOP RUN or GOBACK here!
      *    Execution continues into whatever follows the
      *    program's object code in memory
      * FIX: Always end with STOP RUN or GOBACK
           GOBACK
           .

      * SCENARIO 2: Dynamic CALL to non-existent program
           MOVE 'BADPROG' TO WS-PROG-NAME
           CALL WS-PROG-NAME USING WS-PARMS
      * FIX: Use ON EXCEPTION
           CALL WS-PROG-NAME USING WS-PARMS
               ON EXCEPTION
                   DISPLAY 'PROGRAM NOT FOUND: '
                       WS-PROG-NAME
           END-CALL

S0CB: Division Exception

What it means: A fixed-point divide produced a quotient too large for the register, or division by zero occurred.

      * Prevention: Always check for zero divisor
           IF WS-DIVISOR NOT = ZERO
               DIVIDE WS-DIVIDEND BY WS-DIVISOR
                   GIVING WS-QUOTIENT
                   ON SIZE ERROR
                       DISPLAY 'OVERFLOW IN DIVISION'
               END-DIVIDE
           ELSE
               DISPLAY 'ZERO DIVISOR DETECTED'
               MOVE 0 TO WS-QUOTIENT
           END-IF

S322: Job Step Timed Out

What it means: The job step exceeded its CPU time limit (TIME parameter on JOB or EXEC statement in JCL).

How to diagnose:

  1. Check for infinite loops by examining the last paragraph executed (shown in the dump or LE traceback).
  2. Check loop termination conditions.
  3. Check file I/O patterns for inefficiency.
      * Common cause: PERFORM UNTIL with unreachable condition
       2000-PROCESS-LOOP.
           PERFORM UNTIL WS-DONE
               PERFORM 2100-READ-NEXT
      *        BUG: WS-DONE is never set to TRUE!
               PERFORM 2200-PROCESS
           END-PERFORM
           .

      * FIX: Ensure the termination condition is reachable
       2100-READ-NEXT.
           READ INPUT-FILE
               AT END
                   SET WS-DONE TO TRUE
               NOT AT END
                   ADD 1 TO WS-READ-COUNT
           END-READ
           .

      * Additional safety: Add a maximum iteration guard
       01  WS-SAFETY-COUNTER  PIC 9(9) COMP VALUE 0.
       01  WS-MAX-ITERATIONS  PIC 9(9) COMP VALUE 50000000.

       2000-PROCESS-LOOP.
           PERFORM UNTIL WS-DONE
               ADD 1 TO WS-SAFETY-COUNTER
               IF WS-SAFETY-COUNTER > WS-MAX-ITERATIONS
                   DISPLAY 'SAFETY LIMIT REACHED'
                   SET WS-DONE TO TRUE
               ELSE
                   PERFORM 2100-READ-NEXT
                   IF NOT WS-DONE
                       PERFORM 2200-PROCESS
                   END-IF
               END-IF
           END-PERFORM
           .

S806: Load Module Not Found

What it means: The system could not find the load module (program) specified in a dynamic CALL or LINK.

How to diagnose:

  1. Check the module name for typos (including trailing spaces).
  2. Check the STEPLIB/JOBLIB DD statements in the JCL.
  3. Verify the module has been compiled, linked, and placed in the load library.
      * Prevention: Use ON EXCEPTION
           CALL WS-MODULE-NAME USING WS-PARMS
               ON EXCEPTION
                   DISPLAY 'MODULE NOT IN LOADLIB: '
                       WS-MODULE-NAME
                   INSPECT WS-MODULE-NAME
                       TALLYING WS-SPACE-COUNT
                       FOR TRAILING SPACES
                   DISPLAY 'NAME LENGTH (LESS TRAILING SPACES): '
                       (FUNCTION LENGTH(WS-MODULE-NAME)
                        - WS-SPACE-COUNT)
           END-CALL

20.5 Dump Analysis

When a COBOL program abends on z/OS, the system produces a dump -- a snapshot of the program's memory at the time of failure. Reading a dump is an essential skill for COBOL programmers working on mainframes. While it can seem intimidating at first, the process follows a systematic procedure.

Components of a Dump

A typical SYSUDUMP or SYSABEND dump contains:

  1. Job and step information: Job name, step name, program name, abend code
  2. PSW (Program Status Word): Contains the address of the instruction that caused the abend
  3. General Purpose Registers (GPR): Register contents at the time of the abend
  4. Program storage: The hexadecimal contents of the program's code and data areas
  5. LE Traceback: A formatted traceback showing the chain of COBOL paragraphs executed (IBM Language Environment)

Step-by-Step Dump Analysis

Step 1: Identify the abend code and PSW

Look at the beginning of the dump for the completion code:

COMPLETION CODE - SYSTEM=0C7  USER=0000  REASON=00000000
PSW AT TIME OF ERROR  078D1000 80012A4E

The PSW address is X'80012A4E'. The high-order bit (8) indicates 31-bit addressing mode. The effective address is X'00012A4E'.

Step 2: Find the program entry point

Look for the program's entry point address in the dump or the LE traceback:

ENTRY POINT = 00012000

Step 3: Calculate the offset

Subtract the entry point from the PSW address:

00012A4E - 00012000 = 00000A4E

The failing instruction is at offset X'A4E' in the program.

Step 4: Look up the offset in the compiler listing

In your OFFSET listing:

LINE #   HEXLOC   VERB
000245   000A3C   MOVE
000247   000A4E   ADD      <-- This is the failing statement
000249   000A5A   IF

Line 247 is an ADD statement. Now you know which COBOL statement caused the S0C7.

Step 5: Examine the data

Use the DATA MAP to find the offsets of the variables used in line 247, then locate those offsets in the WORKING-STORAGE dump to see their hexadecimal contents.

LE Traceback

IBM Language Environment provides a formatted traceback that is much easier to read than a raw dump:

CEE3DMP V2 R1.0: Condition processing resulted in the
unhandled condition.
Information for enclave CUSTPROC

Traceback:
  DSA   Entry     E  Offset Statement
  1     CUSTPROC  1  +00A4E  247
  2     CUSTPROC  1  +00832  198   (PERFORM from 0000-MAIN)
  3     IGZCFCC   1  +001A8

This tells you directly that the abend occurred at statement 247, which was called via a PERFORM at statement 198 from 0000-MAIN. This is far quicker than manual offset calculation.

Reading Hexadecimal Data

When examining dump data, you need to understand how COBOL data types appear in hexadecimal:

COBOL Type Example Value Hex Representation
PIC X(5) VALUE 'HELLO' HELLO C8C5D3D3D6 (EBCDIC)
PIC 9(5) VALUE 12345 12345 F1F2F3F4F5 (Zoned)
PIC 9(5) COMP VALUE 12345 12345 00003039 (Binary)
PIC S9(5) COMP-3 VALUE 12345 12345 12345C (Packed)
PIC S9(5) COMP-3 VALUE -12345 -12345 12345D (Packed)
PIC X(5) VALUE SPACES (spaces) 4040404040 (EBCDIC)
PIC X(5) VALUE LOW-VALUES (nulls) 0000000000

Recognizing bad data in a dump:

Field at offset 0034: 00000000 0C
Expected COMP-3 PIC S9(7)V99: should end in C, D, or F
This looks like X'000000000C' which is valid COMP-3 for +0.00

Field at offset 0034: C8C5D3D3 D6
This is 'HELLO' in EBCDIC -- clearly not valid COMP-3!
This is the cause of the S0C7.

20.6 IBM Debug Tool (IBM Debugger)

IBM Debug Tool (now part of IBM Developer for z/OS) is an interactive source-level debugger for COBOL, PL/I, C/C++, and Assembler programs running on z/OS. It allows you to set breakpoints, step through code line by line, examine and modify variable values, and watch variables for changes -- capabilities that transform debugging from dump analysis to interactive investigation.

Compiling for Debug Tool

To use Debug Tool, you must compile your program with the TEST compiler option:

//COBOL  EXEC PGM=IGYCRCTL,
//       PARM='TEST(ALL,SYM,SEPARATE),SOURCE,LIST'

The TEST option parameters: - ALL: Generate debug information for all statements - SYM: Include symbolic variable names (required for variable display) - SEPARATE: Store debug information in a separate debug side file

Launching Debug Tool

Debug Tool can be launched in several modes:

Batch mode (most common for batch COBOL programs):

//CEEOPTS  DD *
  TEST(ALL,*,PROMPT,INSPPREF%USERID:.DBGTOOL.PREFS:*)
/*
//INSPLOG  DD SYSOUT=*

Full-screen mode (via 3270 terminal):

//CEEOPTS  DD *
  TEST(ALL,*,PROMPT,VTAM%userid:*)
/*

Common Debug Tool Commands

Once Debug Tool is active, you can use commands interactively:

AT 247                     -- Set breakpoint at line 247
AT ENTRY 2000-PROCESS      -- Break at paragraph entry
AT CALL                    -- Break at every CALL statement

GO                         -- Run until next breakpoint
STEP                       -- Execute one statement
STEP 5                     -- Execute five statements

LIST WS-AMOUNT             -- Display variable value
LIST WS-RECORD-AREA        -- Display group item
LIST TITLED (WS-COUNTERS)  -- Display with field names
LIST STORAGE(WS-AMOUNT) 8  -- Display 8 bytes of storage

MOVE 100 TO WS-AMOUNT      -- Change a variable value
SET WS-EOF TO TRUE          -- Set a condition name

AT CHANGE WS-AMOUNT DO     -- Watch for variable change
  LIST WS-AMOUNT
  LIST TITLED (WS-COUNTERS)
END-AT

QUERY LOCATION              -- Show current location
QUERY QUALIFY               -- Show current qualification

CLEAR AT *                  -- Remove all breakpoints

Debug Tool with Batch Programs

For batch programs, Debug Tool is typically used with a commands file that automates common debugging steps:

//INSPCMD  DD *
  AT ENTRY 2000-PROCESS
  AT 247
  GO;
  LIST TITLED (WS-INPUT-RECORD);
  LIST WS-CUST-BALANCE;
  GO;
/*

20.7 GnuCOBOL Debugging

GnuCOBOL provides several debugging mechanisms that parallel the mainframe tools, adapted for Linux/Unix/Windows environments.

Compile-Time Debug Options

# Compile with debugging enabled
cobc -x -debug program.cob

# Compile with all checks (bounds checking, etc.)
cobc -x -debug -Wall -fcheck program.cob

# Generate a listing file
cobc -x -t listing.txt program.cob

# Compile with GDB debugging symbols
cobc -x -g program.cob

Runtime Debug Environment Variables

GnuCOBOL supports several environment variables that control runtime debugging:

# Enable runtime debugging
export COB_SET_DEBUG=Y

# Enable runtime tracing (output to stderr)
export COB_SET_TRACE=Y

# Enable paragraph-level tracing
export COB_TRACE_FILE=trace.log

# Enable core dumps on error
export COB_CORE_ON_ERROR=1

# Set dump output file
export COB_DUMP_FILE=dump.txt

Using GDB with GnuCOBOL

Since GnuCOBOL compiles COBOL to C and then to native machine code, you can use the GNU Debugger (GDB) to debug COBOL programs. Compile with -g to include debug symbols:

# Compile with GDB symbols
cobc -x -g -fdebugging-line program.cob

# Launch GDB
gdb ./program

Common GDB commands for COBOL debugging:

(gdb) break program.cob:247        # Break at line 247
(gdb) break 2000-PROCESS           # Break at paragraph
(gdb) run                          # Start the program
(gdb) next                         # Step to next line
(gdb) step                         # Step into subprogram
(gdb) print WS-AMOUNT              # Display variable
(gdb) print WS-RECORD-AREA         # Display group
(gdb) set WS-AMOUNT = 100          # Change variable
(gdb) backtrace                    # Show call stack
(gdb) continue                     # Run to next breakpoint
(gdb) info locals                  # Show local variables
(gdb) quit                         # Exit GDB

GnuCOBOL Runtime Tracing Example

       IDENTIFICATION DIVISION.
       PROGRAM-ID. TRACEDEM.

       ENVIRONMENT DIVISION.
       CONFIGURATION SECTION.
       SOURCE-COMPUTER. PC WITH DEBUGGING MODE.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-COUNTER      PIC 9(3) VALUE 0.
       01  WS-TOTAL         PIC 9(7) VALUE 0.
       01  WS-INPUT         PIC 9(5).

       PROCEDURE DIVISION.
      D    DISPLAY 'DEBUG MODE IS ACTIVE'

       0000-MAIN.
           PERFORM 1000-PROCESS 5 TIMES
           DISPLAY 'TOTAL: ' WS-TOTAL
           STOP RUN
           .

       1000-PROCESS.
           ADD 1 TO WS-COUNTER
      D    DISPLAY 'ITERATION: ' WS-COUNTER
           MOVE WS-COUNTER TO WS-INPUT
           COMPUTE WS-TOTAL = WS-TOTAL +
               (WS-INPUT * WS-INPUT)
      D    DISPLAY 'RUNNING TOTAL: ' WS-TOTAL
           .

Running with tracing:

export COB_SET_TRACE=Y
export COB_TRACE_FILE=trace.log
./tracedem

The trace log shows each paragraph entry and exit:

Source: 'TRACEDEM.cob'
Program-Id: TRACEDEM    Entry:     0000-MAIN
Program-Id: TRACEDEM    Section:   (None)
Program-Id: TRACEDEM    Paragraph: 0000-MAIN
Program-Id: TRACEDEM    Paragraph: 1000-PROCESS
Program-Id: TRACEDEM    Paragraph: 1000-PROCESS  Exit
Program-Id: TRACEDEM    Paragraph: 1000-PROCESS
...

20.8 Debugging COBOL-DB2 Programs

COBOL programs that use embedded SQL introduce additional debugging challenges. SQL errors produce SQLCODEs rather than abend codes, and the interaction between the COBOL program and the DB2 engine creates failure modes that do not exist in pure COBOL programs.

SQLCODE-Based Debugging

The first step in debugging any DB2 error is to capture and display the SQLCODE, SQLSTATE, and SQLERRMC fields from the SQLCA:

       9100-SQL-ERROR.
           DISPLAY '*** DB2 ERROR ***'
           DISPLAY 'SQLCODE:   ' SQLCODE
           DISPLAY 'SQLSTATE:  ' SQLSTATE
           DISPLAY 'SQLERRMC:  ' SQLERRMC(1:SQLERRML)
           DISPLAY 'SQLERRD(1):' SQLERRD(1)
           DISPLAY 'SQLERRD(2):' SQLERRD(2)
           DISPLAY 'SQLERRD(3):' SQLERRD(3)
           DISPLAY 'SQLERRD(4):' SQLERRD(4)
           DISPLAY 'SQLERRD(5):' SQLERRD(5)
           DISPLAY 'SQLERRD(6):' SQLERRD(6)
           DISPLAY 'PARAGRAPH: ' WS-CURRENT-PARAGRAPH
           .

Common DB2 Debugging Scenarios

SQLCODE -818: Timestamp Mismatch

This error means the program's DBRM (produced by the DB2 precompile) does not match the bound package. The program was recompiled but not rebound, or vice versa.

Fix: Rebind the package using the current DBRM.

SQLCODE -805: Package Not Found

The program references a package that does not exist. This usually means the program was never bound, or it was bound to a different collection.

Fix: Bind the package, or verify the PLAN/COLLECTION that the program is using.

SQLCODE -904: Unavailable Resource

DB2 cannot access a required resource (tablespace, index, or the DB2 subsystem itself). The resource may be stopped, in a pending state, or unavailable due to a utility running against it.

Fix: Check the DB2 resource status with the DB2 DISPLAY commands.

Using DSNTEP2 for SQL Testing

Before embedding complex SQL in a COBOL program, test it interactively using DSNTEP2 (DB2's batch SQL processor) or SPUFI (SQL Processor Using File Input):

//DSNTEP2  EXEC PGM=IKJEFT01,DYNAMNBR=20
//STEPLIB  DD DSN=DSN.V13R1.SDSNLOAD,DISP=SHR
//SYSTSPRT DD SYSOUT=*
//SYSPRINT DD SYSOUT=*
//SYSUDUMP DD SYSOUT=*
//SYSTSIN  DD *
  DSN SYSTEM(DB2P)
  RUN PROGRAM(DSNTEP2) PLAN(DSNTEP2)
/*
//SYSIN    DD *
  SELECT CUST_ID, CUST_NAME, CUST_BALANCE
  FROM CUSTOMER
  WHERE CUST_STATUS = 'A'
  ORDER BY CUST_NAME
  FETCH FIRST 10 ROWS ONLY;
/*

20.9 Unit Testing COBOL Programs

Unit testing -- the practice of writing automated tests that verify individual program components in isolation -- has been standard practice in modern software development for decades. In the COBOL world, unit testing adoption has been slower, but several frameworks now make it practical.

cobol-check: A Unit Testing Framework for COBOL

cobol-check is an open-source unit testing framework designed specifically for COBOL. It allows you to write test cases that are embedded alongside or adjacent to your COBOL source code.

Installing cobol-check:

cobol-check runs on the JVM and can be used with GnuCOBOL. It is downloaded from its GitHub repository.

Writing a test:

Test cases are written in a COBOL-like syntax within special test source files:

      * File: DATEUTIL.cut (COBOL Under Test)

       TESTSUITE 'DATE VALIDATION TESTS'

       TESTCASE 'VALID DATE RETURNS SUCCESS'
           MOVE '20250115' TO WS-INPUT-DATE
           PERFORM 1000-VALIDATE-DATE
           EXPECT WS-DATE-VALID TO BE TRUE
           EXPECT WS-RETURN-CODE TO EQUAL 0

       TESTCASE 'INVALID MONTH RETURNS ERROR'
           MOVE '20251315' TO WS-INPUT-DATE
           PERFORM 1000-VALIDATE-DATE
           EXPECT WS-DATE-VALID TO BE FALSE
           EXPECT WS-RETURN-CODE TO EQUAL 8

       TESTCASE 'FEBRUARY 29 IN LEAP YEAR IS VALID'
           MOVE '20240229' TO WS-INPUT-DATE
           PERFORM 1000-VALIDATE-DATE
           EXPECT WS-DATE-VALID TO BE TRUE

       TESTCASE 'FEBRUARY 29 IN NON-LEAP YEAR IS INVALID'
           MOVE '20250229' TO WS-INPUT-DATE
           PERFORM 1000-VALIDATE-DATE
           EXPECT WS-DATE-VALID TO BE FALSE

Running tests:

# Run cobol-check
java -jar cobol-check.jar -p DATEUTIL

# Output:
# TESTSUITE: DATE VALIDATION TESTS
#   PASS: VALID DATE RETURNS SUCCESS
#   PASS: INVALID MONTH RETURNS ERROR
#   PASS: FEBRUARY 29 IN LEAP YEAR IS VALID
#   PASS: FEBRUARY 29 IN NON-LEAP YEAR IS INVALID
# 4 TESTS, 4 PASSED, 0 FAILED

IBM zUnit

For IBM Enterprise COBOL on z/OS, IBM provides zUnit (part of IBM Developer for z/OS), which allows writing and running unit tests for COBOL programs in a mainframe environment.

zUnit tests are written in COBOL and use IBM-provided copybooks for assertions:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. TCALCPAY.
      * zUnit test case for CALCPAY subprogram

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       COPY AZUNIT.

       01  WS-HOURS         PIC 9(3)V9 VALUE 40.0.
       01  WS-RATE           PIC 9(3)V99 VALUE 25.00.
       01  WS-GROSS-PAY      PIC 9(5)V99 VALUE 0.
       01  WS-EXPECTED-PAY   PIC 9(5)V99 VALUE 1000.00.

       PROCEDURE DIVISION.
           MOVE 'TEST REGULAR HOURS' TO AZ-TEST-NAME

           CALL 'CALCPAY' USING WS-HOURS
                                 WS-RATE
                                 WS-GROSS-PAY
           END-CALL

           IF WS-GROSS-PAY = WS-EXPECTED-PAY
               MOVE 'PASS' TO AZ-TEST-RESULT
           ELSE
               MOVE 'FAIL' TO AZ-TEST-RESULT
               STRING 'EXPECTED ' WS-EXPECTED-PAY
                      ' GOT ' WS-GROSS-PAY
                   DELIMITED BY SIZE
                   INTO AZ-TEST-MESSAGE
               END-STRING
           END-IF

           CALL 'AZUNIT' USING AZ-TEST-RECORD
           GOBACK
           .

Test Driver Approach

Even without a formal testing framework, you can create simple test driver programs that call your subprograms with known inputs and verify the outputs:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. TESTDRV.
      *========================================================*
      * TEST DRIVER FOR VALIDATECUST SUBPROGRAM                 *
      *========================================================*

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-TEST-COUNT      PIC 99 VALUE 0.
       01  WS-PASS-COUNT      PIC 99 VALUE 0.
       01  WS-FAIL-COUNT      PIC 99 VALUE 0.

       01  WS-CUSTOMER-DATA.
           05  WS-CUST-ID     PIC X(10).
           05  WS-CUST-NAME   PIC X(30).
           05  WS-CUST-BAL    PIC S9(7)V99 COMP-3.

       01  WS-EXPECTED-RC     PIC S9(4) COMP.
       01  WS-ACTUAL-RC       PIC S9(4) COMP.

       PROCEDURE DIVISION.
       0000-MAIN.
           DISPLAY '===== VALIDATECUST TEST SUITE ====='

      *    TEST 1: Valid customer
           MOVE 'CUST000001' TO WS-CUST-ID
           MOVE 'JOHN DOE'   TO WS-CUST-NAME
           MOVE 1000.00      TO WS-CUST-BAL
           MOVE 0            TO WS-EXPECTED-RC
           PERFORM 8000-RUN-TEST

      *    TEST 2: Blank customer ID
           MOVE SPACES        TO WS-CUST-ID
           MOVE 'JANE DOE'    TO WS-CUST-NAME
           MOVE 500.00        TO WS-CUST-BAL
           MOVE 8             TO WS-EXPECTED-RC
           PERFORM 8000-RUN-TEST

      *    TEST 3: Negative balance (warning)
           MOVE 'CUST000003'  TO WS-CUST-ID
           MOVE 'BOB SMITH'   TO WS-CUST-NAME
           MOVE -100.00       TO WS-CUST-BAL
           MOVE 4             TO WS-EXPECTED-RC
           PERFORM 8000-RUN-TEST

           DISPLAY '============================='
           DISPLAY 'TOTAL: ' WS-TEST-COUNT
               ' PASS: ' WS-PASS-COUNT
               ' FAIL: ' WS-FAIL-COUNT
           DISPLAY '============================='

           IF WS-FAIL-COUNT > 0
               MOVE 8 TO RETURN-CODE
           ELSE
               MOVE 0 TO RETURN-CODE
           END-IF
           STOP RUN
           .

       8000-RUN-TEST.
           ADD 1 TO WS-TEST-COUNT
           DISPLAY 'TEST ' WS-TEST-COUNT ':'

           CALL 'VALIDATECUST' USING WS-CUSTOMER-DATA
           MOVE RETURN-CODE TO WS-ACTUAL-RC

           IF WS-ACTUAL-RC = WS-EXPECTED-RC
               ADD 1 TO WS-PASS-COUNT
               DISPLAY '  PASS (RC=' WS-ACTUAL-RC ')'
           ELSE
               ADD 1 TO WS-FAIL-COUNT
               DISPLAY '  FAIL - EXPECTED RC='
                   WS-EXPECTED-RC
                   ' GOT RC=' WS-ACTUAL-RC
           END-IF
           .

20.9 Debugging Common Problems

Beyond the abend codes covered in Section 20.4, several categories of bugs recur frequently in COBOL programs. This section covers diagnosis and resolution of these common problems.

Problem: Wrong Results (No Abend)

The program completes successfully (return code 0) but produces incorrect output. These bugs are often harder to find than abends because there is no dump, no abend code, and no obvious failure point.

Diagnostic approach:

      * Add checkpoints to trace data flow
       2000-CALCULATE-INTEREST.
           DISPLAY 'CHECKPOINT 1: PRINCIPAL='
               WS-PRINCIPAL
               ' RATE=' WS-RATE
               ' DAYS=' WS-DAYS

           COMPUTE WS-DAILY-RATE =
               WS-RATE / 365
           DISPLAY 'CHECKPOINT 2: DAILY-RATE='
               WS-DAILY-RATE

           COMPUTE WS-INTEREST =
               WS-PRINCIPAL * WS-DAILY-RATE * WS-DAYS
           DISPLAY 'CHECKPOINT 3: INTEREST='
               WS-INTEREST
           .

Common causes:

  1. Truncation: A receiving field is too small for the result.
  2. Missing ROUNDED: Decimal truncation instead of rounding.
  3. Wrong sign: Using unsigned PIC 9 when PIC S9 is needed.
  4. Implicit decimal alignment: Moving PIC 9(5)V99 to PIC 9(7) loses the decimal places.
  5. REDEFINES confusion: Reading one format, processing as another.

Problem: Records Missing from Output

Diagnostic approach:

      * Add counters at every decision point
       01  WS-DIAG-COUNTERS.
           05  WS-TOTAL-READ    PIC 9(7) VALUE 0.
           05  WS-PASSED-EDIT   PIC 9(7) VALUE 0.
           05  WS-FAILED-EDIT   PIC 9(7) VALUE 0.
           05  WS-WRITTEN       PIC 9(7) VALUE 0.
           05  WS-SKIPPED       PIC 9(7) VALUE 0.

       3000-TERMINATE.
           DISPLAY 'TOTAL READ:    ' WS-TOTAL-READ
           DISPLAY 'PASSED EDIT:   ' WS-PASSED-EDIT
           DISPLAY 'FAILED EDIT:   ' WS-FAILED-EDIT
           DISPLAY 'WRITTEN:       ' WS-WRITTEN
           DISPLAY 'SKIPPED:       ' WS-SKIPPED
           DISPLAY 'BALANCE CHECK: '
           COMPUTE WS-BALANCE =
               WS-TOTAL-READ -
               WS-PASSED-EDIT - WS-FAILED-EDIT
           IF WS-BALANCE NOT = 0
               DISPLAY '*** RECORDS UNACCOUNTED FOR: '
                   WS-BALANCE ' ***'
           END-IF
           .

Problem: Infinite Loop (S322)

Diagnostic approach:

      * Add a safety counter and periodic progress display
       01  WS-LOOP-DIAG.
           05  WS-ITERATION     PIC 9(9) COMP VALUE 0.
           05  WS-LAST-KEY      PIC X(10) VALUE SPACES.
           05  WS-DISPLAY-FREQ  PIC 9(5) COMP VALUE 10000.

       2000-PROCESS-LOOP.
           PERFORM UNTIL WS-DONE
               ADD 1 TO WS-ITERATION

               IF FUNCTION MOD(WS-ITERATION,
                               WS-DISPLAY-FREQ) = 0
                   DISPLAY 'PROGRESS: ITERATION '
                       WS-ITERATION
                       ' CURRENT KEY: ' WS-CURRENT-KEY
                       ' LAST KEY: ' WS-LAST-KEY
               END-IF

               IF WS-CURRENT-KEY = WS-LAST-KEY
                   DISPLAY '*** KEY NOT ADVANCING: '
                       WS-CURRENT-KEY
                   DISPLAY '*** POSSIBLE INFINITE LOOP'
                   SET WS-DONE TO TRUE
               END-IF

               MOVE WS-CURRENT-KEY TO WS-LAST-KEY
               PERFORM 2100-PROCESS-RECORD
               PERFORM 2200-READ-NEXT
           END-PERFORM
           .

Problem: File I/O Errors After OPEN

      * Comprehensive file open diagnostic
       1100-OPEN-INPUT.
           DISPLAY 'OPENING INPUT FILE...'
           OPEN INPUT INPUT-FILE
           DISPLAY 'OPEN STATUS: ' WS-INPUT-STATUS

           EVALUATE WS-INPUT-STATUS
               WHEN '00'
                   DISPLAY 'FILE OPENED SUCCESSFULLY'
               WHEN '05'
                   DISPLAY 'WARNING: OPTIONAL FILE NOT PRESENT'
               WHEN '35'
                   DISPLAY 'ERROR: FILE DOES NOT EXIST'
                   DISPLAY 'CHECK DD STATEMENT IN JCL'
                   SET WS-ABORT TO TRUE
               WHEN '37'
                   DISPLAY 'ERROR: FILE DOES NOT SUPPORT '
                       'REQUESTED OPEN MODE'
                   SET WS-ABORT TO TRUE
               WHEN '39'
                   DISPLAY 'ERROR: FILE ATTRIBUTES MISMATCH'
                   DISPLAY 'CHECK RECORD LENGTH AND FORMAT'
                   SET WS-ABORT TO TRUE
               WHEN '41'
                   DISPLAY 'ERROR: FILE ALREADY OPEN'
                   SET WS-ABORT TO TRUE
               WHEN '96'
                   DISPLAY 'ERROR: DD STATEMENT MISSING'
                   SET WS-ABORT TO TRUE
               WHEN OTHER
                   DISPLAY 'UNEXPECTED STATUS: '
                       WS-INPUT-STATUS
                   SET WS-ABORT TO TRUE
           END-EVALUATE
           .

20.10 Debugging CICS Programs

CICS programs present unique debugging challenges because they run inside the CICS region, share memory with other tasks, and use pseudo-conversational programming where each user interaction creates a new task.

CEDF: The CICS Execution Diagnostic Facility

CEDF (pronounced "sed-f") is an interactive debugging tool built into CICS. It intercepts every EXEC CICS command and shows the programmer what the program is doing, step by step.

To activate CEDF for a terminal, type the transaction code CEDF and press Enter. Then enter your application's transaction code. CEDF will intercept every EXEC CICS command and display the command, its options, and the result:

TRANSACTION CUSTINQ - EIB INFORMATION

EIBTIME  = 143025    EIBDATE  = 0125015
EIBTRNID = INQC      EIBTASKN = 00358
EIBTRMID = S027      EIBCPOSN = 00399
EIBCALEN = 00012     EIBAID   = ENTER

ABOUT TO EXECUTE COMMAND:
  EXEC CICS READ
    FILE('CUSTFILE')
    INTO(........)
    RIDFLD(X'0000004D3B')
    LENGTH(51)
    RESP(........)
    RESP2(........)

PRESS ENTER TO CONTINUE

After the command executes, CEDF shows the results:

COMMAND EXECUTION COMPLETE:
  EXEC CICS READ
    FILE('CUSTFILE')
    INTO(........)
    RIDFLD(X'0000004D3B')
    LENGTH(51)
    RESP(NORMAL)
    RESP2(+0000000000)

PRESS ENTER TO CONTINUE

CEDF allows you to modify data values before a command executes, which is useful for testing error conditions without having to create test data.

Temporary Storage Queue Debugging

A useful debugging technique in CICS is to write diagnostic information to a Temporary Storage Queue (TSQ) that can be browsed after the transaction completes:

       9000-DEBUG-LOG.
           STRING EIBTRNID EIBTRMID
               ' ' WS-DEBUG-MESSAGE
               DELIMITED BY SIZE
               INTO WS-DEBUG-RECORD
           END-STRING

           EXEC CICS WRITEQ TS
               QUEUE(WS-DEBUG-QUEUE)
               FROM(WS-DEBUG-RECORD)
               LENGTH(LENGTH OF WS-DEBUG-RECORD)
               RESP(WS-CICS-RESP)
           END-EXEC
           .

The TSQ can then be browsed using the CEBR transaction (CICS Extended Browse), which displays the contents of any TSQ.

CICS Transaction Dump

When a CICS application abends, CICS produces a transaction dump. Unlike a batch dump, a CICS dump includes the COMMAREA contents, the EIB, and the terminal buffer in addition to WORKING-STORAGE. The dump is written to the CICS dump dataset and can be viewed using the CICS-supplied transaction CDMP or through the IPCS utility.


20.11 Prevention Through Defensive Programming

The best debugging technique is to prevent bugs from occurring in the first place. This section summarizes defensive programming practices that eliminate the most common categories of COBOL bugs.

Always Initialize Variables

      * GOOD: Explicit initialization
       01  WS-TOTAL        PIC S9(7)V99 COMP-3 VALUE 0.
       01  WS-COUNT        PIC 9(5) COMP VALUE 0.
       01  WS-FLAG         PIC 9 VALUE 0.

      * ALSO GOOD: Initialize at start of processing
       1000-INITIALIZE.
           INITIALIZE WS-WORK-FIELDS
           INITIALIZE WS-COUNTERS
           INITIALIZE WS-ACCUMULATORS
           .

Always Check File Status

      * After EVERY I/O operation:
           OPEN INPUT MASTER-FILE
           IF WS-MASTER-STATUS NOT = '00'
               PERFORM 9100-FILE-ERROR
           END-IF

           READ MASTER-FILE INTO WS-MASTER-REC
           EVALUATE WS-MASTER-STATUS
               WHEN '00'    CONTINUE
               WHEN '10'    SET WS-EOF TO TRUE
               WHEN OTHER   PERFORM 9100-FILE-ERROR
           END-EVALUATE

Always Validate Before Using Numeric Data

           IF WS-INPUT-AMOUNT IS NUMERIC
               MOVE WS-INPUT-AMOUNT TO WS-WORK-AMOUNT
               ADD WS-WORK-AMOUNT TO WS-TOTAL
           ELSE
               DISPLAY 'NON-NUMERIC: ' WS-INPUT-AMOUNT
               ADD 1 TO WS-ERROR-COUNT
           END-IF

Always Use Explicit Scope Terminators

      * GOOD: Explicit scope terminators
           READ INPUT-FILE
               AT END
                   SET WS-EOF TO TRUE
               NOT AT END
                   ADD 1 TO WS-COUNT
           END-READ

      * BAD: Period-terminated, ambiguous
           READ INPUT-FILE AT END SET WS-EOF TO TRUE.

Always Protect Table Access

      * GOOD: Bounds checking
           IF WS-INDEX >= 1 AND WS-INDEX <= WS-TABLE-MAX
               MOVE WS-DATA TO WS-TABLE-ENTRY(WS-INDEX)
           ELSE
               DISPLAY 'INDEX OUT OF RANGE: ' WS-INDEX
           END-IF

Always Use ON SIZE ERROR for Arithmetic

           COMPUTE WS-RESULT = WS-A * WS-B / WS-C
               ON SIZE ERROR
                   DISPLAY 'ARITHMETIC ERROR'
                   MOVE 0 TO WS-RESULT
           END-COMPUTE

Use EVALUATE Instead of Nested IF

      * GOOD: Clear, maintainable
           EVALUATE WS-TRANS-CODE
               WHEN 'A'   PERFORM 3100-ADD
               WHEN 'C'   PERFORM 3200-CHANGE
               WHEN 'D'   PERFORM 3300-DELETE
               WHEN OTHER PERFORM 3900-INVALID-CODE
           END-EVALUATE

      * BAD: Nested IF, hard to follow, easy to misalign
           IF WS-TRANS-CODE = 'A'
               PERFORM 3100-ADD
           ELSE
               IF WS-TRANS-CODE = 'C'
                   PERFORM 3200-CHANGE
               ELSE
                   IF WS-TRANS-CODE = 'D'
                       PERFORM 3300-DELETE
                   END-IF
               END-IF
           END-IF

20.12 Memory Analysis and Storage Dumps

When more advanced debugging is required, understanding how COBOL data is laid out in memory is essential. This section covers techniques for analyzing storage content in both batch and CICS environments.

Understanding COBOL Storage Layout

COBOL programs allocate storage in several areas, each with different characteristics:

Area Contents Scope
WORKING-STORAGE All WS variables, initialized at program load Static, persists between CALLs (unless CANCELed)
LOCAL-STORAGE All LS variables, initialized at each invocation Dynamic, fresh copy each time
LINKAGE SECTION Parameters passed from caller Mapped to caller's storage
FILE SECTION File buffers Managed by runtime

In a dump, WORKING-STORAGE is a contiguous block of memory. The DATA MAP from the compiler listing gives you the offset of each variable within this block. By adding the offset to the starting address of WORKING-STORAGE (shown in the dump or traceback), you can locate any variable.

Interpreting EBCDIC and Packed Decimal in Dumps

On IBM mainframes, character data is stored in EBCDIC, not ASCII. Common character values in EBCDIC:

Character EBCDIC Hex
Space X'40'
0-9 X'F0' - X'F9'
A-I X'C1' - X'C9'
J-R X'D1' - X'D9'
S-Z X'E2' - X'E9'
LOW-VALUES X'00'
HIGH-VALUES X'FF'

Packed decimal (COMP-3) stores two digits per byte, with the sign in the low nibble of the last byte:

PIC S9(5)V99 COMP-3 VALUE +12345.67
Storage: 01 23 45 67 0C    (C = positive)

PIC S9(5)V99 COMP-3 VALUE -12345.67
Storage: 01 23 45 67 0D    (D = negative)

PIC S9(5)V99 COMP-3 VALUE 0
Storage: 00 00 00 00 0C

Invalid packed decimal data (which causes S0C7) looks like character data in the packed field:

Expected: 01 23 45 67 0C
Actual:   C1 C2 C3 C4 C5    (This is 'ABCDE' in EBCDIC!)
The sign nibble 5 is not valid (must be C, D, or F).

Using IPCS for Dump Analysis

IPCS (Interactive Problem Control System) is the z/OS tool for analyzing dumps. Key IPCS commands for COBOL debugging:

VERBEXIT LEDATA        - Display LE runtime information
VERBEXIT CEDATA        - Display COBOL-specific data
VERBX LEDATA 'ALL'     - Complete LE diagnostic output
VERBX LEDATA 'CEEDUMP' - Formatted COBOL variable display

LIST address LENGTH(n) - Display n bytes at address

The LEDATA verb exit is particularly useful because it can display COBOL variables by name, showing both the formatted value and the hexadecimal content.


20.13 Debugging Checklist

When a COBOL program fails, follow this systematic checklist:

  1. Read the abend code: What type of error occurred? (S0C7, S0C4, S0C1, S322, etc.)
  2. Read the LE traceback: Which paragraph and line number did it fail at?
  3. Check the compiler listing: What statement is at that line? What variables are involved?
  4. Check the DATA MAP: What are the offsets and types of those variables?
  5. Check the dump data: What are the hexadecimal contents of those variables? Are they valid for the declared type?
  6. Trace the data flow: Where did those variables get their values? Follow the XREF backwards.
  7. Check the input data: Is the input file or parameter data valid?
  8. Check the JCL: Are all DD statements present? Are file attributes correct?
  9. Check recent changes: What was changed since the program last worked?
  10. Reproduce locally: Can you reproduce the error with a subset of the data?

Summary

Debugging COBOL programs requires a combination of tools, techniques, and systematic thinking:

  • DISPLAY-based debugging is universally available and effective for tracing program flow and data values. Strategic placement at decision points, I/O operations, and loop boundaries makes it productive.
  • D in column 7 provides compile-time toggling of debug statements without editing them out of the source code.
  • Compiler listings (DATA MAP, XREF, OFFSET) are essential for dump analysis, providing the mapping between hexadecimal addresses and COBOL source statements and data items.
  • Common abend codes (S0C7, S0C4, S0C1, S0CB, S322, S806) each have specific causes, diagnostic procedures, and prevention techniques. Knowing these codes from memory is a basic professional requirement.
  • Dump analysis follows a systematic procedure: identify the abend code, extract the PSW address, calculate the offset, look up the COBOL statement, and examine the data values in hex.
  • IBM Debug Tool provides interactive source-level debugging on z/OS with breakpoints, stepping, variable inspection, and variable modification.
  • GnuCOBOL debugging uses compile options (-debug, -g), environment variables (COB_SET_TRACE, COB_SET_DEBUG), and GDB for interactive debugging on open platforms.
  • Unit testing with frameworks like cobol-check and zUnit enables proactive defect prevention through automated, repeatable tests.
  • Defensive programming -- initializing variables, checking file status, validating data, protecting array bounds, using scope terminators, and handling arithmetic overflow -- prevents the majority of production bugs.

The most effective debugging strategy is prevention. Every defensive programming technique you apply during development is a production abend you will never have to debug at 3:00 AM.


20.14 Debugging Batch vs. Online Programs: Key Differences

The debugging approach differs significantly between batch and online (CICS) programs. Understanding these differences is essential for programmers who work in both environments.

Batch Program Debugging

Batch programs run in their own address space, have exclusive access to their files (in most cases), and produce output to SYSOUT that you can review after the job completes. Debugging batch programs follows a straightforward cycle:

  1. Review the job output (SYSOUT listings, DISPLAY output)
  2. If the program abended, analyze the dump using the compiler listing
  3. Fix the source code
  4. Recompile, relink
  5. Resubmit the job
  6. Repeat until the bug is resolved

The turnaround time for each iteration is typically minutes (time to compile, link, and run).

Online Program Debugging

CICS programs share an address space with hundreds of other programs and thousands of concurrent users. You cannot simply add DISPLAY statements (DISPLAY is not available in CICS). Instead, you must use CEDF (the CICS Execution Diagnostic Facility), IBM Debug Tool in CICS mode, or write diagnostic information to TSQs or TDQs.

The pseudo-conversational model adds another challenge: each user interaction creates a new task, so you must trace the state across multiple task instances through the COMMAREA.

Key Differences Summary

Aspect Batch CICS
Output SYSOUT (DISPLAY) TSQ/TDQ, CEDF, Debug Tool
Dump SYSUDUMP/SYSABEND Transaction dump
Compile cycle Minutes Minutes + CICS NEWCOPY
State Continuous execution Pseudo-conversational (state in COMMAREA)
Concurrency Usually single-threaded Multi-user, shared resources
Error handling File status, RETURN-CODE RESP/RESP2, EIB

CICS NEWCOPY

After recompiling a CICS program, you must tell CICS to load the new version. This is done with the CEMT transaction:

CEMT SET PROGRAM(CUSTINQ) NEWCOPY

Alternatively, in CICS TS 5.x and later, PHASEIN can be used to gradually transition to the new copy without disrupting active tasks:

CEMT SET PROGRAM(CUSTINQ) PHASEIN

20.15 Systematic Debugging Methodology

Effective debugging is not random exploration -- it is a systematic process. The following methodology applies to all types of COBOL debugging, whether batch, CICS, or DB2.

Step 1: Reproduce

Before attempting to fix a bug, reproduce it. If you cannot reproduce the problem, you cannot verify that your fix works. For batch programs, this means running the program with the same input data that caused the failure. For CICS programs, this means entering the same sequence of inputs on the same screen.

Step 2: Isolate

Narrow down the location of the bug. Use binary search strategy: if the program has 5,000 lines, determine whether the bug is in the first half or the second half. Then narrow further. For batch programs, add DISPLAY statements at strategic points. For CICS programs, use CEDF or TSQ logging.

Step 3: Understand

Once you know where the bug is, understand why it occurs. Do not just look at the symptom -- understand the root cause. An S0C7 is a symptom; the root cause might be a missing validation three paragraphs earlier.

Step 4: Fix

Fix the root cause, not the symptom. If the root cause is missing input validation, add the validation. Do not simply initialize the variable to zero and hope for the best.

Step 5: Verify

Verify that the fix works by reproducing the original problem and confirming it no longer occurs. Also verify that the fix does not break anything else by running regression tests.

Step 6: Prevent

After fixing the bug, consider how to prevent similar bugs in the future. Should you add a coding standard? Should you add a validation routine? Should you create a unit test? The best debugging is the debugging you never have to do because you prevented the bug from being written in the first place.


Exercises

  1. DISPLAY Debugging: Take a program you have written in a previous chapter and add strategic DISPLAY statements to trace the processing of the first five records. Run the program and verify that the output matches your expectations.

  2. Compiler Listing Analysis: Compile one of your programs with the MAP and XREF options. Find the offset and length of every variable in WORKING-STORAGE. Identify any variables that are defined but never referenced.

  3. Abend Diagnosis: Given the following dump information, identify the failing COBOL statement and the likely cause: - Abend code: S0C7 - PSW address: X'0001A4B6' - Program entry point: X'0001A000' - OFFSET listing shows line 185 at offset X'4B0' (COMPUTE statement) - DATA MAP shows WS-QUANTITY at offset X'0048', PIC S9(5) COMP-3 - Dump shows offset X'0048' contains: X'C1C2C3'

  4. Test Driver: Write a test driver program for the file status checking routine from Chapter 16. The test driver should test at least five different scenarios (successful operation, end of file, record not found, duplicate key, and file not open).

  5. GnuCOBOL Debugging: Compile a program with -g and use GDB to set a breakpoint at a paragraph, step through five statements, and display the values of three variables at each step. Document the GDB commands you used and the output you observed.