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

Joh*_*all 3 c linker symbols shared-libraries static-libraries

考虑 3 个 C 源文件:

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

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

/* utility.c */
void utilityTwiddle ( int * bitsPtr, int bits ) {
    *bitsPtr ^= bits;
}
Run Code Online (Sandbox Code Playgroud)

它被编译并放入库中(例如 libww.a 或 libww.so)。有没有办法让utilityTwiddle()其他两个库成员可见和可用,但对链接到该库的人不可见?也就是说,考虑到:

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

cc -o appl appl.c -lww
Run Code Online (Sandbox Code Playgroud)

utilityTwiddle()它将无法链接,因为appl.c. 并且,因此appl.c可以自由定义自己的utilityTwiddle函数或变量。

[编辑]显然,我们希望它能起作用:

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

这似乎与链接共享库时限制符号的可见性相关,但它似乎没有解决被抑制的符号是否可供其他库成员使用。

[EDIT2]我已经找到了一种无需修改 C 源代码即可实现的方法。添加地图文件:

/* utility.map */
{ local: *; };
Run Code Online (Sandbox Code Playgroud)

然后执行以下操作:

$ gcc -shared -o utility.so utility.c -fPIC -Wl,--version-script=utility.map
Run Code Online (Sandbox Code Playgroud)

给我们一个动态符号表 w/o utilityTwiddle

$ nm -D utility.so
             w _Jv_RegisterClasses
             w __cxa_finalize
             w __gmon_start__
Run Code Online (Sandbox Code Playgroud)

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

/* libww.map */
{ global: list; of; all; symbols; to; export; local: *; };
Run Code Online (Sandbox Code Playgroud)

$ gcc -shared -o libww.so *.c -fPIC -Wl,--version-script=libww.map
Run Code Online (Sandbox Code Playgroud)

[EDIT3] 天哪,看起来这在不使用共享库的情况下也应该是可能的。如果我做:

ld -r -o wboth.o widgets.o wombats.o utility.o
Run Code Online (Sandbox Code Playgroud)

我可以看到链接器已解析到位置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
Run Code Online (Sandbox Code Playgroud)

utilityTwiddle仍然作为一个符号:

$ nm wboth.o
                 U _GLOBAL_OFFSET_TABLE_
0000000000000020 T utilityTwiddle
0000000000000000 T widgetTwiddle
0000000000000010 T wombatTwiddle
Run Code Online (Sandbox Code Playgroud)

因此,如果您能找到一种方法来删除该符号,您仍然可以成功链接wboth.o(我已经通过二进制编辑 wboth.o 对此进行了测试),并且它仍然链接并运行良好:

$ nm wboth.o
                 U _GLOBAL_OFFSET_TABLE_
0000000000000000 T widgetTwiddle
0000000000000010 T wombatTwiddle
0000000000000020 T xtilityTwiddle
Run Code Online (Sandbox Code Playgroud)

Mik*_*han 5

通过创建静态库并不能达到你想要的效果libww.a。如果您阅读静态库,您就会明白原因。静态库可用于向链接器提供一堆N个目标文件,链接器将从中提取所需的k (可能 = 0)并链接它们。因此,通过链接静态库无法实现直接链接那些k 个目标文件无法实现的任何目标。出于链接目的,静态库实际上并不存在。

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

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

