Linking problems due to symbols with abi::cxx11? - c++

We recently caught a report because of GCC 5.1, libstdc++ and Dual ABI. It seems Clang is not aware of the GCC inline namespace changes, so it generates code based on one set of namespaces or symbols, while GCC used another set of namespaces or symbols. At link time, there are problems due to missing symbols.
If I am parsing the Dual ABI page correctly, it looks like a matter of pivoting on _GLIBCXX_USE_CXX11_ABI and abi::cxx11 with some additional hardships. More reading is available on Red Hat's blog at GCC5 and the C++11 ABI and The Case of GCC-5.1 and the Two C++ ABIs.
Below is from a Ubuntu 15 machine. The machine provides GCC 5.2.1.
$ cat test.cxx
#include <string>
std::string foo __attribute__ ((visibility ("default")));
std::string bar __attribute__ ((visibility ("default")));
$ g++ -g3 -O2 -shared test.cxx -o test.so
$ nm test.so | grep _Z3
...
0000201c B _Z3barB5cxx11
00002034 B _Z3fooB5cxx11
$ echo _Z3fooB5cxx11 _Z3barB5cxx11 | c++filt
foo[abi:cxx11] bar[abi:cxx11]
How can I generate a binary with symbols using both decorations ("coexistence" as the Red Hat blog calls it)?
Or, what are the options available to us?
I'm trying to achieve an "it just works" for users. I don't care if there are two weak symbols with two different behaviors (std::string lacks copy-on-write, while std::string[abi:cxx11] provides copy-on-write). Or, one can be an alias for the other.
Debian has a boatload of similar bugs at Debian Bug report logs: Bugs tagged libstdc++-cxx11. Their solution was to rebuild everything under the new ABI, but it did not handle the corner case of mixing/matching compilers modulo the ABI changes.
In the Apple world, I think this is close to a fat binary. But I'm not sure what to do in the Linux/GCC world. Finally, we don't control how the distro's build the library, and we don't control what compilers are used to link an applications with the library.

Disclaimer, the following is not tested in production, use at your own risk.
You can yourself release your library under dual ABI. This is more or less analogous to OSX "fat binary", but built entirely with C++.
The easiest way to do so would be to compile the library twice: with -D_GLIBCXX_USE_CXX11_ABI=0 and with -D_GLIBCXX_USE_CXX11_ABI=1. Place the entire library under two different namespaces depending on the value of the macro:
#if _GLIBCXX_USE_CXX11_ABI
# define DUAL_ABI cxx11 __attribute__((abi_tag("cxx11")))
#else
# define DUAL_ABI cxx03
#endif
namespace CryptoPP {
inline namespace DUAL_ABI {
// library goes here
}
}
Now your users can use CryptoPP::whatever as usual, this maps to either CryptoPP::cxx11::whatever or CryptoPP::cxx03::whatever depending on the ABI selected.
Note, the GCC manual says that this method will change mangled names of everything defined in the tagged inline namespace. In my experience this doesn't happen.
The other method would be tagging every class, function, and variable with __attribute__((abi_tag("cxx11"))) if _GLIBCXX_USE_CXX11_ABI is nonzero. This attribute nicely adds [cxx11] to the output of the demangler. I think that using a namespace works just as well though, and requires less modification to the existing code.
In theory you don't need to duplicate the entire library, only functions and classes that use std::string and std::list, and functions and classes that use these functions and classes, and so on recursively. But in practice it's probably not worth the effort, especially if the library is not very big.

