How to correctly assign a pointer returned by dlsym into a variable of function pointer type?

眉间皱痕 提交于 2019-11-29 11:47:18

问题


I am trying to use dlopen() and dlsym() in my code and compile it with gcc.

Here is the first file.

/* main.c */

#include <dlfcn.h>

int main()
{
    void *handle = dlopen("./foo.so", RTLD_NOW);

    if (handle) {
        void (*func)() = dlsym(handle, "func");
        func();
    }

    return 0;
}

Here is the second file.

/* foo.c */

#include <stdio.h>

void func()
{
    printf("hello, world\n");
}

Here is how I compile and run the code.

$ gcc -std=c99 -pedantic -Wall -Wextra -shared -fPIC -o foo.so foo.c
$ gcc -std=c99 -pedantic -Wall -Wextra -ldl -o main main.c
main.c: In function ‘main’:
main.c:10:26: warning: ISO C forbids initialization between function pointer and ‘void *’ [-Wpedantic]
         void (*func)() = dlsym(handle, "func");
                          ^
$ ./main
hello, world

How can I get rid of the warning?

Type casting doesn't help. If I try to type cast the return value of dlsym() into a function pointer, I get this warning instead.

main.c:10:26: warning: ISO C forbids conversion of object pointer to function pointer type [-Wpedantic]
         void (*func)() = (void (*)()) dlsym(handle, "func");
                          ^

What would convince the compiler that this code is fine?


回答1:


If you want to be pedantically correct, don't try to resolve the address of a function. Instead, export some kind of structure from the dynamic library:

In the library

struct export_vtable {
   void (*helloworld)(void);
};
struct export_vtable exports = { func };

In the caller

struct export_vtable {
   void (*helloworld)(void);
};

int main() {
   struct export_vtable* imports;
   void *handle = dlopen("./foo.so", RTLD_NOW);

   if (handle) {
        imports = dlsym(handle, "exports");
        if (imports) imports->helloworld();
    }

    return 0;
}

This technique is actually quite common, not for portability -- POSIX guarantees that function pointers can be converted to and from void* -- but because it allows more flexibility.




回答2:


The problem here is that a pointer to object is subtly separated from a function pointer. In ISO/IEC 9899:201x paper §6.3.2.3 Pointers it's stated:

  1. A pointer to void may be converted to or from a pointer to any object type. A pointer to any object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.

.

  1. A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the pointed-to type, the behavior is undefined.

So a function pointer is different from object pointers, and consequently the assignment of a void * to a function pointer is always strictly non compliant.

Anyway, as I said in comments, in 99.9999....9999% of cases it is permitted thanks to the ANNEX J - Portability issues, §J.5.7 Function pointer casts of the previously mentioned paper that states:

  1. A pointer to an object or to void may be cast to a pointer to a function, allowing data to be invoked as a function (6.5.4).
  2. A pointer to a function may be cast to a pointer to an object or to void, allowing a function to be inspected or modified (for example, by a debugger) (6.5.4).

Now on the practical side a technique that avoid the splitting of code in more files is to use pragmas to suppress pedantic warnings for a small piece of code.
The more brutal form can be:

/* main.c */

#include <dlfcn.h>

#pragma GCC diagnostic push    //Save actual diagnostics state
#pragma GCC diagnostic ignored "-pedantic"    //Disable pedantic
int main()
{
    void *handle = dlopen("./foo.so", RTLD_NOW);
    if (handle) {
        void (*func)() = dlsym(handle, "func");
        func();
    }
    return 0;
}
#pragma GCC diagnostic pop    //Restore diagnostics state

A more sophisticated way could be actuated isolating the offending code in a small function, then forcing its inlining. It's more a makeup than effective solution, but will suppress the unwanted diagnostic:

/* main.c */

#include <dlfcn.h>

#pragma GCC diagnostic push    //Save actual diagnostics state
#pragma GCC diagnostic ignored "-pedantic"    //Disable pedantic
void (*)() __attribute__((always_inline)) Assigndlsym(void *handle, char *func)
{
    return dlsym(handle, func);  //The non compliant assignment is done here
}
#pragma GCC diagnostic pop    //Restore diagnostics state

int main()
{
    void *handle = dlopen("./foo.so", RTLD_NOW);
    if (handle) {
        void (*func)() = Assigndlsym(handle, "func"); //Now the assignment is compliant
        func();
    }
    return 0;
}



回答3:


To keep the -pedantic option for your code while having parts of code that are not strictly conforming, separate that code into a separate file with custom warning options.

So, make a function that wraps the dlsym function and returns a function pointer. Put it in a separate file and compile that file without -pedantic.




回答4:


This made my code sufficiently pedantic:

*(void**)(&func_ptr) = dlsym(handle, "function_name");

(I found it here http://pubs.opengroup.org/onlinepubs/009695399/functions/dlsym.html)




回答5:


you can use union, like this:

union {
    void *ptr;
    void (*init_google_logging) (char* argv0);
} orig_func;

orig_func.ptr = dlsym (RTLD_NEXT, "_ZN6google17InitGoogleLoggingEPKc");

orig_func.init_google_logging (argv0);



回答6:


The compiler only "tries to help", so you have to use two typecasts:

#include <inttypes.h>

void (*func)() = (void (*)())(intptr_t)dlsym(handle, "func");


来源:https://stackoverflow.com/questions/36384195/how-to-correctly-assign-a-pointer-returned-by-dlsym-into-a-variable-of-function

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!