0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

安卓动态链接库文件体积优化探索实践

京东云 来源:jf_75140285 作者:jf_75140285 2024-11-21 14:07 次阅读

作者:CCO体系 尚红泽

背景介绍

应用安装包的体积影响着用户下载量、安装时长、用户磁盘占用量等多个方面,据Google Play统计,应用体积每增加6MB,安装的转化率将下降1%。

安装包的体积受诸多方面影响,针对dex、资源文件、so文件都有不同的优化策略,在此不做一一展开,本文主要记录了在研发时针对动态链接库的文件体积裁剪优化方案。

我开发的链接库使用rust语言开发,通过安卓jni接口实现java层和native层之间的相互调用。为什么使用rust主要有以下几个方面的考虑:

1.稳。安卓的JNI接口调用复杂,又涉及到native层的内存管理,随着代码量的增加,代码的安全稳定性会受到很大的挑战。使用rust开发,开发者几乎不需要考虑GC的问题,只要开发的时候按照规范老老实实写代码并且通过了编译器的检查,基本上就很难把程序写崩,这一点在代码上线后也确实得到了验证。

2.安全。传统使用C、C++开发的代码编译完成以后,如果不加保护,很容易使用反汇编工具破解,市面上比较成熟的工具如IDA、ghidra等都可以将汇编代码还原到高级语言。使用rust编译的产物,内部函数间的调用规约和传统都不一样,目前市面上还没有相对完善的反编译工具,软件的防破解能力直接上升一个数量级。

但是使用rust有一个非常明显的缺点就是编译产物体积过大。在不修改默认的rust编译选项的情况下,仅开启strip的情况下,我的动态库体积达到了495k。

优化方案

参考网上前人的经验,依次进行了以下优化方式。

调整优化等级

默认的编译优化等级是O3,该优化的目的提高代码的运行速度,但是与此同时会对部分循环进行展开,体积造成膨胀。在此我们以缩减体积为目标,将优化选项改为z,表示生成最小二进制体积:

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

优化后前后体积变化

编译选项 体积
strip 495k
strip + opt-level = 'z' 437k

开启LTO

LTO(Link Time Optimization)可以在链接时消除冗余代码,减小二进制体积——代价是更长的链接时间。

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

优化后前后体积变化

编译选项 体积
strip 495k
strip + opt-level = 'z' 437k
strip + opt-level = 'z' + lto 436k

优化效果非常不明显,聊胜于无。

Panic立刻终止

rust默认的panic会在崩溃时进行栈回溯,方便定位问题。然而会带来额外的体积增加,将这一功能使用abort替代。

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

优化后前后体积变化

编译选项 体积
strip 495k
strip + opt-level = 'z' 437k
strip + opt-level = 'z' + lto 436k
strip + opt-level = 'z' + lto + panic = 'abort' 366K

到目前为止,常规的优化手段已经用完了,后续优化需要配合一些代码的额外变动。

使用rust分析工具bloat对产物进行分析,结果如下:

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的额外开销!

移除一些无用字符串

在引入的第三方依赖里,开发者自己添加了很多字符串信息,大部分是用来完善提供运行时报错信息。通过修改、精简这些依赖库,删除无用代码,又可以省出一部分空间来。

同时,上面的优化尽管使用abort替代了panic,rust编译器仍然会生出一些格式化的字符串,使用panic_immediate_abort这个编译选项禁用这个行为。

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

优化后前后体积变化

编译选项 体积
strip 495k
strip + opt-level = 'z' 437k
strip + opt-level = 'z' + lto 436k
strip + opt-level = 'z' + lto + panic = 'abort' + 代码裁减 + panic_immediate_abort 135k

再次分析,整个文件的体积已经降到了135k,自己开发的核心代码占总代码量的52%,基本符合预期。

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

优化linker script

尽管目前文件体积已经相比一开始优化了不少,但是还没有达到接入要求。通过readelf进一步分析ELF文件的各个section,我找到了一些额外的优化空间。

$ 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

在对这些section进行优化时,有必要搞清楚每个section在程序运行的作用。

section 作用
.text 代码段
.data .rodata .bss 数据段
.plt .got .dynamic .dynsym .rela.dyn .rela.plt .shstrtab 运行时被动态链接库解析,用于动态链接。
.eh_frame .eh_frame_hdr 用于保存函数的栈帧偏移,方便栈回溯
.gnu.hash .gnu.version .gnu.version_r .hash 保存编译文件元信息

程序在正常运行时,代码段、数据段必不可少,同时需要保留动态链接需要的section。剩余的section可以移除,可以进一步优化文件体积。值得注意到是,删除.eh_frame .eh_frame_hdr后,在程序崩溃时只能得到一个崩溃地址,无法进行栈回溯。

创建一个linker script,只保留程序运行最小依赖的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.*); } }

