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

news2026/5/10 14:41:53
文章目录一、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)接收Visitorvoid accept(ClassVisitor classVisitor, int parsingOptions)ClassVisitor访问者接口/抽象类定义了类各个结构如字段、方法、注解的访问方法是修改字节码的入口。构造函数ClassVisitor(int api, ClassVisitor classVisitor)方法visit、visitOuterClass、visitAnnotation、visitField、visitMethodClassWriter字节码生成器继承自ClassVisitor负责接收访问事件并生成修改后的二进制字节数组。构造函数ClassWriter(ClassReader classReader, int flags)生成类byte[] toByteArray()2. 设计模式访问者模式和责任链模式访问者接口 (Visitor):定义了访问每一个具体元素的方法visit(Element)。具体访问者 (Concrete Visitor):实现访问者接口负责定义具体的算法/操作逻辑。元素接口 (Element):定义一个accept(Visitor)方法允许访问者访问。具体元素 (Concrete Element):实现accept方法并在该方法内部回调访问者的visit方法。对象结构 (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插桩。启动速度优化在Application、Activity关键生命周期方法中插入 Trace 开关精准统计冷启动、温启动各阶段耗时。2. 自动化埋点与数据采集无痕埋点全量页面访问统计拦截Activity.onCreate/onResume、Fragment.onResume自动上报页面名称、停留时长。点击事件埋点在View.OnClickListener.onClick执行前插入代码获取控件 ID、文本、位置等信息进行上报。列表曝光统计结合RecyclerView的onBindViewHolder或滚动监听插入曝光标记代码。3. 热修复与功能动态化热修复框架的核心机制之一就是通过字节码插桩为每个方法预留“补丁”入口。方法替换Method Hook在每个方法开头插入一个静态方法调用检查是否有需要执行的补丁代码如有则跳转执行补丁实现不重启修复线上 bug。代表框架Tinker、Sophix。资源修复/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/1604498052. 插件类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:PluginProject{overridefunapply(project:Project){project.logger.lifecycle( ASM Method Time Cost Plugin Applied )LogUtil.init(project.logger)valandroidComponentsproject.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:AsmClassVisitorFactoryInstrumentationParameters.None{overridefuncreateClassVisitor(classContext:ClassContext,nextClassVisitor:ClassVisitor):ClassVisitor{// 责任链模式valcheckClassVisitorCheckClassAdapter(nextClassVisitor)// 检查asm修改后的代码是否符合规范如果不符合规范会抛出异常valtraceClassVisitorTraceClassVisitor(checkClassVisitor,PrintWriter(System.out))// 打印asm修改后的代码valcvMethodTimeCostClassVisitor(traceClassVisitor)returncv}// 判断是否需要对该类进行插桩对不需要插桩的类进行过滤overridefunisInstrumentable(classData:ClassData):Boolean{returnclassData.className.contains(com.example.myapplication2.ui)}}4. 函数插桩实现。MethodTimeCostMethodVisitor继承自AdviceAdapter类重写onMethodEnter和onMethodExit放在在函数进入退出时插桩。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:ArrayoutString??){logger?.lifecycle(MethodTimeCostClassVisitor visit method:$name)super.visit(version,access,name,signature,superName,interfaces)}overridefunvisitMethod(access:Int,name:String?,descriptor:String?,signature:String?,exceptions:ArrayoutString??):MethodVisitor?{valmvsuper.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 个 slotprivatevartimeVarIndex-1overridefunonMethodEnter(){// 调用 System.currentTimeMillis() 记录开始时间mv.visitMethodInsn(INVOKESTATIC,java/lang/System,currentTimeMillis,()J,false)// 将返回的 long 时间存储到局部变量表中timeVarIndexnewLocal(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 msmv.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插桩。不用考虑帧结构问题valcheckClassVisitorCheckClassAdapter(nextClassVisitor)// 检查asm修改后的代码是否符合规范如果不符合规范会抛出异常valtraceClassVisitorTraceClassVisitor(checkClassVisitor,PrintWriter(System.out))// 打印asm修改后的代码valcvMethodTimeCostClassVisitor(traceClassVisitor)//责任链模式最外层的classVisitor先执行。插桩类在最外层五、ASM插桩经典架构1. 经典架构byte[]b1...;ClassWritercwnewClassWriter(0);// cv 将所有事件转发给 cwClassVisitorcvnewClassVisitor(ASM4,cw){// 修改类内容 };ClassReadercrnewClassReader(b1);cr.accept(cv,0);byte[]b2cw.toByteArray();// b2 与 b1 表示同一个类2. 优化ClassReader读取效率在构造ClassWriter时传入ClassReader对象如果方法没有修改过classReader遍历方法时就会直接将原方法拷贝而不详细解析方法体。实际上ClassReader是将symbolTable传入ClassWriter。用于直接解析方法相关的位置信息。byte[]b1...ClassReadercrnewClassReader(b1);ClassWritercwnewClassWriter(cr,0);// 优化点ClassVisitorcvnewClassVisitor(ASM4,cw){// 修改类内容 };cr.accept(ca,0);byte[]b2cw.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.currentMethodAccessFlagsOpcodes.ACC_DEPRECATED)!0,readUnsignedShort(methodInfoOffset4),signatureIndex,exceptionsOffset)){methodWriter.setMethodAttributesSource(methodInfoOffset,currentOffset-methodInfoOffset);returncurrentOffset;}}六、类结构类结构详细结构类信息修饰符、类名字、超类、接口常量池数值、字符串、类型常量其他源文件名、封装的类引用、注释*、属性*内部类*名称字段*修饰符、名字、类型、注释*、属性*方法*修饰符、名字、返回类型与参数类型、注释*、属性*、编译后的代码finalintaccess// 访问权限finalStringname// 函数名、字段名finalStringdescriptor// 字段的类型函数的描述符finalStringsignature// 泛型信息七、参考资料ASM版本implementation org.ow2.asm:asm:7.2文档https://www.yuque.com/mikaelzero/asm原文链接转载请附上原文出处链接和本声明https://blog.csdn.net/followYouself/article/details/160512010

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2562181.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…