Accessing and Modifying TPF Globals in Prisym/C
by Dan Evans

TPF global data is protected from arbitrary modification by the storage protect key hardware of the S/370. In order for a program to modify global data, it must first change its protection key to that of the global data. While running with the global protect key, the program cannot modify its own data. Any violation of these rules is trapped by the hardware, and the program is terminated. The Assembler programmer has no problem with these constraints, but the C programmer must control the compiled code so as not to violate hardware protection. There are several approaches to this in Prisym/C. The following example was extracted from a production C program.

The example program uses the first byte of a word in one of TPF's global areas to coordinate multiple ECB's executing the same program. All of the following source code has been directly copied from the program. The C declarations for the global area and for some symbolic constants are:

/*
* device record load global area
* dvl uses 4 bytes in the global area
*/
#define DVL_GLOB_OFFSET 0x7E7
#define GLOBAL_AREA @DEVRECL
#define DVLACTIV 0x80 /* load active */
#define DVLPAUSE 0x40 /* load paused */
#define DVLABORT 0x20 /* load abort */
#define DVLINQRY 0x10 /* status request */

struct dvlglob
{
char dvlstat; /* run status */
char dvlrsvd1[3]; /* reserved */
};

In order to avoid declaring the entire structure of the global area to this program which uses only a small piece of it, the symbol DVL_GLOB_OFFSET is defined as the byte offset of the four byte global field used by the program. Since the purpose here is technique, the merits of this approach are left for philosophical debate. The actual area used is defined in the structure dvlglob. The symbol GLOBAL_AREA is defined as the TPF symbol for the area.

Addressability to the area is acquired by issuing a globz() call in the initialization routine housekeeping().

struct dvlglob *global;
static int housekeeping(void)

{
int return_code;
char *cp;
system_state(&return_code);
if (return_code == -1)

{
anomaly(ERR_STATE);
return -1;
}

globz(GLOBAL_AREA, &cp);
global = (struct dvlglob *)(cp + DVL_GLOB_OFFSET);
return 0;
}

The definition of the globz() C macro is:

/*
* get the address of global field
* field is the TPF symbol for the field
* expr is an expression which evaluates to the address
* of a pointer where the address will be stored
*/

#define globz(field, expr) \
#asm\
GLOBZ REGR=RAC,FLD=field\
#endasm\
*(expr) = (void *)_asmrc();

Notice that RAC (register 0) is used as the return from the TPF GLOBZ as a convenience. When the C preprocessor expands globz(), the TPF name is substituted for the parameter field, and the address returned is stored i the location whose address is the parameter expr. This C macro should be defined by a TPF Interface Specialist and placed in an #include library. The programmer using it does not need to be concerned with the details. He only knows that he passes globz() a TPF global name and the address of a location to store the returned address of the name. Note that in housekeeping(), after the address is returned, it is added to DVL_GLOB_OFFSET to get the actual location of the program's four byte global area. This location is stored in C variable global which is defined as a pointer to the structure dvlglob. The expansion of this code is shown in the following portion of the Assembler code from housekeeping().

* globz(GLOBAL_AREA, &cp);

TPFREST
GLOBZ REGR=RAC,FLD=@DEVRECL
ST 0,68(0,6)

* global = (struct dvlglob *) (cp + DVL_GLOB_OFFSET);

LA 13,2023(0,0)
A 13,68(0,6) cp
ST 13,44(0,7) global

The TPFREST macro is generated by the compiler every time in-line Assembler code is compiled in a C program. This macro restores TPF registers 11, 12, and 13 to their expected values, since the compiler uses them between TPF function calls. The GLOBZ macro shows the result after the C preprocessor performs its substitutions. The ST instruction is generated by the call to _asmrc() and assignment, since _asmrc() is a dummy routine. The Prisym/C/TPF Manual contains a complete explanation of this technique used to return values to a C program. After the ST instruction, the offset is added and the result stored in the pointer global.

Now that we have addressability to the global area, we can read it using the expression global->dvlstat. However, in order to modify it, the program's protect key must be changed to that of the global area. This example uses on of several TPF ways to do this. The technique puts all global modification requests into the following routine called set_global(), and provides for three function codes, AND, OR, and REPL.

static void set_global(int flag, int code)

{
glmod();

if (code = = AND)
global->dvlstat &= flag;
else

if (code = = OR)
global->dvlstat |= flag;
else

if (code == REPL)
global->dvlstat = flag;
filkw(R,NO):
}

set_global() is passed a flag value and a function code. The flag value modifies the global area according to the function code. But before that can be done, the protection key must be set to that of the global area. The glmod() C macro does this. glmod() is defined as:

/*
* prepare to modify a global area
*/
#define glmod(area) \
#asm\

GLMOD area\

#endasm

Since no parameter is used in set_global(), the TPF GLMOD macro is issued with no parameters, and therefore refers to the default global area. After glmod() is issued, the protection key has been changed to that of the global area. Any stores into C areas will cause a program check. This is the same situation which holds for any Assembler program. At the end of the function, the C macro filkw() is called to issue the corresponding TPF macro, to restore the protect key. It has the definition:

/*
* File by keyword macro
* up to eight fields can be specified, fields not
* specified are null and result in a macro such as
* FILKW R,@GLOB,,,,,,,,FLD=NO
*/

#define filkw(restore, field,f1,f2,f3,f4,f5,f6,f7,f8) \
#asm\

FILKW restore,f1,f2,f3,f4,f5,f6,f7,f8,FLD=field\
#endasm

Once the protection key has been changed to that of the global area, the C program must avoid generating modification instructions for area other than the global area. In order to do this:

  1. Keep expressions simple so that the compiler does not need temporary variables; do all computations before changing the key; the register storage class can be used if there are a few C variables which must be modified while the protect key is changed.
  2. Keep the execution path between the change and restoration of the protect key as short as possible.
  3. Don't call any functions after the key has been changed; functions which the compiler generates in-line such as memcpy(), are excepted;
  4. Write the modification code in in-line Assembler so that there is no question as to what the compiler will generate.

A close look at the actual code generated shows that the compiler is not hobbled by the requirements for accessing TPF global areas. It does not involve any more work than would be done in an Assembler program. The C macros globz(), glmod(), and filkw() are easily defined Once they have been defined, the programmers using them are not concerned with the details.

When programs such as the above example are debugged using Prisym's XRAY Source Debugger, the program should never be stopped in the area where the protection key has been changed. This will result in an immediate program check. It can be avoided by using the #pragma list off and the #pragma list on commands to exclude source code where the protect key is changed from debug eligibility, using such as the following:

#ifdef _XRAY_
#pragma list off
#endif