The Useless Box in COBOL, C and Assembler on an IBM mainframe.

Frank van der Wal
Theropod
Published in
12 min readJul 8, 2024

🖥🖥🖥

Useless Box — Fun — but useless

Perhaps you know the funny concept of the Useless Box: A lidded box with one simple switch. If one activates the switch, the lid opens, a mechanism pops out to reset the switch and retracts itself in the box and the lid closes again.

It is a silly, simple and therefore amusing little setup. On YouTube there are many implementations of this concept.

Playing around with an IBM mainframe, or IBM Z, I was figuring out how the mechanism of one program calling another program, even written in a different language, works on an IBM mainframe.

To put the Useless Box together with the capacity of programs calling each other, I had the crazy, and completely useless idea, of combining the two.

Would I be able to make a COBOL application (the lidded box) that calls a C application (the activation of the switch) and have an assembler application (the pop-up mechanism) to reset the mechanism?

As this is written in software, I need to make an analogy with the physical Useless Box: Let’s say we start with a number in COBOL, 42 seems always the best number to pick, C is going to change that number to 34 and assembler puts it all back into line by changing it to 42 again.

Apart from being utterly useless, it might involve and explain some niceties in how IBM Z and z/OS handles the hand-over of data. This article describes my step-by-step journey and I couldn’t have done it without the patient help of my colleagues, Michael Cataldo, Flemming Petersen and Marc van der Meer.

Let’s break this silly project up into some smaller pieces.

First, let’s see if we call any C program from COBOL.

Secondly pass in parameters and see how we can change the values in both C and COBOL.

Thirdly, let’s have assembler correct the value again to the initial state.

Before we dig into the code, it might be handy to explain the working environment. The system used is the IBM Learning platform for IBM Z: zXplore. Microsoft Visual Studio is the IDE and an ssh terminal is used to invoke the C (C89) and COBOL (cob2) compilers as well as the assembler (as). The linking process is part of the cob2 instruction. The applications are written in a 31 bit mode and I’m statically linking the object files.

How to call C from COBOL, the easy way.

It is easy to have COBOL calling a simple C program. The COBOL code looks like:

      *****************************************************************
* Program name: USELESSBOX
*****************************************************************
IDENTIFICATION DIVISION.
PROGRAM-ID. UBCBL1.
AUTHOR. Frank van der Wal.
*****************************************************************
ENVIRONMENT DIVISION.
*****************************************************************
DATA DIVISION.
WORKING-STORAGE SECTION.

01 WS-MSG PIC X(45).
****************************************************************

PROCEDURE DIVISION.

MAIN-PARA.
MOVE "Hello from COBOL!" TO WS-HELLO.
DISPLAY WS-MSG.

CALL 'UBC1'

STOP RUN.

Apart from the usual COBOL DIVISIONS, you can see that we move our welcome line of text in the variable WS_MSG that we are going to DISPLAY.

To invoke the C program we need the CALL statement. Here we are CALL-ing UBC1, which is the C part:

void UBC1(){
printf("\nHello from C!!\n\n");
}

Notice 2 things in this C code:

Firstly, you won’t spot the expected #include <stdio.h> line. The reason is that on the IBM Z there is a mechanism called the Language Environment. It provides common services and language specific routines in a single runtime environment for C, C++, COBOL, PL/I, and assembler applications. It offers consistent and predictable results for language applications, independent of the language in which they are written. This nets out to that the printf() function in C (as part of the common services) is provided by the Language Environment. During the linkedit step this function will be resolved.

The other thing is that you won’t see a main() function. We are statically linking this C code into the COBOL program and this code could be seen as function or paragraph in the COBOL code.

The function UBC1() will be compiled into an object file with:

c89 -c UBC1.c

creating the object file UBC1.o.

NOTE:

1: The -c flag compiles the source file without linkedit and therefore creating an object file with the name of UBC.o

2: The function name (UBC1) should be the same as in the CALL statement in COBOL.

We can now invoke the COBOL compiler like so:

cob2 -o UB1 UBCBL1.cbl UBC1.o

where:

· cob2 is the command line COBOL compiler in Unix for z/OS

· -o UB1 specifies the output name we give i.e. the executable

· UBCBL1.cbl is the COBOL source file

· UBC1.o the object file created by the C89 C compiler is the previous step.

We can now run our program by:

./UB1

/z/z16889/sources/COBOL/uselessBox > ./UB1
Hello from COBOL!

Hello from C!!

Passing parameters from COBOL to C — Pressing the switch on the Useless Box

Let’s now pass in a parameter that C is going to change the value into 34. From COBOL we need first to declare the variable and pass it to the C program:

       DATA DIVISION.
WORKING-STORAGE SECTION.

