Technical Aspects of Loading

KEY The following are a series of sections devoted to various aspects of loading and how the loader copes with them. It is appreciated that several of the topics covered are fairly technical and the average reader may have neither the background nor the interest to persevere with all of them. Before proceeding, however it might be sensible to define what exactly is meant by 'the loader' in this documentation. A loader is a piece of system software whose job it is to set up a suitable machine environment for executable files. There are several different types of loader, however the 2900 EMAS program loader, which is part of the subsystem, is an example of the class of loader known as a linking loader. As well as loading executable files, a linking loader handles external linkage between executable modules at load time. This eliminates the need for a separate link-edit step or for module collection or consolidation.

Executable files - EMAS 2900 object file format

KEY EXECUTABLE,OBJECT As stated above, the most basic task of the loader is to load executable files i.e. EMAS 2900 object files. These are normally generated as the primary output of the various language compilers available on EMAS. The files conform to a standard format so that, subject to any parameter passing restrictions imposed by the various languages, cross-calling between modules written in different languages is possible. The essence of EMAS is shareability and object file format is designed to exploit this. Many users can be executing the same object file at the same time with all the efficiency and savings in resources that implies, while each has his own unique values of variables and run-time addresses. Therefore an object file consists of two parts - one shareable and capable of being executed directly in place and the other unshareable and representing a database for initialisation. To achieve overall shareability of object files, areas described by the database to contain run-time dependent items are set up by the loader in the user's private space. The shareable part of the object file comprises two separate areas, the executable code(CODE) and the shared symbol tables(SST). The latter contains information relevant to run-time diagnostics. The unshareable database, sometimes known as the General Linkage And initialisation Pattern or GLAP, describes five separate areas to be set up by the loader - the general linkage area(GLA), the procedure linkage table(PLT), the unshared symbol tables(UST), the initialised common areas(INITCMN) and the static initialised area on stack(INITSTACK). The first four of these are set up in the 'user GLA', which is a file specially created by the loader for each user, and contain such user dependent information as global variables, linkage tables to external objects and common areas. The INITSTACK is set up by the loader in a reserved area of the 'user stack', which is another file created by the loader for the user when required, and used exclusively for stack operations. The executable code always refers to run-time dependent items in terms of offsets in the user gla or the user stack to achieve the desired generality and shareability. Note that the unshareable areas in total are generally very small relative to the size of the (shared) object file. This is why sharing is so valuable and is one of the great virtues of EMAS object file format.

EMAS fundamentals - Stacks, GLAs, Logging-on, Stack switching.

