动态链接库中的.symtab和.dynsym

本文详细介绍了共享库(.so文件)的概念、命名约定、放置位置及加载方式,并深入探讨了共享库与符号表的关系,解释了为什么共享库需要符号表以及符号表是如何工作的。

shared library (.so)

"Program Library Howto-Shared Libraries"是很好的材料, 下面的内容多是据此整理的.

定义:
Shared libraries are libraries that are loaded by programs when they start.
使用shared library(共享库)会有很多好处, 比如软件升级, 不难想象.

命名约定:
1. soname: 每个共享库都有一个soname, 形式为"libNAME.so.x", 其中x是版本号. 如"libc.so.6".
2. real name: 真正的库文件, 一般形式为"soname.y[.z]", 即"libName.so.x.y[.z]", 其中y是minor number, z是release number, 是可选的. 如"libattr.so.1.1.0".
3. linker name: compiler用来请求库时使用的名字, 一般是没有版本号的soname.

放置位置 & load/preload:
共享库一般放在一些约定的目录下, 如/usr/lib/, /usr/local/lib, /lib/等. 这其实是遵循FHS的, 比如/usr/local/lib下放置的一般是用户开发的库.
在启动程序时, program loader(ld-linux.so.x)会找到并加载程序需要的共享库, loader查找的路径一般就是上述的几个目录, 这些目录在/etc/ld.so.conf文件中配置.
如果只想覆盖共享库的某几个函数, 保持其余函数不变, 则可以将共享库名字和函数名字输入到/etc/ld.so.preload中, 这里面定义的规则会覆盖标准规则.

cache arrangement & ldconfig
实际上, 在启动程序时再去搜寻所需的共享库不是高效做法, 所以loader使用了cache. ldconfig的作用就是读取文件/etc/ld.so.conf, 在各个库目录中, 对共享库设置合适的symbolic link(使得遵守命名约定), 然后写入某种数据到/etc/ld.so.cache, 这个文件再今后就被其他程序使用, 从而大幅提升了共享库的查找速度.
所以在每加入/移除一个共享库, 或者修改了/etc/ld.so.conf(即修改库目录)的时候, 最要运行ldconfig.

创建共享库
step1. 编译出object files, 需要使用-fPIC-fpic flag. fPIC和fpic的区别是, 前者生成的文件更大, 不过具有更好的平台无关性, 后者恰好相反. 这说明前者为了platform-independence做了更多工作.
step2. 用-Wl向linker传递参数. 如: "gcc -shared -Wl,-soname,libmystuff.so.1 -o libmystuff.so.1.0.1 a.o b.o -lc".
step3. 把共享库拷贝到约定的某个目录下即可, 如/usr/local/lib.
step4. ldconfig -n /path/to/lib.

elf

elf的内容参考"elf & libelf, elftoolchain", 它是一种格式,也是一种规范, 可以用libelf写程序去操作它, 可以用objdump、nm和readelf去读取elf文件的内容.

symbols

我也已经熟悉共享库了, 我知道ldconfig的作用, 我知道常用的库放置目录, 我知道ltrace, ldd可以用来帮助确认某程序和某些共享库的关联关系是否正确.
所以, 如果没有symbols这一节, 本篇文章存在的意义不大.

"Inside ELF Symbol Tables"是绝佳的资料, 当然正如很多网文一样, 它仅是帮助理解, 而不涉及很深的细节. 细节标准什么的还是要看书和文档了, 这方面很不错的书籍就是校友的<程序员的自我修养>了.

查看elf规范, 你必然可以看到symtab和dynsym, 如"ELF-64 Object File Format"中"4.Sections"就列出了标准的sections, .symtab和.dynsym就是其中之二.
实际上, 我们知道机器可执行的是machine code, 而我们使用的高级语言编程, 并不是利用晦涩的机器码, 而是用human-readable的变量名, 函数名等, 这些名字就是symbolic name. 编译器在编译时收集symbol信息, 并储存在object file的.symtab和.dynsym中. symbols是linkerdebugger所必需的信息, 如果没有symbols, 试想debugger如何能展示给用户调试信息了? 如果没有symbol, 而只有地址(相对于object file的offset), linker如何能够链接多个object file了?
对于linker和symbol, 我们可以做个小实验:

// 编写一个简单的 a.c
$ cat a.c
void func(void)
{
        printf("call func()\n");
}

$ nm a.o
00000000 T func
         U puts

// 编写一个简单的 main.c
$ cat main.c
#include <stdio.h>
extern void func(void);
int main(
{
        func();
        return 0;
}

$ nm main.o
         U func
         00000000 T main

// 正常情况下
$ gcc main.o a.o -o main
$ ./main
call func()

// 为了验证symbol对于linker来说是必需品, 我做如下操作
$ file a.o
a.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
$ strip a.o
$ file a.o
a.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), stripped
$ gcc main.o a.o -o main
main.o: In function `main':
/home/xan/lab/main.c:7: undefined reference to `func'
collect2: ld returned 1 exit status