01 WS-MSG PIC X(45).
01 v1 PIC S9(8) BINARY VALUE 42.

*****************************************************************

PROCEDURE DIVISION.

MAIN-PARA.
MOVE "The value in COBOL is:" TO WS-MSG.
DISPLAY WS-MSG.
DISPLAY v1

MOVE "--- Breaking out to C code ---" TO WS-MSG.
DISPLAY WS-MSG.

CALL 'UBC2' USING BY VALUE v1

STOP RUN.

The C-code needs to accept a parameter and that will be of type int. We can print out the value received, change it and print out the new value.


void UBC2(int val1){

printf("The input value in C is: \n");

printf("val1 : %d\n", val1);

val1 = 34; /* add 10 to val1 */

printf("\nThe value in C is now: \n");
printf("val1 is now: %d\n", val1); /* and print out the value */

}

Please note that the function name is changed and should be the same as in the CALL statement.

If we compile the C source:

c89 -c UBC2.c

and then compile and linkedit it with:

cob2 -o UB2 UBCBL2.cbl UBC2.o

we can run it:

The value in COBOL is:             
00000042
--- Breaking out to C code ---
The input value in C is:
val1 : 42

The value in C is now:
val1 is now: 34

While that is good we need COBOL to be aware of our change of the value. Let’s see if we print out the COBOL variable after we’ve CALLed our C program by adding:

           CALL 'UBC2'   USING BY VALUE       v1

MOVE "The value in COBOL after calling C is:" TO WS-MSG.
DISPLAY WS-MSG.
DISPLAY v1

The output:

The value in COBOL is:             
00000042
--- Breaking out to C code ---
The input value in C is:
val1 : 42

The value in C is now:
val1 is now: 34
The value in COBOL after calling C
00000042

What!?! Still 42? What happened?

Well, when it comes to passing data from COBOL to C (or any other languages invoked by the CALL statement) there are different ways of doing so.

In the COBOL code we explicitly passed v1 BY VALUE to C. This means that in the C function the parameter will be given a local copy of type int with the name of val1. In C we can change val1 to whatever an int can be, but when we return to COBOL val1 will be destroyed. For COBOL it doesn’t exists and therefore we see that in COBOL v1 is still 42.

We need to change this!

To solve this, we need to CALL the C program BY REFERENCE. This way we do not pass a value to C but the location in storage where COBOL have put v1. The change into COBOL is straightforward:

           CALL 'UBC2'   USING BY REFERENCE       v1

In the C code we need to change the input parameter’s type from int to a pointer to int:

void UBC2(int* val1){

printf("The input value in C is: \n");

printf("val1 : %d\n", *val1);

*val1 = 34; /* change val1 to 34 */

printf("\nThe value in C is now: \n");
printf("val1 is now: %d\n", *val1); /* and print out the value */

}

If we compile and run it now we see what we would like to have seen:

The value in COBOL is:             
00000042
--- Breaking out to C code ---
The input value in C is:
val1 : 42

The value in C is now:
val1 is now: 34
The value in COBOL after calling C
00000034

That’s better!

Passing parameters from COBOL to Assembler — Resetting the switch on the Useless Box

We now must go a step deeper but hang in there!

The design of z/OS is so that passing data from one application to another, like we are doing here, is done by means of general register 1 (R1) on processor level.

All general registers are 64 bits wide, and you might argue that passing in the number 42 fits more than adequate into R1. But this is not how it is done. In real life, CALLing other programs could involve passing in massive data structures. These are way too big to fit in 64 bits.

The way z/OS implemented it, is not to put the value in R1, but address of a storage location where the parameters are located.

Let’s call an assembler program from COBOL with two values and two methods.

The first value is 42 , passed in BY VALUE for it need no change and the assembler program knows what the original value was. It can reset the switch!

The second value is what C has changed, v1 in the COBOL program, and we need to pass it in BY REFERENCE for assembler is going to put it right.

The COBOL code will now look like this:


DATA DIVISION.
WORKING-STORAGE SECTION.

01 WS-MSG PIC X(45).
01 v1 PIC S9(8) BINARY VALUE 42.
01 BASE-VALUE PIC S9(8) BINARY VALUE 42.
01 V11 PIC ----9.

*****************************************************************

PROCEDURE DIVISION.

MAIN-PARA.
MOVE "The value in COBOL is:" TO WS-MSG.
DISPLAY WS-MSG.
DISPLAY v1

MOVE "--- Breaking out to C code ---" TO WS-MSG.
DISPLAY WS-MSG.

CALL 'UBC2' USING BY REFERENCE v1

MOVE "The value in COBOL after calling C is:" TO WS-MSG.
DISPLAY WS-MSG.
DISPLAY v1

