如何调试ByteBuddy对FileInputStream构造方法的无效插桩?
问题描述
使用ByteBuddy的AgentBuilder.Identified.Extendable对FileInputStream的构造方法插桩后无效果,预期的onEnter方法未被调用。推测可能的原因:
- 类型匹配器存在问题?
- 方法匹配器存在问题?
- 被插桩类未正确重载?
- 处于错误的类加载器中?
- ...?
请问该如何调试此场景,明确问题根源?
以下是源代码:
package com.example.javaagent.instrumentation; import net.bytebuddy.agent.ByteBuddyAgent; import net.bytebuddy.agent.builder.AgentBuilder; import net.bytebuddy.asm.Advice; import org.junit.jupiter.api.Test; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import static net.bytebuddy.matcher.ElementMatchers.*; public class FileInputStreamConstructorInstrumentationTests { @Test public void testByteBuddyfileInputStreamConstructor() { ByteBuddyAgent.install(); AgentBuilder.Identified.Extendable extendableAgentBuilder = new AgentBuilder.Default() .type(named("java.io.FileInputStream")) .transform(new AgentBuilder.Transformer.ForAdvice()); extendableAgentBuilder = extendableAgentBuilder.transform( new AgentBuilder.Transformer.ForAdvice() .advice( isConstructor().and(takesArguments(1)).and(takesArgument(0, String.class)), this.getClass().getName() + "$FileInputStreamCtorString")); extendableAgentBuilder.installOnByteBuddyAgent(); File file = new File("example.txt"); createFile(file); try { // Open a file input stream to read from a file FileInputStream inputStream = new FileInputStream(file); // Read the contents of the file and print them to the console int data = inputStream.read(); while (data != -1) { System.out.print((char) data); data = inputStream.read(); } // Close the input stream to release system resources inputStream.close(); } catch (IOException e) { e.printStackTrace(); } deleteFile(file); } public static class FileInputStreamCtorString { @Advice.OnMethodEnter(suppress = Throwable.class) public static void onEnter(@Advice.Argument(0) String name) { System.out.println("entered " + name); } } private static void deleteFile(File file) { // Check if the file exists if (file.exists()) { // If the file exists, delete it if (file.delete()) { System.out.println("File deleted successfully"); } else { System.out.println("Failed to delete file"); } } else { System.out.println("File does not exist"); } } private static void createFile(File file) { // Check if the file already exists if (!file.exists()) { // If the file does not exist, create a new file try { file.createNewFile(); System.out.println("File created successfully"); } catch (IOException e) { e.printStackTrace(); } } else { System.out.println("File already exists"); } } }
另外,直接使用ByteBuddy实例插桩可正常工作,示例代码:
ByteBuddyAgent.install(); Foo foo = new Foo(); new ByteBuddy() .redefine(Bar.class) .name(Foo.class.getName()) .make() .load(Foo.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); assertEquals(foo.m(), "bar");
怀疑对AgentBuilder的正确使用方式存在误解。
解决与调试方案
一、先修正代码中的核心错误
1. AgentBuilder链式调用错误
你代码中两次调用.transform的写法会导致配置失效,正确的方式是一次性完成类型匹配与Advice绑定:
@Test public void testByteBuddyfileInputStreamConstructor() { ByteBuddyAgent.install(); new AgentBuilder.Default() .type(named("java.io.FileInputStream")) .transform(new AgentBuilder.Transformer.ForAdvice() .advice(isConstructor().and(takesArguments(1)).and(takesArgument(0, String.class)), FileInputStreamCtorString.class.getName())) .installOnByteBuddyAgent(); // 后续文件操作代码不变 }
2. 插桩目标与测试调用不匹配
你测试时调用的是new FileInputStream(file)(File参数构造方法),但插桩的是String参数的构造方法,这是插桩不触发的直接原因。可以二选一修正:
- 测试时调用String参数构造方法:
new FileInputStream(file.getAbsolutePath()) - 修改匹配器同时覆盖File参数构造方法:
.advice(isConstructor().and(takesArguments(1)).and( takesArgument(0, anyOf(String.class, File.class))), FileInputStreamCtorString.class.getName())
二、调试步骤
1. 开启ByteBuddy调试日志
添加监听器查看类匹配、转换状态,快速定位是否有转换失败或忽略的情况:
new AgentBuilder.Default() .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) .with(new AgentBuilder.Listener.Adapter() { @Override public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded) { System.out.println("已转换类: " + typeDescription.getName()); } @Override public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded) { System.out.println("忽略类: " + typeDescription.getName()); } @Override public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) { System.err.println("转换出错: " + typeName); throwable.printStackTrace(); } }) .type(named("java.io.FileInputStream")) .transform(/* ... */)
2. 允许转换启动类加载器的类
FileInputStream属于JDK核心类,由启动类加载器加载,默认ByteBuddy不会转换这类类,需显式开启:
new AgentBuilder.Default() .ignore(none()) // 取消默认忽略规则 .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) .type(named("java.io.FileInputStream")) .transform(/* ... */)
3. 验证方法匹配器是否命中目标
单独测试匹配器,确认是否能找到目标构造方法:
TypeDescription fileInputStreamType = TypeDescription.ForLoadedType.of(FileInputStream.class); MethodMatcher matcher = isConstructor().and(takesArguments(1)).and(takesArgument(0, String.class)); for (MethodDescription method : fileInputStreamType.getDeclaredMethods()) { if (matcher.matches(method)) { System.out.println("匹配到构造方法: " + method.getSignature()); } }
4. 确认类重定义策略
因为FileInputStream在测试代码运行前已被加载,必须使用RETRANSFORMATION策略:
new AgentBuilder.Default() .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) .type(named("java.io.FileInputStream")) .transform(/* ... */)
三、完整修正后的测试代码
package com.example.javaagent.instrumentation; import net.bytebuddy.agent.ByteBuddyAgent; import net.bytebuddy.agent.builder.AgentBuilder; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatchers; import org.junit.jupiter.api.Test; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import static net.bytebuddy.matcher.ElementMatchers.*; public class FileInputStreamConstructorInstrumentationTests { @Test public void testByteBuddyfileInputStreamConstructor() { ByteBuddyAgent.install(); // 先验证方法匹配器是否命中 verifyMethodMatcher(); new AgentBuilder.Default() .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) .ignore(none()) .with(new AgentBuilder.Listener.Adapter() { @Override public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, java.lang.Module module, boolean loaded) { System.out.println("已转换类: " + typeDescription.getName()); } @Override public void onError(String typeName, ClassLoader classLoader, java.lang.Module module, boolean loaded, Throwable throwable) { System.err.println("转换出错: " + typeName); throwable.printStackTrace(); } }) .type(named("java.io.FileInputStream")) .transform(new AgentBuilder.Transformer.ForAdvice() .advice(isConstructor().and(takesArguments(1)).and( takesArgument(0, anyOf(String.class, File.class))), FileInputStreamCtor.class.getName())) .installOnByteBuddyAgent(); File file = new File("example.txt"); createFile(file); try { // 测试String参数构造方法 FileInputStream inputStream1 = new FileInputStream(file.getAbsolutePath()); inputStream1.close(); // 测试File参数构造方法 FileInputStream inputStream2 = new FileInputStream(file); inputStream2.close(); } catch (IOException e) { e.printStackTrace(); } deleteFile(file); } private void verifyMethodMatcher() { TypeDescription type = TypeDescription.ForLoadedType.of(FileInputStream.class); MethodMatcher matcher = isConstructor().and(takesArguments(1)).and( takesArgument(0, anyOf(String.class, File.class))); for (MethodDescription method : type.getDeclaredMethods()) { if (matcher.matches(method)) { System.out.println("匹配到构造方法: " + method.getSignature()); } } } public static class FileInputStreamCtor { @Advice.OnMethodEnter(suppress = Throwable.class) public static void onEnter(@Advice.Argument(0) Object param) { System.out.println("进入FileInputStream构造方法,参数: " + param); } } private static void deleteFile(File file) { if (file.exists()) { if (file.delete()) { System.out.println("File deleted successfully"); } else { System.out.println("Failed to delete file"); } } else { System.out.println("File does not exist"); } } private static void createFile(File file) { if (!file.exists()) { try { file.createNewFile(); System.out.println("File created successfully"); } catch (IOException e) { e.printStackTrace(); } } else { System.out.println("File already exists"); } } }
内容的提问来源于stack exchange,提问作者Meir Benayoun
相关产品推荐
相关产品推荐

