Relocatable code

When I think about relocatable things, the image of a container comes to my mind. To me, a container is the good definition of relocatable thing: it works on ships, trains and trucks in exactly the same way.
In this post, I talk about the challenges I had to achieve truly relocatable code and data in memory. If you wonder why achieving truly relocatable code is so important, then I will tell you that this is the backbone of any computer system. The operative system (OS) is the general manager of all resources (hardware and software) and the applications are the tools provided to the users for many kinds of tasks. One never knows how much RAM is installed on a computer, or how many RAM is free at any given time. The operative system manages and solves this kind of problem hence the operative system must be free to load and start the application at any RAM address. Additionally, during the development of any application, one uses libraries and already developed procedures, then it is important for all these procedures to work at any position in the address space of the final application. In this last case, it is about relocatable procedures that can be reused and repackaged based on the needs of the main program under development. My focus is on this very last case.
I want to talk here about the very last details that I fixed in order to have a truly relocatable code. The best way for me to explain it is to use an example and the best way to learn is to start with a bad example (mistakes have much more to teach).

The problem

In "HAND_LNK_DBG (the bad one)" I show an example about how I used to code a procedure that was intended to be a full program by its own in contrast to a service procedure. I bring here the protocol of the compilation job done with DEBUG.EXE1 because this lets me use the addresses as if those were numbered lines that I can mention when I want to refer to them.

