使用Javassist记录方法调用和参数值,如何在每个检测类中显示记录器类? [英] Using Javassist to log method calls and argument values, how to make a logger class visible in every instrumented class?

查看:220
本文介绍了使用Javassist记录方法调用和参数值,如何在每个检测类中显示记录器类?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

该工具(在此回购中)包含3个类别(如下所示)。问题是如何使我的 ParaTracer.Logger 类在我的每个类中都可见(例如 java.util.Random 如下所示)。语句 cp.importPackage(ParaTracer.Logger); 似乎不起作用,我收到此错误:

The tool (in this repo) comprises 3 classes (given below). The problem is how to make my ParaTracer.Logger class visible in every class I instrument (such as java.util.Random shown below). The statement cp.importPackage( "ParaTracer.Logger"); doesn't seem to work and I am getting this error:

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

我尝试在每个检测类中动态加载 Logger 类。但似乎我错误地使用 Class.getMethod(),或者Javassist编译器太原始而无法编译动态类加载代码。我收到此错误:

I tried dynamically loading the Logger class inside every instrumented class. But it seems I was using Class.getMethod() incorrectly, or the Javassist compiler is too primitive to compile dynamic class-loading code. I get this error:

javassist.CannotCompileException:[source error] getMethod(java.lang.String,java.lang.Class,java。 lang.Class)在java.lang.Class中找不到

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

The following 3 classes are exported into a JAR file with MANIFEST.MF file defining the Premain-Class and is passed to the JVM when running any instrumented program using the switch:

-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;
    }
}

线程安全记录器类由下式给出: / p>

The thread-safe logger class is given by:

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> >();
    }
}


推荐答案

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

According to the documentation for Java agents the agent class is loaded by the system classloader. But if you want to instrument core Java classes and refer from those to a custom class of your own then that class would need to be available to the bootstrap classloader rather than the system one.

Logger 类移动到单独的JAR文件中,并在 Boot-Class-Path中列出该文件代理JAR清单的属性:

Move your Logger class into a separate JAR file, and list that file in the Boot-Class-Path attribute of the agent JAR's manifest:

Boot-Class-Path: ParaTracerLogger.jar

现在,logger类在引导加载程序中可见,并且可以通过检测的 java看到.lang classes。

Now the logger class is visible on the bootstrap loader and can be seen by the instrumented java.lang classes.

这篇关于使用Javassist记录方法调用和参数值,如何在每个检测类中显示记录器类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