8

我正在用 C 语言构建一个共享库,该库由我没有源代码访问权限的程序动态加载。目标平台是 64 位 Linux 平台,我们使用 gcc 构建。我能够在大约 100 行中构建该问题的复制品,但仍然需要阅读一些内容。希望它是说明性的。

核心问题是我在共享库中定义了两个非静态函数(bar和)。baz两者都需要是非静态的,因为我们希望调用者能够对它们进行 dlsym。此外,baz调用bar. 使用我的库的程序还有一个名为 的函数bar,这通常不会成为问题,但调用程序是用 编译的-rdynamic,因为它有一个foo需要在我的共享库中调用的函数。结果是我的共享库最终bar在运行时链接到调用程序的版本,产生不直观的结果。

在理想情况下,我可以在编译共享库时包含一些命令行开关,以防止这种情况发生。

我目前的解决方案是将我的非静态函数重命名为funname_local并将它们声明为静态。然后我定义了一个新函数: ,并将我的共享库中对 的funname() { return funname_local(); }任何引用更改为。这可行,但感觉很麻烦,我更愿意告诉链接器更喜欢在本地编译单元中定义的符号。funnamefunname_local

内部.c

#include <stdio.h>
#include "internal.h"

void
bar(void)
{
  printf("I should only be callable from the main program\n");
}

内部.h

#if !defined(__INTERNAL__)
#define __INTERNAL__

void
bar(void);

#endif /* defined(__INTERNAL__) */

主程序

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

void
foo(void)
{
  printf("It's important that I am callable from both main and from any .so "
         "that we dlopen, that's why we compile with -rdynamic\n");
}

int
main()
{
  void *handle;
  void (*fun1)(void);
  void (*fun2)(void);
  char *error;

  if(NULL == (handle = dlopen("./shared.so", RTLD_NOW))) { /* Open library */
    fprintf(stderr, "dlopen: %s\n", dlerror());
    return 1;
  }
  dlerror(); /* Clear any existing error */

  *(void **)(&fun1) = dlsym(handle, "baz"); /* Get function pointer */
  if(NULL != (error = dlerror()))  {
    fprintf(stderr, "dlsym: %s\n", error);
    dlclose(handle);
    return 1;
  }
  *(void **)(&fun2) = dlsym(handle, "bar"); /* Get function pointer */
  if(NULL != (error = dlerror()))  {
    fprintf(stderr, "dlsym: %s\n", error);
    dlclose(handle);
    return 1;
  }

  printf("main:\n");
  foo();
  bar();
  fun1();
  fun2();

  dlclose(handle);
  return 0;
}

主文件

#if !defined(__MAIN__)
#define __MAIN__

extern void
foo(void);

#endif /* defined(__MAIN__) */

共享.c

#include <stdio.h>
#include "main.h"

void
bar(void)
{
  printf("bar:\n");
  printf("It's important that I'm callable from a program that loads shared.so"
         " as well as from other functions in shared.so\n");
}

void
baz(void)
{
  printf("baz:\n");
  foo();
  bar();
  return;
}

编译:

$ gcc -m64 -std=c89 -Wall -Wextra -Werror -pedantic -o main main.c internal.c -l dl -rdynamic
$ gcc -m64 -std=c89 -Wall -Wextra -Werror -pedantic -shared -fPIC -o shared.so shared.c

跑:

$ ./main
main:
It's important that I am callable from both main and from any .so that we dlopen, that's why we compile with -rdynamic
I should only be callable from the main program
baz:
It's important that I am callable from both main and from any .so that we dlopen, that's why we compile with -rdynamic
I should only be callable from the main program
bar:
It's important that I'm callable from a program that loads shared.so as well as from other functions in shared.so
4

2 回答 2

9

您是否尝试过-Bsymbolic链接器选项(或-Bsymbolic-functions)?引用ld男人的话:

-Bsymbolic

创建共享库时,将对全局符号的引用绑定到共享库中的定义(如果有)。通常,链接到共享库的程序可以覆盖共享库中的定义。此选项还可以与 --export-dynamic 选项一起使用,在创建与位置无关的可执行文件时,将对全局符号的引用绑定到可执行文件中的定义。此选项仅在支持共享库和位置独立可执行文件的 ELF 平台上有意义。

它似乎解决了这个问题:

$ gcc -m64 -std=c89 -Wall -Wextra -Werror -pedantic -shared -fPIC -o shared.so shared.c
$ gcc -m64 -std=c89 -Wall -Wextra -Werror -pedantic -o main main.c internal.c -l dl -rdynamic
$ ./main 
main:
It's important that I am callable from both main and from any .so that we dlopen, that's why we compile with -rdynamic
I should only be callable from the main program
baz:
It's important that I am callable from both main and from any .so that we dlopen, that's why we compile with -rdynamic
I should only be callable from the main program
bar:
It's important that I'm callable from a program that loads shared.so as well as from other functions in shared.so
$ gcc -m64 -std=c89 -Wall -Wextra -Werror -pedantic -shared -fPIC -Wl,-Bsymbolic -o shared.so shared.c
$ ./main 
main:
It's important that I am callable from both main and from any .so that we dlopen, that's why we compile with -rdynamic
I should only be callable from the main program
baz:
It's important that I am callable from both main and from any .so that we dlopen, that's why we compile with -rdynamic
bar:
It's important that I'm callable from a program that loads shared.so as well as from other functions in shared.so
bar:
It's important that I'm callable from a program that loads shared.so as well as from other functions in shared.so
于 2016-09-13T20:00:25.720 回答
1

此问题的常见解决方案是实际上不依赖于未被覆盖的全局符号。而是执行以下操作:

  • bar从您的库mylib_bar或类似的东西中调用该函数
  • 隐藏或mylib_bar类似__attribute__((visibility("hidden")))
  • bar一个弱符号,指的mylib_bar是这样的:

    #pragma weak bar = mylib_bar
    
  • 让你的图书馆mylib_bar到处打电话,而不是bar

现在一切都按预期工作:

  • 当您的库调用mylib_bar时,这始终是指库中的定义,因为可见性是隐藏的。
  • 当其他代码调用bar时,默认调用mylib_bar
  • 如果有人定义了他自己的bar,这会覆盖bar但不会mylib_bar,使您的库保持不变。
于 2016-09-13T19:24:14.853 回答