hand_lnk_dbg.npp (the bad one)
-f 7c00 7fff 3f -a 7c00 137B:7C00 ;234567890123456789012345678901234567890123456789012345678901234567890123456789 137B:7C00 ;-------10--------20--------30--------40--------50--------60--------70-------79 137B:7C00 ;------------------------------------------------------------------------------ 137B:7C00 ; HAND_LNK: HAND LiNKing 137B:7C00 ; 137B:7C00 ; Copyright (C) 2020 - Michele Musci 137B:7C00 ; Distributed under the GNU Affero General Public License version 3. 137B:7C00 ; See https://www.gnu.org/licenses/agpl-3.0.txt 137B:7C00 ; 137B:7C00 ; This procedure builds a test environment for different service procedures 137B:7C00 ; and shows a method to link procedures together in an incremental growing 137B:7C00 ; software. 137B:7C00 ; 137B:7C00 ; INPUT: --- 137B:7C00 ; OUTPUT: --- 137B:7C00 ; THIS CALLS: OUT_NBL, NBL_ASC, SHOW_STR, CREG_STR 137B:7C00 ; 137B:7C00 ; Build it with command: 137B:7C00 ; debug < out_nbl.npp > out_nbl_dbg.npp 137B:7C00 ;------------------------------------------------------------------------------ 137B:7C00 call 7c60 ; Direct call into test code that can be executed all 137B:7C03 ; at once with the command "p" of DEBUG.EXE 137B:7C03 ; 137B:7C03 ret ; This is necessary to test the true reallocation 137B:7C04 ; of this code with the use of a "p" command in DEBUG.EXE 137B:7C04 ; taking care that daisy chain of calls returns fully all 137B:7C04 ; the way back to the origin. 137B:7C04 ; 137B:7C04 ;------------------------------------------------------------------------------ 137B:7C04 ; Load service functions into memory 137B:7C04 ;------------------------------------------------------------------------------ 137B:7C04 - -n OUT_NBL.BIN -l 7c10 - -n NBL_ASC.BIN -l 7c30 - -n SHOW_STR.BIN -l 7c40 - -n CREG_STR.BIN -l 7c50 - - -a 7c60 137B:7C60 ;------------------------------------------------------------------------------ 137B:7C60 ; CODE 137B:7C60 ;------------------------------------------------------------------------------ 137B:7C60 ; You may perform here some initialization job 137B:7C60 ; and stack preparation and finally jump over 137B:7C60 ; DATA and LOCAL FUNCTIONS to MAIN procedure. 137B:7C60 jmp 7c7a ; Jump Start: ---> 137B:7C62 ; 137B:7C62 ; 137B:7C62 ;------------------------------------------------------------------------------ 137B:7C62 ; DATA (if any) 137B:7C62 ;------------------------------------------------------------------------------ 137B:7C62 db 'Register BX = 0x' 137B:7C72 db '????.', 0d, 0a, 00 137B:7C7A ; 137B:7C7A ; 137B:7C7A ;------------------------------------------------------------------------------ 137B:7C7A ; LOCAL FUNCTIONS (if any) 137B:7C7A ;------------------------------------------------------------------------------ 137B:7C7A ; 137B:7C7A ; 137B:7C7A ;------------------------------------------------------------------------------ 137B:7C7A ; 137B:7C7A ; START of MAIN procedure 137B:7C7A ; 137B:7C7A ;------------------------------------------------------------------------------ 137B:7C7A ; 137B:7C7A ; Start: <--- 137B:7C7A ; 137B:7C7A mov bx, 9abc ; This can be any value. It gets converted into ASCII. 137B:7C7D mov cx, 4 ; We want to convert all 4 nibbles. 137B:7C80 mov di, 7c72 ; This points the first "?" in string "Register BX = 0x????." 137B:7C83 call 7c50 ; Call CREG_STR 137B:7C86 ; 137B:7C86 ; 137B:7C86 ;---------------------------------------- 137B:7C86 ; show string on screen. 137B:7C86 ;---------------------------------------- 137B:7C86 mov si, 7c62 ; This points the beginning of string "Register BX = 0x????." 137B:7C89 call 7c40 ; call SHOW_STR 137B:7C8C ; 137B:7C8C ; 137B:7C8C ;------------------------------------------------------------------------------ 137B:7C8C ; END of MAIN procedure. 137B:7C8C ;------------------------------------------------------------------------------ 137B:7C8C ret 137B:7C8D ; 137B:7C8D ; 137B:7C8D - -d 7c00 7c8f 137B:7C00 E8 5D 00 C3 3F 3F 3F 3F-3F 3F 3F 3F 3F 3F 3F 3F .]..???????????? 137B:7C10 89 D8 89 CA 49 D1 E1 D1-E1 D3 E8 25 0F 00 89 D1 ....I......%.... 137B:7C20 C3 3F 3F 3F 3F 3F 3F 3F-3F 3F 3F 3F 3F 3F 3F 3F .??????????????? 137B:7C30 24 0F 04 30 3C 39 76 02-04 07 C3 3F 3F 3F 3F 3F $..0<9v....????? 137B:7C40 B4 0E BB 07 00 AC 3C 00-74 04 CD 10 EB F7 C3 3F ......<.t......? 137B:7C50 E8 BD FF E8 DA FF AA E2-F7 C3 3F 3F 3F 3F 3F 3F ..........?????? 137B:7C60 EB 18 52 65 67 69 73 74-65 72 20 42 58 20 3D 20 ..Register BX = 137B:7C70 30 78 3F 3F 3F 3F 2E 0D-0A 00 BB BC 9A B9 04 00 0x????.......... 137B:7C80 BF 72 7C E8 CA FF BE 62-7C E8 B4 FF C3 3F 3F 3F .r|....b|....??? - -u 7c60 7c62 137B:7C60 EB18 JMP 7C7A 137B:7C62 52 PUSH DX - -u 7c7a 7c8d 137B:7C7A BBBC9A MOV BX,9ABC 137B:7C7D B90400 MOV CX,0004 137B:7C80 BF727C MOV DI,7C72 137B:7C83 E8CAFF CALL 7C50 137B:7C86 BE627C MOV SI,7C62 137B:7C89 E8B4FF CALL 7C40 137B:7C8C C3 RET 137B:7C8D 3F AAS - - -rbx BX 0000 :0 -rcx CX 000A :8d -r AX=0000 BX=0000 CX=008D DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=137B ES=137B SS=137B CS=137B IP=0100 NV UP EI PL NZ NA PO NC 137B:0100 0000 ADD [BX+SI],AL DS:0000=CD - -n HAND_LNK.BIN -w 7c00 Writing 0008D bytes -q

Notice that at address SEG:7C00 I did write code this time because this was the main procedure. All service procedures were comprised (statically linked in the final code) and the final binary was created starting from 0x7C00 until the end. According to all what I said in the post "Hand made linking", this should be a fully relocatable code but well it wasn't due to the small detail that I am going to explain now. Before it, let me show the test I did with DEBUG.EXE.

