【问题标题】:Java agent cannot transform all the classes in my projectJava 代理无法转换我项目中的所有类
【发布时间】:2016-04-22 09:54:02
【问题描述】:

长话短说:

  • 我需要转换程序中的每个类(甚至是 java
    在我的代理之前加载的库)。
  • 我找到了办法 但没有完全工作。我乐于接受新想法。
  • 我的实际方法表现得很奇怪:它应该在文件和控制台中打印相同的名称,但事实并非如此。我确信这些类会达到我的转换方法,因为如果我尝试对它们进行检测 我收到一个错误。

完整故事: 我创建了一个代理,以便在加载到我的项目中的每个类中注入一些自定义代码。我在运行时使用选项 -javaagent 添加了这个代理,这工作得很好。

问题是,当我的代理连接到 JVM 时,已经加载了很多类(例如 java.io.* 类),所以我的转换错过了一大堆类。 所以我的问题是:有一种方法可以让我对所有我错过的课程进行检测?每个建议都非常受欢迎。

目前我已经尝试过这样的:

public class MyTransformer implements ClassFileTransformer {


 public static void premain(String agentArgs, Instrumentation inst) {
    final MyTransformer  t = MyTransformer .getInstance();
    inst.addTransformer(t, true);
    MyTransformer .log.info(t + " registered via JVM option -javaagent");

    // TEST
    // by the time we are attached, the classes to be
    // dumped may have been loaded already. So, check
    // for candidates in the loaded classes.
    Class[] classes = inst.getAllLoadedClasses();
    List<Class> candidates = new ArrayList<Class>();
    for (Class c : classes) {
        if (inst.isModifiableClass(c) && inst.isRetransformClassesSupported()){
            candidates.add(c);
        }
    }
    System.out.println("There are "+candidates.size()+" classes");
    try {
        // if we have matching candidates, then
        // retransform those classes so that we
        // will get callback to transform.
        if (! candidates.isEmpty()) {
            Iterator it = candidates.iterator();
            while(it.hasNext()){
                Class c = (Class)it.next();
                if(!c.getName().startsWith("javassist")){
                    System.out.println(" ========================> In Progress:"+c.getName());
                    inst.retransformClasses(c);
                }
            }
        }else{
            System.out.println("candidates.isEmpty()");
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
  }

  public byte[] transform(ClassLoader loader, String className,
        Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
        byte[] classfileBuffer) throws IllegalClassFormatException {

    byte[] byteCode = classfileBuffer;

    final String dot_classname    = className.replace('/', '.');
    // log classes that are going throug the instrumentor
    // just log if the java.io. is coming here
    try {
        PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(path, true)));
        out.println(dot_classname);
        out.close();
     } catch (IOException ex) {
        Logger.getLogger(MyTransformer.class.getName()).log(Level.SEVERE, null, ex);
     }
     return byteCode;
  }
}

所以我的第一次尝试是采用“premain”方法,每个已经加载的类,如果可能的话,用“retransfromClasses”方法重新转换它。

现在在我的转换方法中,我只有一个日志,它将每个要转换的类写入文件。现在我的测试结果是:

There are 1486 classes  
==> In Progress:com.sun.org.apache.xerces.internal.parsers.AbstractXMLDocumentParser
==> In Progress:java.lang.Enum  
==> In Progress:java.lang.ThreadGroup  
==> In Progress:java.nio.file.FileSystem  
==> In Progress:java.util.regex.Pattern$Prolog  
==> In Progress:com.sun.org.apache.xerces.internal.dom.AttributeMap 
==> In Progress:java.util.regex.Matcher  
==> In Progress:org.apache.commons.beanutils.converters.ShortConverter 
==> In Progress:com.google.gson.JsonNull 
==> In Progress:java.util.concurrent.CopyOnWriteArrayList$COWIterator 
==> In Progress:java.util.concurrent.locks.ReentrantLock 
==> In Progress:java.lang.NoSuchMethodError 
==> In Progress:org.apache.commons.lang.BooleanUtils 
==> In Progress:java.lang.reflect.WeakCache$CacheValue 
==> In Progress:com.google.gson.internal.bind.TypeAdapters$33 
==> In Progress:java.lang.reflect.Type 
==> In Progress:sun.reflect.generics.scope.AbstractScope 
==> In Progress:org.apache.log4j.helpers.DateTimeDateFormat 
==> In Progress:sun.nio.cs.MS1252 
==> In Progress:java.lang.Integer$IntegerCache 
==> In Progress:com.sun.org.apache.xerces.internal.utils.SecuritySupport$3 
==> In Progress:org.apache.commons.configuration.MapConfiguration 
==> In Progress:org.apache.commons.beanutils.IntrospectionContext 
==> In Progress:java.io.Reader 
==> In Progress:java.util.WeakHashMap$Holder 
==> In Progress:java.util.ServiceLoader$LazyIterator 
==> In Progress:java.util.regex.Pattern$Branch 
==> In Progress:java.lang.IllegalMonitorStateException 
==> In Progress:java.util.regex.Pattern$Curly 
==> In Progress:org.apache.commons.configuration.resolver.EntityRegistry 
==> In Progress:java.io.IOException 
==> In Progress:java.io.FilterOutputStream 
==> In Progress:org.apache.log4j.LogManager 
==> In Progress:sun.util.logging.PlatformLogger$Level 
==> In Progress:java.nio.charset.CoderResult$1 
==> In Progress:com.google.gson.FieldNamingPolicy$5 
==> In Progress:com.google.gson.internal.ObjectConstructor 
==> In Progress:sun.util.calendar.BaseCalendar$Date

