nm、readelf和共享库

本文探讨了Linux系统下.so库的符号表通常不在自身文件中,而是存储在.a档案中。通过readelf命令可以查看共享库的依赖和搜索名。实验表明,nm输出的符号信息与.so文件的符号表不同。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

逛论坛时 有人说

现在linux系统里的.so大部分不是用gcc -shared生成的,本身不包含符号表,而符号表是保存在一个.a文件里的,链接时只需要那个.a就可以了,类似Windows那样。

使用方法1:

查看共享库的依赖库(NEEDED)和搜索名(SONAME)。

readelf -d <file_name>

例如:

#readelf -d libuClibc-0.9.30rc2.so 

第一次听说这样的事,我也试试:


$nm /usr/lib/libm.so
nm: /usr/lib/libm.so: no symbols


果然如此,上网查了查,有人说是因为/usr/lib/libm.so是符号链接,找到真正的文件一试还是这样,其余符号链接和真实文件读取上没有什么差别。
又找了找,发现了一个不小的问题。

$ readelf -s /usr/lib/libm.so
    ...
    35: 000092c0    80 FUNC    WEAK   DEFAULT   12 sin@@GLIBC_2.0
    36: 00010530   125 FUNC    WEAK   DEFAULT   12 acosf@@GLIBC_2.0
    37: 000094d0   124 FUNC    WEAK   DEFAULT   12 acosh@@GLIBC_2.0
    38: 00013a00    15 FUNC    WEAK   DEFAULT   12 fmaf@@GLIBC_2.1
    39: 00017c10   125 FUNC    WEAK   DEFAULT   12 acosl@@GLIBC_2.0
    40: 0000b570   346 FUNC    WEAK   DEFAULT   12 catan@@GLIBC_2.1
    ...


使用readelf读取符号表完全正常,这又是怎么回事?

其余问题已经很明显了,nm输出的符号信息和.so的符号表完全是两回事。我们可以做个实验:


#include <iostream>
using namespace std;
void fun()
{
    cout << "fun" << endl;
}
int main()
{
    fun();
    return 0;
}


这是一个很简单的c++程序(c++.cpp)。


$ g++ c++.cpp && ll a.out
-rwxr-xr-x 1 osily osily 6085 Dec  7 18:47 a.out*


分别用nm和readelf分析(篇幅所限就不列出来了):

osily@ly50247:/tmp
$ nm a.out|wc -l
45
osily@ly50247:/tmp
$ nm a.out|grep fun
080486b4 t _GLOBAL__I__Z3funv
08048634 T _Z3funv
osily@ly50247:/tmp
$ readelf -s a.out|wc -l
96
osily@ly50247:/tmp
$ readelf -s a.out|grep fun
    46: 080486b4    28 FUNC    LOCAL  DEFAULT   13 _GLOBAL__I__Z3funv
    65: 08048634    44 FUNC    GLOBAL DEFAULT   13 _Z3funv


可以看出二者都有许多符号信息,也都有fun函数。
现在我找a.out strip一下:

osily@ly50247:/tmp
$ strip a.out
osily@ly50247:/tmp
$ nm -a a.out
nm: a.out: no symbols
osily@ly50247:/tmp
$ readelf -s a.out|wc -l
15
osily@ly50247:/tmp
$ readelf -s a.out
Symbol table '.dynsym' contains 12 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 00000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_atexit@GLIBC_2.1.3 (2)
     2: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     3: 00000000     0 NOTYPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
     4: 00000000     0 FUNC    GLOBAL DEFAULT  UND _ZNSt8ios_base4InitC1Ev@GLIBCXX_3.4 (3)
     5: 00000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.0 (4)
     6: 00000000     0 FUNC    GLOBAL DEFAULT  UND _ZStlsISt11char_traitsIcE@GLIBCXX_3.4 (3)
     7: 00000000     0 FUNC    GLOBAL DEFAULT  UND _ZNSolsEPFRSoS_E@GLIBCXX_3.4 (3)
     8: 0804878c     4 OBJECT  GLOBAL DEFAULT   15 _IO_stdin_used
     9: 08048568     0 FUNC    GLOBAL DEFAULT  UND _ZSt4endlIcSt11char_trait@GLIBCXX_3.4 (3)
    10: 08048538     0 FUNC    GLOBAL DEFAULT  UND _ZNSt8ios_base4InitD1Ev@GLIBCXX_3.4 (3)
    11: 080499a0   140 OBJECT  GLOBAL DEFAULT   25 _ZSt4cout@GLIBCXX_3.4 (3)


