[LINUX] Limit public symbols in shared libraries

Introduction

When developing a shared library, You will want to limit public symbols to prevent misuse and to improve performance.

If you simply set it to file scope, you cannot call it from another file even in the library. Also, in the case of C ++, even if it is a private method, it will be exposed to the outside as a symbol.

Therefore, I will introduce three methods to control the symbols exposed to the outside of the shared library.

-[Method 1](https://qiita.com/takeoverjp/items/d86fba2843faac955d4b#%E6%96%B9%E6%B3%951-ld-version-script%E3%82%92%E4%BD%BF % E3% 81% A3% E3% 81% A6% E5% 85% AC% E9% 96% 8B% E9% 96% A2% E6% 95% B0% E3% 82% 92% E3% 83% AA% E3 % 82% B9% E3% 83% 88% E3% 82% A2% E3% 83% 83% E3% 83% 97% E3% 81% 99% E3% 82% 8B): [LD version script](https:: Use //www.gnu.org/software/gnulib/manual/html_node/LD-Version-Scripts.html) to list public functions -[Method 2](https://qiita.com/takeoverjp/items/d86fba2843faac955d4b#%E6%96%B9%E6%B3%952-%E9%9D%9E%E5%85%AC%E9%96% 8B% E9% 96% A2% E6% 95% B0% E3% 81% AB__visibility__hidden% E3% 82% 92% E6% 8C% 87% E5% AE% 9A% E3% 81% 99% E3% 82% 8B) : Specify __visibility __ ("hidden") for private function -[Method 3](https://qiita.com/takeoverjp/items/d86fba2843faac955d4b#%E6%96%B9%E6%B3%953-%E3%83%87%E3%83%95%E3%82% A9% E3% 83% AB% E3% 83% 88% E3% 82% 92% E9% 9D% 9E% E5% 85% AC% E9% 96% 8B% E3% 81% AB% E3% 81% 97% E3% 81% 9F% E4% B8% 8A% E3% 81% A7% E5% 85% AC% E9% 96% 8B% E9% 96% A2% E6% 95% B0% E3% 81% AB__visibility__default% E3% 82% 92% E6% 8C% 87% E5% AE% 9A% E3% 81% 99% E3% 82% 8B): Make the default private and add __visibility__ ("default ") to the public function specify

Considering maintenance costs and leak prevention, [Method 3](https://qiita.com/takeoverjp/items/d86fba2843faac955d4b#%E6%96%B9%E6%B3%953-%E3%83%87% E3% 83% 95% E3% 82% A9% E3% 83% AB% E3% 83% 88% E3% 82% 92% E9% 9D% 9E% E5% 85% AC% E9% 96% 8B% E3% 81% AB% E3% 81% 97% E3% 81% 9F% E4% B8% 8A% E3% 81% A7% E5% 85% AC% E9% 96% 8B% E9% 96% A2% E6% 95% B0% E3% 81% AB__visibility__default% E3% 82% 92% E6% 8C% 87% E5% AE% 9A% E3% 81% 99% E3% 82% 8B) is the best method.

We have confirmed the operation in the following environment.

$ uname -m
x86_64
$ uname -r
5.5.8
$ lsb_release -d
Description:    Ubuntu 18.04.5 LTS
$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 7.5.0-3ubuntu1~18.04' --with-bugurl=file:///usr/share/doc/gcc-7/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++ --prefix=/usr --with-gcc-major-version-only --program-suffix=-7 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04) 

theme

The following MyClass is defined in the shared library, and the following are requirements.

・ Constructor / destructor and public method are open to the public ・ Private method is private

sample.h


class MyClass {
 public:
  MyClass();
  ~MyClass();

  void PublicMethod();
  int PublicMethodWithArgs(int argc, char* argv[]);

 private:
  void PrivateMethod();
  int PrivateMethodWithArgs(int argc, char* argv[]);
};

The public symbols when built as it is are as follows. You can see that the private method is also published.

$ g++ -W -Werror -shared -fPIC -o libsample.so sample.cc
$ nm -gC libsample.so | awk '$2=="T"' | grep MyClass
0000000000000722 T MyClass::PublicMethod()
0000000000000740 T MyClass::PrivateMethod()
000000000000072e T MyClass::PublicMethodWithArgs(int, char**)
000000000000074c T MyClass::PrivateMethodWithArgs(int, char**)
000000000000070a T MyClass::MyClass()
000000000000070a T MyClass::MyClass()
0000000000000716 T MyClass::~MyClass()
0000000000000716 T MyClass::~MyClass()

Method 1. List public functions using LD version script

You can limit the symbols to be exposed by specifying version script for ld. The symbols described in global: are made public, and the symbols described in local: are made private. You can use the * and ? Wildcards.

In the case of C ++, it is mangling, so enclose it in extern" C ++ " and And you need to specify the wildcard * after the method name. As a result, it may be applied to unintended methods.

By the way, the line MyClass ::? MyClass * is for exposing the destructor. Since ~ is invalid in the grammar of version script, the wildcard ? is used instead. This may also apply to unintended methods.

sample.map


{
  global:
    extern "C++" {
      MyClass::MyClass*;
      MyClass::?MyClass*;
      MyClass::PublicMethod*;
    };
  local:
    *;
};

The result of building with the above version script specified and checking the symbols is as follows. The private method is private.

$ g++ -W -Werror -shared -fPIC -Wl,--version-script=sample.map -o libsample.so sample.cc
$ nm -gC libsample.so | awk '$2=="T"' | grep MyClass
00000000000005e2 T MyClass::PublicMethod()
00000000000005ee T MyClass::PublicMethodWithArgs(int, char**)
00000000000005ca T MyClass::MyClass()
00000000000005ca T MyClass::MyClass()
00000000000005d6 T MyClass::~MyClass()
00000000000005d6 T MyClass::~MyClass()

Method 2: Specify __visibility__ ("hidden") for a private function

Public / private can be specified as an attribute for each function in gcc.

Since the default is public, for the function you want to keep private as below You can achieve your goal by specifying __visibility__ ("hidden ").

sample.cc


MyClass::MyClass() {}

MyClass::~MyClass() {}

void
MyClass::PublicMethod() {}

int
MyClass::PublicMethodWithArgs(int argc, char* argv[]) {}

void __attribute__((__visibility__("hidden")))
MyClass::PrivateMethod() {}

int __attribute__((__visibility__("hidden")))
MyClass::PrivateMethodWithArgs(int argc, char* argv[]) {}

Even if you do not specify the version script as follows The private method can be kept private.

$ g++ -W -Werror -shared -fPIC -o libsample.so sample.cc
$ nm -gC libsample.so | awk '$2=="T"' | grep MyClass
0000000000000692 T MyClass::PublicMethod()
000000000000069e T MyClass::PublicMethodWithArgs(int, char**)
000000000000067a T MyClass::MyClass()
000000000000067a T MyClass::MyClass()
0000000000000686 T MyClass::~MyClass()
0000000000000686 T MyClass::~MyClass()

Method 3: Make the default private and specify __visibility__ ("default ") in the public function

I can do what I want to do with method 2, but the blacklist method definitely leaks. (Because it can be used even if it leaks)

Therefore, after making the default private, I think it is safe to use attributes to specify public functions in a whitelisted manner.

To keep the default private, specify -fvisibility = hidden at compile time. After that, contrary to Plan 2, specify __visibility__ ("default ") for the function you want to expose.

sample.cc


__attribute__((__visibility__("default")))
MyClass::MyClass() {}

__attribute__((__visibility__("default")))
MyClass::~MyClass() {}

void __attribute__((__visibility__("default")))
MyClass::PublicMethod() {}

int __attribute__((__visibility__("default")))
MyClass::PublicMethodWithArgs(int argc, char* argv[]) {}

void
MyClass::PrivateMethod() {}

int
MyClass::PrivateMethodWithArgs(int argc, char* argv[]) {}

The private method can be made private as follows.

$ g++ -W -Werror -shared -fPIC -fvisibility=hidden -o libsample.so sample.cc
$ nm -gC libsample.so | awk '$2=="T"' | grep MyClass
0000000000000692 T MyClass::PublicMethod()
000000000000069e T MyClass::PublicMethodWithArgs(int, char**)
000000000000067a T MyClass::MyClass()
000000000000067a T MyClass::MyClass()
0000000000000686 T MyClass::~MyClass()
0000000000000686 T MyClass::~MyClass()

Experimental code

It is placed below.

takeoverjp/hide_shared_lib_symbols

reference

GNU Gnulib: Exported Symbols of Shared Libraries Visibility - GCC Wiki How To Write Shared Libraries GNU Gnulib: LD Version Scripts Using the GNU Compiler Collection (GCC): Common Function Attributes

Recommended Posts

Limit public symbols in shared libraries
Use shared memory with shared libraries
error while loading shared libraries
Debug shared libraries with VScode