/* * Copyright (c) 2008-2013, Matthias Mann * Copyright (C) 2014 Zhang,Yuexiang (xfeep) * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Matthias Mann nor the names of its contributors may * be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Copyright (c) 2012, Enhanced Four * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'Enhanced Four' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package nginx.clojure.wave; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.lang.instrument.Instrumentation; import java.security.ProtectionDomain; import java.util.regex.Pattern; import nginx.clojure.Stack; import nginx.clojure.asm.ClassReader; import nginx.clojure.asm.ClassVisitor; import nginx.clojure.asm.ClassWriter; import nginx.clojure.asm.MethodVisitor; import nginx.clojure.asm.Opcodes; import nginx.clojure.asm.commons.JSRInlinerAdapter; import nginx.clojure.asm.util.CheckClassAdapter; import nginx.clojure.logger.TinyLogService; import nginx.clojure.wave.MethodDatabase.ClassEntry; /* * Created on Nov 21, 2010 * * @author Riven * @author Matthias Mann * @author Zhang,Yuexing (xfeep) */ public class JavaAgent { public static final String NGINX_CLOJURE_WAVE_UDFS = "nginx.clojure.wave.udfs"; public static final String NGINX_CLOJURE_WAVE_TRACE_CLASSMETHODPATTERN = "nginx.clojure.wave.trace.classmethodpattern"; public static final String NGINX_CLOJURE_WAVE_TRACE_CLASSPATTERN = "nginx.clojure.wave.trace.classpattern"; public static final String NGINX_CLOJURE_WAVE_DUMPDIR = "nginx.clojure.wave.dumpdir"; public static MethodDatabase db; public static void premain(String agentArguments, Instrumentation instrumentation) { ClassFileTransformer cft = buildClassFileTransformer(agentArguments); if (cft != null) { instrumentation.addTransformer(cft, true); for (String c : db.getRetransformedClasses()) { try { instrumentation.retransformClasses(db.getClassLoader().loadClass(c)); } catch (Throwable e) { db.warn("retransformClasses error:" + c, e); } } } } public static ClassFileTransformer buildClassFileTransformer(String agentArguments) { MethodDatabase db = JavaAgent.db = new MethodDatabase(Thread.currentThread().getContextClassLoader()); boolean checkArg = false; // boolean runTool = false; boolean append = false; if(agentArguments != null) { for(char c : agentArguments.toCharArray()) { switch(c) { case 'v': db.setVerify(true); break; case 'm': db.setAllowMonitors(true); break; case 'd': break; case 'c': checkArg = true; break; case 'b': db.setAllowBlocking(true); break; case 't': db.setRunTool(true); break; case 'a': append = true; break; case 'h': db.setHookDumpWaveCfg(true); break; case 'p' : db.setDump(true); break; case 'n': TinyLogService.createDefaultTinyLogService().info("nginx clojure will do nothing about class waving!"); //do nothing!! db.setDoNothing(true); return null; default: throw new IllegalStateException("Usage: nvdmcbtap (do Nothing, Verbose, Debug, allow Monitors, Check class, allow Blocking, run configuration generation Tool, Append result, dumP waved class)"); } } } MethodDatabase.getLog(); if (System.getProperty(NGINX_CLOJURE_WAVE_DUMPDIR) != null) { db.setDumpDir(System.getProperty(NGINX_CLOJURE_WAVE_DUMPDIR)); } else { db.setDumpDir(System.getProperty("java.io.tmpdir") + "/nginx-clojure-wave-dump"); } if (System.getProperty(NGINX_CLOJURE_WAVE_TRACE_CLASSPATTERN) != null) { db.setTraceClassPattern(Pattern.compile(System.getProperty(NGINX_CLOJURE_WAVE_TRACE_CLASSPATTERN))); } if (System.getProperty(NGINX_CLOJURE_WAVE_TRACE_CLASSMETHODPATTERN) != null) { db.setTraceClassMethodPattern(Pattern.compile(System.getProperty(NGINX_CLOJURE_WAVE_TRACE_CLASSMETHODPATTERN))); } //load system configurations for method database try { db.info("load system coroutine wave file %s", "nginx/clojure/wave/coroutine-method-db.txt"); MethodDatabaseUtil.load(db, "nginx/clojure/wave/coroutine-method-db.txt"); } catch (IOException e) { db.error("can not load nginx/clojure/wave/coroutine-method-db.txt", e); } String udfs = System.getProperty(NGINX_CLOJURE_WAVE_UDFS); if (udfs != null) { for (String udf : udfs.split(",|;")) { try { db.info("load use defined coroutine wave file %s", udf); MethodDatabaseUtil.load(db, udf); } catch (IOException e) { db.warn("can not load " + udf, e); } } } Stack.setDb(db); MethodDatabaseUtil.buildClassEntryFamily(db, "nginx/clojure/wave/MethodDatabaseUtil"); SuspendMethodVerifier.db = db; if (db.isRunTool()) { SuspendMethodTracer.db = db; SuspendMethodTracer.quiteFlags.set(false); return new CoroutineConfigurationToolWaver(db, append); }else { return new CoroutineWaver(db, checkArg); } } static byte[] instrumentClass(MethodDatabase db, byte[] data, boolean check) { ClassReader r = new ClassReader(data); ClassWriter cw = new DBClassWriter(db, r); ClassVisitor cv = check ? new CheckClassAdapter(cw) : cw; ClassEntry ce = MethodDatabaseUtil.buildClassEntryFamily(db, r); if(db.shouldIgnore(r.getClassName())) { return null; } db.trace("TRANSFORM: %s", r.getClassName()); InstrumentClass ic = new InstrumentClass(r.getClassName(), ce, cv, db, false); r.accept(ic, ClassReader.SKIP_FRAMES); return cw.toByteArray(); } public static void dumpClass(byte[] classfileBuffer, File df, MethodDatabase db) { try{ RandomAccessFile raf = new RandomAccessFile(df, "rw"); raf.setLength(0); raf.write(classfileBuffer, 0, classfileBuffer.length); raf.close(); }catch(IOException e) { e.printStackTrace(); db.error("dump file : " + df.getName(), e); } } public static class CoroutineWaver implements ClassFileTransformer { private final MethodDatabase db; private final boolean check; public CoroutineWaver(MethodDatabase db, boolean check) { this.db = db; this.check = check; } @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { if (className.startsWith("java/util/LinkedHashMap")) { return null; } if (db.meetTraceTargetClass(className)) { db.info("meet traced class %s", className); } try { byte[] bs = instrumentClass(db, classfileBuffer, check); if (db.isDump() && bs != null && bs.length != classfileBuffer.length) { File wavedFile = new File(new File(db.getDumpDir() + "/waved"), className + ".class"); wavedFile.getParentFile().mkdirs(); dumpClass(bs, wavedFile, db); } if (db.meetTraceTargetClass(className)) { File orgFile = new File(new File(db.getDumpDir() + "/org"), className + ".class"); orgFile.getParentFile().mkdirs(); dumpClass(classfileBuffer, orgFile, db); } return bs; } catch(Throwable ex) { if (db.isDump()){ File errDumpFile = new File(new File(db.getDumpDir() + "/failed"), className + ".class"); errDumpFile.getParentFile().mkdirs(); dumpClass(classfileBuffer, errDumpFile, db); } db.error("Unable to instrument:" + className, ex); return null; } } } public static class CoroutineConfigurationToolWaver implements ClassFileTransformer { private final MethodDatabase db; private final boolean append; public CoroutineConfigurationToolWaver(MethodDatabase db, boolean append) { this.db = db; this.append = append; if (db.isHookDumpWaveCfg()) { Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { String file = System.getProperty("nginx.clojure.wave.CfgToolOutFile"); if (file == null) { file = "nginx.clojure.wave.cfgtooloutfile"; CoroutineConfigurationToolWaver.this.db.warn("system property 'nginx.clojure.wave.CfgToolOutFile' not found, use '%s' as file path", file); } try { SuspendMethodTracer.dump(file, CoroutineConfigurationToolWaver.this.append); } catch (Throwable e) { CoroutineConfigurationToolWaver.this.db.error("dump error!", e); } } }); } } // filter:org/objectweb/asm/ // filter:nginx/clojure/asm/ // filter:java/ // filter:sun/ // filter:com/sun/ // filter:clojure/asm // filter:clojure/lang // filter:clojure/core // filter:org/junit @Override public byte[] transform(ClassLoader loader, final String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { if (db.meetTraceTargetClass(className)) { db.info("meet traced class %s", className); } if (SuspendMethodTracer.quiteFlags.get()) { return classfileBuffer; } try { ClassReader cr = new ClassReader(classfileBuffer); ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES) { @Override protected String getCommonSuperClass(String type1, String type2) { return db.getCommonSuperClass(type1, type2); } }; ClassEntry ce = MethodDatabaseUtil.buildClassEntryFamily(db, cr); // if (className.startsWith("sun/launcher/") // || className.startsWith("clojure/asm") // || className.startsWith("org/objectweb/asm/") // || className.startsWith("clojure/asm") // || className.startsWith("org/junit") //// || className.startsWith("clojure/main") //// || className.startsWith("clojure/lang") // || className.startsWith("sun/misc/ClassFileTransformer") // || className.startsWith("nginx/clojure/asm") // || className.startsWith("nginx/clojure/wave") // || className.startsWith("java/util/ArrayList") // || className.startsWith("org/eclipse/jetty/server/") // || className.startsWith("clojure/lang/Compiler") // || className.startsWith("java/net/URLClassLoader") // || className.startsWith("java/io/PrintStream") // || className.startsWith("java/util/concurrent/ConcurrentHashMap") // || className.startsWith("java/util/concurrent/atomic/AtomicBoolean") // || className.startsWith("java/") // || className.startsWith("sun/") // || className.startsWith("com/sun/") // || className.startsWith("org/eclipse/jdt/") // || className.endsWith("ClassLoader") // || className.startsWith("clojure/lang/PersistentHashMap") // || className.startsWith("clojure/lang/Keyword") // || className.startsWith("clojure/lang/Symbol") // || className.startsWith("clojure/lang/Namespace") // || className.startsWith("clojure/lang/Persistent") // || className.startsWith("java/lang")) if (db.shouldIgnore(className)){ db.debug("skip class %s", className); return classfileBuffer; } db.debug("loading class %s", className); // ClassVisitor cv = db.isVerbose() ? new TraceClassVisitor(cw, new PrintWriter(System.out)) : cw; ClassVisitor cv = new ClassVisitor(Opcodes.ASM4, cw) { @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); if ( (Opcodes.ACC_NATIVE & access) != 0 || (Opcodes.ACC_ABSTRACT & access) != 0 // || name.startsWith("<cinit>") ) { db.debug("skip native or abstract method: %s.%s%s", className, name, desc); return mv; } return new JSRInlinerAdapter(new SuspendMethodTracerAdvice(db, className, mv, access, name, desc), access, name, desc, signature, exceptions); } }; cr.accept(cv, ClassReader.EXPAND_FRAMES); byte[] rt = cw.toByteArray(); if (db.isDump()) { File wavedFile = new File(new File(db.getDumpDir() + "/waved-by-tool"), className + ".class"); wavedFile.getParentFile().mkdirs(); dumpClass(rt, wavedFile, db); } return rt; } catch(Throwable ex) { db.error("Unable to transform:" + className, ex); return null; } } } }