现在nm表示压力很大,已经无符号信息可输出。而readelf仍可以输出一些,但其中并没有fun函数。
再看下这个:

osily@ly50247:/tmp
$ g++ -c c++.cpp
osily@ly50247:/tmp
$ strip c++.o
osily@ly50247:/tmp
$ g++ c++.o
/usr/bin/ld: error in c++.o(.eh_frame); no .eh_frame_hdr table will be created.
/usr/lib/gcc/i686-pc-linux-gnu/4.5.1/../../../crt1.o: In function `_start':
(.text+0x18): undefined reference to `main'
collect2: ld returned 1 exit status


这说明nm输出的许多符号信息是用于链接的,而不是运行的。还有如果用g++ -g编译的话用nm -a还会读到调试信息。
再看这个:

osily@ly50247:/tmp
$ g++ -shared c++.cpp
osily@ly50247:/tmp
$ strip a.out
osily@ly50247:/tmp
$ readelf -s a.out
Symbol table '.dynsym' contains 18 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 00000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_atexit@GLIBC_2.1.3 (2)
     2: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     3: 00000000     0 NOTYPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
     4: 00000000     0 FUNC    GLOBAL DEFAULT  UND _ZNSt8ios_base4InitC1Ev@GLIBCXX_3.4 (3)
     5: 00000000     0 FUNC    GLOBAL DEFAULT  UND _ZNSt8ios_base4InitD1Ev@GLIBCXX_3.4 (3)
     6: 00000000     0 FUNC    GLOBAL DEFAULT  UND _ZStlsISt11char_traitsIcE@GLIBCXX_3.4 (3)
     7: 00000000     0 OBJECT  GLOBAL DEFAULT  UND _ZSt4cout@GLIBCXX_3.4 (3)
     8: 00000000     0 FUNC    GLOBAL DEFAULT  UND _ZNSolsEPFRSoS_E@GLIBCXX_3.4 (3)
     9: 00000000     0 FUNC    GLOBAL DEFAULT  UND _ZSt4endlIcSt11char_trait@GLIBCXX_3.4 (3)
    10: 00000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@GLIBC_2.1.3 (2)
    11: 00001950     0 NOTYPE  GLOBAL DEFAULT  ABS _end
    12: 00001944     0 NOTYPE  GLOBAL DEFAULT  ABS _edata
    13: 0000065c    44 FUNC    GLOBAL DEFAULT   11 _Z3funv
    14: 00001944     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start
    15: 00000688    20 FUNC    GLOBAL DEFAULT   11 main
    16: 0000053c     0 FUNC    GLOBAL DEFAULT    9 _init
    17: 00000738     0 FUNC    GLOBAL DEFAULT   12 _fini


