Search code examples
clinuxdynamic-library

Calling function from dynamic library?


I'm experimenting with using dynamic libraries and C on Linux. The following code will print wrong ouput:

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

int main(int argc, char **arg)
{
        void *dl = dlopen("./lib.so", RTLD_NOW);
        if (!dl) {
                fprintf(stderr, "ERROR: %s\n", dlerror());
                exit(1);
        }

        char *ver = dlsym(dl, "show_version");
        printf("%s\n", ver);
}

If I make the following change the output will be correct:

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

int main(int argc, char **arg)
{
        void *dl = dlopen("./lib.so", RTLD_NOW);
        if (!dl) {
                fprintf(stderr, "ERROR: %s\n", dlerror());
                exit(1);
        }

        char *(*ver)() = dlsym(dl, "show_version");
        printf("%s\n", ver());
}

I'm not sure what char *(*ver)() is doing and why it's needed? Can anyone explain?


Solution

  • dlsym - obtain address of a symbol in a shared object or executable

    This means that when you do dlsym(dl, "show_version"); you are not actually calling the function show_version in your shared library. You obtain the address of that function - which can be used to call the function over and over again.

    To "decode" what char *(*ver)() means, you can use what is often called the Clockwise/Spiral Rule

            +-----+
            |     V
    char*  (*ver) ()   ver is a 
     ^      ^ |   |    pointer to
     |      | |   |    a function (taking no arguments)
     |      +-+   |    returning char*
     |            |
     +------------+
    

    I assume the above matches the signature of the show_version function that you put in the shared library. Example:

    // a function (taking no arguments), returning a char*
    char *show_version(void) {
        static char version[] = "1.0";
        return version;
    }
    

    Using the same rule on your first attempt, char* ver:

    char* ver
      ^    |     ver is a
      |    |     char*
      +----+
    

    You need a pointer to a function (with the correct signature) to be able to call the function and get the result you want. You can't call a char* and when you do printf("%s\n", ver); it'll just start reading the memory at the address (where your function is stored) until it finds a null terminator. You probably see just gibberish.

    If you on the other hand have a proper function pointer, you can as you've noticed, call the function it points at with ver() and you get a char* in return which points at the string your dynamically loaded function returned.

    You can also use function pointers in your programs without involving shared libraries.

    #include <stdio.h>
    
    long foo(short x, int y) {
        return x + y;
    }
    
    int main() {
        long(*foo_ptr)(short, int) = foo;
    
        // foo_ptr is a pointer to a function taking (short, int) as
        // arguments and returning a long
    
        printf("%ld\n", foo(1, 2) );       // prints 3
        printf("%ld\n", foo_ptr(1, 2) );   // also prints 3
    }