修改编译参数,替换默认的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"]

经过一番操作,程序的体积最终裁减到了95k!完美符合要求。

总结

编译选项 体积
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


审核编辑 黄宇

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 安卓
    +关注

    关注

    5

    文章

    2136

    浏览量

    57557
  • 动态链接库
    +关注

    关注

    0

    文章

    11

    浏览量

    7076
收藏 人收藏

    相关推荐

    Linux动态链接库的基本概念

    学习Linux动态链接库是一个绕不开的话题,我们今天就一起来看一下什么是动态链接库动态链接库
    发表于 09-27 14:31 1582次阅读

    关于使用动态链接库及图像采集的问题

    我用的是方诚科技的工业相机,里面提供了一些动态链接库,包括了相机初始化,采集图像,颜色处理等函数,我以前都是用VB做的,买相机的时候他会提供VB的模块,所以用VB比较方便。现在我想用LABVIEW做
    发表于 05-26 18:05

    什么是动态链接库?如何编写、生成DLL

    什么是动态链接库?如何编写、生成DLL
    发表于 01-17 09:54

    关于labview'的动态链接库的问题

    最近使用labview调用动态链接库,使用vs2017生成dll文件,然后调用,但是为什么输入数组的情况下输出一直为0呢,我使用公式节点调用同样的c语言,就没问题?请教大佬们怎么解决?还有我想问一下labview是调用公式节点的
    发表于 03-14 11:26

    8168的demos里如何加.so的动态链接库

    8168的demos里如何加c++文件生成的 .so的动态链接库
    发表于 06-21 11:56

    基于动态链接库技术的感应器非线性特性校正

    提出一种基于动态链接库技术的传感器非线性特性校正新方法。将传感器是数据采集程序与传感器的非线性特性校正算法置于同一个动态链接库中,这样应用程序从动态
    发表于 06-25 09:55 26次下载

    C++中动态链接库的创建和调用

    动态连接的创建步骤: 一、创建Non-MFC DLL动态链接库 1、打开File —> New —> Project选项,选择Win32 Dynamic-Link Library
    发表于 11-24 18:13 7次下载

    LINUX环境下CLIPS动态链接库的实现方法

    在LINUX环境下,为了简便、快捷地制作出CLIPS动态链接库,本文采用了CNU AUTOTOOLS把CLIPS嵌入式高级语言编译成动态链接库的实现方法,重点研究如何编写配置信息,利用
    发表于 04-14 21:18 30次下载

    虚拟仪器中动态链接库的应用

    本文在阐述了动态链接库技术和虚拟仪器中的 动态链接 机制的基础上,详述了基于DLL的USB接口虚拟仪器的设计的关键内容。
    发表于 07-05 17:17 27次下载
    虚拟仪器中<b class='flag-5'>动态</b><b class='flag-5'>链接库</b>的应用

    VC++动态链接库编程深入浅出

    静态链接库动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意,lib中的指令都被直接包含在最终生成的EXE文件中了。但是若
    发表于 10-21 17:03 0次下载
    VC++<b class='flag-5'>动态</b><b class='flag-5'>链接库</b>编程深入浅出

    由MATLAB的.m文件生成动态链接库的方法说明

    由MATLAB的.m文件生成动态链接库的方法说明
    发表于 08-16 18:54 0次下载

    英创信息技术WinCE设备动态链接库的制作与调用

    在使用英创ARM9系列主板做开发时,用户可能希望将自己一部分代码封装起来,隐藏代码的实现过程,只提供接口供其他程序调用。使用动态链接库(Dynamic Link Library)可以很好实现这个要求
    的头像 发表于 01-15 14:33 1184次阅读
    英创信息技术WinCE设备<b class='flag-5'>动态</b><b class='flag-5'>链接库</b>的制作与调用

    单片机高阶技能之动态链接库技术实现

    单片机高阶技能之动态链接库技术实现
    发表于 11-17 12:21 13次下载
    单片机高阶技能之<b class='flag-5'>动态</b><b class='flag-5'>链接库</b>技术实现

    Linux下的静态链接库动态链接库的区别是什么?

    学习Linux动态链接库是一个绕不开的话题,我们今天就一起来看一下什么是动态链接库动态链接库
    的头像 发表于 02-17 10:49 1351次阅读
    Linux下的静态<b class='flag-5'>链接库</b>和<b class='flag-5'>动态</b><b class='flag-5'>链接库</b>的区别是什么?

    深入探讨Linux系统中的动态链接库机制

    异常或崩溃。为深入理解动态链接机制及其工作原理,我重温了《程序员的自我修养》,并通过实践演示与反汇编分析,了解了动态链接的过程。 本文将深入
    的头像 发表于 12-18 10:06 185次阅读
    深入探讨Linux系统中的<b class='flag-5'>动态</b><b class='flag-5'>链接库</b>机制