这个小实验证实了symbols对于linker的重要性, 同时使用file看出"not stripped"->"stripped"的变化, 说的就是去除了symbols信息.

现在假使我们生成了最后的可执行文件(当然是elf格式了), 那么这个elf中是否包含symbols呢? 其中又是否需要symbols呢?
不妨先下结论: 一般地, 生成的可执行文件都是包含symbols, 不过这些信息不是程序执行所必需的, 可以通过strip(Discard symbols from object files)去除.
同样可以做个小实验:

// 仍用上面实验的代码
$ file main
main: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, not stripped
$ ./main
call func()
$ strip main
$ file main
main: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped
$ ./main
call func()
$ 

这个小实验证实了symbols对于可执行文件来说不是必需的, 这是因为可执行的代码都是machine code, 只需要address信息, 无需symbol信息.

对于elf和symbols, 还是好理解的啦. 就是我elf文件中留了一席之地给你放symbols, compiler在生成elf时会往其中填充. debugger/nm/readelf等可以来读取. 不过这些symbols不是程序执行必需的, 所以完全可以去除, 只不过去除之后, debugger就读不到信息了.

而对于共享库来说, 情况略复杂些了. 我们来特别说明.

共享库和symbols

在继续下去之前, 先来看两个事实.

$ ldd /bin/ls
        linux-gate.so.1 =>  (0xb7711000)
        libselinux.so.1 => /lib/libselinux.so.1 (0xb76e5000)
        librt.so.1 => /lib/i686/cmov/librt.so.1 (0xb76dc000)
        libacl.so.1 => /lib/libacl.so.1 (0xb76d4000)
        libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb758d000)
        libdl.so.2 => /lib/i686/cmov/libdl.so.2 (0xb7589000)
        /lib/ld-linux.so.2 (0xb7712000)
        libpthread.so.0 => /lib/i686/cmov/libpthread.so.0 (0xb7570000)
        libattr.so.1 => /lib/libattr.so.1 (0xb756b000)
$ nm /lib/i686/cmov/libc.so.6 
nm: /lib/i686/cmov/libc.so.6: no symbols    --> libattr, libacl也一样, 都显示"no symbols"

// 而libpthread有一大串
$ nm /lib/i686/cmov/libpthread.so.0 | tail
        U twalk@@GLIBC_2.0
        U uname@@GLIBC_2.0
        U unlink@@GLIBC_2.0
0000c930 t unwind_cleanup
0000c970 t unwind_stop
0000cfd0 W vfork
0000de90 W wait
0000df50 W waitpid
0000c140 t walker
0000d020 W write

这仅仅是因为有些库做了strip, 而其他库没做strip而已? 还是说对于某些共享库来说, symbols也是必需的?
目前不知答案, 分析下去.

前面提到.symtab和.dynsym两个不同的symbol table, 它们有什么区别?
.dynsym是.symtab的一个子集, 大家都有疑问, 为什么要两个信息重合的结构?
需要先了解allocable/non-allocable ELF section, ELF文件包含一些sections(如code和data)是在运行时需要的, 这些sections被称为allocable; 而其他一些sections仅仅是linker,debugger等工具需要, 在运行时并不需要, 这些sections被称为non-allocable的. 当linker构建ELF文件时, 它把allocable的数据放到一个地方, 将non-allocable的数据放到其他地方. 当OS加载ELF文件时, 仅仅allocable的数据被映射到内存, non-allocable的数据仍静静地呆在文件里不被处理. strip就是用来移除某些non-allocable sections的.
.symtab包含大量linker,debugger需要的数据, 但并不为runtime必需, 它是non-allocable的; .dynsym包含.symtab的一个子集, 比如共享库所需要在runtime加载的函数对应的symbols, 它世allocable的.

因此, 得到答案:
1. strip移除的应是.symtab.
2. nm读取的应是.symtab: 上面发现的libattr等nm结果为空, libpthread nm结果非空应是正常的. 3. 共享库包含的.dynsym是runtime必需的, 是allocable的.

可做验证, 期望的结果为:
1. strip libpthread, ls依然能够工作.
2. strip libpthread, nm libpthread得到结果为空.
3. 可以通过设置nm options, 或使用readelf读出.dynsym的内容.

$ sudo strip /lib/i686/cmov/libpthread-2.11.3.so
$ file /lib/i686/cmov/libpthread-2.11.3.so
/lib/i686/cmov/libpthread-2.11.3.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped
$ ls
...(输出正确结果)

$ nm /lib/i686/cmov/libpthread-2.11.3.so
nm: /lib/i686/cmov/libpthread-2.11.3.so: no symbols

$ readelf -s /lib/i686/cmov/libpthread-2.11.3.so | tail
322: 0000b6a0   292 FUNC    GLOBAL DEFAULT   13 __pthread_clock_gettime@@GLIBC_PRIVATE
323: 0000ec30    46 FUNC    GLOBAL DEFAULT   13 pthread_mutex_consistent_@@GLIBC_2.4
324: 0000b3a0    50 FUNC    GLOBAL DEFAULT   13 pthread_testcancel@@GLIBC_2.0
325: 0000d6b0   111 FUNC    WEAK   DEFAULT   13 fsync@@GLIBC_2.0
326: 0000d1f0   180 FUNC    WEAK   DEFAULT   13 fcntl@@GLIBC_2.0
327: 0000dde0   176 FUNC    WEAK   DEFAULT   13 tcdrain@@GLIBC_2.0
328: 00009390     7 FUNC    GLOBAL DEFAULT   13 pthread_mutexattr_destroy@@GLIBC_2.0
329: 00006de0    23 FUNC    GLOBAL DEFAULT   13 pthread_yield@@GLIBC_2.2
330: 000077c0   259 FUNC    GLOBAL DEFAULT   13 pthread_mutex_init@@GLIBC_2.0
331: 000093c0    49 FUNC    GLOBAL DEFAULT   13 pthread_mutexattr_setpsha@@GLIBC_2.2

$ readelf -s /lib/libattr.so.1.1.0 | tail
48: 00002f50    50 FUNC    GLOBAL DEFAULT   13 lremovexattr@@ATTR_1.0
49: 00003010    57 FUNC    GLOBAL DEFAULT   13 llistxattr@@ATTR_1.0
50: 00002ae0    50 FUNC    GLOBAL DEFAULT   13 attr_copy_check_permissio@@ATTR_1.1
51: 00001b50   259 FUNC    GLOBAL DEFAULT   13 attr_set@@ATTR_1.0
52: 00002b20  1002 FUNC    GLOBAL DEFAULT   13 attr_copy_action
53: 000031f0    71 FUNC    GLOBAL DEFAULT   13 setxattr@@ATTR_1.0
54: 00001380   543 FUNC    GLOBAL DEFAULT   13 attr_list@@ATTR_1.2
55: 000030d0    64 FUNC    GLOBAL DEFAULT   13 lgetxattr@@ATTR_1.0
56: 00002fd0    57 FUNC    GLOBAL DEFAULT   13 flistxattr@@ATTR_1.0
57: 00002f10    50 FUNC    GLOBAL DEFAULT   13 fremovexattr@@ATTR_1.0

至此, 对symbols和共享库,ELF的关系的了解告一段落.

more

既然已经说到共享库(shared library), 不妨稍微提一下动态装载库(Dynamically Loaded Libraries), 共享库是在程序startup时被加载, 而DLL(注意区别于windows下的概念)则是在程序运行过程中显式被加载, 实际上就是调用dlopen,dlsym等接口显式地打开共享库, 显示地查找库中的symbol, 然后找到对应的代码去执行.

How To Write Shared Libraries by Ulrich Drepper.

