Android NDK creates unreasonable large binaries, how to optimize .so size?

I noticed that Android NDK ( r6b in my case) creates unreasonable large .so files. For example, in my case, I have ~ 150-200 lines of C ++ code (6 native methods and 3 simple C ++ classes), and this native code creates 60kb (!) .So with exceptions enabled and RTTI or 12kb.so with exceptions and RTTI disabled. Just to verify that I am compiling the hello-jni example included in the NDK package and get 10kb.so for the single-line native method from this example.

In my opinion, this is somehow unreasonably overhead for a mobile platform (on my desktop, a code comparable in size is 10-15 times smaller than .so).

  • Are there any tricks I should know to reduce the size of the binaries?
  • Why is there such overhead on C-only?
  • And why is there even more overhead for C ++ code with exceptions and RTTI included?

Update # 1: readelf output for hello-jni example from NDK

ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2 complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: DYN (Shared object file) Machine: ARM Version: 0x1 Entry point address: 0xc18 Start of program headers: 52 (bytes into file) Start of section headers: 9344 (bytes into file) Flags: 0x5000002, has entry point, Version5 EABI Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 5 Size of section headers: 40 (bytes) Number of section headers: 19 Section header string table index: 18 Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .hash HASH 000000d4 0000d4 0001a4 04 A 2 0 4 [ 2] .dynsym DYNSYM 00000278 000278 000420 10 A 3 3 4 [ 3] .dynstr STRTAB 00000698 000698 0004aa 00 A 0 0 1 [ 4] .rel.dyn REL 00000b44 000b44 000048 08 A 2 0 4 [ 5] .rel.plt REL 00000b8c 000b8c 000030 08 A 2 6 4 [ 6] .plt PROGBITS 00000bbc 000bbc 00005c 04 AX 0 0 4 [ 7] .text PROGBITS 00000c18 000c18 001518 00 AX 0 0 4 [ 8] .rodata PROGBITS 00002130 002130 000014 01 AMS 0 0 4 [ 9] .ARM.extab PROGBITS 00002144 002144 000024 00 A 0 0 4 [10] .ARM.exidx ARM_EXIDX 00002168 002168 0000e0 00 AL 7 0 4 [11] .init_array INIT_ARRAY 00003248 002248 000008 00 WA 0 0 1 [12] .fini_array FINI_ARRAY 00003250 002250 00000c 00 WA 0 0 1 [13] .dynamic DYNAMIC 0000325c 00225c 0000e8 08 WA 3 0 4 [14] .got PROGBITS 00003344 002344 000040 04 WA 0 0 4 [15] .bss NOBITS 00003390 002384 000010 00 WA 0 0 16 [16] .comment PROGBITS 00000000 002384 000036 00 0 0 1 [17] .ARM.attributes ARM_ATTRIBUTES 00000000 0023ba 000029 00 0 0 1 [18] .shstrtab STRTAB 00000000 0023e3 00009b 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings) I (info), L (link order), G (group), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific) There are no section groups in this file. Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align EXIDX 0x002168 0x00002168 0x00002168 0x000e0 0x000e0 R 0x4 LOAD 0x000000 0x00000000 0x00000000 0x02248 0x02248 RE 0x1000 LOAD 0x002248 0x00003248 0x00003248 0x0013c 0x00158 RW 0x1000 DYNAMIC 0x00225c 0x0000325c 0x0000325c 0x000e8 0x000e8 RW 0x4 GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4 Section to Segment mapping: Segment Sections... 00 .ARM.exidx 01 .hash .dynsym .dynstr .rel.dyn .rel.plt .plt .text .rodata .ARM.extab .ARM.exidx 02 .init_array .fini_array .dynamic .got .bss 03 .dynamic 04 Dynamic section at offset 0x225c contains 25 entries: Tag Type Name/Value 0x00000001 (NEEDED) Shared library: [libstdc++.so] 0x00000001 (NEEDED) Shared library: [libm.so] 0x00000001 (NEEDED) Shared library: [libc.so] 0x00000001 (NEEDED) Shared library: [libdl.so] 0x0000000e (SONAME) Library soname: [libhello-jni.so] 0x00000010 (SYMBOLIC) 0x0 0x00000019 (INIT_ARRAY) 0x3248 0x0000001b (INIT_ARRAYSZ) 8 (bytes) 0x0000001a (FINI_ARRAY) 0x3250 0x0000001c (FINI_ARRAYSZ) 12 (bytes) 0x00000004 (HASH) 0xd4 0x00000005 (STRTAB) 0x698 0x00000006 (SYMTAB) 0x278 0x0000000a (STRSZ) 1194 (bytes) 0x0000000b (SYMENT) 16 (bytes) 0x00000003 (PLTGOT) 0x3344 0x00000002 (PLTRELSZ) 48 (bytes) 0x00000014 (PLTREL) REL 0x00000017 (JMPREL) 0xb8c 0x00000011 (REL) 0xb44 0x00000012 (RELSZ) 72 (bytes) 0x00000013 (RELENT) 8 (bytes) 0x00000016 (TEXTREL) 0x0 0x6ffffffa (RELCOUNT) 7 0x00000000 (NULL) 0x0 Relocation section '.rel.dyn' at offset 0xb44 contains 9 entries: Offset Info Type Sym.Value Sym. Name 00000c24 00000017 R_ARM_RELATIVE 00003254 00000017 R_ARM_RELATIVE 00003368 00000017 R_ARM_RELATIVE 0000336c 00000017 R_ARM_RELATIVE 00003374 00000017 R_ARM_RELATIVE 00003378 00000017 R_ARM_RELATIVE 00003380 00000017 R_ARM_RELATIVE 00003370 00001015 R_ARM_GLOB_DAT 00000000 __cxa_call_unexpected 0000337c 00003215 R_ARM_GLOB_DAT 00000000 __gnu_Unwind_Find_exid Relocation section '.rel.plt' at offset 0xb8c contains 6 entries: Offset Info Type Sym.Value Sym. Name 00003350 00000d16 R_ARM_JUMP_SLOT 00000000 __cxa_begin_cleanup 00003354 00001216 R_ARM_JUMP_SLOT 00000000 memcpy 00003358 00001416 R_ARM_JUMP_SLOT 00000000 __cxa_finalize 0000335c 00001f16 R_ARM_JUMP_SLOT 00000000 abort 00003360 00002a16 R_ARM_JUMP_SLOT 00000000 __cxa_type_match 00003364 00003216 R_ARM_JUMP_SLOT 00000000 __gnu_Unwind_Find_exid There are no unwind sections in this file. Symbol table '.dynsym' contains 66 entries: Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000c18 0 SECTION LOCAL DEFAULT 7 2: 00003390 0 SECTION LOCAL DEFAULT 15 3: 00001c64 36 FUNC GLOBAL DEFAULT 7 ___Unwind_ForcedUnwind 4: 00001668 164 FUNC GLOBAL DEFAULT 7 __gnu_Unwind_RaiseExcepti 5: 00001b20 0 FUNC GLOBAL DEFAULT 7 __gnu_Unwind_Save_VFP 6: 00001c40 36 FUNC GLOBAL DEFAULT 7 _Unwind_Resume_or_Rethrow 7: 00002248 0 NOTYPE GLOBAL DEFAULT ABS __exidx_end 8: 000011c0 8 FUNC GLOBAL DEFAULT 7 __aeabi_unwind_cpp_pr0 9: 00001d4c 44 FUNC GLOBAL DEFAULT 7 _Unwind_GetRegionStart 10: 00001c40 36 FUNC GLOBAL DEFAULT 7 ___Unwind_Resume_or_Rethr 11: 000033a0 0 NOTYPE GLOBAL DEFAULT ABS _bss_end__ 12: 00001c88 36 FUNC GLOBAL DEFAULT 7 _Unwind_Backtrace 13: 00000000 0 NOTYPE WEAK DEFAULT UND __cxa_begin_cleanup 14: 00001b04 20 FUNC GLOBAL DEFAULT 7 __restore_core_regs 15: 00001b40 0 FUNC GLOBAL DEFAULT 7 __gnu_Unwind_Save_VFP_D_1 16: 00000000 0 NOTYPE WEAK DEFAULT UND __cxa_call_unexpected 17: 00000cf0 8 FUNC GLOBAL DEFAULT 7 _Unwind_GetCFA 18: 00000000 0 FUNC GLOBAL DEFAULT UND memcpy 19: 00000c8c 76 FUNC GLOBAL DEFAULT 7 _Unwind_VRS_Set 20: 00000000 0 FUNC GLOBAL DEFAULT UND __cxa_finalize 21: 00003250 0 NOTYPE GLOBAL DEFAULT 12 __FINI_ARRAY__ 22: 00003384 0 NOTYPE GLOBAL DEFAULT ABS __bss_start__ 23: 00001404 212 FUNC GLOBAL DEFAULT 7 __gnu_Unwind_Backtrace 24: 00003390 4 OBJECT GLOBAL DEFAULT 15 __dso_handle 25: 00001b30 0 FUNC GLOBAL DEFAULT 7 __gnu_Unwind_Save_VFP_D 26: 00001798 876 FUNC GLOBAL DEFAULT 7 _Unwind_VRS_Pop 27: 000011b0 8 FUNC WEAK DEFAULT 7 __aeabi_unwind_cpp_pr2 28: 00001c88 36 FUNC GLOBAL DEFAULT 7 ___Unwind_Backtrace 29: 00002168 0 NOTYPE GLOBAL DEFAULT ABS __exidx_start 30: 00001bf8 36 FUNC GLOBAL DEFAULT 7 ___Unwind_RaiseException 31: 00000000 0 FUNC GLOBAL DEFAULT UND abort 32: 00001c1c 36 FUNC GLOBAL DEFAULT 7 ___Unwind_Resume 33: 00001b48 0 FUNC GLOBAL DEFAULT 7 __gnu_Unwind_Restore_WMMX 34: 00001b18 0 FUNC GLOBAL DEFAULT 7 __gnu_Unwind_Restore_VFP 35: 00001c1c 36 FUNC GLOBAL DEFAULT 7 _Unwind_Resume 36: 00000cfc 32 FUNC GLOBAL DEFAULT 7 _Unwind_DeleteException 37: 00000cf8 4 FUNC GLOBAL DEFAULT 7 _Unwind_Complete 38: 000033a0 0 NOTYPE GLOBAL DEFAULT ABS __bss_end__ 39: 00003248 0 NOTYPE GLOBAL DEFAULT 11 __INIT_ARRAY__ 40: 00001d78 888 FUNC GLOBAL DEFAULT 7 __gnu_unwind_execute 41: 00001b28 0 FUNC GLOBAL DEFAULT 7 __gnu_Unwind_Restore_VFP_ 42: 00000000 0 NOTYPE WEAK DEFAULT UND __cxa_type_match 43: 0000172c 108 FUNC GLOBAL DEFAULT 7 __gnu_Unwind_Resume 44: 00001b38 0 FUNC GLOBAL DEFAULT 7 __gnu_Unwind_Restore_VFP_ 45: 00001bf8 36 FUNC GLOBAL DEFAULT 7 _Unwind_RaiseException 46: 00003384 0 NOTYPE GLOBAL DEFAULT ABS __bss_start 47: 000033a0 0 NOTYPE GLOBAL DEFAULT ABS __end__ 48: 000015f4 28 FUNC GLOBAL DEFAULT 7 __gnu_Unwind_ForcedUnwind 49: 0000170c 32 FUNC GLOBAL DEFAULT 7 __gnu_Unwind_Resume_or_Re 50: 00000000 0 FUNC WEAK DEFAULT UND __gnu_Unwind_Find_exidx 51: 00001b04 20 FUNC GLOBAL DEFAULT 7 restore_core_regs 52: 00001be4 0 FUNC GLOBAL DEFAULT 7 __gnu_Unwind_Save_WMMXC 53: 00001d04 8 FUNC GLOBAL DEFAULT 7 _Unwind_GetTextRelBase 54: 00000c29 24 FUNC GLOBAL DEFAULT 7 Java_com_example_hellojni 55: 00001d14 56 FUNC GLOBAL DEFAULT 7 _Unwind_GetLanguageSpecif 56: 00000c40 76 FUNC GLOBAL DEFAULT 7 _Unwind_VRS_Get 57: 00001bd0 0 FUNC GLOBAL DEFAULT 7 __gnu_Unwind_Restore_WMMX 58: 000020f0 64 FUNC GLOBAL DEFAULT 7 __gnu_unwind_frame 59: 00001c64 36 FUNC GLOBAL DEFAULT 7 _Unwind_ForcedUnwind 60: 00003384 0 NOTYPE GLOBAL DEFAULT ABS _edata 61: 000033a0 0 NOTYPE GLOBAL DEFAULT ABS _end 62: 00001b8c 0 FUNC GLOBAL DEFAULT 7 __gnu_Unwind_Save_WMMXD 63: 000011b8 8 FUNC WEAK DEFAULT 7 __aeabi_unwind_cpp_pr1 64: 00001d0c 8 FUNC GLOBAL DEFAULT 7 _Unwind_GetDataRelBase 65: 00003384 0 NOTYPE GLOBAL DEFAULT 14 __data_start Histogram for bucket list length (total of 37 buckets): Length Number % of total Coverage 0 5 ( 13.5%) 1 14 ( 37.8%) 22.2% 2 9 ( 24.3%) 50.8% 3 6 ( 16.2%) 79.4% 4 2 ( 5.4%) 92.1% 5 1 ( 2.7%) 100.0% No version information found in this file. Attribute Section: aeabi File Attributes Tag_CPU_name: "5TE" Tag_CPU_arch: v5TE Tag_ARM_ISA_use: Yes Tag_THUMB_ISA_use: Thumb-1 Tag_ABI_PCS_wchar_t: 4 Tag_ABI_FP_denormal: Needed Tag_ABI_FP_exceptions: Needed Tag_ABI_FP_number_model: IEEE 754 Tag_ABI_align8_needed: Yes Tag_ABI_align8_preserved: Yes, except leaf SP Tag_ABI_enum_size: int 

