news 2026/4/28 8:34:22

ASM开源库实现函数耗时插桩

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ASM开源库实现函数耗时插桩

文章目录

    • 一、ASM简介
      • 1. 设计框架
      • 2. 设计模式:访问者模式和责任链模式
      • 3. visitor访问顺序
    • 二、ASM插桩常见用途
      • 1. 性能监控优化
      • 2. 自动化埋点与数据采集(无痕埋点)
      • 3. 热修复与功能动态化
      • 4. 隐私合规与安全改造
    • 三、ASM实现函数耗时统计
      • 1. AGP环境
      • 2. 插件类
      • 3. 生成ClassVisitor的工厂类
      • 4. 函数插桩实现。
      • 5. 插桩实现的效果
    • 四、常用的工具类
    • 五、ASM插桩经典架构
      • 1. 经典架构
      • 2. 优化ClassReader读取效率
      • 3. 优化原理
    • 六、类结构
    • 七、参考资料

原文链接: https://blog.csdn.net/followYouself/article/details/160512010

一、ASM简介

1. 设计框架

说明功能定位核心职责关键函数
ClassReader数据解析器负责解析原始类的字节数组,并将其结构化的事件流传递给访问者对象,驱动整个流程。构造函数:ClassReader( byte[] classFile)
接收Visitor:void accept(ClassVisitor classVisitor, int parsingOptions)
ClassVisitor访问者接口/抽象类定义了类各个结构(如字段、方法、注解)的访问方法,是修改字节码的入口。构造函数:ClassVisitor(int api, ClassVisitor classVisitor)
方法:visitvisitOuterClassvisitAnnotationvisitFieldvisitMethod
ClassWriter字节码生成器继承自ClassVisitor,负责接收访问事件并生成修改后的二进制字节数组。构造函数:ClassWriter(ClassReader classReader, int flags)
生成类:byte[] toByteArray()

2. 设计模式:访问者模式和责任链模式

  1. 访问者接口 (Visitor):定义了访问每一个具体元素的方法visit(Element)
  2. 具体访问者 (Concrete Visitor):实现访问者接口,负责定义具体的算法/操作逻辑。
  3. 元素接口 (Element):定义一个accept(Visitor)方法,允许访问者访问。
  4. 具体元素 (Concrete Element):实现accept方法,并在该方法内部回调访问者的visit方法。
  5. 对象结构 (Object Structure):用于存储和遍历元素对象集合(如列表或树)。

3. visitor访问顺序

  • 类:visit visitSource? visitOuterClass? (visitAnnotation|visitAttribute)* (visitInnerClass|visitField|visitMethod)* visitEnd
  • 函数方法:visitAnnotationDefault? (visitAnnotation|visitParameterAnnotation|visitAttribute)* (visitCode (visitTryCatchBlock|visitLabel|visitFrame|visitXxxInsn|visitLocalVariable|visitLineNumber)* visitMaxs)? visitEnd

二、ASM插桩常见用途

1. 性能监控优化

  • 批量统计函数执行耗时,自动在方法开头插入System.currentTimeMillis(),结尾插入计算与上报逻辑,用于定位启动慢、卡顿的方法。比如BlockCanary
  • 批量trace插桩。启动速度优化:在ApplicationActivity关键生命周期方法中插入 Trace 开关,精准统计冷启动、温启动各阶段耗时。

2. 自动化埋点与数据采集(无痕埋点)

  • 全量页面访问统计:拦截Activity.onCreate/onResumeFragment.onResume,自动上报页面名称、停留时长。
  • 点击事件埋点:在View.OnClickListener.onClick执行前插入代码,获取控件 ID、文本、位置等信息进行上报。
  • 列表曝光统计:结合RecyclerViewonBindViewHolder或滚动监听,插入曝光标记代码。

3. 热修复与功能动态化

热修复框架的核心机制之一就是通过字节码插桩为每个方法预留“补丁”入口。

  • 方法替换(Method Hook):在每个方法开头插入一个静态方法调用,检查是否有需要执行的补丁代码,如有则跳转执行补丁,实现不重启修复线上 bug。代表框架:TinkerSophix
  • 资源修复/So 修复:同样可在初始化阶段插入代码,实现资源路径或 So 加载路径的替换。

4. 隐私合规与安全改造

  • 敏感 API 统一拦截/替换:扫描所有调用TelephonyManager.getDeviceId()Settings.Secure.getString()(获取 Android ID)、MAC地址获取等代码行,替换为返回“合规空值”或统一管理,以适应监管要求。
  • 增加try catch安全防护:对一些通用逻辑增加catch保护,减少线上崩溃。

三、ASM实现函数耗时统计

1. AGP环境

  • AGP 7.x以后,支持使用AsmClassVisitorFactory实现ASM字节码插桩,废弃掉传统的transform API接口。

  • Gradle插件实现参考:https://blog.csdn.net/followYouself/article/details/160449805

2. 插件类

packagecom.example.asm.testimportcom.android.build.api.instrumentation.FramesComputationModeimportcom.android.build.api.instrumentation.InstrumentationScopeimportcom.android.build.api.variant.AndroidComponentsExtensionimportorg.gradle.api.Pluginimportorg.gradle.api.ProjectclassAsmPlugin:Plugin<Project>{overridefunapply(project:Project){project.logger.lifecycle("=========== ASM Method Time Cost Plugin Applied ===============")LogUtil.init(project.logger)valandroidComponents=project.extensions.getByType(AndroidComponentsExtension::class.java)androidComponents.onVariants{variant->project.logger.quiet("注册 ASM 变换到 variant:${variant.name}")// 注册 AsmClassVisitorFactoryvariant.instrumentation.transformClassesWith(AsmClassVisitorFactoryImpl::class.java,InstrumentationScope.PROJECT){// 配置参数(如果需要)}// 设置 ASM frames 计算模式,对应ASM中的ClassWriter.COMPUTE_FRAMESvariant.instrumentation.setAsmFramesComputationMode(FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS)}}}

3. 生成ClassVisitor的工厂类

packagecom.example.asm.testimportcom.android.build.api.instrumentation.AsmClassVisitorFactoryimportcom.android.build.api.instrumentation.ClassContextimportcom.android.build.api.instrumentation.ClassDataimportcom.android.build.api.instrumentation.InstrumentationParametersimportorg.objectweb.asm.ClassVisitorimportorg.objectweb.asm.util.TraceClassVisitorimportorg.objectweb.asm.util.CheckClassAdapterimportjava.io.PrintWriterabstractclassAsmClassVisitorFactoryImpl:AsmClassVisitorFactory<InstrumentationParameters.None>{overridefuncreateClassVisitor(classContext:ClassContext,nextClassVisitor:ClassVisitor):ClassVisitor{// 责任链模式valcheckClassVisitor=CheckClassAdapter(nextClassVisitor)// 检查asm修改后的代码是否符合规范,如果不符合规范,会抛出异常valtraceClassVisitor=TraceClassVisitor(checkClassVisitor,PrintWriter(System.out))// 打印asm修改后的代码valcv=MethodTimeCostClassVisitor(traceClassVisitor)returncv}// 判断是否需要对该类进行插桩,对不需要插桩的类进行过滤overridefunisInstrumentable(classData:ClassData):Boolean{returnclassData.className.contains("com.example.myapplication2.ui")}}

4. 函数插桩实现。

  • MethodTimeCostMethodVisitor继承自AdviceAdapter类,重写onMethodEnteronMethodExit放在在函数进入退出时插桩。
packagecom.example.asm.testimportorg.gradle.api.logging.Loggerimportorg.objectweb.asm.ClassVisitorimportorg.objectweb.asm.ClassWriterimportorg.objectweb.asm.MethodVisitorimportorg.objectweb.asm.Opcodesimportorg.objectweb.asm.Typeimportorg.objectweb.asm.commons.AdviceAdapterclassMethodTimeCostClassVisitor(classVisitor:ClassVisitor):ClassVisitor(Opcodes.ASM9,classVisitor){privatevallogger:Logger?=LogUtil.getLogger()init{if(classVisitorisClassWriter){logger?.quiet("classVisitor is ClassWriter instance")}logger?.lifecycle("MethodTimeCostClassVisitor 初始化")}overridefunvisit(version:Int,access:Int,name:String?,signature:String?,superName:String?,interfaces:Array<outString?>?){logger?.lifecycle("MethodTimeCostClassVisitor visit method:$name")super.visit(version,access,name,signature,superName,interfaces)}overridefunvisitMethod(access:Int,name:String?,descriptor:String?,signature:String?,exceptions:Array<outString?>?):MethodVisitor?{valmv=super.visitMethod(access,name,descriptor,signature,exceptions)returnif(mv!=null)MethodTimeCostMethodVisitor(mv,access,name,descriptor)elsemv}privateclassMethodTimeCostMethodVisitor(mv:MethodVisitor,access:Int,name:String?,descriptor:String?,privatevallogger:Logger?=null):AdviceAdapter(Opcodes.ASM9,mv,access,name,descriptor){// 用于存储开始时间的局部变量索引(long 类型需要 2 个 slot)privatevartimeVarIndex=-1overridefunonMethodEnter(){// 调用 System.currentTimeMillis() 记录开始时间mv.visitMethodInsn(INVOKESTATIC,"java/lang/System","currentTimeMillis","()J",false)// 将返回的 long 时间存储到局部变量表中timeVarIndex=newLocal(Type.LONG_TYPE)storeLocal(timeVarIndex)logger?.debug("方法$name进入时已插入时间记录代码")}overridefunonMethodExit(opcode:Int){if(timeVarIndex==-1)return// 1. 获取结束时间并计算耗时mv.visitMethodInsn(INVOKESTATIC,"java/lang/System","currentTimeMillis","()J",false)loadLocal(timeVarIndex)mv.visitInsn(LSUB)// 2. 使用 String.valueOf() 将 long 转为 Stringmv.visitMethodInsn(INVOKESTATIC,"java/lang/String","valueOf","(J)Ljava/lang/String;",false)// 3. 拼接字符串:"methodName cost: " + duration + " ms"mv.visitTypeInsn(NEW,"java/lang/StringBuilder")mv.visitInsn(DUP)mv.visitMethodInsn(INVOKESPECIAL,"java/lang/StringBuilder","<init>","()V",false)mv.visitLdcInsn("method$namecost: ")mv.visitMethodInsn(INVOKEVIRTUAL,"java/lang/StringBuilder","append","(Ljava/lang/String;)Ljava/lang/StringBuilder;",false)mv.visitInsn(SWAP)// 交换 StringBuilder 和 duration 字符串mv.visitMethodInsn(INVOKEVIRTUAL,"java/lang/StringBuilder","append","(Ljava/lang/String;)Ljava/lang/StringBuilder;",false)// 拼接耗时mv.visitLdcInsn(" ms")// 拼接msmv.visitMethodInsn(INVOKEVIRTUAL,"java/lang/StringBuilder","append","(Ljava/lang/String;)Ljava/lang/StringBuilder;",false)mv.visitMethodInsn(INVOKEVIRTUAL,"java/lang/StringBuilder","toString","()Ljava/lang/String;",false)// 4. 调用 Log.i()mv.visitLdcInsn("$name")mv.visitInsn(SWAP)mv.visitMethodInsn(INVOKESTATIC,"android/util/Log","i","(Ljava/lang/String;Ljava/lang/String;)I",false)mv.visitInsn(POP)logger?.debug("方法$name退出时已插入耗时计算代码")}}}

5. 插桩实现的效果

四、常用的工具类

类名作用
TraceClassVisitor打印转换完成后的字节码
CheckClassAdapter校验字节码文件是否合法,字节码文件不合法时抛出编译异常。
AdviceAdapter有函数进入和退出的回调,用于实现ASM插桩。不用考虑帧结构问题
valcheckClassVisitor=CheckClassAdapter(nextClassVisitor)// 检查asm修改后的代码是否符合规范,如果不符合规范,会抛出异常valtraceClassVisitor=TraceClassVisitor(checkClassVisitor,PrintWriter(System.out))// 打印asm修改后的代码valcv=MethodTimeCostClassVisitor(traceClassVisitor)//责任链模式,最外层的classVisitor先执行。插桩类在最外层

五、ASM插桩经典架构

1. 经典架构

byte[]b1=...;ClassWritercw=newClassWriter(0);// cv 将所有事件转发给 cwClassVisitorcv=newClassVisitor(ASM4,cw){// 修改类内容 };ClassReadercr=newClassReader(b1);cr.accept(cv,0);byte[]b2=cw.toByteArray();// b2 与 b1 表示同一个类

2. 优化ClassReader读取效率

  • 在构造ClassWriter时,传入ClassReader对象,如果方法没有修改过,classReader遍历方法时,就会直接将原方法拷贝,而不详细解析方法体。
  • 实际上ClassReader是将symbolTable传入ClassWriter。用于直接解析方法相关的位置信息。
byte[]b1=...ClassReadercr=newClassReader(b1);ClassWritercw=newClassWriter(cr,0);// 优化点ClassVisitorcv=newClassVisitor(ASM4,cw){// 修改类内容 };cr.accept(ca,0);byte[]b2=cw.toByteArray();

3. 优化原理

  • 优化源码参考:org.objectweb.asm.ClassReader#readMethod,参考文档:https://www.yuque.com/mikaelzero/asm/bwbaz7
  • 在ClassReader组件的accept方法参数中传送了ClassVisitor,如果ClassReader检测到这个ClassVisitor返回的MethodVisitor来自一个ClassWriter,这意味着这个方法的内容将不会被转换,事实上,应用程序甚至不会 看到其内容。 在这种情况下,ClassReader组件不会分析这个方法的内容,不会生成相应事件,只是复制ClassWriter中表示这个方法的字节数组。
// If the returned MethodVisitor is in fact a MethodWriter, it means there is no method// adapter between the reader and the writer. In this case, it might be possible to copy// the method attributes directly into the writer. If so, return early without visiting// the content of these attributes.if(methodVisitorinstanceofMethodWriter){MethodWritermethodWriter=(MethodWriter)methodVisitor;if(methodWriter.canCopyMethodAttributes(this,synthetic,(context.currentMethodAccessFlags&Opcodes.ACC_DEPRECATED)!=0,readUnsignedShort(methodInfoOffset+4),signatureIndex,exceptionsOffset)){methodWriter.setMethodAttributesSource(methodInfoOffset,currentOffset-methodInfoOffset);returncurrentOffset;}}

六、类结构

类结构详细结构
类信息修饰符、类名字、超类、接口
常量池数值、字符串、类型常量
其他源文件名、封装的类引用、注释*、属性*
内部类*名称
字段*修饰符、名字、类型、注释*、属性*
方法*修饰符、名字、返回类型与参数类型、注释*、属性*、编译后的代码
finalintaccess// 访问权限finalStringname// 函数名、字段名finalStringdescriptor// 字段的类型,函数的描述符finalStringsignature// 泛型信息

七、参考资料

  1. ASM版本implementation "org.ow2.asm:asm:7.2"
  2. 文档:https://www.yuque.com/mikaelzero/asm
  3. 原文链接,转载请附上原文出处链接和本声明:https://blog.csdn.net/followYouself/article/details/160512010
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/28 8:33:00

医疗影像AI分割技术:VISTA-3D模型解析与应用实践

1. 医疗影像分割的现状与挑战 全球每年进行超过3亿次CT扫描&#xff0c;仅美国就占8500万次。放射科医生每天需要处理海量影像数据&#xff0c;传统的人工标注方式效率低下且容易出错。以肝脏肿瘤分割为例&#xff0c;经验丰富的放射科医生完成一例标注平均需要15-20分钟&#…

作者头像 李华
网站建设 2026/4/28 8:28:45

第三届“长城杯”网数智安全大赛(防护赛)总决赛即将开启

4月28日&#xff0c;由中央网络安全和信息化委员会办公室、教育部、国家市场监督管理总局、国家数据局指导&#xff0c;中国信息安全测评中心、中国电信集团有限公司、中国移动通信集团有限公司、中国联合网络通信集团有限公司、北京师范大学联合主办的第三届“长城杯”网数智安…

作者头像 李华
网站建设 2026/4/28 8:23:54

遗传算法原理与Python实现详解

1. 遗传算法基础概念解析遗传算法(Genetic Algorithm)是一种模拟自然选择过程的优化算法&#xff0c;它通过模拟生物进化中的选择、交叉和变异机制来寻找最优解。这种算法特别适合解决复杂的非线性问题&#xff0c;在机器学习、工程优化和金融建模等领域都有广泛应用。我第一次…

作者头像 李华
网站建设 2026/4/28 8:21:22

RK3588开发板驱动AMD显卡实战与优化

1. 项目背景与硬件选型在嵌入式系统领域&#xff0c;将独立显卡与ARM架构单板计算机(SBC)结合一直是个有趣的技术挑战。Rockchip RK3588处理器的出现改变了游戏规则——它搭载的PCIe接口不再像前代RK3399那样受限于32MB寻址空间。这个突破让开发者们重新燃起了在ARM平台上使用独…

作者头像 李华
网站建设 2026/4/28 8:19:21

CLUE框架:基于隐藏状态分析的LLM生成内容验证方法

1. 项目概述CLUE&#xff08;Clustering and Experience-based Verification&#xff09;是一种创新的无参数验证框架&#xff0c;专门用于评估大型语言模型&#xff08;LLM&#xff09;生成内容的正确性。与传统的基于文本或置信度的方法不同&#xff0c;CLUE直接分析模型内部…

作者头像 李华