C语言 如何将dlsym返回的指针正确赋值给函数指针类型的变量?

hsgswve4  于 2022-12-03  发布在  其他
关注(0)|答案(7)|浏览(199)

我尝试在代码中使用dlopen()dlsym(),并用gcc编译它。
这里是第一个文件。

/* main.c */

#include <dlfcn.h>

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

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

    return 0;
}

这是第二个文件。

/* foo.c */

#include <stdio.h>

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

下面是我编译和运行代码的方法。

$ 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

我怎样才能摆脱警告?
类型转换没有帮助。如果我试图将dlsym()的返回值类型转换为函数指针,我会得到这个警告。

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

怎样才能让编译器相信这段代码是正确的呢?

vcudknz3

vcudknz31#

如果你想让自己的方法更加精确,不要试图解析函数的地址,而是从动态库中导出某种结构:
在图书馆

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

在调用方

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;
}

这种技术实际上非常常见,不是为了可移植性-- POSIX保证函数指针可以与void* 相互转换--而是因为它允许更大的灵活性。

cclgggtu

cclgggtu2#

这使得我的代码变得十分迂腐:

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

(我在这里找到的http://pubs.opengroup.org/onlinepubs/009695399/functions/dlsym.html

20jt8wwn

20jt8wwn3#

这里的问题是指向对象的指针与函数指针被巧妙地分开了。在ISO/IEC 9899:201 xpaper**§6.3.2.3指针中指出:
1.指向void的指针可以转换为指向任何对象类型的指针,也可以从指向任何对象类型的指针转换为指向void的指针,然后再转换回来;结果将与原始指针比较相等。

1.指向一种类型的函数的指针可以转换为指向另一种类型的函数的指针,然后再转换回来;其结果应该与原始指针相等。如果转换后的指针用于调用一个类型与所指向的类型不兼容的函数,则该行为未定义。
因此,函数指针不同于对象指针,因此void *对函数指针的赋值总是
非严格兼容**。
无论如何,正如我在评论中所说的,在99.9999....9999%的情况下,它是允许的,这要归功于附录J -可移植性问题,§J.5.7函数指针转换,前面提到的论文指出:
1.指向对象或void的指针可以转换为指向函数的指针,从而允许将数据作为函数调用(6.5.4)。
1.指向函数的指针可以转换为指向对象或void的指针,从而允许检查或修改函数(例如,通过调试器)(6.5.4)。
现在从实用的Angular 来看,一种避免将代码分割成更多文件的技术是使用pragma来抑制一小段代码的学究式警告。
更残酷的形式可以是:

/* 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

一个更复杂的方法可以是在一个小函数中隔离出违规代码,然后强制其内联。这与其说是一个有效的解决方案,不如说是一个补充,但会抑制不必要的诊断:

/* 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;
}
s2j5cfk0

s2j5cfk04#

若要保留代码的-pedantic选项,同时保留不严格符合的代码部分,请将该代码分隔到具有自定义警告选项的单独文件中。
因此,创建一个函数, Package dlsym函数并返回一个函数指针。将其放在一个单独的文件中,并在不使用-pedantic的情况下编译该文件。

chhkpiq4

chhkpiq45#

您可以使用union,如下所示:

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

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

orig_func.init_google_logging (argv0);
mnemlml8

mnemlml86#

POSIX标准explicitly states,你可以“运行”一个地址作为一个指针传递给对象(强调我):
请注意,从void *指针到函数指针的转换如下所示:

fptr = (int (*)(int))dlsym(handle, "my_function");

ISO C标准未定义。此标准要求此转换在一致的实现上正确工作。
要执行此操作,您需要首先“激活”对象指针。

#define ANIMATED_POINTER(FUNC_TYPE, OBJECT_POINTER) \
    (*((FUNC_TYPE *)(&(OBJECT_POINTER))))

然后可以直接调用对象变量,

#include <dlfcn.h>

#define ANIMATED_POINTER(FUNC_TYPE, OBJECT_POINTER) \
    (*((FUNC_TYPE *)(&(OBJECT_POINTER))))

typedef void (* MyFuncType) ();

int main () {

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

    if (handle) {

        void * func_obj_ptr = dlsym(handle, "func");
        ANIMATED_POINTER(MyFuncType, func_obj_ptr)();

    }

    return 0;

}

也可以将其转换为另一个适当函数类型变量。

#include <dlfcn.h>

#define ANIMATED_POINTER(FUNC_TYPE, OBJECT_POINTER) \
    (*((FUNC_TYPE *)(&(OBJECT_POINTER))))

typedef void (* MyFuncType) ();

int main () {

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

    if (handle) {

        void * func_obj_ptr = dlsym(handle, "func");
        MyFuncType func = ANIMATED_POINTER(MyFuncType, func_obj_ptr);
        func();

    }

    return 0;

}

加法

尽管从对象指针到函数指针的转换可能会有问题,但在每个标准C中都允许在它们之间转换不同类型的函数指针。您可以利用此权限并使用void (*) (void)作为宏的固定目标类型。

#define UNTYPED_ANIMATED_POINTER(OBJECT_POINTER) \
    (*((void (**) (void))(&(OBJECT_POINTER))))

因此,想象一个addition.c库,

int add_numbers (int first, int second) {
    return first + second;
}

以下是同一代码的四个变体。

第一名

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

#define UNTYPED_ANIMATED_POINTER(OBJECT_POINTER) \
    (*((void (**) (void))(&(OBJECT_POINTER))))

int main () {

    void * handle = dlopen("./addition.so", RTLD_NOW);

    if (handle) {

        int (* add_numbers) (int, int);
        void * func_obj_ptr = dlsym(handle, "add_numbers");

        add_numbers = (int (*) (int, int)) UNTYPED_ANIMATED_POINTER(func_obj_ptr);
        int result = add_numbers(19, 23);
        printf("The result is: %d\n", result);  // "The result is: 42"

    }

    return 0;

}

第二名

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

#define UNTYPED_ANIMATED_POINTER(OBJECT_POINTER) \
    (*((void (**) (void))(&(OBJECT_POINTER))))

int main () {

    void * handle = dlopen("./addition.so", RTLD_NOW);

    if (handle) {

        void * func_obj_ptr = dlsym(handle, "add_numbers");

        int result = ((int (*) (int, int)) UNTYPED_ANIMATED_POINTER(func_obj_ptr))(19, 23);
        printf("The result is: %d\n", result);  // "The result is: 42"

    }

    return 0;

}

第三名

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

#define UNTYPED_ANIMATED_POINTER(OBJECT_POINTER) \
    (*((void (**) (void))(&(OBJECT_POINTER))))

typedef int (* AdditionFunc) (int, int);

int main () {

    void * handle = dlopen("./addition.so", RTLD_NOW);

    if (handle) {

        void * func_obj_ptr = dlsym(handle, "add_numbers");

        AdditionFunc add_numbers = (AdditionFunc) UNTYPED_ANIMATED_POINTER(func_obj_ptr);
        int result = add_numbers(19, 23);
        printf("The result is: %d\n", result);  // "The result is: 42"

    }

    return 0;

}

第四名

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

#define UNTYPED_ANIMATED_POINTER(OBJECT_POINTER) \
    (*((void (**) (void))(&(OBJECT_POINTER))))

typedef int (* AdditionFunc) (int, int);

int main () {

    void * handle = dlopen("./addition.so", RTLD_NOW);

    if (handle) {

        void * func_obj_ptr = dlsym(handle, "add_numbers");

        int result = ((AdditionFunc) UNTYPED_ANIMATED_POINTER(func_obj_ptr))(19, 23);
        printf("The result is: %d\n", result);  // "The result is: 42"

    }

    return 0;

}
ruoxqz4g

ruoxqz4g7#

编译器只“试图提供帮助”,因此必须使用两种类型转换:

#include <stdint.h>

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

相关问题