Fig. A - Debugging HAND_LNK (the bad one)
Fig. A - Debugging HAND_LNK (the bad one)

In Fig. A, you see that the code worked when loaded at the same memory position where it was developed. The code HAND_LNK is in red and contains inside itself the string (in yellow) and all other service procedures (in green). You can observe how the yellow string changed in memory before and after code execution (white boxes). Then I loaded the HAND_LNK.BIN file at another location in RAM (SEG:0300) and I tried to run it, but it failed. Do you have any idea why was it so?

Fig. B - Debugging HAND_LNK (the bad one)
Fig. B - Debugging HAND_LNK (the bad one)

Well, you can find the problem in "HAND_LNK_DBG (the bad one)" at addresses SEG:7C80 and SEG:7C86. Did you spot it? Yes, once again it was the same problem as in Fig. F of the post "AP-PROBE": the pointing of strings needs absolute addressing mode and this made the code not fully relocatable. No matter where the code was loaded in RAM, all jumps and calls kept working but strings were still expected to be found at the same fixed absolute place.

Fig. C - Debugging HAND_LNK (the bad one)
Fig. C - Debugging HAND_LNK (the bad one)

In Fig. C, you see that I went looking at the absolute memory address where the original string was placed and I saw that the code had modified the RAM content indeed.

So is there really nothing that one can do to realize a truly relocatable software both in its code and data component? Are strings really non-relocatable? Indeed the code expects to find the string always at fixed offset inside a segment which is undefined and, as such still relocatable.

Let me explain it, be patient. Intel 8086 implements relocatable software in different ways. There is a strategy in place to realize relocatable code, and a different one to implement relocatable data. The strategy for the relocatable code is the use of relative addresses for jumps, loops and calls so that the total code can be moved anywhere and still work. There still exist instructions to perform absolute jumps and calls if required by the programmer and, I like to group things in two categories: use of absolute or relative addresses.

  • A - Absolute address far (this is no more relocatable per se2).
  • R1 - Relative address short (this is relocatable).
  • R2 - Relative address near (this is still relocatable).

The first addressing mode is what I called "A" (for Absolute) and it let us change the IP and CS all together at the same time.

The last two, R1 and R2, are identical except for the fact that, in case of R1, the relative address of the jump is coded using one byte, which is interpreted as a signed integer so it can cover a span from -128 to +127 bytes from the current location of the instruction pointer (IP), and with two bytes in case of R2. In both cases, the code segment remains unchanged and the code jumps within the same exact segment.
For the relative addressing mode of code, it is very important to remember that the IP register can flip around during a relative jump, call or loop. I think of a segment as a circle rather than a line of addresses, where the first and the last address (SEG:0000 and SEG:FFFF) are glued just one after the other as I am going to demonstrate now with a short experiment.

Fig. D - IP flips around with a backwards call
Fig. D - IP flips around with a backwards call

In Fig. D, I placed at address SEG:0600 a backward call, then I copied it (with the command m) to address SEG:0100. 0x E8 FD FA means push IP on the stack (0x E8 = CALL) and subtract 1283 bytes from IP (0x FA FD = -1283 in decimal). If executed from address SEG:0100, the backwards call would just go before the address SEG:0000, which means restart the segment from SEG:FFFF and keep going backwards from thereon.
When I executed just this very one instruction I saw that no exception was thrown and the CPU executed it just fine, with a flip around the 16bit boundary of the register IP. Finally and in the most astonishing way, if you create a truly relocatable code and put it halfway across the 16bit boundary of a segment, this is still working fine due to the fact that IP can flip back and forward across the 16bit boundary without any problem (I show this at the end of the post).
You should stop now for a moment and think about this just to realize that the CPU doesn't just "think" the way we "think". The CPU is much closer to a toothed wheel than you may imagine. If you turn the wheel right, all other wheels turn accordingly. No thinking in between. Just mechanics as if it was made of nothing else than toothed wheels.
In conclusion, the code part of a procedure is not just relocatable anywhere in RAM but it is relocatable anywhere within the CS segment and this is a huge difference (as we are going to see immediately).

