2015-04-04 63 views
3

该工具(在this repo中)包含3个类(在下面给出)。问题是如何使我的ParaTracer.Logger类在每个I类仪器中都可见(如下面显示的java.util.Random)。声明cp.importPackage("ParaTracer.Logger");似乎并没有工作,我得到这个错误:使用Javassist记录方法调用和参数值,如何在每个检测类中使日志类可见?

java.lang.NoClassDefFoundError: ParaTracer/Logger at java.util.Random.nextLong(Random.java)

我试图动态加载每个仪表类内部的Logger类。但似乎我错误地使用了Class.getMethod(),或者Javassist编译器太原始,无法编译动态类加载代码。我得到这个错误:

javassist.CannotCompileException: [source error] getMethod(java.lang.String,java.lang.Class,java.lang.Class) not found in java.lang.Class

以下3类导出到一个JAR文件与MANIFEST.MF文件定义Premain-Class和使用开关运行任何检测的程序时,传递给JVM:

-javaagent:/Path/To/ParaTracerAgent.jar

这里有3个类。

package ParaTracer; 

import java.lang.instrument.Instrumentation; 

import javassist.CannotCompileException; 
import javassist.ClassPool; 
import javassist.CtClass; 
import javassist.CtField; 
import javassist.CtNewConstructor; 
import javassist.CtNewMethod; 

public class ParaTracer { 

    private static volatile Instrumentation instr; 

    public static void premain(String agentArgs, Instrumentation inst) { 
     instr = inst; 
     SimpleClassTransformer transformer = new SimpleClassTransformer(); 
     inst.addTransformer(transformer, false); 
    } 
} 

变压器类:

package ParaTracer; 

import java.lang.instrument.ClassFileTransformer; 
import java.lang.instrument.IllegalClassFormatException; 
import java.security.ProtectionDomain; 
import java.util.HashMap; 
import java.util.HashSet; 

import javassist.ClassPool; 
import javassist.CtClass; 
import javassist.CtMethod; 


public class SimpleClassTransformer implements ClassFileTransformer { 

    public HashMap< String, HashSet<String> > mInstrumentedMethods; 

    public SimpleClassTransformer() { 
     mInstrumentedMethods = new HashMap< String, HashSet<String> >(); 

     mInstrumentedMethods.put("java.util.Random", new HashSet<String>()); 
     mInstrumentedMethods.get("java.util.Random").add("nextLong"); 
    } 

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

     System.err.println("---- Instrumenting: " + className); 

     byte[] byteCode = classfileBuffer; 

     String normalizedClassName = className.replaceAll("/", "."); 

     if (mInstrumentedMethods.containsKey(normalizedClassName)) { 
      try { 
       ClassPool cp = ClassPool.getDefault(); 

       cp.importPackage("ParaTracer.Logger"); 

       CtClass cc = cp.get(normalizedClassName); 

       for(String method : mInstrumentedMethods.get(normalizedClassName)) { 
        CtMethod m = cc.getDeclaredMethod(method); 

        StringBuilder sbs = new StringBuilder(); 
        sbs.append("long tid = Thread.currentThread().getId();"); 
        sbs.append("StringBuilder sbArgs = new StringBuilder();"); 
        sbs.append("sbArgs.append(System.identityHashCode($0));"); 
        CtClass[] pTypes = m.getParameterTypes(); 
        for(int i=0; i < pTypes.length; ++i) { 
         CtClass pType = pTypes[i]; 
         if (pType.isPrimitive()) { 
          sbs.append("sbArgs.append(\", \" + $args[" + i + "]);"); 
         } else { 
          sbs.append("sbArgs.append(\", \" + System.identityHashCode($args[" + i + "]));"); 
         } 
        } 
        sbs.append("ParaTracer.Logger.pushArgs(tid, sbArgs.toString());"); 
        sbs.append("StringBuilder sb = new StringBuilder();"); 
        sbs.append("sb.append(tid + \" : " + m.getLongName() + ".<START>(\");"); 
        sbs.append("sb.append(sbArgs.toString());"); 
        sbs.append("sb.append(\")\");"); 
        sbs.append("ParaTracer.Logger.print(sb.toString());"); 

        m.insertBefore("{" + sbs.toString() + "}"); 

        StringBuilder sbe = new StringBuilder(); 
        sbe.append("long tid = Thread.currentThread().getId();"); 
        sbe.append("String args = ParaTracer.Logger.popArgs(tid);"); 
        sbe.append("StringBuilder sb = new StringBuilder();"); 
        sbe.append("sb.append(tid + \" : " + m.getLongName() + ".<END>(\");"); 
        sbe.append("sb.append(args);"); 
        sbe.append("sb.append(\")\");"); 
        sbe.append("ParaTracer.Logger.print(sb.toString());"); 

        m.insertAfter("{" + sbe.toString() + "}"); 
       } 
       byteCode = cc.toBytecode(); 
       cc.detach(); 
      } catch (Exception ex) { 
       ex.printStackTrace(); 
      } 
     } 
     return byteCode; 
    } 
} 

