[comp.unix.wizards] Dynamic function loading

perl@rdin.UUCP (04/10/87)

I posted this request several months ago but our news reception went
flakey after the newsgroup reorganization and if I got any responses,
my system lost them.  So, if you have any information on this, please
let me know, even if you already sent it to me.

A number of people have pointed out that UNIX has the ability to link
functions into a running program.  Pointers have been given to look at
the documentation for the -A and -T options of ld(1).  Well, I and a
number of other netters have stared at the documentation as long as our
little brains can stand and the bright flash of light that was promised
hasn't come to us.  I know someone out there knows how this works since
Franz Lisp does it.  I can dig that you use "ld -A -T" to create a file
that can be loaded into a specific location within a running process,
but what do you do to do the actual loading?  Exec? What strange
arguments or calling sequences do I have to know about?

As I have already recieved a number of responses requesting the results
of this query, I will post a summary.

Thank you.

Robert Perlberg
Resource Dynamics Inc.
New York
{philabs|delftcc}!rdin!perl

bzs@bu-cs.UUCP (04/12/87)

>I can dig that you use "ld -A -T" to create a file
>that can be loaded into a specific location within a running process,
>but what do you do to do the actual loading?  Exec? What strange
>arguments or calling sequences do I have to know about?

No, not exec, wrong thought, bzzzt.

You'll need to know the symbol you wish to link to (eg. the function
in the file to be loaded.)

Then grovel the sizes out of the header (see a.out.h) from the object
deck and the offsets.

Allocate the memory for the mess, toss the object into this (after
having been relocated with ld of course.)

Then make the "link". You will need to associate the code with a
function pointer, depends on how your program plans to notice that it
is going to use this stuff.

For example, say you had a jump table:

struct jtable {
	char *fnname;
	int (*fnptr)();
} jtable[MAXFNS];

then called via funcall("_dynsubr"), funcall would look something like:

	funcall(name) char *name;
	{
		struct jtable *jp;

		for(jp = jtable; jp < &jtable[MAXFNS]; jp++)
			if(strcmp(jp->fnname,name) == 0) { /* found it */
				if(jp->fnptr == NULL) /* not loaded yet */
				    if((jp->fnptr = funload(name)) == NULL)
					return(CANTLOADERROR);
				return((*jp->fnptr)());
		return(NOTFOUNDERROR);
	}

Obviously you would adjust the code above to allow for args to the function
and return values but it should give you the flavor (also, a linear search
is unnecessary if you need something faster.)

The code for funload() is too hairy to throw in off the top of my head
but groveling the stuff I mentioned above should steer you in the right
direction.

Or, I could send you my consulting schedules :-) It's not that hard, but
it takes some attention to detail.

	-Barry Shein, Boston University

garry@batcomputer.UUCP (04/14/87)

In a recent article bzs@bu-cs.BU.EDU (Barry Shein) wrote:
>The code for funload() is too hairy to throw in off the top of my head
>but groveling the stuff I mentioned above should steer you in the right
>direction.
>
>Or, I could send you my consulting schedules :-) It's not that hard, but
>it takes some attention to detail.

Several problems come immediately to mind:

- There's no way to get more than 65K bytes at a time from K&R-standard
  malloc(). You have to mess with sbrk(), I guess. There's a tickle in
  my brain which says "don't call sbrk yourself if you want the run-time
  library not to get confused."  Is my tickle true on some systems?

- How do you fix up the relocations in the image you're bringing in? Or
  do you link it at an absolute address? (ick)

- What do you do on machines with separate Instruction/Data spaces?

- Does a.out.h tend to be quite similar or quite different on different
  systems?

As an embellishment to Barry's scheme, I'd suggest:

    int foo () {
  	static	int	(*real_foo)() = null;
	if (real_foo == null) real_foo = please_load ("foo","real_library");
	(*real_foo)();
    }

This gives you a regular entry point to call and avoids the string
comparisons on calls after the first.

This whole mess should be done properly and thoroughly by the system, in
a new system call. Shareable library images are amazingly handy - you can
play tricks at run time, executable images get a lot smaller, and, most
important, software maintenance is *much* easier - there is rarely/never
such a thing as a "required re-link" - you just pop the new library image
into the system. I'm coming from VMS land; I know other systems have this
feature too. Is there any hope for it in Unix?

I'd also mention "faster linking to large libraries", but I've recently
had good luck with using "ld -r" to simplify my library entries and speed up
the (eventual) linking.

Anyhow, this subject has come up many times. Does anyone have any code that 
they'd care to post to mod.sources?  (For more than one machine, pray?)

garry wiegand   (garry@oak.cadif.cornell.edu - ARPA)
		(garry@crnlthry - BITNET)

