/*
* Copyright (C) 2009-2014 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package lombok.patcher.inject;
import java.io.File;
import java.lang.management.ManagementFactory;
import java.lang.reflect.InvocationTargetException;
import lombok.patcher.ClassRootFinder;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;
/**
* This class allows you to inject a java agent 'on the fly' into your VM. This feature only works on sun-derived VMs, such as the openJDK, sun JVMs,
* and apple's VMs.
*
* While an agent can be injected in both 1.5 and 1.6 VMs, class reloading is only supported on 1.6 VMs, so if you want to be 1.5 compatible,
* and want to transform classes, you'll need to ensure you inject the agent before the class you're interested in gets loaded. Otherwise, you can
* just reload these classes from within the injected agent.
* <p>
* As a convenience, you can choose to inject this class (which is also an agent), but as a java agent <b>has</b> to come in a jar file, this only
* works if this class has been loaded from a jar file on the local file system. If it has indeed been loaded that way, then this class can take care
* of injecting itself. A common scenario is to distribute your patching application together with {@code lombok.patcher} in a single jar file.
* That way, you can use the live injector, and your injected agent can then run patchscripts that call into your own code, and all your code's dependencies
* will be available because they, too, are in this unified jar file.
*/
public class LiveInjector {
/**
* This interface is used internally by the {@code LiveInjector} to interface with the VM core.
*/
public static interface LibInstrument extends Library {
void Agent_OnAttach(Pointer vm, String name, Pointer Reserved);
}
/**
* This interface is used internally by the {@code LiveInjector} to interface with the VM core.
*/
public interface LibJVM extends Library {
int JNI_GetCreatedJavaVMs(PointerByReference vms, int count, IntByReference found);
}
/**
* Injects the jar file that contains the code for {@code LiveInjector} as an agent. Its your job to make sure this jar is in fact an agent jar.
*
* @throws IllegalStateException If this is not a sun-derived v1.6 VM.
*/
public void injectSelf() throws IllegalStateException {
inject(ClassRootFinder.findClassRootOfSelf());
}
/**
* Injects a jar file into the current VM as a live-loaded agent. The provided jar will be loaded into its own separate class loading context,
* and its manifest is checked for an {@code Agent-Class} to load. That class should have a static method named {@code agentmain} which will
* be called, with an {@link java.lang.instrument.Instrumentation} object that you're probably after.
*
* @throws IllegalStateException If this is not a sun-derived v1.6 VM.
*/
public void inject(String jarFile) throws IllegalStateException {
File f = new File(jarFile);
if (!f.isFile()) throw new IllegalArgumentException("Live Injection is not possible unless the classpath root to inject is a jar file.");
if (System.getProperty("lombok.patcher.safeInject", null) != null) {
slowInject(jarFile);
} else {
fastInject(jarFile);
}
}
private void fastInject(String jarFile) throws IllegalStateException {
try {
Class.forName("sun.instrument.InstrumentationImpl");
} catch (ClassNotFoundException e) {
throw new IllegalStateException("agent injection only works on a sun-derived 1.6 or higher VM");
}
LibJVM libjvm = (LibJVM) Native.loadLibrary(LibJVM.class);
PointerByReference vms = new PointerByReference();
IntByReference found = new IntByReference();
libjvm.JNI_GetCreatedJavaVMs(vms, 1, found);
LibInstrument libinstrument = (LibInstrument)Native.loadLibrary(LibInstrument.class);
Pointer vm = vms.getValue();
libinstrument.Agent_OnAttach(vm, jarFile, null);
}
private void slowInject(String jarFile) throws IllegalStateException {
String ownPidS = ManagementFactory.getRuntimeMXBean().getName();
ownPidS = ownPidS.substring(0, ownPidS.indexOf('@'));
int ownPid = Integer.parseInt(ownPidS);
boolean unsupportedEnvironment = false;
Throwable exception = null;
try {
Class<?> vmClass = Class.forName("com.sun.tools.attach.VirtualMachine");
Object vm = vmClass.getMethod("attach", String.class).invoke(null, String.valueOf(ownPid));
vmClass.getMethod("loadAgent", String.class).invoke(vm, jarFile);
} catch (ClassNotFoundException e) {
unsupportedEnvironment = true;
} catch (NoSuchMethodException e) {
unsupportedEnvironment = true;
} catch (InvocationTargetException e) {
exception = e.getCause();
if (exception == null) exception = e;
} catch (Throwable t) {
exception = t;
}
if (unsupportedEnvironment) throw new IllegalStateException("agent injection only works on a sun-derived 1.6 or higher VM");
if (exception != null) throw new IllegalStateException("agent injection not supported on this platform due to unknown reason", exception);
}
}