Im Magazin „Microcomputing" wurde im Juni 1980 der folgende Artikel abgedruckt.
Der Artikel beschreibt eine Methode, auch bei einer 8080 CPU relative Sprünge wie bei der Z80 durchzuführen 1.

Position-Independent Code for the 8080


This code-relocation method needs no special loader or systems software changes.

Lance E. Rose
COMLABS, Inc.
PO Box 1082
Kalispell MT 59901

Anyone who's programmed an 8080-based micro in other than BASIC will probably agree with me that a serious shortcoming is the lack of relative addressing. Even the rival 6800 has one type of indexed addressing. Many of us have a lot of time and money invested in 8080 systems and aren't about to start over again, at least not until something substantially better comes along – my next machine will undoubtedly be a 16-bit processor.
Schemes have been proposed in Microcomputing and elsewhere for 8080 code relocation. Implicit in most of them is the need for a special loader that can recognize certain symbols embedded in the code to define program and data areas and make the necessary address changes as it loads.
The method proposed here requires no such loader and, in addition, requires no changes to system software. It does require one extra byte for every 3-byte instruction referencing a relative address and also requires 24 bytes of available memory starting at the RST 1 location. Other RST iocations may be used depending on whether they are required by your system software or are free for use.
CP/M, for example, uses the RST location and reserves RST 6 and RST 7, but otherwise makes no demands on RST 1-5. The North Star DOS, in its standard form, resides at 2000H and runs most other software at 2A00H and up. You need some memory at for this method, which otherwise doesn't need to be there for North Star systems.

Principles

In the more advanced micros and minis, relative program addressing is usually based on the location of the program counter. Jumps, calls and other references to memory locations are set up as relative offsets to the program counter's present position. Other types of addressing such as index addressing, where a location is defined as an offset from a particular register (not necessarily the program counter), are also used and accomplish much the same result.
In the 8080, however, it's tricky enough just to find out what the value of the program counter is, let alone make use of it in an addressing scheme. Two basic requirements exist: there must be a way of determining at any point in the program what the value of the program counter is; the proper offset must be added to it to obtain an absolute address that can be used in any 8080 3-byte instruction.
The first of these is satisfied by setting up the RST 1 location to get the program counter from the stack, where it is automatically pushed when the RST instruction is executed, and placing it in the H,L registers. The second is accomplished by using this value to fetch a relative address that is initially stored as the argument to the 3-byte instruction and add it to the PC to produce the absolute address required. Finally, this absolute address is stored in place of the relative address, and the RST instruction is replaced by a NOP so that the modification routine only has to run once for each relative jump or call. Thus the only decrease in execution speed is due to the miscellaneous NOPs left in memory by the modifier program.
The NOP is a fast instruction, and unless it is located in a loop where there are very few other instructions, it will probably not be noticed.
A side benefit of this method is that the modifier leaves behind it a trail of NOPs replacing RSTs, thereby allowing you to determine whether a particular part of the program has been executed yet. This doesn't take the place of a fancy debugger, but it can come in handy by flagging the areas of the program that the PC has "seen."

Programs

