MVS TOOLS AND TRICKS OF THE TRADE September 1995 Sam Golob MVS Systems Programmer sbgolob@cbttape.org Sam Golob is a Senior Systems Programmer A NOVICE'S GUIDE TO ASSEMBLER PROGRAMMING - PART II Last month, we lamented the fact that many new systems programmers do not know assembler language. In my opinion, this situation is being made worse by the fact that so much system code gets distributed in OCO (Object Code Only) form, without access to source code or logic manuals. Now it is well known that our job as systems programmers involves not only installing packages of code, but diagnosing their problems as well. It is in that area, that a lack of assembler knowledge causes a large handicap. How does a knowledge of assembler language help? A person who knows assembler can look at a load module in a dump, or on screen, and recognize patterns. It is usually easy to spot a DCB (Data Control Block) and its associated DDNAME, if the DDNAME is hard coded or even if it is not. A glance at the beginning of the program will show which registers are the base registers, and how they are loaded. You can watch the STOREs and MOVE CHARACTERs into displacements off Register 13, and see how the work areas are loaded. If you can spot a BALR 14,15 instruction X'05EF', this is an indicator that another program is being called. You can actually look at the code previous to this instruction, and see how the parameter list (if it exists) is being loaded. The parameters are pointed to by Register 1, and the branch entry point is pointed to by Register 15. All of this "action" can be viewed if you know assembler language and you have developed a practiced eye. Such activity is aided by access to a disassembler program, which looks at a load module and decodes its machine instructions into assembler language source instructions. Our aim in using a disassembler is to help in diagnosing problems, not to "spy" on some vendor's coding techniques. If we are investigating a bug, and we know assembler language, we can actually help the vendor solve the problem more easily, as any veteran in this field knows. You can find several disassemblers on the CBT MVS Utilities Tape, an independently produced tape which can be ordered through the NaSPA office. The CBT Tape is a huge collection of free MVS software. Disassembler programs are found on Files 171, 217, 220, 238 thru 242, and 458 of the CBT Tape. You can take your choice of what disassembler to use. I have written several previous columns about disassemblers (January and March 1994). These columns are also in the CBT Tape, on File 120. This month, we will continue to explain more assembler concepts. Although the level will be a bit higher than in our last column, the text should be quite understandable, and it will still be fairly simple. At the end, we will show a revised version of last month's program called MYID, which reflects the additional concepts of reentrancy and mapping macros that we'll talk about today. REENTRANCY. Assembler programs running under MVS utilize MVS system services. This is something we cannot get away from. Therefore, depending on the task the program was designed for, some system services will usually have to be used. For example, suppose you want to write a program which can be run by many users at the same time from the same copy. A program that is in the LPA list (in common storage) is in this category. You have to construct it so it does not change itself. This is so that all the users of that program will get the same results from its execution. Most programs have data areas that are changed during the program's execution. Obviously this could pose a problem if there are multiple users of that code. There is a coding technique which gets around this problem. An "unchangeable" program can acquire dynamically obtained storage, just for this run, which will contain all of the changeable data. The rest of the program will remain completely unaltered throughout the run. Writing a program in such a way, is called "making the program reentrant". Making a program reentrant requires the use of system services to acquire the dynamic storage needed to contain the changeable data. In an MVS system, the service of obtaining storage is called GETMAIN. Oppositely, the service of freeing acquired main storage is called FREEMAIN. The GETMAIN and FREEMAIN services are invoked by system assembler macros. These macros, in their expansion, generate SVC (Supervisor Call) instructions. There are several different SVC's that a GETMAIN or FREEMAIN macro can generate. Which SVC is called, depends on what parameters are coded in the GETMAIN or FREEMAIN macro. In higher level MVS systems (ESA), there is also a STORAGE macro that can be used by a program to dynamically obtain additional storage areas for the program's use. There is one more thing we have to say about reentrancy. Suppose a program is indeed written so it does not modify itself. The system, however, will not force the use of a single copy of this program, unless the program also has the RENT (reentrant) linkage editor attribute turned on. That attribute is a bit setting in the pds directory entry for the load module, which was set by the linkage editor at the time the program was linkedited. This is done by coding a parm of RENT in the linkage editor JCL. The RENT linkage editor attribute tells the program fetch service to set things up, so that many callers of this program will use only one actual copy. How do we write a re-entrant program? Remember from last month, that when a normal application program is run under MVS, it is called by a chain of perhaps eight or nine other programs. Register 13 comes in, pointing to the previous program's register save area. In any program, the register save area will contain variable data. So before establishing a new save area for our program, we GETMAIN enough additional storage to contain the 72 bytes of the register save area, followed by all the storage needed to contain all the variable data which the program will have. If the save area is placed at the beginning of this dynamic storage, we can point Register 13 there, and do a USING on Register 13 to be able to find all of the fields in the dynamic area. Just before our program's execution is about to end, after Register 13 is restored to point to the calling program's save area, our program does a FREEMAIN of the dynamic storage, to give it back to the operating system. You can see a coded example in Figure 1. ASSEMBLER MACROS AND DSECTS. The Assembler (which is a compiler for assembler language code) normally generates only one machine instruction for each instruction that was coded by the programmer. So why do we need a compiler if we are really coding in machine language, one generated instruction per coded instruction? There are several reasons for this. First, the Assembler instructions are pseudo-English, whereas the machine instructions are merely hexadecimal numbers. It is a lot easier to remember something that looks like a language. Second, the Assembler does the arithmetic for us. If we tell the program to branch (jump) to a certain location, we can code the target location as a label, and the Assembler will calculate the location to which the program has to go. This is just one of many examples in which the Assembler makes our coding easier by doing a calculation. Third, the Assembler contains a Macro Facility, which allows the generation of a complicated result from the coding of just one "macro" instruction that can have options and switches built into it. The GETMAIN and FREEMAIN macro instructions, which obtain and free storage for a program's use, are examples of macro instructions. See Figure 2 for a sample of the kind of code these macros generate. Macro instructions can be pictured as coded mini-programs which perform a specific function. The source code for each macro starts with a MACRO statement and ends with a MEND statement; almost any kind of assembler statements may be coded in between, including other macro statements. In an assembler program, a macro is as though it were an instruction. When the program is assembled, the Assembler will realize that the macro is not part of the regular instruction set, and it will try to find the macro source code, either inline together with the program source, or externally in a macro library. The macro libraries are pointed to by the SYSLIB ddname in the assembly JCL. IBM supplies large collections of macros for its own use and for programmer use. Many of the IBM macros are shipped in two libraries: SYS1.MACLIB and SYS1.MODGEN. Informally, one might say there are various categories of macros. Some, like GETMAIN and FREEMAIN, are designed to invoke a system service. Others, which can be termed "mapping macros", contain data patterns which subdivide data areas into individual fields. A third type of macro is similar to a subroutine. You can input some keywords into the macro, and it will return a result. Actually, one can code almost anything in between the MACRO and MEND statements, so macros have an enormous amount of flexibility, by definition. Macro coding often makes extensive use of what is called "conditional assembly". Assembly-time branches, switches, and changes of variable may be built into the structure of the macro source. This allows macros to have enormous power and flexibility. Conditional assembly is discussed in detail in the Assembler Language manuals for the various IBM assemblers. In our treatment today, we only have time to discuss the mapping macros, so we will introduce the concept of the DSECT, or Dummy Section of a program. DSECTs map out patterns of storage, without actually defining a piece of the executable section of the program. For example, suppose we are dealing with a 20-byte record, which is divided into fields of various sizes and kinds. We can code a DSECT which will map the record layout of our record. Then we can load a register with the actual address of where the record is. A USING statement containing the label of the beginning of the DSECT, and the register in which the beginning of the data is loaded, will allow the program to subsequently address any of the individual fields in the data record. Storage in an assembler program is defined either with the DS (Define Storage) instruction or the DC (Define Constant) instruction. DS will define a certain amount of storage, with a certain length attribute, but will not specify the actual data values which are loaded into that place. DC, in addition to that, defines the initial value of the data occupying the storage which has been defined. There are many details concerning the kinds of storage which one can define. This matter is explained in the IBM Assembler Language manuals. For now, we can explain that hexadecimally expressed storage is indicated by the letter X, EBCDIC character form is indicated by the letter C, and binary bit form is indicated by the letter B. See Figure 2, which shows some storage definitions. Control blocks defined in the MVS operating system, are usually mapped by IBM-supplied macros which define DSECTs. A USING statement referring to a specific area (often the beginning) of the control block, and connecting to a register, which has been loaded with the address of that area in virtual storage, will allow a program to specifically refer to all the fields of the control block, by label. Space is short here, but we have re-coded last month's program, called MYID, to make it reentrant, and to use mapping macros to follow the address pointers in the control block chains. MYID, as you recall, is a TSO command that obtains your own userid, and displays it in a message to the terminal. The revised version of MYID is displayed in Figure 1. Please study the illustrations, and compare this version of the MYID program to last month's version. I hope you'll come away with a bit more knowledge of what makes an assembler program tick. * * * * * * * * * * * * * * * * * * * * * * * Figure 1. The MYID program revisited. This is the same program as we displayed last month, except that we have made it reentrant, we use IBM's register equate macro YREGS, we run the control block chain using IBM mapping macros for the CVT, TCB, and JSCB control blocks, and we moved the message area to GETMAINed storage. * M Y I D P R O G R A M - A T S O C O M M A N D * * TSO COMMAND PROCESSOR TO DISPLAY THE USERID OF THE INVOKER. * * REGISTER EQUATES YREGS , IBM'S REGISTER EQUATE MACRO SP000 EQU 0 DEFINE SUBPOOL TO BE 0 * MYID CSECT STM R14,R12,12(R13) SAVE CALLER'S REGISTERS R14 THRU R12 LR R12,R15 LOAD ENTRY POINT INTO BASE REGISTER USING MYID,R12 TELL THE ASSEMBLER, R12 IS THE BASE GETMAIN RU,LV=DATALEN,SP=SP000,LOC=BELOW * The address of the obtained storage is placed into Register 1. ST R13,4(,R1) SAVE CALLER'S SAVEAREA ADDRESS ST R1,8(,R13) STORE OUR SAVEAREA ADDRESS IN HIS LR R13,R1 POINT REGISTER 13 TO OUR SAVE AREA USING SAVEAREA,R13 TELL ASSEMBLER RUNCHAIN L R3,16 POINT TO CVT. ADDR IS IN LOW STORAGE USING CVT,R3 L R3,CVTTCBP POINT TO TCB/ASCB WORDS, "0" OFF CVT L R3,4(,R3) POINT TO TCB, "4" OFF TCB/ASCB WORDS DROP R3 USING TCB,R3 L R3,TCBJSCB POINT TO JSCB. X'B4' OFF CURRENT TCB DROP R3 USING IEZJSCB,R3 L R3,JSCBPSCB POINT TO PSCB. X'108' OFF THE JSCB DROP R3 USING PSCB,R3 MVC MESSAGE(20),MSGLINE MOVE TEXT TO VARIABLE AREA MVC MESSAGE+13(7),PSCBUSER MOVE MY USERID INTO MESSAGE DROP R3 TPUT MESSAGE,L'MESSAGE DISPLAY THE WHOLE MESSAGE ON THE TUBE RETURN DS 0H LR R1,R13 SET UP FOR SAVEAREA FREEMAIN L R13,4(,R13) POINT TO CALLER'S SAVEAREA FREEMAIN RU,LV=DATALEN,A=(R1),SP=SP000 LM R14,R12,12(R13) RELOAD THE CALLER'S REGISTERS BR R14 RETURN TO CALLER MSGLINE DC CL20'MY USERID IS ' CONSTANT PART OF MESSAGE * SAVEAREA DSECT DS 18F DEFINE MY SAVEAREA - 18 FULLWORDS MESSAGE DS CL20 VARIABLE MESSAGE AREA DS 0D ALIGN ON DOUBLEWORD DATALEN EQU *-SAVEAREA DEFINE LENGTH OF VARIABLE STORAGE * CVT DSECT=YES CVT mapping macro IKJTCB TCB mapping macro IEZJSCB JSCB mapping macro IKJPSCB PSCB mapping macro END * * * * * * * * * * * * * * * * * * * * * * * Figure 2. One instance of the expansion of the GETMAIN macro. In this example, we are requesting that the system acquire unconditionally, an amount of storage represented by the numerical value in the variable DATALEN. We also ask that the location of this virtual storage be at addresses below the 16 megabyte storage line, i.e. that its numeric addresses be less than the hex quantity X'00FFFFFF'. This is followed by an expansion of the corresponding FREEMAIN macro. The variable SP000 has been equated to zero, so the obtained storage will come from Subpool 0. The plus signs to the left, show the lines of code that are generated when the Assembler expands the macro code. GETMAIN RU,LV=DATALEN,SP=SP000,LOC=BELOW + CNOP 0,4 + B *+12-4*0-2*0 BRANCH AROUND DATA + DC A(DATALEN) LENGTH +IHB0001F DC BL1'00000000' Flags + DC AL1(0) RESERVED + DC AL1(SP000) SUBPOOL + DC BL1'00010010' MODE BYTE + L 0,*-8+2*0 LOAD LENGTH + L 15,IHB0001F LOAD GETMAIN PARMS + SR 1,1 ZERO RESERVED REG 1 + SVC 120 ISSUE GETMAIN SVC FREEMAIN RU,LV=DATALEN,A=(R1),SP=SP000 + CNOP 0,4 + B *+12-4*0-2*0 BRANCH AROUND DATA + DC A(DATALEN) LENGTH +IHB0013F DC BL1'00000000' Flags + DC AL1(0) RESERVED + DC AL1(SP000) SUBPOOL NUMBER + DC BL1'00000011' MODE BYTE + L 0,*-8+2*0 LOAD LENGTH + LR 1,R1 LOAD AREA ADDRESS + L 15,IHB0013F LOAD PARAMETERS + SVC 120 ISSUE FREEMAIN SVC