https://edu.csdn.net/course/detail/36074
https://edu.csdn.net/course/detail/35475
本文介绍了一个针对Dex进行插桩的工具,讲解了一下直接修改Dalvik字节码和Dex文件时遇到的问题和解决方法
作者:字节跳动终端技术—— 李言
线下场景中,我们经常需要在APK中插入一些检测代码,来实现一些记录方法调用耗时,或者增加一些打印日志的功能。目前的常规做法都是在编译期修改class字节码达到,例如byteX提供了方便的修改class框架。
但是,编译期修改灵活性不足,对于已经编译好的apk则无能为力,无法插桩或修改。导致很多业务方都要配置独立的jenkins打包后,才能触发进步一步的测试。一次自动化测试任务有将近一半的时间都消耗在打包过程中。
为了解决这个痛点,我们开发了一套直接针对APK(dex)插桩的工具,DexInjector。主要用来做一些日志、性能方面的数据采集和注入一些第三方工具,避免业务方二次打包,节省测试时间。
该方案已经用在日志旁路、网络数据抓取、第三方库注入,用户信息注入、日常调试等。
工具目前可以实现:
调研了一下市面上现有的字节码修改方案。
可以通过smali 和baksmali 工具将dex文件转换成可方便阅读的smali语法文件,但是smali的工具对smali字节码的解析是通过语法解析,如果要插入一个新的代码进去对寄存器等操作没有办法实现结构化操作。
redex 支持通过配置在方法前进行插桩,可以通过实现pass来完成自己的插桩功能。但是功能实现有限,使用起来比较复杂,而且在执行之后插入了一些fb自定义的代码,但Redex 提供了一套强大的字节码修改能力,后续的版本会基于redex的字节码修改能力进行完善。 https://github.com/facebook/redex/blob/master/opt/instrument/Instrument.cpp
https://android.googlesource.com/platform/tools/dexter/+/refs/heads/master
dexter 工具是google开发的一个类似dexdump的工具,但其内部实现了对dex文件结构和字节码指令的一套完整的操作api,轻量简洁,对字节码的操作可以达到ASM的体验。
综合,选用dexter对dex进行操作。
根据性能防劣化和流量统计的需求,都是在一个方法的方法体内部前后插入对其他方法的调用。以网络流量统计为例,需要在 okhttp3.RealCall.getResponseWithInterceptorChain
的方法内部开头插入一个方法来获取request请求的详细数据。
Response getResponseWithInterceptorChain() throws IOException {
com.netflow.inject.hookRealCall(this);//插入的方法
List interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
//.....省略部分代码
return chain.proceed(originalRequest);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t7GRM4c2-1645636506944)(https://img2022.cnblogs.com/blog/2399767/202202/2399767-20220223211839635-544113435.gif “点击并拖拽以移动”)]
先要分析Dex文件格式,将其序列化成各种数据结构,Dex文件的结构可以参照官方文档
Dalvik 可执行文件格式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rIpHhKPx-1645636506945)(https://tech-proxy.bytedance.net/tos/images/1645586888510_7e92820dee293b6463e23a8acccf90c5)]
在code 段将二进制的字节码解析成可处理的数据结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HHFouqaq-1645636506946)(https://tech-proxy.bytedance.net/tos/images/1645586888371_6b39dfd04bc53e45939a7f404599708f)]
按照字节码规范构造字节码指令,并插入到现有字节码的序列中即可完成字节码的插入。
将修改后的Dex结构重新计算Index,然后将各个数据Section序列化为Dex的文件格式。
插桩支持两种能力,在一个方法的方法体前面和后面插入一个静态方法调用。
如果被插入的方法为实例方法,则方法的第一参数为 this
,随后的参数和被插入的方法一致 ,如果方法是静态方法则插入的方法定义需要和被插入的方法参数类型和个数一致,举例:
public class Tracer{
//被插入的方法,为实例方法
private void MethodA(int a,int b){
}