在Windows中,要在DLL中调用函数,该函数必须具有显式导出声明。例如,__declspec(dllexport)
或.def
文件。
除了Windows,我们可以在.so
(共享对象文件)中调用函数,即使该函数没有导出声明也是如此。就此而言,使我制作.so比.dll容易得多。
同时,我很好奇非Windows如何启用.so中定义的功能,以便其他程序在没有显式导出声明的情况下调用它们。我大概猜想.so文件中的所有函数都会自动导出,但是我不确定。
答案 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的链接描述文件:即
还有更专业的方法。
进一步阅读: