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