把c++.cpp编译链接成.so,strip,readelf仍然能发现fun函数。
所以一个.so的导出函数表不是用nm看的,而系统里的.so只是strip一下,可平时自己弄大没有太大的区别。
<think>我们正在讨论Linux中的readelf命令。readelf是一个用于显示ELF(ExecutableandLinkableFormat)文件信息的工具,常用于分析可执行文件、共享库、目标文件等。以下是一些基本用法示例:###基本用法readelf命令的基本语法是:```bashreadelf[选项]<elf文件>```###常用选项1.**-h**或**--file-header**:显示ELF文件头信息。```bashreadelf-h/bin/ls```2.**-S**或**--sections**:显示节头表(sectionheaders)信息。```bashreadelf-Sprogram.o```3.**-s**或**--symbols**:显示符号表(symboltable)信息。```bashreadelf-slibc.so```4.**-l**或**--program-headers**:显示程序头表(programheaders)信息(用于可执行文件共享库)。```bashreadelf-la.out```5.**-d**或**--dynamic**:显示动态段(dynamicsection)信息(用于动态链接的可执行文件或共享库)。```bashreadelf-d/bin/bash```6.**-r**或**--relocs**:显示重定位信息。```bashreadelf-rmodule.o```7.**-a**或**--all**:显示所有信息(相当于多个选项的组合)。```bashreadelf-aexecutable```###示例1.**查看文件头**:```bashreadelf-h/bin/ls```输出将包括ELF文件的类型(如可执行文件、共享库等)、机器架构、入口点地址、节头表程序头表的位置大小等信息。2.**查看节头表**:```bashreadelf-Sprogram.o```输出将列出目标文件中的所有节(如.text、.data、.bss等)及其大小、地址、偏移量等。3.**查看符号表**:```bashreadelf-slibc.so```输出将显示共享库中定义引用的符号,包括函数变量。4.**查看动态段**:```bashreadelf-d/bin/bash```输出将显示动态链接所需的信息,如依赖的共享库(NEEDED)、重定位信息等。###注意事项-readelf只能用于ELF格式的文件,对于非ELF文件(如a.out格式)则无法使用。-使用readelf时,通常需要指定一个或多个选项来查看特定的信息。通过readelf,开发者系统管理员可以深入了解ELF文件的结构内容,这对于调试、链接二进制分析非常有用。###相关问题1.readelfobjdump命令有什么区别?2.如何解析ELF文件中的重定位条目?3.在Linux中如何查看一个可执行文件依赖哪些共享库?</think>### Linux readelf 命令详解 `readelf` 是 Linux 中用于分析 ELF (Executable and Linkable Format) 格式文件的工具,可查看可执行文件、目标文件或共享库的底层结构信息。 #### 1. 基础语法 ```bash readelf [选项] <文件名> ``` #### 2. 常用选项及示例 - **查看文件头信息** (`-h`) 显示 ELF 文件的基本架构入口点: ```bash readelf -h /bin/ls ``` 输出示例: ``` ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Entry point address: 0x5b20 ... [其他元数据] ``` - **查看节头表** (`-S`) 显示所有节区(section)信息: ```bash readelf -S program.o ``` 输出包含 `.text`(代码)、`.data`(初始化数据)、`.symtab`(符号表) 等节区的偏移量/大小。 - **查看符号表** (`-s`) 列出所有函数/全局变量符号: ```bash readelf -s libfoo.so ``` 输出包含符号名称、类型(FUNC/OBJECT)、绑定信息(LOCAL/GLOBAL)等。 - **查看动态段信息** (`-d`) 分析共享库依赖动态链接信息: ```bash readelf -d /usr/bin/bash ``` 关键输出: ``` Dynamic section at offset 0x2dfe8 contains 28 entries: Tag Type Name/Value 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] ... ``` - **查看重定位条目** (`-r`) 显示需要重定位的符号: ```bash readelf -r module.o ``` - **完整分析** (`-a`) 显示所有可用信息(头/节/段/符号等): ```bash readelf -a executable ``` #### 3. 典型应用场景 - **调试链接问题**:通过符号表检查未定义引用 - **分析二进制结构**:验证编译器优化效果(如节区大小) - **安全审计**:检查可疑的节区(如注入的代码) - **跨平台兼容性**:确认文件架构(ELF32/ELF64) - **动态依赖检查**:查看 `NEEDED` 字段确认共享库依赖[^1] #### 4. 与类似工具对比 | 工具 | 特点 | |-------------|-------------------------------| | `readelf` | 专精ELF结构,无依赖外部库 | | `objdump` | 支持反汇编,但依赖BDF库 | | `nm` | 仅查看符号表 | | `ldd` | 仅检查动态依赖 | > 提示:处理剥离( stripped )的二进制时,使用 `-s` 可能返回空符号表,此时需结合调试信息分析。 ### 相关问题 1. 如何解析 ELF 文件中的动态链接器路径? 2. readelf 如何帮助诊断 "undefined symbol" 链接错误? 3. ELF 文件中的 `.got` `.plt` 节区有什么作用? 4. 如何通过 readelf 判断一个可执行文件是否启用了 PIE(位置无关可执行文件)? [^1]: 动态段中的 `NEEDED` 字段直接对应 `ldd` 的输出结果
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值