静态链接库的dlsym类行为

4
我正在尝试编写一个C语言的分发函数,它应该接收一个由函数名组成的字符串,并且在分发函数内部能够调用指定名称的函数。整个重点是我不想在动态加载库中定义函数。流程应该像这样:
file1.c
void hello()
{
   fprintf(stderr,"Hello world\n");
}

void myfunc()
{
   mydispatch("hello");
}

file2.c

void mydispatch(const char *function)
{
...
}

我知道这种结构可能有点奇怪,但我主要想知道是否可以这样做。

3个回答

3

您可以使用BFD来从符号名称中获取运行时地址。

#include <stdio.h>
#include <stdlib.h>
#include <bfd.h>
#include <string.h>

void func1(int n)
{
    printf("%d\n", n);
}

void * addr_from_name(char * filename, char * symname)
{
    bfd *        ibfd;
    asymbol **   symtab;
    symbol_info  info;
    void *       symaddress = NULL;
    long         size;
    long         syms;
    unsigned int i;

    bfd_init();

    ibfd = bfd_openr(filename, NULL);
    bfd_check_format(ibfd, bfd_object);

    size   = bfd_get_symtab_upper_bound(ibfd);
    symtab = malloc(size);
    syms   = bfd_canonicalize_symtab(ibfd, symtab);

    for(i = 0; i < syms; i++) {
        if(strcmp(symtab[i]->name, symname) == 0) {
            bfd_symbol_info(symtab[i], &info);
            symaddress = (void *)info.value;
        }
    }

    bfd_close(ibfd);
    return symaddress;
}

int main(void)
{
    void (*func)(int) = addr_from_name("/homes/mk08/Desktop/lala", "func1");
    printf("%p\n", func);
    func(5);
    return EXIT_SUCCESS;
}

在这种情况下,我硬编码了filename,但是动态获取它也很容易。假设你的源代码/二进制文件被称为lala,那么可以进行编译和运行,如下所示:
gcc -Wall -std=gnu99 lala.c -o lala -lbfd && ./lala

在我的系统上,这会打印出:
0x400814
5

请注意,此方法需要一个完整的符号表。为了减少开销,您可以在初始化例程中重复使用上述代码。在那里,您将存储从函数名称到地址的映射。这会导致生成查找表的一次性成本。

0

你可以这样做:

void mydispatch(const char *function)
{
    if (!strcmp(function, "hello")
        hello();
    else if (!strcmp(function, "goodbye")
        goodbye();
}

你会成为一个优秀的公司成员;在早期,许多语言解释器都是这样构建的。

就动态性而言:对于静态链接库,必要的运行时结构并不存在。无论你做什么,你都需要手动或自动创建某种调度表(就像我在这里展示的那样)--但你必须自己完成。


是的,但这将违背整个目的,而且当你面对可能需要分派的20多个方法时,它还会导致非常混乱的代码。 - skyel
好的,就像我说的那样,如果你聪明地处理,你可以自动完成它——也就是说,调度程序可以由脚本生成,或者至少你可以将其编写为一个简单宏的连续应用。 - Ernest Friedman-Hill
我实际上看不到你如何在运行时做这样的事情。使用两个文件的整个想法是,可以在较早的时间编译file2.c,然后稍后使用它而无需修改它。例如,您可以拥有一个可用方法的_char **_,它可以在file2.c中静态存在,并且您可以通过某些函数附加到id,然后调度可以检查您的调用是否有效。如果您决定使用脚本/宏,则每次都必须重新编译。 - skyel
是的,脚本或宏是避免手写代码的一种方式,而不是动态执行任务的方式。正如我所说,静态库中的函数没有可用于动态查找的运行时机制。除了编译代码并分支到库中正确的偏移量之外,没有其他调用此类函数的方法。最终,对每个静态函数的调用(或至少提及)都必须编译进您的代码中。 - Ernest Friedman-Hill
我想你可以自己创建一个函数名和函数地址的表,然后通过该表进行查找/跳转。由于这些偏移量是在链接期间计算的,因此不清楚如何在运行时执行此操作。 - Ernest Friedman-Hill
如果目标只是让file2对更改不敏感,那么您可以拥有一个全局的、动态分配的函数名/函数指针表,并让file2通过搜索它来操作。file1可以在运行时填充该表,至少在硬编码的函数名/指针对可以在运行时插入到表中的意义上。 - Ernest Friedman-Hill

0

也许你正在寻找以下类型的解决方案:

file1.c:

void myfunc()
{
   my_function_type fun = search_function( "operation1" );
   fun(); 
}

tools.h:

typedef void (*my_function_type)( void );
void register_function( my_function_type fp, const char* name );
my_function_type search_function( const char* name );

file2.c:

static void mydispatch( void )
{
...
}    

static void __attribute__ ((constructor)) register_functions()
{
     register_function( mydispatch, "operation1" );
}

在这个解决方案中,当您添加新功能时,无需重新编译旧文件。只需要编译新文件并进行链接即可。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接