next up previous
Next: ELF Support in Up: Dynamic Linking and Previous: Dynamic Linking

Dynamic Loading

Dynamic loading is the process in which one can attach a shared library to the address space of the process during execution, look up the address of a function in the library, call that function and then detach the shared library when it is no longer needed. It is implemented as an interface to the services of the dynamic linker.

Under ELF, the programming interface is usually defined in <dlfcn.h>. These are:

void *dlopen (const char * filename, int flag);
const char * dlerror (void);
const void * dlsym (void  handle*, const char * symbol);
int dlclose (void * handle);

These functions are contained in libdl.so. Here is an example of how dynamic loading works.

We have a main program which loads in the shared library dynamically at the run time. One can specify which shared library to use and which function to call. One can also access the data in the shared library.

# cat dltest.c

#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <dlfcn.h>
#include <ctype.h>

typedef void (*func_t) (const char *);

void
dltest (const char *s)
{
  printf ("From dltest: ");
  for (; *s; s++)
  {
    putchar (toupper (*s));
  }
  putchar ('\n');
}

main (int argc, char **argv)
{
  void *handle;
  func_t fptr;
  char *libname = "libfoo.so";
  char **name = NULL;
  char *funcname = "foo";
  char *param = "Dynamic Loading Test";
  int ch;
  int mode = RTLD_LAZY;
  
  while ((ch = getopt (argc, argv, "a:b:f:l:")) != EOF)
  {
    switch (ch)
    {
    case 'a': /* argument. */
      param = optarg;
      break;
      
    case 'b': /* how to bind. */
      switch (*optarg)
      {
      case 'l': /* lazy */
        mode = RTLD_LAZY;
        break;

      case 'n': /* now */
        mode = RTLD_NOW;
        break;
      }
      break;

    case 'l': /* which shared library. */
      libname = optarg;
      break;

    case 'f': /* which function? */
      funcname = optarg;
    }
  }

  handle = dlopen (libname, mode);
  if (handle == NULL)
  {
    fprintf (stderr, "%s: dlopen: `%s'\n", libname, dlerror ());
    exit (1);
  }

  fptr = (func_t) dlsym (handle, funcname);
  if (fptr == NULL)
  {
    fprintf (stderr, "%s: dlsym: `%s'\n", funcname, dlerror ());
    exit (1);
  }

  name = (char **) dlsym (handle, "libname");
  if (name == NULL)
  {
    fprintf (stderr, "%s: dlsym: `libname'\n", dlerror ());
    exit (1);
  }

  printf ("Call `%s' in `%s':\n", funcname, *name);

  /* call that function with `param' */
  (*fptr) (param);

  dlclose (handle);
  return 0;
}

There are two shared libraries here, libfoo.so and libbar.so. Each has the same global string variable, libname, but their own functions, foo and bar, respectively. They are both available to the program via dlsym.

# cat libbar.c

 
#include <stdio.h>

extern void dltest (const char *);
const char *const libname = "libbar.so";

void bar (const char *s) 
{
  dltest ("called from libbar.");
  printf("libbar: %s\n", s);
}

# cat libfoo.c

 
#include <stdio.h>

extern void dltest (const char *s);
const char *const libname = "libfoo.so";

void
foo (const char *s) 
{
  const char *saved = s;

  dltest ("called from libfoo");
  printf("libfoo: ");
  for (; *s; s++);
  for (s--; s >= saved; s--)
  {   
    putchar (*s);
  }
  putchar ('\n');
}

Makefile is used to build the shared libraries and the main program since libbar.so and libfoo.so call the function dltest in the main program.

# cat Makefile

 
CC=gcc
LDFLAGS=-rdynamic
SHLDFLAGS=

all: dltest

libfoo.o: libfoo.c
        $(CC) -c -fPIC $<

libfoo.so: libfoo.o
        $(CC) $(SHLDFLAGS) -shared -o $@ $^

libbar.o: libbar.c
        $(CC) -c -fPIC $<

libbar.so: libbar.o
        $(CC) $(SHLDFLAGS) -shared -o $@ $^

dltest: dltest.o libbar.so libfoo.so
        $(CC) $(LDFLAGS) -o $@ dltest.o -ldl

clean:
        $(RM) *.o *.so dltest

Here is the flow of the program:

# export ELF_LD_LIBRARY_PATH=.
# dltest
Call `foo' in `libfoo.so':
From dltest: CALLED FROM LIBFOO
libfoo: tseT gnidaoL cimanyD
# dltest -f bar
bar: dlsym: `Unable to resolve symbol'
# dltest -f bar -l libbar.so
Call `bar' in `libbar.so':
From dltest: CALLED FROM LIBBAR.
libbar: Dynamic Loading Test

dlopen is the first function to call in the dynamic loading process which makes a shared library, libfoo.so, available to a running process. dlopen returns a handle which should be used in subsequent calls to the dlsym and dlclose functions for operation on libfoo.so. Using NULL for the filename argument of dlopen has a special meaning --- it will make the exported symbols in the executable and the currently loaded shared libraries available via dlsym.

After a shared library has been loaded into the address space of the running process, dlsym may be used to obtain the address of an exported symbol in that shared library. One can then access the function or data through the address returned from dlsym.

dlclose can be called to detach the shared library loaded in via the dlopen call when the shared library is no longer needed. The shared library will not be removed from the address space of the calling process if the shared library has been loaded in during startup time or through other dlopen calls.

dlclose returns 0 if the operation on the shared library is successful. Both dlopen and dlsym will return NULL in case of any error condition. dlerror can be called in that case to obtain diagnostic information. More detailed programming information on dlopen/dlsym/dlclose/dlerror can be found in [1].



next up previous
Next: ELF Support in Up: Dynamic Linking and Previous: Dynamic Linking



J.H.M.Dassen
Tue Aug 1 14:18:10 MDT 1995