64->31 C SVC99 – Making Developers Lives Better

64->31 C SVC99 – Making Developers Lives Better

Photo by israel palacio on Unsplash

Background

z/OS is incredibly powerful, especially in the myriad of ways you can work with your data on disk. Unfortunately, many of those methods are only available as 31-bit services, and often require that you fill in assembler control blocks that can be annoying in C.

If you want to use a 31-bit z/OS service, you not only need to call them in 31-bit, but you need to ensure all the data is in 31-bit addressable storage. In z/OS-speak, we refer to 31-bit addressable storage as storage that is allocated below the bar. This should not be confused with a slightly different expression where storage is below the line (that means the storage has to be allocated as 24-bit addressable storage – luckily there are not many services left that require storage below the line).

The SVC99service is the penultimate example. It has relatively complex control blocks, has a C interface you can call in either 64-bit or 31-bit, but requires that all the storage be below the bar. In addition, there is no C interface to get human-readable error messages from SVC99 errors and instead you have to resort to looking up the errors in a book.

SVC99X Introduction

I have used svc99 and variants such as dynalloc over the years and have repeatedly cobbled together messy code to get the job done. I decided to write a service so that the next time I have to use svc99, it will be easier. My goals are to:

write C source code to call SVC99, with no special logic for 31 bit or 64-bit usagefollow a standard C programming model for building the C objectshave debug services for dumping out structures and printing human-readable messages

The interface is what you might expect:

SVC99init: pass in the parameters and build an opaque 31-bit object to be passed to the other servicesSVC99X: perform the SVC99 operationSVC99free: free the underlying control blocks allocated for use by SVC99XSVC99prtmsg: print out the error and information messages for a failed SVC99X callSVC99fmtdmp: dump out a formatted version of the SVC99X parameters

The largest C service is the initialization routine SVC99init, where you provide the verb, flags, extended request block, number of text units, and each text unit.
SVC99init passes back a pointer to the 31-bit object with all the necessary control blocks allocated below the bar. I won’t walk through the code in this blog, so that I can focus on the 64-bit to 31-bit assembler.

The var-arg interface makes it easy for developers to call the interface with 1, 2, or 10 text units, as needed.

Examples

The following example shows how to allocate the ddname DDPASS to the dataset SYS1.MACLIB with DISP=SHR. If there is an error, a formatted human-readable message, and a formatted dump of the SVC99X object is written to stderr.

#include “svc99.h”

SVC99Verb_T verb = S99VRBAL;
SVC99Flag1_T s99flag1 = {0};
SVC99Flag2_T s99flag2 = {0};
SVC99RBX_T s99rbx = {“S99RBX”,S99RBXVR,{0,1,0,0,0,0,0},0,0,0};
size_t numtextunits = 3;
SVC99CommonTextUnit_T dsn = { DALDSNAM, 1, 11, “SYS1.MACLIB” };
SVC99CommonTextUnit_T dd = { DALDDNAM, 1, 6, “DDPASS” };
SVC99CommonTextUnit_T stats = { DALSTATS, 1, 1, {0x8} };
SVC99_T* __ptr32 parms;
int rc;

parms = SVC99init(verb, s99flag1, s99flag2, &s99rbx, numtextunits, &dsn, &dd, &stats );
if (!parms) {
fprintf(stderr, “Unable to initialize SVC99 control blocksn”);
return 16;
}
rc = SVC99X(parms);
if (rc) {
SVC99fmtdmp(stderr, parms);
SVC99prtmsg(stderr, parms, rc);
}
SVC99free(parms);
return rc;

A more complex example would be setting up a ddname DDSPOOL to the JES2 master console on system S0W1, called S0W1.SYSLOG.SYSTEM

SVC99CommonTextUnit_T ddname = { DALDDNAM, 1, 7, {“DDSPOOL”}};
SVC99CommonTextUnit_T dsname = { DALDSNAM, 1, 18, {“S0W1.SYSLOG.SYSTEM”}};
SVC99CommonTextUnit_T dsstat = { DALSTATS, 1, 1, 0x8};
SVC99CommonTextUnit_T ssreq = { DALSSREQ, 1, 4, {“JES2”}};

SVC99BrowseTokenTextUnit_T brtkn = {
DALBRTKN, 7, BTOKIDLEN, “BTOK”, 2, BTOKSTKN, BTOKVRNM, BTOKIOTPLEN, 0,
BTOKJKEYLEN, 0, BTOKASIDLEN, BTOKACTBUF, BTOKRCIDLEN, {0}, 255, {0}
};

SVC99CommonTextUnit_T eropt = { DALEROPT, 1, 1, { DALEROPT_SKIP }};
SVC99_T* __ptr32 parms;
SVC99Verb_T verb = S99VRBAL;
SVC99Flag1_T s99flag1 = {0};
SVC99Flag2_T s99flag2 = {0};
SVC99RBX_T s99rbx = {“S99RBX”,S99RBXVR,{0,1,0,0,0,0,0},0,0,0};
size_t numtextunits = 6;
int rc;