The strategy used to implement relocatable data part of a procedure is the SEG:OFF addressing mode. In fact, the offset is a relative address from the start of the segment. In the intention of the 8086 architecture, one should use the segment DS and ES for data (but thanks to segment prefix overwrite instructions, one can use also CS and SS for data if needed). When you access data such as strings it occurs relative to the DS segment. In practice, a programmer should implement strict separation of code (all in CS) and data (all in DS and or ES). In this way, the segments can be placed anywhere in RAM and the code will work perfectly because the programmer uses the offset part for the address meanwhile the loader is free to decide where in RAM to allocate all segments.
In conclusion, the data part of a procedure is not relocatable anywhere within the segment, but just relocatable somewhere in RAM3 thanks to the reallocation of the data segments (DS, ES).

So far so good with the description of the problem, but the solution to it will not be so immediate to implement. I can think about a working solution to this problem but I am not fully convinced from a general perspective.
A general solution should enforce the spirit of segmentation of the 8086 CPU. In other terms, I should develop a loader that:

  1. reads a file,
  2. can distinguish between bytes of code and bytes of data,
  3. finds the spots in RAM to place the different segments and finally
  4. loads each portion in the belonging segments in RAM.

At the same time, I should develop a program to create such a "loadable" file starting from a text file containing just mnemonics and symbolic labels for jumps and calls.
At a second glance, I would realize that I cannot do the complete conversion from the text file to the loadable file (let us call it executable file) just with one program within one stage, but I need two different stages and two different programs for each stage in order to create an executable file starting from a text file containing just mnemonics and symbolic labels. But why this has to be so complicated?
Suppose that I have two different procedures that together perform the total task of the final program I want to realize. For each procedure I can distinguish among code part and data part, so let me call the procedures "PA" and "PB" then I end having four logical pieces: "PA_CODE", "PA_DATA", "PB_CODE" and "PB_DATA". When I write the procedures "PA" and "PB" I don't separate logically the two components (DATA and CODE) because those components belong together but at the end, I need a process that can find, takes and repackages "PA_CODE" plus "PB_CODE" all in the code segment part of the executable file, and "PA_DATA" plus "PB_DATA" all in the data segment part of the same executable file. The best way to achieve it is to have two stages.
In the very first stage, a software (let me call it assembler) takes a text file as input and not only it converts mnemonics in opcodes, but also it performs a separation between the CODE-Part from the DATA-part and saves them in a clear way into an intermediate file (let me call it object-file) together with all information concerning jumps and references pointed by the CODE-Part into the DATA-Part within the very same procedure (internal reference or symbol) and outside it in case the procedure PA calls the procedure PB (external reference or symbol).
In the second stage, another software (let me call it linker) takes the intermediate files (object-files) and creates a bigger package with them. It takes and groups all the CODE-Parts together and separates them from all the DATA-parts that, once again, are grouped together. Once all groups are re-packaged in this way, it fixes all references (or symbols) and produces the final file that can be used by the loader program (let me call this final file "executable" file or "loadable" file). With all this job done, I have finally a code that is truly relocatable also for the DATA-part of the software which now is totally grouped within a single segment so that the SEG:OFF strategy of the CPU can really work.

You may think while reading, as well as I thought when I was writing, that I described the reason why Assembler-Linker-Loader works together in the very way they currently work, and that the portable executable file format (.EXE) is the general valid solution for it.
Personally, I am more than happy to be able to see and understand the real reason why Assembler, Loader, Linker, .EXE-File and Operative Systems they all work together in this way, but honestly, I cannot solve the problem in the right and elegant way as they do. I have to think of a workaround that works at my skill level.

My solution

When I write code with DEBUG.EXE, I see addresses that increase as I scroll down the lines of code with the lower addresses towards the top of the file and the higher addresses towards the bottom of the file. In Fig. E I pointed the arrow going from top to bottom in order to reflect the same reading experience when I write code in DEBUG.EXE4.

Fig. E - adresses in DEBUG and REAL addresses
Fig. E - adresses in DEBUG and REAL addresses

