UP | HOME

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.