最近移植一些既存的c程序到android中,不可避免的需要了解ndk,jni之類的東西,編譯體系,toolchain之類的東西。偶爾,發現了Android中的src中有一個叫做“LOCAL_PRELINK_MODULE”的參數,它是Android採用的加快一些系統的函數庫加載速度的手段。
因爲對這個比較感興趣,就稍微去了解了一下。
首先,需要了解一下什麼是prelink
由RedHat的大牛Jakub Jelínek所發佈的開源程序。通過修改ELF的可執行文件預先計算出函數庫的relocation的信息,來加速系統對於被prelink處理過後的函數庫的加載速度。
詳細可以參考一下資源:
http://people.redhat.com/jakub/prelink/prelink.pdf
http://jserv.blogspot.com/2010/10/android.html這篇是臺灣同胞寫的,因爲發佈在blogspot上而悲慘的被牆了,所以在本文最後貼出來方便大家查看。
接着,就看看Android中什麼地方使用到了
以下是Android的開發這David Turner在郵件列表中對於一個開發者的疑問的回答(原文鏈接):
Android使用了一個定製的prelinker tool來降低系統庫的大小和加載速度(通過預先指定可執行文件使用到的動態庫的location地址配合定製的linker來完成)。它的回答中提到了,Android中使用的linker也是定製過的,沒有完全實現一些ELF所規定的relocations相關的功能。這就會導致如果用其它的toolchain編譯出的動態庫如果用了一些Android的linker所不支持的特性,有可能就會出奇怪的問題。如果你用了其它的toolchain編譯了一些動態庫,導致在android上鍊接失敗,最好還是使用android提供的toolchain。(從linker和兩個prelink工具的代碼看,默認的android執行的linker是在/system/bin/linker)
grep了一下android源代碼,發現只有編譯dalvik時會根據是否是模擬器來顯示開啓這個選項,而其它的庫都默認是打開prelink的。
mydroid/bionic/linker/README.TXT文件中對Android中的prelink做了一些描述:android默認對所有系統的庫都會進行prelink,一旦有新的庫被加入或者更改時,需要重新更新prelink-linux-xxx.map的文件,android提供了定製的apriori和soslim(在使用)兩個工具在build的時候進行相應的prelink動作。
主要由apriori(代碼在mydroid/build/tools/apriori/下)和soslim(代碼在mydroid/build/tools/soslim/)配合完成prelink的動作
dalvik/vm/Android.mk: LOCAL_PRELINK_MODULE := true
同時,build/core/prelink-linux-arm.map是關鍵的一個配置文件。
查看dalvik的代碼,在Native.c的註釋中,有關於dalvik中使用dynamic library的一些注意事項
最後,作爲開發者我們可以如何使用它
適用於Android的系統開發者,用來定製系統級的動態庫,加速定製的android的系統的啓動及加載速度。
由於嵌入式設備尤其是android設備,目前的升級比較頻繁,一旦prelink過的庫函數修改過了,要求所有引用該庫函數的可執行文件也要被重新編譯。這就要求系統開發者謹慎的修改系統代碼,畢竟每次OTA升級的代價還是很大的。
例子:
讓我們以系統的一個可執行文件,service爲例(mydroid/out/target/product/generic/system/bin/service)
使用readelf dump出該可執行elf文件的信息,可以看到.interp這個section,並發現它是指向/system/bin/linker這個定製化的android的連接器。爲什麼這裏會有.interp這個section,主要是因爲它的Dynamic Setction明確用到了一些庫函數,而這些庫函數是被prelink過的。
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 00008114 000114 000013 00 A 0 0 1
[ 2] .hash HASH 00008128 000128 00022c 04 A 3 0 4
Dynamic section at offset 0x2068 contains 27 entries:
Tag Type Name/Value
...
0x00000004 (HASH) 0x8128
0x00000001 (NEEDED) Shared library: [liblog.so]
0x00000001 (NEEDED) Shared library: [libutils.so]
0x00000001 (NEEDED) Shared library: [libbinder.so]
0x00000001 (NEEDED) Shared library: [libc.so]
0x00000001 (NEEDED) Shared library: [libstdc++.so]
0x00000001 (NEEDED) Shared library: [libm.so]
0x00000020 (PREINIT_ARRAY) 0xa000
...
再來看看作爲動態庫的文件中prelink和不prelink的區別,以liblog.so爲例(mydroid/system/core/liblog/xxx)
兩者的編譯命令如下:
target Prelink: liblog (out/target/product/generic/symbols/system/lib/liblog.so)
out/host/linux-x86/bin/apriori --prelinkmap build/core/prelink-linux-arm.map --locals-only --quiet out/target/product/generic/obj/SHARED_LIBRARIES/liblog_intermediates/LINKED/liblog.so --output out/target/product/generic/symbols/system/lib/liblog.so
target Strip: liblog (out/target/product/generic/obj/lib/liblog.so)
out/host/linux-x86/bin/soslim --strip --shady --quiet out/target/product/generic/symbols/system/lib/liblog.so --outfile out/target/product/generic/obj/lib/liblog.so
Install: out/target/product/generic/system/lib/liblog.so
out/host/linux-x86/bin/acp -fpt out/target/product/generic/obj/lib/liblog.so out/target/product/generic/system/lib/liblog.so
target Non-prelinked: liblog (out/target/product/generic/symbols/system/lib/liblog.so)
out/host/linux-x86/bin/acp -fpt out/target/product/generic/obj/SHARED_LIBRARIES/liblog_intermediates/LINKED/liblog.so out/target/product/generic/symbols/system/lib/liblog.so
target Strip: liblog (out/target/product/generic/obj/lib/liblog.so)
out/host/linux-x86/bin/soslim --strip --shady --quiet out/target/product/generic/symbols/system/lib/liblog.so --outfile out/target/product/generic/obj/lib/liblog.so
Install: out/target/product/generic/system/lib/liblog.so
out/host/linux-x86/bin/acp -fpt out/target/product/generic/obj/lib/liblog.so out/target/product/generic/system/lib/liblog.so
分別編譯一份prelink的和沒有prelink的庫,再分別對其使用objdump和readelf查看它們區別,結果如下:
readelf.dump for prelink
------------------------------------------------------------------
[ 5] .rel.dyn REL 00000e28 000e28 000018 08 A 2 2 4
0x00000012 (RELSZ) 24 (bytes)
Relocation section '.rel.dyn' at offset 0xe28 contains 3 entries:
Offset Info Type Sym.Value Sym. Name
000030dc 00001515 R_ARM_GLOB_DAT 00000000 __stack_chk_guard
000030e0 00002d15 R_ARM_GLOB_DAT 00000000 __sF
000030e4 00003715 R_ARM_GLOB_DAT 00000000 _tolower_tab_
readelf.dump for non-prelink------------------------------------------------------------------
[ 5] .rel.dyn REL 00000e28 000e28 000030 08 A 2 2 4
0x00000012 (RELSZ) 48 (bytes)
Relocation section '.rel.dyn' at offset 0xe28 contains 6 entries:
Offset Info Type Sym.Value Sym. Name
00001034 00000017 R_ARM_RELATIVE
0000300c 00000017 R_ARM_RELATIVE
00003198 00000017 R_ARM_RELATIVE
000030dc 00001515 R_ARM_GLOB_DAT 00000000 __stack_chk_guard
000030e0 00002d15 R_ARM_GLOB_DAT 00000000 __sF
000030e4 00003715 R_ARM_GLOB_DAT 00000000 _tolower_tab_
objdump.dump for prelink
------------------------------------------------------------------
Disassembly of section .rel.dyn:
00000e28 <.rel.dyn>:
e28: 000030dc ldrdeq r3, [r0], -ip
e2c: 00001515 andeq r1, r0, r5, lsl r5
e30: 000030e0 andeq r3, r0, r0, ror #1
e34: 00002d15 andeq r2, r0, r5, lsl sp
e38: 000030e4 andeq r3, r0, r4, ror #1
e3c: 00003715 andeq r3, r0, r5, lsl r7
00001028 <__android_log_bwrite-0x18>:
1028: e28f0004 add r0, pc, #4 ; 0x4
102c: e5900000 ldr r0, [r0]
1030: eaffff8d b e6c <__android_log_bwrite-0x1d4>
1034: afa031a0 svcge 0x00a031a0
1038: 42402001 submi r2, r0, #1 ; 0x1
103c: 46c04770 undefined
Disassembly of section .fini_array:
00003008 <__FINI_ARRAY__>:
3008: ffffffff undefined instruction 0xffffffff
300c: afa01028 svcge 0x00a01028
3010: 00000000 andeq r0, r0, r0
00003014 <.dynamic>:
...
3040: 00000018 andeq r0, r0, r8, lsl r0
...Disassembly of section .data:
00003188 <__data_start>:
3188: ffffffff undefined instruction 0xffffffff
318c: ffffffff undefined instruction 0xffffffff
3190: ffffffff undefined instruction 0xffffffff
3194: ffffffff undefined instruction 0xffffffff
3198: afa010a1 svcge 0x00a010a1
objdump.dump for not-prelink------------------------------------------------------------------
Disassembly of section .rel.dyn:
00000e28 <.rel.dyn>:
e28: 00001034 andeq r1, r0, r4, lsr r0
e2c: 00000017 andeq r0, r0, r7, lsl r0
e30: 0000300c andeq r3, r0, ip
e34: 00000017 andeq r0, r0, r7, lsl r0
e38: 00003198 muleq r0, r8, r1
e3c: 00000017 andeq r0, r0, r7, lsl r0
e40: 000030dc ldrdeq r3, [r0], -ip
e44: 00001515 andeq r1, r0, r5, lsl r5
e48: 000030e0 andeq r3, r0, r0, ror #1
e4c: 00002d15 andeq r2, r0, r5, lsl sp
e50: 000030e4 andeq r3, r0, r4, ror #1
e54: 00003715 andeq r3, r0, r5, lsl r7
Disassembly of section .text:
00001028 <__android_log_bwrite-0x18>:
1028: e28f0004 add r0, pc, #4 ; 0x4
102c: e5900000 ldr r0, [r0]
1030: eaffff8d b e6c <__android_log_bwrite-0x1d4>
1034: 000031a0 andeq r3, r0, r0, lsr #3
1038: 42402001 submi r2, r0, #1 ; 0x1
103c: 46c04770 undefined
Disassembly of section .fini_array:
00003008 <__FINI_ARRAY__>:
3008: ffffffff undefined instruction 0xffffffff
300c: 00001028 andeq r1, r0, r8, lsr #32
3010: 00000000 andeq r0, r0, r0
Disassembly of section .dynamic:
00003014 <.dynamic>:
...
3040: 00000030 andeq r0, r0, r0, lsr r0...
Disassembly of section .data:
00003188 <__data_start>:
3188: ffffffff undefined instruction 0xffffffff
318c: ffffffff undefined instruction 0xffffffff
3190: ffffffff undefined instruction 0xffffffff
3194: ffffffff undefined instruction 0xffffffff
3198: 000010a1 andeq r1, r0, r1, lsr #1
從兩者的數據很明顯看出來prelink的要比non-prelink的數據簡單一些,.so的大小也小一些。再跟prelink-linux-arm.map中的liblog.so所分配的地址一比較,可以很容易看到所有的prelink好的數據的地址在prelink類別的dump結果中都是從0xAFA00000開始的
參考資料:
加速 Android 的動態連結
就如人們所熟悉,Android 底層運作 Dalvik 虛擬機器,在 C Library 層面採用衍生自 NetBSD libc 的 bionic。與過往的
Java 為基礎的操作環境 (OE; Operating Environment,如 Transvirtual 的 PocketLinux / XOE) 不同的是,每個 VM instance 均為獨立的 Linux process,且由 Zygote 所「孵化」。就係統的角度來說,縱使底層對
system library 做了 prelink (透過工具 build/tools/apriori),但因語言實做的特性,需頻繁經由 JNI 去存取底層服務,也就是說,有大量的 dlopen()/dlsym() 操作,這些均無法透過 prelink 來縮減載入時間。
在 COSCUP 2010 的議程「打造特製的
Android Toolchain」中,小弟介紹了 0xlab 近來的幾項嘗試與改進項目,除了 GNU Toolchain (採用 gcc-4.4.4 搭配一系列的修改)
之外,就包含 Android bionic libc 與 prelink 的修改,企圖引入 DT_GNU_HASH 的機制 (也稱為 gnu hashstyle,或 gnu hash),以加速 Android 的動態連結。現在基礎工作大致完畢,陸續提交到 AOSP (Android Open Source Project) 的 Code
Review,應該會納入 Android Gingerbread 以及後續的版本中。實驗平臺採用 Qualcomm MSM7x25 (arm1136j-s),平均縮減 26% 的 ELF 動態連結時間,這對所有的 Android Activity (Java 程式) 與需要額外作 dlopen() 處理的系統函式庫,如 Qualcomm 的 camera HAL/service 或 Opencore/DSP,均可適用。
這過程中,讓小弟學習到頗多,從一開始的分析 (透過 oprofile 與開啟 bionic 裡頭 linker 的除錯資訊),觀察到 Android 的動態連結處理,有點類似 OpenOffice 所面臨的議題,於是嘗試引入 DT_GNU_HASH。背景知識可參考以下文獻:
- LWN 清晰的介紹文章 "Optimizing Linker Load Times",涵蓋四項 linker 參數背後的處理機制: -Wl,-O1, -Bdirect Linking, dynsor, gnu hashstyle (Precomputed Hash Values)
- Ali Bahrami 的 blog 文章 "GNU Hash ELF Sections" 與 "The
Cost Of ELF Symbol Hashing",淺顯易懂
- glibc 維護者 Ulrich Drepper 的論文 "How To Write Shared Libraries" (不要被 How To 字眼騙了,其實頗深入),廣泛探討從歷史因素到 DSO 運作機制,以及 GNU Hackers 如何參照 Sun Microsystems 過往的改良機制,重新實做出 gnu hashstyle
The number of collisions in the 537403 symbols is:
name 2sym collision # 3sym collision # more than 3sym collision #
sysv 1749 5
libiberty 42
dcache 37
djb2 29
sdbm 39
djb3 31
rot 337 39 61
sax 34
fnv 40
oat 30
sysv 那項是原本的 hash function,而 djb2 則是 GNU Toolchain 採用的方案。就現有自由軟體的實做來看,包含 glibc, uClibc, dietlibc (前三者支援 GNU/Linux), FreeBSD libc 均提供 gnu hashstyle 的支援,但 Android 未正式在 bionic 提供。需留意的是,prelink 方案總是比 gnu hashstyle 帶來更快的動態連結時間,但付出的代價 (空間) 較高,而且不若後者有彈性。