When I write code in DEBUG.EXE I know which is the address of the string relative to the axis used by DEBUG.EXE. I call it SIDebug because DS:SI is the suggested way of pointing a source string and this is relative to DEBUG.EXE. When the code runs it has a different axis of addresses in memory. It has then the REAL axis of addresses. I call the value that SI assumes when the program runs SIReal. In the same way, I can mark a different position in the procedure, this time however, I mark the point in the code using the register IP. I call IPDebug the value of the instruction pointer register that marks the code while I am writing within DEBUG.EXE and IPReal the value of IP register that marks the code in the REAL address axis when the program runs. SIDebug and IPDebug are known to me at the moment I write code. I can get IPReal while the code runs so I can calculate SIReal at runtime according to the equation in Fig. F since the distance or offset between IPReal and SIReal is the same as the offset between IPDebug and SIDebug.

Fig. F - calculation of the correction factor
Fig. F - calculation of the correction factor

With this new technique, I can write code and read the addresses directly as they are in DEBUG.EXE, calculate the correction factor at runtime and apply it when necessary. You can see here the new version of "HAND_LNK_DBG (the good one)" meanwhile you can find the file "hand_lnk.npp" in the DOWNLOAD AREA.

hand_lnk_dbg.npp (the good one)
-f 7c00 7fff 3f -a 7c00 137B:7C00 ;234567890123456789012345678901234567890123456789012345678901234567890123456789 137B:7C00 ;-------10--------20--------30--------40--------50--------60--------70-------79 137B:7C00 ;------------------------------------------------------------------------------ 137B:7C00 ; HAND_LNK: HAND LiNKing 137B:7C00 ; 137B:7C00 ; Copyright (C) 2020 - Michele Musci 137B:7C00 ; Distributed under the GNU Affero General Public License version 3. 137B:7C00 ; See https://www.gnu.org/licenses/agpl-3.0.txt 137B:7C00 ; 137B:7C00 ; This procedure builds a test environment for different service procedures 137B:7C00 ; and shows a method to link procedures together in an incremental growing 137B:7C00 ; software. 137B:7C00 ; 137B:7C00 ; INPUT: --- 137B:7C00 ; OUTPUT: --- 137B:7C00 ; THIS CALLS: OUT_NBL, NBL_ASC, SHOW_STR, CREG_STR 137B:7C00 ; 137B:7C00 ; Build it with command: 137B:7C00 ; debug < out_nbl.npp > out_nbl_dbg.npp 137B:7C00 ;------------------------------------------------------------------------------ 137B:7C00 call 7c60 ; Direct call into test code that can be executed all 137B:7C03 ; at once with the command "p" of DEBUG.EXE 137B:7C03 ; 137B:7C03 ret ; This is necessary to test the true reallocation 137B:7C04 ; of this code with the use of a "p" command in DEBUG.EXE 137B:7C04 ; taking care that daisy chain of calls returns fully all 137B:7C04 ; the way back to the origin. 137B:7C04 ; 137B:7C04 ;------------------------------------------------------------------------------ 137B:7C04 ; Load service functions into memory 137B:7C04 ;------------------------------------------------------------------------------ 137B:7C04 - -n OUT_NBL.BIN -l 7c10 - -n NBL_ASC.BIN -l 7c30 - -n SHOW_STR.BIN -l 7c40 - -n CREG_STR.BIN -l 7c50 - - -a 7c60 137B:7C60 ;------------------------------------------------------------------------------ 137B:7C60 ; CODE 137B:7C60 ;------------------------------------------------------------------------------ 137B:7C60 ; You may perform here some initialization job 137B:7C60 ; and stack preparation and finally jump over 137B:7C60 ; DATA and LOCAL FUNCTIONS to MAIN procedure. 137B:7C60 ; 137B:7C60 call 7c63 ; push IP with call myself: ---+ 137B:7C63 ; | 137B:7C63 ; myself: <-------------------------------------------------+ 137B:7C63 ; 137B:7C63 ; [BP + 02] -> return address of caller procedure. 137B:7C63 mov bp, sp ; [BP + 00] -> IP_real 137B:7C65 sub word ptr [bp], 7c63 ; [BP + 00] -> Corr_Factor = (IP_real - IP_debug) 137B:7C6A jmp 7c84 ; Jump Start: ---> 137B:7C6C ; 137B:7C6C ;------------------------------------------------------------------------------ 137B:7C6C ; DATA (if any) 137B:7C6C ;------------------------------------------------------------------------------ 137B:7C6C db 'Register BX = 0x' 137B:7C7C db '????.', 0d, 0a, 00 137B:7C84 ; 137B:7C84 ; 137B:7C84 ;------------------------------------------------------------------------------ 137B:7C84 ; LOCAL FUNCTIONS (if any) 137B:7C84 ;------------------------------------------------------------------------------ 137B:7C84 ; 137B:7C84 ; 137B:7C84 ;------------------------------------------------------------------------------ 137B:7C84 ; 137B:7C84 ; START of MAIN procedure 137B:7C84 ; 137B:7C84 ;------------------------------------------------------------------------------ 137B:7C84 ; 137B:7C84 ; Start: <--- 137B:7C84 ; 137B:7C84 mov bx, 9abc ; This can be any value. 137B:7C87 ; It gets converted into ASCII. 137B:7C87 mov cx, 4 ; We want to convert all 4 nibbles. 137B:7C8A mov di, 7c7c ; This points the first "?" in string 137B:7C8D ; "Register BX = 0x????." 137B:7C8D add di, [bp] ; Address conversion from debug to real. 137B:7C90 call 7c50 ; Call CREG_STR 137B:7C93 ; 137B:7C93 ; 137B:7C93 ;---------------------------------------- 137B:7C93 ; show string on screen. 137B:7C93 ;---------------------------------------- 137B:7C93 mov si, 7c6c ; This points the beginning of string "Registe..." 137B:7C96 add si, [bp] ; Address conversion from debug to real. 137B:7C99 call 7c40 ; call SHOW_STR 137B:7C9C ; 137B:7C9C ; 137B:7C9C ;------------------------------------------------------------------------------ 137B:7C9C ; END of MAIN procedure. 137B:7C9C ;------------------------------------------------------------------------------ 137B:7C9C pop ax ; Just remove from stack the address correction 137B:7C9D ; factor = (IP_real - IP_debug) 137B:7C9D ret 137B:7C9E ; 137B:7C9E ; 137B:7C9E - -d 7c00 7c9f 137B:7C00 E8 5D 00 C3 3F 3F 3F 3F-3F 3F 3F 3F 3F 3F 3F 3F .]..???????????? 137B:7C10 89 D8 89 CA FE C9 D0 E1-D0 E1 D3 E8 25 0F 00 89 ............%... 137B:7C20 D1 C3 3F 3F 3F 3F 3F 3F-3F 3F 3F 3F 3F 3F 3F 3F ..?????????????? 137B:7C30 24 0F 04 30 3C 39 76 02-04 07 C3 3F 3F 3F 3F 3F $..0<9v....????? 137B:7C40 B4 0E BB 07 00 AC 3C 00-74 04 CD 10 EB F7 C3 3F ......<.t......? 137B:7C50 E8 BD FF E8 DA FF AA E2-F7 C3 3F 3F 3F 3F 3F 3F ..........?????? 137B:7C60 E8 00 00 89 E5 81 6E 00-63 7C EB 18 52 65 67 69 ......n.c|..Regi 137B:7C70 73 74 65 72 20 42 58 20-3D 20 30 78 3F 3F 3F 3F ster BX = 0x???? 137B:7C80 2E 0D 0A 00 BB BC 9A B9-04 00 BF 7C 7C 03 7E 00 ...........||.~. 137B:7C90 E8 BD FF BE 6C 7C 03 76-00 E8 A4 FF 58 C3 3F 3F ....l|.v....X.?? - -u 7c60 7c6a 137B:7C60 E80000 CALL 7C63 137B:7C63 89E5 MOV BP,SP 137B:7C65 816E00637C SUB WORD PTR [BP+00],7C63 137B:7C6A EB18 JMP 7C84 - -u 7c84 7c9e 137B:7C84 BBBC9A MOV BX,9ABC 137B:7C87 B90400 MOV CX,0004 137B:7C8A BF7C7C MOV DI,7C7C 137B:7C8D 037E00 ADD DI,[BP+00] 137B:7C90 E8BDFF CALL 7C50 137B:7C93 BE6C7C MOV SI,7C6C 137B:7C96 037600 ADD SI,[BP+00] 137B:7C99 E8A4FF CALL 7C40 137B:7C9C 58 POP AX 137B:7C9D C3 RET 137B:7C9E 3F AAS - - -rbx BX 0000 :0 -rcx CX 000A :9e -r AX=0000 BX=0000 CX=009E DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=137B ES=137B SS=137B CS=137B IP=0100 NV UP EI PL NZ NA PO NC 137B:0100 0000 ADD [BX+SI],AL DS:0000=CD - -n HAND_LNK.BIN -w 7c00 Writing 0009E bytes -q

