Using an external function

My “simple” problem was to have one application, using functions from another application, and allow me to upgrade both, and continue working. I want to write some code for openssl to call my program. I’m changing my code frequently, and I do not want to have to recompile and rebind openssl every time I change my program. (The recompile takes about 3 hours on my baby machine).

It took me a couple of days to get working; I learned a lot.

The simple solution which does not really work

I have a suite of programs called mycode, and I want to use stuff from another package which I’ll call otherstuff.

I can simply bind mycode, and otherstuff together to resolve all of the functions entry points. It works, but if I upgrade mycode, or otherstuff, I will not pick up the latest versions, and the code which is in the final load module may be incompatible with the newer versions.

Use fetch!

In mycode I can use the C function fetch, to load a load module, and give me the entry point. I then call the functions using the entry point.

This works – after a fashion. The key word is load module. A load module exists in a PDS or a PDSE. A Program Object can be in Unix or in a PDS, so the fetch solution does not work with file files in Unix.

The code worked if the module was in a load module – which I didn’t want.

I could not use fetch to load a program object from Unix.

Putting the code in a load module

I used the binder to create a load module.

xlc  dummy.o-o "//'COLIN.LOAD(dummy)'"  ...

To get my program to run I had to use

EXPORT STEPLIB=COLIN.LOAD
./myprog

The fetch worked, and my program was called.

Use Dynamic Link Library

The Unix eqivilent to a load module is the DLL.

When you create a DLL, two parts are created.

  • The .x file which is used for C to find how to call a function (stub code)
  • The .so file which contains the executable code.

The .x file

The .x file contains data like

IMPORT DATA,'cpfopen','ascii_tab'
IMPORT CODE,'cpfopen','cpfopen1'
IMPORT CODE,'cpfopen','printHex'

Which says

  • there is an entry point cpfopen1 in program object cpfopen
  • there is an external data object called ascii_tab in program object cpfopen
  • there is an entry point printHex in program object cpfopen

This .x file contains information for C to create stub code to load the actual code.

My calling program has

#pragma linkage(cpfopen1 ,OS)
...
int myopen ;
myopen = cpfopen1("ABC","DEF");
...

This needs to be complied with option

-Wc,DLL  

If you do not use the DLL option it ignores the IMPORT statement, and reports cpfopen1 and printHex are not found.

I bound it using

xlc  -o fopen  myfile.o cpfopen1.x  V 

The executed code.

You need to compile your code and specify which function names you want to make visible to other programs(export).

You can use

  • the compiler option EXPORTALL or
  • #pragma export(function1) #pragma export(function2) within your code.

At bind time

l1="-Wl,DLL  " 
xlc cpfopen1.o -o cpfopen1 -V $l1 1>a

This creates

  • the executable with the name in the -o parameter (cpfopen1)
  • and cpfopen1.x using the name of the object in the -o parameter

With

l1="-Wl,DLL  " 
xlc cpfopen1.o -o cpfopen1.so -V $l1 1>a

the file cpfopen1.x has

IMPORT CODE,'cpfopen1.so','cpfopen1'

Doing it this way, and letting the C and the binder resolve the entry point is the easy way of doing it.

Advanced DLL

You can load a DLL yourself

void * hDLL; 
int (*fptr)(const char * filename, const char *mode);

hDLL = dlopen("cpfopen1", RTLD_LOCAL | RTLD_LAZY );
if (!hDLL) {
fprintf(stderr, "%s\n", dlerror());
exit(99);
}
dlerror(); /* Clear any existing error */

The find the entry point using dlsym. This uses the handle of the DLL object loaded above, and you pass the entry point name. (It might not exist).

Define a pointer to the function

int   (*fptr)(const char * filename, const char *mode); 

This defines a pointer to a function fptr, which returns an int, and has two char * parameters.

fptr = (int (*)(const char *, const char *)) dlsym(hDLL, "cpfopen1"); 

if ( fptr != 0)
{
myopen = (*fptr)("ABC","DEF");
printf("result %i\n",myopen);
}

It took me some time to get the definitions of the function parameters correct.

The called function is

#pragma linkage(cpfopen1, fetchable)
int cpfopen1(char * filename, char *mode)
{
printf(">>>>>>>>>>>>>>>In cpfopen1 %s %s\n",filename,mode);
return 7 ;
}

