PDLN.DOC - for Version 1.0 - Last Revision 04/08/86 PDLN - The Public-Domain Linker Copyright (c) 1986 Wilson H. Bent, Jr. OVERVIEW: PDLN is a Public-Domain version of Microsoft's L80, Digital Research's LINK, and SLR System's SLRNK. And it's free. PDLN is a linking loader. It takes one or more .REL files, which are non-executable files created by assemblers, loads them into memory, resolves links between them, and creates an executable .COM file. If desired, it can also create a Symbol Table file for use with a debugger. It can search through Library files (in the .REL format) and extract only the necessary modules. Now for the drawbacks. It's slower than the other linkers mentioned. It's for a Z80 or compatible CPU only. It works totally in-memory, meaning that the generated code AND the symbol table must fit in the TPA (Many other linkers suffer from this 'drawback', too). In addition, it does not handle all of the special fields in the .REL format. The omitted fields were selected as those least likely to be used. See the "NITTY-GRITTY" section for details. NOTE: Throughout this document, ".REL format" means the Microsoft Relocatable Format for object modules. This is a well- documented format, and I'm not willing to detail it here. If you need details, contact me or your local expert. .he OVERVIEW SYNTAX: PDLN [option] file(s) [-L libfile] Options: -L FILE : Search FILE as a Library file. ALL OTHER FILES are fully loaded! -S : Create a Symbol Table file (.SYM) with same name as first .REL file (not changeable). -V : Verbose Mode. -B XXXX : Set the Base Address to XXXX (hex). Default 100h. -C XXXX : Set the Code Address to XXXX (hex). Default 103h. -D XXXX : Set the Data Address to XXXX (hex). Defaults to immediately after Code segment. In addition, if a slash ('/') is found in the command line, PDLN prints an abbreviated version of the above syntax message and exits. The .REL extension is required of ALL files; any extension given in the command line is ignored. No space is required between options and their arguments. However, arguments may not be 'packed,' i.e, giving the command "PDLN -SV" will set only the S (Symbol file) option, not the V (Verbose mode) option. Options which call for arguments must have them: the -L option takes the next argument as a file name, and the address options (-B, -C, and -D) take the next argument as a Hexidecimal number. PDLN will not work properly with missing or improper arguments. If any one of the address options (-B, -C, and -D) appears more than once in the command string, the last value given will be the one used. PDLN will first load all normal .REL files (in argument order), then search all Library files (also in order). This means that Library files may be placed anywhere in the argument list. There may be up to 10 .REL files in the command line, and up to 6 Library may be searched. Exceeding these numbers will cause errors; see the "NITTY-GRITTY" section. There is no interactive mode, nor may commands be read from files. File names may be given in the ZCPR DU: form: [Disk] [User] [:] Name If PDLN is run under ZCPR with Named Directories, file names may be given in the DIR: form: [Dir:] Name..pa EXAMPLES: PDLN -S MAIN SUBROUTE -L A14:SYSLIB Load the files MAIN.REL and SUBROUTE.REL, search the library file SYSLIB.REL (which is on disk A: in User Area 14), create the executable file MAIN.COM and the symbol file MAIN.SYM. PDLN -B2000 -D3000 F1 F2 F3 F4 -LLIB:ROMLIB Load F1.REL, F2.REL, F3.REL, and F4.REL, search the Library file ROMLIB.REL in the ZCPR Directory LIB:, create the executable file F1.COM with the Base Address of Hex 2000 and the Data segment starting at Hex 3000. The first byte of F1.COM will be assumed to load at Hex 2000 rather than Hex 100, and the Data segment will start at the Hex 1000'th byte in the file. Here's an actual example of the output produced by PDLN during a somewhat contrived run with the Verbose option used: B0>PDLN -V -S TMP -L Z3LIB -B 1000 -D 1003 -C 2000 PDLN Public-Domain Linker Ver 1.0 Loading TMP.REL Searching Z3LIB.REL Resolving Symbols Non-Standard .COM File *** Symbol not found: RETUD *** Symbol not found: CAPSTR Writing TMP.COM Writing TMP.SYM Base: 1000 - 1003 : 0003 Code: 2000 - 2242 : 0242 Space: AC96 Done! Now for the analysis: B0>PDLN -V -S TMP -L Z3LIB -B 1000 -D 1003 -C 2000 Summon PDLN, set the Verbose and Symbol options, load the file TMP.REL, search the library Z3LIB, set the Base Address to Hex 1000, the Data Address to Hex 1003, and the Code Address to Hex 2000. PDLN Public-Domain Linker Ver 1.0 The signon message. Printed even if not Verbose. Loading TMP.REL The base file is being loaded. Searching Z3LIB.REL The library file is being searched. Resolving Symbols All files have been loaded; PDLN is now resolving the symbol table so that the various segments will be linked as desired. Non-Standard .COM File Since the output Base address is not Hex 100, this cannot be used as a standard .COM file. *** Symbol not found: RETUD *** Symbol not found: CAPSTR Two symbols were not resolved. In this case, they were external to routines loaded from the Z3LIB library, and are defined in the SYSLIB library (but we didn't load that). Writing TMP.COM The output file is being written. Even though it's a Non- Standard .COM file, the file type is still .COM. Writing TMP.SYM The symbol table file is being written. This step comes after writing the .COM file, so that the more important of the two is handled first. Base: 1000 - 1003 : 0003 Code: 2000 - 2242 : 0242 Space: AC96 The segment start and end locations and sizes are reported in Hexidecimal notation. Note that even though we defined a starting address for the Data segment, since there was nothing loaded as Data, the size is Zero, thus it is not reported. 'Space' is the amount of memory unused by PDLN; see Appendix 1. Done! That's it, PDLN is finished. Printing this message is the last thing PDLN does before executing a cold boot. .he NITTY-GRITTY NITTY-GRITTY: The following symbols are available: $MEMRY : first "free" byte (two bytes) $$PROG : address of Code segment (two bytes) If PDLN finds these locations defined, they are loaded with the appropriate numbers. The Base segment of the output .COM file may contain one of three things: Absolute code, a jump instruction, or zeros. Assuming that there is no Absolute code, PDLN will first load the .COM file with either three null bytes, or an instruction to jump to a starting address (the size of the Base segment depends on the use of the -B, -C, and -D options). The jump instruction is used if any of the input .REL files has the Start Address field, which is set in the source code at the 'END' pseudo-op: Source Code: CSEG DW 0 AA: RET END AA This file, when assembled and linked, will have as the first three bytes the instruction 'JUMP AA'. This is often used in ZCPR utilities and programs written in High-Level Languages. If two or more .REL files include the Start Address field, PDLN will use the last address specified. No message is printed if more than one Start Address is found. PDLN is fairly forgiving about the order in which modules are loaded: if both module A and module B are being loaded, and module A references something in module B, the order in which they're loaded doesn't matter. However, the order in which libraries are searched does matter: if a module in library X references something in library Y, then X must be searched first to insure that the referenced part of Y is loaded. Some assemblers create symbols in their .REL files with up to seven characters, others with up to six. Thus, for example, if your assembler creates 7-char symbols, they will not be matched with 6-char symbols in a library file. This can also cause problems with symbol names which are identical to 6 or more chars: the assembler may be able to keep them straight, but once they're in the .REL file, they look identical. There is no way to determine beforehand which size exists in a given .REL file (unless you have a program such as the Public-Domain RELDUMP, by Ron Fowler), the only solution is to try both sizes. If a .REL file includes the 'Request Library Search' operative (resulting from the pseudo-op ".REQUEST" in the assembly source code), those libraries are put at the top of the list of libraries to search. If this makes the number of libraries to search greater than 6, the last ones on the list will be bumped off and an error message will be printed. This is best shown by example: Source Code: RET ; end of code .REQUEST LIB1, LIB2 ; request libraries END ; this was FILE.MAC Command Line: PDLN FILE -L LIB3 -L LIB4 -L LIB5 -L LIB6 -L LIB7 PDLN will first parse the command line and put LIB3, LIB4, LIB5, LIB6, and LIB7 on the Library Search list. It will then load FILE.REL and get the requests for LIB1 and LIB2. These go at the top of the list, and LIB7 is bumped off. Thus the following libraries will be searched in order: LIB2 LIB1 LIB3 LIB4 LIB5 LIB6 Note that LIB1 and LIB2 are reversed in order relative to the source file. This is because PDLN first got the request for LIB1 and put it at the top of the list. Then it got the request for LIB2 and put THAT at the top of the list, above LIB1. If a library is referred to both in the command line and via ".REQUEST", it will be searched twice (assuming that it's not bumped off the list as above). Because ".REQUEST" pseudo-ops usually cannot carry disk or user information, libraries requested using this directive must be on the same disk as the most recently loaded .REL file. Any and all Common segments are placed after the Data segment, sequenced as they are referred to in the .REL file(s). Note that this is the REVERSE of many linkers. It is possible to link absolute and relocatable code, but only under certain restrictions. The first absolute address loaded defines the minimum load address (otherwise resulting in a fatal error), and all absolute code should be loaded before any relocatable code (otherwise the "Segments overlap" error will occur). This is true EVEN IF IT'S KNOWN TO FIT, i.e, even if the absolute segment is placed beyond the known end of the relocatable segment(s). The following examples show good and bad combinations of absolute (ABS) and relocatable code (REL, which may be any combination of Code, Data and Common segments): 1) Good: Bad: 2) Good: Bad: ----- ---- ----- ---- AA: ABS BB: REL CC: ABS DD: ABS REL REL BB: REL AA: ABS DD: ABS CC: ABS REL REL In the above examples, both 'Bad' situations are cured by loading the files in reverse order, thus loading all ABS code before any REL code. (The second example assumes that the ABS code in file DD is origin'd after that in CC. In fact, the second bad example would have two errors: low ABS after high, and ABS after REL.) In addition, loading absolute code will override any previous Base Address assignment, whether it's from the -B option or the default of 100h. (If the -B option was used, the "Base option ignored" error will occur.) If the verbose mode is selected (the -V option) and the Base Address is NOT 100h, PDLN will report "Non-Standard .COM File". The one thing to look out for when loading absolute and relocatable code is ending the absolute code with "DS" (Define Space) pseudo-op. Virtually all assemblers will not fill the space (they usually aren't supposed to), nor will they define the space used (the .REL format doesn't really have a way to do that). Thus the first relocatable code loaded will go in the space defined by DS! This is best shown by an example: Source Code: ASEG ORG 100H SPACE: DS 10 END Any relocatable code loaded after this file will START at the label SPACE, and will be overwritten if any instructions write to that area. The solution is to define at least one byte at the end of the absolute code, either data or instruction. WARNING: If the Absolute code being loaded has a destination address which overlaps PDLN's working Symbol Table, trouble is likely to occur, including hanging up or crashing the system. (Disk files are almost completely safe and unlikely to be damaged in such an event. I wouldn't want to ruin your work!) The indication that this has happened is that, in the verbose mode (the -V option), nothing (or garbage) happens after the "Resolving Symbols" message. This results from PDLN's method of resolving symbols, which is handled differently depending on whether the resolution is being done in an absolute or relocatable segment. The symbol table is built in the space below the BDOS (see Appendix 1). If you are creating a file destined for that space, it is VERY HIGHLY RECOMMENDED that you use relocatable code only, and use the -B, -C, and/or -D options to define the load addresses. With most assemblers, it is possible to create a Common area with a 0-length name, and PDLN supports this. However, since Common area names are treated as symbols, this could result in a strange entry in the symbol file. The symbol file format is: [Value 1] [space] [Name 1] [tab] [Value 2] ... If there is a null-named Common, it will be the first entry in the symbol file, which will thus start out Value-space-tab-value... which can cause trouble with some debuggers. Digital Research's ZSID, for example, handles it fine until it tries to print the symbol name, whereupon it spews out a semi-random string of characters. Be careful with null-named Common areas. If PDLN is to be run under ZCPR, then it should be initialized with Z3INS. It requires an External Environment Descriptor (i.e, the Environment Descriptor is not within PDLN). This allows PDLN to use the DIR: form of filenames. The DU: form (B2: for Disk B, User 2) is always available. The result of using the DIR: form with an uninitialized PDLN (whether under ZCPR or not) cannot be predicted. PDLN uses the Alternate Register set of the Z80. Any previous contents are overwritten. It does not assume that the Alt. Registers are saved during system calls. PDLN does NOT use undocumented Z80 opcodes. It's worth noting that in the Digital Research documentation of the .REL format, the External Minus Offset field, although unused, is said to include a name field; no other documentation agrees with this. This means that if an assembler makes use of this .REL field and includes the name field, PDLN will become out of sync with the .REL file. You're on your own. .he QUESTION & ANSWER QUESTION & ANSWER PERIOD: Q: Why did you write it? A: For two reasons: because of the need, and because of the challenge. I was impressed with the number of PD assemblers, especially with ZASM (which was, I assume, originally a money-making product for Cromemco), impressed with the amount of PD subroutine libraries (especially Echelon's Syslib et. al.), and disappointed with the number of PD linkers, none to be exact. (As a later note, it was during the development of PDLN that Ron Fowler's ProLink was released. I felt that PDLN differed in syntax and abilities enough that I continued to work on it.) The challenge came as I wrote it. I never thought it would be easy, and I was never disappointed. Just to give you an insight as to the workings of my mind, FIRST I decided to write PDLN, THEN I looked at the format of a REL file, THEN I looked at how other linkers worked. In fact, the writing of PDLN would make a classic example of Bottom- Up programming. Q: Where's the source code? A: Safely tucked away in my wall safe, behind the painting of Great-Grandfather Bent. I'm not interested in sharing it at this time for one reason: revisions. If you find a bug, feel free to write, call, or (best yet) send electronic mail to me, that's why I've provided all those means of contacting me. If you're truly serious about making changes, I'll truly seriously consider giving YOU the source code. When I wrote and released LDSK, a utility for ZCPR3, it was quite well received. That also meant that it was hacked to fit the ideas of others; some with good ideas, some with (how shall I put it?) nit-picky ideas. I finally gave it up to the best of the revisionists. I'd rather not do that with PDLN. Q: Why didn't you implement [insert feature here]? A: Do you really need and use that feature? If so, let me know and I'll try to work it in to a later version. I'm all in favor of such suggestions, and I've got a few of my own. The sad part is, I doubt a new version will be out soon (a term which is relative to when you read this!), so you may have to wait. (The real answer to this question is: Because I never use it.) Wilson H. Bent, Jr. 39 Maple Ave. Fair Haven, NJ 07701 Work: (201) 949-1277 UN*X: ... ihnp4!vax135!hoh-2!whb RCP/M: Lillipute: (312) 649-1730 Chicago .he APPENDIX 1 APPENDIX 1: MEMORY MAP HIGH: BDOS, BIOS ___/ from \ LD HL, (6) String Buffer --\/ These areas Symbol Table --/\ grows downwards v v v Empty (We Hope!) ---< Reported as 'Space' in Verbose mode ^ ^ ^ Commons -\ Data --\/ These areas Code --/\ grow upwards Abs (org 100) -/ ___/ from PDLN \ LD HL, ($MEMRY) LOW: Page 0 .he APPENDIX 2 APPENDIX 2: ERROR STRINGS Here is a list of the error messages produced by PDLN, along with their type, possible causes, and suggested solutions should they ever occur. The solutions may at times be simplistic, but then so is PDLN. FATAL errors cause an immediate exit from PDLN. I/O ERRORS cause PDLN to cease processing on the current file and go to the next file (if any). All such errors are preceded with the name of the file associated with the error. WARNING errors have various effects, discussed along with the string. (I don't like noisy programs: only Fatal errors beep.) Bad Option: WARNING: The letter following the error message was parsed as an option, but not found in the list of available options. As a result, subsequent arguments may not be correctly parsed. The solution is to check the argument string vs. the available options. (PDLN cannot deal with files whose name begins with a dash; such files are parsed as arguments. Sorry if this causes trouble.) Base option ignored WARNING: The -B option was used, but the code loaded included an absolute base which could not be changed. Note that this error will occur even if the option base is the same as the absolute base - in this case, the option need not be used. The solution is to either recompile the absolute code with the desired base, or to leave out the -B option. : Close error I/O ERROR: An error occurred while attempting to close the named file. As far as I can tell, this would only occur if the file did not exist or was not previously opened. This SHOULD never occur; feel free to report any occurrences to the author. : Directory full I/O ERROR: Space ran out in the disk directory while writing the named file. Assuming that there is enough storage space available (see "Disk full" error), this usually results from too many small files on the disk. The solution is to create directory space on the disk, either by removing unneeded files or by copying files to another disk. : Disk full I/O ERROR: Space ran out on the disk while writing the named file. The solution is to create space on the disk, either by removing unneeded files or by copying files to another disk. : Input file not found I/O ERROR: The named file was not found where expected. The expected disk and user area are not printed; you should check your command line to be sure where the file was expected to be. All file names default to the current disk and user. This error may also be a result of an improper directory name under ZCPR. No arguments! FATAL: You've got to tell PDLN to do something! Ideally, this would not be a fatal error, rather the help message (which appears if the "//" option is given) would be printed. No base files to load! FATAL: The command line included only options, no .REL files. There must be at least one 'base' file on which to base the program - library modules are linked to it, etc. The solution is to specify a base file! (In all fairness, this can also result from a mangled set of commands, such as arguments to a SUBMIT file.) Out of Memory! FATAL: PDLN has run out of space for the program you are trying to load. The only solutions are to make the program smaller, use fewer symbols, or to use a different loader! (Making more of the existing code Absolute rather than Relocatable can also help avoid this error.) : Read past EOF I/O ERROR: An attempt was made to read data beyond the end- of-file. This could possibly be the result of a munged .REL file. This SHOULD never occur; feel free to report any occurrences to the author. : Reopen attempt on open file I/O ERROR: An attempt was made to open a file which had already been opened. This SHOULD never occur; feel free to report any occurrences to the author. Resized Common: WARNING: The size of each common area is set by the first reference to that area. This error results from a subsequent reference to the named common area which attempts to increase the size of the area. As a result, references to locations outside the original area will likely be in error. The solution is to either check the source code and make all references to the common area the same size, or to put the file with the largest reference to the common area first in the argument list. Segments overlap WARNING: Two (unnamed) segments were trying to occupy the same address. The effect in the output file is to truncate the segment which comes first in memory; the later one is on top in the overlap. This error results from improper use of the -B, -C, or -D options (If this error occurs and the options were not used, feel free to report it to the author). In particular, remember that the 'Base' segment (if needed) is at least 3 bytes long. The solution is to check the segment sizes and be sure that they fit where you want them to. Symbol multiply defined: WARNING: The named symbol was used to refer to two (or more) different values. This error results not only from using the same name twice, but also from using long symbol names which do not differ in the first 6 or 7 characters (see this subject in the NITTY-GRITTY section). As a result, the first occurrence of the symbol will be used throughout the .COM file. The solution is to rewrite the source code to avoid such conflicts. Symbol not found: WARNING: The named symbol was not resolved by PDLN. In other words, it was referenced as an External Symbol in a program module, but never defined. The result is that PDLN defines the symbol as Zero. The solution is to either rewrite the source code to define it, or to check the files to be linked for it (i.e, are you searching the necessary library?). Too many arguments WARNING: The command line parsed into more than 20 arguments. Arguments after the 20th are ignored. An easy solution is to remove the spaces between options and their arguments, e.g. -LLIB -C103 rather than -L LIB -C 103 Too many Common Areas! FATAL: PDLN can process a maximum of 8 common areas. This error occurs when attempting to define a 9th common. The solution is to rewrite the code to use fewer common areas. Too many libraries WARNING: There is a limit of 6 library files to search. Library files after the 6th will not be searched. This can also occur if any of the .REL files includes the pseudo-op ".REQUEST "; see the section on libraries in the NITTY-GRITTY section. Are you sure you need to search all the libraries requested? Can they be combined into bigger libraries? Too many REL files WARNING: There is a limit of 10 .REL files to load. .REL files after the 10th will not be loaded. The best solution is to combine the source files before assembly (perhaps by use of the "INCLUDE" operative) to create fewer .REL files. : Unopened file I/O ERROR: An attempt was made to access an unopened file. This SHOULD never occur; feel free to report any occurrences to the author. Unsupported .REL field: WARNING: PDLN is unable to deal with 3 types of .REL file special field: Extension Link Item External Minus Offset Chain Address If one of these fields occurs in the current .REL file, PDLN will skip over it and print the warning message. In most cases, the affected area in the .COM file is filled with zeros. The solution is to ignore it and patch the code later, which may be difficult, or to rewrite the source code to avoid use of these fields, which might be easier. See also the paragraph on this subject in the NITTY-GRITTY section.