This chapter polishes the worked example I introduced in A Small COLLECTIVE Project, and developed in A Large COLLECTIVE Project. As always, the ideas presented here are my own views and not necessarily the only way to do things.  Everything I present here has, however, served me well for quite some time, and you should find plenty of interesting ideas for your own projects.

Herein, I will add a libltdl module loading system to Sic, as well as some sample modules to illustrate how extensible such a project can be. I will also explain how to integrate the dmalloc library into the development of a project, and show why this is important.

If you noticed that, as it stands, Sic is only useful as an interactive shell unable to read commands from a file, then go to the top of the class!  In order for it to be of genuine use, I will extend it to interpret commands from a file too.

A Module Loading Subsystem
==========================

As you saw in Using GNU libltdl, I need to put an invocation of the macro AC_LIBTOOL_DLOPEN just before AC_PROG_LIBTOOL, in the file configure.in.  But, as well as being able to use libtoolize --ltdl, which adds libltdl in a subdirectory with its own subconfigure, you can also manually copy just the ltdl source files into your project (If you have an early 1.3c snapshot of Libtool, you will also need to copy the ltdl.m4 file into your distribution.), and use AC_LIB_LTDL in your existing configure.in.  At the time of writing, this is still a very new and (as yet) undocumented feature, with a few kinks that need to be ironed out.  In any case you probably shouldn't use this method to add ltdl.lo to a C++ library, since ltdl.c is written in C. If you do want to use libltdl with a C++ library, things will work much better if you build it in a subdirectory generated with libtoolize --ltdl.

For this project, lets:

     $ cp /usr/share/libtool/libltdl/ltdl.[ch] sic/

The Sic module loader is probably as complicated as any you will ever need to write, since it must support two kinds of modules: modules which contain additional built-in commands for the interpreter; and modules which extend the Sic syntax table.  A single module can also provide both syntax extensions and additional built-in commands.

Initialising the Module Loader
------------------------------

Before using this code (or any other libltdl based module loader for that matter), a certain amount of initialisation is required:

  * libltdl itself requires initialisation.

      1. libltdl should be told to use the same memory allocation routines used by the rest of Sic.

      2. Any preloaded modules (see dlpreopen Loading) need to be initialised with LTDL_SET_PRELOADED_SYMBOLS().

      3. ltdl_init() must be called.

  * The module search path needs to be set.  Here I allow the installer to specify a default search path to correspond with the installed Sic modules at compile time, but search the directories in the runtime environment variable SIC_MODULES_PATH first.

  * The internal error handling needs to be initialised.

Here is the start of the module loader, sic/module.c, including the initialisation code for libltdl:

     m4_include(examples/sic-complex/sic/module_1.texi)

Managing Module Loader Errors
-----------------------------

The error handling is a very simplistic wrapper for the libltdl error functions, with the addition of a few extra errors specific to this module loader code (This is very different to the way errors are managed when writing a custom loader for libltdl. Compare this section with Loader Errors.).  Here are the error messages from module.c:

     m4_include(examples/sic-complex/sic/module_2.texi)

Loading a Module
----------------

Individual modules are managed by finding specified entry points (prescribed exported symbols) in the module:

 -- Variable: const Builtin * builtin_table
     An array of names of built-in commands implemented by a module, with associated handler functions.

 -- Function: void module_init (Sic *SIC)
     If present, this function will be called when the module is loaded.

 -- Function: void module_finish (Sic *SIC)
     If supplied, this function will be called just before the module is unloaded.

 -- Variable: const Syntax * syntax_table
     An array of syntactically significant symbols, and associated handler functions.

 -- Function: int syntax_init (Sic *SIC)
     If specified, this function will be called by Sic before the syntax of each input line is analysed.

 -- Function: int syntax_finish (Sic *SIC, BufferIn *IN, BufferOut *OUT)
     Similarly, this function will be call after the syntax analysis of each line has completed.

All of the hard work in locating and loading the module, and extracting addresses for the symbols described above is performed by libltdl.  The module_load function below simply registers these symbols with the Sic interpreter so that they are called at the appropriate times -- or diagnoses any errors if things don't go according to plan:

     m4_include(examples/sic-complex/sic/module_3.texi)

Notice that the generalised List data type introduced earlier (see A Small COLLECTIVE Project) is reused to keep a list of accumulated module initialisation and finalisation functions.

Unloading a Module
------------------

When unloading a module, several things must be done:

  * Any built-in commands implemented by this module must be unregistered so that Sic doesn't try to call them after the implementation has been removed.

  * Any syntax extensions implemented by this module must be similarly unregistered, including syntax_init and syntax_finish functions.

  * If there is a finalisation entry point in the module, module_finish (see Loading a Module), it must be called.

My first cut implementation of a module subsystem kept a list of the entry points associated with each module so that they could be looked up and removed when the module was subsequently unloaded.  It also kept track of multiply loaded modules so that a module wasn't unloaded prematurely.  libltdl already does all of this though, and it is wasteful to duplicate all of that work.  This system uses lt_dlforeach and lt_dlgetinfo to access libltdls records of loaded modules, and save on duplication.  These two functions are described fully inLibltdl interface (The Libtool Manual).

     m4_include(examples/sic-complex/sic/module_4.texi)