You can see the difference from the one provided by @Joel F , and especially the stuff about expanding the stack (for C ++ exceptions?)

Update # 2

The problem is the toolchain included in the NDK r6b, and in particular, it is the linker. Thanks to @Joel F for bonding about the previous version of NDK. I installed NDK r5c along with NDK r6b and compare the results. Compilers produce the same object files from both tool chains, but after the comparison results differ from each other.

+6
source share
3 answers

EDIT 3 I was able to play the 10KB hello-jni binary with NDK r6b. I found a terrible hack here . Basically put this line in one of your files:

 char __aeabi_unwind_cpp_pr0[0]; 

But your code will be deprived of exception handling (which I thought was the point -fno-exceptions ....)

Anyway, this leads to libhello-jni.so up to 2228 bytes for me. Still larger than r5c, but much less than 10K.

EDIT 2 Yes, it looks like you have a lot of C ++ overhead in your readelf file. Perhaps they changed something between r5c and r6b? R5c hello-jni.c is missing C ++ code.

I have 160 lines of C code that NDK r5c boils down to 3.8KB.so. Try the following to find out what is responsible for the size:

 /path/to/ndk/toolchains/arm-linux-androideabi-4.4.3/prebuilt/<platform>/bin/arm-linux-androideabi-readelf -a libmylib.so 

