001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package org.openimaj.aop.agent; 031 032import java.io.File; 033import java.io.FileOutputStream; 034import java.io.IOException; 035import java.io.InputStream; 036import java.io.OutputStream; 037import java.io.UnsupportedEncodingException; 038import java.lang.instrument.Instrumentation; 039import java.lang.management.ManagementFactory; 040import java.lang.reflect.Method; 041import java.net.URL; 042import java.net.URLClassLoader; 043import java.security.AccessController; 044import java.security.PrivilegedActionException; 045import java.security.PrivilegedExceptionAction; 046import java.util.ArrayList; 047import java.util.List; 048import java.util.jar.JarEntry; 049import java.util.jar.JarOutputStream; 050 051import org.apache.log4j.Logger; 052 053/** 054 * Dynamic agent loader. Provides methods to extract an agent jar with 055 * a specific agent class, and to attempt to dynamically load agents 056 * on Oracle JVMs (including OpenJDK). Dynamic loading won't work on 057 * all JVMs (i.e. IBMs), but the standard "-javaagent" commandline option 058 * can be used instead. If dynamic loading fails, instructions on using 059 * the command line flag will be printed to stderr. 060 * 061 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 062 */ 063public class AgentLoader { 064 private static final Logger logger = Logger.getLogger(AgentLoader.class); 065 private static final String VMCLASS = "com.sun.tools.attach.VirtualMachine"; 066 067 private static long copy(InputStream input, OutputStream output) throws IOException { 068 long count = 0; 069 int n = 0; 070 byte[] buffer = new byte[4096]; 071 072 while (-1 != (n = input.read(buffer))) { 073 output.write(buffer, 0, n); 074 count += n; 075 } 076 077 return count; 078 } 079 080 private static byte[] createManifest(Class<?> agentClass) { 081 StringBuffer sb = new StringBuffer(); 082 083 try { 084 agentClass.getDeclaredMethod("premain", String.class, Instrumentation.class); 085 sb.append("Premain-Class: "+ agentClass.getName() + "\n"); 086 } catch (NoSuchMethodException e) { 087 //IGNORE// 088 } 089 090 try { 091 agentClass.getDeclaredMethod("agentmain", String.class, Instrumentation.class); 092 sb.append("Agent-Class: "+ agentClass.getName() + "\n"); 093 } catch (NoSuchMethodException e) { 094 //IGNORE// 095 } 096 097 sb.append("Can-Redefine-Classes: true\n"); 098 sb.append("Can-Retransform-Classes: true\n"); 099 100 try { 101 return sb.toString().getBytes("US-ASCII"); 102 } catch (UnsupportedEncodingException e) { 103 throw new RuntimeException("Charset US-ASCII isn't supported!! This should never happen."); 104 } 105 } 106 107 /** 108 * Create an agent jar file with the required manifest entries. 109 * 110 * @param file the location to create the jar 111 * @param agentClass the agent class 112 * @throws IOException if an error occurs 113 */ 114 public static void createAgentJar(File file, Class<?> agentClass) throws IOException { 115 JarOutputStream jos = new JarOutputStream(new FileOutputStream(file)); 116 117 String classEntryPath = agentClass.getName().replace(".", "/") + ".class"; 118 InputStream classStream = agentClass.getClassLoader().getResourceAsStream(classEntryPath); 119 120 if (classEntryPath.startsWith("/")) classEntryPath = classEntryPath.substring(1); 121 122 JarEntry entry = new JarEntry(classEntryPath); 123 jos.putNextEntry(entry); 124 copy(classStream, jos); 125 jos.closeEntry(); 126 127 entry = new JarEntry("META-INF/MANIFEST.MF"); 128 jos.putNextEntry(entry); 129 jos.write(createManifest(agentClass)); 130 jos.closeEntry(); 131 132 jos.close(); 133 } 134 135 /** 136 * Attempt to locate potential "tools.jar" jars 137 */ 138 private static List<File> getPotentialToolsJars() { 139 List<File> jars = new ArrayList<File>(); 140 141 File javaHome = new File(System.getProperty("java.home")); 142 143 File jreSourced = new File(javaHome, "lib/tools.jar"); 144 if (jreSourced.exists()) { 145 jars.add(jreSourced); 146 } 147 148 if ("jre".equals(javaHome.getName())) { 149 File jdkHome = new File(javaHome, "../"); 150 File jdkSourced = new File(jdkHome, "lib/tools.jar"); 151 if (jdkSourced.exists()) { 152 jars.add(jdkSourced); 153 } 154 } 155 156 return jars; 157 } 158 159 /** 160 * Try and get the VirtualMachine class 161 */ 162 private static Class<?> tryGetVMClass() { 163 try { 164 return AccessController.doPrivileged(new PrivilegedExceptionAction<Class<?>>() { 165 @Override 166 public Class<?> run() throws Exception { 167 try { 168 return ClassLoader.getSystemClassLoader().loadClass(VMCLASS); 169 } catch (ClassNotFoundException e) { 170 for (File jar : getPotentialToolsJars()) { 171 try { 172 return new URLClassLoader(new URL[] {jar.toURI().toURL()}).loadClass(VMCLASS); 173 } catch (Throwable t) { 174 logger.trace("Exception while loading tools.jar from "+ jar, t); 175 } 176 } 177 } 178 return null; 179 } 180 }); 181 } catch (PrivilegedActionException pae) { 182 Throwable actual = pae.getCause(); 183 184 if (actual instanceof ClassNotFoundException) { 185 logger.trace("No VirtualMachine found"); 186 return null; 187 } 188 189 throw new RuntimeException("Unexpected checked exception : " + actual); 190 } 191 } 192 193 private static void loadFailed() { 194 System.err.println("Unable to load the java agent dynamically"); 195 //FIXME: instructions 196 } 197 198 /** 199 * Attempt to dynamically load the given agent class 200 * 201 * @param agentClass the agent class 202 * @throws IOException if an error occurs creating the agent jar 203 */ 204 public static void loadAgent(Class<?> agentClass) throws IOException { 205 File tmp = File.createTempFile("agent", ".jar"); 206 tmp.deleteOnExit(); 207 createAgentJar(tmp, agentClass); 208 209 String nameOfRunningVM = ManagementFactory.getRuntimeMXBean().getName(); 210 int p = nameOfRunningVM.indexOf('@'); 211 String pid = nameOfRunningVM.substring(0, p); 212 213 Class<?> vmClass = tryGetVMClass(); 214 215 if (vmClass == null) { 216 loadFailed(); 217 } else { 218 try { 219 Method attach = vmClass.getMethod("attach", String.class); 220 Method loadAgent = vmClass.getMethod("loadAgent", String.class); 221 Method detach = vmClass.getMethod("detach"); 222 223 Object vm = attach.invoke(null, pid); 224 try { 225 loadAgent.invoke(vm, tmp.getAbsolutePath()); 226 } finally { 227 detach.invoke(vm); 228 } 229 } catch (Exception e) { 230 logger.warn("Loading the agent failed", e); 231 loadFailed(); 232 } 233 } 234 } 235}