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.
LOADLEVEL
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.