Here's one way to do it, but its not very elegant. Its also not clear to me how to make GCC automate it so I don't have to do things twice.
First, the example that's going to be turned into a library:
$ cat test.cxx
#include <string>
std::string foo __attribute__ ((visibility ("default")));
std::string bar __attribute__ ((visibility ("default")));
Then:
$ g++ -D_GLIBCXX_USE_CXX11_ABI=0 -c test.cxx -o test-v1.o
$ g++ -D_GLIBCXX_USE_CXX11_ABI=1 -c test.cxx -o test-v2.o
$ ar cr test.a test-v1.o test-v2.o
$ ranlib test.a
$ g++ -shared test-v1.o test-v2.o -o test.so
Finally, see what we got:
$ nm test.a
test-v1.o:
00000004 B bar
U __cxa_atexit
U __dso_handle
00000000 B foo
0000006c t _GLOBAL__sub_I_foo
00000000 t _Z41__static_initialization_and_destruction_0ii
U _ZNSsC1Ev
U _ZNSsD1Ev
test-v2.o:
U __cxa_atexit
U __dso_handle
0000006c t _GLOBAL__sub_I__Z3fooB5cxx11
00000018 B _Z3barB5cxx11
00000000 B _Z3fooB5cxx11
00000000 t _Z41__static_initialization_and_destruction_0ii
U _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1Ev
U _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEED1Ev
And:
$ nm test.so
00002020 B bar
00002018 B __bss_start
00002018 b completed.7181
U __cxa_atexit##GLIBC_2.1.3
w __cxa_finalize##GLIBC_2.1.3
00000650 t deregister_tm_clones
000006e0 t __do_global_dtors_aux
00001ef4 t __do_global_dtors_aux_fini_array_entry
00002014 d __dso_handle
00001efc d _DYNAMIC
00002018 D _edata
00002054 B _end
0000087c T _fini
0000201c B foo
00000730 t frame_dummy
00001ee8 t __frame_dummy_init_array_entry
00000980 r __FRAME_END__
00002000 d _GLOBAL_OFFSET_TABLE_
000007dc t _GLOBAL__sub_I_foo
00000862 t _GLOBAL__sub_I__Z3fooB5cxx11
w __gmon_start__
000005e0 T _init
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
00001ef8 d __JCR_END__
00001ef8 d __JCR_LIST__
w _Jv_RegisterClasses
00000690 t register_tm_clones
00002018 d __TMC_END__
00000640 t __x86.get_pc_thunk.bx
0000076c t __x86.get_pc_thunk.dx
0000203c B _Z3barB5cxx11
00002024 B _Z3fooB5cxx11
00000770 t _Z41__static_initialization_and_destruction_0ii
000007f6 t _Z41__static_initialization_and_destruction_0ii
U _ZNSsC1Ev##GLIBCXX_3.4
U _ZNSsD1Ev##GLIBCXX_3.4
U _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1Ev##GLIBCXX_3.4.21
U _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEED1Ev##GLIBCXX_3.4.21

Related

Linking to C++ static library on linux throws linker errors while building an execuatble [duplicate]

