/* * JBoss, Home of Professional Open Source * Copyright 2008-10 Red Hat and individual contributors * by the @authors tag. See the copyright.txt in the distribution for a * full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * * @authors Andrew Dinn */ package org.jboss.byteman.agent; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.Socket; import java.util.ArrayList; import java.util.List; import java.util.jar.JarFile; /** * agent class supplied at JVM startup to install byteman package bytecode transformer */ public class Main { public static boolean firstTime = true; public final static String BYTEMAN_PREFIX = "org.jboss.byteman."; public final static String BYTEMAN_AGENT_LOADED = "org.jboss.byteman.agent.loaded"; public static void premain(String args, Instrumentation inst) throws Exception { // guard against the agent being loaded twice synchronized (Main.class) { if (firstTime) { firstTime = false; System.setProperty(BYTEMAN_AGENT_LOADED, Boolean.TRUE.toString()); } else { throw new Exception("Main : attempting to load Byteman agent more than once"); } } boolean installPolicy = false; if (args != null) { // args are supplied separated by ',' characters String[] argsArray = args.split(","); // we accept extra jar files to be added to the boot/sys classpaths // script files to be scanned for rules // listener flag which implies use of a retransformer for (String arg : argsArray) { if (arg.startsWith(BOOT_PREFIX)) { bootJarPaths.add(arg.substring(BOOT_PREFIX.length(), arg.length())); } else if (arg.startsWith(SYS_PREFIX)) { sysJarPaths.add(arg.substring(SYS_PREFIX.length(), arg.length())); } else if (arg.startsWith(ADDRESS_PREFIX)) { hostname = arg.substring(ADDRESS_PREFIX.length(), arg.length()); if (managerClassName == null) { managerClassName=MANAGER_NAME; } } else if (arg.startsWith(PORT_PREFIX)) { try { port = Integer.valueOf(arg.substring(PORT_PREFIX.length(), arg.length())); if (port <= 0) { System.err.println("Invalid port specified [" + port + "]"); port = null; } else if (managerClassName == null) { managerClassName=MANAGER_NAME; } } catch (Exception e) { System.err.println("Invalid port specified [" + arg + "]. Cause: " + e); } } else if (arg.startsWith(SCRIPT_PREFIX)) { scriptPaths.add(arg.substring(SCRIPT_PREFIX.length(), arg.length())); } else if (arg.startsWith(RESOURCE_SCRIPT_PREFIX)) { resourcescriptPaths.add(arg.substring(RESOURCE_SCRIPT_PREFIX.length(), arg.length())); } else if (arg.startsWith(LISTENER_PREFIX)) { // listener:true is an alias for manager:o.j.b.a.TransformListener // listener:false means no manager (yes, not even TransformListener) String value = arg.substring(LISTENER_PREFIX.length(), arg.length()); if (Boolean.parseBoolean(value)) { managerClassName = MANAGER_NAME; } else { managerClassName = null; } } else if (arg.startsWith(REDEFINE_PREFIX)) { // this is only for backwards compatibility -- it is the same as listener String value = arg.substring(REDEFINE_PREFIX.length(), arg.length()); if (Boolean.parseBoolean(value)) { managerClassName = MANAGER_NAME; } else { managerClassName = null; } } else if (arg.startsWith(PROP_PREFIX)) { // this can be used to set byteman properties String prop = arg.substring(PROP_PREFIX.length(), arg.length()); String value=""; if (prop.startsWith(BYTEMAN_PREFIX)) { int index = prop.indexOf('='); if (index > 0) { // need to split off the value if (index == prop.length() - 1) { // value is empty so just drop the = prop = prop.substring(0, index); } else { value = prop.substring(index + 1); prop = prop.substring(0, index); } } System.out.println("Setting " + prop + "=" + value); System.setProperty(prop, value); } else { System.err.println("Invalid property : " + prop); } } else if (arg.startsWith(POLICY_PREFIX)) { String value = arg.substring(POLICY_PREFIX.length(), arg.length()); installPolicy = Boolean.parseBoolean(value); } else if (arg.startsWith(MANAGER_PREFIX)) { managerClassName = arg.substring(MANAGER_PREFIX.length(), arg.length()); if (managerClassName.length() == 0) { managerClassName = null; } } else if (arg.startsWith(MODULE_PREFIX)) { // this can be used to set byteman properties String mod = arg.substring(MODULE_PREFIX.length(), arg.length()); String moduleArgs=""; int index = mod.indexOf('='); if (index > 0) { // need to split off the value if (index == mod.length() - 1) { // value is empty so just drop the = mod = mod.substring(0, index); } else { moduleArgs = mod.substring(index + 1); mod = mod.substring(0, index); } } moduleSystemName = mod; moduleSystemArgs = moduleArgs; } else { System.err.println("org.jboss.byteman.agent.Main:\n" + " illegal agent argument : " + arg + "\n" + " valid arguments are boot:<path-to-jar>, sys:<path-to-jar>, script:<path-to-script> or listener:<true-or-false>"); } } } // add any boot jars to the boot class path for (String bootJarPath : bootJarPaths) { try { JarFile jarfile = new JarFile(new File(bootJarPath)); inst.appendToBootstrapClassLoaderSearch(jarfile); } catch (IOException ioe) { System.err.println("org.jboss.byteman.agent.Main: unable to open boot jar file : " + bootJarPath); throw ioe; } } // add any sys jars to the system class path for (String sysJarPath : sysJarPaths) { try { JarFile jarfile = new JarFile(new File(sysJarPath)); inst.appendToSystemClassLoaderSearch(jarfile); } catch (IOException ioe) { System.err.println("org.jboss.byteman.agent.Main: unable to open system jar file : " + sysJarPath); throw ioe; } } // create a socket so we can be sure it is loaded before the transformer gets created. otherwise // we seem to hit a deadlock when trying to instrument socket Socket dummy = new Socket(); // look up rules in any script files for (String scriptPath : scriptPaths) { FileInputStream fis = null; try { fis = new FileInputStream(scriptPath); byte[] bytes = new byte[fis.available()]; fis.read(bytes); String ruleScript = new String(bytes); scripts.add(ruleScript); } catch (IOException ioe) { System.err.println("org.jboss.byteman.agent.Main: unable to read rule script file : " + scriptPath); throw ioe; } finally { if (fis != null) fis.close(); } } // look up rules in any resource script files for (String scriptPath : resourcescriptPaths) { try { InputStream is = ClassLoader.getSystemResourceAsStream(scriptPath); if (is == null) { throw new Exception("org.jboss.byteman.agent.Main: could not read rule script resource file : " + scriptPath); } byte[] bytes = new byte[is.available()]; is.read(bytes); String ruleScript = new String(bytes); scripts.add(ruleScript); // merge the resource and file script paths into one list scriptPaths.add(scriptPath); } catch (IOException ioe) { System.err.println("org.jboss.byteman.agent.Main: error reading rule script resource file : " + scriptPath); throw ioe; } } // install an instance of Transformer to instrument the bytecode // n.b. this is done with boxing gloves on using explicit class loading and method invocation // via reflection for a GOOD reason. This class (Main) gets loaded by the System class loader. // If we refer to Transformer by name then it also gets loaded via the System class loader. // But if we want to transform a bootstrap class we need Transformer (et al) to be visible // from the bootstrap class loader. That will not happen until after this method has called // inst.appendToBootstrapClassLoaderSearch (see above) to add the byteman jar to the path. // Directly referring to Transformer will give us two versions of Transformer et al. Not only // does that cause us class mismatch problem it also means that a new done here will not install // the new instance in the static field of the one loaded in the bootstrap loader. If instead we // use boxing gloves then the byteman code will get loaded in the bootstrap loader and its constructor // will be called. // // Of course, if the user does not supply boot:byteman.jar as a -javaagent option then class references // resolve against the system loader and injection into bootstrap classes fails. But that's still ok // because the byteman classes are still only found in one place. ClassLoader loader = ClassLoader.getSystemClassLoader(); if (moduleSystemName == null) { moduleSystemName = "org.jboss.byteman.modules.NonModuleSystem"; } Class<?/*ModuleSystem*/> moduleSystemInteraceClazz = loader.loadClass(MODULE_SYSTEM_NAME); Class<?/*ModuleSystem*/> moduleSystemImplClazz = loader.loadClass(moduleSystemName); final Object/*ModuleSystem*/ moduleSystem = moduleSystemImplClazz.newInstance(); final Method/*String->void*/ moduleSystemInit = moduleSystemInteraceClazz.getMethod("initialize", String.class); moduleSystemInit.invoke(moduleSystem, moduleSystemArgs); boolean isRedefine = inst.isRedefineClassesSupported(); Class/*<Transformer>*/ transformerClazz; ClassFileTransformer transformer; if (managerClassName != null && isRedefine) { transformerClazz = loader.loadClass(RETRANSFORMER_NAME); //transformer = new Retransformer(inst, moduleSystem, scriptPaths, scripts, true); Constructor/*<Transformer>*/ constructor = transformerClazz.getConstructor(Instrumentation.class,moduleSystemInteraceClazz, List.class, List.class, boolean.class); transformer = (ClassFileTransformer)constructor.newInstance(new Object[] { inst, moduleSystem, scriptPaths, scripts, isRedefine}); } else { transformerClazz = loader.loadClass(TRANSFORMER_NAME); //transformer = new Transformer(inst, moduleSystem, scriptPaths, scripts, isRedefine); Constructor/*<Retransformer>*/ constructor = transformerClazz.getConstructor(Instrumentation.class, moduleSystemInteraceClazz, List.class, List.class, boolean.class); transformer = (ClassFileTransformer)constructor.newInstance(new Object[] { inst, moduleSystem, scriptPaths, scripts, isRedefine}); } inst.addTransformer(transformer, true); if (managerClassName != null && isRedefine) { Class managerClazz = loader.loadClass(managerClassName); try { Method method = managerClazz.getMethod("initialize", transformerClazz, String.class, Integer.class); method.invoke(null, transformer, hostname, port); } catch (NoSuchMethodException e) { Method method = managerClazz.getMethod("initialize", transformerClazz); method.invoke(null, transformer); } } if (installPolicy) { Method method = transformerClazz.getMethod("installPolicy"); method.invoke(transformer); } if (isRedefine) { Method method; method = transformerClazz.getMethod("installBootScripts"); method.invoke(transformer); //transformer.installBootScripts(); } } public static void agentmain(String args, Instrumentation inst) throws Exception { premain(args, inst); } /** * prefix used to specify port argument for agent */ private static final String PORT_PREFIX = "port:"; /** * prefix used to specify bind address argument for agent */ private static final String ADDRESS_PREFIX = "address:"; /** * prefix used to specify boot jar argument for agent */ private static final String BOOT_PREFIX = "boot:"; /** * prefix used to specify system jar argument for agent */ private static final String SYS_PREFIX = "sys:"; /** * prefix used to request installation of an access-all-areas security * policy at install time for agent code */ private static final String POLICY_PREFIX = "policy:"; /** * prefix used to specify file script argument for agent */ private static final String SCRIPT_PREFIX = "script:"; /** * prefix used to specify resource script argument for agent */ private static final String RESOURCE_SCRIPT_PREFIX = "resourcescript:"; /** * prefix used to specify transformer type argument for agent */ private static final String LISTENER_PREFIX = "listener:"; /** * for backwards compatibiltiy */ private static final String REDEFINE_PREFIX = "redefine:"; /** * prefix used to specify system properties to be set before starting the agent */ private static final String PROP_PREFIX = "prop:"; /** * prefix used to specify the manager class */ private static final String MANAGER_PREFIX = "manager:"; /** * prefix used to specify the module system class */ private static final String MODULE_PREFIX = "modules:"; /** * name of basic transformer class. */ private static final String TRANSFORMER_NAME = "org.jboss.byteman.agent.Transformer"; /** * name of retransformer class. */ private static final String RETRANSFORMER_NAME = "org.jboss.byteman.agent.Retransformer"; /** * name of default manager class. */ private static final String MANAGER_NAME = "org.jboss.byteman.agent.TransformListener"; /** * name of module system interface. */ private static final String MODULE_SYSTEM_NAME = "org.jboss.byteman.modules.ModuleSystem"; /** * list of paths to extra bootstrap jars supplied on command line */ private static List<String> bootJarPaths = new ArrayList<String>(); /** * list of paths to extra system jars supplied on command line */ private static List<String> sysJarPaths = new ArrayList<String>(); /** * list of paths to script files supplied on command line */ private static List<String> scriptPaths = new ArrayList<String>(); /** * list of paths to resource script files supplied on command line */ private static List<String> resourcescriptPaths = new ArrayList<String>(); /** * list of scripts read from script files */ private static List<String> scripts = new ArrayList<String>(); /** * The hostname to bind the listener to, supplied on the command line (optional argument) */ private static String hostname = null; /** * The port that the listener will listen to, supplied on the command line (optional argument) */ private static Integer port = null; /** * The name of the manager class responsible for loading/unloading scripts, supplied on the * command line (optional argument) */ private static String managerClassName = null; /** * The name of the module system implementation class, supplied on the * command line (optional argument) */ private static String moduleSystemName = null; /** * The arguments to the module system implementation class, supplied on the * command line (optional argument) */ private static String moduleSystemArgs = ""; }