parms = SVC99init(verb, s99flag1, s99flag2, &s99rbx,
numtextunits, &dsstat, &ddname, &dsname, &ssreq, &brtkn, &eropt);
if (!parms) {
fprintf(stderr, “Internal Error: Unable to initialize SVC99 control blocksn”);
return 16;
}
rc = SVC99X(parms);
if (rc) {
SVC99fmtdmp(stderr, parms);
SVC99prtmsg(stderr, parms, rc);
}
SVC99free(parms);
return rc;

Note in the second example the use of a different text unit object from the common case – it is a SVC99BrowseTokenTextUnit_T text unit. The code currently supports only a few types of text units, but more can be added as needed. I would welcome anyone that is interested to reach out and contribute.

The various structures, enums, and functions are described in svc99.h.

Under the Hood: SVC99prtmsg

The code for SVC99prtmsg() is relatively straight-forward. It sets up the object for the message service to indicate that the error and information messages should be returned in two 256 byte buffers. It then calls the assembler service and prints out the messages. The tricky part is calling the assembler service.

Language Environment provides 31-bit and 64-bit C functions for most services. svc99() for example has both a 31-bit and 64-bit implementation, with the caveat that the parameters have to be below the bar. This enables developers to use svc99 without having to write assembler. There is a bunch of annoying bit twiddling and storage management using the __malloc31 function, but it is manageable, and this is what I’ve done with SVC99init().

Unfortunately there is no such C service that I could find for printing out human-readable error messages. There is a 31-bit assembler interface called IEFDB476. The name just rolls off the tongue, doesn’t it ? . Calling 31-bit code from 64-bit code under Language Environment isn’t straight-forward (to me at least), but if the underlying 31-bit code doesn’t require Language Environment (for example, the system service assembler code IEFDB476), then it is possible.

A short aside: If you get an exception in the underlying assembler code, Language Environment will generate a CEEDUMP assuming it is in 64-bit mode and therefore the storage around the registers will be wrong if the upper word of the 64-bit register is not zeros. Sometimes you can help Language Environment out by clearing the high order word before calling the assembler service, but you need to be careful to restore it again.

I created a general purpose C wrapper so I can call 31-bit assembler services in the same way, regardless of whether my C code is is 31-bit or 64-bit. I use this same approach for calling all my 31-bit assembler routines from 31-bit and 64-bit C code, but have trimmed down the sample code to just show SVC99MSG. For SVC99MSG, I use a common interface called SVC99MSG that takes one parameter. Here’s the header file, not surprisingly called wrappers.h

#ifdef _LP64
#pragma variable(SVC99MSG, NORENT)
extern int SVC99MSG;
#define SVC99MSG(parms) call31asm(“SVC99MSG”, &SVC99MSG, 1, parms)
#else
#pragma linkage(SVC99MSG, OS)
int SVC99MSG(void* __ptr32 parms);
#endif