This function asks libltdl to call the function unload_ltmodule for each of the modules it has loaded, along with some details of the module it wants to unload.  The tricky part of the callback function below is recalculating the entry point addresses for the module to be unloaded and then removing all matching addresses from the appropriate internal structures.  Otherwise, the balance of this callback is involved in informing the calling lt_dlforeach loop of whether a matching module has been found and handled:

     m4_include(examples/sic-complex/sic/module_5.texi)

The userdata_address_compare helper function at the end is used to compare the address of recalculated entry points against the already registered functions and handlers to find which items need to be unregistered.

There is also a matching header file to export the module interface, so that the code for loadable modules can make use of it:

     m4_include(examples/sic-complex/sic/module_h_1.texi)

This header also includes some of the other Sic headers, so that in most cases, the source code for a module need only #include <sic/module.h>.

To make the module loading interface useful, I have added built-ins for load and unload.  Naturally, these must be compiled into the bare sic executable, so that it is able to load additional modules:

     m4_include(examples/sic-complex/src/sic_builtin_1.texi)

These new built-in commands are simply wrappers around the module loading code in module.c.

As with dlopen, you can  use libltdl to lt_dlopen the main executable, and then lookup its symbols.  I have simplified the initialisation of Sic by replacing the sic_init function in src/sic.c by ``loading'' the executable itself as a module.  This works because I was careful to use the same format in sic_builtin.c and sic_syntax.c as would be required for a genuine loadable module, like so:

     m4_include(examples/sic-complex/src/sic_1.texi)

A Loadable Module
=================

A feature of the Sic interpreter is that it will use the unknown built-in to handle any command line which is not handled by any of the other registered built-in callback functions.  This mechanism is very powerful, and allows me to lookup unhandled built-ins in the user's PATH, for instance.

Before adding any modules to the project, I have created a separate subdirectory, modules, to put the module source code into.  Not forgetting to list this new subdirectory in the AC_OUTPUT macro in configure.in, and the SUBDIRS macro in the top level Makefile.am, a new Makefile.am is needed to build the loadable modules:

     m4_include(examples/sic-complex/modules/Makefile_1.texi)

pkglibdir is a Sic specific directory where modules will be installed, see Installing and Uninstalling Configured Packages.

     For a library to be maximally portable, it should be written so that it does not require back-linking (see Introducing libltdl) to resolve its own symbols.  That is, if at all possible you should design all of your libraries (not just dynamic modules) so that all of their symbols can be resolved at linktime.  Sometimes, it is impossible or undesirable to architect your libraries and modules in this way.  In that case you sacrifice the portability of your project to platforms such as AIX and Windows.

The key to building modules with libtool is in the options that are specified when the module is linked.  This is doubly true when the module must work with libltdl's dlpreopening mechanism.

     m4_include(examples/sic-complex/modules/Makefile_2.texi)

Sic modules are built without a lib prefix (-module), and without version suffixes (-avoid-version).  All of the undefined symbols are resolved at linktime by libsic.la, hence -no-undefined.

Having added ltdl.c to the sic subdirectory, and called the AC_LIB_LTDL macro in configure.in, libsic.la cannot build correctly on those architectures which do not support back-linking.  This is because ltdl.c simply abstracts the native dlopen API with a common interface, and that local interface often requires that a special library be linked -- -ldl on linux, for example.  AC_LIB_LTDL probes the system to determine the name of any such dlopen library, and allows you to depend on it in a portable way by using the configure substitution macro, @LIBADD_DL@.  If I were linking a libtool compiled libltdl at this juncture, the system library details would have already been taken care of.  In this project, I have bypassed that mechanism by compiling and linking ltdl.c myself, so I have altered sic/Makefile.am to use @LIBADD_DL@:

     m4_include(examples/sic-complex/sic/Makefile_1.texi)

Having put all this infrastructure in place, the code for the unknown module is a breeze (helper functions omitted for brevity):

     m4_include(examples/sic-complex/modules/unknown_1.texi)

In the first instance, notice that I have used the preprocessor to redefine the entry point functions to be compatible with libltdls dlpreopen, hence the unknown_LTX_builtin_table cpp macro.  The unknown handler function itself looks for a suitable executable in the user's path, and if something suitable is found, executes it.

