Android學習之--prelink

最近移植一些既存的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 Prelink代碼分析


加速 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。背景知識可參考以下文獻:

gnu hashstyle 採用 Daniel J Bernstein 提出的 hash function,Jakub Jelinek 做了分析,比較原本 ELF hash:

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 帶來更快的動態連結時間,但付出的代價 (空間) 較高,而且不若後者有彈性。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章