Static Linking with glibc for Android
Once upon a time, a question was raised: is it possible to make shared library without shared dependencies on Android?
TL;DR out of the box it does not work, probably by design, probably for the good.
Blindly switching to static
When using CMake, static linking could be achieved with -static flag on normal platforms.
With Android NDK, -static
flag still causes -latomic -lm
to be present.
This happens in ~/Android/Sdk/ndk/21.4.7075529/build/cmake/android.toolchain.cmake
.
And still it fails with:
[...] -static -latomic -lm && : ~/Android/Sdk/ndk/21.4.7075529/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/21/crtbegin_static.o: In function `_start_main': crtbegin.c:(.text+0x38): undefined reference to `main' crtbegin.c:(.text+0x3c): undefined reference to `main' clang++: error: linker command failed with exit code 1 (use -v to see invocation) ninja: build stopped: subcommand failed.
Which is much deeper issue that will require revising/fixing linking script and/or crt
code for each platform binary combination bootstrap code within Android NDK. Good luck with that.
Brutally removing -latomic
and -lm
set(CMAKE_C_STANDARD_LIBRARIES "" CACHE STRING "compile flags" FORCE) set(CMAKE_CXX_STANDARD_LIBRARIES "" CACHE STRING "compile flags" FORCE)
This will override hard coded CMAKE_C_STANDARD_LIBRARIES_INIT
values set in ~/Android/Sdk/ndk/21.4.7075529/build/cmake/android.toolchain.cmake
. Although it is wrong practice, let's try.
When compiled without any actual library code to reproduce the minimal binary, readelf
reports as following:
[...] Dynamic section at offset 0xda0 contains 30 entries: Tag Type Name/Value [...] 0x0000000000000001 (NEEDED) Shared library: [libm.so] 0x0000000000000001 (NEEDED) Shared library: [libdl.so] 0x0000000000000001 (NEEDED) Shared library: [libc.so] [...] Relocation section '.rela.plt' at offset 0x530 contains 3 entries: Offset Info Type Sym. Value Sym. Name + Addend 000000001fe8 000200000007 R_X86_64_JUMP_SLO 0000000000000000 __cxa_finalize@LIBC + 0 000000001ff0 000100000007 R_X86_64_JUMP_SLO 0000000000000000 __cxa_atexit@LIBC + 0 000000001ff8 000600000007 R_X86_64_JUMP_SLO 00000000000005e0 _Z11myLibOnLoadv + 0 No processor specific unwind information to decode Symbol table '.dynsym' contains 8 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __cxa_atexit@LIBC (2) 2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __cxa_f[...]@LIBC (2) 3: 0000000000002000 0 NOTYPE GLOBAL DEFAULT ABS _edata 4: 0000000000002000 0 NOTYPE GLOBAL DEFAULT ABS _end 5: 0000000000002000 0 NOTYPE GLOBAL DEFAULT ABS __bss_start 6: 00000000000005e0 6 FUNC GLOBAL DEFAULT 13 _Z11myLibOnLoadv
__cxa_finalize
and __cxa_atexit
are binary platform specific functions mandatory for C++ support. Especially, __cxa_finalize
is also a strongly stateful, shared library loading/unloading aware function. Having __cxa_finalize
implementation as static in main application binary or any of the dependent shared libraries, leads to unpredictable consequences for whole assembly.
Introducing more C++ code into library, pulls more glibc dependencies, which are subject to similar problems caused by stateful implementations as mentioned previously.
In general, static linking with vanilla glibc is discouraged on any platform. And as it seen above with __cxa_finalize
and __cxa_atexit
, even more discouraged in C++ environment.
Any solutions?
IMHO, there is no short path when it comes to avoiding dependency on glibc
. Technically there are two ways:
I want glibc
anyway statically compiled in
One would list all (I mean it, ALL, direct and indirect) references to glibc
incurred by your code. May be split them between stateless
and stateful
occurrences. Then wisely decide on how to reduce references on case by case basis. For instance, most likely, having C++ in the picture, may/will lead to direct no-go decision, especially on embedded platforms.
If I don't want dependency, I won't use glibc
at all
Probably most purest, idealistic and correct approach, leading to a thorny path of -shared
, -nodefaultlibs
, -nostartfiles
etc. Then write your trully independent shared library.