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.