home *** CD-ROM | disk | FTP | other *** search
- (******************************************************************************
-
- CLONE.PAS
- Version 3
- October 17, 1985
- Borland SIG, CompuServe
-
- by: Bob Tolz 70475,1071
- with input from: Randy Forgaard 70307,521
- Kim Kokkonen 72457,2131
- Bela Lubkin 76703,3015
-
-
- This Turbo Pascal program demonstrates how to have a program "clone" a copy of
- itself as a new .COM file, after the user has entered any desired changes.
- This is analogous to the Setup menus of Sidekick and SuperKey, and similar to
- the effect that TINST would achieve if it were built right into the Turbo
- compiler itself. It can also be used to clone slightly different versions of a
- program, for different purposes.
-
- What follows is a rather lengthy description of the implementation of this
- program, called CLONE. However, CLONE.PAS is ready to run as-is. If you just
- want to see what CLONE does, compile CLONE.PAS ==> to a .COM file <== (very
- important), under DOS Turbo 2.0 or 3.0, and then run CLONE.COM. Furthermore,
- don't be intimidated by the large size of this file; when you take out the
- documentation and the test program, the resulting code only fills a screen or
- two.
-
- CLONE has been tested using all regular, 8087, and BCD versions of DOS Turbo
- Pascal 2.00B, 3.00B, and 3.01A. (We will use DOS to mean both MS-DOS and
- PC-DOS.) It probably runs without change with all other versions of the DOS
- Turbo 2.0 and 3.0 compilers. If you have a different version of DOS Turbo, and
- CLONE works for you, or you have modified CLONE to work for you, please let us
- know, or upload a new copy of CLONE.
-
- With some coaxing, CLONE could be modified to run under CP/M-86 and CP/M-80.
- If you make CLONE work on any non-DOS system, please incorporate the new
- procedures required into this file, and upload a new CLONE. We would love to
- see CLONE become as generally applicable as possible.
-
- The theory behind CLONE is as follows: CLONE is designed to be run as a
- compiled Turbo program. When loaded into memory, it has the capability to make
- a copy of itself, by just writing the image of itself, which already resides in
- memory, to a new .COM file. If the user changes some typed constants in CLONE
- before CLONE clones itself, the new copy of clone will reflect those new values
- for the typed constants. Voila! A program that can "install" itself. The
- trick in implementing CLONE is to find out where the memory image of CLONE.COM
- begins, and how long it is, so that CLONE can write out the correct memory
- image of itself as a new .COM file.
-
- The first question is easy. When DOS loads CLONE.COM (or any .COM program)
- into memory, it first builds a 256 byte ($100 bytes, in hexadecimal) Program
- Segment Prefix (PSP) in memory, and then reads CLONE.COM into the memory
- immediately following the PSP. DOS then sets the CS (code segment) register so
- that address CS:0000 is the beginning of the PSP. Consequently, the contents
- of CLONE.COM really start at CS:0100, or, in Turbo parlance, CSeg:$0100.
-
- The second question, the code length of CLONE.COM in memory, is a little
- trickier. One way to find out is to compile CLONE, issue a "DIR CLONE.COM"
- command at the DOS prompt to see how big the .COM file is, and then hard-code
- this constant into CLONE.COM. The disadvantage of this method is that every
- time you change CLONE, you have to compile it, see how long it is, change that
- constant in the CLONE code, and then re-compile CLONE.
-
- We propose an alternate scheme, to have CLONE "discover," at run-time, how many
- bytes the memory image of itself occupies. It turns out that .COM programs
- produced by DOS Turbo set up the DS (data segment) register so that the address
- DS:0000 is the first 16-byte paragraph boundary past the end of the .COM file
- code. Depending on where the .COM code ends, there can be up to 15 "garbage"
- bytes between the end of the .COM code and the beginning of the data segment,
- because of the 16-byte paragraph boundary restriction on DS:0000. Thus, we
- could say that the .COM code ends at DSeg:$0000 (in Turbo notation), and that
- would be almost correct, but it would usually include a few extra garbage bytes
- that weren't actually part of the .COM file.
-
- One school of thought is to just keep those few garbage bytes as part of the
- cloned .COM file that gets produced by CLONE. I.e., when CLONE writes out a
- "copy" of itself, it could include all the bytes between CS:0100 and DS:0000,
- including those few extra garbage bytes. Then, when you tell DOS to run the
- cloned copy of the program, DOS will just load those garbage bytes into memory
- along with the rest of the .COM file. These extra bytes will not affect the
- execution of the cloned copy, any more than they affected the execution of
- CLONE when they happened to be located in memory when CLONE decided to clone
- itself. However, it may be disconcerting to have the cloned copy of a program
- be a few bytes longer than the original, due to the garbage bytes. In
- particular, you won't be able to use the DOS COMP program to compare the
- original .COM file with its clone, because the files will be different sizes.
-
- However, if you decide that the garbage bytes are all right, in the interest of
- removing some complexity from CLONE, all you have to do is run CLONE.COM once
- so that it makes a clone of itself (including the garbage bytes). If you use
- the clone of CLONE to make another CLONE, the second CLONE will be exactly the
- same size as the first clone of CLONE. Thus, if you use the clone of CLONE as
- your distribution copy, all clones of the distribution copy will be the same
- size as the distribution copy, so the DOS COMP program will be able to compare
- clones. In summary, you can choose to use a cloned copy of CLONE, so that all
- clones of the cloned CLONE will be the same size as the first clone of CLONE.
- (There will be a quiz on this material.)
-
- If you decide to use the simpler scheme that includes the few garbage bytes at
- the end of the cloned .COM file, you can replace the body of the CodeSize
- function, below, with the single statement:
- CodeSize := ((DSeg - CSeg) shl 4) - $100
- and omit the "while" loop and the variable declaration entirely.
-
- However, suppose you want the clones to be the same size as the originals, and
- you don't want to have to perform an extra cloning step prior to each time you
- distribute your program. After some discussion with various DOS and Turbo
- wizards, including Borland itself, we have not discovered any magic location in
- the Turbo run-time system, or any DOS function call, or any field in the PSP,
- that can be used to compute the actual length of the loaded .COM file, minus
- the garbage bytes. However, we have found a reliable way to compute this
- length by having CLONE examine the last few machine instructions of itself in
- memory. In particular, we have found that .COM files that were compiled using
- either the regular or the 8087 version of Turbo 2.00B always end in the
- following pattern of bytes:
-
- E9 00 00 E8 ? ?
-
- where ? is any byte at all. This is probably true of any of the Turbo 2.0
- compilers, not just the 2.00B compilers that we tested. Likewise, .COM files
- produced by the regular, 8087, or BCD versions of Turbo 3.00B and 3.01A always
- end in the following pattern of bytes:
-
- ? 00 00
-
- where ? is any byte except E9. Again, this pattern probably holds for all
- versions of Turbo 3.0, not just the 3.00B and 3.01A compilers we tested.
-
- The CodeSize function, below, computes the length of the loaded .COM file at
- run-time by examining the bytes immediately prior to DSeg:$0000 to find the
- above patterns. For a pattern that is "n" bytes long, it looks for the first
- occurrence of the pattern starting at the memory location that is 16-n+1 bytes
- prior to DSeg:$0000. It turns out that the Turbo 2.0 pattern and Turbo 3.0
- pattern are mutually exclusive, so there is no possibility of accidentally
- finding the Turbo 2.0 pattern near the end of a Turbo 3.0 .COM file and vice
- versa. Consequently, for simplicity of exposition, CodeSize looks for both the
- Turbo 2.0 pattern and the Turbo 3.0 pattern at the same time, since there is no
- danger of confusion. If you know for certain that you will only be using the
- Turbo 3.0 compiler, you can remove the test for the Turbo 2.0 pattern from
- CodeSize, and vice versa.
-
- If you find byte patterns for any other versions of Turbo, or can verify that
- the byte patterns used by the CodeSize function below already work for versions
- of DOS Turbo other than 2.00B, 3.00B, and 3.01A (regular, 8087, and BCD),
- please let us know, and/or upload a new copy of CLONE.PAS that incorporates the
- appropriate generalization of the CodeSize function.
-
- One way to discover the byte patterns at the end of a .COM file, produced by a
- version of the Turbo compiler that we have not yet tried, is to compile several
- programs, into .COM files, using that Turbo compiler, and use the DEBUG program
- that comes with DOS to look for similarities at the ends of the .COM files.
- For example, suppose DIR shows that TEST.COM, produced by your Turbo compiler,
- has a length of 12396. Converting that to hex (which is $306C), and adding
- $100 (for the PSP), indicates that memory location CS:316C will be the first
- byte past the end of the .COM file. (It will be the first byte past the end,
- rather than the last byte itself, because we start counting from CS:0000
- instead of CS:0001.) CS:316B (one less than CS:316C) will be the actual last
- byte of the .COM file. To find a a pattern, it is prudent to look at least at
- the last $20 bytes (2 16-byte paragraphs) of the .COM file. For this example,
- we issue the command:
-
- D314C 316B
-
- at the DEBUG "-" prompt. (The CS segment register is implicitly used by DEBUG
- in this command.) DEBUG will display the bytes in those 32 locations. Save
- the contents of the screen to your printer, or (if you have Sidekick) to a
- file, and do a "Q" to quit out of DEBUG. Using DEBUG and your printer, compare
- those bytes to the last 32 bytes of the .COM files produced by your Turbo
- compiler for several other example programs. A pattern should emerge. Note
- that the pattern will almost certainly consist of certain bytes that are always
- the same, and certain "?" bytes that are less restricted. Once the pattern
- has been found, it can be incorporated into CodeSize to extend the number of
- Turbo compilers for which CodeSize will work.
-
- If you run the DOS COMP program to compare CLONE.COM with a clone of itself,
- you may find discrepancies at a few byte locations. For example, using the
- regular 3.00B compiler, we found that the byte at $92 and the word (two bytes)
- at $2BD2 differed between CLONE.COM and its clone. These reflect changes that
- the Turbo run-time system makes to itself after CLONE.COM has been loaded by
- DOS. Bela Lubkin of Borland has indicated that $92 is irrelevant, and that
- $2BD2/$2BD3 is probably innocuous. If you do have a problem, or if you want to
- make triple sure that there won't be a problem, then: 1) use COMP to see what
- data should be in those byte locations, and 2) immediately before the
- BlockWrite operation in the Clone procedure below, insert some assignment
- statements to the appropriate Mem and MemW locations, e.g.:
- Mem[CSeg:$0092] := ????
- MemW[CSeg:$2CD2] := ????
-
- The CodeSize function is only half of the story. The other half is the Clone
- procedure itself. The Clone procedure uses the CodeSize function to determine
- how long the new .COM file will be, and then writes those bytes directly from
- memory to a new .COM file. Using Turbo 3.0 for DOS, this is very easy to do,
- using BlockWrite and the new optional record size parameter for Rewrite,
- allowing us to declare the record size to be one byte long. This allows us to
- write a new .COM file that is exactly the right length.
-
- Using Turbo 2.0, or a non-DOS version of Turbo 3.0, is more difficult. In
- these cases, BlockWrite can only be used to write 128-byte blocks, to the
- resulting .COM file will not be the exact byte length we are looking for. One
- way around this is to use a File of Byte, and to write the new .COM file one
- byte at a time, but this turns out to be dreadfully slow. Instead, we have
- opted below for a DOS-specific version of Clone that uses DOS function calls
- (assumes DOS 2.0 or higher) to write the new .COM file very quickly.
-
- We have written two versions of the Clone procedure below. The first one,
- involving DOS function calls, will work under both Turbo 2.0 and 3.0. The
- second version of Clone, which is much cleaner than the first and runs just as
- fast, can only be compiled under DOS Turbo 3.0. As this CLONE.PAS file
- currently exists, the first Clone procedure will be used, due to a sneaky
- commenting convention. However, as indicated in the comment immediately
- preceding the first Clone procedure, deleting a single character from this
- source file will cause the second, more compact Clone procedure to be used
- instead.
-
- A caution: The CLONE technique has not been tried with any programs that use
- overlays. This could be quite tricky, because the memory image of CLONE would
- change as soon as program execution caused an overlay to be read from disk.
- The CLONE idea might be usable with overlayed programs if no overlays get
- loaded prior to cloning, but this has not been tested. Please let us know if
- you come up with any results or insights in this regard.
-
- CP/M-86 users: We would be very interested if someone could add, to this
- CLONE.PAS file, a special version of the Clone procedure, and an enhancement or
- new version of the CodeSize function, that will work for CP/M-86. A CodeSize
- function will probably still be required if one wishes to avoid cloning the
- garbage bytes between the end of the .CMD file image and the beginning of the
- data segment. The byte patterns to look for will probably be different for
- CP/M-86 than for DOS, so additional clauses will probably have to be added to
- the "while" loop in the CodeSize function. The subtraction of $100, in
- CodeSize, should be omitted, since CP/M-86 sets the CS segment register such
- that CS:0000 (rather than CS:0100) is the beginning of the loaded program.
- Also, the Clone procedure will have to be rewritten, either to use a File of
- Byte (slow), or to replace the DOS function calls with CP/M-86 function calls.
-
- CP/M-80 users: It would be terrific if someone could CP/M-80 capability to this
- CLONE.PAS file. It appears, from reading the Turbo manual, that the heap
- (rather than the data segment) starts immediately after the object code of the
- loaded .COM file, with no garbage bytes in-between. Thus, the length of the
- .COM file can probably be determined by subtracting, from the initial value of
- HeapPtr, the size of CP/M and its run-time workspace. It is pretty clear, in
- any case, that the CodeSize function for CP/M-80 would be very different, and
- probably much simpler. The Clone procedure will have to be rewritten also, so
- as not to use DOS function calls or features that are specific to the DOS
- version of Turbo.
-
- We hereby donate CLONE to the Public Domain...happy cloning!
-
- ******************************************************************************)
-
-
- program CloneDemo;
-
- type
- FileName = string[80];
- Message = string[80];
-
-
- {This function returns the number of bytes occupied by the image of this .COM
- file in memory. Known to work for Turbo programs compiled under the regular,
- 8087, and BCD versions of the Turbo 2.00B, 3.00B, and 3.01A compilers for DOS.
- See comments above for details.}
-
- function CodeSize: Integer;
- var
- i: Byte;
- begin
- i := 11;
- while {Turbo version is marked on the left:}
- {3.0:} not ((Mem [DSeg-2:i+3] <> $00E9) and (MemW[DSeg-2:i+4] = $0000)) and
- {2.0:} not ((MemW[DSeg-2:i+0] = $00E9) and (MemW[DSeg-2:i+2] = $E800)) do
- i := i + 1;
- CodeSize := ((((DSeg - 2) - CSeg) shl 4) + i + 6) - $100
- end {CodeSize};
-
-
- (*Currently, the first Clone procedure below will be compiled. To use the
- second Clone procedure, delete the curly bracket on the line immediately
- following this comment.*)
- {
-
- (*The next line is part of the Clone selection scheme.*)
- (* }
-
-
- {Writes, into the file "fn," a clone of this program, including any new values
- for typed constants that may have been changed since the program was loaded.
- This is the long version of Clone that uses DOS function calls (assumes DOS
- 2.0 or higher) to quickly write out the new .COM file. It can be used both
- with the 2.0 and the 3.0 versions of Turbo for DOS. See comments above for
- details.}
-
- procedure Clone (fn: FileName);
-
- procedure Abort(msg: Message);
- begin
- writeln(msg);
- Halt
- end {Abort};
-
- var
- handle, length: Integer;
- regPack: record
- case Integer of
- 1: (AX, BX, CX, DX, BP, SI, DI, DS, ES, Flags: Integer);
- 2: (AL, AH, BL, BH, CL, CH, DL, DH: Byte)
- end;
- writeError: Boolean;
- begin
- with regPack do
- begin
- fn := fn + #0; {Convert "fn" to an ASCIIZ string}
- length := CodeSize; {Length of code image in memory}
- AH := $3C; {Create a file}
- DS := Seg(fn[1]); {Segment of ASCIIZ file name}
- DX := Ofs(fn[1]); {Offset of ASCIIZ file name}
- CX := 0; {Default attributes}
- MsDos(regPack); {Create the clone file}
- if Odd(Flags) then {Check if carry bit is set}
- Abort('Unable to create file');
- handle := regPack.AX; {Retrieve handle for opened file}
- AH := $40; {Write to a file}
- BX := handle; {File to write to}
- DS := CSeg; {Segment of code}
- DX := $100; {Beginning address of code}
- CX := length; {Length of code}
- MsDos(regPack); {Write the code to the clone file}
- writeError := Odd(Flags) or (AX <> length);
- if writeError then {Allow the file to be closed, anyway}
- writeln('Unable to write to file');
- AH := $3E; {Close a file}
- BX := handle; {File to close}
- MsDos(regPack); {Close the output file}
- if Odd(Flags) then {Check if carry bit is set}
- Abort('Unable to close file');
- if writeError then Halt {Halt if there was a write error previously}
- end
- end {Clone};
-
-
- {The next line is part of the Clone selection scheme.}
- { *)
-
-
- (*Writes, into the file "fn," a clone of this program, including any new values
- for typed constants that may have been changed since the program was loaded.
- This is the more elegant version of CLONE that uses BlockWrite and the
- optional record size parameter to Rewrite to quickly write out the new .COM
- file. It can only be used with DOS Turbo 3.0 and higher. See comments above
- for details.*)
-
- procedure Clone (fn: FileName);
- var
- f: File;
- contents: Byte Absolute CSeg:$0100;
- begin
- Assign(f, fn);
- Rewrite(f, 1);
- BlockWrite(f, contents, CodeSize);
- Close(f)
- end (*Clone*);
-
-
- (*The next line is part of the Clone selection scheme.*)
- { }
-
-
- {Test program for CLONE:}
-
- const
- default: Message =
- '"How many Fortune 1000 companies are there, anyway?" -- Steve Jobs';
- var
- newFile: FileName;
- new: Message;
- begin
- writeln('The size of this .COM file is ', CodeSize, ' bytes.');
- writeln('The program currently contains the following default string:');
- writeln;
- writeln('=> ', default);
- writeln;
- writeln('Please enter a new value for this default string, or just press ');
- writeln('Enter (Return) if you do not want to change the default value: ');
- writeln;
- write('=> ');
- readln(new);
- writeln;
- if Length(new) > 0 then default := new;
- writeln('Please enter a file name (ending in .COM) for storing the clone, ');
- writeln('which can be the same name as the original .COM file, or just ');
- writeln('press Enter (Return) if you do not want to make a clone: ');
- write('=> ');
- readln(newFile);
- if Length(newFile) > 0 then
- begin
- Clone(newFile);
- writeln;
- writeln('If you now run "', newFile,
- '," you will see the new default string.');
- writeln('This program has just changed its own defaults!')
- end
- end {CloneDemo}.