At address line SEG:7C60, I marked the code and I created my IPReal. The mnemonic CALL 7C63 (which I renamed into "call myself:") produces the opcode 0x E8 00 00 which pushes the IP on the stack and continues normal execution with the following instruction at line address SEG:7C63 (MOV BP, SP) because the IP increment is equal to zero in this case (0x 00 00). At this point, I had IPReal on the stack at address SS:[BP + 0x00]. I calculated immediatelly the conversion factor just by subtracting the IPDebug from it.
At address lines SEG:7C8D and SEG:7C96, I converted at runtime the SI and DI pointers from Debug to Real just with the simple addition of the correction factor stored at SS:[BP + 0x00]. In this way, I could achieve a truly relocatable code as you can see in Fig. G.

Fig. G - Hand linking is truly relocatable code
Fig. G - Hand linking is truly relocatable code

For the last test, I just loaded the code at two different memory location and run it to verify it worked correctly. The first time, I loaded it at SEG:023A and then I triggered the test with a direct call and use of the command p (lines marked with the blue colour). The second time, I loaded it at SEG:1BC7 and I repeated the test (lines marked with the yellow colour).

Some few lines above I was writing about the fact that the IP register flips around the 16bit boundary with ease and that I see a segment as a circle rather than a line of addresses, where the first and the last address (SEG:0000 and SEG:FFFF) are glued just one after the other. So why not test the relocatable code just placing the procedure halfway across the boundary of the code segment? I thought that this was just a great idea, so I did it (Fig. H) and look it worked as expected!