It was much easier to include the .x file at bind time, and let C run time sort it out.

Clean up

You should use dlclose(handle) to close the object and free its resources

Binding in Unix on z/OS. A whole new world

I had been happy compiling and binding small programs in Unix on z/OS. It got a bit more complex as my programs got bigger and needed more files at bind time.

I stumbled into an area which I knew nothing about, (the pirate world of ar!) and which may help with doing binds.

My simple compile and bind script

name=wr 
rm ${name}.o
export _C89_CCMODE=1
p1="-Wc,arch(8),target(zOSV2R4),list,source,ilp32,gonum,asm,float(ieee) "

p8="-Wc,LIST(c.lst),SOURCE,NOWARN64,XREF,SHOWINC"

xlc $p1 $p8 -c $name.c
xlc $name.o -o wr -V 1>a

This script

  • compiles a program (wr.c) putting the compile listing into c.lst
  • binds the .o file and outputs (-o) into a file wr, and puts the output listing into a

You can use the ld command to invoke the binder, or use the xlc command. The xlc command looks at the types of the files to decide the action. For example *.c means compile *.o means bind.

My messy compile and bind script

name=colinm 
rm ${name}.o
export _C89_CCMODE=1
p1="-Wc,arch(8),target(zOSV2R4),list,source,ilp32,gonum,asm,float(ieee) "
p7="-Wc,ASM,ASMLIB(//'SYS1.MACLIB') "
p8="-Wc,LIST(c.lst),SOURCE -Wa,LIST(133),RENT"

# compile it

xlc $p1 $p7 $p8 $p2 -c $name.c -o $name.o

as -mgoff -aegmrsx=a.lst BPXTPOST.s
as -mgoff -aegmrsx=a.lst BPXTWAIT.s
as -mgoff -aegmrsx=a.lst DUMMY.s
as -mgoff -aegmrsx=a.lst br14.s
as -mgoff -aegmrsx=a.lst cpfetch.s

# create a load module in a PDS

include=" BPXTPOST.o BPXTWAIT.o pcexit.o br14.o DUMMY.o DUMMY2.o mywto.o cpfetch.o "
l1="-Wl,LIST,MAP,XREF,AC=1 "

/bin/xlc $name.o $include -o "//'COLIN.LOAD(colinm)'" -V $l1 1>a

This script

  • compiles colinm.c into colinm.o, puts the listing into c.lst
  • assembles the files BPXTPOST… putting the listing into a.lst
  • binds the list of “text files” into a load module COLIN.LOAD(COLINM), setting AC=1, and producing a load map.

Linkedit control statements

name=pcexit 
export _C89_CCMODE=1
l2="-l //'CEE.SCEESPC' "
l3="-O EDCXSTRT"
# create a load module in a PDS
l1="-b LIST,MAP,XREF,AC=1 "
ss="//'CEE.SCEESPC.OBJ(EDCXSTRT)'"
ld $ss $name.o -v -o "//'COLIN.LOAD(PCEXIT)'" -V $l1 $l2 $l3 1>ab

This script generates binder like JCL statements. (ld does not use them – it is a JCL equivilent of what is used)