We recently caught a report because of GCC 5.1, libstdc++ and Dual ABI. It seems Clang is not aware of the GCC inline namespace changes, so it generates code based on one set of namespaces or symbols, while GCC used another set of namespaces or symbols. At link time, there are problems due to missing symbols.
If I am parsing the Dual ABI page correctly, it looks like a matter of pivoting on _GLIBCXX_USE_CXX11_ABI and abi::cxx11 with some additional hardships. More reading is available on Red Hat's blog at GCC5 and the C++11 ABI and The Case of GCC-5.1 and the Two C++ ABIs.
Below is from a Ubuntu 15 machine. The machine provides GCC 5.2.1.
$ cat test.cxx
#include <string>
std::string foo __attribute__ ((visibility ("default")));
std::string bar __attribute__ ((visibility ("default")));
$ g++ -g3 -O2 -shared test.cxx -o test.so
$ nm test.so | grep _Z3
...
0000201c B _Z3barB5cxx11
00002034 B _Z3fooB5cxx11
$ echo _Z3fooB5cxx11 _Z3barB5cxx11 | c++filt
foo[abi:cxx11] bar[abi:cxx11]
How can I generate a binary with symbols using both decorations ("coexistence" as the Red Hat blog calls it)?
Or, what are the options available to us?
I'm trying to achieve an "it just works" for users. I don't care if there are two weak symbols with two different behaviors (std::string lacks copy-on-write, while std::string[abi:cxx11] provides copy-on-write). Or, one can be an alias for the other.
Debian has a boatload of similar bugs at Debian Bug report logs: Bugs tagged libstdc++-cxx11. Their solution was to rebuild everything under the new ABI, but it did not handle the corner case of mixing/matching compilers modulo the ABI changes.
In the Apple world, I think this is close to a fat binary. But I'm not sure what to do in the Linux/GCC world. Finally, we don't control how the distro's build the library, and we don't control what compilers are used to link an applications with the library.
Disclaimer, the following is not tested in production, use at your own risk.
You can yourself release your library under dual ABI. This is more or less analogous to OSX "fat binary", but built entirely with C++.
The easiest way to do so would be to compile the library twice: with -D_GLIBCXX_USE_CXX11_ABI=0 and with -D_GLIBCXX_USE_CXX11_ABI=1. Place the entire library under two different namespaces depending on the value of the macro:
#if _GLIBCXX_USE_CXX11_ABI
# define DUAL_ABI cxx11 __attribute__((abi_tag("cxx11")))
#else
# define DUAL_ABI cxx03
#endif
namespace CryptoPP {
inline namespace DUAL_ABI {
// library goes here
}
}
Now your users can use CryptoPP::whatever as usual, this maps to either CryptoPP::cxx11::whatever or CryptoPP::cxx03::whatever depending on the ABI selected.
Note, the GCC manual says that this method will change mangled names of everything defined in the tagged inline namespace. In my experience this doesn't happen.
The other method would be tagging every class, function, and variable with __attribute__((abi_tag("cxx11"))) if _GLIBCXX_USE_CXX11_ABI is nonzero. This attribute nicely adds [cxx11] to the output of the demangler. I think that using a namespace works just as well though, and requires less modification to the existing code.
In theory you don't need to duplicate the entire library, only functions and classes that use std::string and std::list, and functions and classes that use these functions and classes, and so on recursively. But in practice it's probably not worth the effort, especially if the library is not very big.
Here's one way to do it, but its not very elegant. Its also not clear to me how to make GCC automate it so I don't have to do things twice.
First, the example that's going to be turned into a library:
$ cat test.cxx
#include <string>
std::string foo __attribute__ ((visibility ("default")));
std::string bar __attribute__ ((visibility ("default")));
Then:
$ g++ -D_GLIBCXX_USE_CXX11_ABI=0 -c test.cxx -o test-v1.o
$ g++ -D_GLIBCXX_USE_CXX11_ABI=1 -c test.cxx -o test-v2.o
$ ar cr test.a test-v1.o test-v2.o
$ ranlib test.a
$ g++ -shared test-v1.o test-v2.o -o test.so
Finally, see what we got:
$ nm test.a
test-v1.o:
00000004 B bar
U __cxa_atexit
U __dso_handle
00000000 B foo
0000006c t _GLOBAL__sub_I_foo
00000000 t _Z41__static_initialization_and_destruction_0ii
U _ZNSsC1Ev
U _ZNSsD1Ev
test-v2.o:
U __cxa_atexit
U __dso_handle
0000006c t _GLOBAL__sub_I__Z3fooB5cxx11
00000018 B _Z3barB5cxx11
00000000 B _Z3fooB5cxx11
00000000 t _Z41__static_initialization_and_destruction_0ii
U _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1Ev
U _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEED1Ev
And:
$ nm test.so
00002020 B bar
00002018 B __bss_start
00002018 b completed.7181
U __cxa_atexit##GLIBC_2.1.3
w __cxa_finalize##GLIBC_2.1.3
00000650 t deregister_tm_clones
000006e0 t __do_global_dtors_aux
00001ef4 t __do_global_dtors_aux_fini_array_entry
00002014 d __dso_handle
00001efc d _DYNAMIC
00002018 D _edata
00002054 B _end
0000087c T _fini
0000201c B foo
00000730 t frame_dummy
00001ee8 t __frame_dummy_init_array_entry
00000980 r __FRAME_END__
00002000 d _GLOBAL_OFFSET_TABLE_
000007dc t _GLOBAL__sub_I_foo
00000862 t _GLOBAL__sub_I__Z3fooB5cxx11
w __gmon_start__
000005e0 T _init
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
00001ef8 d __JCR_END__
00001ef8 d __JCR_LIST__
w _Jv_RegisterClasses
00000690 t register_tm_clones
00002018 d __TMC_END__
00000640 t __x86.get_pc_thunk.bx
0000076c t __x86.get_pc_thunk.dx
0000203c B _Z3barB5cxx11
00002024 B _Z3fooB5cxx11
00000770 t _Z41__static_initialization_and_destruction_0ii
000007f6 t _Z41__static_initialization_and_destruction_0ii
U _ZNSsC1Ev##GLIBCXX_3.4
U _ZNSsD1Ev##GLIBCXX_3.4
U _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1Ev##GLIBCXX_3.4.21
U _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEED1Ev##GLIBCXX_3.4.21

