« FCC wiretapping regs upheld, so what? | Main | Security of contactless smartcards »
June 11, 2006
Notes on plugins and shared libraries
One of the systems I work on has a feature that lets you add new functionality via loading shared libraries (i.e., plugins). This is a pretty conventional technique for programs like Apache, IE, etc. but we add a new wrinkle; there are multiple programs that all need to load the same shared object. For instance, if you want to add a new protocol decoder, you're actually adding it in three places:
- Into the packet processing stack (the obvious place).
- New commands into the management shell.
- A statistics pretty printer into the statistics command line utility.
So, at some level here you actually have three plugins that work together, one for each application. You could do this as three separate .sos, but that makes for a nasty management problem, especially as the number of applications goes up, and of course it makes code re-use difficult. So, ideally, you'd like to just have one .so.
Getting something like this to work properly requires a fair
amount of attention to dependencies (I'm assuming you know
how to make a .so using ld -shared.)
The simplest problem is
that the plugins need to invoke functions that are in the
main program. For instance, in the management shell we
need to call functions in the main program in order to register
our new commands. Normally, functions that are linked into
the main program aren't exported to dynamically loaded objects,
so you need to use the --export-dynamic argument
to ld when you link the main program.
A secondary problem with this kind of reference is that there may be functions that plugins depend upon that aren't called anywhere in the program itself. Plugins need to be able to know they can call a specific API. In order to make this work, you need to force all the API functions to be linked into the application program (so they can be re-exported to the plugins). Basically, you do this by creating a new source file that references the API functions via placing a function pointer for the API function into some static variable. On UNIX-type systems, files are linked in one at a time, and the transitive closure of all references needs to be linked in. So, what you do is create some API forcing file which points to each API file (at least transitively) and then have that file export some variable that gets referenced in some source file that is actually used in the main program.
The above two techniques take care of making the functions we need available. Now we have to deal with the opposite problem, which is having things work when not everything is available. So, for instance, if we're writing an SSL processing module, it needs to reference OpenSSL in order to do its thing. But, the prettyprinter for its stats doesn't need OpenSSL. We don't want to force the stats system to link with OpenSSL just to print some values.
This is taken care of with two techniques. First, when we
dynamically load modules, we call dlopen with
the argument RTLD_LAZY. This tells it only
to try to resolve functions when they're first referenced
rather then when the library is loaded. This gets us part
but not all of the way there. The second thing is that we
need to compile our plugins with position-independent code---code which
can run correctly no matter where the library is loaded
into memory. With gcc, this is done with the -fPIC
falg.
This is a simple fact and something I knew about, but when I
started writing shared libraries for this system, I hadn't
written one in a long time and I forgot -fPIC
and yet all my shared libraries seemed to load and work properly
so I never noticed the problem. Then I noticed that I was starting
to have problems where even though I was loading with
RTLD_LAZY, dlopen was still trying
to resolve all the symbols referenced in the .so. This isn't
just a code size issue: remember that the module needs
to call functions in all the application programs, but the
management shell doesn't provide the functions that are in
the packet processing stack, so you get linkage errors.
A little debugging resolved what appears to be the problem: if you don't
provide -fPIC you get relocatable code and the linker
automatically relocates it, but it tries to resolve all (or at
least some) of the symbols even if they're not referenced, which,
as I mentioned before, is bad.
So, to recap:
- Forcibly link all the APIs that plugins can depend on.
- Use
--export-dynamicto make those APIs available to the plugins. - Compile your plugin code with
-fPIC - Use
RTLD_LAZYwhen you link in the modules.
Fun, eh?
Posted by ekr at June 11, 2006 8:43 PM | Filed under:
Comments
Does the gcc 4.x __attribute__(dllexport) work the way you might want it to for exporting symbols from the app? That might remove the need for the hacky force-the-linkage thing. You might also need to use -fkeep-inline-functions to make sure that anything which gets inlined in the main app is still there as a callable exported function too. Using dllexport instead of --export-dynamic will allow you better control over which symbols are exported (assuming that it works right for apps and not just regular DLLs). Otherwise some fun-loving plugin author might start calling functions in your app which they shouldn't be calling, since *everything* is exported.
Posted by: Craig Hughes at June 12, 2006 12:01 PM