[net.sources] dynamic loading for SVR2

tony@asgb.UUCP (Tony Andrews) (05/01/85)

I had a couple of slow days recently and played around with the dynamic
loading code posted by wateng!padpowell several months ago. The following
code works on SVR2 (vax (swapping) and ns32000 (paging)).


Tony Andrews
Burroughs Corp.
Boulder, CO
...!ihnp4!noao!hao!asgb!tony

-------------------------------- CUT HERE -------------------------------
# This is a shar archive.  Extract with sh, not csh.
# The rest of this file will extract:
#
#	Makefile
#	dld.3l
#	dld.c
#	main.c
#	test.c
#
echo extracting - Makefile
sed 's/^X//' > Makefile << '^HE HATES THESE CANS^'
XCFLAGS = -O
Xall:	load test
Xload:	main.o dld.o
X	cc -o load main.o dld.o -lld
Xtest:	test.o
X	load -o test.o -e _function -e _test_function
^HE HATES THESE CANS^
echo extracting - dld.3l
sed 's/^X//' > dld.3l << '^HE HATES THESE CANS^'
X.TH "dld" 3L  4/14/85
X.UC 4
X.SH NAME
XSet_prog_name, Load_file  \- support dynamic loading of functions
X.SH SYNOPSIS
X.PP
X.nf
X.in +.5i
X.ti -.5i
Xstruct dld_sym {
Xchar *name;	(pointer to entry point name)
Xint (*function)(); (call address)
X.ti -.5i
X};
X.ti -.5i
XSet_prog_name(str)
Xchar *str;
X.ti -.5i
XLoad_file(objects, entries, library)
Xchar **objects;
Xstruct symbol_references *symbols;
Xchar *library;
X.fi
X.in -.5i
X.SH DESCRIPTION
XThe
X.I "Dynamic Load"
Xroutines support dynamic loading of functions into an already executing
Xprogram.
X.I "Set_prog_name"
Xis called with the name of the currently executing binary file.
XThe
X.B PATH
Xenvironment variable is searched to determine the possible executing files.
XIf
X.B PATH
Xwas not provided, a default search path is used.
XIf a candidate binary file is not found,
Xthen a nonzero value is returned.
X.PP
X.I Load_files
Xis passed a pointer to a list of structures containing
Xpointers to strings and corresponding entry point values,
Xa pointer to a list of object of library file names,
Xand a string containing loader options.
XThe structure list is terminated with a structure with a zero
Xentry, as is the object file list (see EXAMPLE below).
XThe UNIX loader
X.IR ld (1)
Xis invoked,
Xand will search the supplied object files for global entry points
Xof the specified functions or variables.
XIf the load is successful,
Xthe entry point values are placed in the structure.
X.PP
XThe library string can be used to pass additional parameters to the
Xloader, such as a default set of libraries to be searched.
X.SH EXAMPLE
X.PP
X.nf
Xchar *objects = {
X	"movies.o", "library.a", 0 };
Xstruct dld_sym look_for[] = {
X	{"_good"}, {"_bad"}, {"_ugly"}, {(char *)0} };
Xmain( argv, argc )
X	char **argv; int argc;
X{
X	if (Set_prog_name(argv[0]) == 0
X		&& Load_file(objects, lookfor, "-lmath -ltermlib") == 0) {
X		(*look_for[0].function)();
X		(*look_for[1].function)();
X		(*look_for[2].function)();
X	}
X}
X.fi
X.PP
XAnother method of using this would be do have a symbol references
Xtable in one of the object files,
Xand to request loading of the table.
XIt is possible to generate the table from a program,
Xby using the C compiler,
Xand to then load the output.
XIf this method is chosen, note that the C compiler should be run in
Xa writeable directory, like /tmp.
X.SH "SEE ALSO"
Xld(1),
Xcc(1)
X.SH DIAGNOSTICS
X.I Set_prog_name
Xand
X.I Load_file
Xreturn zero upon success,
Xnon-zero otherwise.
XDiagnostic messages are output on
X.I stderr
Xusing the
X.I stdio
Xpackage.
X.SH BUGS
X.PP
XYou must be careful in how you specify your program name.
XIf you are doing peculiar things, you should provide the full path
Xname as the
X.I argv[0]
Xvalue.
X.PP
XThe entry point names must be specified in
X.IR ld (2)
Xrecognizable form.
XFor example,
Xthe C compiler will prefix an underscore to external variable names.
XThe F77 compiler will prefix and append underscores.
XThus, the variable
X.I var
Xwill be
X.I _var
Xif in a C object file,
Xand
X.I _var_
Xif in a F77 object file.
X.PP
XThe
X.I Load_file
Xroutine constructs a command using a fixed size string buffer.
XIf you have lots of entry points, this will overflow,
Xand you will get an error message.
XOne solution to this problem is to create an entry point table as part
Xof an object file.
XAnother solution is to modify the internal function that checks and finds
Xentry points in the symbol table.
X.PP
XThe code that finds the values of entry points is very crude.
XIt has been partially speeded up to minimize the number of reads
Xthat must be done, but will be very slow if a large number of
Xentry points are provided.  Again, this may be solved by adding
Xan object file with an entry point table, and determining that
Xtable address.
^HE HATES THESE CANS^
echo extracting - dld.c
sed 's/^X//' > dld.c << '^HE HATES THESE CANS^'
X/*
X *	Run time loading of functions.
X *	- Basic technique.
X *		A loader input file is created with the proper directives
X *		for incremental loading.  The output of the loader will be
X *		a file which can be read into an already executing program.
X *		The symbol table of the already executing program must be
X *		specifed.
X *
X *		The currently executing file is searched for using the
X *		entries in the $PATH environment variable.  When it is
X *		found, the loader is invoked with
X *		ld	-N	Do not make text portion readable or sharable
X *			-x 	no local symbols in symbol table
X *			-e <name>
X *				name of an entry point (first function only)
X *			-u <name>
X *				name of other functions
X *			-o <filename>
X *				output file name
X *			<file>	tmp file with loader directives
X *
X *			<library>
X *				libraries to be searched, etc.
X *			-lc
X *				default library
X *
X *		The tmp file contains the following:
X *
X *		SECTIONS
X *		{
X *			.text  <offset> (NOLOAD) : { executing file }
X *			.data           (NOLOAD) : { executing file }
X *			.bss            (NOLOAD) : { executing file }
X *			.dyn   <addr>            : { object files }
X *		}
X *
X *
X *		<offset> is where the text segment must start
X *		<addr> is the address where the object files will be put.
X *
X *	Support functions:
X *	struct	dld_sym {
X *		char	*name;		(pointer to entry point name)
X *		int	(*function)();	(call address)
X *	};
X *
X *	Set_prog_name(str)
X *		Must be called first to set up the program text file name.
X *		This is used in the loading process.
X *	Load_file(objects, entries, library)
X *		char	**objects;
X *		struct	dld_sym	*symbols;
X *		char	*library;
X *
X *		The "objects" points to a list of object file names terminated
X *		with a null (char *)0.  For example, a static definition 
X *			char *objects[] = { "lib.a", "usr_lib.b", 0 };
X *		Note: currently, a maximum of 10 object files can be
X *			specified.  This limit is set by the compile time
X *			variable MAXOBJECTS.
X *
X *		The "symbols" points to a list of dld_sym structures.
X *		Each structure has the name of a function, and a function
X *		address field which is set to the name of the function.
X *
X *		struct dld_sym symbols[] = {
X *			{ "_ugly" }, { "_really" }, { (char *)0 }
X *		};
X *
X *		NOTE: this is acceptable C, missing field initializers
X *		are supposed to be acceptable.
X *
X *		The "library" should be null (char *)0, or a string containing
X *		loader library information.  For example:
X *			entries = "-lmath -ltermlib" 
X *		Note that this can also be used to specify a list of object
X *		fields, set up "undefined symbol information" to force
X *		loading from libraries ("-u _ugly mylib.a -lmath").
X *
X *	IMPLEMENTATION:
X *		The functions use the stdio package, and write error
X *	messages on stderr.  If an error is detected, a 1 is returned.
X *	The PATH environment variable is used to determine the executing
X *	function.  If it is not set, then a default set of paths is used.
X *		The sbrk() system call is used to allocate memory.  This
X *	should have no effect on a properly designed memory allocation package,
X *	such as malloc(3).
X *		The external entry point names must be specified in the
X *	form that the loader needs.  For example, the C name "ugly"
X *	would have to be specified as "_ugly", and the F77 name "ugly"
X *	as "_ugly_".
X */
X
X#include <stdio.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <filehdr.h>
X#include <aouthdr.h>
X#include <scnhdr.h>
X#include <syms.h>
X#include <ldfcn.h>
X
X#define	LOADER		"/bin/ld"	/* the loader to be used */
X#define	DSECTION	".dyn"		/* name of the loader section created */
X#define	reg		register
X#define	MAXPATHLEN	512
X
X#if ns32000
X#define	TEXTADDR	(sizeof(struct filehdr) + \
X			 sizeof(struct aouthdr) + \
X			 sizeof(struct scnhdr) * 3)
X#else
X#define	TEXTADDR	0
X#endif
X
Xstatic	char	Prog_name[MAXPATHLEN];	/* saves the name of the program */
X
Xextern	char	*Find_text_file(),
X		*Strend(),		/* append to end of string */
X		*strcpy(),
X		*mktemp(),
X		*sbrk();		/* memory allocation */
X
Xstruct	dld_sym {
X	char	*name;
X	int	(*function)();
X};
X
X/*
X *	Set_prog_name(str)
X *		Checks the file name for execute perms,
X *		and then saves the name.
X */
X
XSet_prog_name(str)
Xreg	char	*str;
X{
X	strcpy(Prog_name, str);
X
X	if ((str = Find_text_file(Prog_name)) == 0) {
X		fprintf(stderr, "program name %s not found\n");
X		return (1);
X	}
X	return (0);
X}
X
X/*
X *	Find_text_file(str)
X *		Find the pathname of the indicated executing file.
X *	1. Get the $PATH environment varible. If none, use a set of
X *		default paths.
X *	2. Check each directory in turn, by copying the directory name,
X *		then appending the file name.
X *	Uses the small Ok_to_exec(str) function
X */
X
Xstatic	char *
XFind_text_file(str)
Xreg	char	*str;
X{
X	reg	char	*path, *dir;
X	char	*getenv();
X	char	name[MAXPATHLEN];
X
X	if ((path = getenv("PATH")) == 0)
X		path = ":/usr/ucb:/bin:/usr/bin";	/* default */
X
X	while (*path) {
X		if (*path == ':' || str[0] == '/' || str[0] == '.') {
X			if (Ok_to_exec(str))
X				return (str);
X		} else {
X			/* copy directory name */
X			for (dir=name; *path && *path != ':' ;*dir++ = *path++)
X				;
X
X			if (*path)
X				++path;			/* skip : */
X
X			/* form full path name */
X
X			if (strlen(name) + strlen(str)+2 > MAXPATHLEN) {
X				fprintf(stderr, "file name too long");
X				return ((char *) 0);
X			}
X			*dir++ = '/';
X			*dir = 0;
X			strcpy(dir, str);
X			if (Ok_to_exec(name))
X				return (strcpy(str, name));
X		}
X	}
X	return ((char *) 0);
X}
X
X/*
X *	Ok_to_exec(str)
X *		check perms for execution.
X */
X
XOk_to_exec(str)
Xreg	char	*str;
X{
X	struct	stat	stbuff;
X
X	return (0 == stat(str, &stbuff)			/* stat it */
X		&& (stbuff.st_mode&S_IFMT) == S_IFREG	/* check for file */
X		&& 0 == access(str,01)		/* & can be executed */
X		);
X}
X
X/*
X *	Load_file(objects, entries, library)
X *		NOTE: the number of object files is limited to 10
X *		in this implementation.
X *	1. generate a command for the loader.
X *		NOTE: this uses the execv function.
X *	2. execute the loader command.
X *	3. get the entry points
X */
X
X#define round(x,s) ((((x)-1) & ~((s)-1)) + (s))
X
Xint
XLoad_file(objects, entries, library)
Xchar	**objects;
Xstruct	dld_sym	*entries;
Xchar	*library;
X{
X	char	*ofile = "/tmp/llXXXXXX";	/* loader output */
X	char	*ifile = "/tmp/liXXXXXX";	/* loader directives */
X	FILE	*ifp;
X	LDFILE	*fcb;				/* used to read the obj file */
X	SCNHDR	dynhdr,				/* header for dynamic section */
X		bsshdr;				/* header for bss section */
X
X	char	cmd_str[2*MAXPATHLEN + 2048],
X		*cmd, *cmdend;			/* command string for loader */
X	int	readsize,			/* size of obj to read */
X		totalsize,			/* size including bss */
X		n, diff;			/* working variables */
X	char	*end;				/* current end of memory */
X	struct	dld_sym *entry;			/* single pointer */
X
X	mktemp(ifile);				/* temp files for loader */
X	mktemp(ofile);
X
X	cmd = cmd_str;				/* make up a command */
X	cmdend = cmd + sizeof(cmd_str);
X
X	/* set up the first part of the command string */
X	
X	sprintf(cmd, "%s -N -x -o %s %s ",
X		LOADER,
X		ofile,				/* executable file */
X		ifile				/* the loader input file */
X		);
X
X	cmd += strlen(cmd);			/* set cmd to end of string */
X
X	entry = entries;			/* now add the entry points */
X	if (entry->name == 0) {
X		fprintf(stderr,"missing entry name");
X		return (1);
X	}
X	if ((cmd = Strend(cmd, "-e", cmdend)) == 0)
X		fprintf(stderr, "too many entries\n");
X
X	if ((cmd = Strend(cmd, entry->name, cmdend)) == 0)
X		fprintf(stderr, "too many entries\n");
X
X	/* set up the rest */
X	for (++entry; entry->name ;++entry) {
X		if ((cmd = Strend(cmd, "-u", cmdend)) == 0)
X			fprintf(stderr, "too many entries\n");
X
X		if ((cmd = Strend(cmd, entry->name, cmdend)) == 0)
X			fprintf(stderr, "too many entries\n");
X	}
X
X	if (library && *library)
X		if ((cmd = Strend(cmd, library, cmdend)) == 0)
X			fprintf(stderr, "library too long\n");
X
X	if ((cmd = Strend(cmd, "-lc > /dev/null 2>&1", cmdend)) == 0)
X		fprintf(stderr, "total loader command too long\n");
X
X#ifdef DEBUG
X	printf("ld cmd : %s\n", cmd_str);
X#endif
X
X#ifdef DEBUG
X	printf("text address is 0x%x\n", TEXTADDR);
X#endif
X
X	if ((ifp = fopen(ifile, "w")) == NULL) {
X		fprintf(stderr, "can't create loader ifile\n");
X		return (1);
X	}
X
X	fprintf(ifp, "SECTIONS\n{\n");
X	fprintf(ifp, "\t.text\t0x%x (NOLOAD) : { %s }\n", TEXTADDR, Prog_name);
X	fprintf(ifp, "\t.data\t(NOLOAD) : { %s }\n", Prog_name);
X	fprintf(ifp, "\t.bss\t(NOLOAD) : { %s }\n", Prog_name);
X
X	/* force end of memory to be aligned to a 1024 byte boundary
X	 * note: this restriction is applied by the loader
X	 *
X	 * This has to be done after the first fprintf because stdio
X	 * allocates space that would screw this up if done earlier.
X	 */
X
X	end = sbrk(0);			/* find the currend end */
X	n = (int) end;
X	diff = round(n,1024) - n;	/* get the difference */
X#ifdef DEBUG
X	printf("end %x, diff %x\n", end, diff);
X#endif
X	if (diff != 0) {
X		end = sbrk(diff);	/* use sbrk to round up */
X		if ((int) end <= 0) {
X			fprintf(stderr,"sbrk failed\n");
X			return (1);
X		}
X		end = sbrk(0);
X	}
X
X#ifdef DEBUG
X	printf("sbrk sets end to be 0x%x\n", end);
X#endif
X
X	/*
X	 * Put dynamically loaded text and data into a single section
X	 * making it easy to find and read later. Bss goes into .bss
X	 */
X
X	fprintf(ifp, "\t%s\t0x%x : { ", DSECTION, end);
X
X	/* now add the object files */
X	for(; *objects ;++objects)
X		fprintf(ifp, "%s ", *objects);
X
X	fprintf(ifp, "}\n}\n");
X	fclose(ifp);
X
X	if ((n = system(cmd_str)) != 0) {
X#ifndef	DEBUG
X		unlink(ifile);
X		unlink(ofile);
X#endif
X		fprintf(stderr, "load of objects and entries failed\n");
X		return (1);
X	}
X	unlink(ifile);
X
X	/* read the section header and find out how much room to allocate */
X
X	if ((fcb = ldopen(ofile, NULL)) == NULL) {
X		fprintf(stderr, "can't open loader output file\n");
X		return (1);
X	}
X
X	if (ldnshread(fcb, DSECTION, &dynhdr) != SUCCESS) {
X		fprintf(stderr, "can't read dynamic section header\n");
X		return (1);
X	}
X
X	if (ldnshread(fcb, ".bss", &bsshdr) != SUCCESS) {
X		fprintf(stderr, "can't read bss section header\n");
X		return (1);
X	}
X
X	/* calculate  sizes */
X	totalsize = (readsize = dynhdr.s_size) + bsshdr.s_size;
X
X#ifdef DEBUG
X	printf("readsize %d, totalsize %d\n", readsize, totalsize);
X#endif
X
X	/* allocate more memory, using sbrk */
X
X	end = sbrk(totalsize);
X
X#ifdef DEBUG
X	printf("sbrk(0x%x) returns 0x%x\n", totalsize, end);
X#endif
X
X	if ((int) end <= 0) {
X		fprintf(stderr, "sbrk failed\n");
X		return (1);
X	}
X
X#ifdef DEBUG
X	printf("end is now 0x%x\n", end);
X#endif
X
X	/* now read in the functions */
X
X	if (FSEEK(fcb, dynhdr.s_scnptr, 0) != 0) {
X		fprintf(stderr, "can't fseek on object file\n");
X		return (1);
X	}
X
X	if (FREAD(end, readsize, 1, fcb) != 1) {
X		fprintf(stderr, "can't read object code\n");
X		return (1);
X	}
X	
X	if (Find_symbol_names(entries, fcb)) {
X		fprintf(stderr, "failed to find all symbols\n");
X		return (1);
X	}
X	close(fcb);
X#ifndef	DEBUG
X	unlink(ofile);
X#endif
X	return (0);
X}
X
X/*
X	Append a string and a space, check for overflow
X*/
X
Xchar *
XStrend(str, append, last)
Xchar	*str, *append, *last;
X{
X	while (str < last && *append)
X		*str++ = *append++;
X
X	if (*append)
X		return ((char *) 0);
X
X	if (str+1 < last) {
X		*str++ = ' ';
X		*str = 0;
X	} else
X		return ((char *) 0);
X
X	return (str);
X}
X
X/*
X *	Look through the symbol table and find entries
X */
X
XFind_symbol_names(entries, fcb)
Xreg	struct	dld_sym	*entries;		/* list of entries */
Xreg	LDFILE	*fcb;
X{
X	reg	int	i, nsyms;
X	SYMENT	nl;
X	char	*name, *ldgetname();
X
X	nsyms = HEADER(fcb).f_nsyms;
X
X	for (i = 0; i < nsyms ;i++) {
X		if (ldtbread(fcb, i, &nl) != SUCCESS) {
X			fprintf(stderr, "can't read symbol %d\n", i);
X			return (1);
X		}
X
X		if (nl.n_sclass != C_EXT)	/* skip all non-externals */
X			continue;
X
X		if ((name = ldgetname(fcb, &nl)) == NULL) {
X			fprintf(stderr, "can't get name of symbol %d\n", i);
X			return (1);
X		}
X
X		if (Check_entry(name, (unsigned)nl.n_value, entries) == 0)
X			return (0);
X	}
X	return (0);
X}
X
X/*
X *	Check_entry(str, val, entries)
X *	char *str: string name
X *	unsigned val: value for it
X *	struct dld_sym *entries: list of entries
X *
X *	1. for each un-set entry, check to see if string matches.
X *	2. if it does, then you set the function value.
X */
X
Xint
XCheck_entry(str, val, entries)
Xreg	char	*str;
Xreg	unsigned val;
Xstruct	dld_sym	*entries;
X{
X	reg	struct	dld_sym	*entry;
X	reg	int	found;
X
X	found = 0;
X	for (entry = entries; entry->name ;++entry) {
X		if (entry->function == 0) {
X			found = 1;
X			if (strcmp(str, entry->name) == 0) {
X				entry->function = (int (*)()) val;
X#ifdef DEBUG
X				printf("update %s with %d\n",
X					entry->name, entry->function);
X#endif
X				return (found);
X			}
X		}
X	}
X	return (found);
X}
^HE HATES THESE CANS^
echo extracting - main.c
sed 's/^X//' > main.c << '^HE HATES THESE CANS^'
X/*
X *	Test function for the dynamic loader
X */
X#include <stdio.h>
X
Xchar *Program;
X
Xstruct	dld_sym {
X	char	*name;
X	int	(*function)();
X};
X
Xmain(argc, argv)
Xint	argc;
Xchar	**argv;
X{
X	char	*objects[100], **obj;
X	struct	dld_sym	refs[100], *ref;
X	char	*parm;
X
X	/* get the name of the object file, and a ref point */
X
X	Program = argv[0];
X	if (argc < 2)
X		syntax();
X
X	++argv;
X	--argc;
X	ref = refs;
X	obj = objects;
X
X	while (argc-- > 0) {
X		parm = *argv++;
X		if (*parm != '-')
X			syntax();
X
X		switch (*++parm) {
X		case 'o':
X			*obj++ = *argv++;
X			argc--;
X			break;
X		case 'e':
X			(ref++)->name = *argv++;
X			argc--;
X			break;
X		default:
X			syntax();
X			break;
X		}
X	}
X
X	/* now find the name of the currently executing file */
X
X	if (Set_prog_name(Program)) {
X		fprintf(stderr, "cannot find program file %s\n", Program);
X		exit(1);
X	}
X	if (Load_file(objects, refs, "")) {
X		fprintf(stderr, "cannot load objects\n");
X		exit(1);
X	}
X	for (ref = refs; ref->name ;++ref)
X		printf("ref point for %s is %d\n", ref->name, ref->function);
X
X	for (ref = refs; ref->name ;++ref) {
X		printf("calling %s\n", ref->name);
X		(*ref->function)();
X	}
X	return (0);
X}
X
Xsyntax()
X{
X	fprintf(stderr, "%s: -e entry_point -o object\n", Program);
X	exit(1);
X}
^HE HATES THESE CANS^
echo extracting - test.c
sed 's/^X//' > test.c << '^HE HATES THESE CANS^'
X/*
X *	test file
X */
X
X#include <stdio.h>
X
Xchar	foo[10000];
Xchar	bar[] = "this is a test to add some data to this module";
X
Xfunction()
X{
X	printf("Hello, world\n");
X}
X
Xtest_function()
X{
X	printf("Hi, I am your friendly loadable function\n");
X	printf("%s\n", bar);
X}
^HE HATES THESE CANS^