The 31-bit case (the #else block) is straight-forward: the assembler routine uses standard OS linkage and a function prototype indicates that parameters are in a 31-bit pointer.

The 64-bit case is more complex. If we were to just treat SVC99MSG as a function, then the C compiler would generate function descriptors that would cause us grief. So, we tell a small lie to the compiler and indicate that function is actually NORENT global variable (an external CSECT). We can then pass the address of that CSECT (the assembler code we want to call) as a parameter to call31asm, along with a count of the parameters and the parameters. To summarize, when in 64-bit mode, we don’t call the assembler code for SVC99MSG directly, instead we call a wrapper routine called call31asm, passing the name of the assembler routine, the address of the assembler routine, a count of the parameters, and then each of the parameters (in this case, just one parameter: parms).

The wrapper function call31asm needs to build a parameter list below the bar, allocate stack frame storage below the bar, then call another wrapper routine CALL31A (written in assembler) shown next.

#pragma linkage(CALL31A, OS)
int CALL31A(int* fn, char* dsa, unsigned int* parms);
int call31asm(const char* fn_name, int* fn, size_t num_parms, …) {
va_list args;
size_t i;
int rc;
unsigned int* r1_31bit_parms = __malloc31(num_parms*sizeof(unsigned int));
char* r13_31bit_dsa = __malloc31(MAX_DSA_SIZE);
if (r1_31bit_parms == NULL || r13_31bit_dsa == NULL) {
return -1;
}

va_start(args, num_parms);
for (i=0; i<num_parms; ++i) {
unsigned int parm = va_arg(args, unsigned int);
r1_31bit_parms[i] = parm;
}
va_end(args);

rc = CALL31A(fn, r13_31bit_dsa, r1_31bit_parms);
free(r1_31bit_parms);
free(r13_31bit_dsa);

return rc;
}

The following assembler CALL31A took me more than 5 minutes to write ? … But, the logic is fairly straight-forward:

load the target assembler routine and parameter list into registersload the 31-bit stack frame and 31-bit parameter listsave the 64-bit stack framesave the 64-bit return addressset up the next-available-byte (NAB) for the 31-bit stack frame (DSA)clean up the target assembler routine to set bit 32 on (indicating an AMODE 31 address)branch and set to the target assembler routine, switching from AMODE 64 to AMODE 31call the 31-bit assembler codeon return, restore the 64-bit stack frame, load the 64-bit return address, and branch back to the call31asm wrapper functionCALL31A CSECT ,
CALL31A AMODE 64
CALL31A RMODE ANY
*
* Specialized linkage for CALL31A
* R1 ->
* @fn -> 31-bit assembler routine to call *
* ————— *
* @dsa -> 31-bit storage to use as stack frame *
* ————— *
* @parms -> 31-bit parameter list for target routine*
* —————
LG R15,0(,R1) # target fn pointer
LGR R0,R13 # save R13 into R0
LG R13,8(,R1) # load 31-bit DSA into R13
LG R1,16(,R1) # load 31-bit R1 into R1
STG R0,0(,R13) # store old R13 (dsa)
STG R14,8(,R13) # store old R14 (return address)
LA R13,16(,R13) # move 31-bit DSA to user DSA
ST R13,76(,R13) # store NAB of DSA
*
OILH R15,X’8000′ # set bit 32 on in target fn
BASSM R14,R15 # branch-and-set to 31-bit mode
*
* Reload 64-bit R13 from start of low memory storage area
*
AHI R13,-16
LG R14,8(,R13) # restore R14 return address
LG R13,0(,R13)
*
* Abbreviated epilog
*
BR R14
*
END

What could be simpler ?

After all that, the assembler routine SVC99MSG is a bit anti-climactic – it just LINKs to the module, passing in the parameters, which are already in R1:

SVC99M MSFSECT
ENTRY SVC99MSG
SVC99MSG MSFPRO BASE_REG=12,USR_DSAL=MSG_DSAL
USING MSG_DSA,R13
ST R1,PARMS
LINK EP=IEFDB476
MSFEPI
*
MSG_DSA DSECT
DS CL(120+8)
MSG_TOP DS 0D
PARMS DS A
MSG_DSAL EQU *-MSG_TOP

The MSFSECT macro sets the AMODE and RMODE for the CSECT and, for 64-bit mode only, sets the extended attribute for the CSECT as REFERENCE(DATA) so that the binder will bind the external variable reference on the C code to this assembler section.

&NAME XATTR REFERENCE(DATA)

The MSFPRO macro uses the standard Language Environment EDCPRLG macro for the assembler prolog when building for 31-bit or MSF64PRO when building for 64-bit. The main difference between these macros is again the need for the extended attribute specification on the CSECT.

The MSFEPI macro is analogous to the MSFPRO macro. The MSFEPI macro uses the standard Language Environment EDCEPIL macro for the assembler epilog when building for 31-bit or MSF64EPI when building for 64-bit. The main difference between these macros is that MSF64EPI needs to issue a BSM to switch the addressing mode back to 64-bit.

How to Build and Run the Code

To show how to use the code, I wrote a script that compiles, assembles, links and runs a test case, first in 64-bit mode and then in 31-bit mode. The test case does 3 different SVC99’s – the 2 examples shown earlier, plus another SVC99 that is designed to fail so you can see the formatted dump and human-readable error messages. The output from each test will have a few informational about 64-bit portability and then something similar to:

Allocate DD to Master Console S0W1.SYSLOG.SYSTEM – this should pass if you have authority (change S0W1 to your system name)
Allocate DD to SYS1.MACLIB – this should pass
Allocate DD to SYS1.LINKLIB – this should fail
SVC99X Formatted Dump
RBLN:20 VERB:1 FLAG1:0000 ERROR:0210 INFO:0000 FLAG2:00000000
S99X: 2039A060 EID:S99RBX EVER: 01 EOPTS: 40 SUBP: 00 EKEY: 00 EMGSV: 00 ECPPL: 00000000 EMSGP: 203A2F70
textunit[0] 2039A0B0 18 00020001 000CE2E8 E2F14BD3 C9D5D2D3 C9C2
textunit[1] 2039A0D0 9 00010001 0003C4C4 C6
textunit[2] A039A0E8 7 00050001 000108
SVC99X rc:0x4
SVC99X failed with error:528 (0x210) info: 0 (0x0)
IKJ56225I DATA SET SYS1.LINKLIB ALREADY IN USE, TRY LATER+
IKJ56225I DATA SET IS ALLOCATED TO ANOTHER JOB OR USER
64-bit Test passed

I hope you have found this useful. If you uncover any bugs, please let me know. If you would like to contribute to expanding the types of text units that SVC99X can process, along with test cases, that would be great as well.

As always, I couldn’t have figured this out without the help from the experts at IBM. My thanks to Peter Relson, Alexei Pytel, Onno Van den Troost, Behnam Al Kajbaf, Mario Perotti, BJ Scheid, and Nick Carbone for answering my questions.

Like this:

Like Loading…

Author: Douglas Hernandez