Fig. H - Hand linking is halfway across the boundary of CS
Fig. H - Hand linking is halfway across the boundary of CS

In Fig. H, I took care to take the Stack Pointer away since CS and SS overlap in DEBUG.EXE (lines marked with the purple colour). After that, I used the command m to copy the first half of "Hand Linking" at the end of the segment (lines marked with the green colour) and the last half of it at the beginning of the segment (lines marked with the blue colour). Finally, I prepared the trigger CALL FF90 and I did my test with the command p (yellow lines). In my opinion, this last test shows that the CPU is very much mechanical in its behaviour.

Once again, I wrote quite a long post, and if you made to read it through till the end, then you must be as passionate as I am, about understanding how the PC works.



  1. You may want to revise the name convention I used on the file, from the post "Change of gear", when I automatized the assembly job with DEBUG.EXE. [click back]
  2. Remembering you that I am the hobbyist and not the expert, I think that you can still keep it relocatable if you take care of a lot of details. As far as I understood, these details are handled at load time with dynamic linking techniques. I don't want to step into this topic now (because my current learning stage just scratches the surface of dynamic linking), just keep in mind that absolute addressing is more or less the boundary where static linking cannot be used anymore and you have to use some other techniques. [click back]
  3. Just "somewhere" instead of "anywhere" in RAM because segments can be set only at aligned begin of a paragraph (for instance at 0x 1 23 B0 but you cannot place a segment to start at 0x 1 34 C7). [click back]
  4. In many other places, and also in the 8086 INTEL Manual, lower addresses in memory are at the bottom of the picture and higher addresses in memory are at the top of it which is then drawn as an arrow that points from the bottom upwards. [click back]

<PREV.  -  ALL  -  NEXT>

Comments