Shared Libraries
Table of Contents
1. Static Libraries
在介绍动态库(Shared Libraries)之前,我们先介绍一下静态库(Static Libraries)。
1.1. 制作静态库
要制作静态库,先把各个模块编译为对应的 .o 文件,再用工具 ar
打包为静态库即可。
$ cc -c mod1.c mod2.c mod3.c $ ar r libdemo.a mod1.o mod2.o mod3.o $ rm mod1.o mod2.o mod3.o
1.2. 链接静态库
要把程序链接到静态库,有两种方法。
方法 1:直接指定静态库的文件名。
$ cc -c prog.c $ cc -o prog prog.o libdemo.a
方法 2:通过 -l
指定静态库的库名(库名为文件名去掉 lib 前缀和 .a 后缀)。
$ cc -c prog.c $ cc -o prog prog.o -ldemo
如果静态库不在标准的搜索路径中,可以通过 -L
指定静态库的路径。
注: 如果 libdemo.so 和 libdemo.a 在库探索路径中同时存在,-ldemo 会优先使用共享库,通过指定 gcc 的 -static
选项可强制使用静态库。
2. Shared Libraries 基本用法
静态库有很多缺点,比如更新和维护比较麻烦,如果想使用库的最新版本,必须显示地重新链接。静态库对存储器资源造成极大地浪费,如果 100 个程序都引用某个静态库,当它们运行时都会复制库中函数代码到每个进程的 text 段中。
共享库能解决上面提到的静态库缺点。共享库以两种方法来实现“共享”:首先,在文件系统中,对于一个库只有一个 .so 文件,所有引用该库的可执行目标文件共享这个 .so 文件中的代码和数据,而不是像静态库那样被拷贝和嵌入到引用它们的可执行文件中。其次,在存储器中,一个共享库的 .text 节的一个副本可以被不同的正在运行的进程共享。
2.1. 制作共享库
不同的编译器,生成共享库的方法不一样,下面仅介绍 gcc 的方法。
$ gcc -c -fPIC -Wall mod1.c mod2.c mod3.c $ gcc -shared -o libfoo.so mod1.o mod2.o mod3.o
也可以用下面一个命令生成共享库 .so 文件:
$ gcc -fPIC -Wall mod1.c mod2.c mod3.c -shared -o libfoo.so
2.2. 链接共享库
要把程序链接到动态库,有两种方法。
方法 1:链接时,直接指定共享库即可。
$ gcc -Wall -o prog prog.c libfoo.so
方法 2:通过 -l
指定共享库的库名(注:库名为文件名去掉 lib 前缀和 .so 后缀)。
$ gcc -Wall -o prog prog.c -lfoo
如果共享库不在标准的搜索路径中,可以通过 -L
指定共享库的路径。如果找不到共享库,gcc 会尝试找对应的静态库 libfoo.a。
运行程序时,如果共享库不在标准路径中,需要指定环境变量 LD_LIBRARY_PATH。
$ LD_LIBRARY_PATH=. ./prog
2.2.1. LD_LIBRARY_PATH/LIBPATH/DYLD_LIBRARY_PATH
在不同的 Unix 系统的,设置非标准共享库的搜索路径的环境变量可能不同,如表 1 所示。
Linux | AIX | Solaris | MacOS |
---|---|---|---|
LD_LIBRARY_PATH | LIBPATH | LD_LIBRARY_PATH | DYLD_LIBRARY_PATH |
3. 加载共享库的两种机制(Load-time Relocation 和 PIC)
共享库是一个目标模块。在运行时,共享库可以加载到任意的存储器地址,并和一个存储器中的程序链接起来,这个过程称为“动态链接”。
有两种主要的机制来处理动态链接:
- Load-time Relocation(加载时重定位)
- Position Independent Code (PIC)
如果没有使用 -fPIC
编译共享库,则会使用前者(即加载时重定位)来处理动态链接;不过现在一般使用后者(即 PIC),后面会介绍 PIC 的优点。
3.1. Load-time Relocation
Load-time Relocation 是一种直观的方法,在加载共享库时把共享库中的符号地址地址修改为当前进程地址空间的一个地址(这就是重定位过程)。这种方法有两个缺点:
- 存在性能问题,当一个程序使用很多共享库时,在启动时要花费较多的时间在来对每个共享库进行重定位。
- 共享库的代码段没有办法在内存中共享。由于进行重定位过程时共享库的代码段被修改了,所以无法在多个进程的地址空间中共享(除非所有进程加载共享库到地址空间的相同位置,但会带来管理问题,Linux 没有这么做)。
3.2. Position Independent Code (PIC)
The –fPIC
option specifies that the compiler should generate position-independent code. It allow the code to be located at any virtual address at run time (rather than load time).
现在一般使用这种方法。它在动态链接过程中不会修改共享库中代码段。
如何访问 PIC 共享库中的全局变量符号?利用 GOT (Global Offset Table)。
如何访问 PIC 共享库中的函数符号?利用 GOT (Global Offset Table) 和 PLT(Procedure Linkage Table)。
说明:PIC 解决了 Load-time relocation 方法的两个缺点,但 PIC 代码也有性能缺陷——访问全局变量需要更多的指令(因为它通过 GOT 间接地访问全局变量)。
参考:
深入理解计算机系统(原书第 2 版)7.12 与位置无关代码
Position Independent Code (PIC) in shared libraries
3.2.1. 检测 .o 文件是否是用 -fPIC 选项编译
如何检测 .o 文件是否是用 -fPIC
选项编译?
先做个测试,不用或用 -fPIC
选项编译时,目标文件中的符号有什么不同?
$ cat mod1.c extern int i; void test(void) { i = 1; } $ gcc -c mod1.c # 不使用 -fPIC 编译 $ nm mod1.o U i 0000000000000000 T test $ gcc -c -o mod1-PIC.o -fPIC mod1.c # 使用 -fPIC 编译 $ nm mod1-PIC.o U _GLOBAL_OFFSET_TABLE_ U i 0000000000000000 T test
由上面测试可以知道, 当目标文件用 -fPIC
选项编译生成时,会多个符号 _GLOBAL_OFFSET_TABLE_
。 所以可以用下面任一个命令来检测 .o 文件是否是用 -fPIC
选项编译,如果有输出,则说明 .o 文件用 -fPIC
选项编译生成。
$ nm mod1.o | grep _GLOBAL_OFFSET_TABLE_ $ readelf -s mod1.o | grep _GLOBAL_OFFSET_TABLE_
Global Offset Table 参考:http://www.bottomupcs.com/global_offset_tables.html
4. 把“库搜索目录”写死在目标文件中 (-Wl,-rpath,/path/to/lib)
We have already seen two ways of informing the dynamic linker of the location of shared libraries: using the LD_LIBRARY_PATH environment variable and installing a shared library into one of the standard library directories (/lib, /usr/lib, or one of the directories listed in /etc/ld.so.conf).
There is a third way: during the static editing phase, we can insert into the executable a list of directories that should be searched at run time for shared libraries. This is useful if we have libraries that reside in fixed locations that are not among the standard locations searched by the dynamic linker. To do this, we employ the –rpath
linker option when creating an executable:
$ gcc -g -Wall -Wl,-rpath,/home/mtk/pdir -o prog prog.c libdemo.so
The above command copies the string /home/mtk/pdir into the run-time library path (rpath) list of the executable prog, so, that when the program is run, the dynamic linker will also search this directory when resolving shared library references.
4.1. 编译时指定 LD_RUN_PATH
An alternative to the –rpath
option is the LD_RUN_PATH
environment variable. This variable can be assigned a string containing a series of colon-separated directories that are to be used as the rpath list when building the executable file. LD_RUN_PATH is employed only if the –rpath option is not specified when building the executable.
4.2. 指定动态库相对搜索路径(在 rpath 中使用$ORIGIN)
The dynamic linker interprets $ORIGIN to mean “the directory containing the application.”
This means that we can, for example, build an application with the following command:
$ gcc -Wl,-rpath,'$ORIGIN'/lib ...
This presumes that at run time the application’s shared libraries will reside in the subdirectory lib under the directory that contains the application executable.
5. 运行时符号解析 (-Wl,-Bsymbolic)
Suppose that a global symbol (i.e., a function or variable) is defined in multiple locations, such as in an executable and in a shared library, or in multiple shared libraries. How is a reference to that symbol resolved?
假设有下面程序,符号 xyz 同时出现在主程序和一个动态库中。
You might expect this: prog libfoo.so +----------------------------+ +----------------------------+ | #include<stdio.h> | | #include<stdio.h> | | extern void func(void); | | | | | | void xyz() { | | void xyz() { | | printf("foo-xyz\n"); | | printf("main-xyz\n"); | | } | | } | | ^ | | | | | | | int main() { | | | | | xyz(); | | | | | func(); -------+----> | void func() { | | return 0; | | xyz(); | | } | | } | +----------------------------+ +----------------------------+ But what you get may be this: prog libfoo.so +----------------------------+ +----------------------------+ | #include<stdio.h> | | #include<stdio.h> | | extern void func(void); | | | | | | void xyz() { | | void xyz() { |<-+ | printf("foo-xyz\n"); | | printf("main-xyz\n"); | | | } | | } | | | | | | +---+--------+ | | int main() { | | | | | xyz(); | | | | | func(); -------+----> | void func() { | | return 0; | | xyz(); | | } | | } | +----------------------------+ +----------------------------+
测试 1:
$ gcc -g -c -fPIC -Wall foo.c $ gcc -g -shared -o libfoo.so foo.o $ gcc -g -o prog prog.c libfoo.so # 把libfoo.so放在前面不会影响结果 gcc -g -o prog libfoo.so prog.c $ LD_LIBRARY_PATH=. ./prog main-xyz main-xyz
通过上面的结果可知,func() 调用的是 prog 中的 xyz(),而没有调用动态库 libfoo.so 中的 xyz()。
The –Bsymbolic
linker option specifies that references to global symbols within a shared library should be preferentially bound to definitions (if they exist) within that library. (Note that, regardless of this option, calling xyz() from the main program would always invoke the version of xyz() defined in the main program.)
测试 2(编译动态库时,增加选项 -Wl,-Bsymbolic
):
$ gcc -g -c -fPIC -Wall foo.c $ gcc -g -shared -Wl,-Bsymbolic -o libfoo.so foo.o # libfoo.so中的符号优先调用同一库中的符号 $ gcc -g -o prog prog.c libfoo.so $ LD_LIBRARY_PATH=. ./prog main-xyz foo-xyz
说明:上面的实验在 Linux 中测试通过,其它平台可能不一样,如默认地 Mac OS X 中库中的函数优先使用同一个库中的函数。
说明:仅就上面的问题而言,我们还有其它办法来使 func() 调用动态库 libfoo.so 中的 xyz(),如:
- 把 prog.c 中的函数 xyz() 声明为 static;
- 利用 gcc 的 __attribute__ 把 prog.o 中的符号 xyz() 隐藏起来。即:
#include<stdio.h> extern void func(void); __attribute__ ((visibility ("hidden"))) void xyz() { printf("main-xyz\n"); } int main() { xyz(); func(); return 0; }
6. 动态加载共享库
当开始程序执行时,所有的共享库都会被加载,如果找不到会直接报错。但有时候我们只想在需要的时候进行加载(如插件)。这可以通过在程序运行过程中动态加载共享库来实现。
6.1. dlopen API
The dlopen API enables a program to open a shared library at run time, search for a function by name in that library, and then call the function. A shared library loaded at run time in this way is commonly referred to as a dynamically loaded library, and is created in the same way as any other shared library.
The core dlopen API consists of the following functions (all of which are specified in SUSv3):
- The
dlopen()
function opens a shared library, returning a handle used by subsequent calls. - The
dlsym()
function searches a library for a symbol (a string containing the name of a function or variable) and returns its address. - The
dlclose()
function closes a library previously opened by dlopen(). - The
dlerror()
function returns an error-message string, and is used after a failure return from one of the preceding functions.
注:在 Linux 上,如果程序使用了 dlopen/dlsym/dlclose/dlerror 这些 API,则编译时必须使用 -ldl
选项以链接到 libdl 库。
6.2. 让动态加载库能访问主程序中符号 (-rdynamic)
Suppose that we use dlopen() to dynamically load a shared library, use dlsym() to obtain the address of a function x() from that library, and then call x(). If x() in turn calls a function y(), then y() would normally be sought in one of the shared libraries loaded by the program.
Sometimes, it is desirable instead to have x() invoke an implementation of y() in the main program. (This is similar to a callback mechanism.)
In order to do this, we must make the (global-scope) symbols in the main program available to the dynamic linker, by linking the program using the –rdynamic
linker option:
$ gcc -rdynamic main.c
gcc 的下面 4 个选项的含义一样:
-rdynamic
-export-dynamic
-Wl,--export-dynamic
-Wl,-E
7. 控制共享库的导出符号
如果我们不想公开共享库中的某个符号,可通过把它设置为 static 来实现。但有时这种方法不可行,如:
// file foo.c void foo() { // foo()是libfoo的主要函数,想公开 bar(); } // file bar.c void bar() { // bar()仅是内部函数,不想公开。但却不能把它设置为static,因为另一文件foo.c中调用了它。 }
可把上面的文件 foo.c 和 bar.c 分别编译,链接生成 libfoo.so。
$ gcc -fPIC -c foo.c $ gcc -fPIC -c bar.c $ gcc -shared -o libfoo.so foo.o bar.o $ nm libfoo.so |egrep 'foo|bar' 0000000000001126 T bar 0000000000001115 T foo
可以看到有两个符号 foo 和 bar 在动态库 libfoo.so 中是对外可见(记号 T 表示 Global Symbol in Text Section),但 bar 并不是我们想公开的。如何隐藏符号 bar 呢?可以利用符号的“visibility 属性”,具体请看下文分解。
参考:
Binary Hacks: 黑客秘笈 100 选,Hack#29 控制对外公开库的符号
How To Write Shared Libraries, by Ulrich Drepper: http://www.akkadia.org/drepper/dsohowto.pdf
How To Write Shared Libraries, (slides) by Ulrich Drepper Slides: http://www.akkadia.org/drepper/ukuug2002slides.pdf
Shared Library Symbol Conflicts: https://holtstrom.com/michael/blog/post/437/Shared-Library-Symbol-Conflicts.html
7.1. 控制共享库导出符号——visibility 属性
可用通过定义符号的 visibility 属性来控制它的对外可见性。
具体来说,下面任一种策略都行:
- 把想要公开的符号声明为
__attribute__ ((visibility("default")))
;然后编译 .o 时,默认使用选项-fvisibility=hidden
。 - 把所有不想公开的符号声明为
__attribute__ ((visibility("hidden")))
;然后编译 .o 时,默认使用选项-fvisibility=default
。
下面是采用策略 1 的测试:
// file foo.c __attribute__ ((visibility("default"))) void foo() { // foo() 是 libfoo 的主要函数,想公开,设置 __attribute__ ((visibility("default"))) bar(); }
编译时指定 -fvisibility=hidden
:
$ gcc -fPIC -c -fvisibility=hidden foo.c $ gcc -fPIC -c -fvisibility=hidden bar.c $ gcc -shared -o libfoo.so foo.o bar.o $ nm libfoo.so |egrep 'foo|bar' 0000000000001116 t bar 0000000000001105 T foo
可以看到,符号 bar 在 libfoo.so 中已经不再对外可见了(记号 t 表示 Local Symbol in Text Section,这不是对外可见的)。
参考:
https://gcc.gnu.org/onlinedocs/gcc-5.2.0/gcc/Code-Gen-Options.html#index-fvisibility-2829
7.1.1. visibility 属性的实现基础
The ELF application binary interface (ABI) defines the visibility of symbols. Generally, it defines four classes:
- STV_DEFAULT - Symbols defined with it will be exported. In other words, it declares that symbols are visible everywhere.
- STV_HIDDEN - Symbols defined with it will not be exported and cannot be used from other objects
- STV_PROTECTED - The symbol is visible outside the current executable or shared object, but it may not be overridden. In other words, if a protected symbol in a shared library is referenced by an other code in the shared library, the other code will always reference the symbol in the shared library, even if the executable defines a symbol with the same name.
- STV_INTERNAL - The symbol is not accessible outside the current executable or shared library.
In most cases, only STV_DEFAULT and STV_HIDDEN are more commonly used.
参考:
man 5 elf
Controlling symbol visibility for shared libraries: http://www.ibm.com/developerworks/aix/library/au-aix-symbol-visibility/
7.1.2. 检查 .o 文件中符号的可见性
如何检查 .o 文件中符号的可见性呢?这时,不能直接使用 nm 命令。
$ cat file.c __attribute__ ((visibility("default"))) void fun1() { } __attribute__ ((visibility("hidden"))) void fun2() { } $ gcc -fPIC -c file.c $ nm file.o 0000000000000000 T fun1 0000000000000007 T fun2
在上面例子中,我们指定了 fun1 和 fun2 分别为对外可见和对外不可见。但使用 nm 检查时,输出的都是 T。
下面介绍两种在 Linux 中检查 .o 文件中符号的可见性的方法。
方法一、使用 objdump -t file.o
,如:
$ objdump -t file.o file.o: file format elf64-x86-64 SYMBOL TABLE: 0000000000000000 l df *ABS* 0000000000000000 file.c 0000000000000000 l d .text 0000000000000000 .text 0000000000000000 l d .data 0000000000000000 .data 0000000000000000 l d .bss 0000000000000000 .bss 0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack 0000000000000000 l d .eh_frame 0000000000000000 .eh_frame 0000000000000000 l d .comment 0000000000000000 .comment 0000000000000000 g F .text 0000000000000007 fun1 0000000000000007 g F .text 0000000000000007 .hidden fun2
在上面输出中 fun2 前面有个 .hidden
字样,表示是对外不可见的。
方法二,使用 readelf -s file.o
,如:
$ readelf -s file.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 file.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 7 FUNC GLOBAL DEFAULT 1 fun1 9: 0000000000000007 7 FUNC GLOBAL HIDDEN 1 fun2
在命令 readelf -s file.o
输出的第 6 列(即 Vis 列)中的 DEFAULT/HIDDEN 分别表示符号对外可见/不可见。如上面例子中 fun1 是对外可见的,而 fun2 是对外不可见的。
7.2. 控制共享库导出符号——LD Version Script (--version-script
)
使用 GNU 链接器的版本控制脚本可以控制对外公开的符号。
如,可以准备下面文件来实现仅导出符号 foo:
$ cat libfoo.map { global: foo; # only export symbol foo local: *; };
使用链接选项 --version-script
来指定这个文件即可。
$ gcc -fPIC -c foo.c $ gcc -fPIC -c bar.c $ gcc -shared -o libfoo.so foo.o bar.o -Wl,--version-script,libfoo.map $ nm libfoo.so | egrep 'foo|bar' 0000000000001116 t bar 0000000000001105 T foo
可以看到,符号 bar 已经对外不可见了。
7.2.1. Version Script 的格式
LD Version Script 的格式和 Sun's linker in Solaris 2.5 相同。
参考:
Sun's Linker and Libraries Guide: http://docs.oracle.com/cd/E19253-01/817-1984/
http://docs.oracle.com/cd/E19683-01/817-3677/817-3677.pdf
7.2.2. Solaris 系统(-M<mapfile>
)
Solaris 系统中 C 编译器(cc
)不支持 --version-script
选项,可以使用 -M<mapfile>
选项。
$ cc -flags|grep -w M -M<file> Pass <file> mapfile to linker
测试实例如下(源文件及 map 文件和前面实例相同):
$ cc -fPIC -c foo.c $ cc -fPIC -c bar.c $ cc -shared -o libfoo.so foo.o bar.o $ nm libfoo.so |grep GLOB [49] | 66288| 0|OBJT |GLOB |0 |11 |_DYNAMIC [50] | 66220| 0|OBJT |GLOB |0 |9 |_GLOBAL_OFFSET_TABLE_ [40] | 66224| 0|OBJT |GLOB |0 |10 |_PROCEDURE_LINKAGE_TABLE_ [48] | 66440| 0|OBJT |GLOB |0 |13 |_edata [44] | 66440| 0|OBJT |GLOB |0 |14 |_end [42] | 684| 0|OBJT |GLOB |0 |8 |_etext [41] | 668| 12|FUNC |GLOB |0 |7 |_fini [43] | 656| 12|FUNC |GLOB |0 |6 |_init [47] | 680| 4|OBJT |GLOB |0 |8 |_lib_version [45] | 632| 20|FUNC |GLOB |0 |5 |bar [46] | 584| 28|FUNC |GLOB |0 |5 |foo $ cc -shared -o libfoo.so foo.o bar.o -M libfoo.map $ nm libfoo.so |grep GLOB [45] | 66136| 0|OBJT |GLOB |0 |11 |_DYNAMIC [48] | 66132| 0|OBJT |GLOB |0 |10 |_GLOBAL_OFFSET_TABLE_ [47] | 0| 0|OBJT |GLOB |0 |ABS |_PROCEDURE_LINKAGE_TABLE_ [49] | 66248| 0|OBJT |GLOB |0 |13 |_edata [44] | 66248| 0|OBJT |GLOB |0 |14 |_end [46] | 596| 0|OBJT |GLOB |0 |9 |_etext [50] | 496| 28|FUNC |GLOB |0 |6 |foo
从上面的测试结果可以看到使用 -M libfoo.map
后,符号 bar 被隐藏了。
7.2.3. AIX 系统(-bE:exportlist
)
AIX 系统中用来控制导出符号的选项是 -bE:exportlist
。
但是对应的“导出符号配置文件”的格式和 Linux/Solaris 不同。AIX 使用的格式更简单,把需要导出的符号一行行地列到文件中即可。如:
$ cat libfoo.exp foo
测试实例如下(源文件和前面实例相同):
$ xlc -c foo.c $ xlc -c bar.c $ xlc -G -o libfoo.so foo.o bar.o $ nm -g libfoo.so .bar T 268435840 .bar T 268435912 .foo T 268435776 .foo T 268435872 bar D 536871420 12 foo D 536871408 12 $ xlc -G -o libfoo.so foo.o bar.o -bE:libfoo.exp $ nm -g libfoo.so .bar T 268435840 .foo T 268435776 .foo T 268435872 foo D 536871368 12
从上面的测试结果可以看到使用 -bE:libfoo.exp
后,符号 bar 被隐藏了。
参考:
Compiling a shared library: http://www.ibm.com/support/knowledgecenter/SSGH2K_13.1.2/com.ibm.xlc131.aix.doc/proguide/compiling_shared_aix.html?lang=zh
7.3. 隐藏共享库内部符号的好处
It is useful because
- It prevents abuse of undocumented APIs of your library. Symbols that are not exported from the library cannot be used. This eliminates the problem that when the maintainer of the library changes internals of the library, maintainers of other projects cry "breakage". Instead, these maintainers are forced to negotiate the desired API from the maintainer of the library.
- It reduces the risk of symbol collision between your library and other libraries. For example, the symbol 'readline' is defined in several libraries, most of which don’t have the same semantics and the same calling convention as the GNU readline library.
- It reduces the startup time of programs linked to the library. This is because the dynamic loader has less symbols to process.
- It allows the compiler to generate better code. Within a shared library, a call to a function that is a global symbol costs a "call" instruction to a code location in the so-called PLT (procedure linkage table) which contains a "jump" instruction to the actual function’s code. (This is needed so that the function can be overridden, for example by a function with the same name in the executable or in a shared library interposed with LD_PRELOAD.) Whereas a call to a function for which the compiler can assume that it is in the same shared library is just a direct "call" instructions. Similarly for variables: A reference to a global variable fetches a pointer in the so-called GOT (global offset table); this is a pointer to the variable’s memory. So the code to access it is two memory load instructions. Whereas for a variable which is known to reside in the same shared library, it is just a direct memory access: one memory load instruction.
参考:https://www.gnu.org/software/gnulib/manual/html_node/Exported-Symbols-of-Shared-Libraries.html
7.4. 查看共享库的导出符号 (nm
)
在 Linux 中,可以用 nm -D /path/to/libfoo.so
或者 readelf -W --dyn-syms /path/to/libfoo.so
查看共享库的导出符号。
下面是在 Linux 中使用 nm
查看前面生成的 libfoo.so 的导出符号的例子:
$ nm -D libfoo.so w _Jv_RegisterClasses 0000000000200848 A __bss_start w __cxa_finalize w __gmon_start__ 0000000000200848 A _edata 0000000000200858 A _end 00000000000005c8 T _fini 0000000000000458 T _init 000000000000057c T bar 000000000000056c T foo
下面是在 MacOS 中使用 nm
查看共享库的导出符号的例子:
$ nm libfoo.dylib # MacOS 下不要使用 -D 选项,否则提示 File format has no dynamic symbol table 0000000000000fb0 T _bar 0000000000000f90 T _foo U dyld_stub_binder
下面是在 Linux 中使用 readelf
查看共享库的导出符号的例子:
$ readelf -s -W libfoo.so Symbol table '.dynsym' contains 12 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000458 0 SECTION LOCAL DEFAULT 9 2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses 4: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2 .5 (2) 5: 000000000000057c 6 FUNC GLOBAL DEFAULT 11 bar 6: 000000000000056c 16 FUNC GLOBAL DEFAULT 11 foo 7: 0000000000200858 0 NOTYPE GLOBAL DEFAULT ABS _end 8: 0000000000200848 0 NOTYPE GLOBAL DEFAULT ABS _edata 9: 0000000000200848 0 NOTYPE GLOBAL DEFAULT ABS __bss_start 10: 0000000000000458 0 FUNC GLOBAL DEFAULT 9 _init 11: 00000000000005c8 0 FUNC GLOBAL DEFAULT 12 _fini Symbol table '.symtab' contains 54 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000190 0 SECTION LOCAL DEFAULT 1 2: 00000000000001b8 0 SECTION LOCAL DEFAULT 2 3: 00000000000001f8 0 SECTION LOCAL DEFAULT 3 4: 0000000000000318 0 SECTION LOCAL DEFAULT 4 5: 000000000000038e 0 SECTION LOCAL DEFAULT 5 6: 00000000000003a8 0 SECTION LOCAL DEFAULT 6 7: 00000000000003c8 0 SECTION LOCAL DEFAULT 7 8: 0000000000000428 0 SECTION LOCAL DEFAULT 8 9: 0000000000000458 0 SECTION LOCAL DEFAULT 9 10: 0000000000000470 0 SECTION LOCAL DEFAULT 10 11: 00000000000004a0 0 SECTION LOCAL DEFAULT 11 12: 00000000000005c8 0 SECTION LOCAL DEFAULT 12 13: 00000000000005d8 0 SECTION LOCAL DEFAULT 13 14: 00000000000005f8 0 SECTION LOCAL DEFAULT 14 15: 0000000000200658 0 SECTION LOCAL DEFAULT 15 16: 0000000000200668 0 SECTION LOCAL DEFAULT 16 17: 0000000000200678 0 SECTION LOCAL DEFAULT 17 18: 0000000000200680 0 SECTION LOCAL DEFAULT 18 19: 0000000000200688 0 SECTION LOCAL DEFAULT 19 20: 0000000000200808 0 SECTION LOCAL DEFAULT 20 21: 0000000000200820 0 SECTION LOCAL DEFAULT 21 22: 0000000000200848 0 SECTION LOCAL DEFAULT 22 23: 0000000000000000 0 SECTION LOCAL DEFAULT 23 24: 00000000000004a0 0 FUNC LOCAL DEFAULT 11 call_gmon_start 25: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c 26: 0000000000200658 0 OBJECT LOCAL DEFAULT 15 __CTOR_LIST__ 27: 0000000000200668 0 OBJECT LOCAL DEFAULT 16 __DTOR_LIST__ 28: 0000000000200678 0 OBJECT LOCAL DEFAULT 17 __JCR_LIST__ 29: 00000000000004c0 0 FUNC LOCAL DEFAULT 11 __do_global_dtors_aux 30: 0000000000200848 1 OBJECT LOCAL DEFAULT 22 completed.6338 31: 0000000000200850 8 OBJECT LOCAL DEFAULT 22 dtor_idx.6340 32: 0000000000000540 0 FUNC LOCAL DEFAULT 11 frame_dummy 33: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c 34: 0000000000200660 0 OBJECT LOCAL DEFAULT 15 __CTOR_END__ 35: 0000000000000650 0 OBJECT LOCAL DEFAULT 14 __FRAME_END__ 36: 0000000000200678 0 OBJECT LOCAL DEFAULT 17 __JCR_END__ 37: 0000000000000590 0 FUNC LOCAL DEFAULT 11 __do_global_ctors_aux 38: 0000000000000000 0 FILE LOCAL DEFAULT ABS foo.c 39: 0000000000000000 0 FILE LOCAL DEFAULT ABS bar.c 40: 0000000000200820 0 OBJECT LOCAL DEFAULT ABS _GLOBAL_OFFSET_TABLE_ 41: 0000000000200680 0 OBJECT LOCAL DEFAULT 18 __dso_handle 42: 0000000000200670 0 OBJECT LOCAL DEFAULT 16 __DTOR_END__ 43: 0000000000200688 0 OBJECT LOCAL DEFAULT ABS _DYNAMIC 44: 000000000000057c 6 FUNC GLOBAL DEFAULT 11 bar 45: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 46: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses 47: 00000000000005c8 0 FUNC GLOBAL DEFAULT 12 _fini 48: 000000000000056c 16 FUNC GLOBAL DEFAULT 11 foo 49: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@@GLIBC_2. 2.5 50: 0000000000200848 0 NOTYPE GLOBAL DEFAULT ABS __bss_start 51: 0000000000200858 0 NOTYPE GLOBAL DEFAULT ABS _end 52: 0000000000200848 0 NOTYPE GLOBAL DEFAULT ABS _edata 53: 0000000000000458 0 FUNC GLOBAL DEFAULT 9 _init
比较新的 readelf 支持 --dyn-syms
选项来仅输出 .dynsym 中的符号,如:
$ readelf --dyn-syms -W libfoo.so Symbol table '.dynsym' contains 12 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000458 0 SECTION LOCAL DEFAULT 9 2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses 4: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2 .5 (2) 5: 000000000000057c 6 FUNC GLOBAL DEFAULT 11 bar 6: 000000000000056c 16 FUNC GLOBAL DEFAULT 11 foo 7: 0000000000200858 0 NOTYPE GLOBAL DEFAULT ABS _end 8: 0000000000200848 0 NOTYPE GLOBAL DEFAULT ABS _edata 9: 0000000000200848 0 NOTYPE GLOBAL DEFAULT ABS __bss_start 10: 0000000000000458 0 FUNC GLOBAL DEFAULT 9 _init 11: 00000000000005c8 0 FUNC GLOBAL DEFAULT 12 _fini
8. 参考
本文主要参考:
"The Linux Programming Interface" 41 Fundamentals of Shared Libraries
"The Linux Programming Interface" 42 Advanced Features of Shared Libraries