KEY STACK,GLA As implied by the previous section an executing object file will require access to a stack file, where local variables and the intermediate results of calculations are stored, and a 'gla' file, where linkage information, global variables, common areas etc. unique to that process are kept. In this document we are primarily concerned with the loading and linking activities which take place at the subsystem interface. However it may be of some interest to place this in context by considering how a process is created and how the system protects itself against programs which, so to speak, 'go berserk'. The log-on sequence is initiated by a request from the front end processor which is passed through the global part of the Supervisor, the Global Controller, to process DIRECT. DIRECT creates several files on behalf of the embryonic process: #STK, the process or base stack on which the process Director and Subsystem will run, #LCSTK, the Local Controller stack, on which the process local Supervisor will run, #DGLA, a file for the Director's gla, T#IT, a file used for i/o buffers and #UINFI, a file to contain information about the user. DIRECT then calls the Global Controller to start the process. The Global Controller starts up the Local Controller for the process which loads and calls the local copy of the Director (running on #STK, gla in #DGLA). The Director continues the initialisation sequence by creating and connecting another stack file, #STK, the signal stack, which is used when contingencies occur. At the end of Director's initialisations it connects the subsystem basefile then creates the file #BGLA, the base gla. The local Director loads and calls the Subsystem (running on #STK, gla in #BGLA). After some Subsystem initialisation we finally arrive at 'command level'. It will have been observed that at each stage a given component loads and calls the next in the sequence. Each component has its own piece of code which functions as a loader. The Subsystem's piece of code is what we are calling 'the loader' in this document. It will load and call the next level up, i.e. user commands. The other 'loaders' are all short and simple since they only have one task to perform. The Local Controller runs at a higher level of privilege than the rest of the user process and the two can be regarded as co-operating processes with the Local Controller in charge. When cpu becomes available to the process as a whole, the Local Controller has priority and runs on its own stack. When the local Director is called a stack switch is executed and the Director (and eventually the Subsystem) runs on the stack file #STK. To summarise thus far: after the log-on sequence, Subsystem code is executing, stack operations are being carried on the base stack (#STK) and linkage information and own variables for the system are held in the base gla (#BGLA). The situation remains thus until the first command which is not in the subsystem is called. User commands are intrinsically less 'trustworthy' than system commands, but run at the same level of privilege. To protect the system, which is running on the base stack, a new stack, the user stack (T#USTK), is created. A stack switch is then executed which ensures that the user command runs on this stack. Return from the user command causes a return to the base stack. A catastrophic program failure which corrupts the user stack will not therefore corrupt the system. The user stack has a hardware imposed upper size limit of 252K. Of this 252K, a portion can be reserved by the user by the OPTION(INITSTACKSIZE= ) command. This area, though part of the stack, is essentially static; normal stack operations take place between the top of the initialised stack area and the top of the stack. The initialised stack area is used by some languages, especially FORTRAN, to store variables between calls of routines. In the case of FORTRAN, the language definition guarantees preservation of variables between calls of the same function or subroutine. In normal stack operations all local variables would be lost between calls. The loader distinguishes between routines which are to be 'permanently' loaded (i.e. those which are to remain loaded to the end of the current session or the first call of RESETLOADER) and those which are only loaded until the end of the current command. To avoid fragmentation of the initialised stack area, therefore, 'permanent' initialised stack is taken from the top of the area and temporary initialised stack from the bottom. The gla requirements of permanently loaded files are taken from the basegla for convenience but a call to load the first temporarily loaded object file triggers off the creation of the user gla, T#UGLA. This file is used to satisfy the gla requirements of all temporarily loaded object files - except bound object files which are treated somewhat differently (see below).

Loader tables

KEY Loading information for subsystem entries is held in a single file which is shared by all users. Private loader data in the form of a set of tables for each user is held in a file, T#LOAD, created at subsystem start up time in each process. T#LOAD is organised into 3 areas each of which can be extended as required up to the maximum file size for T#LOAD imposed by the process. The areas are assigned to 1) a table for external references, 2) a table for 'permanently' loaded entry points and 3) a table for temporarily loaded entries. Each table is similarly organised and consists of a set of listheads (currently 251) from which entry or reference records, as appropriate, are organised in chains. The particular listhead that a specific entry or reference is attached to is determined by hashing the name. This form of organisation has the advantage of short chain lengths (and hence fast look up) and compact tables. Additionally, the entry tables operate on a last-in first-out basis which eases many of the problems associated with unloading.


KEY LOADLEVEL All object files require some unshareable space from the gla when they are loaded. 'Permanently' loaded object files acquire space from the base gla and temporarily loaded files from the user gla. When a command or program has been run or a load has failed in mid flight for whatever reason, then the loader must know what to unload. Provided that the loader has noted the address of the next free byte of user gla before loading began and the same information at termination then all the object files assigned areas of user gla within the starting and finishing addresses of the 'top of the user gla' should be unloaded. Similarly, if a load has failed, then in addition to unloading any temporarily loaded files, any 'permanently' loaded files which were loaded as a result of the failed load should also be unloaded. This is done by noting the start and finishing values of the top of the base gla. However as well as distinguishing between permanently and temporarily loaded files, EMAS recognises 'degrees of temporariness'. For example, EMAS provides facilities whereby files may be loaded from within programs, executed, then unloaded again before the calling program resumes. The subsystem routine CALL and its FORTRAN equivalent EMASFC operate like this as does the EMAS command RUN when it is issued from within a program. Each time we decide that we want to be able to do a partial unload of temporarily loaded files, we must store away the current 'top of the gla' at least. To formalise the concept we define a global integer LOADLEVEL. A change in loading conditions as described above is associated with an increment in LOADLEVEL and the eventual partial unload with a decrement. Increments and decrements are always 1 (except in the case of receiving INT:A described below). Strictly speaking it is sufficient to know the range of gla addresses to be discarded, but unloading can be made much faster by storing some extra information when the loadlevel is incremented. Just as the gla is organised on a last-in, first-out basis so also are the initialised stack and the loader's tables of entries. Unloading can be vastly speeded up if the current values of the 'tops' of these areas are noted. When the loadlevel is incremented, then, we are effectively taking a snapshot of conditions at that moment, so each value of LOADLEVEL has an associated record, LLINFO, in which the required data is kept. When we unload a loadlevel then we discard all loading information associated with it. Formally, each loadlevel has three associated integers: - the offset of the first entry in loader's entry tables at this loadlevel - the address of the first byte of gla used at this loadlevel - the address of the first byte of initialised stack used at this loadlevel Currently, loadlevel is defined as an integer in the range 0 - 31 and with each loadlevel there is an associated record, LLINFO, which holds the addresses required at unload time. Level 0 and level 1 have predefined meanings. Level 0 is the level at which 'permanently loaded' files are loaded. (In this context 'permanently loaded' means loaded until the end of the current session or a call of RESETLOADER. A file is 'permanently loaded' if it is found via the subsystem base directory or is specifically PRELOADed). 'Permanently loaded' files have their entries in a separate permanent entries table, use gla taken from the base gla and initialised stack from the 'permanent initialised stack' area. Loadlevel 1 is 'command level'. Levels 1 to 31 use the temporary entries table, the user gla and the 'temporary initialised stack area' respectively. The programmer cannot directly alter LOADLEVEL but can select whether or not a file is to be permanently loaded. The current value of LOADLEVEL is returned by a supplied function, CURRENTLL. As stated above, LOADLEVEL is normally incremented or decremented by 1 but the loadlevel will revert automatically to 1 - command level - on the receipt of INT:A whatever the current value. To illustrate, consider the following example: Suppose we have a user written command which makes several external references, at least one of which will be found via the subsystem base directory. This command also calls CALL at some point. The following events should occur when the command is executed: (We restrict our attention to the loader entry tables for simplicity.) Just before the command is run then the permanent entries table will contain the entry point names of any 'permanently loaded' files or specially defined items (see DATASPACE,ALIASENTRY). The loadlevel 0 entry table pointer will point to the next free byte in the permanent entries table. The temporarily loaded entries table will be empty, so the the loadlevel 1 entry table pointer will point to the first free byte. Since we are at command level then the loadlevel is 1. The command is then typed and loading commences. The first file to be loaded is the file which contains the command just typed, and entry point names from this file are added to the temporarily loaded entries table. We have assumed that this file contains several external references which were not already loaded and the loader now starts to search for each in turn, loading more files as required. Provided that these references are not found through the subsystem base directory then further entry points are added at loadlevel 1 to the temporarily loaded entries table. Finally a reference is encountered which can be satisfied by loading a file pointed at by the subsystem base directory. This must be 'permanently' loaded so the local loadlevel switches temporarily to 0 and all the entries are added to the permanent entries table. And so on until the load is complete. Note that the loadlevel 0 entry table pointer is not updated until the load has completed successfully conclusion for, if otherwise, everything loaded by a failed load is unloaded. Execution of the code then begins and proceeds until CALL is called. This causes a jump to the loader which has to load the entry point CALLed. Loadlevel 1 is 'frozen' and the loadlevel incremented to 2 with the loadlevel 2 entry table pointer set to the next free byte in the entries table. If the CALL causes files to be loaded then the entry point information is added to the temporarily loaded entries table (or the permanent entries table if found via the subsystem base directory). Control is then passed to the code CALLed, which executes. Assuming no failure, control is passed back to the loader which unloads everything at loadlevel 2. The loadlevel is decreased to 1 and loadlevel 2 abandoned. Execution of the user program resumes and proceeds to termination at which everything loaded at loadlevel 1 is unloaded and we are back where we began with the exception that there may be more permanently loaded material.

Bound object files

KEY Before discussing the binding of object files, a short resume of object file structure would be in order. From the viewpoint of the loader, object files on EMAS consist of seven distinct areas each of which can be assigned to one of two categories: shareable and unshareable. The object file in total is shareable, so the initial stage in loading an object file is to create the unshareable areas in private (unshared) space. Five of the seven areas are normally unshared; four - PLT,GLA,UST and INITCMN - are assigned space in the 'user gla' which is a private file used solely for procedure linking and data areas, and the fifth, INITSTK, an area from the initialised stack area of the user stack. The remaining two - the CODE and SST - are usually, though not always, shareable. In essence, then, the loader requires the addresses of the separate object file areas, which in turn boils down to the connect addresses of three files - the object file, the user's gla file and the user's stack. Now in the general case, none of these addresses is known in advance so the loader has to go to every location which requires an actual run time address and plant the appropriate value. This can be expensive as these lists of 'relocations' can be long and may involve extensive paging as the loader jumps from location to location filling in addresses. Obviously if it was possible to fix the addresses of the object file areas beforehand then all the relocations could be done once, independently of the loader. Loading the object file thereafter would avoid the necessity of doing relocations. This process is known as 'binding the object file' and can be done by the EMAS object file editor MODIFY, details of which are available elsewhere. Using MODIFY, the user nominates two sites, one for the shareable areas and one for a file to contain the four gla areas. (The user stack from which any INITSTK requests must be satisfied is assigned the same fixed site for all users.) The utility then processes the object file and obeys the relocation requests. When a bound object file is loaded then the loader checks the shareable and unshareable preferred sites. If the shareable preferred site is free then the object file is disconnected and reconnected at that site. If the site is occupied the object file is left where it is and all relocation requests involving the CODE or SST are executed. If the unshareable preferred site is free then a temporary file, T#GLAnn where nn is a unique identifying suffix chosen by the system, is created at that site and the PLT, GLA, UST and INITCMN areas set up in it. Should the site not be free then any available site is used for the T#GLAnn file, the unshareable areas are set up and all relocation requests involving these areas are honoured. The INITSTK requests are handled slightly differently. A user has only one user stack so it follows that only one file can claim the preferred site. All others will be assigned other areas within the initialised stack and for these cases all relocation requests will have to be done. Loader monitoring (#MONLOAD) will warn when a bound file cannot be connected at all its preferred sites.

Exceptional conditions

KEY (Condition 1 refers to ALL object files, 2 only to bound files) 1. Code is unshareable if a) the area is flagged as such in the object file (rare) or b) the area crosses a segment boundary (A segment is 256K). This latter condition can arise if an object file is a member of a large (>256K) pd file and its CODE area straddles the segment boundary. In these circumstances, the loader will create a temporary file called T#CODEnn where nn is a unique suffix chosen by the system, and copy the CODE and SST areas into it. This is expensive and should be avoided if at all possible by suitable file organisation. Note that pd files can be checked for object code crossing segment boundaries by the command ANALYSE(pdfilename,M) which will print a warning.
2. Code cannot be connected at its preferred site if the object file is a member of a pd file, even if the preferred site is free. This means that bound files should NEVER be collected into a pd file for running. Note that any T#CODE or T#GLA files created during loading will be destroyed when the file that gave rise to them is unloaded.

External References