所以你可以注意到我可以找到我感兴趣的每个类(甚至是 java.io)。现在,如果我们查看应该包含 MyTransformer 尝试转换的每个类的文件,我们会注意到没有公共条目,这真的很奇怪。

org.apache.commons.beanutils.converters.ShortConverter
com.google.gson.JsonNull
org.apache.commons.lang.BooleanUtils
com.google.gson.internal.bind.TypeAdapters$33
org.apache.log4j.helpers.DateTimeDateFormat
org.apache.commons.configuration.MapConfiguration
org.apache.commons.beanutils.IntrospectionContext
org.apache.commons.configuration.resolver.EntityRegistry
org.apache.log4j.LogManager
com.google.gson.FieldNamingPolicy$5
com.google.gson.internal.ObjectConstructor
org.apache.log4j.Layout
com.google.gson.internal.bind.TypeAdapters
org.apache.log4j.PropertyConfigurator
com.sap.psr.vulas.java.JavaEnumId
org.apache.commons.collections.collection.AbstractSerializableCollectionDecorator
org.apache.commons.logging.impl.LogFactoryImpl
org.apache.commons.lang.text.StrTokenizer

我发现的另一个问题是,如果我尝试在 Mytransformer.transform(..) 方法中插入一些篡改字节码,它会破坏我的执行。我的意思是我使用的转换操作非常好,因为我在附加代理后加载的每个类上都使用它。但不知何故,如果我尝试在那些“重新转换”的类中使用它,我会出现这个错误:

 ==> In Progress:org.apache.commons.lang.text.StrMatcher$TrimMatcher java.lang.UnsupportedOperationException: class redefinition failed: attempted to change the schema (add/remove fields)
        at sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
        at sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:144)

我知道本地类的转换存在限制,但我并不认为我的转换操作会尝试更改架构。我将对此进行调查并发布更新。

【问题讨论】:

  • 您能告诉我们为什么要在本地 Java 类中注入代码吗?实现目标可能比在环境的每个类中注入代码更简单。
  • 很复杂,但我向您保证我需要更改这些类的字节码。 (简单来说,我的目标是替换某些方法的返回语句,例如替换调用 byte[] java.io.readFile 的返回,以便我可以返回我的自定义字节而不是真正读取它。这将每次调用此方法时都会发生,即使我不知道在哪里使用)

标签: java bytecode javaagents


【解决方案1】:

This link 与您提出的问题类似。看起来您可以修改本机方法的主体,但不能添加方法或字段

我可以在您的代码中看到您正在使用 Javassist 来执行此操作。我花了几个月的时间试图让它工作,但它对我不起作用,所以我最终使用了 ASM(顺便说一句,它确实有效)。

那么你是怎么做到的? 首先,我会使用 Attach API 来挂钩 rt.jar(因为它已经加载,你需要将你的代理附加到 java 进程。然后你可以从agentmain方法将你的转换器添加到java代理。然后为要编辑的类创建一个类阅读器,并接受你扩展的ClassVisitor。然后,你覆盖一个方法,如果它是方法如果需要,请将代码替换为其他代码。您可以查看示例 on my Github。您需要每次对其进行转换,因为它会加载到内存中 - 您无需担心删除代码。

我希望这会有所帮助!如果您有任何问题,请发表评论:)

【讨论】:

    【解决方案2】:

    您是否已将所需的清单条目添加到您的代理 jar manifest.mf 中?

    您的“manifest.mf”看起来如何?它应该有类似

    Can-Redefine-Classes: true

    Can-Retransform-Classes: true

    问候,

    格热西克

    【讨论】:

    • 是的,它看起来就像这样。我注册了我的代理及其重新定义和重新转换的能力。
    • 我刚刚检查了docs.oracle.com/javase/7/docs/api/java/lang/instrument/…,似乎重新转换有限制。但是同样,您启动 jvm 时附加了代理,还是将代理附加到运行 JVM?
    • 我附加了在 cmd 行中传递 -javaagent 选项的代理,因此它在启动时。问题是在我的代理实际附加之前加载了类。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-12-29
    • 1970-01-01
    • 2015-07-31
    • 2021-07-20
    • 1970-01-01
    • 2013-04-09
    • 1970-01-01
    相关资源
    最近更新 更多