The Computer Journal, Issue 29

Z-System Corner

© Jay Sage

Reproduced with permission of author and publisher.

One of the problems with writing a column for a magazine that only appears every two months or so is that so many things can happen between when one column is written and the next one is published. (Of course, there would be other, insurmountable problems if I had to turn out these columns every month, so I am not complaining.) At the end of the last article, I mentioned that ZCPR33 would probably be out by the time that issue appeared. Indeed, that was true. What I did not write, because no public announcement had been made yet, was that I had joined the Echelon team and would be the author of that version. So I am now wearing two hats, one as ZSIG software librarian and one as the Echelon team member in charge of command processor development. Richard Conn has gone off to do more esoteric things. In recognition of this change, I have broadened the title of this column.

ZCPR Version 3.3

Since ZCPR33, or Z33 as I will call it for short, is too exciting a subject to pass up, I will say a few words about it before continuing the discussion we began last time of techniques for customizing Z-COM. I will not say too much, however, since a lot of effort already went into preparing the 60-page "ZCPR33 User Guide". It has all the details and is available from either Echelon or Sage Microsystems East for $15 plus shipping ($3 from SME). Since the code, as in the past, is still available free of charge for personal, noncommercial use, sale of the manual is the only way other than OEM sales that we get any compensation for the enormous amount of effort that went into Z33. I will talk this time only about the design goals for Z33, and perhaps in future columns I will talk about some of the new features and capabilities.

Design Goals

In developing Z33, I tried to achieve five things (the number keeps growing every time it think about it). (1) I have tried to maintain a very high level of compatibility; (2) I have tried to increase flexibility, control, and speed; (3) I have tried to make the code rigorous and reliable; (4) I have tried to make more information about the internal state of the command processor accessible to user programs; and (5) I have tried to make the code readable and educational.

To the greatest extent reasonable, I have maintained compatibility between Z30 and Z33. No change need be made to any part of the operating system other than the command processor; the Z33 command processor, as I hinted at in the last column, can simply be dropped in wherever the old command processor was, either on the system tracks of the boot disk or in the appropriate places in a Z-COM system. No changes need be made in the memory allocations, and the officially released system modules that worked with Z30 will work with Z33 as well, though new, more powerful RCP and FCP modules were released with Z33 (the 'H' command in the unofficial experimental RCP145, because it made direct references to internal addresses in the CPR, will not work). All application programs and almost all utility programs will work unchanged with Z33. New utility programs have been written to take advantage of some of the new features in Z33.

Many features of ZCPR3 - such as automatic path searching for COM files, extended command processing, and error handling - are very convenient but can significantly slow system response. With Z33, the user is given greater control over these features from the command line so that unnecessary operations can be bypassed to save disk activity and time. No longer does the path automatically include the current directory first. The user can now, at his option, omit the current directory or include it in any position in the path. This has a dramatic effect on system speed. A command entered with a leading slash ("/") is handled directly by the extended command processor without wasting time searching the path for a COM file. For systems that take increasing advantage of ARUNZ or other extended command processing, speed is, again, greatly improved. Programs with what is called a type-3 environment are automatically loaded and executed at addresses other than 100H. By loading error handlers, shells, and extended command processors high in memory, user programs at 100H are left intact and can be reinvoked using the GO command.

The code in Z33 was almost totally rewritten, taking only the basic functions from Z30. Quite a few bugs, some very serious, were corrected, and new algorithms were used for many of the functions. A great deal of effort was devoted to making the code rigorous. No longer can a command tail longer than 128 bytes overwrite the program code and crash the system. No longer can command lines in SUBMIT files write beyond the end of the command line buffer. The root path and minimum path features now work correctly, so that duplicated elements in the search path do not have to be searched more than once. Extended command processing functions reliably and in combination with error handling.

The Z33 command processor makes much more information available about its operation. In Z30, only COM files that could not be found would invoke error handling. Z33 traps many different kinds of errors, and it can report the nature of the error to the user. Some examples are TPA overflow, disk full, bad numerical expression, incorrect password, bad directory specification, or ambiguous file specification. Z33 even makes it possible for user programs and routines in the resident command package to invoke error handling and to report the type of error. When Z30 parsed a file expression that specified an invalid directory, it simply substituted the current directory, but the program had no way to tell that this had been done. Z33 sets a flag to indicate the error. With Z33, a program can tell where on the search path it was actually found. This can be valuable for shells and error handlers that install themselves into the system. They can operate faster if they know where they are located.

Finally, the source code has been completely reorganized and very extensively commented. I did this not only to make it easier for me to maintain it but also so that others could read it to learn how the command processor works. One of the main reasons why many of us hobbyists remain involved in the 8-bit world is that a Z80 can be comprehended fairly easily and can thus be used to learn deeply about the operation of a computer. Z33 is designed to contribute to this.

Advanced Z-COM Customization

In our discussion last time we described what Z-COM is and how it works, and we presented a number of techniques for carrying out simple modifications that did not alter the basic structure of the Z-COM system. This time we will examine some much more far-reaching modifications, including those that involve changing the way Z-COM operates. The goal is to develop techniques that will permit us to use the basic principles behind Z-COM to build arbitrary systems of our choice. I do want to warn you: a good part of this discussion will be at an advanced technical level. Even I feel rather mentally exhausted after going through the process of developing it and writing it down. If any one section is getting too technical for you, please do not give up completely. Skip ahead to each new section and read the introductory philosophical comments. There is material there that I would really like everyone to see.

