Contents

Linux查看共享库导出的函数列表

1. 概述

在本教程中,我们将了解 Linux 共享库中的导出符号以及如何查看它们。

2. 共享库中的导出符号

外部程序只能使用从共享库中导出的符号。

让我们用一个例子来证明这一点。首先,让我们创建一个名为lib.so的共享库并从中导出符号:

$ cat lib.c 
#include <stdio.h>
void lib_exported1(void) {
    printf("Hello, this is an exported symbol\n");
}
void lib_exported2(void) {
    printf("Hello, this is another exported symbol\n");
}
static void lib_private(void) {
    printf("This function is static and can't be used from outside\n");
}
$ gcc lib.c -shared -o lib.so

我们使用带有*-shared标志的gcc 来输出一个共享库。**在这里,函数lib_private被标记为static函数并且不会被导出,因为static*函数无法在它们所在的文件之外访问。**

现在,让我们尝试链接到私有符号:

$ cat program.c 
// Forward declarations
void lib_exported1(void);
void lib_exported2(void);
void lib_private(void);
int main(void) {
    lib_exported1();
    lib_exported2();
    lib_private();
}
$ cc program.c lib.so
/usr/bin/ld: /tmp/ccfZyf8j.o: in function `main':
program.c:(.text+0xf): undefined reference to `lib_private'
collect2: error: ld returned 1 exit status

如我们所见,我们无法链接到未导出的私有符号。

此外,如果库是用 C++ 编写的,符号名称可能会被破坏。这意味着像lib_exported1这样的符号名称可能显示为_Z13lib_exported1v。大多数实用程序可以使用特殊标志处理这些符号。**

3. 列出共享库的导出符号

现在,让我们学习如何借助上面示例中创建的库来查看库的导出符号。

3.1. 使用readelf

我们可以使用带有*-s*标志的readelf 命令来查看导出的符号:

$ readelf -s lib.so
Symbol table '.dynsym' contains 8 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterT[...]
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5 (2)
     3: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     4: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMC[...]
     5: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND [...]@GLIBC_2.2.5 (2)
     6: 000000000000111f    22 FUNC    GLOBAL DEFAULT   12 lib_exported2
     7: 0000000000001109    22 FUNC    GLOBAL DEFAULT   12 lib_exported1
...

在这里,我们可以看到lib_exported1lib_exported2函数,但看不到私有lib_private函数。其他符号如puts属于 C 库,即glibcreadelf不支持符号名称的反修饰。

3.2. 使用objdump

我们还可以使用带有*-T*标志的objdump 命令来查看导出的符号:

$ objdump -T lib.so
lib.so:     file format elf64-x86-64
DYNAMIC SYMBOL TABLE:
0000000000000000  w   D  *UND*	0000000000000000  Base        _ITM_deregisterTMCloneTable
0000000000000000      DF *UND*	0000000000000000  GLIBC_2.2.5 puts
0000000000000000  w   D  *UND*	0000000000000000  Base        __gmon_start__
0000000000000000  w   D  *UND*	0000000000000000  Base        _ITM_registerTMCloneTable
0000000000000000  w   DF *UND*	0000000000000000  GLIBC_2.2.5 __cxa_finalize
000000000000111f g    DF .text	0000000000000016  Base        lib_exported2
0000000000001109 g    DF .text	0000000000000016  Base        lib_exported1

让我们将我们的库编译为 C++,看看objdump如何处理损坏的符号:

$ g++ lib.c -shared -o lib.so
$ objdump -T lib.so
lib.so:     file format elf64-x86-64
DYNAMIC SYMBOL TABLE:
...
0000000000001109 g    DF .text	0000000000000016  Base        _Z13lib_exported1v
000000000000111f g    DF .text	0000000000000016  Base        _Z13lib_exported2v

默认情况下它不会分解符号,所以我们必须传递–demangle*标志:*

$ objdump -T --demangle lib.so
lib.so:     file format elf64-x86-64
DYNAMIC SYMBOL TABLE:
...
0000000000001109 g    DF .text	0000000000000016  Base        lib_exported1()
000000000000111f g    DF .text	0000000000000016  Base        lib_exported2()

3.3. 使用nm

最后,我们还可以使用带有-D标志的nm 命令来查看导出的符号。它可以像objdump一样使用*–demangle*标志来分解名称:**

$ nm -D --demangle lib.so
                 w __cxa_finalize@GLIBC_2.2.5
                 w __gmon_start__
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
                 U puts@GLIBC_2.2.5
0000000000001109 T lib_exported1()
000000000000111f T lib_exported2()