.so文件中的功能如何自动导出?

时间:2019-01-02 02:52:42

标签: shared-libraries

在Windows中,要在DLL中调用函数,该函数必须具有显式导出声明。例如,__declspec(dllexport).def文件。

除了Windows,我们可以在.so(共享对象文件)中调用函数,即使该函数没有导出声明也是如此。就此而言,使我制作.so比.dll容易得多。

同时,我很好奇非Windows如何启用.so中定义的功能,以便其他程序在没有显式导出声明的情况下调用它们。我大概猜想.so文件中的所有函数都会自动导出,但是我不确定。

1 个答案:

答案 0 :(得分:1)

.so文件通常是类Unix操作系统中的DSO(动态共享库,也称为共享库)。你想要 知道如何使此类文件中定义的符号对运行时加载程序可见 用于在以下情况下将DSO动态链接到某些程序的过程中 它被执行了。那就是你所说的“出口”。 “出口”有点 Windows / DLL类术语,也容易与“外部”或“全局”混淆, 因此我们改为说动态可见

我将解释如何在以下情况下控制符号的动态可见性 使用GNU工具链构建的DSO-即使用GCC编译器(gcc g++gfortran等),并与binutils链接器ld(或兼容)链接 替代编译器和链接器)。我将用C代码说明。力学是 其他语言也一样。

在目标文件中定义的符号是C源代码中的文件作用域变量。即变量 没有在任何块中定义的那些。大范围变量:

{ int i; ... }

仅在执行封闭块且没有永久块时定义 放在目标文件中。

由GCC生成的目标文件中定义的符号为 local global

可以在定义它的目标文件中引用本地符号,但是 对象文件根本不会显示链接 。不用于静态链接。 不用于动态链接。在C语言中,文件作用域变量定义是全局的 默认情况下为本地(如果它符合static存储类的话)。所以 在此源文件中:

foobar.c(1)

static int foo(void)
{
    return 42;
}

int bar(void)
{
    return foo();
}

foo是局部符号,bar是全局符号。如果我们编译这个文件 与-save-temps

$ gcc -save-temps -c -fPIC foobar.c

然后GCC将程序集列表保存在foobar.s中,我们可以 查看生成的汇编代码如何注册bar是全局的而foo不是全局的事实:

foobar.s(1)

    .file   "foobar.c"
    .text
    .type   foo, @function
foo:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    $42, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   foo, .-foo
    .globl  bar
    .type   bar, @function
bar:
.LFB1:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    call    foo
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE1:
    .size   bar, .-bar
    .ident  "GCC: (Ubuntu 8.2.0-7ubuntu1) 8.2.0"
    .section    .note.GNU-stack,"",@progbits

汇编器指令.globl bar表示bar是全局符号。 没有.globl foo;因此foo是本地的。

如果我们检查目标文件本身中的符号,则使用

$ readelf -s foobar.o

Symbol table '.symtab' contains 10 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS foobar.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    2
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    3
     5: 0000000000000000    11 FUNC    LOCAL  DEFAULT    1 foo
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    5
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    6
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    4
     9: 000000000000000b    11 FUNC    GLOBAL DEFAULT    1 bar

消息相同:

     5: 0000000000000000    11 FUNC    LOCAL  DEFAULT    1 foo
     ...
     9: 000000000000000b    11 FUNC    GLOBAL DEFAULT    1 bar

在目标文件中定义的 global 符号,只有全局符号, 可用于静态链接器来解析其他目标文件中的引用。确实 本地符号仅可能出现在文件的符号表中 由调试器或其他一些目标文件探测工具使用。如果我们重做编译 甚至没有任何优化:

$ gcc -save-temps -O1 -c -fPIC foobar.c
$ readelf -s foobar.o

Symbol table '.symtab' contains 9 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS foobar.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    2
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    3
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    6
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    4
     8: 0000000000000000     6 FUNC    GLOBAL DEFAULT    1 bar

然后foo从符号表中消失。

由于全局符号可用于静态链接器,因此我们可以链接程序 使用foobar.o从另一个目标文件调用bar

main.c

#include <stdio.h>

extern int foo(void);

int main(void)
{
    printf("%d\n",bar());
    return 0;
}

像这样:

$ gcc -c main.c
$ gcc -o prog main.o foobar.o
$ ./prog
42

但是,正如您已经注意到的那样,我们无需以任何方式更改foobar.o bar对加载程序动态可见。我们可以直接将其链接到 共享库:

$ gcc -shared -o libbar.so foobar.o

然后将同一程序动态链接到该共享库:

$ gcc -o prog main.o libbar.so

没关系:

$ ./prog
./prog: error while loading shared libraries: libbar.so: cannot open shared object file: No such file or directory

...糟糕。只要我们让加载程序知道libbar.so在哪里,就可以了,因为 工作目录不是默认情况下缓存的搜索目录之一:

$ export LD_LIBRARY_PATH=.
$ ./prog
42

目标文件foobar.o包含一个符号表,如我们所见, .symtab部分中的内容,(至少包括)静态链接器可用的全局符号。 DSO libbar.so在其.symtab部分中也有一个符号表。但它也有一个 dynamic 符号表, .dynsym部分:

$ readelf -s libbar.so

    Symbol table '.dynsym' contains 6 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
         1: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __cxa_finalize
         2: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
         3: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab
         4: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
         5: 00000000000010f5     6 FUNC    GLOBAL DEFAULT    9 bar

    Symbol table '.symtab' contains 45 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
         ...
         ...
        21: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
        22: 0000000000001040     0 FUNC    LOCAL  DEFAULT    9 deregister_tm_clones
        23: 0000000000001070     0 FUNC    LOCAL  DEFAULT    9 register_tm_clones
        24: 00000000000010b0     0 FUNC    LOCAL  DEFAULT    9 __do_global_dtors_aux
        25: 0000000000004020     1 OBJECT  LOCAL  DEFAULT   19 completed.7930
        26: 0000000000003e88     0 OBJECT  LOCAL  DEFAULT   14 __do_global_dtors_aux_fin
        27: 00000000000010f0     0 FUNC    LOCAL  DEFAULT    9 frame_dummy
        28: 0000000000003e80     0 OBJECT  LOCAL  DEFAULT   13 __frame_dummy_init_array_
        29: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS foobar.c
        30: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
        31: 0000000000002094     0 OBJECT  LOCAL  DEFAULT   12 __FRAME_END__
        32: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS
        33: 0000000000003e90     0 OBJECT  LOCAL  DEFAULT   15 _DYNAMIC
        34: 0000000000004020     0 OBJECT  LOCAL  DEFAULT   18 __TMC_END__
        35: 0000000000004018     0 OBJECT  LOCAL  DEFAULT   18 __dso_handle
        36: 0000000000001000     0 FUNC    LOCAL  DEFAULT    6 _init
        37: 0000000000002000     0 NOTYPE  LOCAL  DEFAULT   11 __GNU_EH_FRAME_HDR
        38: 00000000000010fc     0 FUNC    LOCAL  DEFAULT   10 _fini
        39: 0000000000004000     0 OBJECT  LOCAL  DEFAULT   17 _GLOBAL_OFFSET_TABLE_
        40: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __cxa_finalize
        41: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
        42: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab
        43: 00000000000010f5     6 FUNC    GLOBAL DEFAULT    9 bar

动态符号表中的符号是动态可见的符号- 可用于运行时加载程序。您 可以看到bar.symtab的{​​{1}}和.dynsym中都出现了。 在这两种情况下,符号在libbar.so中都有GLOBAL(= binding ) 列和bind(= visibility )列中的DEFAULT

如果您想vis仅向您显示动态符号表,则:

readelf

将执行此操作,但不会readelf --dyn-syms libbar.so 执行此操作,因为目标文件没有动态符号表:

foobar.o

所以链接:

$ readelf --dyn-syms foobar.o; echo Done
Done

创建 $ gcc -shared -o libbar.so foobar.o 的动态符号表,并在其中填充符号 libbar.so global 符号表(以及各种GCC样板 GCC通过默认添加到链接的文件)。

这看起来像您的猜测:

  

我大概猜测.so文件中的所有功能都会自动导出

是正确的。实际上,这很接近,但不正确。

看看我重新编译foobar.o会发生什么 像这样:

foobar.c

让我们再看一下组装清单:

foobar.s(2)

$ gcc -save-temps -fvisibility=hidden -c -fPIC foobar.c

注意汇编指令:

...
...
    .globl  bar
    .hidden bar
    .type   bar, @function
bar:
.LFB1:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    call    foo
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
...
...

那是以前没有的。 .hidden bar 仍然在那里; .globl bar仍然是全球性的 符号。我仍然可以在此程序中静态链接bar

foobar.o

我仍然可以链接此共享库:

$ gcc -o prog main.o foobar.o
$ ./prog
42

但是我不能再动态链接该程序:

$ gcc -shared -o libbar.so foobar.o

$ gcc -o prog main.o libbar.so /usr/bin/ld: main.o: in function `main': main.c:(.text+0x5): undefined reference to `bar' collect2: error: ld returned 1 exit status 中,foobar.o仍在符号表中:

bar