Two programs are required to make this sytem work. The first is the code-modifier that resides at RST 1 and takes up 24 bytes of memory. The second is a short program that you must execute before any other program when you turn the system on. You can do this by placing the program at the beginning of the initializing routine called by most systems first thing after power-up. Another way is to insert it as part of the code of a main program preceding any instructions with relative-address operands. The first program is implicitly contained within the second as data, so there is actually only one program to assemble.
Inspection of the modifier program shows that it is relatively simple. All it does is retrieve the PC and change it accordingly to alter the operand address of the instruction. In doing so, it preserves all flags and registers so that there is no need to do this inline. It also puts the NOP in place of the RST to assure that the process will not occur again in all succeeding passes through that part of the program. This reduces execution time.
The initializing program simply places the modifier program into RST 1 (or other RST location) and then continues with the rest of the program. Note that this program can locate anywhere in memory and still run properly.
To make this whole procedure work, you insert an RST 1 instruction before any relative 3-byte instruction. In addition, when you write the program (presumably with an assembler), you must express all operands of the 3-byte instructions (label-$) so that they express the location of the label relative to the beginning of the instruction. (Caution: Some assemblers assign the value of the beginning of the next instruction to the $ symbol, so be careful! If yours does this you'll have to change the modifier slightly to account for it, but the change is easy.) The CP/M assembler is "well-behaved" and should function normally with the 8080.
Note that this method does not destroy the ability to use absolute addressing. All you have to do is leave out the RST 1, and the 3-byte instruction is treated as absolute. This makes it possible to mix relative and absolute code together. More effort is required to include the RST 1 where needed, and make sure the location counter is subtracted from the labels. A little practice with this can go a long way.
Since normal system software can be left entirely alone, you can experiment with your assembler and find the area that gets the value for $. Then patch it slightly to recognize any symbols or labels starting with a special character as relative, and automatically subtract the location counter from them.

An Application

Consider the input routine for a console device with a wait loop. I/O routines are routines you may want to run in different parts of memory and not want to reassemble each time. A normal routine might look like:
INPUT:  IN      STAT
        ANI     FLAG
        JZ      INPUT
        IN      DATA
        ANI     MASK
        RET
The reference to INPUT must be absolute if the routine is to execute properly. Write this in position-independent code, and it becomes:
INPUT:  IN      STAT
        ANI     FLAG
        RST     1	
        JZ      INPUT-$
        IN      DATA
        ANI     MASK
        RET
The only changes required are the RST 1 immediately preceding the JZ instruction and the change of INPUT to INPUT-$. After the loop is executed once and the modifier program is run, the operand of the JZ will change to the actual value of INPUT and the RST 1 will be a NOP. This prevents the modifier program from having to run each time the loop is run and also says that the loop has been executed at least once (from the presence of the NOP).
I tried this procedure with the CP/M I/O routines, and it worked without a hitch. Preceding user programs with these changes allows them to be executed anywhere in memory or to be appended as object code rather than source code, neglecting complications that occur with cross-references between programs.
This short program doesn't turn the 8080 into, say, a PDP-11, but I think it's an improvement over what Intel gave us to start with.
;
;       Program for initializing modifier
;
INIT:   LXI     H,0E9E1H        ;Put POP H, PCHL at RST 1
        SHLD    0008H
        RST     1               ;Get PC in H,L
        LXI     D,PUT-$	        ;Add offset to get PUT
        DAD     D
        MOV     B,H             ;Move PUT to B,C
        MOV     C,L
        RST     1               ;Get PC in H,L
        LXI     D,RELRT-$       ;Add offset to get RELRT
        DAD     D
        LXI     D,0008H         ;Set D,E to RST 1
        MVI     A,18H           ;Set A to byte count
PUT:    PUSH    B               ;Transfer to RST 1
        PUSH    PSW
        MOV     A,M
        STAX    D
        INX     H
        INX     D
        POP     PSW
        DCR     A
        RNZ
        POP     B               ;Clean up stack
        PCHL                    ;Continue with program
                                ;(Can be changed to RET if
                                ;initialization complete)
;
RELRT:  DB      0E3H,2BH,36H,00H,23H,23H,0D5H,5EH
        DB      23H,56H,0F5H,0EBH,19H,0EBH,1BH,1BH
        DB      72H,2BH,73H,0F1H,0D1H,2BH,0E3H,0C9H
Program for initializing monitor.

[8080 Quelle und Z80 Quelle]
;
;       Program for modifying addresses 
;
RST1:   XTHL            ;Save H,L and get next PC
        DCX     H       ;Change RST 1 to NOP.
        MVI     M,00H
        INX     H
        INX     H
        PUSH    D       ;Save D,E.
        MOV     E,M     ;Get relative addr. in D, E.
        INX     H
        MOV     D,M
        PUSH    PSW     ;Save condition codes.
        XCHG            ;Add offset for abs. addr.
        DAD     D
        XCHG
        DCX     D       ;Set to beginning of instr
        DCX     D
        MOV     M,D     ;Store absolute addr.
        DCX     H
        MOV     M,E
        POP     PSW     ;Restore condition codes.
        POP     D       ;Restore D,E.
        DCX     H       ;Set H,L to start of instr
        XTHL            ;Restore H,L
        RET             ;Execute.
Program for modifying addresses
[8080 Quelle und Z80 Quelle]
[Hier ein einfaches Beispiel als 8080 Quelle und Z80 Quelle. Das Z80-Programm umgeht den Nachteil, dass auch RST 2 und RST 3 belegt werden. Statt des kompletten Codes wir ein Sprung auf den Code in RST 1 eingetragen und das eigentliche Programm dahinter verschoben]

1. Das betrifft aber nicht nur Sprünge sondern jede 16 Bit Instruktion, wie im Artikel beschrieben.
Zwei grundlegende Artikel zum Thema Verschieben von Programmen finden sich in A Machine Code Relocator for the 8080 und Relocating 8080 System Software.
Der Nachteil in dem hier beschriebenem Verfahren ist, dass durch die Länge der Routine RST 1 neben dem RST 1 auch RST 2 und RST 3 belegt werden. Der Nachteil lässt sich aber umgehen, wie das Z80-Beispiel oben zeigt. CP/M Plus verfügt bekanntlich über das PRL-Format (Page ReLocatable), so dass dieses Problem eleganter gelöst werden kann.

Eingescanned von Werner Cirsovius
Januar 2014
© Microcomputing (kilobaud)