__attribute__((visibility("default|hidden|protected|internal")
Run Code Online (Sandbox Code Playgroud)

和/或编译器开关2

-fvisibility=default|hidden|protected|internal
Run Code Online (Sandbox Code Playgroud)

以下是如何构建的演示libww.so,以便utilityTwiddle对库的客户端隐藏wombatTwiddlewidgetTwiddle可见。

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

网址 (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
Run Code Online (Sandbox Code Playgroud)

实用程序.h (1)

#ifndef UTILITY_H
#define UTILITY_H

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

#endif
Run Code Online (Sandbox Code Playgroud)

实用程序.c

#include "utility.h"

void utilityTwiddle ( int * bitsPtr, int bits ) {
    *bitsPtr ^= bits;
}
Run Code Online (Sandbox Code Playgroud)

袋熊.c

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

void wombatTwiddle ( struct wombat * w ) {
    utilityTwiddle(&w->bits, 1);
}
Run Code Online (Sandbox Code Playgroud)

小部件.c

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

void widgetTwiddle ( struct widget * w ) {
    utilityTwiddle(&w->bits, 1);
}
Run Code Online (Sandbox Code Playgroud)

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

$ gcc -Wall -Wextra -c widgets.c wombats.c utility.c
Run Code Online (Sandbox Code Playgroud)

libww.so并以默认方式将它们链接到:

$ gcc -shared -o libww.so widgets.o wombats.o utility.o
Run Code Online (Sandbox Code Playgroud)

以下是*Twiddle全局符号表中的符号libww.so

$ nm libww.so | egrep '*Twiddle'
000000000000063a T utilityTwiddle
00000000000005fa T widgetTwiddle
000000000000061a T wombatTwiddle
Run Code Online (Sandbox Code Playgroud)

这只是进入目标文件链接的全局 ( extern)符号的总和。它们都已定义( ),因为如果要在没有外部依赖项的情况下链接库本身,则它们必须如此。*Twiddlelibww.soT*Twiddle

任何ELF文件(目标文件、共享库、程序)都有一个全局符号表,但共享库也有一个动态符号表。以下是 的*Twiddle动态符号表中的符号libww.so

$ nm -D libww.so | egrep '*Twiddle'
000000000000063a T utilityTwiddle
00000000000005fa T widgetTwiddle
000000000000061a T wombatTwiddle
Run Code Online (Sandbox Code Playgroud)

它们完全一样。这就是我们想要改变的,让它utilityTwiddle 消失。

这是第二次剪辑。我们必须稍微更改一下源代码。

实用程序.h (2)

#ifndef UTILITY_H
#define UTILITY_H

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

#endif
Run Code Online (Sandbox Code Playgroud)

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

$ gcc -Wall -Wextra -c widgets.c wombats.c utility.c
$ gcc -shared -o libww.so widgets.o wombats.o utility.o
Run Code Online (Sandbox Code Playgroud)

以下是*Twiddle全局符号表中现在的符号:

$ nm libww.so | egrep '*Twiddle'
000000000000063a T utilityTwiddle
00000000000005fa T widgetTwiddle
000000000000061a T wombatTwiddle
Run Code Online (Sandbox Code Playgroud)

那里没有变化。以下是动态*Twiddle符号表中现在的符号:

$ nm -D libww.so | egrep '*Twiddle'
00000000000005aa T widgetTwiddle
00000000000005ca T wombatTwiddle
Run Code Online (Sandbox Code Playgroud)

utilityTwiddle离开了。

这是第三次切割,以不同的方式实现相同的结果。它比较冗长,但说明了-fvisibility编译器选项的作用。这次, utility.h再次按照(1),但是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
Run Code Online (Sandbox Code Playgroud)

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

$ gcc -Wall -Wextra -fvisibility=hidden -c widgets.c wombats.c utility.c
Run Code Online (Sandbox Code Playgroud)

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

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

$ nm libww.so | egrep '*Twiddle'
00000000000005ea t utilityTwiddle
00000000000005aa T widgetTwiddle
00000000000005ca T wombatTwiddle
Run Code Online (Sandbox Code Playgroud)

并在动态符号表中:

$ nm -D libww.so | egrep '*Twiddle'
00000000000005aa T widgetTwiddle
00000000000005ca T wombatTwiddle
Run Code Online (Sandbox Code Playgroud)

最后,为了表明以这些方式之一 utilityTwiddle从动态符号表中删除 确实确实对与 链接的客户端隐藏了它。这是一个想要调用所有s 的程序:libww.solibww.so*Twiddle

程序c

#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;
}
Run Code Online (Sandbox Code Playgroud)

我们可以像这样构建它:

$ gcc -Wall -Wextra -I. -c prog.c
$ gcc -o prog prog.o utility.o widgets.o wombats.o
Run Code Online (Sandbox Code Playgroud)

但没有人能像这样构建它:

$ 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
Run Code Online (Sandbox Code Playgroud)

请明确这-fvisibility是一个编译选项,而不是链接选项。__attribute__((visibility("..."))) 您将其传递给编译命令而不是链接命令,因为它的效果与在源代码中的声明上撒上限定符相同,编译器必须通过将链接信息注入到它生成的目标文件中来遵守该限定符。如果您想查看证据,您可以重复上次编译并请求保存程序集文件:

$ gcc -Wall -Wextra -fvisibility=hidden -c widgets.c wombats.c utility.c -save-temps
Run Code Online (Sandbox Code Playgroud)

然后对比说:

小部件

    .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
Run Code Online (Sandbox Code Playgroud)

和:

实用程序.s

    .file   "utility.c"
    .text
    .globl  utilityTwiddle
    .hidden utilityTwiddle
    ^^^^^^^^^^^^^^^^^^^^^^
    .type   utilityTwiddle, @function
utilityTwiddle:
    ...
    ...
Run Code Online (Sandbox Code Playgroud)


[1]参见GCC手册

[2] 请参阅GCC 手册,3.16 代码生成约定选项