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

标签 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函数或变量。

[编辑] 显然,希望我们可以这样做:
/* 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

最佳答案

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

但是共享库确实确实存在用于链接的目的,并且共享库公开了全局符号
获得一个额外的属性,即动态能见度,该属性恰好适合您
目的。动态可见符号是全局符号的子集:
动态链接(即链接共享库)可见的全局符号
与其他东西(一个程序或另一个共享库)。

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

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

和/或编译器switch2:
-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文件(目标文件,共享库,程序)都具有全局符号表,但是
共享库还具有动态符号表。这是*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

那里没有变化。现在是动态符号表中的*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("...")))相同
编译器具有的源代码中的声明的限定符
通过将链接信息注入到它生成的目标文件中来兑现。如果
您希望看到可以重复进行最后编译的证据
并要求保存程序集文件:
$ gcc -Wall -Wextra -fvisibility=hidden -c widgets.c wombats.c utility.c -save-temps

然后比较说:

widgets.s
    .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:
  • 6.31.1 Common Function Attributes
  • 6.32.1 Common Variable Attributes

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

    关于c - 我可以使某些符号仅对其他图书馆成员可见吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52798143/

    相关文章:

    linux - 在构建依赖共享库的程序后如何更改共享库的文件名?

    haskell - 如何使用 Haskell 的 FunPtr?

    c - 此版本的 C 程序内存不足。有人请告诉我为什么内存不足

    c - %p 和 0x%08x 有什么区别

    c - 链接实际上是如何在内部发生的?

    c++ - OpenCV C++ 与 extern "C"

    shared-libraries - 我可以使用Groovy以外的其他语言在Jenkins Pipeline中创建 'shared library'吗?

    c - 使用函数 findMax(int **a,int m, int n) 在矩阵中查找最大元素

    c - 从函数返回一个指向 main 的指针

    c++ - 使用 undefined symbol 构建库 (C++)