如何使用OW2 ASM库从Java/Kotlin字节码提取字符串字面量并替换为函数调用
用Gradle Instrumentation + ASM替换字节码中的字符串字面量为函数调用
1. Gradle Instrumentation 基础配置
首先在Gradle项目中启用instrumentation,注册自定义ClassFileTransformer:
在build.gradle(Groovy)中添加配置:
plugins { id 'java' } tasks.withType(JavaCompile) { options.compilerArgs += ['-javaagent:build/libs/your-agent.jar'] } jar { manifest { attributes 'Premain-Class': 'com.yourpkg.StringTransformAgent' } from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } }
对应的Agent类实现:
public class StringTransformAgent { public static void premain(String args, Instrumentation inst) { inst.addTransformer(new StringClassTransformer()); } }
2. 实现ASM ClassTransformer
StringClassTransformer负责将类字节码交给ASM处理:
public class StringClassTransformer implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { // 跳过JDK核心类,避免不必要处理 if (className.startsWith("java/") || className.startsWith("javax/")) { return classfileBuffer; } ClassReader cr = new ClassReader(classfileBuffer); ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES); ClassVisitor cv = new StringReplaceClassVisitor(cw); cr.accept(cv, ClassReader.EXPAND_FRAMES); return cw.toByteArray(); } }
3. ASM核心:替换字符串字面量的ClassVisitor
通过拦截ldc指令(加载常量到操作数栈),将字符串字面量替换为函数调用:
public class StringReplaceClassVisitor extends ClassVisitor { public StringReplaceClassVisitor(ClassVisitor cv) { super(Opcodes.ASM9, cv); } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); return new StringReplaceMethodVisitor(mv); } private static class StringReplaceMethodVisitor extends MethodVisitor { public StringReplaceMethodVisitor(MethodVisitor mv) { super(Opcodes.ASM9, mv); } @Override public void visitLdcInsn(Object cst) { // 仅处理字符串字面量 if (cst instanceof String) { // 先加载原字符串到栈 super.visitLdcInsn(cst); // 调用自定义包装函数:com.yourpkg.StrUtils.wrap(String) -> String super.visitMethodInsn(Opcodes.INVOKESTATIC, "com/yourpkg/StrUtils", "wrap", "(Ljava/lang/String;)Ljava/lang/String;", false); } else { // 非字符串常量保持原样 super.visitLdcInsn(cst); } } } }
4. 自定义包装函数示例
提前定义目标函数,确保类路径中存在:
public class StrUtils { public static String wrap(String original) { // 这里可添加自定义逻辑,比如返回处理后的字符串 return "[processed] " + original; } }
5. 注意事项
- ASM版本匹配:代码使用ASM9,需确保依赖引入对应版本的ASM库
- Kotlin字节码兼容:Kotlin编译后的字节码与Java兼容,上述逻辑无需修改即可处理Kotlin中的字符串字面量
- 栈帧自动计算:使用
ClassWriter.COMPUTE_FRAMES自动处理栈帧,避免手动计算栈大小和局部变量表 - 类过滤:跳过JDK核心类、第三方库类,避免性能问题或兼容性异常
内容的提问来源于stack exchange,提问作者Ghost
相关产品推荐
相关产品推荐