的线程安全记录器类由下式给出:

package ParaTracer; 

import java.io.FileWriter; 
import java.io.IOException; 
import java.io.PrintWriter; 
import java.util.HashMap; 
import java.util.Stack; 

public class Logger { 

    private static String loggerFilePath = "\"/some/fixed/path\""; 
    private static FileWriter fw; 
    private static PrintWriter out; 
    private static HashMap< Long, Stack<String> > callStacks; 

    public static synchronized void pushArgs(long tid, String args) { 
     try { 
      init(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 
     if (! callStacks.containsKey(tid)) { 
      callStacks.put(tid, new Stack<String>()); 
     } 
     callStacks.get(tid).push(args); 
    } 

    public static synchronized String popArgs(long tid) { 
     assert(callStacks.containsKey(tid)); 
     assert(! callStacks.get(tid).empty()); 
     return callStacks.get(tid).pop(); 
    } 

    public static synchronized void shutdown() { 
     if (fw == null) return; 
     try { 
      fw.close(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 
    } 

    public static synchronized void print(String str) { 
     try { 
      init(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 
     out.print(str); 
    } 

    private static void init() throws IOException { 
     if (fw != null) return; 
     fw = new FileWriter(loggerFilePath); 
     out = new PrintWriter(fw); 
     callStacks = new HashMap< Long, Stack<String> >(); 
    } 
} 
+0

由于Javassist问题似乎没有太多兴趣,所以我完全删除了'ParaTracer.Logger'类,并使用'System.err'来记录方法调用,并使用了'System.setOut()/。 setErr()'将标准输出/错误重定向到一个文件。没有必要在方法返回时打印参数值。它现在有效。 – 2015-04-05 00:58:20

回答

6

根据the documentation for Java agents代理类是由系统类加载器加载。但是如果你想测试核心Java类并将它们引用到你自己的自定义类中,那么该类将需要可用于类加载器而不是系统类。

将您Logger类到一个单独的JAR文件,并在代理JAR的清单的Boot-Class-Path属性文件列表:

Boot-Class-Path: ParaTracerLogger.jar 

现在记录器类是在引导加载程序可见和可略见一斑仪器化的java.lang类。

+0

感谢您的回答;我花了无数个小时追逐这个。我还发现开放JDK上的-Xbootclasspath/a:/ 或-Xbootclasspath/p:/ 选项也很有帮助,但是您的回答激励我研究这些选项。 – Leo 2015-04-05 19:28:29

+0

另外,我刚发现你并不需要将代码移动到另一个jar中;设置Boot-Class-Path:如下所示会导致代理jar本身被引导加载(./与代理jar文件的路径有关): 展台类路径:./ParaTracerAgent.jar – Leo 2015-04-05 20:35:09

+0

@Ian罗伯茨:非常感谢。现在,单独的'ParaTracer.Logger'类可以正常工作,而且我不必在每个仪表位置都嵌入非线程安全的日志代码,并且每次消息需要时都不必打开/关闭日志文件被打印。 – 2015-04-06 03:42:25

1

我结束了具有如下所述ClassFileTransformer.tranform方法:

基本上,Javassist插入的代码在每次有消息时重新打开相同的日志文件e被写出并且附加该消息到输出文件。拥有单独的日志文件非常重要,因为重定向标准输出/错误将导致受污染的日志文件,如果这些流已被仪器化应用程序使用(通常情况如此)。

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

    byte[] byteCode = classfileBuffer; 

    String normalizedClassName = className.replaceAll("/", "."); 
    System.out.println("\tNormalized: " + normalizedClassName); 

    ClassMonitorSet classMonitorSet = monitorClass(normalizedClassName); 
    if (classMonitorSet != null) { 
     System.out.println("\tMonitoring: " + normalizedClassName); 
     try { 
      ClassPool cp = ClassPool.getDefault(); 
      CtClass cc = cp.get(normalizedClassName); 

      for(String methodName : classMonitorSet.monitorSet) { 
       CtMethod[] methods = cc.getDeclaredMethods(methodName); 
       for(CtMethod method : methods) { 

        StringBuilder sbs = new StringBuilder(); 
        sbs.append("long tid = Thread.currentThread().getId();"); 
        sbs.append("StringBuilder sbArgs = new StringBuilder();"); 
        sbs.append("sbArgs.append(System.identityHashCode($0));"); 
        CtClass[] pTypes = method.getParameterTypes(); 
        for(int i=0; i < pTypes.length; ++i) { 
         CtClass pType = pTypes[i]; 
         if (pType.isPrimitive()) { 
          sbs.append("sbArgs.append(\", \" + $args[" + i + "]);"); 
         } else { 
          sbs.append("sbArgs.append(\", \" + System.identityHashCode($args[" + i + "]));"); 
         } 
        } 
        sbs.append("StringBuilder sb = new StringBuilder();"); 
        sbs.append("sb.append(tid + \" : " + method.getLongName() + ".<START>(\");"); 
        sbs.append("sb.append(sbArgs.toString());"); 
        sbs.append("sb.append(\")\");"); 
        sbs.append("String fPath = \"/path/to/log.out\";"); 
        sbs.append("try {"); 
        sbs.append(" java.io.FileWriter fw = new java.io.FileWriter(fPath, true);"); 
        sbs.append(" java.io.PrintWriter out = new java.io.PrintWriter(fw, true);"); 
        sbs.append(" out.println(sb.toString());"); 
        sbs.append(" fw.close();"); 
        sbs.append("} catch (java.io.IOException e) {"); 
        sbs.append(" e.printStackTrace();"); 
        sbs.append("}"); 

        method.insertBefore("{" + sbs.toString() + "}"); 

        StringBuilder sbe = new StringBuilder(); 
        sbe.append("long tid = Thread.currentThread().getId();"); 
        sbe.append("StringBuilder sb = new StringBuilder();"); 
        sbe.append("sb.append(tid + \" : " + method.getLongName() + ".<END>(*)\");"); 
        sbe.append("String fPath = \"/path/to/log.out\";"); 
        sbe.append("try {"); 
        sbe.append(" java.io.FileWriter fw = new java.io.FileWriter(fPath, true);"); 
        sbe.append(" java.io.PrintWriter out = new java.io.PrintWriter(fw, true);"); 
        sbe.append(" out.println(sb.toString());"); 
        sbe.append(" fw.close();"); 
        sbe.append("} catch (java.io.IOException e) {"); 
        sbe.append(" e.printStackTrace();"); 
        sbe.append("}"); 

        method.insertAfter("{" + sbe.toString() + "}"); 
       } 
      } 
      byteCode = cc.toBytecode(); 
      cc.detach(); 
     } catch (Exception ex) { 
      ex.printStackTrace(); 
     } 
    } 
    return byteCode; 
} 
+0

你的项目的源代码是否在某处? – 2017-10-05 18:00:20

+0

@ joe-jeff它曾经是私人回购(作为更大的研究项目的一部分)。我现在在这里公开:github.com/anassar/ParaTracer – 2017-10-11 21:56:48

相关问题