A piece of code can refer to other pieces of code or data areas external to it. For each reference there is an associated location eight bytes long in the gla of the calling routine into which a 'descriptor' to the called item must be placed by the loader. A descriptor consists of two integers. The first contains fields which describe the type, and for data descriptors, the length, of the item and the second holds the address of the item. For a code reference the loader must plant the complete descriptor whereas for a data reference the loader is only required to provide the address. With some compilers (e.g. COBOL) and some non-EMAS generated 2900 object code, there are items designated as 'single word references'. These references are not defined as either code or data references - although in practice they are likely be one or the other - and indeed the loader has no information available on their nature. The loader is only required to provide the address of these items. In EMAS 2900 object file format, code references are flagged as either static or dynamic. Static references are expected to be satisfied at the same time the file is loaded whereas dynamic references are only satisfied when actually called. Data references and single word references are assumed to be static. However it is not always convenient or desirable to accept these strictures and the LOADPARM command can be used to instruct the loader to pursue a different course of action. For instance, at load time it may be desired to load the absolute minimum number of object files necessary to do the desired run i.e. a reference would only be satisfied if called, whether it started life as a static or as a dynamic reference. LOADPARM(MIN) has the effect of treating all references as dynamic. Another possibility is that even after a complete search, there are still some static code or data references which could not be found. By default the load would fail but we may wish to run anyway and take the chance that the unsatisfied references would not be called. It is not possible to leave the contents of the descriptor locations in the gla untouched since a call to any of them would prove catastrophic. Similarly it is pointless to make them dynamic since a search for them has already failed. The solution is to make them a special type - 'unresolved' - which will ensure that the effect of a call will be a controlled rather than a catastrophic failure. LOADPARM(LET) follows this course of action. It is fairly obvious how the loader can satisfy references while loading files but it is not so obvious how a program can be interrupted in full flow for the loader to do some more searching and loading, then the program resume as though nothing had happened. To do this, a special feature of the 2900 hardware, the hardware escape mechanism, is used. Dynamic and unresolved references have a special descriptor known as an escape descriptor planted at the required location in the gla. When the reference is called, the escape descriptor is recognised by the hardware which invokes the escape mechanism. The net effect of this is to suspend execution of the calling routine and cause a jump to a previously nominated piece of code in the loader. The loader will search for and load the entry, if appropriate, or will fail the calling routine tidily. If the loader search has been successful then the escape descriptor in the gla is overwritten by the actual descriptor, the environment restored exactly as it was at the moment the escape mechanism was invoked and control is passed back to the called routine. N.B. For most uses of the loader, dynamic references do not occur unless they are specifically requested. The difficulties described in the following subsections should not therefore affect the innocent user. For those who want to use dynamic references, loading code entries - routines, functions, subroutines etc. - is safe and reliable. But dynamic references to data should be avoided unless you are very sure of what you are doing.

Dynamic Data References

KEY Code references pose no problems as the eight bytes of the descriptor location contain no information and can be safely overwritten, however data references have the first four bytes already assigned to the descriptor type and length and the second four contain an offset which has to be added to the address supplied by the loader. This information must therefore be safely stored away by the loader before the location can be overwritten by an escape descriptor and must be restored again exactly during the escape sequence. The basic assumption behind the foregoing is, of course, that it is possible to invoke the escape mechanism at the appropriate time. The trigger for this is the presence of an escape descriptor in the hardware descriptor register. Calling a code reference will ensure that this occurs, but whether a data reference does or not depends on the individual compiler writer. As was pointed out above, all that is required to satisfy a data reference is an address, not a full descriptor, and in some situations the item may be addressed via an offset from another register e.g. 'load the integer 10 bytes on from the address in the CTB register'. If this occurs the escape descriptor is never loaded into the descriptor register, the escape mechanism is not invoked and the program fails catastrophically. On EMAS however, for the currently most heavily used compilers, i.e. IMP, FORTRAN and PASCAL, the situation is not as bad as the last paragraph might suggest. FORTRAN and PASCAL only refer to common data areas which are always created by the loader at load time by default, so dynamic data references will never arise. IMP on the other hand does have %extrinsic data items and these do create dynamic data references if the relevant loader parms are set. Of the various possible types only extrinsic records can cause trouble; all the others do invoke the escape mechanism. The problem can be avoided by ensuring that any records which are shared among program modules are data entries in the first module to be loaded. Note, however, that if such a reference is not called directly but the appropriate entry point happens to be in a module which gets loaded to satisfy a different reference, then the dynamic data reference will be satisfied correctly.

'Dynamic' Single Word References.

