You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何调试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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 22:40:40