When Z-COM Will Not Work

Before launching into the heavy code patching, I would like to cover a topic that probably should have been included last time: why Z-COM will not work in all systems or will not work fully.

There are two circumstances that I have experienced in which Z-COM interferes with the proper operation of a system. One class of difficulties arises when the system uses utilities that make modifications to the operating system image in memory. Such utilities always invite disaster, but they are nevertheless quite common (partly because they are so useful). If the utilities calculate the addresses to change from the BIOS warmboot address at location 0001, then there will be trouble, because that address points to the virtual BIOS set up by Z-COM and not to the real BIOS.

The Ampro BIOS, for example, has a number of special structures in defined locations with respect to the beginning of the BIOS. Some of these, for example, support the various configuration options. Since these options are rarely changed except when the system is first assembled or when new hardware is added, one can overcome any problem by running the utilities when Z-COM is not in operation. Other BIOS structures are used to define the alternative disk formats. These one might want to change during a session at the computer. Although it is inconvenient, one could again exit from Z-COM using ZCX, change the disk format, and then reenter Z-COM. Of course, a conventionally installed ZCPR system is available for the Ampro so that Z-COM is not necessary. However, a number of other computers running standard CP/M 2.2 use similar techniques and have similar utilities.

The second class of difficulties arises when the BIOS warmboot code performs some indispensable function. Remember that when Z-COM is running, the warm boot is intercepted, and the warmboot code in the original BIOS does not run. My BigBoard I with the double-density upgrade automatically selects from a large number of disk formats. One simply puts the diskette with the new format into the drive and presses control-c. The warmboot code in the BIOS includes code for determining the format of the diskette. When Z-COM is running, I often experience problems when I try to log in a new drive for the first time or when I try changing disk formats. The trouble seems to have to do with the way the BIOS keeps track of what drives are logged in, and by using disk resets or control-c's from Z-COM, I can often get the system to work. But clearly there can be problems when the BIOS warmboot code is completely by-passed.

More Named Directories

To get our feet wet again with Z-COM patching, let's start with a relatively simple but very practical example. The most frequent request I get from users of Z-COM, especially those using it to make a remote access system, is for a way to increase the number of named directories.

To refresh our memories, I have reproduced in Fig. 1 the memory map of our unmodified Z-COM system. Those of you who are really sharp (or have photographic memories or are cheating by actually looking at the last issue) will notice that this is not exactly the same as the map presented last time as Fig. 2. The reason for this is that I recently was offered a deal that I simply could not refuse on a hard-disk Televideo 803H system (every household needs four complete computer systems, no?). Since I cannot bear to operate a computer without Z-System, I immediately implemented Z-COM on it and have been using it as the testbed for the techniques described here. It's BIOS is obviously even less compact than the one on my BigBoard and starts 200H lower in memory. I hope this address switch does not confuse you too much, but since your system probably does not match mine anyway, you have to get used to translating addresses. The addresses in the ZC.COM image, of course, do not change.

System ComponentZC.COM AddressSystem Address

CPR0200 - 09FFBA00 - C1FF
ZRDOS0A00 - 17FFC200 - EFFF
Virtual BIOS1800 - 19FFD000 - D1FF
Named Directory Register1A00 - 1AFFD200 - D2FF
Shell Stack1B00 - 1B7FD300 - D47F
Z3 Message Buffer1B80 - 1BCFD380 - D4CF
External FCB1BD0 - 1BF3D3D0 - D4F3
Wheel Byte1BFF - 1BFFD3FF - D4FF
Environment Descriptor1C00 - 1C7FD400 - D47F
TCAP1C80 - 1CFFD480 - D4FF
Multiple Command Line1D00 - 1DCFD500 - D5CF
External Stack1DD0 - 1DFFD5D0 - D5FF
Resident Command Package1E00 - 25FFD600 - DDFF
Flow Control Package2600 - 27FFDE00 - DFFF
I/O Package2800 - 2DFFE000 - E5FF

Fig. 1.
Addresses of system components in the ZC.COM file and in the example system for which it was generated (Televideo 803H).

Now, if we want to have room for more directory names, we have to find a way to allocate more memory to the NDR module. Where can we steal some memory? Unfortunately, the memory cannot be taken from either of the neighbours of the NDR. The virtual BIOS and shell stack are indispensable. That means that we will have to move the NDR or something else from its present position. The best target for our memory raid is that hulking 6-page, 1.5K IOP that often goes unused, especially on remote access systems. We will cut it down to 3 pages and use the top 3 pages for our new NDR, which will have a capacity for 42 names [ (3 * 256 - 1 ) div 18 = 42 ]. The resulting memory map is shown in Fig. 2.

System ComponentZC.COM AddressSystem Address

CPR0200 - 09FFBA00 - C1FF
ZRDOS0A00 - 17FFC200 - EFFF
Virtual BIOS1800 - 19FFD000 - D1FF
( unused space )1A00 - 1AFFD200 - D2FF
Shell Stack1B00 - 1B7FD300 - D47F
Z3 Message Buffer1B80 - 1BCFD380 - D4CF
External FCB1BD0 - 1BF3D3D0 - D4F3
Wheel Byte1BFF - 1BFFD3FF - D4FF
Environment Descriptor1C00 - 1C7FD400 - D47F
TCAP1C80 - 1CFFD480 - D4FF
Multiple Command Line1D00 - 1DCFD500 - D5CF
External Stack1DD0 - 1DFFD5D0 - D5FF
Resident Command Package1E00 - 25FFD600 - DDFF
Flow Control Package2600 - 27FFDE00 - DFFF
I/O Package2800 - 2AFFE000 - E2FF
Named Directory Register2B00 - 2DFFE300 - E5FF

