1. Overview

In this tutorial, we’ll learn about exported symbols in Linux shared libraries and how we can view them.

2. Exported Symbols in Shared Libraries

External programs can only use exported symbols from shared libraries.

Let’s demonstrate this with an example. First, let’s create a shared library called lib.so and export symbols from it:

$ 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

We used gcc with the -shared flag to output a shared library. Here, the function lib_private is marked as a static function and will not be exported as static functions can’t be accessed outside of the file they are present in.

Now, let’s try to link to the private symbol:

$ 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

As we can see, we can’t link to unexported private symbols.

Additionally, symbol names can be mangled if the library is written in C++. This means that symbol names like lib_exported1 might appear as _Z13lib_exported1v. Most utilities can deal with these symbols with special flags.

3. Listing Exported Symbols of a Shared Library

Now, let’s learn how to view the exported symbols of a library with the help of the library created in the above example.

3.1. Using readelf

We can use the readelf command with the -s flag to view exported symbols:

$ 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
...

Here, we can see the lib_exported1 and lib_exported2 functions, but not the private lib_private function. The other symbols like puts belong to the C library, i.e., glibc. readelf does not support de-mangling of symbol names.

3.2. Using objdump

We can also use the objdump command with the -T flag to view exported symbols:

$ 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

Let’s compile our library as C++ and see how objdump handles mangled symbols:

$ 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

It doesn’t demangle symbols by default, so we must pass the –demangle flag:

$ 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. Using nm

Finally, we can also use the nm command with the -D flag to view exported symbols. It can demangle names with the –demangle flag just like objdump:

$ 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()

4. Conclusion

In this article, we learned about exported and private symbols in a shared library. We also covered the various commands used for viewing exported symbols.