Search code examples
ccompilationlinkerstatic-linkinglibtool

How does one use libtool/libltdl's dlpreopening / preloading?


Here is a crude example of the type of code I would like to use libtool's libltdl dlpreopening with:

https://github.com/EmmaJaneBonestell/dlopen-sample

I wish to be able to rewrite various projects that use libdl functions ( dlopen, dlsym, etc ), to instead use libtool's libltdl dlpreopening/preloading mechanism. From libtool's documentation, it will instead link the objects at compile time, resulting in truly static executables. It was intended for systems that do not support dynamic loading.

If you use it to compile and link these objects, libtool will run some of its scripts, and create the necessary data structures, and I believe perform mangling and demangling, to allow for duplicate symbol names.

Even after looking at the examples in libtool's source code, reading its entire documentation, and looking at relevant tests from the test-suite (e.g. tests 118 & 120), I've been unable to do so.

Outside of libtool itself, there is essentially no mention of this functionality anywhere I could find. I did manage to see it used in a too-complex-for-me manner in Graphviz, but there's little documentation on that either.

I'm really not much of a programmer, more of a tinkerer. It's easy enough for me to use libltdl for a wrapper for standard dlopen/dlsym.

I think my main issue is figuring out how I can return a proper handle for a preopened object, though I may be doing other things incorrectly.

The documentation states that lt_dlopen can work on preloaded static modules, but it doesn't seem to accept any possible reference to it I could imagine.

I would also prefer not to have to even invoke libtool, but c'est la vie.

I didn't bother including any of what I've tried code wise (on the example) because I've tried such a mess of things that I feel it would really just be confusing to show it.

Current libtool documentation: https://www.gnu.org/software/libtool/manual/libtool.html


Solution

  • Apparently libtool requires the .la file to be present and have its rpath properly declared, even though it won't be used for anything in this truly-static binary.

    Libtool's documentation makes fairly clear that your "module" files should contain the following preprocessor macro for all symbols to be exported.

    #define SYMBOL SOURCEFILENAME_LTX_SYMBOL
    

    In my case, this results in bromine.c and chlorine.c having the following, respectively.

    #define chemical_name bromine_LTX_chemical_name
    #define chemical_name chlorine_LTX_chemical_name
    

    Still assuming the code repository in my question, the following commands are an example of how to use libtool to automatically dlpreopen your binary.

    libtool --mode=compile gcc -static -fPIC -fno-plt -c bromine.c
    
    libtool --mode=compile gcc -static -fPIC -fno-plt -c chlorine.c
    
    libtool --mode=link gcc -all-static -fPIC -fno-plt -o bromine.la -rpath $PWD -module -no-undefined -avoid-version bromine.lo
    
    libtool --mode=link gcc -all-static -fPIC -fno-plt -o chlorine.la -rpath $PWD -module -no-undefined -avoid-version chlorine.lo
    

    However, for me, it's much simpler to skip every single libtool step and simply declare the needed array of structures inside main.c itself. Declare your redefined functions/symbols as an extern int, regardless of their type, and create a lt_dlsymlist array named lt__PROGRAM__LTX_preloaded_symbols. The first element will be the program itself, and the last will be {0, (void *) 0}. In-between these, one struct line will declare the basename of the dlpreopened object/static archive, with the file suffix of ".a".

    It will have the .a suffix regardless of whether it is a .a, .o, .c, or any other file. The next struct(s) will be all the symbols exported from it that you wish to use, then, if you wish, another file name, its symbols, and so forth. The symbol names will use (void *) &SYMBOL instead of (void *) 0 as all other fields do.

    extern int bromine_LTX_chemical_name();
    extern int chlorine_LTX_chemical_name();
    
    const lt_dlsymlist lt__PROGRAM__LTX_preloaded_symbols[] ={ 
      {"@PROGRAM@", (void *) 0},
      {"chlorine.a", (void *) 0},
      {"chlorine_LTX_chemical_name", (void *) &chlorine_LTX_chemical_name},
      {"bromine.a", (void *) 0},
      {"bromine_LTX_chemical_name", (void *) &bromine_LTX_chemical_name},
      {0, (void *) 0}
    };
    

    Lastly, lt_dlopen does not mirror the lt_dlsymlist declarations -- ideally you will call only the basename of the file, without it's extension. e.g.

    handle = lt_dlopen("chlorine");
    

    NOT

    handle = lt_dlopen("chlorine.a")
    

    However, I believe it will work regardless of the extension you provide as the lt_dlopen function strips the extension.

    Doing it in the above prescribed manner can reduce your compilation to a single line and avoid libtool altogether. For example

    gcc -static -Wl,-static -fPIC -fno-plt -Wl,-z,now,--no-undefined -o main main.c bromine.c chlorine.c -lltdl -ldl
    

    I also verified that this produces a truly static binary by linking to a debug build of libltdl with everything-but preopening removed from it (no libdl, etc.).

    $./main HOBr HOCl
    
    loaders: lt_preopen
    try_dlopen (bromine, (null))
    tryall_dlopen (bromine.a, lt_preopen)
    Calling lt_preopen->module_open (bromine.a)
      Result: Success
    try_dlopen (chlorine, (null))
    tryall_dlopen (chlorine.a, lt_preopen)
    Calling lt_preopen->module_open (chlorine.a)
      Result: Success
    The IUPAC name of HOBr is hypobromous acid.
    The IUPAC name of HOCl is hypochlorous acid.