Fig. 2.
Addresses of system components in the ZC.COM file and in the target system as modified to support 42 named directories.

To implement this change in our Z-COM file we have to change only the ENV and CPR modules. The ENV has to know about the new memory map, and the CPR code has to know where the NDR module is located. Although the NDR module will be placed in a new location in the ZC.COM file, the NDR data are position-independent, so the module itself need not be changed (though we will presumably be adding many new names). The FCP and RCP are still in the same place doing the same thing.

One of the new features of Z33, by the way, is the ability to determine the locations of the NDR, FCP, and RCP from the environment descriptor. Thus if we are using ZCPR33 with this feature enabled, no change in the CPR code is required.

Only three changes in the Z3BASE.LIB file are required to reflect the new memory map. These are the definitions for the symbols IOPS (the number of 128-byte records allocated to the IOP), Z3NDIR (the address of the NDR), and Z3NDIRS (the number of names in the NDR). These changes are as follows:

symbolold expressionnew expression

IOPS12 (0CH)6 (06H)
Z3NDIRz3env - 200Hz3env + 0E00H or
iop + 300H
Z3NDIRS18 (12H)42 (2AH)

The new SYS.ENV file can be made either by assembling SYSENV.ASM with the modified Z3BASE.LIB, or it can be done by patching (either to the image imbedded in ZC.COM or to the standalone ENV file). I didn't have a copy of SYSENV handy, so I have been using ZPATCH (which is much more fun anyway). I find that I am constantly in need of the addresses of various items in the environment descriptor, so to make them easier to find, I took my copies of Richard Conn's "ZCPR3, The Manual" and put a 3M Post-It (one of those wonderful little yellow semi-stick note sheets) on page 300 where the SYSENV module is described. Then I wrote the offsets shown in Fig. 3 into the margin next to the symbols. It was thus very easy to determine that the addresses to patch in the ZC.COM image are 1C11H (IOPS), 1C15H (Z3NDIR), and 1C17H (Z3NDIRS).

offset   SYSENV code line

09 dw expath
0B db expaths

0C dw rcp
0E db rcps

0F dw iop
11 db iops

12 dw fcp
14 db fcps

15 dw z3ndir
17 db z3ndirs

18 dw z3cl
1A db z3cls

1B dw z3env
1D db z3envs

1E dw shstk
20 db shstks
21 db shsize

22 dw z3msg

24 dw extfcb

26 dw extstk

28 db [quiet flag -- no symbol]

29 dw z3whl

2B db [cpu speed -- no symbol]

2C db [max drive (A=1) -- no symbol]
2D db [max user]

Fig. 3.
Offsets to various symbols and information in SYSENV.ASM, the environment descriptor module (see "ZCPR3, The Manual" p. 300ff).

The next steps were to assemble up a new version of the command processor, create the desired NDR file, and then put all the pieces into the ZC.COM file as described last time. While I was at it, I decided that it would be nice if this new version could co-exist on the system with the previous version, in case I ever wanted the full IOP space back temporarily. To achieve this, I made two additional changes in the image and saved it to a file called ZC1.COM instead of ZC.COM. These two changes were to the name of the startup alias and the name of the CPR file to be loaded from A15 by the warmboot code.

The startup command is stored in the multiple command line buffer, whose image begins at 1D00H. The standard ZC.COM has the following data there for my Televideo 803H with its real MCL at D500H:

<04> <D5> <CC> <04> S T R T <00>

The first two bytes are a pointer to the address D504, where the next command (in this case the only command) to be executed is stored. They do not have to be changed. The third byte, CCH, is the maximum number of characters that the command line can contain. It should not have to be changed, but in fact the value is wrong. Fortunately, this mistake can only cause trouble in highly exceptional circumstances, but while we are at it we can put in the correct value of CBH = 203. The value of the symbol Z3CLS in Z3BASE.LIB should also be changed. The correct value is the maximum number of actual characters in the command line. As can be seen above, there are four bytes before the command string and one byte (the terminating null) after it. Hence the proper value for Z3CLS is five less than the total amount of memory allocated to the multiple command line buffer module.

The fourth byte is the number of text characters in the command line. This value is never used by the operating system, but the DOS line input function writes the count to that position, so we have to provide space for it. If you put a wrong value there, it will not make any difference, at least not for the operations performed here. I have heard that there was at least one utility program that used this value for some purpose. I do not recommend this practice, since some command-line-generator programs, I believe, and do not update the value after they produce their command lines. I am also not sure whether or not the Z3LIB routines APPCL and PUTCL update the character count.

To make ZC1.COM use a startup alias called START1 (6 characters), we would change the MCL buffer to

<04> <D5> <CB> <06> S T A R T 1 <00>

This can be done either with ZPATCH or a debugger. If there is a lot of garbage in the rest of the command line buffer, you can fill it with zeros out through address 1DCFh to make things look neater.

The file control block for the ZC.CP command processor image that is loaded from directory A15 starts at address 1944H. By changing the space character at address 1947H from 20H to 31H ('1' ASCII), the CPR image ZC1.CP will be loaded instead. You can also change the message at address 1901H to reflect the name of the CPR image file. There is room to squeeze two extra characters into that message, one by eliminating the leading space and one by omitting the ending period. After you're done with these changes, don't forget to put the CPR image file ZC1.CP in A15.

