| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139 |
- IMPLEMENTATION MODULE Linker ;
- (* Linker and .COM writer for TP3-compiled programs.
- The compiler has already produced the whole image - Runtime is copied to
- offset 0 of the code buffer and the program follows it, so every address in
- the image is already absolute (see Compiler.Inittur). There is therefore
- no relocation to do: "linking" here means
- 1. pad the image so the data area exists, and
- 2. write it out as a DOS .COM file.
- Why padding is needed at all: a .COM file is loaded at CS:0100 with the
- whole 64 KiB segment available, but the program's globals live at
- Compiler.DataBase = rtSz + 1000H, which for a small program is far past
- the last code byte. Those globals are read and written by absolute
- address, so the bytes have to be present in the file. The gap between
- code and data is zero-filled, which is also what makes it safe: a .COM's
- stack lives at the TOP of the segment (DOS gives a .COM SS=SP=CS:FFFE), so
- code and data both have to stay well below it. A 4 KiB gap keeps them
- apart for programs up to 4 KiB of code, which is the documented limit.
- Faithfulness note: the original did this in the compiler's overlay loader
- (TPSRC7 opendest) and wrote a .COM from a different buffer layout with a
- larger header served by the overlay segment. Our header is our own - see
- Runtime.EmitInitMem - and the file layout is the same idea, smaller. *)
- FROM SYSTEM IMPORT BYTE, ADR ;
- FROM Posix IMPORT open, write, close, unlink ;
- FROM Compiler IMPORT ImageBytes, ImageByteAt, DataBase, DataBytes ;
- CONST
- O_WRONLY = 1 ;
- O_CREAT = 64 ;
- O_TRUNC = 512 ;
- VAR
- padByte : BYTE ; (* the filler for the code/data gap *)
- PROCEDURE DropCh (v : CHAR) ;
- BEGIN
- END DropCh ;
- PROCEDURE DropB (v : BOOLEAN) ;
- BEGIN
- END DropB ;
- PROCEDURE DropC (v : CARDINAL) ;
- BEGIN
- END DropC ;
- PROCEDURE ZCopy (VAR dst : ARRAY OF CHAR ; src : ARRAY OF CHAR) ;
- (* Copy into a NUL-terminated buffer. The C bindings take a plain ADDRESS, so
- the string must be terminated in OUR buffer - passing the caller's
- unbounded array straight through would pass a pointer to whatever followed
- it.
- The VAR is load-bearing, and was found by running: without it the formal is
- a COPY, the writes land in the copy, and the caller writes to whatever
- garbage was in its buffer. The symptom was a .COM file named "END" -
- adjacent string data - created with the right size and the right contents
- in the wrong file. tests/OpenArrTest.mod measures the three variants. *)
- VAR i : CARDINAL ;
- BEGIN
- i := 0 ;
- WHILE (i <= HIGH (dst) - 1) AND (i <= HIGH (src)) AND (src [i] # 0C) DO
- dst [i] := src [i] ;
- INC (i)
- END ;
- dst [i] := 0C
- END ZCopy ;
- PROCEDURE LinkSize () : CARDINAL ;
- (* Size of the linked file in bytes: the image, padded out to cover the whole
- data area. This is what a .COM must be, and it is larger than
- Compiler.ImageBytes whenever the program declares no globals, because the
- data base is a fixed 4 KiB above the code. *)
- BEGIN
- IF DataBase () + DataBytes () > ImageBytes () THEN
- RETURN DataBase () + DataBytes ()
- END ;
- RETURN ImageBytes ()
- END LinkSize ;
- PROCEDURE MakePad () ;
- BEGIN
- (* A local array is not required to be zeroed, and relying on it would be
- a silent-corruption bug: an uninitialised pad byte would appear in the
- middle of a program's data area. ISO will not let an array be assigned
- the ZType, so the filler is a single explicit BYTE. *)
- padByte := 0
- END MakePad ;
- PROCEDURE WriteCom (path : ARRAY OF CHAR) : BOOLEAN ;
- (* Write the linked image to `path` as a DOS .COM. Returns TRUE on success;
- on failure the file is removed, so a half-written .COM is never left
- behind to be mistaken for a good one. *)
- VAR fd : INTEGER ;
- n : LONGINT ;
- i, total, chunk : CARDINAL ;
- buf : ARRAY [0..255] OF BYTE ;
- z : ARRAY [0..511] OF CHAR ;
- BEGIN
- MakePad ;
- ZCopy (z, path) ;
- total := LinkSize () ;
- fd := open (ADR (z), O_WRONLY + O_CREAT + O_TRUNC, 420) ;
- IF fd < 0 THEN
- RETURN FALSE
- END ;
- i := 0 ;
- WHILE i < total DO
- chunk := 0 ;
- WHILE (chunk < 256) AND (i + chunk < total) DO
- IF i + chunk < ImageBytes () THEN
- buf [chunk] := ImageByteAt (i + chunk)
- ELSE
- buf [chunk] := padByte (* the gap before the data *)
- END ;
- INC (chunk)
- END ;
- n := write (fd, ADR (buf), chunk) ;
- IF n # VAL (LONGINT, chunk) THEN
- DropC (VAL (CARDINAL, close (fd))) ;
- DropB (unlink (ADR (z)) = 0) ;
- RETURN FALSE
- END ;
- i := i + chunk
- END ;
- IF close (fd) # 0 THEN
- DropB (unlink (ADR (z)) = 0) ;
- RETURN FALSE
- END ;
- RETURN TRUE
- END WriteCom ;
- END Linker.
|