Violating the one definition rule by simply linking dynamically

Question: Are dynamically linked C++ programs on ELF platforms always on the brink of producing undefined behavior by violating the one definition rule?
More specific: By simply writing a shared library exposing one function
#include <string>
int __attribute__((visibility("default"))) combined_length(const char *s,
const char *t)
{
const std::string t1(t);
const std::string u(s + t1);
return u.length();
}
and compiling it with GCC 7.3.0 via
$ g++ -Wall -g -fPIC -shared \
-fvisibility=hidden -fvisibility-inlines-hidden \
-o liblibrary.so library.cpp
I create a binary which defines a weak symbol for the operator+() of a pointer to a character array and a string:
$ readelf -sW liblibrary.so | grep "_ZStpl"
24: 0000000000000ee2 202 FUNC WEAK DEFAULT 12 _ZStplIcSt11char_traitsIcESaIcEENSt7__cxx1112basic_stringIT_T0_T1_EEPKS5_RKS8_
...
But looking at the standard library binary I got
$ readelf -sW /usr/lib/x86_64-linux-gnu/libstdc++.so.6 | grep "_ZStplIcSt11char_traitsIcESaIcEENSt7__cxx1112basic_stringIT_T0_T1_EEPKS5_RKS8_"
2829: 000000000012b1c0 169 FUNC WEAK DEFAULT 13 _ZStplIcSt11char_traitsIcESaIcEENSt7__cxx1112basic_stringIT_T0_T1_EEPKS5_RKS8_##GLIBCXX_3.4.21
That's the point where I say: Oh my gosh, the symbol inside my library ought to have a version attached to it too!
In the current state I'm fine because I can assume that the standard library binary is built with the same headers as my library. But what happens if the implementers of libstdc++-v3 decide to define a new version of this function and tag it with GLIBCXX_3.4.22? Since the symbol is weak, the runtime linker is free to decide whether it takes the unversioned symbol of my library or the versioned symbol of the libstdc++-v3. If I ship my library to such a system I provoke undefined behavior there. Something symbol versions should have solved for me.

When did Clang add visibility support for shared objects?