It seems wasteful with this configuration to leave unused the block of memory where the NDR used to be. When I implemented this version, I moved the multiple command line buffer there so that I could increase its size from 208 to a full 256. One might not enter such long commands by hand, but aliases and other command line generators occasionally overflow the 203 character limit in the usual configuration. For ZCPR34 I am considering some techniques for extending the length to a two-byte value so that the command line can be as large as one would like. I will not describe the extra changes required to move the command line buffer, since the next example will cover that and more.

Completely Revamping the Memory Model

We will now consider how we go about completely revamping the memory model, including moving the IOP. Moving any module except for the IOP can be accomplished using a straightforward extension of what we have already described. The IOP poses some special problems that we will now deal with.

Before turning to that subject, I would like to make some general comments about the memory allocation in a ZCPR3 system. With a fixed system - that is, any particular system that one will use at all times - it does not really matter how the modules are distributed in memory, just so long as they all fit somewhere.

As soon as one wants to be able to change from one system configuration to another, not all memory models are as good as others. I first noticed this when I wanted to run both Z3-DOT-COM and Z-COM on my system. I usually used Z3-DOT-COM because it left a larger TPA, but occasionally I wanted to make use of the IOP for redirecting console output to a disk file. At that point I discovered that Joe Wright's choice of memory models was a poor one. By adding the IOP at the top in Z-COM rather than at the bottom, the environment descriptor moved down to a lower address, and that meant that I either had to have two sets of utility programs or had to reinstall all the utilities every time I changed from one system to the other. This provided a powerful incentive to discover methods for modifying the memory maps. Of course, with ZCPR33 and its automatic installation of utilities, this is no longer a concern.

Even with ZCPR33 there are compelling reasons to choose some memory configurations over others. The addresses of most system components are hard-coded into even the ZCPR33 command processor. However, as we mentioned earlier, the addresses of the largest memory buffers - the NDR, FCP, and RCP - can determined dynamically from the environment descriptor in memory. As a result, if these buffers are placed adjacent to one another, the single block of memory allocated to the set of them can be reconfigured simply by loading a new environment descriptor. Since the command processor does not directly refer to the IOP, the IOP can also be included at the bottom of this single buffer space. The tradeoffs that become possible are illustrated in Fig. 4, where a single memory block of 4.25K (the standard amount in Z-COM for these modules) is allocated in two different ways.

total space standard configuration alternate configuration
--- --- ---
| | NDR (2) |
| --- | NDR (6)
| | FCP (4) |
| | ---
| all --- |
| buffers | | FCP (7)
| 4.25K | |
| 34 records | ---
| | RCP (16) |
| | |
| | |
| | |
| | | RCP (20)
| --- |
| | |
| | |
| | IOP (12) |
| | |
| | ---
| | | IOP (1)
--- --- ---

Fig. 4.
One illustration of how a single block of memory can be dynamically allocated among the IOP, RCP, FCP, and NDR buffers in a ZCPR33 system.

