Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Open Source

Dynamic Linking Under Berkeley Unix


MAY93: DYNAMIC LINKING UNDER BERKELEY UNIX

DYNAMIC LINKING UNDER BERKELEY UNIX

Invoking code at run time

Oliver Sharp

Oliver is a graduate student at the University of California Berkeley, doing research into parallel proramming environments. He can be reached at [email protected].


The job of a linker is to take a group of object modules and combine them into a single program. With a normal (static) linker, you supply all the different pieces of your program and it assembles the final binary. Most of the work is patching up the locations of code and data; each module of a language like C is compiled independently, so the compiler doesn't know exactly where in the final program the current module will go. When it sees a call to a function, what address should it use? Even if the function is defined in the current module, the compiler can't know how the different modules will be assembled into the final program. If the function is in another module, the compiler doesn't have any idea where the function will be--in fact, the programmer may have forgotten to write it entirely. So, the compiler leaves the addresses unresolved, relying on the linker to handle them.

A dynamic linker is more flexible--it can be invoked on new pieces of code at run time, combining them with the already executing program. Unlike other dynamic techniques such as overlays, dynamic linking is not set up ahead of time. You can add any code that you like while your program runs; the new code may not have even been written when you started executing your program. Loading code on-the-fly is useful in many situations, but it is particularly important feature of an interactive programming environment.

Added Complications of Dynamic Linking

One problem with linking to an existing binary is that the result will be read into some memory buffer. We need to give the address of that buffer to the linker, so it can figure out where the new routines will be located. A more interesting problem is the exporting and importing of symbols.

Suppose you have a running program called "parent" that decides to load the module "child." Child consists of a group of functions; when parent calls the dynamic linker, the linker returns a list of all the functions in child and their addresses. That might be good enough--child sits inside parent like a foreign body, providing a set of services--but such a restricted solution has its disadvantages. Parent may have functions in it that would be useful for child to access. Also, if parent and child both use the same library routines, each would need its own copy, wasting space.

Since the idea of a linker is to let many pieces of code act together as a single entity, we would like a dynamic linker to offer the same level of integration. In other words, the linker should also have a list of parent's symbols, to which child is allowed access. If parent wants the dynamic linker to act just like a static linker, it can give the linker a complete symbol table. Alternatively, parent can restrict the list, limiting child's access to internal routines. This has the additional advantage that child can use any of the unexported symbols inside parent for its own purposes without causing a conflict.

UNIX Support

Berkeley UNIX (BSD) added support for dynamic linking because the Franz LISP system that came with it offered the ability to invoke code written in more traditional languages. Franz could have executed the foreign code as a separate program, but passing data between languages would have been extremely inefficient. Rather than incorporating a modified linker into Franz, the implementors chose to add the functionality into the standard linker, ld. Unfortunately, the man page for ld devotes a single paragraph to dynamic linking, enough only for someone well versed in the details of ld's behavior. I figured out how the facility works by poking around in the UNIX source code for ld and nm, and by getting some helpful tips from Keith Sklower(a Berkeley staff programmer and one of the Franz LISP implementors). Listing One (page 86) contains dyn_link, a simple program that invokes ld on a new module, loads the result, and lets the user call functions within it. Listing Two (page 88) is a sample module to load.

Invoking ld

To prepare a module for dynamic linking, you invoke the linker with

ld-A old_binary -T F000 -N new_file.o

The -A flag notifies ld that it is being used as a dynamic linker. Instead of constructing a new binary from scratch, it will base its address resolution on an existing binary called old_binary. It scans through old_binary, keeping track of all the symbols in it which are publicly exported. If new_file.o refers to those symbols, the addresses will be patched up correctly. If we specify our real initial binary, all our symbols will be visible to new_file.o. To achieve better control, dyn_link sets up an ititial binary with a restricted symbol table that contains only those symbols we are willing to export.

When dyn_link loads the new code, it first figures out how large it is and allocates a large enough buffer. We have to tell ld the buffer's starting address so it knows what the new code's addresses will be after being loaded. The -T flag specifies the address, which in this invocation is set to F000h. Many linkers require that this address be on an even-page boundary.

The values called "magic numbers" in UNIX are just a code that tells the system what kind of information is in a file and how to treat it. Unlike older operating systems (OS/360, for example), files in UNIX are simply a sequence of bytes. However, many kinds of files require special treatment. The system must know, for instance, that a given file is an executable, so it doesn't try to run a data file. One way UNIX distinguishes file types is by putting these magic numbers at the beginning of the file. The magic number for an executable also tells the system how to do memory management. UNIX, like MS-DOS, can load whole programs at once, but it is much more efficient to do demand paging--bring in pieces of the program when needed--because most of the time a program only executes a small fraction of its body. Why waste all that valuable RAM to store unused information? Since dyn_link reads all the new code into a buffer, the magic number in the executable is never seen by the operating system. For simplicity's sake, dyn_link specifies the -N flag, asking that old-fashioned memory management be used.

If the linking process succeeds, ld creates a binary with just the new code, ready to load into memory. The binary's symbol table has all the symbols from the original binary, together with the new ones. The dyn_link program figures out if the link succeeded by looking at the return value; ld returns 0 if there were no errors and nonzero otherwise.

UNIX Binary File Format

There are three main object-module formats in current implementations of UNIX. The oldest, a.out, was developed for the DEC VAX and is still used in BSD. The other two, COFF and ELF, are used in other implementations of UNIX (notably those developed by AT&T). Since dyn_link is designed for BSD, it only knows about the a.out format.

Figure 1 shows the structure of an a.out file, which is made up of five regions. The first is the exec structure, which gives a road map to the rest of the file. The fields of interest to dyn_link are the text, data, and bss segment sizes, and the symbol-table size. The exec structure is followed by the text and data segments; text contains executable code, and data has space for global and initialized static variables. The bss segment is not stored in the binary, but created when the program is executed; bss is used to store uninitialized static variables and is initially filled with 0s. The text and data segments follow the exec structure immediately, unless the binary is set up for demand-paged memory management, in which case they (usually) follow on the next page boundary. UNIX lets the programmer ignore this bit of complexity by providing a macro (N_TXTOFF) which returns the starting address of the text segment. After the two segments, there may be some relocation records; we won't worry about those. The last two regions of the file are the symbol table and the string table, the addresses of which are given by N_SYMOFF and N_STROFF, respectively.

Figure 1: Structure of an a.out file.

  N_TXTOFF:  Exec structure
             (possibly padded to page boundary)
             Text and data segments
             Relocation records
  N_SYMOFF:  Symbol table
  N_STROFF:  String table

There is a separate string table so that each symbol can be described in a fixed amount of space; the symbol description gives an offset into the string table for the symbol's name. Each name in the string table is followed by '\0'. It would be a little more convenient if the string table's size were also in the exec structure, but it isn't. It is the first piece of information in the table; see Figure 2 for an example.

Figure 2: Sample string table.

                  12
                  ''
                  -
                 'g'
                 'r'
                 'e'
                 'e'
                 'n'
                '\0'
                  ''
                  -
                 'r'
                 'e'
                 'd'
                '\0'

The symbol table is a series of nlist structures; see Figure 3. The structure fields we'll be using are the symbol's type (internal, external, text, and so on), an index into the string table for the name, and the value. In an executable binary, the value is its address in memory.

The Code

The dyn_link program in Listing One is a simple demonstration of dynamic linking. It loads SAMPLE.o, the object module derived from Listing Two, exporting any symbols inside itself which begin with export. While loading the new code, dyn_link displays the names of all the newly defined external functions. It then loops, waiting for the user to specify functions to call. The dyn_link program counts the number of times the user specifies a function to call, passing that value as the argument. When the user hits a carriage return, dyn_link exits.

Begin by setting up a binary that reflects the currently running program. We could use the actual dyn_link binary, but then its internal symbols would be exported. To restrict the exported symbols to those that start with export, we'll construct a fake binary with a restricted symbol table. First, open the original dyn_link binary and read in the exec structure at the front. The find_symbol_table() routine prepares us to look through the symbols. It uses the N_STROFF macro in calling fseek() to move to the start of the string table. The first word in the string table is an integer that gives its length. When we scan through the symbols, we can use the string table to figure out their names. We prepare an output file by opening it destructively and moving the file pointer forward to leave space for the exec structure. We can't write the new exec structure yet, because we don't know how many symbols will be in the fake binary.

With the string table in hand, one file pointer sitting at the beginning of the old binary's symbol table, and another file pointer to an empty file, we are ready to pick out the symbols to export. The output_exported_symbols() routine loops through the symbols in the old binary (having computed their number from the information in the exec structure), building up a new string table. Each symbol that starts with _export is copied into the new file; the n_un.strx field is patched to point to the name in the new string table. (The preceding underscore is automatically added to symbols by the C compiler.) After all the symbols have been scanned, we write out the size of the new string table and its contents. Finally, we rewind the output file and write the new exec structure at the beginning. The new exec is a copy of the old one, modified to reflect the smaller number of symbols and the absence of code.

To access the functions in the new code after we load it, we need to find all the text symbols (like function names) that it exports. It is easiest to find them in the object module being loaded, before the old and new symbols are mixed together into a single binary by the linker. This is the job of get_ops(), which scans through the symbols in the object module in much the same way that output_exported_symbols() reads the initial binary. The get_ops() function looks at each symbol, checking to see if it is an external text symbol by looking at the n_type field in its nlist structure. If so, get-ops() adds it to a linked list of new functions.

Before invoking the linker, we must allocate enough space in memory for the loaded code, so that we can tell the linker what the new starting address will be. Many linkers insist that the address be at a page boundary, so allocate_space() uses the PAGE_SIZE constant to enforce that constraint. Finally, we use system() to invoke the linker. If there was a problem, the linker returns a nonzero value, in which case we exit. If everything succeeded, there should now be a file called "a.out" which contains the new code ready to be loaded.

To read in the code, open a.out and read the exec structure. Use the N_TXTOFF macro to find the beginning of the text and data segments and read them into the previously allocated buffer. Then get the string table and go through the symbols one more time. Running through the symbols in the linked list, scan through the symbol table to find each one and figure out its address (which is stored in the n_value field of the nlist structure). To avoid scanning the entire symbol table for each symbol, dyn_link relies on the fact that the symbols in the linked binary's table appear in the same order as they did in the object code's table.

With the code loaded into memory, calling it is straightforward; when the user types in the name of a routine, dyn_link runs through the linked list looking for it. If the name is in the list, the structure contains a pointer to the function.

Note that the loaded code cannot use any of the standard C library calls because we did not export them. To allow the routines to print something, dyn_link exports a routine called exported_printf(), which the loaded code can use. Alternatively, you could modify dyn_link to export more of its internal symbols, including the standard C library routines it contains.

Listing Four, page 88, shows a sample run of dyn_link. The program lists the routines exported by SAMPLE.o and lets the user execute them. The first one, simple(), just calls exported_printf() with a message. The call_back() function prints a message, calls the routine export_hook() inside dyn_link, and prints the return value.

Portability

I've tested the code in Listing One under SunOS (Sun 3 and SPARC), BSD 4.3 Reno, and Dynix. Under Dynix, the linker looks for a symbol called start, so you must export it along with the ones starting with _export. On a SPARC, you must compile the original dyn_link binary with the -Bstatic flag. (See the makefile in Listing Three, page 88.) To use dyn_link on a different flavor of BSD, you may have to make a few changes. There might be special symbols that must be defined, or the linker might add some segments to the a.out file, which you must leave space for in your allocated buffer. include files also have an unfortunate habit of moving or changing their names slihtly. Make sure that the constant PAGE_SIZE is an even multiple of your system's page size. (If you aren't sure of the value for your system, 4096 is a pretty safe bet.)

A further complication is that modern RISC processors have made life more difficult for dynamic linkers. Because they have a variety of caching strategies, it is possible to run afoul of the processor by trying to cross back and forth between old and newly loaded code. David Keppel's "A Portable Interface for On-the-Fly Instruction Space Modification" provides a detailed discussion of the problem and its solution.

If your linker does not support dynamic linking, or you want to have more control over the process, Wilson Ho has released a dynamic linker called "dld" through the GNU project. Unfortunately, it does not yet support System V. GNU code is available on many archives and bulletin boards (notably prep.ai.mit.edu on the Internet).

References

Keppel, David. "A Portable Interface for On-the-Fly Instruction Space Modification." Fourth International Conference on Architectural Support for Programming Languages and Operation Systems (ASPLOS). Santa Clara, CA (April, 1991).

Ho, W. Wilson and Ronald A. Olsson. "An Approach to Genuine Dynamic Linking." Software--Practice and Experience (April, 1991).



_DYNAMIC LINKING UNDER BERKELEY UNIX_
by Oliver Sharp


[LISTING ONE]
<a name="0128_000e">

/*  dyn_link.c - a simple dynamic linker  */
#include <stdio.h>
#include <a.out.h>

/************                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 (<CR> 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);
}





<a name="0128_000f">
<a name="0128_0010">
[LISTING TWO]
<a name="0128_0010">

/*  sample.c - a few functions to test the dynamic loader  */
#include <stdio.h>
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);
}





<a name="0128_0011">
<a name="0128_0012">
[LISTING THREE]
<a name="0128_0012">

# 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




<a name="0128_0013">
<a name="0128_0014">
[LISTING FOUR]
<a name="0128_0014">

Sizes: text = 0x5c, data = 0x78, bss = 0x0.  Total = 0xd4,212
simple
call_back
Enter name of routine to call (<CR> to exit): simple
I'm a simple routine and got argument: 1
Enter name of routine to call (<CR> 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 (<CR> to exit): simple
I'm a simple routine and got argument: 3
Enter name of routine to call (<CR> 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 (<CR> to exit):











Copyright © 1993, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.