MOVE " " TO WS-MSG.
DISPLAY WS-MSG.

MOVE "--- Breaking out to assembler ---" TO WS-MSG.
DISPLAY WS-MSG

CALL 'ASM4COB' USING BY VALUE BASE-VALUE
BY REFERENCE v1

MOVE "The value in COBOL after calling assembler is:"
TO WS-MSG.
DISPLAY WS-MSG.
MOVE v1 TO V11

DISPLAY V11

STOP RUN.

In the WORKING-STORAGE SECTION, BASE-VALUE and a V11 variable are added. The first to hold the value that is passed to assembler as the reference value, the v11 is there to display a number neatly formatted after it comes back from assembler.

In the PROCEDURE SECTION there are some cosmetic changes, and a call to the assembler program ASM4COB has been made. The assembler part is a bit more trickier than C, but it boils down to these couple of lines:

         LR    R2,R1    copy R1 into R2
L R3,0(R2) Load val stored in address pointed by R2 in R3
L R4,4(R2) Load 4 bytes from where R2 points to into R4
L R4,0(R4) Dereference R4 into R4 to get value in address
L R5,4(R2) Load the address of the ref val into R5
ST R3,0(R5) Store R3 (42) into ref val address

The first line has instruction Load Register and is copying the content of register 1 (R1) into register 2 (R2). The reason for doing so is more a best practice than technically needed. (R1 is a register that z/OS uses to pass info as we do here. It is best practice to store the content of R1 into another register in case we need to CALL another program)

(Note that Load and Move instructions takes two operands separated by a comma and the source of the Move or Load is the second operand, the destination, the first.)

Now R2 contains the address of where COBOL puts the parameters. For clarity reasons, let’s say the address of where COBOL stores its parameters starts at 0xFA ED E4 A0. We could depict this situation like so:

In the next instruction

L R3,0(R2)

we Load into R3 the content of the address where R2 is pointing to. To distinguish that we need the content of the address rather than the content of R2, we need to use the parentheses around R2. The ‘0’ before (R2) is indicating that there is no offset, we just take the first 4 bytes of the storage location R2 points to. After this instruction, R3 contains 42 or in hex notation: 0x2A.

This covers the BY VALUE passing of parameter BASE-VALUE. We now need to get assembler to find the value that we passed in BY REFERENCE.

We need two instructions to do that. The first one

L R4,4(R2)

is Loading into R4 the value of where R2 is pointing two, offset by 4 bytes. The offset is needed for we need to skip the first 4 bytes as this was holding the value of 42 (0x2A). The second 4 bytes contains the address of our BY REFERENCE parameter. Let’s say that the address location is at 0xFB E1 3F 08:

After the instruction R4 contains the address of the value we are after but not yet the value itself. We need to de-reference it so we can find the actual value of 37. (0x25 in hexadecimal notation).

We can do so with the fourth instruction:

L R4,0(R4)

This might look a bit strange that we can use R4 both as the destination as well as the source but this is perfectly normal. After this instruction R4 contains the value of 37 (0x25):

We now need to put the reference value of 42, into the storage location so COBOL can display it properly. Let me remind you that the storage location of our BY REFERENCE parameter is stored 4 bytes from where R2 is pointing at.

Best is to store that location into another register, let’s say R5 by means of the instruction:

L R5,4(R2)

As you might recall that we’ve put our reference value of 42 into R3. To complete our mission we would like to STore the value in R3 into the storage location that R5 points to. We can do so with this instruction

ST R3,0(R5)

This store instruction also takes two operands, this time from left to right, hence storing the content of R3 into where R5 points to with no offset:

Remember that address location 0xFAEDE4A0 is the location where COBOL stored the parameters and the second 4 bytes are still holding the address where once was 37 but now is 42 (0x2A). If we are now handing back the control to COBOL, it should find 42 when printing out v1.

In the assembler program I’ve added some print statements for the incoming parameters using the BPX1WRT system Services call and I print out the values in R3 and R4 before changing the value in 42 again.

If I assemble, compile and linkedit it all I see:

The value  in COBOL is:                      
00000042
--- Breaking out to C code ---
The input value in C is:
val1 : 42

The value in C is now:
val1 is now: 34
The value in COBOL after calling C is:
00000034

--- Breaking out to assembler ---
00000042
00000034
The value in COBOL after calling assembler is
42

We’ve completed the full circle. In COBOL we first had 42 and had the C program change it into 37. Then we called the assembler program with two parameters, one is the base-value and one is the C changed number.

Assembler put back 42 and all is in good order again.

As I stated earlier, a complete Useless piece of code, nevertheless it is trying to explain how IBM z/OS handles the hand-over if, in one application, calling different parts of code created in C and Assembler.

--

--