ramsdell@linus.UUCP (John D. Ramsdell) (04/14/87)

Here is a simple example giving the details of
dynamic function loading using ld -A.
John

#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	dyn.c
#	fun.c
#	makefile
# This archive created: Tue Apr 14 14:36:34 1987
export PATH; PATH=/bin:$PATH
if test -f 'dyn.c'
then
	echo shar: will not over-write existing file "'dyn.c'"
else
cat << \SHAR_EOF > 'dyn.c'
/* Dynamically loads a function constructed using ld -A .. -T ... */
/* John D. Ramsdell -- April 1987 -- The MITRE Corporation. */
#include <stdio.h>
#include <sys/types.h>
#include <a.out.h>

typedef int (*intfun)();

caddr_t fun, sbrk();

caddr_t funload (fun)
     caddr_t fun;
{				/* Loads a function from a file. */
  FILE *obj; struct exec hdr[1]; int size;
  obj = fopen ("fun", "r");	/* Name hardwired in this example. */
  fread (hdr, sizeof(*hdr), 1, obj);
  size = hdr->a_text + hdr->a_data + hdr->a_bss;
  brk (fun);			/* Return of brk and sbrk should be checked. */
  fun = sbrk (size);		/* Allocate space. */
  fread (fun, size, 1, obj);	/* Load code. */
  fclose (obj);
  return fun;
}
  
main ()
{
  printf ("Start at 0x%lx.\n", sbrk (0));
  fun = (caddr_t) 0x25000;	/* Must be same number used in ld command. */
  printf ("Load at 0x%lx.\n", fun);
  fun = funload(fun);
  printf ("New end at 0x%lx.\n", sbrk (0));
  printf ("The answer is %d.\n", (*((intfun) fun))());
}
SHAR_EOF
fi # end of overwriting check
if test -f 'fun.c'
then
	echo shar: will not over-write existing file "'fun.c'"
else
cat << \SHAR_EOF > 'fun.c'
int dynamically_loaded_function ()
{
  return 42;
}
SHAR_EOF
fi # end of overwriting check
if test -f 'makefile'
then
	echo shar: will not over-write existing file "'makefile'"
else
cat << \SHAR_EOF > 'makefile'
all:	dyn fun

dyn:	dyn.o makefile
	cc -u _end -o $@ dyn.o

fun:	fun.o dyn makefile
	ld -A dyn -N -X -o fun -T 25000 fun.o -lc 
SHAR_EOF
fi # end of overwriting check
#	End of shell archive
exit 0

bzs@bu-cs.BU.EDU (Barry Shein) (04/17/87)

Responding to some notes by Garry Wiegand
>- There's no way to get more than 65K bytes at a time from K&R-standard
>  malloc(). You have to mess with sbrk(), I guess. There's a tickle in
>  my brain which says "don't call sbrk yourself if you want the run-time
>  library not to get confused."  Is my tickle true on some systems?

I don't believe this is a limit (65KB/malloc, max) on any malloc()
that's been written on a 32-bit machine. Obviously on a PDP-11 it
would be true due to architectural limitations, but the object code
you are loading had better reflect that (note that with dynamic
loading you can effect an overlay linking by swapping in/on object
code in the same area and artificially extend the text space beyond
any architectural limit.)

I'm sure *one* such malloc() exists, a better malloc can be written,
maybe there's even one available (I think there's one inside GNU Emacs.)

>- How do you fix up the relocations in the image you're bringing in? Or
>  do you link it at an absolute address? (ick)

