我可以使某些符号仅对其他图书馆成员可见吗?

时间:2018-10-13 23:14:23

标签: c linker shared-libraries static-libraries symbols

考虑3 C源文件:

/* widgets.c */
void widgetTwiddle ( struct widget * w ) {
    utilityTwiddle(&w->bits, 1);
}

/* wombats.c */
void wombatTwiddle ( struct wombat * w ) {
    utilityTwiddle(&w->bits, 1);
}

/* utility.c */
void utilityTwiddle ( int * bitsPtr, int bits ) {
    *bitsPtr ^= bits;
}

将其编译并放入库中(例如,libww.a或libww.so)。 有没有一种方法可以使utilityTwiddle()对其他两个库成员可见并可用,但是对链接到该库的人不可见?也就是说,鉴于此:

/* appl.c */
extern void utilityTwiddle ( int * bitsPtr, int bits );
int main ( void ) {
    int bits;
    utilityTwiddle(&bits, 1);
    return 0;
}

cc -o appl appl.c -lww

由于utilityTwiddle()appl.c不可见,因此链接失败。因此,appl.c可以自由定义自己的utilityTwiddle函数或变量。

[EDIT] 显然,我们希望此方法能够起作用:

/* workingappl.c */
extern void wombatTwiddle ( struct wombat * wPtr );
int main ( void ) {
    struct wombat w = { .bits = 0 };
    wombatTwiddle(&w);
    return 0;
}

这个Limiting visibility of symbols when linking shared libraries似乎相关,但似乎没有解决被抑制的符号是否可用于其他库成员的情况。

[EDIT2] 我已经找到一种无需修改C源代码即可完成此操作的方法。添加地图文件:

/* utility.map */
{ local: *; };

然后执行:

$ gcc -shared -o utility.so utility.c -fPIC -Wl,--version-script=utility.map

为我们提供了不带utilityTwiddle的动态符号表:

$ nm -D utility.so
             w _Jv_RegisterClasses
             w __cxa_finalize
             w __gmon_start__

但是我不清楚如何有效地从这开始构建包含所有三个源文件的共享库。如果我将所有三个源文件放在命令行上,则这三个文件中的符号都将被隐藏。如果有一种增量构建共享库的方法,我可以有两个简单的映射文件(一个不导出任何内容,一个不导出所有内容)。这是可行的还是唯一的选择是这样的:

/* libww.map */
{ global: list; of; all; symbols; to; export; local: *; };

$ gcc -shared -o libww.so *.c -fPIC -Wl,--version-script=libww.map

[EDIT3] 伙计,似乎在不使用共享库的情况下也应该可以实现。如果我这样做:

ld -r -o wboth.o widgets.o wombats.o utility.o

我可以看到链接器已解析到utilityTwiddle()widgetTwiddle()称为wombatTwiddle()的位置:

$ objdump -d wboth.o
0000000000000000 <widgetTwiddle>:
   0:   be 01 00 00 00          mov    $0x1,%esi
   5:   e9 00 00 00 00          jmpq   a <widgetTwiddle+0xa>
0000000000000010 <wombatTwiddle>:
  10:   be 01 00 00 00          mov    $0x1,%esi
  15:   e9 00 00 00 00          jmpq   1a <wombatTwiddle+0xa>
0000000000000020 <utilityTwiddle>:
  20:   31 37                   xor    %esi,(%rdi)
  22:   c3                      retq

但是utilityTwiddle仍然是符号:

$ nm wboth.o
                 U _GLOBAL_OFFSET_TABLE_
0000000000000020 T utilityTwiddle
0000000000000000 T widgetTwiddle
0000000000000010 T wombatTwiddle

,因此,如果您找到删除该符号的方法,则仍然可以成功地与wboth.o链接(我已经通过wboth.o的二进制编辑进行了测试),并且它仍然可以正常运行:

$ nm wboth.o
                 U _GLOBAL_OFFSET_TABLE_
0000000000000000 T widgetTwiddle
0000000000000010 T wombatTwiddle
0000000000000020 T xtilityTwiddle