EDIT I built the hello-jni example using the NDK r5c, and the resulting libhello-jni.so file is 1588 bytes.

Build Command:

 ndk-build V=1 

Create output: http://pastebin.com/AdRDVbnF (obviously, SO has limits on the length of the string or something like that).

Exiting readelf -a libhello-jni.so :

  ELF Header:
   Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 00 
   Class: ELF32
   Data: 2 complement, little endian
   Version: 1 (current)
   OS / ABI: UNIX - System V
   ABI Version: 0
   Type: DYN (Shared object file)
   Machine: ARM
   Version: 0x1
   Entry point address: 0x2dc
   Start of program headers: 52 (bytes into file)
   Start of section headers: 1108 (bytes into file)
   Flags: 0x5000002, has entry point, Version5 EABI
   Size of this header: 52 (bytes)
   Size of program headers: 32 (bytes)
   Number of program headers: 5
   Size of section headers: 40 (bytes)
   Number of section headers: 12
   Section header string table index: 11

 Section Headers:
   [Nr] Name Type Addr Off Size ES Flg Lk Inf Al
   [0] NULL 00000000000000000000 00 0 0 0
   [1] .hash HASH 000000d4 0000d4 00004c 04 A 2 0 4
   [2] .dynsym DYNSYM 00000120 000120 0000e0 10 A 3 2 4
   [3] .dynstr STRTAB 00000200 000200 0000db 00 A 0 0 1
   [4] .text PROGBITS 000002dc 0002dc 00002c 00 AX 0 0 4
   [5] .rodata PROGBITS 00000308 000308 000014 00 A 0 0 4
   [6] .ARM.exidx ARM_EXIDX 0000031c 00031c 000008 00 AL 4 0 4
   [7] .dynamic DYNAMIC 00001324 000324 000088 08 WA 3 0 4
   [8] .got PROGBITS 000013ac 0003ac 00000c 04 WA 0 0 4
   [9] .comment PROGBITS 00000000 0003b8 000012 00 0 0 1
   [10] .ARM.attributes ARM_ATTRIBUTES 00000000 0003ca 000029 00 0 0 1
   [11] .shstrtab STRTAB 00000000 0003f3 000061 00 0 0 1
 Key to Flags:
   W (write), A (alloc), X (execute), M (merge), S (strings)
   I (info), L (link order), G (group), x (unknown)
   O (extra OS processing required) o (OS specific), p (processor specific)

 There are no section groups in this file.

 Program Headers:
   Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
   EXIDX 0x00031c 0x0000031c 0x0000031c 0x00008 0x00008 R 0x4
   LOAD 0x000000 0x00000000 0x00000000 0x00324 0x00324 RE 0x1000
   LOAD 0x000324 0x00001324 0x00001324 0x00094 0x00094 RW 0x1000
   DYNAMIC 0x000324 0x00001324 0x00001324 0x00088 0x00088 RW 0x4
   GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4

  Section to Segment mapping:
   Segment Sections ...
    00 .ARM.exidx 
    01 .hash .dynsym .dynstr .text .rodata .ARM.exidx 
    02 .dynamic .got 
    03 .dynamic 
    04     

 Dynamic section at offset 0x324 contains 12 entries:
   Tag Type Name / Value
  0x00000001 (NEEDED) Shared library: [libc.so]
  0x00000001 (NEEDED) Shared library: [libstdc ++. So]
  0x00000001 (NEEDED) Shared library: [libm.so]
  0x00000001 (NEEDED) Shared library: [libdl.so]
  0x0000000e (SONAME) Library soname: [libhello-jni.so]
  0x00000010 (SYMBOLIC) 0x0
  0x00000004 (HASH) 0xd4
  0x00000005 (STRTAB) 0x200
  0x00000006 (SYMTAB) 0x120
  0x0000000a (STRSZ) 219 (bytes)
  0x0000000b (SYMENT) 16 (bytes)
  0x00000000 (NULL) 0x0

 There are no relocations in this file.

 There are no unwind sections in this file.

 Symbol table '.dynsym' contains 14 entries:
    Num: Value Size Type Bind Vis Ndx Name
      0: 00000000 0 NOTYPE LOCAL DEFAULT UND 
      1: 000002dc 0 SECTION LOCAL DEFAULT 4 
      2: 00000324 0 NOTYPE GLOBAL DEFAULT ABS __exidx_end
      3: 00000000 0 FUNC GLOBAL DEFAULT UND __aeabi_unwind_cpp_pr0
      4: 000013b8 0 NOTYPE GLOBAL DEFAULT ABS _bss_end__
      5: 000013b8 0 NOTYPE GLOBAL DEFAULT ABS __bss_start__
      6: 0000031c 0 NOTYPE GLOBAL DEFAULT ABS __exidx_start
      7: 000013b8 0 NOTYPE GLOBAL DEFAULT ABS __bss_end__
      8: 000013b8 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
      9: 000013b8 0 NOTYPE GLOBAL DEFAULT ABS __end__
     10: 000002dd 44 FUNC GLOBAL DEFAULT 4 Java_com_example_hellojni
     11: 000013b8 0 NOTYPE GLOBAL DEFAULT ABS _edata
     12: 000013b8 0 NOTYPE GLOBAL DEFAULT ABS _end
     13: 000013b8 0 NOTYPE GLOBAL DEFAULT 8 __data_start

 Histogram for bucket list length (total of 3 buckets):
  Length Number% of total Coverage
       0 0 (0.0%)
       1 0 (0.0%) 0.0%
       2 1 (33.3%) 16.7%
       3 0 (0.0%) 16.7%
       4 1 (33.3%) 50.0%
       5 0 (0.0%) 50.0%
       6 1 (33.3%) 100.0%

 No version information found in this file.
 Attribute Section: aeabi
 File attribute
   Tag_CPU_name: "5TE"
   Tag_CPU_arch: v5TE
   Tag_THUMB_ISA_use: Thumb-1
   Tag_ABI_PCS_wchar_t: 4
   Tag_ABI_FP_denormal: Needed
   Tag_ABI_FP_exceptions: Needed
   Tag_ABI_FP_number_model: IEEE 754
   Tag_ABI_align8_needed: Yes
   Tag_ABI_align8_preserved: Yes, except leaf SP
   Tag_ABI_enum_size: int
   Tag_ABI_optimization_goals: Aggressive Debug
+4
source

The NDK toolchain supports C ++ exceptions since NDK r5, however, all C ++ sources are compiled with -fno-exceptions support by default for compatibility reasons with previous versions.

To enable it, use the C ++ compiler flag '-fexceptions'. This can be done by adding the following to each module definition in your Android.mk:

LOCAL_CPPFLAGS + = -fexceptions Simply put, add one line to Application.mk, this parameter will be automatically applied to all your NDK project modules:

APP_CPPFLAGS + = -fexceptions

follow this page

But in NDK r6b, this FLAG is open by default and cannot be closed.

+2
source

Do not use C ++ exceptions if you want to have a lightweight mobile program. There C ++ reasons were limited in early versions of ndk. Android itself uses a subset of C ++ that was considered cost-effective on the mobile platform, and this is more or less what was originally supported.

Also, make sure that you are not debugging with symbols enabled - this might be your hi-jni bloat.

+1
source

Source: https://habr.com/ru/post/896803/


All Articles