<think>我们正在回答用户关于如何查看Linux系统中.so文件的符号表的问题。 根据引用[1],ELF文件有两个符号表:动态符号表(用于运行时查找符号)主符号表(包含所有符号,包括链接时使用的静态符号信息)。 用户可能想要查看的是动态符号表或主符号表,或者两者之一。常见的工具是nmobjdump。 步骤: 1. 使用nm命令:可以列出目标文件中的符号。对于动态库(.so),我们可以使用nm -D来查看动态符号表,或者不使用-D查看所有符号(包括静态符号)。 2. 使用objdump命令:可以显示目标文件的详细信息,包括符号表。使用objdump -T可以查看动态符号表,使用objdump -t可以查看所有符号(包括静态符号)。 3. 使用readelf命令:这是一个专门用于ELF文件格式的工具,可以显示符号表。使用readelf -s可以显示符号表部分的内容(包括动态符号表主符号表)。 注意:动态符号表是运行时需要的,而主符号表通常包含更多调试信息,可能在发布版本中被去除(strip)。 根据引用[1]中的说明,动态符号表是运行时使用的,主符号表是链接时使用的。所以用户可能根据不同的目的需要查看不同的符号表。 具体命令示例: 1. 查看动态符号表(类似于运行时可见的符号): nm -D libexample.so objdump -T libexample.so readelf -s libexample.so # 这个命令会同时显示两个符号表,需要区分,动态符号表通常位于.dynsym节,主符号表在.symtab节。 2. 查看所有符号(包括静态符号,即主符号表): nm libexample.so objdump -t libexample.so readelf -s libexample.so # 同样,这个命令会显示所有符号表。 3. 如果只想查看动态符号表,可以使用readelf并指定动态符号表节: readelf --dyn-syms libexample.so 另外,如果用户想要查看某个特定符号,可以使用grep过滤。 注意:如果.so文件被strip过,那么主符号表可能已经被移除,此时nm(不加-D)可能看不到任何符号,而动态符号表(因为运行时需要)通常会被保留。 因此,我们通常建议: - 查看动态符号表(运行时可见的符号):使用 `nm -D` 或 `objdump -T` - 查看所有符号(包括静态符号,如果有的话):使用 `nm` 或 `objdump -t` 或 `readelf -s` 另外,引用[3]提到,系统可能有不同版本的库,所以确保你查看的是你想要的库版本。 示例: 假设我们有一个动态库文件:libtest.so 查看动态符号表: nm -D libtest.so 查看所有符号(如果未被strip): nm libtest.so 使用objdump查看动态符号表: objdump -T libtest.so 使用readelf查看动态符号表: readelf --dyn-syms libtest.so 如果用户想要查看函数名(函数符号)或者变量名,这些命令都会显示出来。符号的类型也会显示(比如T表示代码段的符号,D表示初始化数据段的符号,U表示未定义符号等)。 最后,根据引用[2],符号表是由编译器在编译过程中构建的,所以如果用户想要查看自己编译的库,这些符号信息通常都在,除非被strip掉。 因此,我们给出以下步骤: 步骤1:确定要查看的.so文件路径。 步骤2:根据需求选择查看动态符号表还是全部符号。 步骤3:使用上述命令之一进行查看。 注意:如果系统没有安装这些工具,可能需要安装binutils包(包含nm, objdump, readelf等)。 在Ubuntu/Debian上安装: sudo apt-get install binutils 在CentOS/RHEL上安装: sudo yum install binutils 如果用户需要更详细的信息(如符号的地址、大小等),可以使用readelf -s,它会显示更丰富的信息。 示例输出解释(以nm为例): 0000000000000a60 T func1 0000000000000a80 T func2 U malloc 这里,T表示该符号在文本(代码)段中定义,U表示未定义(需要外部链接),等等。 因此,我们可以这样回答用户:</think>在 Linux 系统中查看 `.so` 文件(动态共享库)的符号表,可以使用以下工具方法: --- ### 1. **使用 `nm` 命令(最常用)** `nm` 用于列出目标文件的符号表,支持查看动态符号表静态符号表: ```bash # 查看所有符号(包括静态符号) nm your_library.so # 仅查看动态符号表(运行时可见的符号) nm -D your_library.so ``` **输出示例**: ``` 0000000000003e40 T _init U malloc@GLIBC_2.2.5 0000000000004a20 T my_exported_function ``` - **符号类型说明**: - `T`:代码段定义的符号(函数) - `U`:未定义的符号(需要外部链接) - `D`:已初始化的全局变量 - `t`:局部函数(static 作用域) --- ### 2. **使用 `objdump` 命令** `objdump` 提供更详细的底层信息: ```bash # 查看动态符号表 objdump -T your_library.so # 查看所有符号(包括静态符号) objdump -t your_library.so ``` **输出特点**: - 显示符号的虚拟地址、类型绑定信息(如 `DF *UND*` 表示未定义的动态符号)。 --- ### 3. **使用 `readelf` 命令(ELF 文件专用)** 适合查看 ELF 文件格式的详细信息: ```bash # 查看动态符号表(.dynsym 节) readelf -s --dyn-syms your_library.so # 查看所有符号表(包括 .symtab 静态符号) readelf -s your_library.so ``` **输出优势**: - 明确区分 `.dynsym`(动态符号表) `.symtab`(主符号表)[^1]。 - 显示符号大小、类型所在节区(如 `FUNC`、`OBJECT`)。 --- ### 4. **过滤特定符号** 结合 `grep` 快速查找目标符号: ```bash nm -D your_library.so | grep "my_function" objdump -T your_library.so | grep "GLIBC" ``` --- ### 关键注意事项: 1. **符号表类型**: - **动态符号表(`.dynsym`)**:运行时必需的符号(通过 `nm -D` 或 `objdump -T` 查看)。 - **主符号表(`.symtab`)**:包含所有符号(调试/链接用),可能被 `strip` 命令移除[^1]。 2. **调试信息**: - 若符号表为空,库文件可能被 `strip` 处理过。需使用未裁剪的版本。 3. **依赖项检查**: - 使用 `ldd your_library.so` 确认依赖的库是否可用,避免符号解析失败。 --- ### 示例流程 ```bash # 查看 libc.so 的动态符号表(仅导出函数) nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep "malloc" # 输出:0000000000097c90 T malloc ``` 表示 `malloc` 是动态库中已定义的导出函数。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值