您能否编译一个共享对象以优先使用本地符号,即使它是由使用 -rdynamic 编译的程序加载的?

Can you compile a shared object to prefer local symbols even if it's being loaded by a program compiled with -rdynamic?

我正在用 C 构建一个共享库,该库由我没有源代码访问权限的程序动态加载。目标平台是 64 位 Linux 平台,我们正在使用 gcc 进行构建。我能够在大约 100 行内构建该问题的重现,但它仍然有点难以阅读。希望它是说明性的。

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

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

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

internal.c

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

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

internal.h

#if !defined(__INTERNAL__)
#define __INTERNAL__

void
bar(void);

#endif /* defined(__INTERNAL__) */

main.c

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

main.h

#if !defined(__MAIN__)
#define __MAIN__

extern void
foo(void);

#endif /* defined(__MAIN__) */

shared.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

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

  • 从您的库中调用函数 bar mylib_bar 或类似的东西
  • __attribute__((visibility("hidden")))或类似的
  • 隐藏mylib_bar
  • 使bar成为弱符号,这样引用mylib_bar

    #pragma weak bar = mylib_bar
    
  • 让你的图书馆到处调用 mylib_bar 而不是 bar

现在一切正常:

  • 当您的库调用 mylib_bar 时,这始终引用库中的定义,因为可见性是隐藏的。
  • 当其他代码调用bar时,默认调用mylib_bar
  • 如果有人定义了他自己的 bar,这会覆盖 bar 但不会覆盖 mylib_bar,让您的图书馆保持不变。

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

-Bsymbolic

When creating a shared library, bind references to global symbols to the definition within the shared library, if any. Normally, it is possible for a program linked against a shared library to override the definition within the shared library. This option can also be used with the --export-dynamic option, when creating a position independent executable, to bind references to global symbols to the definition within the executable. This option is only meaningful on ELF platforms which support shared libraries and position independent executables.

似乎解决了问题:

$ 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