This is how it all started. You do a system("ld -A xyzzyx foo.o") or
equivalent (eg. fork/exec) where xyzzyx is determined at run-time
(what the malloc returned.) You can either do this when you are about
to load it (it's quite fast on most machines) or you can re-locate all
your object decks as part of the make process and pre-assign a table
which indicates where these things are to be located (also has to be
re-done each time the objects are re-built, I just added a -L foo.o
flag to the command line which would re-build decks, but it's not
necessary, do it at load-time.)

>- What do you do on machines with separate Instruction/Data spaces?

I'll be flippant: throw them away and replace them with a $1500 Atari
system with 1MB of memory (ok, ok, but it can be done, I forget how,
I believe my PDP11/45 at Harvard Chem had an overlay linker under V7,
my memory could fail me.) You get what you pay for.

>- Does a.out.h tend to be quite similar or quite different on different
>  systems?

You can get *reasonable* portability (look at the GNU Emacs unexec()
routines that rely on this and manage COFF and 4.2 on many different
systems.) The routines that are sensitive to this format would probably
be less than 100 lines of code, they can be re-written for each port.
It's mainly a matter of extracting the portable and non-portable parts
of the code. Remember, this is self-made magic, expect some portability
problems. By and large there's COFF and 4.2 and that's about it although
you'll run into slight aberrations if you port it to enough systems.

Garry, do you realize the reason I am answering this is because I
delivered a running system like this to ComputerVision a short while
back? I can't send the code because I don't own it. Also, the reason
people seem to find this hack is because that's how Franz Lisp works.

Yes, your scheme works fine, I had implemented that as well as the
table driven method (declaring a subroutine for each and having it
load if necessary and jump to the routine.) The other observation was
that you can write a program which builds all the stubs automatically
from the symbol tables at Make time. It can actually just be a
post-processor to nm written in awk, no need to do any serious
object deck groveling.

If you want a code example I suggest Franz Lisp. The fasload stuff
is fairly small and straightforward.

	-Barry Shein, Boston University

cheng@ntvax.UUCP (04/17/87)

/* Written 12:35 pm  Apr 10, 1987 by rdin.UUCP!perl in ntvax:comp.unix.wizards */
/* ---------- "Dynamic function loading" ---------- */
I posted this request several months ago but our news reception went
flakey after the newsgroup reorganization and if I got any responses,
my system lost them.  So, if you have any information on this, please
let me know, even if you already sent it to me.

A number of people have pointed out that UNIX has the ability to link
functions into a running program.  Pointers have been given to look at
the documentation for the -A and -T options of ld(1).  Well, I and a
number of other netters have stared at the documentation as long as our
little brains can stand and the bright flash of light that was promised
hasn't come to us.  I know someone out there knows how this works since
Franz Lisp does it.  I can dig that you use "ld -A -T" to create a file
that can be loaded into a specific location within a running process,
but what do you do to do the actual loading?  Exec? What strange
arguments or calling sequences do I have to know about?

As I have already recieved a number of responses requesting the results
of this query, I will post a summary.

Thank you.

Robert Perlberg
Resource Dynamics Inc.
New York
{philabs|delftcc}!rdin!perl
/* End of text from ntvax:comp.unix.wizards */

wagner@iaoobelix.UUCP (04/18/87)

People having access to FranzLISP and/or CProlog+ should have a look at the
respective bits in the source of these programs. They implement a private
loader and they do all the relocation stuff. Problems remain, however, e.g.
with the CProlog+ loader which doesn't resolve references among separately
loaded object files. It is nice code, anyway...

Juergen Wagner,		     (USENET) ...seismo!unido!iaoobel!wagner
("Gandalf")			 Fraunhofer Institute IAO, Stuttgart

papowell@umn-cs.UUCP (Patrick Powell) (04/21/87)

This subject seems to come up annually,  and here is the most disgusting
hack to do it that you have ever seen.  I take no credit for this thing.
By the way,  I also have a hacked up version of the S5Rx loader which
supports -A so you can do the same thing.  I think this is now standard in
S5Rx so it should not be terribly difficult to port.
Patrick ("You have been warned") Powell

#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	Makefile
#	README
#	dynamic_load.3l
#	dynamic_load.c
#	main.c
#	test.c
# This archive created: Tue Apr 21 09:16:08 1987
export PATH; PATH=/bin:/usr/bin:$PATH
if test -f 'Makefile'
then
	echo shar: "will not over-write existing file 'Makefile'"
else
cat << \SHAR_EOF > 'Makefile'
CFLAGS = -g
all:	load test
load:	main.o dynamic_load.o
	cc -g -o load main.o dynamic_load.o
test:	test.o
	load -o test.o -e _function -e _test_function
SHAR_EOF
fi
if test -f 'README'
then
	echo shar: "will not over-write existing file 'README'"
else
cat << \SHAR_EOF > 'README'
Patrick Powell, U. Waterloo, Feb. 6, 1985

This is the dynamic loading code, as suggested by Bill Joy (U. C. Berkeley),
and as stripped out of the Franz Lisp routines.  It is a terrible, desperate,
one shot attempt to use dynamic loading.  As far as I know, there is no
copyright pending,  and if you want to claim it for your own, go ahead.
Don't complain to me when people flame you about the terrible style, etc.

This code (or a version of it) works on the Berkeley 4.X systems.  Due to
a lack of forsight, the ATT System V loaders do not support the dynamic
loading mode.  I have a hacked version of the System V loader which does,
but it is NOT for distribution.   It is even worse than the loading code.

Don't complain to me when you cannot find the text file.  I know about that
problem already, and it is not solvable.  GGGGRRRR.

Remember: don't make any calls that allocate memory when doing the loading.
Read the comments scattered here and there.

By the way, I have been informed that there are no copyright violations
in posting or using this.

Patrick Powell
SHAR_EOF
fi
if test -f 'dynamic_load.3l'
then
	echo shar: "will not over-write existing file 'dynamic_load.3l'"
else
cat << \SHAR_EOF > 'dynamic_load.3l'
.TH "DYNAMIC LOAD" 3L  "19 July 1984"
.UC 4
.SH NAME
Set_program_name, Load_file  \- support dynamic loading of functions
.SH SYNOPSIS
.PP
.ft B
.nf
.in +.5i
.ti -.5i
struct symbol_references{
char *name;	(pointer to entry point name)
int (*function)(); (call address)
};
.ti -.5i
Set_program_name( str )
char *str;
.ti -.5i
Load_file( objects, entries, library )
char **objects;
struct symbol_references *symbols;
char *library;
.ft P
.fi
.in -.5i
.SH DESCRIPTION
The
.I "Dynamic Load"
routines support dynamic loading of functions into an already executing
program.
.I "Set_program_name"
is called with the name of the currently executing binary file.
The
.B PATH
environment variable is searched to determine the possible executing files.
If
.B PATH
was not provided, a default search path is used.
If a candidate binary file is not found,
then a nonzero value is returned.
.PP
.I Load_files
is passed a pointer to a list of structures containing
pointers to strings and corresponding entry point values,
a pointer to a list of object of library file names,
and a string containing loader options.
The structure list is terminated with a structure with a zero
entry, as is the object file list (see EXAMPLE below).
The UNIX loader
.IR ld (1)
is invoked,
and will search the supplied object files for global entry points
of the specified functions or variables.
If the load is successful,
the entry point values are placed in the structure.
.PP
The library string can be used to pass additional parameters to the
loader, such as a default set of libraries to be searched.
.SH EXAMPLE
.PP
.nf
char *objects = {
	"movies.o", "library.a", 0 };
struct symbol_references look_for[] = {
	{_good}, {_bad}, {_ugly}, {(char *)0} };
main( argv, argc )
	char **argv; int argc;
{
	if( Set_program_name( argv[0] ) == 0
		&& Load_file(objects, lookfor, "-lmath -ltermlib") == 0 ){
		(*look_for[0].function)();
		(*look_for[1].function)();
		(*look_for[2].function)();
	}
}
.fi
.PP
Another method of using this would be do have a symbol references
table in one of the object files,
and to request loading of the table.
It is possible to generate the table from a program,
by using the C compiler,
and to then load the output.
If this method is chosen, note that the C compiler should be run in
a writeable directory, like /tmp.
.SH "SEE ALSO"
ld(1),
lisp(1),
cc(1)
.SH DIAGNOSTICS
.I Set_program_name
and
.I Load_file
return
0
upon success,
non-zero otherwise.
Diagnostic messages are output on
.I stderr
using the
.I stdio
package.
.SH BUGS
.PP
You must be careful in how you specify your program name.
If you are doing peculiar things, you should provide the full path
name as the
.I argv[0]
value.
.PP
The entry point names must be specified in
.IR ld (2)
recognizable form.
For example,
the C compiler will prefix an underscore to external variable names.
The F77 compiler will prefix and append underscores.
Thus, the variable
.I var
will be
.I _var
if in a C object file,
and
.I _var_
if in a F77 object file.
.PP
The
.I Load_file
routine constructs a command using a fixed size string buffer.
If you have lots of entry points, this will overflow,
and you will get an error message.
One solution to this problem is to create an entry point table as part
of an object file.
Another solution is to modify the internal function that checks and finds
entry points in the symbol table.
.PP
The code that finds the values of entry points is very crude.
It has been partially speeded up to minimize the number of reads
that must be done, but will be very slow if a large number of
entry points are provided.  Again, this may be solved by adding
an object file with an entry point table, and determining that
table address.
SHAR_EOF
fi
if test -f 'dynamic_load.c'
then
	echo shar: "will not over-write existing file 'dynamic_load.c'"
else
cat << \SHAR_EOF > 'dynamic_load.c'
/*
	Run time loading of functions.
	- Basic technique.
		The loader ld(1) -A option specifies incremental loading.
		The output of the loader will be a file which can be read
		into an already executing program. The symbol table of the
		already executing program must be specifed.

		The currently executing file is searched for using the
		entries in the $PATH environment variable.  When it is
		found, the loader is invoked with
		ln -N		Do not make text portion readable or sharable
			-x 	no local symbols in symbol table
			-A <executing file>
				used to construct new symbol table
			-T <text end>
				specifies the text segment origin.
			-e <name>
				name of an entry point
			-o <filename>
				output file name
			<files>
				object files to be used.
			<library>
				libraries to be searched, etc.
			-lc
				default library
	Support functions:
	struct symbol_references{
		char *name;	(pointer to entry point name)
		int (*function)(); (call address)
		};
	Set_program_name( str )
		Must be called first to set up the program text file name.
		This is used in the loading process.
	Load_file( objects, entries, library )
		char **objects;
		struct symbol_references *symbols;
		char *library;

		The "objects" points to a list of object file names terminated
		with a null (char *)0.  For example, a static definition 
			char *objects[] = {"lib.a", "usr_lib.b", 0 };
		Note: currently, a maximum of 10 object files can be
			specified.  This limit is set by the compile time
			variable MAXOBJECTS.

		The "symbols" points to a list of symbol_references structures.
		Each structure has the name of a function, and a function
		address field which is set to the name of the function.
			struct symbol_references symbols[] = {
				{ _ugly }, { _really }, { (char *)0 } };
			NOTE: this is acceptable C, missing field initializers
			are supposed to be acceptable.

		The "library" should be null (char *)0, or a string containing
		loader library information.  For example:
			entries = "-lmath -ltermlib" 
		Note that this can also be used to specify a list of object
		fields, set up "undefined symbol information" to force
		loading from libraries ("-u _ugly mylib.a -lmath").

	IMPLEMENTATION:
		The functions use the stdio package, and write error
	messages on STDOUT.  If an error is detected, a 1 is returned.
	The PATHS environment variable is used to determine the executing
	function.  If it is not set, then a default set of paths is used.
		The sbrk() system call is used to allocate memory.  This
	should have no effect on a properly designed memory allocation package,
	such as malloc(3).
		The external entry point names must be specified in the
	form that the loader needs.  For example, the C name "ugly"
	would have to be specified as "_ugly", and the F77 name "ugly"
	as "_ugly_".
*/

#include <sys/param.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <stdio.h>
#include <a.out.h>

char Program_name[ MAXPATHLEN ];	/* saves the name of the program */
char Symbol_name[ MAXPATHLEN ];		/* name of the new object file */
extern char *Find_text_file(),
	*Strend(),	/* append to end of string */
	*strcpy(), *sys_errlist[], *mktemp();
extern int errno;	/* system errors */
extern caddr_t sbrk();	/* memory allocation */
struct symbol_references{
	char *name;
	int (*function)();
	};

/*
	Set_program_name( str )
		Checks the file name for execute perms,
		and then saves the name.
*/

Set_program_name( str )
	char *str;
    {
	(void)strcpy( Program_name, str );
	if( (str = Find_text_file( Program_name )) == 0 ){
		fprintf( stderr, "program name %s not found\n" , Program_name );
	}
	return( (str == 0) );
    }

/*
	Find_text_file( str )
		Find the pathname of the indicated executing file.
	1. Get the $PATH environment varible. If none, use a set of
		default paths.
	2. Check each directory in turn, by copying the directory name,
		then appending the file name.
	Uses the small Ok_to_exec(str) function
*/

char *
Find_text_file( str )
	char *str;
    {
	char *path, *directory, *getenv(), *strcpy();
	char fullname[ MAXPATHLEN ];

	if( (path = getenv("PATH")) == 0 ){
		path = ":/usr/ucb:/bin:/usr/bin:.:"; /* default */
	}
	while( *path ){
		if( *path == ':' || str[0] == '/' || str[0] == '.'){
			if( Ok_to_exec( str ) ){
				return( str );
			}
		} else {
			/* copy directory name */
			for( directory = fullname;
				*path && *path != ':';
				*directory++ = *path++ );
			if( *path ) ++path;	/* skip : */

			/* form full path name */

			if( strlen( fullname ) + strlen(str)+2 > MAXPATHLEN ){
				fprintf(stderr,
					"Find_text_file: file name too long" );
				return( (char *)0 );
			}
			*directory++ = '/';
			*directory = 0;
			(void)strcpy( directory, str );
			if( Ok_to_exec( fullname ) ){
				return( strcpy( str, fullname ) );
			}
		}
	}
	return( (char *)0 );
    }

/*

		Ok_to_exec( str )
			check perms for execution.
*/

Ok_to_exec( str )
	char *str;
    {
	struct stat stbuff;

	return(
		0 == stat(str, &stbuff)		/* stat it */
		&& (stbuff.st_mode&S_IFMT) == S_IFREG	/* check for file */
		&& 0 == access(str,X_OK)	/* and can be executed */
		);
    }

/*
	Load_file( objects, entries, library )
		NOTE: the number of object files is limited to 10
		in this implementation.
	1. generate a command for the loader.
		NOTE: this uses the execv function.
	2. execute the loader command.
	3. get the entry points
*/

#define round(x,s) ((((x)-1) & ~((s)-1)) + (s))

int
Load_file( objects, entries, library )
	char **objects;
	struct symbol_references *entries;
	char *library;
    {
	char *file;		/* temp variable */
	char cmd_str[2*MAXPATHLEN + 2048],
		*cmd, *cmdend;	/* command string for loader */
	static char *file_proto = "/tmp/llXXXXXX";	/* tmp name */
	int readsize,		/* total amount of object to read */
		totalsize,	/* total memory needed */
		n, diff;	/* working variables */
	caddr_t end;		/* current end of memory */
	int fcb;		/* used to read the object file */
	struct exec header;	/* a.out header */
	struct symbol_references *entry;	/* single pointer */

	/* create a temp file to hold output from loader */
	(void) strcpy( Symbol_name, mktemp(file_proto) );

	/* force end of memory to be aligned to a 1024 byte boundary */
	/* note: this restriction is applied by the loader */
	end = sbrk( 0 );	/* find the currend end */
	n = (int)end;
	diff = round(n,1024)-n;	/* get the difference */
#	ifdef DEBUG
		printf( "end %x, diff %x\n", end, diff );
#	endif DEBUG
	if( diff != 0 ){
		/* use sbrk to round up */
		end = sbrk( diff );
		if( (int)end <= 0 ){
			fprintf( stderr,
				"sbrk failed: %s\n", sys_errlist[errno] );
			return( 1 );
		}
		end = sbrk( 0 );
	}

#	ifdef DEBUG
		printf( "sbrk sets end to be 0x%x\n", end );
#	endif DEBUG

	/* make up a command */
	cmd = cmd_str;
	cmdend = cmd + sizeof( cmd_str );

	/* find the loader name */
	file = Find_text_file( "ld" );
	if( file == 0 ){
		fprintf( stderr, "cannot find loader\n" );
		return( 1 );
	}

	/* set up the first part of the command string */
	
	sprintf( cmd, "%s -N -x -A %s -T %x -o %s ",
		file,		/* loader */
		Program_name,	/* program text file */
		end, 		/* end of memory */
		Symbol_name	/* executable file */
		);
#	ifdef DEBUG
		printf( "command: %s\n", cmd );
#	endif DEBUG

	/* set cmd to end of string */
	cmd = cmd + strlen( cmd );

	/* now add the entry points */

	entry = entries;
	if( entry->name == 0 ){
		fprintf( stderr,"missing entry name" );
		return( 1 );
	}
	cmd = Strend( cmd, "-e", cmdend );
	if( cmd == 0 ){
		fprintf( stderr, "too many entries\n" );
	}
	cmd = Strend( cmd, entry->name, cmdend );
	if( cmd == 0 ){
		fprintf( stderr, "too many entries\n" );
	}
	++entry;
	/* set up the rest */
	for( ; entry->name; ++entry ){
		cmd = Strend( cmd, "-u", cmdend );
		if( cmd == 0 ){
			fprintf( stderr, "too many entries\n" );
		}
		cmd = Strend( cmd, entry->name, cmdend );
		if( cmd == 0 ){
			fprintf( stderr, "too many entries\n" );
		}
	}

	/* now add the object files */
	for( ; *objects; ++objects ){
		cmd = Strend( cmd, *objects, cmdend );
		if( cmd == 0 ){
			fprintf( stderr, "too many objects\n" );
		}
	}

	/* now add the library */
	if( library && *library ){
		cmd = Strend( cmd, library, cmdend );
		if( cmd == 0 ){
			fprintf( stderr, "library too long\n" );
		}
	}
	/* now add the defaults */
	cmd = Strend( cmd, "-lc", cmdend );
	if( cmd == 0 ){
		fprintf( stderr, "total loader command too long\n" );
	}
#	ifdef DEBUG
		printf( "loader command %s\n", cmd_str );
#	endif DEBUG
	if( (n = system( cmd_str )) != 0 ){
		(void)unlink( Symbol_name );
		fprintf( stderr, "load of objects and entries failed\n");
		return( 1 );
	}

#	ifdef DEBUG
		printf( "load was successful\n" );
#	endif DEBUG

	/* now try and read the information from the symbol table */

	if( (fcb = open( Symbol_name, O_RDONLY)) < 0){
		fprintf( stderr, "cannot open temp file %s: %s\n",
			Symbol_name, sys_errlist[errno] );
		return( 1 );
	}
#	ifdef DEBUG
		printf( "output file opened successfully\n" );
#	endif DEBUG

	/* read the a.out header and find out how much room to allocate */

	if(sizeof(header)!=read(fcb,(char *)&header,sizeof( header ))){
		fprintf( stderr, "cannot read header from temp file %s\n",
			Symbol_name );
		return( 1 );
	}


	/* calculate  sizes */
	readsize = round(header.a_text, 4) + round(header.a_data, 4);
	totalsize = readsize + header.a_bss;
	totalsize = round( totalsize, 512 );	/* round up a bit */
#	ifdef DEBUG
		printf( "read header: a_text %d, a_data %d, a_entry 0x%x\n",
			header.a_text, header.a_data, header.a_entry);
		printf( "readsize %d, totalsize %d\n", readsize, totalsize );
#	endif DEBUG

	/* allocate more memory, using sbrk */

	end = sbrk( totalsize );
#	ifdef DEBUG
		printf( "sbrk(0x%x) returns 0x%x\n", totalsize, end );
#	endif DEBUG
	if( (int)end <= 0 ){
		fprintf(stderr, "sbrk failed to allocate: %s\n",
			sys_errlist[errno] );
		return( 1 );
	}

#	ifdef DEBUG
		printf( "end is now 0x%x\n", end );
#	endif DEBUG

	/* now read in the functions */
	
	if(readsize != read(fcb,(char *)end,readsize)){
		fprintf( stderr, "cannot read %s: %s\n", Symbol_name,
			sys_errlist[errno] );
		return( 1 );
	}
	/* set the first entry up to the header value */
	entry = entries;
	entry->function = (int (*)())header.a_entry;
	++entry;

	if( entry->name ){
		if( Find_symbol_names( entry, fcb, header ) ){
			fprintf( stderr, "failed to find all symbols\n" );
			return( 1 );
		}
	}
	(void)close(fcb);
	return( 0 );
    }

/*
	Append a string and a space, check for overflow
*/

char *
Strend( str, append, last )
	char *str, *append, *last;
    {
	while( str < last && *append ){
		*str++ = *append++;
	}
	if( *append ){
		return( (char *)0 );
	}
	if( str+1 < last ){
		*str++ = ' ';
		*str = 0;
	} else {
		return( (char *)0 );
	}
	return( str );
    }

/*
	Look through the symbol table and find entries
*/
#define NAMES 100	/* number of entries to read at a time */

Find_symbol_names( entries, fcb, header )
	struct symbol_references *entries;	/* list of entries */
	int fcb;			/* symbol file fcb */
	struct exec header;		/* symbol file header */
    {
	int symbols, strings;		/* offsets to symbols and strings */
	int c, i, n;			/* temp variable */
	int next_symbol;		/* next symbol */
	struct nlist name[NAMES], *nl;		/* entry for a symbol */
	char *str_val;			/* string */
	int str_offset;			/* offset into string tables */
	int str_start, str_end;		/* start and end location of str */
	char str_buff[MAXPATHLEN];	/* should be nice and big */
	char *s;			/* temp variable */

	symbols = N_SYMOFF(header);	/* offset to symbols */
	strings = N_STROFF(header);	/* offset to strings */

	str_start = str_end = 0;
#	ifdef DEBUG
		printf( "symbols %d, strings %d\n", symbols, strings );
#	endif DEBUG
	for( next_symbol = symbols; next_symbol < strings; ){
		/* read in the necessary entries */
		if( lseek( fcb, next_symbol, 0 ) == -1 ){
			fprintf( stderr,"lseek on symbol file failed: %s\n",
				sys_errlist[errno] );
			return( 1 );
		}
		if( sizeof(name) != (n = read(fcb,(char *)name,sizeof(name)))){
			if( n <= 0 ){
				fprintf( stderr,
					"read on symbol file failed: %s\n",
					sys_errlist[errno] );
			}
		}
		n = n / sizeof( struct nlist );
#		ifdef DEBUG
			printf( "read %d symbols\n",n );
#		endif DEBUG
		for( i=0; i < n && next_symbol < strings; ++i ){
			next_symbol += sizeof( struct nlist );
			nl = name+i;
#			ifdef DEBUG
			/* print the information */
			printf(
			"n_un.n_strx %d, n_type %d, n_desc %d, n_value %d\n",
				nl->n_un.n_strx, nl->n_type, nl->n_desc,
				nl->n_value);
#			endif DEBUG
			if( (nl->n_type & N_EXT) ){
#				ifdef DEBUG
					printf( "external symbol\n" );
#				endif DEBUG
				/* seek to the string location */
			readnext:
				str_offset = strings + nl->n_un.n_strx;
#				ifdef DEBUG
				printf( "string offset %d\n", str_offset);
#				endif DEBUG

				/* get string in buffer */
				/* NOTE: this is the first time I have
					ever found a use for a
					"goto";  I suppose I could
					have made the following
					a function.
				*/

				while( str_offset < str_start ||
					str_offset > str_end ){
					/* seek to position and read */
	if( lseek(fcb, str_offset, 0 ) == -1){
		fprintf( stderr,
		"lseek on symbol file failed: %s\n",
			sys_errlist[errno] );
		return( 1 );
	}
	if( (c = read(fcb, str_buff, sizeof( str_buff ) )) <= 0 ){
		fprintf( stderr, "read on symbol file failed: %s\n",
			sys_errlist[errno] );
		return( 1 );
	}
	str_start = str_offset;
	str_end = str_start + c;
				}
				str_val = s =
					str_buff + (str_offset - str_start);
				while( *s && str_offset < str_end ){
					++s; ++str_offset;
				}
				if( *s ){
					str_offset = strings + nl->n_un.n_strx;
					if( str_offset != str_start ){
						str_end = 0;	/* force read */
						goto readnext;
					} else {
						fprintf( stderr,
						"symbol too long\n" );
						return( 1 );
					}
				}
#				ifdef DEBUG
				printf( "str_val %s\n", str_val );
#				endif DEBUG
				if( Check_entry_for_value( str_val,
					(unsigned)nl->n_value,entries) == 0 ){
					return(0);
				}
			}
		}
	}
	return( 0 );
    }

/*
	Check_entry_for_value( str, val, entries )
	char *str: string name
	unsigned val: value for it
	struct symbol_references *entries: list of entries

	1. for each un-set entry, check to see if string matches.
	2. if it does, then you set the function value.
*/

int
Check_entry_for_value( str, val, entries )
	char *str;
	unsigned val;
	struct symbol_references *entries;
    {
	struct symbol_references *entry;
	int found;

	found = 0;
	for( entry = entries; entry->name; ++entry ){
		if( entry->function == 0 ){
			found = 1;
			if( strcmp( str, entry->name ) == 0 ){
				entry->function = (int (*)())val;
#				ifdef DEBUG
				printf( "update %s with %d\n",
					entry->name, entry->function );
#				endif DEBUG
				return( found );
			}
		}
	}
	return( found );
     }
SHAR_EOF
fi
if test -f 'main.c'
then
	echo shar: "will not over-write existing file 'main.c'"
else
cat << \SHAR_EOF > 'main.c'
/*
	Test function for the dynamic loader
*/
#include <stdio.h>
char *Program;
struct symbol_references{
	char *name;
	int (*function)();
	};

main( argc, argv)
	int argc;
	char **argv;
    {
	char *objects[100], **obj;
	struct symbol_references refs[100], *ref;
	char *parm;
	/* get the name of the object file, and an ref point */

	printf( "argc %d, argv[0] %s\n", argc, argv[0] );
	Program = argv[0];
	if( argc < 2 ){
		Use_msg();
	}
	++argv;
	--argc;
	ref = refs;
	obj = objects;
	while( argc-- > 0 ){
		parm = *argv++;
		printf( "parm %d, %s\n", argc, parm );
		if( *parm != '-' ){
			Use_msg();
		}
		switch( *++parm ){
		case 'o':
			*obj++ = *argv++;
			printf( "Object %s\n", argv[-1] );
			argc--;
			break;
		case 'e':
			(ref++)->name = *argv++;
			printf( "Entry %s\n", argv[-1] );
			argc--;
			break;
		default:
			Use_msg();
			break;
		}
	}

	/* now find the name of the currently executing file */

	if( Set_program_name( Program ) ){
		fprintf( stderr, "cannot find program file %s\n", Program );
		exit(1);
	}
	if( Load_file( objects, refs, "" ) ){
		fprintf( stderr, "cannot load objects\n" );
		exit( 1 );
	}
	for( ref = refs; ref->name; ++ref ){
		printf( "ref point for %s is %d\n",
			ref->name, ref->function );
	}

	for( ref = refs; ref->name; ++ref ){
		printf( "calling %s\n", ref->name );
		(*ref->function)();
	}

	return( 0 );
    }

Use_msg()
    {
	fprintf( stderr, "%s: -e entry_point -o object\n", Program );
	exit(1);
    }
SHAR_EOF
fi
if test -f 'test.c'
then
	echo shar: "will not over-write existing file 'test.c'"
else
cat << \SHAR_EOF > 'test.c'
/*
	test file
*/
#include <stdio.h>
function()
    {
	printf( "Hello, world\n" );
    }
test_function()
    {
	printf( "Hi, I am your friendly loadable function\n" );
    }
SHAR_EOF
fi
exit 0
#	End of shell archive
-- 
Patrick Powell, Dept. Computer Science, 136 Lind Hall, 207 Church St. SE,
University of Minnesota,  Minneapolis, MN 55455 (612)625-3543/625-4002