clinuxdlopendlsym

How to call a function dynamically in executable file itself?


I was trying call a function dynamically in an executable file itself. The target function test_func was coded with the main function in the same source file. Here is the sample code:

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

// or with some tricks
// void __attribute__((visibility("default")))
void test_func(void)
{
    printf("test_func in %s \n",__FILE__);
}

int main()
{
    void *handle = dlopen(0, RTLD_LAZY); // or RTLD_GLOBAL
    printf("dlopen return %p\n", handle);

    void (*func)() = dlsym(handle, "test_func");
    printf("dlsym  return %p\n", func);

    if (func) (*func)();
    return 0;
}

Then I compiled it with gcc and it failed with output like this:

$ ./a.out
dlopen return 0xe29dfbec1c7bc0d
dlsym  return 0x0

I can read the symbol in the ELF. It hits my head that it must be some way to get the test_func function and call it.

$ readelf -s a.out | grep test_func
    40: 00000000000017c8    36 FUNC    GLOBAL DEFAULT   13 test_func

Is there something I am missing?


Solution

  • Are you using windows? Then you should consider using DLLs for dynamic function loading.

    Generally when you call dlopen(0, RTLD_LAZY), it attempts to open the executable's own symbol table; it may not work quite as expected for retrieving symbols. This is because the dynamic linker might not expose all of the symbols of the executable in the same way that it does for shared libraries.

    Although you are able to see test_func in the readelf output, it still may not be accessible via dlsym if it is not properly exposed or if the dynamic symbol table is not properly populated.

    So, since the purpose of dlopen is mainly about shared libraries. A common solution would be to switch to a shared library, but if you really have to use an executable: You must make sure to compile with the -rdynamic flag so symbols are included in the dynamic symbol table.

    gcc -rdynamic -o a.out <filename.c>
    

    This is important to make sure that symbols are visible via dlsym.

    Now, if the position -rdynamic were to be passed, then dlopen(NULL, RTLD_LAZY) really ought to work. Assuming that it doesn't, then it's probably a bug, or a misfeature in your linker or dynamic loader and how it handles symbols.


    So in-sort we can say:

    1. Use -rdynamic when compiling to expose symbols.
    2. For shared libraries, use dlopen and dlsym as intended.

    Here is the full code where i have used the -rdynamic and also checked if the symbol is visible or not:

    #include <stdio.h>
    #include <dlfcn.h>
    
    // Define the function to be called
    void __attribute__((visibility("default"))) test_func(void)
    {
        printf("test_func in %s \n", __FILE__);
    }
    
    int main()
    {
        // Open the executable's symbol table
        void *handle = dlopen(NULL, RTLD_LAZY); 
        if (!handle) {
            fprintf(stderr, "dlopen failed: %s\n", dlerror());
            return 1;
        }
        printf("dlopen return %p\n", handle);
    
        // Retrieve the address of test_func
        void (*func)() = dlsym(handle, "test_func");
        if (!func) {
            fprintf(stderr, "dlsym failed: %s\n", dlerror());
            dlclose(handle);
            return 1;
        }
        printf("dlsym return %p\n", func);
    
        // Call the function if found
        (*func)();
    
        // Close the handle
        dlclose(handle);
        return 0;
    }