Notice that Libtool doesn't relink dependent libraries (libsic depends on libcommon, for example) on my GNU/Linux system, since they are not required for the static library in any case, and because the dependencies are also encoded directly into the shared archive, libsic.so, by the original link.  On the other hand, Libtool will relink the dependent libraries if that is necessary for the target host.

     $ make
     /bin/sh ../libtool --mode=compile gcc -DHAVE_CONFIG_H -I. -I. -I.. \
     -I.. -I.. -I../sic -I../sic -I../src -I../src    -g -O2 -c unknown.c
     mkdir .libs
     gcc -DHAVE_CONFIG_H -I. -I. -I.. -I.. -I.. -I../sic -I../sic -I../src \
     -I../src -g -O2 -Wp,-MD,.deps/unknown.pp -c unknown.c  -fPIC -DPIC \
     -o .libs/unknown.lo
     gcc -DHAVE_CONFIG_H -I. -I. -I.. -I.. -I.. -I../sic -I../sic -I../src \
     I../src -g -O2 -Wp,-MD,.deps/unknown.pp -c unknown.c -o unknown.o \
     >/dev/null 2>&1
     mv -f .libs/unknown.lo unknown.lo
     /bin/sh ../libtool --mode=link gcc  -g -O2  -o unknown.la -rpath \
     /usr/local/lib/sic -no-undefined -module -avoid-version unknown.lo \
     ../sic/libsic.la
     rm -fr .libs/unknown.la .libs/unknown.* .libs/unknown.*
     gcc -shared  unknown.lo -L/tmp/sic/sic/.libs ../sic/.libs/libsic.so \
     -lc  -Wl,-soname -Wl,unknown.so -o .libs/unknown.so
     ar cru .libs/unknown.a  unknown.o
     creating unknown.la
     (cd .libs && rm -f unknown.la && ln -s ../unknown.la unknown.la)
     $ ./libtool --mode=execute ldd ./unknown.la
             libsic.so.0 => /tmp/sic/.libs/libsic.so.0 (0x40002000)
             libc.so.6 => /lib/libc.so.6 (0x4000f000)
             libcommon.so.0 => /tmp/sic/.libs/libcommon.so.0 (0x400ec000)
             libdl.so.2 => /lib/libdl.so.2 (0x400ef000)
             /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x80000000)

After compiling the rest of the tree, I can now use the unknown module:

     m4_changequote(,)m4_dnl
     $ SIC_MODULE_PATH=`cd ../modules; pwd` ./sic
     m4_changequote(`,')m4_dnl
     ] echo hello!
     command "echo" not found.
     ] load unknown
     ] echo hello!
     hello!
     ] unload unknown
     ] echo hello!
     command "echo" not found.
     ] exit
     $

Interpreting Commands from a File
=================================

For all practical purposes, any interpreter is pretty useless if it only works interactively.  I have added a source built-in command to sic_builtin.c which takes lines of input from a file and evaluates them using sic_repl.c in much the same way as lines typed at the prompt are evaluated otherwise.  Here is the built-in handler:

     m4_include(examples/sic-complex/src/sic_builtin_2.texi)

And the source function from sic_repl.c:

     m4_include(examples/sic-complex/src/sic_repl_1.texi)

The reason for separating the source function in this way, is that it makes it easy for the startup sequence in main to evaluate a startup file.  In traditional Unix fashion, the startup file is named .sicrc, and is evaluated if it is present in the user's home directory:

     m4_include(examples/sic-complex/src/sic_2.texi)

Integrating Dmalloc
===================

A huge number of bugs in C and C++ code are caused by mismanagement of memory.  Using the wrapper functions described earlier (see Memory Management), or their equivalent, can help immensely in reducing the occurrence of such bugs.  Ultimately, you will introduce a difficult-to-diagnose memory bug in spite of these measures.

That is where Dmalloc (Dmalloc is distributed from http://www.dmalloc.com.) comes in.  I recommend using it routinely in all of your projects --- you will find all sorts of leaks and bugs that might otherwise have lain dormant for some time.  Automake has explicit support for Dmalloc to make using it in your own projects as painless as possible.  The first step is to add the macro AM_WITH_DMALLOC to configure.in.  Citing this macro adds a --with-dmalloc option to configure, which, when specified by the user, adds -ldmalloc to LIBS and defines WITH_DMALLOC.

The usefulness of Dmalloc is much increased by compiling an entire project with the header, dmalloc.h -- easily achieved in Sic by conditionally adding it to common-h.in:

     m4_include(examples/sic-complex/sic/common-h.texi)

I have been careful to include the dmalloc.h header from the end of this file so that it overrides my own definitions without renaming the function prototypes.  Similarly I must be careful to accommodate Dmalloc's redefinition of the mallocation routines in sic/xmalloc.c and sic/xstrdup.c, by putting each file inside an #ifndef WITH_DMALLOC.  That way, when compiling the project, if --with-dmalloc is specified and the WITH_DMALLOC preprocessor symbol is defined, then Dmalloc's debugging definitions of xstrdup et. al. will be used in place of the versions I wrote.

Enabling Dmalloc is now simply a matter of reconfiguring the whole package using the --with-dmalloc option, and disabling it again is a matter of recofinguring without that option.

The use of Dmalloc is beyond the scope of this book, and is in any case described very well in the documentation that comes with the package.  I strongly recommend you become familiar with it -- the time you invest here will pay dividends many times over in the time you save debugging.

This chapter completes the description of the Sic library project, and indeed this part of the book.  All of the infrastructure for building an advanced command line shell is in place now -- you need only add the builtin and syntax function definitions to create a complete shell of your own.

Each of the chapters in the next part of the book explores a more specialised application of the COLLECTIVE, starting with a discussion of M4, a major part of the implementation of Autoconf.