//* ld  --------------------------------------------------------------- 
//LINKEDIT EXEC PGM=IEWBLINK,
// PARM='TERM=YES,PRINT=NO,MSGLEVEL=4,STORENX=NEVER,LIST=NOIMP,XREF=YESc
// ,MAP=YES,PRINT=YES,MSGLEVEL=0,LIST,MAP,XREF,AC=1'
//LDIN0001 DD DSN=CEE.SCEESPC.OBJ(EDCXSTRT),DISP=SHR
//LDAT0001 DD DSN=CEE.SCEESPC,DISP=SHR,DCB=DSORG=DIR
//SYSLMOD DD DSN=COLIN.LOAD(PCEXIT),DISP=REP,DATACLAS=,DSNTYPE=LIBRARc
// Y,MGMTCLAS=,SPACE=,STORCLAS=,UNIT=,DCB=(RECFM=U,LRECL=0,c
// BLKSIZE=6144,DSORG=PO)
//SYSPRINT DD PATH='/dev/fd1',PATHOPTS=(ORDWR,OCREAT,OAPPEND),FILEDATAc
// =TEXT,PATHMODE=(SIROTH,SIRGRP,SIRUSR,SIWOTH,SIWGRP,SIWUSc
// R)
//SYSTERM DD PATH='/dev/fd2',PATHOPTS=(ORDWR,OCREAT,OAPPEND),FILEDATAc
// =TEXT,PATHMODE=(SIROTH,SIRGRP,SIRUSR,SIWOTH,SIWGRP,SIWUSc
// R)
//SYSLIN DD *
INCLUDE LDIN0001
INCLUDE './pcexit.o'
LIBRARY LDAT0001
ORDER EDCXSTRT
/*

Where

  • -l //’CEE.SCEESPC’ becomes a binder LIBRARY statement which points to a PDS, which gets searched before SYSLIB
  • -O EDXCSTRT becomes the binder ORDER statement. For some programs, some CSECTS have to be in the right order. You can specify -O name1,name2.
  • -b…. pass these parameters to the binder
  • //’CEE.SCEESPC.OBJ(EDCXSTRT)’ contains some precompiled code. It gets mapped to INCLUDE … from a JCL dataset
  • $name.o is the precompiled object input to the binder.

    Using an archive file (the ar command)

    An archive file is something between a tar file, and a PDS with the output of compiles. (I have a dataset COLIN.OBJLIB in ISPF).

    * compile an assembler file
    as -mgoff -aegmrsx=a.lst BPXTPOST.s
    ar -rc arch.a BPXTPOST.o

    This

    • assembles the program BPXTPOST.s, creating BPXTPOST.o
    • stores it in the archive arch.a

    I can then bind with it

    /bin/xlc colinm.o   arch.a                 -o out.load   -V $l1    1>a 

    This takes the colinm.o and any .o files it needs from the arch.a archive.

    What is in the archive?

    The ar command

    ar -tv  arch.a

    gives

    rw-r--r-- 990021/990000     220 May 14 09:36 2026 __.SYMDEF  
    rw-r--r-- 990021/0 1200 Apr 27 10:29 2026 BPXTPOST.o
    rw-r--r-- 990021/0 1200 Apr 28 19:18 2026 BPXTWAIT.o
    rw-r--r-- 990021/0 720 May 11 15:11 2026 br14.o

    The command nm (Display symbol table of object, library, or executable files)

    nm -M   arch.a

    gives more information

    BPXTPOST.o:                               
    0 d --- --- - BPXTPOST
    0 T ANY ANY - BPXTPOST
    0 d --- --- - B_IDRL
    0 d --- --- - B_PRV
    0 d ANY ANY - B_TEXT
    0 U --- --- - CEESTART

    Where

    • 0 is offset from the start
    • d is Initialized data (bbs), local
    • T is Text symbol, global
      • ANY ANY – is RMODE(ANY) AMODE(ANY) Compiler_options(-)
    • U is undefined

    Using the nm command on an executable gave

    ...     
    128 U --- --- - B_TEXT
    1312 T MIN MIN - CEEARLU
    1312 U --- --- - CEEARLU
    728 U --- --- - CEEBETBL
    728 T MIN MIN - CEEBETBL
    ...
    104 D --- --- - locs
    152 T ANY ANY - main
    152 U --- --- - main
    40 U --- --- - optarg
    40 D --- --- - optarg
    ...

    we can see the offset of the main entry point is at offset 152 in the module.

    How do I create a load module in a PDS from Unix?

    This is another of the little problems which are easy once you know the anwser.

    I used the shell program to compile my program.

    name=extract 

    export _C89_CCMODE=1

    p1="-Wc,arch(8),target(zOSV2R3),list,source,ilp32,gonum,asm,float(ieee)"
    p7="-Wc,ASM,ASMLIB(//'SYS1.MACLIB') "
    p8="-Wc,LIST(c.lst),SOURCE,NOWARN64,XREF,SHOWINC -Wa,LIST(133),RENT"

    # compile it
    xlc $p1 $p7 $p8 -c $name.c -o $name.o

    l1="-Wl,LIST,MAP,XREF,AC=1 "
    # create an executable in the file system
    /bin/xlc $name.o -o $name -V $l1 1>a
    extattr +a $name

    # create a load module in a PDS
    /bin/xlc $name.o -o "//'COLIN.LOAD(EXTRACT)'" -V $l1 1>a

    Create an executable in the file system

    The first bind xlc step creates an object with name “extract” in the file system.

    Specify the load module

    The second bind step specified a load module in a PDS. The load module is stored in COLIN.LOAD. If you copy and paste the line, make sure you have the correct quotes ( double quote, //, single quote, dataset(member),single quote,double quote). Sometimes my pasting lost a quote.

    Process assembler code

    My program has some assembler code…

     asm( ASM_PREFIX 
    " STORAGE RELEASE,...
    :"r0", "r1" , "r15" );

    It needs the options “-Wc,ASM,ASMLIB(//’SYS1.MACLIB’) ” to compile it, and specify the location of the assembler macros.

    Binder parameters

    The line parameters in -Wl,LIST,MAP,XREF,AC=1 are passed to the binder.

    Message – wrong suffix on the source file

    Without the export _C89_CCMODE=1 I got the message

    FSUM3008 Specify a file with the correct suffix (.c, .i, .s, .o, .x, .p, .I, or .a), or a corresponding data set name, instead of -o ./extract.

    That’s strange – the compile worked.

    I was setting up a script to compile some C code in Unix Services, and it worked – when I expected the bind to fail because I had not specified where to find a stub file.

    How to compile the source

    I used a shell script to compile and bind the source. I was surprised to see that it worked, because it needed some Linkedit stubs from CSSLIB. I thought I needed

    export _C89_LSYSLIB=”CEE.SCEELKEX:CEE.SCEELKED:CBC.SCCNOBJ:SYS1.CSSLIB”

    but it worked without it.

    The script

    name=irrseq 

    export _C89_CCMODE=1
    # export _C89_LSYSLIB="CEE.SCEELKEX:CEE.SCEELKED:CBC.SCCNOBJ:SYS1.CSSLIB"
    p1="-Wc,arch(8),target(zOSV2R3),list,source,ilp32,gonum,asm,float(ieee)"
    p5=" -I. "
    p8="-Wc,LIST(c.lst),SOURCE,NOWARN64,XREF,SHOWINC "

    xlc $p1 $p5 $p8 -c $name.c -o $name.o
    # now bind it
    l1="-Wl,LIST,MAP,XREF "
    /bin/xlc $name.o -o irrseq -V $l1 1>binder.out

    The binder output had

    XL_CONFIG=/bin/../usr/lpp/cbclib/xlc/etc/xlc.cfg:xlc 
    -v -Wl,LIST,MAP,XREF irrseq.o -o./irrseq
    STEPLIB=NONE
    _C89_ACCEPTABLE_RC=4
    _C89_PVERSION=0x42040000
    _C89_PSYSIX=
    _C89_PSYSLIB=CEE.SCEEOBJ:CEE.SCEECPP
    _C89_LSYSLIB=CEE.SCEELKEX:CEE.SCEELKED:CBC.SCCNOBJ:SYS1.CSSLIB

    Where did these come from? – I was interested in SYS1.CSSLIB. It came from xlc config file below.

    xlc config file

    By default the compile command uses a configuration file /usr/lpp/cbclib/xlc/etc/xlc.cfg .

    The key parts of this file are

    * FUNCTION: z/OS V2.4 XL C/C++ Compiler Configuration file
    *
    * Licensed Materials - Property of IBM
    * 5650-ZOS Copyright IBM Corp. 2004, 2018.
    * US Government Users Restricted Rights - Use, duplication or
    * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
    *

    * C compiler, extended mode
    xlc: use = DEFLT
    ...* common definitions
    DEFLT: cppcomp = /usr/lpp/cbclib/xlc/exe/ccndrvr
    ccomp = /usr/lpp/cbclib/xlc/exe/ccndrvr
    ipacomp = /usr/lpp/cbclib/xlc/exe/ccndrvr
    ipa = /bin/c89
    as = /bin/c89
    ld_c = /bin/c89
    ld_cpp = /bin/cxx
    xlC = /usr/lpp/cbclib/xlc/bin/xlc
    xlCcopt = -D_XOPEN_SOURCE
    sysobj = cee.sceeobj:cee.sceecpp
    syslib = cee.sceelkex:cee.sceelked:cbc.sccnobj:sys1.csslib
    syslib_x = cee.sceebnd2:cbc.sccnobj:sys1.csslib
    exportlist_c = NONE
    exportlist_cpp = cee.sceelib(c128n):cbc.sclbsid(iostream,complex)
    exportlist_c_x = cee.sceelib(celhs003,celhs001)
    exportlist_cpp_x = ...
    exportlist_c_64 = cee.sceelib(celqs003)
    exportlist_cpp_64 = ...
    steplib = NONE

    Where the _x entries are for xplink.

    It is easy to find the answer when you know the solution.

    Note:

    Without the export _C89_CCMODE=1

    I got

    IEW2763S DE07 FILE ASSOCIATED WITH DDNAME /0000002 CANNOT BE OPENED
    BECAUSE THE FILE DOES NOT EXIST OR CANNOT BE CREATED.
    IEW2302E 1031 THE DATA SET SPECIFIED BY DDNAME /0000002 COULD NOT BE
    FOUND, AND THUS HAS NOT BEEN INCLUDED.
    FSUM3065 The LINKEDIT step ended with return code 8.

    Compiling in 64 bit

    It was simple to change the script to compile it in 64 bit mode, but overall this didn’t work.

    p1="-Wc,arch(8),target(zOSV2R3),list,source,lp64,gonum,asm,float(ieee)" 
    ...
    l1="-Wl,LIST,MAP,XREF -q64 "

    When I compiled in 64 bit mode, and tried to bind in 31/32 bit mode (omitting the -q64 option) I got messages like

     IEW2469E 9907 THE ATTRIBUTES OF A REFERENCE TO isprint FROM SECTION irrseq#C DO
    NOT MATCH THE ATTRIBUTES OF THE TARGET SYMBOL. REASON 2
    ...
    IEW2469E 9907 THE ATTRIBUTES OF A REFERENCE TO IRRSEQ00 FROM SECTION irrseq#C
    DO NOT MATCH THE ATTRIBUTES OF THE TARGET SYMBOL. REASON 3

    IEW2456E 9207 SYMBOL CELQSG03 UNRESOLVED. MEMBER COULD NOT BE INCLUDED FROM
    THE DESIGNATED CALL LIBRARY.
    ...
    IEW2470E 9511 ORDERED SECTION CEESTART NOT FOUND IN MODULE.
    IEW2648E 5111 ENTRY CEESTART IS NOT A CSECT OR AN EXTERNAL NAME IN THE MODULE.

    IEW2469E THE ATTRIBUTES OF A REFERENCE TO … FROM SECTION … DO
    NOT MATCH THE ATTRIBUTES OF THE TARGET SYMBOL. REASON x

    • Reason 2 The xplink attributes of the reference and target do not match.
    • Reason 3 Either the reference or the target is in amode 64 and the amodes do not match. The IRRSEQ00 stub is only available in 31 bit mode, my program was 64 bit amode.

    IEW2456E SYMBOL CELQINPL UNRESOLVED. MEMBER COULD NOT BE INCLUDED
    FROM THE DESIGNATED CALL LIBRARY.

    • The compile in 64 bit mode generates an “include…” of the 64 bit stuff needed by C. Because the binder was in 31 bit, it used the 31 bit libraries – which did not have the specified include file. When you compile in 64 bit mode you need to bind with the 64 bit libraries. The compile command sorts all this out depending on the options.
    • The libraries used when binding in 64 bit mode are syslib_x = cee.sceebnd2:cbc.sccnobj:sys1.csslib. See the xlc config file above.

    IEW2470E 9511 ORDERED SECTION CEESTART NOT FOUND IN MODULE.
    IEW2648E 5111 ENTRY CEESTART IS NOT A CSECT OR AN EXTERNAL NAME IN THE MODULE.

    • Compiling in 64 bit mode, generates an entry point of CELQSTRT instead of CEESTART, so the binder instructions for 31 bit programs specifying the entry point of CEESTART will fail.

    Overall

    Because IRRSEQ00 only comes in a 31 bit flavour, and not a 64 bit flavour, I could not call it directly from a 64 bit program, and I had to use a 32 bit compile and bind.