,但现在在输出的$ readelf -s foobar.o | grep bar 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS foobar.c 9: 000000000000000b 11 FUNC GLOBAL HIDDEN 1 bar (= HIDDEN)列中将其标记为vis

visibility仍在bar的符号表中:

libbar.so

但这一次,它是一个$ readelf -s libbar.so | grep bar 29: 0000000000000000 0 FILE LOCAL DEFAULT ABS foobar.c 41: 0000000000001100 11 FUNC LOCAL DEFAULT 9 bar 符号。静态可用 来自LOCAL的链接器-正如我们刚才在链接失败时看到的那样。而且它不再存在于 动态符号表:

libbar.so

因此,$ readelf --dyn-syms libbar.so | grep bar; echo done done 在编译-fvisibility=hidden时的作用是使 编译器在foobar.c中将.globl符号注释为.hidden。然后,当 foobar.o链接到foobar.o,链接器会转换每个全局 hidden libbar.so中的 local 符号的符号,因此它不能用于解析引用 每当libbar.so与其他内容链接时。而且它不会添加隐藏的 符号到libbar.so dynamic 符号表中,因此运行时加载程序无法 看到他们动态地解析引用。

到目前为止的故事:链接器创建共享库时,它将添加到动态库中。 符号表所有在输入目标文件中定义且未标记为 hidden 的全局符号 由编译器。这些成为共享库的动态可见符号。全局符号不是 默认为隐藏,但我们可以使用编译器选项libbar.so将其隐藏。 可见性 该选项所指的是动态可见性。

现在可以使用-fvisibility=hidden从动态可见性中删除全局符号 看起来还不是很有用,因为似乎我们编译时使用的任何目标文件 该选项不能为共享库提供任何动态可见的符号。

但是实际上,我们可以单独控制 在目标文件中定义哪些全局符号 将动态可见,而不会。让我们如下更改-fvisibility=hidden

foobar.c(2)

foobar.c

您在此处看到的__attribute__语法是GCC语言扩展 用于指定标准语言无法表达的符号属性,例如动态可见性。微软的 static int foo(void) { return 42; } int __attribute__((visibility("default"))) bar(void) { return foo(); } 是一种Microsoft语言扩展,与GCC的declspec(dllexport)具有相同的作用, 但是对于GCC,默认情况下,在目标文件中定义的全局符号将拥有__attribute__((visibility("default"))) 必须使用__attribute__((visibility("default")))进行编译才能覆盖它。

像上次一样重新编译:

-fvisibility=hidden

现在是$ gcc -fvisibility=hidden -c -fPIC foobar.c 的符号表:

foobar.o
尽管$ readelf -s foobar.o | grep bar 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS foobar.c 9: 000000000000000b 11 FUNC GLOBAL DEFAULT 1 bar

再次显示bar的可见性DEFAULT。如果我们重新链接-fvisibility=hidden

libbar.so

我们看到$ gcc -shared -o libbar.so foobar.o 回到了动态符号表中:

bar

因此,$ readelf --dyn-syms libbar.so | grep bar 5: 0000000000001100 11 FUNC GLOBAL DEFAULT 9 bar 告诉编译器将全局符号标记为隐藏 除非,否则在源代码中,我们明确指定了抵消动态可见性 那个符号。

这是从目标文件中精确选择符号的一种方法 使动态可见:将-fvisibility=hidden传递给编译器,然后 只需在源代码中单独指定-fvisibility=hidden 我们希望动态可见的符号。

另一种方法是 not __attribute__((visibility("default")))传递给编译器,并且单独 在源代码中指定-fvisibility=hidden,仅用于 我们想要的符号是动态可见的。因此,如果我们再次更改__attribute__((visibility("hidden"))) 像这样:

foobar.c(3)

foobar.c

然后使用默认可见性重新编译:

static int foo(void)
{
    return 42;
}

int __attribute__((visibility("hidden"))) bar(void)
{
    return foo();
}

$ gcc -c -fPIC foobar.c 恢复为目标文件中的隐藏

bar

重新链接$ readelf -s foobar.o | grep bar 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS foobar.c 9: 000000000000000b 11 FUNC GLOBAL HIDDEN 1 bar 之后,libbar.so仍然没有出现在其动态符号中 表格:

bar

专业方法是最大程度地减少 完全符合指定的DSO。通过我们讨论过的设备, 这意味着使用$ gcc -shared -o libbar.so foobar.o $ readelf --dyn-syms libbar.so | grep bar; echo Done Done 进行编译并使用-fvisibility=hidden进行编译 公开指定的API。动态API也可以使用GNU链接器进行控制-和 versioned - 使用一种称为version-script的链接描述文件:即 还有更专业的方法。

进一步阅读: