RM新时代网站-首页

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線(xiàn)課程
  • 觀(guān)看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

安卓動(dòng)態(tài)鏈接庫(kù)文件體積優(yōu)化探索實(shí)踐

京東云 ? 來(lái)源:jf_75140285 ? 作者:jf_75140285 ? 2024-11-21 14:07 ? 次閱讀

作者:CCO體系 尚紅澤

背景介紹

應(yīng)用安裝包的體積影響著用戶(hù)下載量、安裝時(shí)長(zhǎng)、用戶(hù)磁盤(pán)占用量等多個(gè)方面,據(jù)Google Play統(tǒng)計(jì),應(yīng)用體積每增加6MB,安裝的轉(zhuǎn)化率將下降1%。

安裝包的體積受諸多方面影響,針對(duì)dex、資源文件、so文件都有不同的優(yōu)化策略,在此不做一一展開(kāi),本文主要記錄了在研發(fā)時(shí)針對(duì)動(dòng)態(tài)鏈接庫(kù)的文件體積裁剪優(yōu)化方案。

我開(kāi)發(fā)的鏈接庫(kù)使用rust語(yǔ)言開(kāi)發(fā),通過(guò)安卓jni接口實(shí)現(xiàn)java層和native層之間的相互調(diào)用。為什么使用rust主要有以下幾個(gè)方面的考慮:

1.穩(wěn)。安卓的JNI接口調(diào)用復(fù)雜,又涉及到native層的內(nèi)存管理,隨著代碼量的增加,代碼的安全穩(wěn)定性會(huì)受到很大的挑戰(zhàn)。使用rust開(kāi)發(fā),開(kāi)發(fā)者幾乎不需要考慮GC的問(wèn)題,只要開(kāi)發(fā)的時(shí)候按照規(guī)范老老實(shí)實(shí)寫(xiě)代碼并且通過(guò)了編譯器的檢查,基本上就很難把程序?qū)懕?,這一點(diǎn)在代碼上線(xiàn)后也確實(shí)得到了驗(yàn)證。

2.安全。傳統(tǒng)使用C、C++開(kāi)發(fā)的代碼編譯完成以后,如果不加保護(hù),很容易使用反匯編工具破解,市面上比較成熟的工具如IDA、ghidra等都可以將匯編代碼還原到高級(jí)語(yǔ)言。使用rust編譯的產(chǎn)物,內(nèi)部函數(shù)間的調(diào)用規(guī)約和傳統(tǒng)都不一樣,目前市面上還沒(méi)有相對(duì)完善的反編譯工具,軟件的防破解能力直接上升一個(gè)數(shù)量級(jí)。

但是使用rust有一個(gè)非常明顯的缺點(diǎn)就是編譯產(chǎn)物體積過(guò)大。在不修改默認(rèn)的rust編譯選項(xiàng)的情況下,僅開(kāi)啟strip的情況下,我的動(dòng)態(tài)庫(kù)體積達(dá)到了495k。

優(yōu)化方案

參考網(wǎng)上前人的經(jīng)驗(yàn),依次進(jìn)行了以下優(yōu)化方式。

調(diào)整優(yōu)化等級(jí)

默認(rèn)的編譯優(yōu)化等級(jí)是O3,該優(yōu)化的目的提高代碼的運(yùn)行速度,但是與此同時(shí)會(huì)對(duì)部分循環(huán)進(jìn)行展開(kāi),體積造成膨脹。在此我們以縮減體積為目標(biāo),將優(yōu)化選項(xiàng)改為z,表示生成最小二進(jìn)制體積:

[profile.release] opt-level = 'z'

優(yōu)化后前后體積變化

編譯選項(xiàng) 體積
strip 495k
strip + opt-level = 'z' 437k

開(kāi)啟LTO

LTO(Link Time Optimization)可以在鏈接時(shí)消除冗余代碼,減小二進(jìn)制體積——代價(jià)是更長(zhǎng)的鏈接時(shí)間。

Cargo.toml [profile.release] opt-level = 'z' lto = true

優(yōu)化后前后體積變化

編譯選項(xiàng) 體積
strip 495k
strip + opt-level = 'z' 437k
strip + opt-level = 'z' + lto 436k

優(yōu)化效果非常不明顯,聊勝于無(wú)。

Panic立刻終止

rust默認(rèn)的panic會(huì)在崩潰時(shí)進(jìn)行?;厮荩奖愣ㄎ粏?wèn)題。然而會(huì)帶來(lái)額外的體積增加,將這一功能使用abort替代。

[profile.release] opt-level = 'z' lto = true panic = 'abort'

優(yōu)化后前后體積變化

編譯選項(xiàng) 體積
strip 495k
strip + opt-level = 'z' 437k
strip + opt-level = 'z' + lto 436k
strip + opt-level = 'z' + lto + panic = 'abort' 366K

到目前為止,常規(guī)的優(yōu)化手段已經(jīng)用完了,后續(xù)優(yōu)化需要配合一些代碼的額外變動(dòng)。

使用rust分析工具bloat對(duì)產(chǎn)物進(jìn)行分析,結(jié)果如下:

File .text Size Crate 4.1% 69.0% 192.7KiB std 1.0% 16.8% 46.9KiB jdmp 0.5% 8.1% 22.7KiB [Unknown] 0.2% 3.8% 10.5KiB jni 0.0% 0.5% 1.5KiB cesu8 0.0% 0.4% 1.1KiB adler32 0.0% 0.3% 904B bytes 0.0% 0.2% 640B aho_corasick 0.0% 0.2% 588B regex_syntax 0.0% 0.2% 572B regex_automata 0.0% 0.2% 440B log 0.0% 0.1% 304B memchr 0.0% 0.0% 52B combine 0.0% 0.0% 8B jni_sys

讓我感到驚訝的是我的核心代碼jdmp模塊只占了46.9k,為此要額外引入幾百k的額外開(kāi)銷(xiāo)!

移除一些無(wú)用字符串

在引入的第三方依賴(lài)?yán)?,開(kāi)發(fā)者自己添加了很多字符串信息,大部分是用來(lái)完善提供運(yùn)行時(shí)報(bào)錯(cuò)信息。通過(guò)修改、精簡(jiǎn)這些依賴(lài)庫(kù),刪除無(wú)用代碼,又可以省出一部分空間來(lái)。

同時(shí),上面的優(yōu)化盡管使用abort替代了panic,rust編譯器仍然會(huì)生出一些格式化的字符串,使用panic_immediate_abort這個(gè)編譯選項(xiàng)禁用這個(gè)行為。

.cargo/config.toml [unstable] build-std-features = ["panic_immediate_abort"] build-std = ["std","panic_abort"]

優(yōu)化后前后體積變化

編譯選項(xiàng) 體積
strip 495k
strip + opt-level = 'z' 437k
strip + opt-level = 'z' + lto 436k
strip + opt-level = 'z' + lto + panic = 'abort' + 代碼裁減 + panic_immediate_abort 135k

再次分析,整個(gè)文件的體積已經(jīng)降到了135k,自己開(kāi)發(fā)的核心代碼占總代碼量的52%,基本符合預(yù)期。

File .text Size Crate 14.2% 52.0% 41.3KiB jdmp 3.2% 11.7% 9.3KiB core 3.1% 11.4% 9.1KiB jni 3.0% 11.0% 8.8KiB [Unknown] 1.9% 6.8% 5.4KiB std 0.9% 3.3% 2.6KiB alloc 0.3% 1.1% 936B cesu8 0.3% 1.0% 792B adler32 0.1% 0.5% 372B aho_corasick 0.1% 0.4% 316B regex_automata 0.1% 0.3% 220B log 0.1% 0.3% 216B hashbrown 0.0% 0.1% 108B bytes 0.0% 0.1% 44B combine 0.0% 0.1% 44B rustc_demangle 0.0% 0.0% 8B compiler_builtins 0.0% 0.0% 8B jni_sys

優(yōu)化linker script

盡管目前文件體積已經(jīng)相比一開(kāi)始優(yōu)化了不少,但是還沒(méi)有達(dá)到接入要求。通過(guò)readelf進(jìn)一步分析ELF文件的各個(gè)section,我找到了一些額外的優(yōu)化空間。

$ aarch64-linux-gnu-readelf -S target/aarch64-linux-android/release/libjdmp.so There are 24 section headers, starting at offset 0x21738: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .note.android.ide NOTE 0000000000000270 00000270 0000000000000098 0000000000000000 A 0 0 4 [ 2] .dynsym DYNSYM 0000000000000308 00000308 00000000000002e8 0000000000000018 A 7 1 8 [ 3] .gnu.version VERSYM 00000000000005f0 000005f0 000000000000003e 0000000000000002 A 2 0 2 [ 4] .gnu.version_r VERNEED 0000000000000630 00000630 0000000000000040 0000000000000000 A 7 2 4 [ 5] .gnu.hash GNU_HASH 0000000000000670 00000670 0000000000000024 0000000000000000 A 2 0 8 [ 6] .hash HASH 0000000000000694 00000694 0000000000000100 0000000000000004 A 2 0 4 [ 7] .dynstr STRTAB 0000000000000794 00000794 000000000000014d 0000000000000000 A 0 0 1 [ 8] .rela.dyn RELA 00000000000008e8 000008e8 00000000000007f8 0000000000000018 A 2 0 8 [ 9] .rela.plt RELA 00000000000010e0 000010e0 00000000000002a0 0000000000000018 AI 2 19 8 [10] .rodata PROGBITS 0000000000001380 00001380 0000000000001d83 0000000000000000 AM 0 0 8 [11] .eh_frame_hdr PROGBITS 0000000000003104 00003104 0000000000002494 0000000000000000 A 0 0 4 [12] .eh_frame PROGBITS 0000000000005598 00005598 00000000000078cc 0000000000000000 A 0 0 8 [13] .text PROGBITS 000000000000de64 0000ce64 0000000000013e0c 0000000000000000 AX 0 0 4 [14] .plt PROGBITS 0000000000021c70 00020c70 00000000000001e0 0000000000000000 AX 0 0 16 [15] .data.rel.ro PROGBITS 0000000000022e50 00020e50 0000000000000430 0000000000000000 WA 0 0 8 [16] .fini_array FINI_ARRAY 0000000000023280 00021280 0000000000000010 0000000000000008 WA 0 0 8 [17] .dynamic DYNAMIC 0000000000023290 00021290 0000000000000180 0000000000000010 WA 7 0 8 [18] .got PROGBITS 0000000000023410 00021410 0000000000000048 0000000000000000 WA 0 0 8 [19] .got.plt PROGBITS 0000000000023458 00021458 00000000000000f8 0000000000000000 WA 0 0 8 [20] .data PROGBITS 0000000000024550 00021550 0000000000000060 0000000000000000 WA 0 0 8 [21] .bss NOBITS 00000000000245b0 000215b0 0000000000000101 0000000000000000 WA 0 0 8 [22] .comment PROGBITS 0000000000000000 000215b0 00000000000000b2 0000000000000001 MS 0 0 1 [23] .shstrtab STRTAB 0000000000000000 00021662 00000000000000d3 0000000000000000 0 0 1

在對(duì)這些section進(jìn)行優(yōu)化時(shí),有必要搞清楚每個(gè)section在程序運(yùn)行的作用。

section 作用
.text 代碼段
.data .rodata .bss 數(shù)據(jù)段
.plt .got .dynamic .dynsym .rela.dyn .rela.plt .shstrtab 運(yùn)行時(shí)被動(dòng)態(tài)鏈接庫(kù)解析,用于動(dòng)態(tài)鏈接。
.eh_frame .eh_frame_hdr 用于保存函數(shù)的棧幀偏移,方便棧回溯
.gnu.hash .gnu.version .gnu.version_r .hash 保存編譯文件元信息

程序在正常運(yùn)行時(shí),代碼段、數(shù)據(jù)段必不可少,同時(shí)需要保留動(dòng)態(tài)鏈接需要的section。剩余的section可以移除,可以進(jìn)一步優(yōu)化文件體積。值得注意到是,刪除.eh_frame .eh_frame_hdr后,在程序崩潰時(shí)只能得到一個(gè)崩潰地址,無(wú)法進(jìn)行?;厮?。

創(chuàng)建一個(gè)linker script,只保留程序運(yùn)行最小依賴(lài)的section。

PHDRS { headers PT_PHDR PHDRS ; text PT_LOAD FILEHDR PHDRS ; data PT_LOAD ; dynamic PT_DYNAMIC ; } ENTRY(Reset); EXTERN(RESET_VECTOR); SECTIONS { . = SIZEOF_HEADERS; .text : { *(.text .text.*) } :text .rodata : { *(.rodata .rodata.*) } :text . = . + 0x1000; .data : { *(.data .data.*) *(.fini_array .fini_array.*) *(.got .got.*) *(.got.plt .got.plt.*) } : data .bss : {*(.bss .bss.*)} : data .dynamic : { *(.dynamic .dynamic.*) } :data :dynamic /DISCARD/ : { *(.ARM.exidx .ARM.exidx.*); *(.gnu.version .gnu.version.*); *(.gnu.version_r .gnu.version_r.*); *(.eh_frame_hdr .eh_frame .eh_frame_hdr.* .eh_frame.* ); *(.note.android.ident .note.android.ident.*); *(.comment .comment.*); } }

修改編譯參數(shù),替換默認(rèn)的linker script

