dlsym / dlopen与运行时参数

时间:2009-08-30 18:28:37

标签: c dlopen

我正在尝试执行以下操作

  enum types {None, Bool, Short, Char, Integer, Double, Long, Ptr};
  int main(int argc, char ** args) {
     enum types params[10] = {0};
     void* triangle = dlopen("./foo.so", RTLD_LAZY);
     void * fun = dlsym(triangle, ars[1]);

     <<pseudo code>>
  }

伪代码就像

fun = {}
for param in params:
      if param == None:
         fun += void
      if param == Bool:
          fun += Boolean
      if param == Integer:
          fun += int
      ...
 returnVal = fun.pop()
 funSignature = returnval + " " + funName + "(" + Riffle(fun, ",") + ")"
 exec funSignature

谢谢

3 个答案:

答案 0 :(得分:21)

实际上,你几乎可以做任何你想做的事。在C语言中(例如,与C ++不同),共享对象中的函数仅由它们的名称引用。因此,要找到 - 并且,最重要的是,调用 - 正确的功能,您不需要其完整的签名。你只需要它的名字!这既是优点也是劣势 - 但这是你选择的语言的本质。

让我演示它是如何运作的。

#include <dlfcn.h>

typedef void* (*arbitrary)();
// do not mix this with   typedef void* (*arbitrary)(void); !!!

int main()
{
    arbitrary my_function;
    // Introduce already loaded functions to runtime linker's space
    void* handle = dlopen(0,RTLD_NOW|RTLD_GLOBAL);
    // Load the function to our pointer, which doesn't know how many arguments there sould be
    *(void**)(&my_function) = dlsym(handle,"something");
    // Call something via my_function
    (void)  my_function("I accept a string and an integer!\n",(int)(2*2));
    return 0;
}

事实上,你可以通过这种方式调用任何函数。但是,有一个缺点。实际上需要在编译时知道函数的返回类型。默认情况下,如果在该typedef中省略void *,则假定int为返回类型 - 是的,它是正确的C代码。问题是编译器需要知道返回类型的大小才能正确操作堆栈。

您可以通过技巧解决问题,例如,事先通过预先声明具有不同大小的返回类型的几种函数类型,然后选择您实际要调用的函数类型。但更简单的解决方案是要求插件中的函数始终返回void *或int;实际结果通过作为参数给出的指针返回。

你必须确保的是你总是用它应该接受的参数的确切数量和类型来调用函数。密切关注不同整数类型之间的差异(最好的选择是明确地向它们转换参数)。

一些评论者报告以上代码不能保证适用于可变函数(例如printf)。

答案 1 :(得分:19)

dlsym()返回的内容通常是函数指针 - 伪装成void *。 (如果你要求它提供全局变量的名称,它也会返回指向该全局变量的指针。)

然后,就像使用任何其他指向函数的指针一样调用该函数:

int (*fun)(int, char *) = (int (*)(int, char *))dlsym(triangle, "function");

(*fun)(1, "abc");    # Old school - pre-C89 standard, but explicit
fun(1, "abc");       # New school - C89/C99 standard, but implicit

我老了;我更喜欢显式符号,以便读者知道'fun'是指向函数的指针而无需查看其声明。使用新的学校符号,您必须记住在尝试查找名为“fun”的函数之前查找变量“fun()”。

请注意,您无法像动态一样动态构建函数调用 - 或者一般情况下不能。要做到这一点,需要做更多的工作。你必须提前知道函数指针在参数的方式和它返回的内容以及如何解释它们时所期望的内容。

管理更多动态函数调用的系统(例如Perl)对如何调用函数和传递参数以及不使用任意签名调用(可以说是不能调用)函数有特殊规则。他们只能使用事先知道的签名来调用函数。一种机制(Perl不使用)是将参数推送到堆栈,然后调用一个知道如何从堆栈中收集值的函数。但即使被调用的函数操纵这些值然后调用任意其他函数,该被调用函数也为任意其他函数提供了正确的调用序列。

C中的反思很难 - 很难。它不可撤销 - 但它需要基础设施来支持它并遵守规则使用它,它只能调用支持基础设施规则的功能。

答案 2 :(得分:0)

正确的解决方案

假设您正在编写共享库;我发现此问题的最佳解决方案是严格地定义和控制通过以下方式动态链接哪些功能:

  1. 将所有符号设置为隐藏
    • 例如clang -dynamiclib Person.c -fvisibility=hidden -o libPerson.dylib(使用clang编译时)
  2. 然后使用__attribute__((visibility("default")))extern "C"选择性地取消隐藏和包含函数
  3. 利润!您知道函数的签名是什么。你写的!

我在Apple's Dynamic Library Design Guidelines中找到了这个。这些文档还包括其他解决方案,只是我最喜欢的。

您问题的答案

如先前的回答所述,定义中带有extern "C"的C和C ++函数不会被篡改,因此函数的符号根本不包含完整的函数签名。如果您使用不带extern "C"的C ++进行编译,但是函数会被破坏,因此您可以将它们分解以获取完整函数的签名(使用demangler.coma c++ library之类的工具)。 See here,详细了解什么是重整。

通常来说,如果要使用dlopen导入函数,最好使用第一个选项。