_DYNAMIC LINKING UNDER BERKELEY UNIX_ by Oliver Sharp [LISTING ONE] /* dyn_link.c - a simple dynamic linker */ #include #include /************ Declarations ************/ #define TRUE 1 #define FALSE 0 #define PAGE_SIZE 4096 /* machine-dependent constant */ /* FUNC_INDEX - a linked list of these structures stores the names and * addresses of dynamically linked code. */ typedef struct index { char *name; int (*function)(); struct index *next; } FUNC_INDEX; /* some declarations to pacify careful compilers */ FUNC_INDEX *dynamic_load(); char *find_symbol_table(); FUNC_INDEX *get_ops(); FUNC_INDEX *reverse_list(); char *allocate_space(); char *malloc(), *calloc(); /************ Main Program ************/ main() { FUNC_INDEX *dynamic_load(), *functions, *index_p; char buffer[BUFSIZ]; setup_initial_binary("dyn_link"); functions = dynamic_load("sample.o"); for (index_p = functions ; index_p ; index_p = index_p->next) puts(index_p->name); while (TRUE) { printf("Enter name of routine to call ( to exit): "); fflush(stdout); if ((fgets(buffer,BUFSIZ,stdin) == NULL) || (strlen(buffer) == 1)) exit(); buffer[strlen(buffer)-1] = '\0'; /* strip off \n */ call_routine(buffer,functions); } } /* call_routine - given the name of a routine and a list of available ones, * scan and find address. If you find routine, call it with an argument equal * to number of times call_routine has been invoked. If not, complain. */ call_routine(name,index_p) char *name; FUNC_INDEX *index_p; { static argument = 1; for ( ; index_p ; index_p = index_p->next) { if (strcmp(index_p->name,name) == 0) { (index_p->function)(argument++); return; } } printf("Sorry, there is no function called '%s'\n",name); } /************ Setting Up ************/ /* setup_initial_binary - read our image, creating a code-less binary a.out * with only those symbols that we want the new code to be able to see. */ setup_initial_binary(my_name) char *my_name; { FILE *fp, *outp; if ((outp = fopen("a.out","w+")) == NULL) { fprintf(stderr,"Can't open a.out for destructive writing\n"); exit(-1); } /* leave room for the exec at the front */ fseek(outp,(long) sizeof(struct exec),0); if ((fp = fopen(my_name,"r")) == NULL) { fprintf(stderr,"That's funny, I thought I was called %s\n",my_name); exit(-1); } output_exported_symbols(fp,outp); fclose(fp); fclose(outp); } /* output_exported_symbols - run through symbols in binary, looking for ones * that start with "_export". Put them in the output file's symbol table. */ output_exported_symbols(fp,outp) FILE *fp, *outp; { struct exec the_exec, fake_exec; struct nlist symbol; char *binary_strings, *name; char name_buffer[BUFSIZ]; int i, new_table_size = sizeof(int), how_many_symbols = 0; if (!fread(&the_exec,sizeof(the_exec),1,fp)) { fprintf(stderr,"I can't read my own header structure\n"); exit(-1); } binary_strings = find_symbol_table(&the_exec,fp); for (i = 0 ; i < (the_exec.a_syms / sizeof(struct nlist)) ; i++) { if (!fread(&symbol,sizeof(symbol),1,fp)) { fprintf(stderr,"Error reading symbol #%d\n",i); exit(-1); } if (!symbol.n_un.n_strx) /* symbol doesn't have a name */ continue; name = binary_strings + symbol.n_un.n_strx - sizeof(int); #ifdef DYNIX if ((strncmp(name,"_export",7) == 0) || (strcmp(name,"start") == 0)) { #else if (strncmp(name,"_export",7) == 0) { #endif symbol.n_un.n_strx = new_table_size; /* fix offset */ how_many_symbols++; if (new_table_size-sizeof(int) + strlen(name) >= BUFSIZ) { fprintf(stderr,"Error: string table overflowed\n"); exit(-1); } strcpy(name_buffer + new_table_size-sizeof(int),name); new_table_size += strlen(name) + 1; /* keep track of size of table */ fwrite(&symbol,sizeof(symbol),1,outp); } } /* write out the string table */ fwrite(&new_table_size,sizeof(int),1,outp); fwrite(name_buffer,new_table_size-sizeof(int),1,outp); /* rewind and write out the proper exec structure */ rewind(outp); bcopy(&the_exec,&fake_exec,sizeof(fake_exec)); fake_exec.a_magic = OMAGIC; /* simple memory management */ fake_exec.a_syms = how_many_symbols * sizeof(struct nlist); fake_exec.a_text = fake_exec.a_data = fake_exec.a_bss = 0; fwrite(&fake_exec,sizeof(fake_exec),1,outp); } /************ Doing the Dynamic Link ************/ /* dynamic_load - figure out how big the file is, allocate a buffer, call the * linker, and load resulting code. Return a linked list of structures giving * name and address of every function in new code that can be called. */ FUNC_INDEX * dynamic_load(object_file) char *object_file; { FILE *ofp; char *buffer; FUNC_INDEX *index; int how_big; if ((ofp = fopen(object_file,"r")) == NULL) { fprintf(stderr,"I can't read the file %s\n",object_file); exit(-1); } index = get_ops(ofp); buffer = allocate_space(ofp,&how_big); /* allocate space for new code */ fclose(ofp); if (!run_the_linker(object_file,buffer)) { fprintf(stderr,"Linker reports errors, exiting\n"); exit(-1); } read_code(buffer,how_big,index); return(index); } /* run_the_linker - given a filename, run "ld" on it to create an a.out file. * Return TRUE if the link succeeds, FALSE otherwise. */ run_the_linker(object_file,buffer) char *object_file, *buffer; { char command[BUFSIZ]; sprintf(command,"ld -N -A a.out -T %x %s",buffer,object_file); return(system(command) ? FALSE : TRUE); /* system returns true for error */ } /************ Manipulating Files ************/ /* find_symbol_table - read the string table, seek the file pointer to * the beginning of the symbol table, and return the string table. */ char * find_symbol_table(the_exec,fp) struct exec *the_exec; FILE *fp; { char *string_table; int table_size; fseek(fp,((long) N_STROFF(*the_exec)),0); if (fread(&table_size,sizeof(int),1,fp) != 1) { fprintf(stderr,"couldn't read string table size\n"); exit(-1); } if ((string_table = malloc(table_size)) == NULL) { fprintf(stderr,"couldn't allocate space for string table\n"); exit(-1); } if (fread(string_table,table_size-sizeof(int),1,fp) != 1) { fprintf(stderr,"couldn't read string table\n"); exit(-1); } fseek(fp,((long) N_SYMOFF(*the_exec)),0); return(string_table); } /* get_ops - given a pointer to beginning of a .o file, build a FUNC_INDEX * structure with names of external text identifiers in symbol table. */ FUNC_INDEX * get_ops(fp) FILE *fp; { char *string_table; struct nlist symbol; struct exec the_exec; int num_symbols, i; FUNC_INDEX *index = NULL, *temp; rewind(fp); if (fread(&the_exec,sizeof(the_exec),1,fp) != 1) { printf("couldn't read file header\n"); return(NULL); } num_symbols = the_exec.a_syms / sizeof(struct nlist); string_table = find_symbol_table(&the_exec,fp); /* run through the symbol table, making an index struct for each * external text identifier */ for (i = 0 ; i < num_symbols ; i++) { if (fread(&symbol,sizeof(symbol),1,fp) != 1) { fprintf(stderr,"Can't read symbol #%d\n",i); exit(-1); } if (symbol.n_type != (N_TEXT | N_EXT)) /* check if exported function */ continue; if ((temp = (FUNC_INDEX *) calloc(1,sizeof(FUNC_INDEX))) == NULL) { fprintf(stderr,"Couldn't allocate a new function index structure\n"); exit(-1); } /* get the name, adding 1 to skip the initial underscore */ temp->name = string_table + symbol.n_un.n_strx - sizeof(int) + 1; temp->next = index; index = temp; } /* since we insert at the front of the list, we need to reverse it to * maintain symbols in their original order */ if (index) index = reverse_list(index); return(index); } /* allocate_space - given a pointer to an object module, figure out how big * it is and return a buffer to hold it. Leave the size in buffer_size. */ char * allocate_space(fp, buffer_size) FILE *fp; int *buffer_size; { struct exec the_exec; int size, buffer; rewind(fp); if (fread(&the_exec,sizeof(struct exec),1,fp) != 1) { fprintf(stderr,"I couldn't read the exec header of the object module\n"); exit(-1); } *buffer_size = size = the_exec.a_text + the_exec.a_data + the_exec.a_bss; printf("Sizes: text = 0x%lx, data = 0x%lx, bss = 0x%lx. Total = 0x%x,%d\n", the_exec.a_text,the_exec.a_data,the_exec.a_bss,size,size); if (size & (PAGE_SIZE-1)) /* if not on page boundary, leave extra space */ size += PAGE_SIZE; /* allocate the buffer; use calloc() so bss is automatically zeroed */ if ((buffer = (int) calloc(size,1)) == NULL) { fprintf(stderr,"Couldn't allocate %d byte buffer\n",size); exit(-1); } /* return the address rounded up to the nearest page */ return((char *) ((buffer + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1))); } /* read_code - read code and data from fp and put it in buffer (which is * "size" bytes long). Fill in new addresses of routines in function index. */ read_code(buffer,size,index) char *buffer; int size; FUNC_INDEX *index; { struct exec the_exec; struct nlist symbol; int code_size, num_symbols, symbols_so_far = 0; char *string_table, *symbol_name; FILE *fp; if ((fp = fopen("a.out","r")) == NULL) { fprintf(stderr,"I can't read a.out\n"); exit(-1); } if (fread(&the_exec,sizeof(the_exec),1,fp) != 1) { fprintf(stderr,"Couldn't read the new image's header\n"); exit(-1); } fseek(fp,(long) N_TXTOFF(the_exec),0); code_size = the_exec.a_text + the_exec.a_data; if ((code_size + the_exec.a_bss) > size) { fprintf(stderr,"Allocated buffer is %d bytes, need %d\n",size, (code_size + the_exec.a_bss)); exit(-1); } if (!fread(buffer,code_size,1,fp)) { fprintf(stderr,"couldn't load the code\n"); exit(-1); } string_table = find_symbol_table(&the_exec,fp); num_symbols = the_exec.a_syms / sizeof(struct nlist); while (index) { if (++symbols_so_far > num_symbols) { fprintf(stderr,"Ran out of symbols while looking for %s\n",index->name); exit(-1); } if (fread(&symbol,sizeof(struct nlist),1,fp) != 1) { fprintf(stderr,"Died reading symbol\n"); exit(-1); } if (symbol.n_un.n_strx == 0) /* doesn't have a name */ continue; /* if we have found the next name in the index list, stash address */ symbol_name = string_table + symbol.n_un.n_strx - sizeof(int) + 1; if (strcmp(symbol_name,index->name) == 0) { index->function = (int (*)()) symbol.n_value; index = index->next; } } fclose(fp); } /************ Utility Routines ************/ /* reverse_list - run down a non-empty linked list, reversing its order. * Return the new head (i.e. the old tail). */ FUNC_INDEX * reverse_list(list) FUNC_INDEX *list; { FUNC_INDEX *last = list, *temp; list = list->next; last->next = NULL; while (list) { temp = list->next; list->next = last; last = list; list = temp; } return(last); } /************ Exported Functions ************/ /* export_hook - this function starts with "export", so it will be visible * to loaded functions. */ export_hook(string,value) char *string; int value; { printf(" ** this is export_hook, and I was called with '%s' **\n",string); return(value * 2); } /* exported_printf - the loaded functions don't have access to the libraries * so we define a printf stub for them to use. */ exported_printf(string,number) char *string; int number; { printf(string,number); } [LISTING TWO] /* sample.c - a few functions to test the dynamic loader */ #include simple(value) int value; { exported_printf("I'm a simple routine and got argument: %d\n",value); } call_back(value) int value; { exported_printf("This is call back; I got argument %d\n",value); value = export_hook("test string",value); exported_printf(" The final value is %d\n",value); } [LISTING THREE] # Makefile for dynamic linker dyn_link - choose one of the targets CFLAGS = -g # Use this for most systems (BSD Reno, SunOS without dynamic libraries, etc.) generic: dyn_link.o sample.o $(CC) $(CFLAGS) -o dyn_link dyn_link.o # Dynamic linking doesn't work correctly on systems running SunOS with # dynamic libraries unless you link the original binary with the -Bstatic flag. sundl: dyn_link.o sample.o $(CC) $(CFLAGS) -Bstatic -o dyn_link dyn_link.o # For dynix machines, you need the symbol "start" in the constructed binary dynix: dyn_link.c sample.o $(CC) $(CFLAGS) -DDYNIX -o dyn_link dyn_link.c [LISTING FOUR] Sizes: text = 0x5c, data = 0x78, bss = 0x0. Total = 0xd4,212 simple call_back Enter name of routine to call ( to exit): simple I'm a simple routine and got argument: 1 Enter name of routine to call ( to exit): call_back This is call back; I got argument 2 ** this is export_hook, and I was called with 'test string' ** The final value is 4 Enter name of routine to call ( to exit): simple I'm a simple routine and got argument: 3 Enter name of routine to call ( to exit): call_back This is call back; I got argument 4 ** this is export_hook, and I was called with 'test string' ** The final value is 8 Enter name of routine to call ( to exit):