KEY As the loader does not have any information as to the nature of single word references, then it cannot make them truly dynamic in the sense that code and data references can be made dynamic. The solution adopted has been to make them 'pseudo dynamic' by planting an impossible address (-1) in the single word location in the gla. The user is warned of the dire consequences of calling the reference but the program is allowed to run. However, if a dynamic single word reference is not called directly but happens to be in a module which is loaded to satisfy a different reference, then the single word reference will be fixed up correctly. A program is never allowed to run with an unresolved single word reference.

Possible states of an external reference.

KEY From the above discussion it is possible to define and summarise the four possible states for an external reference: 1. Unsatisfied. The location of the descriptor to the reference in the gla has been noted but the entry point required to satisfy the reference either has not been searched for, or has been and not found. Nothing has been written to the descriptor location. A program or routine is never allowed to run with unsatisfied references. 2. Dynamic. Dynamic references can arise in several ways: by the user's explicit request (LOADPARM MIN), or by the programmer's request (%dynamicroutinespec, etc) or in complicated loading situations when some object code is unloaded while another module, which refers to it, remains loaded. If a reference is dynamic then the location of the descriptor to the reference has been fixed up with an escape descriptor. The entry point to satisfy the reference has not been searched for. A program or routine is allowed to run with dynamic references. If the reference is called during execution then the escape mechanism is invoked which causes a jump to the dynamic reference code in the loader. The loader will conduct a full search for the entry name. If found then the required entry will be loaded, the escape descriptor overwritten and execution resumed, otherwise a failure occurs. 3. Unresolved. The location of the descriptor to the reference in the gla has been fixed up with an escape descriptor which, if accessed, will cause a jump to the unresolved reference code in the loader. The entry point required to satisfy the reference may have been searched for, but if it has then it has not been found. A program or routine may be allowed to run with unresolved references, but only if the user explicitly requests it (by LOADPARM LET). If the reference is called then the escape sequence is entered, no searching is performed and the program or routine fails tidily. 4. Satisfied. A reference is satisfied when the location of the descriptor to the reference in the gla has been fixed up with a descriptor to an entry point of the same name and type. Normally a satisfied reference is of no further interest to the loader, but there are special circumstances, referred to in 2 above, when the loader has to remember where specific satisfied references occurred. This is in case the reference has to be 'unfixed' and made dynamic.

Loader action on encountering an alias

KEY ALIAS This is best illustrated in parallel with an example. Suppose a loader search has been initiated for entry point AA and further suppose that there are 3 user nominated directories in the searchdir list - SEARCHDIR1, SEARCHDIR2 and SEARCHDIR3. The loader searches in the order given in Note 1. Suppose AA is not loaded and the loader encounters the alias AA=BB in the active directory. The general rule is that the loader always follows the right hand side of the alias but stores an 'alias branch record' on a small, private, last in - first out stack. This record contains the l.h.s. of the alias and an integer to indicate in which directory the alias occurred. The stack can hold up to 10 records. If a chain of aliases comes to a dead end then the loader pops the last record on the stack. The last l.h.s. is restored and the search for it resumes in the next directory down the search list from the one where the alias occurred. When a new alias occurs the loader first checks the stack of alias branch records. The failure 'Alias chain too long' will occur if the stack is already full. If an identical record appears on the stack then a closed loop of aliases has been detected, e.g. A=B,B=C,C=A, and a failure is reported. Both of these failures will print the current alias branch record stack for additional information. If neither failure happens then a new alias branch record is pushed on to the stack and the search resumes for the r.h.s of the alias at the top of the search list. To return to the example, we push an alias branch record on to the stack and start looking for BB at the top of the search list (i.e. the system entries). Suppose we find the further alias BB=CC in SEARCHDIR2. A new record is pushed on to the stack and we start searching for CC. Suppose no entry point CC is found. We have reached a dead end so the loader pops the top record on the stack to find out what spawned the search for CC. This was the alias BB=CC in SEARCHDIR2 so we restore BB and start searching for it in SEARCHDIR3. Should BB not be there then the stack is popped again and we find that this alias was generated by AA=BB in the active directory. AA is restored and the search resumes in the subsystem base directory. And so on until we find a file to load or exhaust the search. Although some time has been taken to describe what happens in complex situations, it is unusual to find an alias chain longer than 1.