In the alternative memory map in Fig. 4 the IOP buffer has been shrunk to a single record, a space just large enough to hold the dummy IOP that Z-COM comes with (we can't shrink it to zero without changing the VBIOS). The extra 11 records of memory are then distributed to the other modules. The NDR increases to 6 records, enough for 42 named directories. The FCP picks up 3 more records. At 7 records, it has enough space to implement a very large number of resident test options. The remaining 4 records go to the RCP, which can probably now include all options in Z33RCP.

Suppose the standard configuration is described by SYS.ENV and uses the modules SYS.NDR, SYS.FCP, and SYS.RCP, and that the alternative configuration is described by ALT.ENV with modules ALT.NDR, ALT.FCP, and ALT.RCP. Then we would change from the standard to the alternate configuration by entering the command


Note that it is essential that the ENV module be listed, and therefore loaded, first. If it is not, LDR will not know the correct addresses for the other modules. Similarly, to change back to the standard configuration, one would use the command


If one were switching back, for example, to use the NuKey IOP to provide keyboard macro capability, the line could read


In this way one can make much more flexible use of system resources. One might choose to reduce the size of the overall buffer space to only 35 records, keeping only the IOP stub in the standard configuration. Then when an IOP like IOR (I/O recorder) or BPRINT (print spooler) is needed, the RCP and/or FCP would be contracted temporarily. Either one or both could even be eliminated (reduced to zero size). Aliases can be used to automate the process of switching configurations.

Moving the IOP

We will now build a Z-COM system of the form described above. The memory map for the new configuration is shown in Fig. 5. Since the ENV (including TCAP) is a fixture in any system, I would have preferred to put it in the invariant position at the very top of memory. However, ZRDOS has to know where the ENV is in order to support wheel-locking of files. If you have purchased ZRDOS separately, you can generate a version to run at any address and to reference an ENV at any address. If you only have Z-COM, however, you do not have this freedom. Therefore, I have left the environment where it was.

System ComponentZC.COM AddressSystem Address

operating system modules
  CPR0200 - 09FFBA00 - C1FF
  ZRDOS0A00 - 17FFC200 - EFFF
  Virtual BIOS1800 - 19FFD000 - D1FF
large, variable buffers
  I/O Package1A00 - 1FFFD200 - D7FF
  Resident Command Package2000 - 25FFD800 - DFFF
  Flow Control Package2800 - 27FFE000 - E1FF
  Named Directory Register2A00 - 1AFFE200 - E2FF
third page of buffers
  Shell Stack2B00 - 1B7FE300 - E47F
  Z3 Message Buffer2B80 - 1BCFE380 - E4CF
  External FCB2BD0 - 1BF3E3D0 - E4F3
  PATH2BF4 - 1BFEE3F4 - E4FE
  Wheel Byte2BFF - 1BFFE3FF - E3FF
second page of buffers
  Environment Descriptor2C00 - 1C7FE400 - E47F
  TCAP2C80 - 2DFFE480 - E4FF
top page of buffers
  Multiple Command Line2D00 - 1DCFE500 - E5CF
  External Stack2DD0 - 1DFFE5D0 - E5FF

Fig. 5.
Memory map for a system designed for dynamic buffer reallocation under ZCPR33.

The first step, as usual, in making a new configuration is to prepare a new Z3BASE.LIB file. The important addresses in that file are shown in Fig. 6. I have considered each page of memory to be a unit. The bottom of the page is referenced to the real BIOS address, and the other modules in the page are referenced to the base module for that page. Of course, you can express these addresses in many other equivalent ways, and you may well prefer to do it differently. In any case, with Z3BASE.LIB in hand, you can assemble up the CPR, RCP, FCP, and ENV modules (or you can make the latter by patching).


rbios equ 0e600h

; First page under BIOS
z3cl equ rbios - 100h
z3cls equ 203
extstk equ z3cl + 0d0h

; Second page under BIOS
z3env equ rbios - 200h
z3envs equ 2

; Third page under BIOS
shstk equ rbios - 300h
shstks equ 4
shsize equ 32

z3msg equ shstk + 080h
extfcb equ shstk + 0d0h
expath equ shstk + 0f4h
expaths equ 5
z3whl equ shstk + 0ffh

; Variable modules
z3ndirs equ 14
z3ndir equ rbios - 0400h
fcps equ 4
fcp equ z3ndir - fcps*80h
rcps equ 16
rcp equ fcp - rcps*80h
iops equ 12
iop equ rcp - iops*80h

; Operating system components
vbios equ iop - 0200h
dos equ vbios - 0e00h
ccp equ dos - 0800h

Fig. 6.
Address equates for the Z3BASE.LIB file corresponding to the memory configuration shown in Fig. 5.

We would be able to implement this configuration without any problem using the techniques we have already seen were it not for the IOP module. The IOP is really an extension of the BIOS, and the problem is that the virtual BIOS has vectors (jump instructions) going to the IOP. When the Z-COM system is built by the loader program ZCLD.COM, the addresses are calculated based on the assumed relative positions of the VBIOS and IOP. Since we are now going to change the spacing between them, we will have to perform some patching on the VBIOS module.

The table of jump vectors in the virtual BIOS is shown in Fig. 7. There are two jumps (warm and cold boot) that are internal to the VBIOS, 7 jumps to addresses in the IOP (6 in one group and one extra), and 10 jumps to the real BIOS. Only the jumps to the IOP have to be changed.

(00)    VBIOS:  JP      VBIOS + 67H     ; coldboot
(03) JP VBIOS + 67H ; warmboot

(06) JP IOP + 0CH ; console status
(09) JP IOP + 0FH ; console input
(0C) JP IOP + 12H ; console out
(0F) JP IOP + 15H ; list out
(12) JP IOP + 18H ; punch out
(15) JP IOP + 1BH ; reader in

(18) JP BIOS + 18H ; home disk
(1B) JP BIOS + 1BH ; select disk
(1E) JP BIOS + 1EH ; set track
(21) JP BIOS + 21H ; set sector
(24) JP BIOS + 24H ; set DMA
(27) JP BIOS + 27H ; read sector
(2A) JP BIOS + 2AH ; write sector

(2D) JP IOP + 1EH ; list status

(30) JP BIOS + 30H ; sector translation

Fig. 7.
Structure of the jump table in the virtual BIOS.

You should begin by making a copy of ZC1.COM, which we generated earlier, giving it the name ZC2.COM. Then determine the absolute address of the IOP in your system. Next, go into ZC2.COM with ZPATCH or with a debugger and change the addresses. For my Televideo 803H system the IOP had been at E000H and is now at D200H. I would look for the seven jumps of the form "C3 ?? E0" and change them to "C3 ?? D2".

We also have to make some changes to the code in the dummy IOP. It has a total of 16 jump instructions, 7 of which refer to the real BIOS and 9 of which refer to internal addresses. The former need not be changed, but, since we are changing the address at which the IOP will execute, we have to change the latter addresses. The source code for the dummy IOP is shown in Fig. 8.

(00)    IOP:    JP      IOP + 3DH       ; status
(03) JP IOP + 3DH ; device select
(06) JP IOP + 3DH ; device name
(09) JP IOP + 3DH ; initialization

(0C) JP BIOS + 06H ; console status
(0F) JP BIOS + 09H ; console input
(12) JP BIOS + 0CH ; console output
(15) JP BIOS + 0FH ; list output
(18) JP BIOS + 12H ; punch output
(15) JP BIOS + 15H ; reader input
(18) JP BIOS + 2DH ; list status

(1B) JP IOP + 3DG ; new I/O routine
(1E) JP IOP + 3DH
(21) JP IOP + 3DH
(24) JP IOP + 3DH
(27) JP IOP + 3DH

(30) DB 'Z3IOPDUMMY ' ; ID string
(3D) XOR A ; set zero value and flag
(3E) RET ; return

Fig. 8.
Source code for the dummy IOP in Z-COM. All the internal routines simply return with the zero flag set, while the substantive functions are vectored off to the real BIOS.

The garbage that appears from address IOP+3FH to the end of the IOP at IOP+5FFH can be filled with zeros to make things look neater. Then the 9 internal vectors have to be modified in the same way those in the BIOS were to point to the new address of the IOP. Finally, the reconfigured IOP has to be moved from its present position at 2800H to its new place in ZC2.COM at 1A00H.

While we are in the debugger doing that, we can also change (1) the file control block to load the command processor image ZC2.CP (and also the 'not found' message) and (2) the multiple command line buffer to run START2. This initial multiple command line must also be moved from its old address at 1D00H to its new position at 2D00H. Make sure that the second byte in the command line buffer points to the page where the real multiple command line buffer will be.

You should then fill all the other buffers with zeros. The CPR, RCP, FCP, and ENV modules can be loaded into the image. Then the initial path should be set up at address 2BF4H, and the wheel byte at address 2BFFH should be set to FFH if you want it on. Finally, if you want an error handler to be loaded when Z-COM is booted, then patch in the error command line (for example, "A0:Z33VERR<0>") at Z3MSG+10H (2B90H in ZC2.COM).

If everything has gone right, and I have not left something out or written something wrong, you should be ready to test it out. Don't forget to put the command processor image files in A15. I know I keep harping on this, but while working on this article, I forgot to do that on one occasion, and there are now a few dents in the table from my fist!

A Minimum System

If you ever meet me and feel like having a little fun at my expense, just casually make some comment like, "Oh, yeah, I hear ZCPR3 has some nice features but that it uses up much too much memory." It is a subject that has become a sore point for me lately and one that is almost guaranteed to provoke me. To be honest, however, the persistance of this false impression is to a large degree the result of poor education on our part. No one ever talks about minimum ZCPR3 systems; we only describe full-blown ones.

It is true that a full-blown ZCPR3 system uses quite a bit of memory. Z-COM takes 1600H = 5.5K bytes of memory out of your TPA, a pretty hefty chunk. I like the features that this big Z-System gives me, and for the kind of work I do, the smaller TPA almost never causes any problem. For me, the benefits far outweigh the cost.

For some people, however, especially those working with voracious memory hogs like database managers and C compilers, this is not the case. (It seems to me that someone like Steve Russell of SLR should write a good, virtual-memory C compiler like his virtual-memory assemblers to prevent this problem.) But the real answer is that a ZCPR3 system does not have to have every feature implemented. As with most things in this world, the 20/80 rule applies: for less than 20% of the cost, you can buy more than 80% of the features. If we eliminate all the variable buffers in the last example, we end up with a system that uses only 5 pages (1.25K) of memory, a very modest amount. The same system installed in the conventional way, with no space required for the virtual BIOS of Z-COM, would take only 0.75K away from the TPA, yet all of the following features would still be there:

  • automatic command search path

  • flexible access to user areas

  • multiple commands on a line

  • aliases

  • shells (including history shell and filer shells)

  • extended command processing (including ARUNZ command generator)

  • powerful error handlers (with command editing)

  • terminal-independent operation (TCAP)

  • flexible program loading (type-3 environment)

  • interprogram communication

The only things missing are named directories and flow control. Some simple flow-control-like features could be implemented even without the FCP. With all the security features and named-directory support removed from the command processor, there is room for many more CPR-resident commands, and the ones that won't fit can be implemented as virtual residents using the new type-3 environment. In summary, a very powerful system can be built with a very small cost in TPA.

To prove this point (and because it will be useful on those rare occasions when I run my database manager), I decided to develop a version of Z-COM that would have only the three pages of core modules. The memory map is shown in Fig. 9. It differs in two fundamental ways from all the other maps we have seen: (1) it is shorter and (2) the real VBIOS and ZRDOS run at different addresses than usual. These differences will require some new techniques, and we will cover them shortly.

System ComponentZC.COM AddressSystem Address

CPR0200 - 09FFCB00 - D1FF
ZRDOS0A00 - 17FFD300 - E0FF
Virtual BIOS1800 - 19FFE100 - E2FF
Shell Stack1A00 - 1A7FE300 - E47F
Z3 Message Buffer1A80 - 1ACFE380 - E4CF
External FCB1AD0 - 1AF3E3D0 - E4F3
Wheel Byte1AFF - 1AFFE3FF - E4FF
Environment Descriptor1B00 - 1B7FE400 - E47F
TCAP1B80 - 1BFFE480 - E4FF
Multiple Command Line1C00 - 1CCFE500 - E5CF
External Stack1CD0 - 1CFFE5D0 - E5FF

Fig. 9.
Addresses of system components in the ZC3.COM file and in the target system for a minimum configuration with no IOP, RCP, FCP, or NDR. The file is 2E00H - 1D00H = 1100H = 4.25K shorter than the other versions.

First we have to make Z3BASE.LIB, the equates for which are shown in Fig. 10. The only subtle change is in the way the VBIOS address is defined. Make sure you do not define it in terms of any of the modules whose addresses have been set to zero to disable them or you will get extremely strange results.


rbios equ 0e600h

; First page under BIOS
z3cl equ rbios - 100h
z3cls equ 203
extstk equ z3cl + 0d0h

; Second page under BIOS
z3env equ rbios - 200h
z3envs equ 2

; Third page under BIOS
shstk equ rbios - 300h
shstks equ 4
shsize equ 32
z3msg equ shstk + 080h
extfcb equ shstk + 0d0h
expath equ shstk + 0f4h
expaths equ 5
z3whl equ shstk + 0ffh

; Variable modules -- all disabled

z3ndirs equ 0
z3ndir equ 0
fcps equ 0
fcp equ 0
rcps equ 0
rcp equ 0
iops equ 0
iop equ 0

; Operating system components
vbios equ shstk - 0200h
dos equ vbios - 0e00h
ccp equ dos - 0800h

Fig. 10.
The Z3BASE.LIB equates for a minimum system that takes only 0.75K for the ZCPR3 buffers and 0.5K for the virtual BIOS required for automatic installation.

The basic procedure for creating this system is very much like what we have done before. We edit the Z3BASE.LIB file and assemble up the CPR and ENV modules (or make the latter by patching). There are no FCP, NDR, or IOP modules to worry about. We set up the path, the wheel, and the error handler; we change the VBIOS file control block to load ZC3.CP for the CPR image file and change the 'not found' message to match; and we put in the ENV and CPR modules.

Now we have to face the new complications that arise because of the change in size of the file. First we will take up the simpler problem - changing the loader code in the first page of the file. It normally copies 2C00H bytes (from 200H to 2E00H) up to the run-time location in memory. Now the image part of the file is only 1B00H bytes long. If you follow through the loader code in a debugger, you should not have too much difficultly figuring out what is going on. The hardest parts to trace through are the places where the code prints out in-line strings. You will typically see a call instruction followed by very strange code. That strange code is the text to be printed. That coding technique is very convenient for the programmer (and I use it all the time), but it makes disassembly and single-stepping in a debugger much more difficult. When you encounter code like this, you have to use the dump ('D') display to locate the null (binary 0) that marks the end of the string. Then you can continue running using the command "G,addr", where 'addr' is the address just after the null.

Anyway, at address 181H you will find the key instruction: LD B.C.,2C00H. This must simply be changed to LD B.C.,1B00H. That's all there is to it - as far as the loader code is concerned. You might wonder why we don't have to change the starting address for the load, since it is not the same as before. The answer is that Z-COM derives it from the initial jump instruction in the CPR image. My first versions of Z33 used a relative jump, and I had to put the absolute jump back after one of my beta-testers pointed out that it would not work with Z-COM.

Now we have to face two much more difficult problems: both the VBIOS and the ZRDOS modules have to be relocated to a new address. How can we possibly do this without source code? Well, if you purchased a separate copy of ZRDOS, you have a file with a name like ZRDINS.COM with which you can create a binary image of ZRDOS that will run at any address and with any ENV address. But what if you don't have it? And what about the VBIOS part? The latter could conceivably be disassembled, since the code is not very long or very complex, but there is a better way, one that makes use of the built-in capabilities of the auto-install package.

If you had two computers with different BIOS entry addresses, you could perform a standard installation of Z-COM on each system. By taking the two ZC.COM files and subtracting them in a debugger, you could derive the relocation map. Now the question is how we can make two ZC.COMs using a single computer.

If you examine the beginning of the code in ZCLD.COM, you will see that ZCLD keys its system generation to the address of the BIOS warmboot vector at address 0001, and that is what we will base our strategy on. We will fool ZCLD into making us two versions of ZC.COM one page apart that we can subtract.

With my standard Z-COM system running on the Televideo 803H, the CPR is at BA00H and the virtual BIOS at D000H. Thus the vector at address 0001 points to D003H. By entering the following commands, we can make the BIOS look as though it were at B900:

POKE B903 C3 03 D0; Set up JP D003H at address B903H
POKE 2 B9; Change bios vector from D003 to B903

Of course, you must use addresses appropriate to your system. Any address below the CPR but above the memory used by ZCLD should work.

With this patch in place, everything will be fine so long as we do not try to run a program that does direct BIOS calls based on the address at 0001. (If we do? - the system will simply go up in flames!) In case you're worried, the next warmboot will delete our patch and restore things to normal.

Fortunately, ZCLD does not mind being fooled this way. All in all, the following sequence of commands will result in two files, ZCB8.COM and ZCB9.COM.


We can then load both files into a debugger using the commands

IZCB8.COM; set file to ZCB8.COM
R; read in at 100H (100H-2DFFH)
IZCB9.COM; set file to ZCB9.COM
R3000; read in at 3100H (3100H-5DFFH)

Now from right in the debugger we can assemble a little program at 3000H to subtract the ZRDOS and VBIOS images in memory block 0A00H-19FFH from the images in memory block 3A00H-49ffH to give us the relocation map we need. Here is how the entry of the assembly code proceeds:

3000 LD DE,3A00 ; set up pointers to two files
3003 LD HL,A00
3006 LD B.C.,1000 ; number of bytes to subtract
3009 LD A,(DE) ; get byte from higher image
300A SUB (HL) ; subtract byte from lower image
300B LD (DE),A ; put result (0 or 1) back
300C INC HL ; increment the pointers
300E DEC B.C. ; check count
300F LD A,B
3010 OR C
3011 JR NZ,3009 ; loop through 1000H bytes

Enter the command "G3000,3013" to run this routine with a breakpoint at 3013. If you look at memory from 3A00H TO 49FFH you will see a pattern of 1s and 0s. This is the relocation map. It indicates which bytes must be changed to shift the execution address of the code.

Now we have to relocate the ZRDOS to run at D300H and the VBIOS to run at E100H instead of the values 9400H and A200H as they are in the image in ZCB8.COM (determined by inspection with a debugger). Thus we have to add an offset of 3FH (E1-A2) to all bytes where the relocation map has a one in it. So we assemble up another little program at 3000H as follows:

3000 LD HL,3A00 ; point to relocation byte
3003 LD DE,1A00 ; point to ZRDOS/VBIOS we are creating
3006 LD B.C.,1000 ; number of bytes to cover
3009 LD A,(DE) ; get current value of byte
300A BIT 0,(HL) ; see if relocation map has a 1 in it
300C JR Z,3011 ; if not, skip the offset addition
300E ADD A,3F ; add the offset
3010 LD (DE),A ; put back the corrected byte
3011 INC HL ; increment the pointers
3012 INC DE
3013 DEC B.C. ; check the count
3014 LD A,B
3015 OR C
3016 JR NZ,3009 ; loop through 1000h bytes

Run this program with "G3000,3018" and you will have a ZRDOS and VBIOS that will run at the required address. Put them in a safe place temporarily while you load in ZC3.COM and then move them into the proper place in the image. This completes the generation of the minimum system except for one step described a little later.

Although all that work with the debugger took quite a lot of space to describe, it is really not that difficult to do. I particularly wanted to show it to you because it is a technique that is useful in many other circumstances as well (like using MOVCPM to relocate your system in one-page rather than 1K increments). Now, however, I will show you a much easier way to accomplish the same thing in the case of Z-COM.

If you load ZCLD.COM into the debugger and trace through the code with the 'L' command, you will see how it works. Before one gets to the main code, there are two places where checks are made. One is to see if the command was invoked as "ZCLD //" to request the built-in help screen. The second test is to make sure that you are not trying to run ZCLD from inside a running Z-COM system. We could drop out of Z-COM after making the changes I am about to describe, but why put up with that trouble. The code is testing for the presence of the letter 'Z' in the copyright notice inside the VBIOS at offset 42H. At address 249H there is a "JP NZ,29D" instruction that takes one to the system-building code if no 'Z' is detected. If we change this to an unconditional jump, we will effectively disable the test.

At 29DH, the code loads the address of the BIOS with the instruction "LD HL,(1)". We know that we want the system to be generated at an address 1100H higher than it would be normally because of all the modules we removed. Since the normal BIOS address was E600H, we want ZCLD to see a value of F700H. To accomplish this, we assemble in the instruction "LD HL,F700", a direct load instead of an indirect load to HL. We can then save away this modified version as ZCLD1.COM. When we run it, it very nicely produces a system with a ZRDOS and VBIOS at just the addresses we wanted.

There is one other change I have forgotten to mention. The jumps in the VBIOS that go to the IOP have to be replaced by jumps to the real BIOS at the very same offset as in the VBIOS. Thus the jump at offset 06, which was "JP IOP+0CH", would become "JP BIOS+06H".

Switching from One Version to Another

What if we are running one of our versions of Z-COM and want to change to another one. We can always run the ZCX command to get back to CP/M and then load the new Z-COM system. This seems unnecessarily tedious. Why can't we just run ZC2.COM from the ZC1 system? The answer is that Joe Wright did not want us to do this. Of course, he was thinking in terms of only single configurations, and then there would be no reason to run ZC.COM when Z-COM was already running. So, he put in some code to protect us from this mistake.

Now we want to do that very thing! How can we disable the safety code? Very simply. It is based on the same check we described with the ZCLD program. It looks for a 'Z' at byte 42H of the BIOS or VBIOS. If it is a VBIOS, the 'Z' will be there; if it is a real BIOS, it would be very unlikely that a 'Z' would be there. We could go into the code itself as I described with ZCLD and disable the test. Alternatively, we could do something much simpler (and reversible): go into the VBIOS and remove the 'Z' by changing it to something else. A third possibility, one I implemented for the fun of it, is to write a little utility program that finds that byte of the BIOS. If it is not a 'Z', it simply returns; if it is a 'Z', it sets it to zero. Then the next ZCx can load over it. If the utility is not run, then the system cannot be overlaid.

I will suggest one last modification to Z-COM to enhance it even further. That is a change to allow alternative versions of Z-COM to be loaded without interrupting the flow of commands. Thus your memory-hungry C compiler would be invoked with an alias the reads something like:

RUNC zc3;c $*;zc2

This alias would load the minimum Z-COM system to give the C compiler the most room to work, and then once the compilation was finished it would reload the full Z-COM configuration. I did this with my Z3-DOT-COM/Z-COM combination. The I/O Recorder IOP was invoked using an alias. The alias would first check to see which Z-System was running (that is what I put the $M parameter into ARUNZ for). If it was Z3-DOT-COM, then alias would load Z-COM before proceeding to load the IOP. I will not go through the method for accomplishing this, but I will give you a hint. You have to change the loader code in the first page of ZCx.COM so that the multiple command line buffer and some other system information is not overwritten by the load.

Plans for Next Time

Whew! That was quite a session of heavy technical material. I don't know yet exactly what I will do next time, but I certainly I will find a less technical subject. You need a rest, and I need a rest. If you have any questions or suggestions, please write or call. See the ad for Sage Microsystems East for the address and phone numbers.

[This article was originally published in issue 29 of The Computer Journal, P.O. Box 12, South Plainfield, NJ 07080-0012 and is reproduced with the permission of the author and the publisher. Further reproduction for non-commercial purposes is authorized. This copyright notice must be retained. (c) Copyright 1987, 1991 Socrates Press and respective authors]