.cargo/config.toml [build] target = ["aarch64-linux-android","armv7-linux-androideabi"] [unstable] build-std-features = ["panic_immediate_abort"] build-std = ["std","panic_abort"] [target.aarch64-linux-android] rustflags = ["-C", "link-arg=-Tlinker.lds"] [target.armv7-linux-androideabi] rustflags = ["-C", "link-arg=-Tlinker.lds"]

經(jīng)過(guò)一番操作,程序的體積最終裁減到了95k!完美符合要求。

總結(jié)

編譯選項(xiàng) 體積
strip 495k
strip + opt-level = 'z' 437k
strip + opt-level = 'z' + lto 436k
strip + opt-level = 'z' + lto + panic = 'abort' + 代碼裁減 + panic_immediate_abort 135k
strip + opt-level = 'z' + lto + panic = 'abort' + 代碼裁減 + panic_immediate_abort + 移除section 95k


審核編輯 黃宇

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀(guān)點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 安卓
    +關(guān)注

    關(guān)注

    5

    文章

    2126

    瀏覽量

    57144
  • 動(dòng)態(tài)鏈接庫(kù)

    關(guān)注

    0

    文章

    11

    瀏覽量

    7064
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    Linux動(dòng)態(tài)鏈接庫(kù)的基本概念

    學(xué)習(xí)Linux動(dòng)態(tài)鏈接庫(kù)是一個(gè)繞不開(kāi)的話(huà)題,我們今天就一起來(lái)看一下什么是動(dòng)態(tài)鏈接庫(kù)、動(dòng)態(tài)鏈接庫(kù)
    發(fā)表于 09-27 14:31 ?1544次閱讀

    關(guān)于使用動(dòng)態(tài)鏈接庫(kù)及圖像采集的問(wèn)題

    我用的是方誠(chéng)科技的工業(yè)相機(jī),里面提供了一些動(dòng)態(tài)鏈接庫(kù),包括了相機(jī)初始化,采集圖像,顏色處理等函數(shù),我以前都是用VB做的,買(mǎi)相機(jī)的時(shí)候他會(huì)提供VB的模塊,所以用VB比較方便?,F(xiàn)在我想用LABVIEW做
    發(fā)表于 05-26 18:05

    什么是動(dòng)態(tài)鏈接庫(kù)?如何編寫(xiě)、生成DLL

    什么是動(dòng)態(tài)鏈接庫(kù)?如何編寫(xiě)、生成DLL
    發(fā)表于 01-17 09:54

    關(guān)于labview'的動(dòng)態(tài)鏈接庫(kù)的問(wèn)題

    最近使用labview調(diào)用動(dòng)態(tài)鏈接庫(kù),使用vs2017生成dll文件,然后調(diào)用,但是為什么輸入數(shù)組的情況下輸出一直為0呢,我使用公式節(jié)點(diǎn)調(diào)用同樣的c語(yǔ)言,就沒(méi)問(wèn)題?請(qǐng)教大佬們?cè)趺唇鉀Q?還有我想問(wèn)一下labview是調(diào)用公式節(jié)點(diǎn)的
    發(fā)表于 03-14 11:26

    8168的demos里如何加.so的動(dòng)態(tài)鏈接庫(kù)

    8168的demos里如何加c++文件生成的 .so的動(dòng)態(tài)鏈接庫(kù)
    發(fā)表于 06-21 11:56

    基于動(dòng)態(tài)鏈接庫(kù)技術(shù)的感應(yīng)器非線(xiàn)性特性校正

    提出一種基于動(dòng)態(tài)鏈接庫(kù)技術(shù)的傳感器非線(xiàn)性特性校正新方法。將傳感器是數(shù)據(jù)采集程序與傳感器的非線(xiàn)性特性校正算法置于同一個(gè)動(dòng)態(tài)鏈接庫(kù)中,這樣應(yīng)用程序從動(dòng)態(tài)
    發(fā)表于 06-25 09:55 ?26次下載

    C++中動(dòng)態(tài)鏈接庫(kù)的創(chuàng)建和調(diào)用

    動(dòng)態(tài)連接庫(kù)的創(chuàng)建步驟: 一、創(chuàng)建Non-MFC DLL動(dòng)態(tài)鏈接庫(kù) 1、打開(kāi)File —> New —> Project選項(xiàng),選擇Win32 Dynamic-Link Library
    發(fā)表于 11-24 18:13 ?7次下載

    LINUX環(huán)境下CLIPS動(dòng)態(tài)鏈接庫(kù)的實(shí)現(xiàn)方法

    在LINUX環(huán)境下,為了簡(jiǎn)便、快捷地制作出CLIPS動(dòng)態(tài)鏈接庫(kù),本文采用了CNU AUTOTOOLS把CLIPS嵌入式高級(jí)語(yǔ)言編譯成動(dòng)態(tài)鏈接庫(kù)的實(shí)現(xiàn)方法,重點(diǎn)研究如何編寫(xiě)配置信息,利用
    發(fā)表于 04-14 21:18 ?30次下載

    虛擬儀器中動(dòng)態(tài)鏈接庫(kù)的應(yīng)用

    本文在闡述了動(dòng)態(tài)鏈接庫(kù)技術(shù)和虛擬儀器中的 動(dòng)態(tài)鏈接 庫(kù)機(jī)制的基礎(chǔ)上,詳述了基于DLL的USB接口虛擬儀器的設(shè)計(jì)的關(guān)鍵內(nèi)容。
    發(fā)表于 07-05 17:17 ?27次下載
    虛擬儀器中<b class='flag-5'>動(dòng)態(tài)</b><b class='flag-5'>鏈接庫(kù)</b>的應(yīng)用

    VC++動(dòng)態(tài)鏈接庫(kù)編程深入淺出

    靜態(tài)鏈接庫(kù)動(dòng)態(tài)鏈接庫(kù)都是共享代碼的方式,如果采用靜態(tài)鏈接庫(kù),則無(wú)論你愿不愿意,lib中的指令都被直接包含在最終生成的EXE文件中了。但是若
    發(fā)表于 10-21 17:03 ?0次下載
    VC++<b class='flag-5'>動(dòng)態(tài)</b><b class='flag-5'>鏈接庫(kù)</b>編程深入淺出

    由MATLAB的.m文件生成動(dòng)態(tài)鏈接庫(kù)的方法說(shuō)明

    由MATLAB的.m文件生成動(dòng)態(tài)鏈接庫(kù)的方法說(shuō)明
    發(fā)表于 08-16 18:54 ?0次下載

    英創(chuàng)信息技術(shù)WinCE設(shè)備動(dòng)態(tài)鏈接庫(kù)的制作與調(diào)用

    在使用英創(chuàng)ARM9系列主板做開(kāi)發(fā)時(shí),用戶(hù)可能希望將自己一部分代碼封裝起來(lái),隱藏代碼的實(shí)現(xiàn)過(guò)程,只提供接口供其他程序調(diào)用。使用動(dòng)態(tài)鏈接庫(kù)(Dynamic Link Library)可以很好實(shí)現(xiàn)這個(gè)要求
    的頭像 發(fā)表于 01-15 14:33 ?1140次閱讀
    英創(chuàng)信息技術(shù)WinCE設(shè)備<b class='flag-5'>動(dòng)態(tài)</b><b class='flag-5'>鏈接庫(kù)</b>的制作與調(diào)用

    單片機(jī)高階技能之動(dòng)態(tài)鏈接庫(kù)技術(shù)實(shí)現(xiàn)

    單片機(jī)高階技能之動(dòng)態(tài)鏈接庫(kù)技術(shù)實(shí)現(xiàn)
    發(fā)表于 11-17 12:21 ?13次下載
    單片機(jī)高階技能之<b class='flag-5'>動(dòng)態(tài)</b><b class='flag-5'>鏈接庫(kù)</b>技術(shù)實(shí)現(xiàn)

    Linux下的靜態(tài)鏈接庫(kù)動(dòng)態(tài)鏈接庫(kù)的區(qū)別是什么?

    學(xué)習(xí)Linux動(dòng)態(tài)鏈接庫(kù)是一個(gè)繞不開(kāi)的話(huà)題,我們今天就一起來(lái)看一下什么是動(dòng)態(tài)鏈接庫(kù)動(dòng)態(tài)鏈接庫(kù)
    的頭像 發(fā)表于 02-17 10:49 ?1268次閱讀
    Linux下的靜態(tài)<b class='flag-5'>鏈接庫(kù)</b>和<b class='flag-5'>動(dòng)態(tài)</b><b class='flag-5'>鏈接庫(kù)</b>的區(qū)別是什么?

    深入探討Linux系統(tǒng)中的動(dòng)態(tài)鏈接庫(kù)機(jī)制

    異常或崩潰。為深入理解動(dòng)態(tài)鏈接機(jī)制及其工作原理,我重溫了《程序員的自我修養(yǎng)》,并通過(guò)實(shí)踐演示與反匯編分析,了解了動(dòng)態(tài)鏈接的過(guò)程。 本文將深入
    的頭像 發(fā)表于 12-18 10:06 ?71次閱讀
    深入探討Linux系統(tǒng)中的<b class='flag-5'>動(dòng)態(tài)</b><b class='flag-5'>鏈接庫(kù)</b>機(jī)制
    RM新时代网站-首页