GNU Visibility Attribute

When going through the folly code base, sometimes, you see definitions like

class FOLLY_EXPORT OptionalEmptyException : public std::runtime_error { ...

FOLLY_EXPORT is defined as

#define FOLLY_EXPORT __attribute__((__visibility__("default")))

It sets the visibility attribute of symbol OptionalEmptyException to "default". What does it do exactly, and how it works?

Static Library

We will start with a very simply example. We have a file foo.cpp,

int foo(int x) { return x*x; }

And a simple main.cpp that calls foo. If we compile main.cpp and foo.cpp and inspect the object files,

clang++ -c main.cpp foo.cpp nm -C main.o 0000000000000000 T main U foo(int) nm -C foo.o 0000000000000000 T foo(int)

nm is a utility that displays symbols from object files. -C dismangles the symbols, so it's more readable ( foo(int) vs _Z3fooi).

Symbol foo is what we are interested in here. In main.o, foo is U (undefined). In foo.o, foo is T (a symbol in .text section and externally visible).

Now let’s add a visibility attribute to foo.

int __attribute__((visibility("hidden"))) foo(int x) { return x*x; }clang++ foo.cpp nm -C foo.o 0000000000000000 T foo(int)

foo is still public. If we make a binary out of main.o and foo.o, it will run just fine. This is because foo.o is a static library, with static linkage. foo is a "public" symbol as we statically link foo.o into our final binary.

Dynamic Library

So the visibility attribute doesn’t affect static library and it must affect dynamic library. We first build a shared library,

clang++ -shared -fpic foo.cpp -o libfoo.so

Now foo becomes a local symbol, hence not visible outside of the shared library itself.

nm -C libfoo.so 0000000000004020 b completed.0 w __cxa_finalize@@GLIBC_2.2.5 0000000000001020 t deregister_tm_clones 0000000000001090 t __do_global_dtors_aux 0000000000003e18 d __do_global_dtors_aux_fini_array_entry 0000000000004018 d __dso_handle 0000000000003e20 d _DYNAMIC 0000000000001100 t _fini 00000000000010e0 t frame_dummy 0000000000003e10 d __frame_dummy_init_array_entry 0000000000002050 r __FRAME_END__ 0000000000004000 d _GLOBAL_OFFSET_TABLE_ w __gmon_start__ 0000000000002000 r __GNU_EH_FRAME_HDR 0000000000001000 t _init w _ITM_deregisterTMCloneTable w _ITM_registerTMCloneTable 0000000000001050 t register_tm_clones 0000000000004020 d __TMC_END__ 00000000000010f0 t foo(int) # <--- notice the lower case t

Now if we try to link the object files together, clang would complain that it cannot find foo.

clang++ main.cpp libfoo.so /usr/bin/ld: /tmp/main-6da6a7.o: in function `main': main.cpp:(.text+0x15): undefined reference to `foo(int)' clang-10: error: linker command failed with exit code 1 (use -v to see invocation)

Usually __attribute__((visibility("default"))) is used in combination with -fvisibility=hidden linker flag. The latter would make "hidden" the default visibility for all symbols of a shared library. Back to the original example from folly,

class FOLLY_EXPORT OptionalEmptyException : public std::runtime_error { ...

Exceptions, if visible outside of the shared library itself, needs to be marked with default visibility explicitly (if -fvisibility=hidden is set). People usually set -fvisibility to reduce shared library size and load time.

How Dynamic Library Works

Dynamic library (a.k.a shared library) is lazily loaded at runtime. It has the benefit of sharing the dynamic library memory section across multiple processes. This means a binary that depends on a shared library is incomplete.

clang++ main.cpp libfoo.so objdump -D a.out -t -s -M intel

This is where foo gets called.

It looks up the Global Offset Table, where it would find out that it needs to load libfoo.so to resolve symbol foo. As expected, string “libfoo.so” is in the final binary.

Summary

To summarize, __attribute__((visibility("default"))) is often used in a shared library, and expected to be compiled with -fvisibility=hidden (though it doesn't have to). Symbols with "default" visibility will always be public outside of the shared library itself. You would often find it set on public interfaces of a shared library (including exceptions).

Originally published at https://blog.the-pans.com on September 20, 2020.

Software Engineer at Facebook working on cache infrastructure

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store