1 个答案:

答案 0 :(得分:1)

通过创建静态库libww.a无法实现所需的目标。如果你 阅读static-libraries您 会明白为什么。静态库可用于提供一堆 N 个目标文件 链接器,它将从中提取所需的 k (可能= 0)并链接它们。那么你 与静态库链接无法实现任何目标 直接链接那些 k 对象文件。出于链接目的,静态库实际上并没有 存在。

但是 shared 库确实确实存在用于链接目的,并且共享库公开了全局符号 获得另外一个属性,动态可见性,该属性正是为您 目的。动态可见的符号是全局符号的子集:它们是 对于 dynamic 链接(即链接共享库)可见的全局符号 以及其他内容(程序或其他共享库)。

动态可见性不是源语言标准可以说明任何问题的属性 关于,因为他们没有说动态链接。所以控制 符号的动态可见性必须通过工具链以单独的方式完成 支持动态链接。 GCC使用特定于编译器的声明来完成此操作 限定词 1

__attribute__((visibility("default|hidden|protected|internal")

和/或编译器开关 2

-fvisibility=default|hidden|protected|internal

这里是一个演示如何构建libww.so的示例,以便utilityTwiddle被隐藏 wombatTwiddlewidgetTwiddle可见的库客户端。

您的源代码需要以一种或另一种方式充实才能进行编译。 这是第一个剪辑:

ww.h(1)

#ifndef WW_H
#define WW_H

struct widget {
    int bits;
};

struct wombat {
    int bits;
};

extern void widgetTwiddle ( struct widget * w );
extern void wombatTwiddle ( struct wombat * w );

#endif

utility.h(1)

#ifndef UTILITY_H
#define UTILITY_H

extern void utilityTwiddle ( int * bitsPtr, int bits );

#endif

utility.c

#include "utility.h"

void utilityTwiddle ( int * bitsPtr, int bits ) {
    *bitsPtr ^= bits;
}

wombats.c

#include "utility.h"
#include "ww.h"

void wombatTwiddle ( struct wombat * w ) {
    utilityTwiddle(&w->bits, 1);
}

widgets.c

#include "utility.h"
#include "ww.h"

void widgetTwiddle ( struct widget * w ) {
    utilityTwiddle(&w->bits, 1);
}

以默认方式将所有*.c文件编译为*.o文件:

$ gcc -Wall -Wextra -c widgets.c wombats.c utility.c

并以默认方式将其链接到libww.so

$ gcc -shared -o libww.so widgets.o wombats.o utility.o

*Twiddle的全局符号表中有libww.so个符号

$ nm libww.so | egrep '*Twiddle'
000000000000063a T utilityTwiddle
00000000000005fa T widgetTwiddle
000000000000061a T wombatTwiddle

这只是进入链接的全局(extern*Twiddle符号的总和 目标文件中libww.so的值。它们都是定义的T),因为它们必须是 如果要链接库本身而没有外部*Twiddle依赖项。

任何ELF文件(目标文件,共享库,程序)都有全局符号表,但是 共享库还具有 dynamic 符号表。这是*Twiddle的动态符号表中的libww.so符号:

$ nm -D libww.so | egrep '*Twiddle'
000000000000063a T utilityTwiddle
00000000000005fa T widgetTwiddle
000000000000061a T wombatTwiddle

它们是完全一样的。这就是我们要更改的内容,因此utilityTwiddle 消失。

这是第二次切。我们必须稍微更改源代码。

utility.h(2)

#ifndef UTILITY_H
#define UTILITY_H

extern void utilityTwiddle ( int * bitsPtr, int bits ) __attribute__((visibility("hidden")));

#endif

然后像以前一样重新编译并重新链接:

$ gcc -Wall -Wextra -c widgets.c wombats.c utility.c
$ gcc -shared -o libww.so widgets.o wombats.o utility.o

这是全局符号表中的*Twiddle符号:

$ nm libww.so | egrep '*Twiddle'
000000000000063a T utilityTwiddle
00000000000005fa T widgetTwiddle
000000000000061a T wombatTwiddle

没有变化。这是 dynamic 符号表中的*Twiddle符号:

$ nm -D libww.so | egrep '*Twiddle'
00000000000005aa T widgetTwiddle
00000000000005ca T wombatTwiddle

utilityTwiddle不见了。

这是第三次切割,以不同的方式获得相同的结果。更long 但说明了-fvisibility编译器选项的播放方式。这次, utility.h仍然是(1),但是ww.h是:

ww.h(2)

#ifndef WW_H
#define WW_H

struct widget {
    int bits;
};

struct wombat {
    int bits;
};

extern void widgetTwiddle ( struct widget * w )  __attribute__((visibility("default")));
extern void wombatTwiddle ( struct wombat * w ) __attribute__((visibility("default")));

#endif

现在,我们像这样重新编译:

$ gcc -Wall -Wextra -fvisibility=hidden -c widgets.c wombats.c utility.c

我们告诉编译器注释其生成的每个全局符号 __attribute__((visibility("hidden"))),除非有反补贴 __attribute__((visibility("...")))在源代码中明确显示。

然后像以前一样重新链接共享库。我们再次在全局符号表中看到:

$ nm libww.so | egrep '*Twiddle'
00000000000005ea t utilityTwiddle
00000000000005aa T widgetTwiddle
00000000000005ca T wombatTwiddle

和动态符号表中:

$ nm -D libww.so | egrep '*Twiddle'
00000000000005aa T widgetTwiddle
00000000000005ca T wombatTwiddle

最后,显示从动态符号表中删除utilityTwiddle 其中libww.so的一种确实确实将其隐藏在与 libww.so。这是一个要调用所有*Twiddle的程序:

程序

#include <ww.h>

extern void utilityTwiddle ( int * bitsPtr, int bits );

int main()
{
    struct widget wi = {1};
    struct wombat wo = {2};
    widgetTwiddle(&wi);
    wombatTwiddle(&wo);
    utilityTwiddle(&wi.bits,wi.bits);
    return 0;
}

我们可以像这样构建它

$ gcc -Wall -Wextra -I. -c prog.c
$ gcc -o prog prog.o utility.o widgets.o wombats.o

但是没有人可以像这样构建它:

$ gcc -Wall -Wextra -I. -c prog.c
$ gcc -o prog prog.o -L. -lww
prog.o: In function `main':
prog.c:(.text+0x4a): undefined reference to `utilityTwiddle'
collect2: error: ld returned 1 exit status

请注意,-fvisibility编译选项,而不是链接选项。 您将其传递给编译命令而不是链接命令, 因为它的效果与撒__attribute__((visibility("...")))一样 您的源代码中声明的限定符, compiler 具有 通过将链接信息注入到它生成的对象文件中来兑现。如果 您希望看到可以重复进行最后编译的证据 并要求保存程序集文件:

$ gcc -Wall -Wextra -fvisibility=hidden -c widgets.c wombats.c utility.c -save-temps

然后比较说:

小工具

    .file   "widgets.c"
    .text
    .globl  widgetTwiddle
    .type   widgetTwiddle, @function
widgetTwiddle:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $16, %rsp
    movq    %rdi, -8(%rbp)
    movq    -8(%rbp), %rax
    movl    $1, %esi
    movq    %rax, %rdi
    call    utilityTwiddle@PLT
    nop
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   widgetTwiddle, .-widgetTwiddle
    .ident  "GCC: (Ubuntu 7.3.0-16ubuntu3) 7.3.0"
    .section    .note.GNU-stack,"",@progbits

具有:

实用程序。

    .file   "utility.c"
    .text
    .globl  utilityTwiddle
    .hidden utilityTwiddle
    ^^^^^^^^^^^^^^^^^^^^^^
    .type   utilityTwiddle, @function
utilityTwiddle:
    ...
    ...


[1]参见GCC manual

[2]参见GCC Manual, 3.16 Options for Code Generation Conventions