Compiling, Linking and Language Environment
|
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), which says nothing about compilers, linkers or run-time options, so everything here is implementation-defined; against IBM Enterprise COBOL for z/OS 6.5 (PTFs through September 2026) on z/OS 3.2, whose compile, bind and run steps and Language Environment (LE) run time are described; 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, which produced the same results). z/OS examples are labelled "z/OS; not run here" and are checked against the Programming Guide and the Language Environment manuals.
A COBOL source file is not a program. It becomes one in a chain of tools: the compiler turns the source into an object module, a binder (linkage editor) joins object modules and library routines into something the system can load, and the run time (LE on z/OS, libcob in GnuCOBOL) provides storage, files, messages and error handling while it runs. This page follows the chain on z/OS first, then reads the compiler listing, goes through the compiler options that matter most with their defaults, describes Language Environment, and ends with the GnuCOBOL counterpart of every step, which is the part that can be run on a laptop. The JCL around the steps is on JCL for COBOL Programmers; the platform itself is introduced on Mainframe Environment Primer; how cobc works and its everyday options are on Getting Started; and the dialect switches (-std=) are on Compilers and Dialects. Choosing options for speed is the subject of Performance and Compiler Options, and finding a failure with the listing and the dump is on Debugging and Troubleshooting.
Where the book is outdated: Taulli’s Modern Mainframe Development (2022) treats Enterprise COBOL 6.3 as the latest compiler and z/OS 2.4 as the platform. Enterprise COBOL 6.5 is current, ARCH(11) is now the default and the older levels are no longer accepted, and cob2 for compiling from the z/OS UNIX shell received further enhancements in a 2026 PTF. The compile options, the listing, the severity codes and the Language Environment options on this page come from the 6.5 and z/OS 3.2 manuals, not from the book.
The build chain on z/OS
Four steps turn the Circulation program LOANCNT (a main program that calls the subprogram FEECALC) into something that can run, and a fifth runs it.
Source: Enterprise COBOL for z/OS 6.5 Programming Guide: Compiling under z/OS; Programming Guide: Compile and link-edit procedure (IGYWCL)
-
Compile. The compiler program is
IGYCRCTL. It reads the source fromSYSIN,COPYmembers fromSYSLIB, writes the object module toSYSLINand the listing toSYSPRINT, and uses work data setsSYSUT1toSYSUT15andSYSMDECK. Several of its output data sets exist only when an option asks for them:SYSPUNCHwithDECK,SYSADATAwithADATA,SYSDEBUGwithTEST(SEPARATE)and the Db2DBRMLIBwithSQL. -
Coprocessors (precompile or translate). Older shops ran a separate Db2 precompiler or CICS translator before the compiler. Enterprise COBOL instead has the
SQLandCICSoptions, so that the compiler calls the Db2 coprocessor or the CICS translation while it compiles; see SQL and CICS: the integrated translation. -
Bind (link-edit). The binder,
IEWBLINKin the IBM procedures, joins the object module with other object modules and the Language Environment libraries into a program object, the form of an executable that lives in a PDSE or a z/OS UNIX file; the older name for the same step is link-edit, which is why the DD is still calledSYSLMODand the procedures end inL. -
Run. The system loads the program object and calls LE, which creates an enclave, initializes the run time and passes control to the main program.
The program object is the current form of the load module. A conventional load module sits in a PDS; a program object needs a PDSE (or a z/OS UNIX file), and the Programming Guide asks for the program object of the COBOL program to be linked into a PDSE in its Java interoperability tasks. The IBM procedure allocates SYSLMOD with DSNTYPE=LIBRARY, which makes a PDSE. This page does not rely on any detail of the binder beyond the Programming Guide; the binder’s own manual (MVS Program Management) defines program objects and the control statements precisely.
The IBM compile procedures
IBM supplies three cataloged procedures, in increasing scope:
| Procedure | Steps | What you supply |
|---|---|---|
|
|
|
|
|
The same, plus extra object modules through |
|
|
The same, plus the run-time DDs under |
In IGYWCL the LKED step runs only if the compile ended with a return code of 8 or less (COND=(8,LT,COBOL)), reads SYSLIB from the LE SCEELKEX and SCEELKED libraries, takes the object module from &&LOADSET and writes the result to SYSLMOD. The data set prefixes and some libraries are installation-dependent, so the values above are those of the IBM-supplied procedure, not necessarily those of a site. Calling the procedure and overriding its DDs is the same technique as for any procedure: the DD name is written as step.ddname.
This job compiles LOANCNT with listing options, binds it with the already-compiled FEECALC, and stores the program object in the Circulation load library (z/OS; not run here; the DD names and procedure parameters are checked against the Programming Guide, the binder control statements INCLUDE and NAME are standard binder syntax that this page did not check against the MVS Program Management manual):
//LOANCNTB JOB (ACCT),'CIRC BUILD',CLASS=A,MSGCLASS=X
//BUILD EXEC IGYWCL,PARM.COBOL='OPT(2),LIST,MAP,XREF,SSRANGE'
//COBOL.SYSIN DD DISP=SHR,DSN=USERID.CIRC.COBOL(LOANCNT)
//COBOL.SYSLIB DD DISP=SHR,DSN=USERID.CIRC.COPYLIB
//LKED.OBJLIB DD DISP=SHR,DSN=USERID.CIRC.OBJLIB
//LKED.SYSLMOD DD DISP=SHR,DSN=USERID.CIRC.LOADLIB(LOANCNT)
//LKED.SYSIN DD *
INCLUDE OBJLIB(FEECALC)
NAME LOANCNT(R)
/*
Running the result is a separate job step (z/OS; not run here). The STEPLIB makes the program object and the LE run-time libraries visible, CEEOPTS supplies LE options (see Runtime options), and CEEDUMP receives the dump that LE writes after an unhandled error:
//LOANCNTR JOB (ACCT),'CIRC RUN',CLASS=A,MSGCLASS=X
//RUN EXEC PGM=LOANCNT
//STEPLIB DD DISP=SHR,DSN=USERID.CIRC.LOADLIB
// DD DISP=SHR,DSN=CEE.SCEERUN
// DD DISP=SHR,DSN=CEE.SCEERUN2
//LOANTRAN DD DISP=SHR,DSN=USERID.CIRC.LOANTRAN
//SYSOUT DD SYSOUT=*
//CEEDUMP DD SYSOUT=*
//CEEOPTS DD *
RPTOPTS(ON),TERMTHDACT(DUMP)
/*
Source: Enterprise COBOL for z/OS 6.5 Programming Guide: Using a cataloged procedure; Programming Guide: Compile and link-edit procedure (IGYWCL); Programming Guide: Data sets used by the compiler under z/OS
A batch compilation can hold several programs in one SYSIN. The NAME compiler option decides how they are bound: the compiler writes a binder NAME statement, and every program from one NAME to the next goes into one program object, which takes the name of the last program of the group. The batch must end each program with END PROGRAM, or the next program is nested inside the previous one. Source: Programming Guide: Compiling multiple programs (batch compilation)
SQL and CICS: the integrated translation
A program with EXEC SQL or EXEC CICS statements needs the matching option, because the compiler would otherwise not process them.
-
SQL(defaultNOSQL) makes the compiler call the Db2 coprocessor, which checks the SQL, replaces it with calls and writes the database request module (DBRM) to theDBRMLIBDD (or, withcob2, to a file). WithNOSQL, any SQL statement found is diagnosed and discarded. A string in quotation marks or apostrophes afterSQLcarries Db2 suboptions, and a long string can be split across severalCBLstatements. The separate Db2 precompiler is not supported withLP(64), where theSQLoption is required for embedded SQL. The DBRM is later bound into a package; that step is on Embedded SQL and Db2. -
CICS(defaultNOCICS) makes the compiler process theEXEC CICSstatements. A program compiled withCICSdoes not run outside CICS. WithNOCICS, the statements are diagnosed and discarded. The transaction side is on CICS Online Programming.
Both options are used at compile time only, so they appear in the listing’s "options in effect" section and in the PARM of the compile step, for example PARM.COBOL='SQL("...")'. DYNAM and CICS conflicted until the September 2026 PTF PH73033, which allows the two to be used together; before it, the compiler gave a warning when both were given. The copy of the Programming Guide read for this page still lists the restriction that DYNAM must not be used when programs with EXEC SQL run under CICS or are called by a program that uses the Db2 call attach facility.
Static and dynamic linkage
How the binder is used depends on how programs call each other. With NODYNAM (the default), a CALL "FEECALC" with a literal is a static call: the object module of FEECALC is bound into the same program object as LOANCNT, as in the job above. With DYNAM, the call is dynamic: FEECALC is a separate program object found at run time through STEPLIB or the link list, and changing it needs no re-bind of the caller. A CALL of a data item always loads the target at run time. For RENT programs (the default) the Programming Guide recommends binding the program object with the RENT binder option (or REUS if non-COBOL serially reusable programs are included); if any program in the object is not reentrant, NOREUS is needed so that CANCEL gives a fresh copy. The language side of CALL, CANCEL and LINKAGE SECTION is on Subprograms: CALL and Linkage; the GnuCOBOL form of the same choice is shown below with cobc -m.
cob2 and the z/OS UNIX shell
cob2 compiles and links from a z/OS UNIX shell with one command, which is how COBOL fits into make, Git and CI on z/OS. It passes only files that end in .cbl to the compiler (everything else goes to the linker), always adds the RENT and TERM options, writes a requested listing to file.lst, and uses the shell command c89, the shell interface to the binder, for the link step. A few forms (z/OS; not run here, taken from the Programming Guide’s examples with the Circulation names):
cob2 -c loancnt.cbl
cob2 loancnt.cbl feecalc.cbl -o loancnt
cob2 -qlist,map,xref loancnt.cbl -o loancnt
cob2 -g -I /u/circ/copy loancnt.cbl -o loancnt
cob2 -qsql loancnt.cbl -o loancnt -dbrmlib=USERID.CIRC.DBRMLIB
The first compiles only (loancnt.o); the second compiles and links two sources into loancnt; the third passes compiler options with -q; the fourth asks for debugging information (-g is TEST with no suboptions) and a copybook directory; the last uses the Db2 coprocessor and writes the DBRM to an existing data set (without a name after -dbrmlib it goes to a .dbrm file). The options that matter most:
| Option | Effect |
|---|---|
|
Compile only; do not link. |
|
Name of the output; the default is |
|
Compiler options, separated by commas. |
|
Directory searched for copybooks that have no library name. After PTF PH72005 it can also name a PDS written as |
|
Write a make dependency file listing the copybooks; |
|
|
|
Highest compiler return code with which |
|
Show the generated compile and link commands ( |
|
Pass options to the binder as |
|
Entry point program of the module; the default is the first program on the command line. |
PTF PH72005 (July 2026) added, for cob2 and z/OS UNIX: automatic code page conversion of tagged z/OS UNIX input files, UTF-8 files included, when their code page differs from the CODEPAGE option of the compile; the PDS forms of -I; -MF and -MT for -M; paths for input and output files handled like xlc, gcc and clang (a relative path to the source is accepted); and the new TERM(SOURCELINE) option, which prefixes diagnostics sent to the terminal with the source file name and the relative line number. This is the same set of enhancements named on Compilers and Dialects and Mainframe Environment Primer. The OPTFILE option is ignored when compiling with cob2. Whether a given site has applied the PTF is a question for its system programmer.
Reading the compiler listing
The compiler writes its report to SYSPRINT (or to file.lst under cob2). What it contains depends on options, and a listing that was not requested cannot be recovered after the fact, so a production compile saves it:
| Option (default) | Section of the listing | Use it for |
|---|---|---|
|
The source with compiler-assigned line numbers (or yours with |
Every message and every other section refers to these numbers. |
|
Sorted cross-reference of programs, data names and procedure names with the lines that define and use them, plus a cross-reference of |
Finding where a field is changed; embedded flags show an undefined ( |
|
The |
Matching a dump address to a field; checking |
|
The assembler-language listing of the generated code, with the constant area, prolog areas, external symbol dictionary and storage maps |
Locating the instruction of an abend when you know the offset. |
|
A condensed |
Finding the failing statement from a CEEDUMP offset at lower cost than |
|
Alphabetical list of the statements used, with a count of each |
Impact analysis ("where do we use |
OFFSET and LIST are mutually exclusive, and if both are given, OFFSET wins. The listing statements EJECT, SKIP1 to SKIP3 and TITLE, and the *CONTROL (*CBL) LIST, MAP and SOURCE switches inside the program, narrow what is printed. A listing, in the order the sections appear, looks like this (a shape only; real listings have a product banner and many more lines):
[ banner: product, release, date ]
[ invocation parameters, CBL/PROCESS statements, options in effect ]
[ SOURCE: numbered source lines, with messages embedded at FLAG(x,y) level ]
[ MAP: data division map ]
[ XREF: programs, data names, procedure names, COPY members ]
[ diagnostics: messages of FLAG(x) level and above, ordered by line number ]
End of compilation 1, program LOANCNT, highest severity 0.
Under a source line the compiler prints diagnostics in the form nnnnnn IGYppxxxx-l message-text, where nnnnnn is the source line number, IGY marks a compiler message, pp two characters naming the compiler phase (relevant only when reporting a suspected compiler error to IBM), xxxx the message number and l the severity letter. Messages found while processing options, CBL and PROCESS statements and COPY or REPLACE statements appear near the top; messages about the program are ordered by line number near the end; and a summary closes the listing. The severity letter decides the return code of the compile step:
| Severity | Return code | Meaning |
|---|---|---|
Informational ( |
0 |
No action; the program runs correctly. |
Warning ( |
4 |
Possible error; the program probably runs correctly as written. |
Error ( |
8 |
A definite error that the compiler tried to correct; results may differ from what you expect. |
Severe ( |
12 |
The compiler could not correct the error; the program does not run correctly and object code might not exist. |
Unrecoverable ( |
16 |
The compilation was ended. |
The final return code is generally the highest one met. FLAG(x,y) chooses which messages are printed (default FLAG(I,I): all, embedded in the source listing too), NOCOMPILE(x) stops generating code once a message of severity x or higher occurs (the printed default is NOCOMPILE(S), so a severe error stops code generation), and a message exit (EXIT(MSGEXIT)) lets a site change a message’s severity or suppress it, which changes the return code as well. In cob2, -comprc_ok plays the role of the COND test of IGYWCL: with the default of 4, a warning does not stop the link, an error does. Compiling a program named ERRMSG prints the complete list of messages with their numbers, severities and texts. The runtime messages, with the prefix IGZ and a severity letter, are different: they come from the library at execution time and are shown in the job output, not in the compiler listing. The meanings of both are in the Messages and Codes manual.
Source: Enterprise COBOL for z/OS 6.5 Programming Guide: Format of compiler diagnostic messages; Programming Guide: Severity codes for compiler diagnostic messages; Programming Guide: LIST; Programming Guide: MAP; Programming Guide: XREF; Programming Guide: OFFSET; Enterprise COBOL for z/OS 6.5 Messages and Codes (PDF)
Compiler options by group, with 6.5 defaults
Options are set at several levels, and the higher level wins. For a compile with JCL the order from highest to lowest is: installation defaults that the site has fixed; the values of BUFSIZE, OUTDD, SQL and SQLIMS for the first program of a batch; options on CBL or PROCESS statements before the IDENTIFICATION DIVISION; options on the invocation (the PARM of the step, or cob2 -q); and installation defaults that are not fixed. Two conflicting options at the same level resolve to the last one. A mutually exclusive pair is resolved by a table in the Programming Guide, of which the rows most often met are these: OFFSET forces NOLIST; NUMPROC(PFD) is not allowed together with NUMCHECK(ZON(LAX)); CICS forces LP(32) and RENT; LP(64) forces NOCICS, RENT, NOSMARTBIN and NOTHREAD; TEST forces OBJECT and NODECK. The installation can change every default below, so the reliable way to know the defaults of a site is to compile a program with no options and read "options in effect" in the listing.
The defaults below were taken from the Enterprise COBOL for z/OS 6.5 Programming Guide, page "COBOL compiler options" and the page of each option.
| Group | Option | 6.5 default | What it decides |
|---|---|---|---|
Machine |
|
|
The oldest processor model the generated code must run on: 11 is z13, 12 z14, 13 z15, 14 z16 and 15 z17. A higher level uses newer instructions and may abend on an older machine, disaster-recovery machines included; IBM recommends setting it in the installation defaults, not in JCL. 6.5 accepts 11 to 15 and no longer accepts 10. |
|
The same as |
The machine the code is scheduled for, within the instructions |
|
Speed |
|
|
0 gives the shortest compile and best debugging; 1 and 2 optimize progressively. IBM recommends |
Debugging |
|
|
Debug information in DWARF format, for z/OS Debugger and for CEEDUMP (variables and statement numbers). |
Run-time checks |
|
|
Checks subscripts, indexes and reference modification at run time; |
|
|
Class tests on the sending fields of |
|
|
|
Compile-time warnings for data used before it is initialized; |
|
Numeric |
|
|
How binary receiving fields are truncated: |
|
|
Accept any valid sign configuration ( |
|
Addressing and linkage |
|
|
31-bit (32) or 64-bit (64) code. |
|
|
Reentrant code, needed above the 16 MB line and for CICS and z/OS UNIX. |
|
|
|
Whether |
|
|
|
Adds binary metadata that IBM’s Automatic Binary Optimizer can use later (introduced in 6.4; a |
|
Coprocessors |
|
|
Db2 coprocessor; DBRM to |
|
|
CICS translation by the compiler. |
|
Listing |
|
|
See the previous section. |
Source: Enterprise COBOL for z/OS 6.5 Programming Guide: COBOL compiler options; ARCH; TUNE; OPTIMIZE; TEST; SSRANGE; NUMCHECK; INITCHECK; TRUNC; NUMPROC; LP; SMARTBIN
Options can also be written in the source with a CBL (or PROCESS) statement before the IDENTIFICATION DIVISION, which is how a program carries the options it must be compiled with; the compiler directives that sit inside the program are on Compiler Directives and Conditional Compilation. An example is CBL OPT(2),SSRANGE,XREF in columns 8 to 72.
Language Environment
Language Environment is the common run time under which Enterprise COBOL, PL/I, C/C++ and assembler routines run on z/OS. The compiler generates calls to it for storage, file and I/O services, DISPLAY, condition handling and the termination of the program, which is why a COBOL program needs the LE libraries (CEE.SCEERUN and SCEERUN2 in the procedures above) when it runs.
Enclaves
LE organizes a running application in three levels. A process is the outermost: the resources of the application, the LE library among them; z/OS Language Environment supports one process per application. An enclave is the unit a program runs in: one or more load modules containing separately compiled and bound routines, the first of which is the main routine and the rest subroutines. The enclave defines the scope of the language semantics: for COBOL it is the run unit, so that STOP RUN ends the whole enclave, EXTERNAL data lasts as long as the enclave, and the first routine called is the main program. A thread is the path of execution inside an enclave.
When an enclave ends, LE computes the enclave return code: the user return code (what the program put in RETURN-CODE) plus an enclave reason code, and places it in register 15, with the reason code in register 0. For a main program that ends normally this is what the job step reports as its condition code, the value COND= tests in JCL.
Runtime options
Runtime options are the settings of the LE run time. Some are COBOL-only (for example CBLOPTS, CHECK, DEBUG, AIXBLD, RTEREUS, UPSI); most are shared. Those most often needed, with their defaults for batch (non-CICS) applications, from the z/OS 3.2 Programming Reference, are:
| Option | Default (non-CICS) | What it controls |
|---|---|---|
|
|
The user heap: initial size, increment, location, whether increments are kept or freed, and the two sizes below the 16 MB line. COBOL |
|
|
The thread’s stack storage: initial and increment size for the upward-growing stack (where COBOL |
|
|
What LE produces when a condition of severity 2 or higher is not handled: |
|
|
With |
|
|
With |
|
|
The DD name of the file for run-time messages, here |
|
|
Whether LE handles abends and program interrupts as conditions ( |
|
|
What an enclave ending with an unhandled severity 2 or higher condition does: end with an abend. |
|
|
A single abend code to pass through without LE condition handling. |
Option values keep their default unless something changes them, and the sources are ordered. From the Programming Guide, from highest to lowest precedence: storage options set by the storage tuning user exit; options set by the assembler user exit CEEBXITA; options on invocation (the PARM of the step, written for COBOL as the program’s arguments, a slash, then the runtime options; under z/OS UNIX the _CEE_RUNOPTS environment variable); options in a CEEOPTS DD; options in a CEEUOPT module linked into the program; and the installation defaults. CEEOPTS is ignored under CICS and for programs started with an exec function. The step PARM for a run with a runtime option and no program argument is therefore PARM='/RPTOPTS(ON)', and the CEEOPTS DD in the job above is the easier form for several options. Option strings are comma-separated, and a skipped suboption keeps its comma (STACK(,,ANYWHERE,FREE) is valid).
Callable services
LE’s services are called with CALL and have names that begin with CEE. A call passes its arguments by reference, and many end with a 12-byte feedback code (a condition token) that is all zeros when the service worked. The ones a COBOL developer meets most often:
| Service | Purpose |
|---|---|
|
Write a formatted LE dump on demand, with a title (80 bytes), an options string and a feedback code. The options select the sections: |
|
End the enclave with an abend, given the abend code (a fullword integer up to 4095) and a clean-up value: 0 abends at once and requests a system dump; 1 processes the abend as a normal one with termination processing; 2 to 5 combine termination processing with choices about the CEEDUMP and the system dump. There is no return and no feedback code. |
|
Get and set the user enclave return code. |
|
Get, format and write the message for a condition token to the message file. |
|
Convert dates to and from Lilian form; they are the LE side of the date functions on Dates and Times. |
A request for a dump of the file and traceback state at a point of interest, here before the end of the nightly update, can be written as follows (z/OS; not run here; the call and its three parameters follow the Programming Guide and the Programming Reference):
WORKING-STORAGE SECTION.
01 WS-DUMP-TITLE PIC X(80) VALUE "LOANCNT BEFORE CLOSE".
01 WS-DUMP-OPTIONS PIC X(255) VALUE "TRACEBACK FILES".
01 WS-FEEDBACK PIC X(12).
PROCEDURE DIVISION.
CALL "CEE3DMP" USING WS-DUMP-TITLE WS-DUMP-OPTIONS
WS-FEEDBACK
The dump goes to the CEEDUMP DD, so the job needs //CEEDUMP DD SYSOUT=*. An unhandled error produces the same kind of dump through TERMTHDACT(DUMP), with the title "Condition processing resulted in the unhandled condition" and trace options of TRACE COND THR(ALL). Compiling with TEST (or NOTEST(DWARF), which gives a smaller amount of information) makes the dump show the failing statement number and the values of variables; without it the dump shows only offsets, which is why OFFSET or LIST output of the same build is kept. A dump of a program that is staged by LLA/VLF needs the IGZLOAD or STEPLIB DD to point at the library; otherwise the variables and statement numbers are missing. How to read the dump is on Debugging and Troubleshooting, and the language side of handling errors is on Error Handling and Declaratives.
The GnuCOBOL counterpart
GnuCOBOL has the same chain with different names. cobc runs the preprocessor, the parser and the translator to C, then the C compiler, then the linker; the "binder" is the system linker; the program object is an executable (-x) or a shared module (-m); and the run time is libcob, configured by runtime.cfg and by COB_* environment variables. The general picture and cobc -v are in the figure and tables on Getting Started. This section runs each stage on the Circulation programs and shows the output. The subprogram FEECALC prices a late return (0.35 per day, capped at 5.00, as in the fines of Arithmetic and COMPUTE), and the main program LOANCNT counts the day’s transactions and calls it.
* FEECALC - overdue fine for one loan (called by LOANCNT)
IDENTIFICATION DIVISION.
PROGRAM-ID. FEECALC.
DATA DIVISION.
LINKAGE SECTION.
01 LS-DAYS-LATE PIC 9(3).
01 LS-FEE PIC 9(3)V99.
PROCEDURE DIVISION USING LS-DAYS-LATE LS-FEE.
MAIN-PARA.
COMPUTE LS-FEE ROUNDED = LS-DAYS-LATE * 0.35
IF LS-FEE > 5.00
MOVE 5.00 TO LS-FEE
END-IF
GOBACK.
* LOANCNT - count the day's loan transactions, price a late return
IDENTIFICATION DIVISION.
PROGRAM-ID. LOANCNT.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT LOAN-FILE ASSIGN TO "LOANTRAN"
ORGANIZATION IS LINE SEQUENTIAL
FILE STATUS IS WS-STATUS.
DATA DIVISION.
FILE SECTION.
FD LOAN-FILE.
COPY LOANREC.
WORKING-STORAGE SECTION.
01 WS-STATUS PIC XX.
01 WS-OUT PIC 9(3) VALUE 0.
01 WS-BACK PIC 9(3) VALUE 0.
01 WS-DAYS PIC 9(3) VALUE 20.
01 WS-FEE PIC 9(3)V99.
01 WS-FEE-EDIT PIC ZZ9.99.
PROCEDURE DIVISION.
MAIN-PARA.
OPEN INPUT LOAN-FILE
IF WS-STATUS NOT = "00"
DISPLAY "LOANTRAN not opened, status " WS-STATUS
STOP RUN
END-IF
PERFORM UNTIL WS-STATUS NOT = "00"
READ LOAN-FILE
AT END CONTINUE
NOT AT END
IF LOAN-CHECKOUT
ADD 1 TO WS-OUT
END-IF
IF LOAN-RETURN
ADD 1 TO WS-BACK
END-IF
END-READ
END-PERFORM
CLOSE LOAN-FILE
CALL "FEECALC" USING WS-DAYS WS-FEE
MOVE WS-FEE TO WS-FEE-EDIT
DISPLAY "Checkouts " WS-OUT " returns " WS-BACK
DISPLAY "Fine for " WS-DAYS " days: " WS-FEE-EDIT
STOP RUN.
The input LOANTRAN is the Circulation transaction file (four lines of the data set of the other pages: three checkouts and a return) placed in a subdirectory data, so that the effect of the file path is visible. Source: GnuCOBOL 3.2 manual
The stages: -E, -C, -c and -x
-E stops after the preprocessor and writes the source with COPY and REPLACE already expanded. The LOANREC copybook arrives with a #line marker that names the file it came from (the copybook text is in LOANREC.cpy next to the source, found with -I .):
$ cobc -E -I . -o loancnt.i loancnt.cob
$ sed -n 12,24p loancnt.i
FILE SECTION.
FD LOAN-FILE.
#line 1 "LOANREC.cpy"
01 LOAN-RECORD.
05 LOAN-TYPE PIC X.
88 LOAN-CHECKOUT VALUE "O".
88 LOAN-RETURN VALUE "R".
05 LOAN-MEMBER-ID PIC 9(6).
05 LOAN-BOOK-ID PIC 9(6).
05 LOAN-DATE PIC 9(8).
05 FILLER PIC X(9).
-C stops after translation to C. For each source it leaves the .c file and two headers, which hold the working storage and the literals; the call to a subprogram shows how a CALL literal is resolved at run time (cob_resolve_cobol):
$ cobc -C -Wall -I . loancnt.cob feecalc.cob
$ ls -1 *.c *.h
feecalc.c
feecalc.c.h
feecalc.c.l.h
loancnt.c
loancnt.c.h
loancnt.c.l.h
$ wc -l loancnt.c
370 loancnt.c
$ grep -n 'LOANCNT (void)\|"FEECALC"' loancnt.c
24:COB_EXT_EXPORT int LOANCNT (void);
190: call_FEECALC.funcvoid = cob_resolve_cobol ("FEECALC", 0, 1);
-c stops after the C compiler and leaves an object file for each source, which is the equivalent of the compile step alone (IGYWC):
$ cobc -c -Wall -I . loancnt.cob feecalc.cob
$ ls -1 *.o
feecalc.o
loancnt.o
Linking the two objects into an executable is the bind step. Whether a program is a main program is decided at compile time, not at link time: cobc generates the C main function only for a source compiled with -x. Linking objects compiled without it fails, which is a common surprise:
$ cobc -x -Wall -o loancnt loancnt.o feecalc.o
(.text+0x1c): undefined reference to `main'
/usr/bin/ld: (.text+0x20): undefined reference to `main'
collect2: error: ld returned 1 exit status
Compiling the main source with -x -c and linking again works (no warning in any of the commands; -Wall was given to each):
$ cobc -x -c -Wall -I . loancnt.cob
$ cobc -x -Wall -o loancnt loancnt.o feecalc.o
$ COB_FILE_PATH=data ./loancnt
Checkouts 003 returns 001
Fine for 020 days: 5.00
The one-command form compiles and links both sources into one executable, the static-call equivalent of the NODYNAM job above:
$ cobc -x -Wall -I . -o loancnt loancnt.cob feecalc.cob
$ COB_FILE_PATH=data ./loancnt
Checkouts 003 returns 001
Fine for 020 days: 5.00
(The COB_FILE_PATH variable is explained below. The first run without it fails because LOANTRAN is not in the current directory.) Compiled with -std=ibm as well, cobc -x -Wall -std=ibm -I . -o loancnt loancnt.cob feecalc.cob printed no warning and gave the same output. Source: GnuCOBOL 3.2 manual: Compile; GnuCOBOL 3.2 manual: Compiler options
Dynamic linkage with -m and COB_LIBRARY_PATH
-m builds a loadable module instead of an executable, which is the counterpart of DYNAM: the main program is built without FEECALC, and libcob loads the module when the CALL runs. The name of the module file is the program name, so it is built as FEECALC.so into a directory mods here. Without a search path the run fails; with COB_LIBRARY_PATH pointing at the directory it works (the current directory and the installation’s extras directory are always searched in addition):
$ mkdir mods
$ cobc -m -Wall -o mods/FEECALC.so feecalc.cob
$ cobc -x -Wall -I . loancnt.cob
$ COB_FILE_PATH=data ./loancnt
libcob: error: module 'FEECALC' not found
$ echo $?
1
$ COB_FILE_PATH=data COB_LIBRARY_PATH=mods ./loancnt
Checkouts 003 returns 001
Fine for 020 days: 5.00
On z/OS the corresponding cure is the library concatenation (STEPLIB or the link list) that holds the dynamically called program, here the search path. The call forms and cobcrun are on Subprograms: CALL and Linkage.
Listings, -save-temps and -v
GnuCOBOL’s compiler listing is much smaller than the IBM one: -t file writes the source, with the text of every COPY member marked with a C after its line number, and ends with the count of warnings and errors. It has no data map or cross-reference. The 0 warnings line is the equivalent of the final severity line of an IBM listing:
$ cobc -x -Wall -I . -t loancnt.lst loancnt.cob
$ sed -n 1,6p loancnt.lst
GnuCOBOL 3.2.0 loancnt.cob Tue Oct 20 2026 17:23:17 Page 0001
LINE PG/LN A...B............................................................
000001 * LOANCNT - count the day's loan transactions, price a late return
000002 IDENTIFICATION DIVISION.
$ tail -2 loancnt.lst
0 warnings in compilation group
0 errors in compilation group
cobc normally deletes its intermediate files. -save-temps keeps them: the expanded source (.i), the C file and its headers, and the object file. -save-temps=dir puts them in an existing directory (it prints a warning and falls back to the current directory if the directory does not exist):
$ mkdir tmp
$ cobc -x -save-temps=tmp -Wall -I . loancnt.cob
$ ls -1 tmp
loancnt.c
loancnt.c.h
loancnt.c.l.h
loancnt.i
loancnt.o
$ ls -1 loancnt
loancnt
cobc -v prints each step with its return status, which is the listing of the build; -v on the same program shows (the temporary names differ on every run):
$ cobc -v -x -Wall -I . -o v loancnt.cob
loading standard configuration file 'default.conf'
command line: cobc -v -x -Wall -I . -o v loancnt.cob
preprocessing: loancnt.cob -> /tmp/cob124_0.cob
return status: 0
parsing: /tmp/cob124_0.cob (loancnt.cob)
return status: 0
translating: /tmp/cob124_0.cob -> /tmp/cob124_0.c (loancnt.cob)
executing: gcc -c -pipe -I/usr/local/include -Wno-unused -fsigned-char
-Wno-pointer-sign -I"." -o "/tmp/cob124_0.o" "/tmp/cob124_0.c"
return status: 0
executing: gcc -Wl,--export-dynamic -o "v" "/tmp/cob124_0.o"
-L/usr/local/lib -lcob
return status: 0
cobc (GnuCOBOL) 3.2.0
Built Oct 20 2026 16:19:46 Packaged Jul 28 2023 17:02:56 UTC
C version "14.2.0"
The C compiler and its flags come from the build of GnuCOBOL (cobc -i shows them), so a diagnostic from the C step is shown as the C compiler prints it. The preprocess, parse and translate steps correspond to the compiler’s own passes on z/OS, and -v is the closest thing to the compile listing’s "options in effect": the line loading standard configuration file names the dialect configuration.
Source: GnuCOBOL 3.2 manual: Compile
Run-time configuration: runtime.cfg and COB_* variables
The libcob run time takes its settings from three places. First the built-in defaults; then a configuration file, runtime.cfg in the installation’s config directory or the file named by COB_RUNTIME_CONFIG; then environment variables, which are read last and therefore win. Every setting has both names, a variable such as COB_FILE_PATH and a parameter in the configuration file such as file_path; the file also accepts setenv, include and ${VAR:default} substitution. The file below sets the data directory and the module directory, so the program runs without any variable:
$ cat circ.cfg
# Circulation run-time settings
file_path: data
library_path: mods
$ ./loancnt
LOANTRAN not opened, status 35
$ COB_RUNTIME_CONFIG=circ.cfg ./loancnt
Checkouts 003 returns 001
Fine for 020 days: 5.00
$ COB_FILE_PATH=. COB_RUNTIME_CONFIG=circ.cfg ./loancnt
LOANTRAN not opened, status 35
Status 35 is "file not found" (see File Status Codes); the last line shows the precedence: the environment variable COB_FILE_PATH=. overrides the file_path of the file, so the data file is no longer found. COB_FILE_PATH gives names like LOANTRAN a common location, which is the role that the DD statements of a job have on z/OS: the file behind the name is chosen outside the program. The nearest equivalent of one DD statement is a variable with the prefix DD_ and the file name, which maps that single name to any path:
$ DD_LOANTRAN=data/LOANTRAN ./loancnt
Checkouts 003 returns 001
Fine for 020 days: 5.00
The variables used on this page, from the `runtime.cfg` comments of the 3.2 installation, are:
| Variable | Parameter | Effect (default) |
|---|---|---|
|
|
Directory prefixed to file names in |
|
|
Directories searched for loadable modules; |
|
|
Switch on the COBOL trace (false). Only a program compiled with |
|
|
Where the trace goes (stderr); a name that starts with |
|
|
Layout of each trace line ( |
|
The configuration file to read in addition to |
|
|
|
Memory and chunk sizes of the built-in sort (see Sort and Merge). |
The trace shows the equivalent of a statement-level traceback. The program is compiled with -ftraceall (without it, with -ftrace, only the entry and the paragraphs are traced), and the variables switch it on and send it to a file; the same binary with no variables prints nothing extra:
$ cobc -x -Wall -ftraceall -I . -o loantr loancnt.cob
$ COB_RUNTIME_CONFIG=circ.cfg COB_SET_TRACE=1 COB_TRACE_FILE=trace.log ./loantr
Checkouts 003 returns 001
Fine for 020 days: 5.00
$ wc -l trace.log
37 trace.log
$ head -14 trace.log
Source: 'loancnt.cob'
Program-Id: LOANCNT
Program-Id: LOANCNT Entry: LOANCNT Line: 22
Program-Id: LOANCNT Paragraph: MAIN-PARA Line: 22
Program-Id: LOANCNT OPEN Line: 23
Program-Id: LOANCNT IF Line: 24
Program-Id: LOANCNT PERFORM Line: 28
Program-Id: LOANCNT UNTIL Line: 28
Program-Id: LOANCNT READ Line: 29
Program-Id: LOANCNT IF Line: 32
Program-Id: LOANCNT ADD Line: 33
Program-Id: LOANCNT IF Line: 35
Program-Id: LOANCNT UNTIL Line: 28
Program-Id: LOANCNT READ Line: 29
$ COB_RUNTIME_CONFIG=circ.cfg COB_SET_TRACE=1 COB_TRACE_FORMAT="%I %L %S" COB_TRACE_FILE=trace2.log ./loantr
$ sed -n 3,8p trace2.log
LOANCNT 22 Entry: LOANCNT
LOANCNT 22 Paragraph: MAIN-PARA
LOANCNT 23 OPEN
LOANCNT 24 IF
LOANCNT 28 PERFORM
LOANCNT 28 UNTIL
The trace is the closest GnuCOBOL has to the statement numbers in a CEEDUMP traceback. The compiled-in checks (-d, which also switches the trace code on, and the -fec= options) and the debugger are on Debugging and Troubleshooting.
Side by side
| Concern | Enterprise COBOL on z/OS | GnuCOBOL 3.2 |
|---|---|---|
Compile only |
|
|
Expanded source ( |
|
|
Intermediate form |
assembler listing with |
|
Make a main program |
first program of the program object, or |
|
Static call |
|
several sources on one |
Dynamic call |
|
|
Listing and cross-reference |
|
|
Which file a name means |
|
|
Run-time options |
|
|
Failure report |
|
|
Common mistakes
-
Linking without marking the main program. On GnuCOBOL,
cobc -xis needed when the main source is compiled, not only when linking, or the link ends withundefined reference to 'main'. -
Forgetting
COB_LIBRARY_PATHorSTEPLIB. A dynamically called program that is not in the search path stops the run (module 'FEECALC' not found; on z/OS an abend S806), even though the build succeeded. -
Reading a
RC=4compile as clean. A warning gives return code 4, and the default-comprc_ok=4andCOND=(8,LT,COBOL)allow the bind to continue; read the messages and treat eachWas a decision. -
Compiling without a saved listing. A production abend is read from the listing of the exact build. Without
MAP,XREFandOFFSET(orLIST) from that compile, the dump offsets cannot be tied to statements. -
Setting a high
ARCHfor a mixed estate. Code built withARCH(15)can abend on a z16 or on the disaster-recovery machine. Use the lowest architecture in the estate and set it in the installation defaults. -
Assuming optimization is free of risk.
OPT(1)andOPT(2)assume valid data; invalid zoned or packed data that "worked" atOPT(0)can behave differently, so useNUMCHECKwhile testing. -
Using
NUMPROC(PFD)orTRUNC(OPT)as a tuning knob. Both are correct only when the data conforms; otherwise results are unpredictable. -
Leaving
NOSSRANGEin test builds. A subscript outside the table stores into the next field and nothing reports it. The compiled-in checks cannot be turned off byCHECK(OFF)at run time. -
Relying on a runtime option without checking the source.
RPTOPTS(ON)shows which source (PARM,CEEOPTS, installation) gave each value; do not guess. -
Mixing AMODE 64 and AMODE 31 programs.
LP(64)programs cannot be combined with AMODE 31 programs in one application, and cannot run in CICS or IMS. -
Copying a
COB_*setting into a z/OS job. Environment variables on GnuCOBOL have no meaning in a batch job; the data set behind a name is bound by theDDstatement.
References
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Compiling under z/OS
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Using a cataloged procedure
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Compile and link-edit procedure (IGYWCL)
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Data sets used by the compiler under z/OS
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Compiling multiple programs (batch compilation)
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Specifying compiler options under z/OS
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Compiling and linking with the cob2 command
-
Enterprise COBOL for z/OS 6.5 Programming Guide: cob2 syntax and options
-
Enterprise COBOL for z/OS 6.5 Programming Guide: COBOL compiler options
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Format of compiler diagnostic messages
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Severity codes for compiler diagnostic messages
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Requesting dumps
-
Tom Taulli, Modern Mainframe Development, O’Reilly Media, 2022 (chapters 2, 3 and 7, consulted for the topics only)