GCC added visibility support at version 4.0. I have the following in my make, which reduces the size of my shared object by about 1/3 (1.5 MB):
GCC40_OR_LATER = $(shell $(CXX) -v 2>&1 | $(EGREP) -c "^gcc version ([4-9])")
ifeq ($(GCC40_OR_LATER),1)
CXXFLAGS += -fvisibility=hidden -fvisibility-inlines-hidden
endif
I'd like to add a similar rule for Clang. When did Clang add visibility support? Has it always been available?
Confirmed it is in 3.3+. I did not test any lower versions, but I'm willing to bet that it is there and has always been there. I've tested 3.3, 3.4, 3.5, 3.6 and 3.7.
For a list of other "new" attributes (for 3.7), see: http://clang.llvm.org/docs/AttributeReference.html
As you can see, the variable "a" is exported in the very first picture but in the second one, I hid it and it's no longer in the symbol table. I proceeded to hide the functions in the last picture as well and they are also not in the symbol table.
I take that as a sign that it works. Tested on Linux Mint Rebecca, no gcc or g++ or mingw or anything else installed. Just codeblocks and clang and the llvm. I had uninstalled gcc and g++ after building clang (to avoid conflicts and problems if any were to arise [which I doubt would happen, but I'm pedantic]).
NOTE: I tried to #define the hidden attribute, but no cigar.
For those that prefer text output:
kira#Kira ~/Desktop/shm/bin/Debug $ nm -gC liblibshm.so
0000000000200980 B __bss_start
w __cxa_finalize##GLIBC_2.2.5
0000000000200980 D _edata
0000000000200988 B _end
0000000000000628 T _fini
w __gmon_start__
00000000000004b0 T _init
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
w _Jv_RegisterClasses
kira#Kira ~/Desktop/shm/bin/Debug $ clang++ --version
Ubuntu clang version 3.3-16ubuntu1 (branches/release_33) (based on LLVM 3.3)
Target: x86_64-pc-linux-gnu
Thread model: posix
kira#Kira ~/Desktop/shm/bin/Debug $

Global constructor call not in .init_array section

I'm trying to add global constructor support on an embedded target (ARM Cortex-M3).
Lets say I've the following code:
class foobar
{
int i;
public:
foobar()
{
i = 100;
}
void inc()
{
i++;
}
};
foobar foo;
int main()
{
foo.inc();
for (;;);
}
I compile it like this:
arm-none-eabi-g++ -O0 -gdwarf-2 -mcpu=cortex-m3 -mthumb -c foo.cpp -o foo.o
When I look at the .init_array section with objdump it shows the .init_section has a zero size.
I do get an symbol named _Z41__static_initialization_and_destruction_0ii.
When I disassemble the object file I see that the global construction is done in the static_initialization_and_destruction symbol.
Why isn't a pointer added to this symbol in the .init_section?
I know it has been almost two years since this question was asked, but I just had to figure out the mechanics of bare-metal C++ initialization with GCC myself, so I thought I'd share the details here. There turns out to be a lot of out-of-date or confusing information on the web. For example, the oft-mentioned collect2 wrapper does not appear to be used for ARM ELF targets, since its arbitrary section support enables the approach described below.
First, when I compile the code above with the given command line using Sourcery CodeBench Lite 2012.09-63, I do see the correct .init_array section size of 4:
$ arm-none-eabi-objdump -h foo.o
foo.o: file format elf32-littlearm
Sections:
Idx Name Size VMA LMA File off Algn
...
13 .init_array 00000004 00000000 00000000 0000010c 2**2
CONTENTS, ALLOC, LOAD, RELOC, DATA
...
When I look at the section contents, it just contains 0:
$ arm-none-eabi-objdump -j .init_array -s foo.o
Contents of section .init_array:
0000 00000000 ....
However, there is also a relocation section that sets it correctly to _GLOBAL__sub_I_foo:
$ arm-none-eabi-objdump -x foo.o
...
RELOCATION RECORDS FOR [.init_array]:
OFFSET TYPE VALUE
00000000 R_ARM_TARGET1 _GLOBAL__sub_I_foo
In general, .init_array points to all of your _GLOBAL__sub_I_XXX initializer stubs, each of which calls its own copy of _Z41__static_initialization_and_destruction_0ii (yes, it is multiply-defined), which calls the constructor with the appropriate arguments.
Because I'm using -nostdlib in my build, I can't use CodeSourcery's __libc_init_array to execute the .init_array for me, so I need to call the static initializers myself:
extern "C"
{
extern void (**__init_array_start)();
extern void (**__init_array_end)();
inline void static_init()
{
for (void (**p)() = __init_array_start; p < __init_array_end; ++p)
(*p)();
}
}
__init_array_start and __init_array_end are defined by the linker script:
. = ALIGN(4);
.init_array :
{
__init_array_start = .;
KEEP (*(.init_array*))
__init_array_end = .;
}
This approach seems to work with both the CodeSourcery cross-compiler and native ARM GCC, e.g. in Ubuntu 12.10 for ARM. Supporting both compilers is one reason for using -nostdlib and not relying on the CodeSourcery CS3 bare-metal support.
Timmmm,
I just had the same issue on the nRF51822 and solved it by adding KEEP() around a couple lines in the stock Nordic .ld file:
KEEP(*(SORT(.init_array.*)))
KEEP(*(.init_array))
While at it, I did the same to the fini_array area too. Solved my problem and the linker can still remove other unused sections...
You have only produced an object file, due to the -c argument to gcc. To create the .init section, I believe that you need to link that .o into an actual executable or shared library. Try removing the -c argument and renaming the output file to "foo", and then check the resulting executable with the disassembler.
If you look carefully _Z41__static_initialization_and_destruction_0ii would be called inside global constructor. Which inturn would be linked in .init_array section (in arm-none-eabi- from CodeSourcery.) or some other function (__main() if you are using Linux g++). () This should be called at startup or at main().
See also this link.
I had a similar issue where my constructors were not being called (nRF51822 Cortex-M0 with GCC). The problem turned out to be due to this linker flag:
-Wl,--gc-sections
Don't ask me why! I thought it only removed dead code.

Stripping linux shared libraries

We've recently been asked to ship a Linux version of one of our libraries, previously we've developed under Linux and shipped for Windows where deploying libraries is generally a lot easier. The problem we've hit upon is in stripping the exported symbols down to only those in the exposed interface. There are three good reasons for wanting to do this
To protect the proprietary aspects of our technology from exposure through the exported symbols.
To prevent users having problems with conflicting symbol names.
To speed up the loading of the library (at least so I'm told).
Taking a simple example then:
test.cpp
#include <cmath>
float private_function(float f)
{
return std::abs(f);
}
extern "C" float public_function(float f)
{
return private_function(f);
}
compiled with (g++ 4.3.2, ld 2.18.93.20081009)
g++ -shared -o libtest.so test.cpp -s
and inspecting the symbols with
nm -DC libtest.so
gives
w _Jv_RegisterClasses
0000047c T private_function(float)
000004ba W std::abs(float)
0000200c A __bss_start
w __cxa_finalize
w __gmon_start__
0000200c A _edata
00002014 A _end
00000508 T _fini
00000358 T _init
0000049b T public_function
obviously inadequate. So next we redeclare the public function as
extern "C" float __attribute__ ((visibility ("default")))
public_function(float f)
and compile with
g++ -shared -o libtest.so test.cpp -s -fvisibility=hidden
which gives
w _Jv_RegisterClasses
0000047a W std::abs(float)
0000200c A __bss_start
w __cxa_finalize
w __gmon_start__
0000200c A _edata
00002014 A _end
000004c8 T _fini
00000320 T _init
0000045b T public_function
which is good, except that std::abs is exposed. More problematic is when we start linking in other (static) libraries outside of our control, all of the symbols we use from those libraries get exported. In addition, when we start using STL containers:
#include <vector>
struct private_struct
{
float f;
};
void other_private_function()
{
std::vector<private_struct> v;
}
we end up with many additional exports from the C++ library
00000b30 W __gnu_cxx::new_allocator<private_struct>::deallocate(private_struct*, unsigned int)
00000abe W __gnu_cxx::new_allocator<private_struct>::new_allocator()
00000a90 W __gnu_cxx::new_allocator<private_struct>::~new_allocator()
00000ac4 W std::allocator<private_struct>::allocator()
00000a96 W std::allocator<private_struct>::~allocator()
00000ad8 W std::_Vector_base<private_struct, std::allocator<private_struct> >::_Vector_impl::_Vector_impl()
00000aaa W std::_Vector_base<private_struct, std::allocator<private_struct> >::_Vector_impl::~_Vector_impl()
00000b44 W std::_Vector_base<private_struct, std::allocator<private_struct> >::_M_deallocate(private_struct*, unsigned int)
00000a68 W std::_Vector_base<private_struct, std::allocator<private_struct> >::_M_get_Tp_allocator()
00000b08 W std::_Vector_base<private_struct, std::allocator<private_struct> >::_Vector_base()
00000b6e W std::_Vector_base<private_struct, std::allocator<private_struct> >::~_Vector_base()
00000b1c W std::vector<private_struct, std::allocator<private_struct> >::vector()
00000bb2 W std::vector<private_struct, std::allocator<private_struct> >::~vector()
NB: With optimisations on you'll need to make sure the vector is actually used so the compiler doesn't optimise the unused symbols out.
I believe my colleague has managed to construct an ad-hoc solution involving version files and modifying the STL headers (!) that appears to work, but I would like to ask:
Is there a clean way to strip all unnecessary symbols (IE ones that are not part of the exposed library functionality) from a linux shared library? I've tried quite a lot of options to both g++ and ld with little success so I'd prefer answers that are known to work rather than believed to.
In particular:
Symbols from (closed-source) static libraries are not exported.
Symbols from the standard library are not exported.
Non-public symbols from the object files are not exported.
Our exported interface is C.
I'm aware of the other similar questions on SO:
NOT sharing all classes with shared library
How to REALLY strip a binary in MacOs
GNU linker: alternative to --version-script to list exported symbols at the command line?
but have had little success with the answers.
So the solution we have for now is as follows:
test.cpp
#include <cmath>
#include <vector>
#include <typeinfo>
struct private_struct
{
float f;
};
float private_function(float f)
{
return std::abs(f);
}
void other_private_function()
{
std::vector<private_struct> f(1);
}
extern "C" void __attribute__ ((visibility ("default"))) public_function2()
{
other_private_function();
}
extern "C" float __attribute__ ((visibility ("default"))) public_function1(float f)
{
return private_function(f);
}
exports.version
LIBTEST
{
global:
public*;
local:
*;
};
compiled with
g++ -shared test.cpp -o libtest.so -fvisibility=hidden -fvisibility-inlines-hidden -s -Wl,--version-script=exports.version
gives
00000000 A LIBTEST
w _Jv_RegisterClasses
U _Unwind_Resume
U std::__throw_bad_alloc()
U operator delete(void*)
U operator new(unsigned int)
w __cxa_finalize
w __gmon_start__
U __gxx_personality_v0
000005db T public_function1
00000676 T public_function2
Which is fairly close to what we're looking for. There are a few gotchas though:
We have to ensure we don't use the "exported" prefix (in this simple example "public", but obviously something more useful in our case) in the internal code.
Many symbol names still end up in the string table, which appears to be down to RTTI, -fno-rtti makes them go away in my simple tests, but is a rather nuclear solution.
I'm happy to accept any better solutions anyone comes up with!
Your use of the default visibility attribute and -fvisibility=hidden should be augmented with -fvisibility-inlines-hidden.
You should also forget about trying to hide stdlib exports, see this GCC bug for why.
Also, if you have all of your public symbols in a specific headers you can wrap them in #pragma GCC visibility push(default) and #pragma GCC visibility pop instead of using attributes. Though if you are creating a cross platform library, take a look at Controlling Exported Symbols of Shared Libraries for a technique to unify your windows DLL and Linux DSO export strategy.
Just to note that Ulrich Drepper wrote an essay regarding (all?) aspects of writing shared libraries for Linux/Unix, which covers control of exported symbols amongst many other topics.
This was very handy in making it clear how to export only functions on a whitelist from a shared lib.
If you wrap up your private part in an anonymous namespace then neither std::abs nor private_function can be seen in the symbol table:
namespace{
#include<cmath>
float private_function(float f)
{
return std::abs(f);
}
}
extern "C" float public_function(float f)
{
return private_function(f);
}
compiling (g++ 4.3.3):
g++ -shared -o libtest.so test.cpp -s
inspecting:
# nm -DC libtest.so
w _Jv_RegisterClasses
0000200c A __bss_start
w __cxa_finalize
w __gmon_start__
0000200c A _edata
00002014 A _end
000004a8 T _fini
000002f4 T _init
00000445 T public_function
In general, across multiple Linux and Unix systems, the answer here is that there is no answer here at link time. it's fairly fundamental to how ld.so works.
This leads to some rather labor-intensive alternatives. For example, we rename STL to live in _STL instead of std to avoid conflicts over STL, and we use namespaces high, low, and in-between to keep our symbols away from possible conflicts with other people's symbols.
Here's a solution you won't love:
Create a small .so with only your exposed API it.
Have it open the real implementation with dlopen, and link with dlsym.
So long as you don't use RTLD_GLOBAL, you now have complete insulation if not particular secrecy .. -Bsymbolic might also be desirable.
Actually, in the ELF structure there are 2 symbol tables: "symtab" and "dynsym" -> see this:
Hiding symbol names in library