Cannot expose Java Instrumentation for tomcat jvm - java

I am using a tomcat7 instance to run a Java application. My application needs the Java instrumentation exposed. This is done with a javaagent and I pass the agent at startup to the JVM in the setenv.bat script.
set JAVA_OPTS=%JAVA_OPTS% -javaagent:"C:\path\to\agent.jar"
In the manifest file I have the required section:
Premain-Class: package.name.agent.ExposeInstrumentation
In the premain method of the agent class assign the instrumentation provided by the JVM to a static variable accessible through a static method
public final class ExposeInstrumentation {
private static Instrumentation s_instrumentation;
public static void premain(String arguments, Instrumentation instrumentation) {
s_instrumentation = instrumentation;
}
public static Instrumentation getInstrumentation() {
return s_instrumentation;
}
}
But in my code when I do this:
Instrumentation instrumentation = ExposeInstrumentation.getInstrumentation();
getInstrumentation() returns null;
What is the problem?
UPDATE
I did some further debugging and premain gets executed and s_instrumentation receives the instrumentation, but when I call getInstrumentation later on in my code s_instumentation is set to null. This is strange I tought the value remains valid thought the life of the program.

I assume that you are loading the class ExposeInstrumentation twice. Once by the application class loader which is child-first (reverse-order) and once via the Java agent where the class is automatically loaded by the system class loader. As a result, the ExposeInstrumentation class is loaded twice where you access the one from your application where the field is not set.
You can solve this by explicitly accessing the class loaded by the system class loader:
class ExposeInstrumentation {
// public to assure accessability
public static Instrumentation s_instrumentation;
public static void premain(String arguments, Instrumentation inst) {
s_instrumentation = inst;
}
public static Instrumentation getInstrumentation() {
try {
return (Instrumentation) ClassLoader.getSystemClassLoader()
.loadClass(ExposeInstrumentation.class.getName())
.getDeclaredField("s_instrumentation")
.get(null);
} catch(Exception e) {
return null;
}
}
}
You can also check out the Byte Buddy Agent project that offers this functionality and more (runtime installation) of an agent. With Byte Buddy, you can simply call ByteBuddyAgent.getInstrumentation().

Related

How do I activate the Java class resource lookup cache?

I want to check if the VM property sun.cds.enableSharedLookupCache helps improve the performance of an application that does numerous calls to ClassLoader::getResource(). I found that property in URLClassPath.
I tried to turn it on with -Dsun.cds.enableSharedLookupCache=true or -Dsun.cds.enableSharedLookupCache as VM argument but that did not activate the cache.
I use this simple test program to test the activation :
public class Test {
public static void main(String[] args) {
System.out.println(Test.class.getClassLoader().getResource("java/lang/Exception.class"));
}
}
The value of field lookupCacheEnabled in URLClassPath is false :
private static volatile boolean lookupCacheEnabled
= "true".equals(VM.getSavedProperty("sun.cds.enableSharedLookupCache"));
EDIT: jdk version is jdk8
What am I missing here ?

ClassTransformer not executing

USE CASE
I'm attempting to write a Java SE8 ClassFileTransformer implementation. The goal of this is Debugging. I'm well aware of BTrace but it doesn't really fit the bill for what I'm trying to do. What that is, is internal method level inspection. BTrace limits its breakpoints to entry/exit. I wanted to get into the details of this.
So I figured I'd do this myself with ASM now hard can it be?
(ASM is a byte code manipulation library used by ByteBuddy and BTrace)
PROBLEM
So I started by defining a simple ClassFileTransformer.
public class PreMainInjection {
public static void premain(String agentArgs,
Instrumentation inst) {
inst.addTransformer(new EntryPoint(), true);
}
}
public class EntryPoint implements ClassFileTransformer {
public EntryPoint() { }
#Override
public byte[] transform(ClassLoader classloader, String name,
Class<?> clazz, ProtectionDomain prot, byte[] data) {
System.out.printf("Loaded: %s\n", name);
}
}
And this worked perfectly :D I see a list of all the classes as the application I'm inspecting start.
So now I bring in ASM.
public class PreMainInjection {
public static void premain(String agentArgs,
Instrumentation inst) {
inst.addTransformer(new EntryPoint(), true);
}
}
public class EntryPoint implements ClassFileTransformer {
public EntryPoint() { }
#Override
public byte[] transform(ClassLoader classloader, String name,
Class<?> clazz, ProtectionDomain prot, byte[] data) {
ClassReader reader = new ClassReader(bytes);
ClassNode node = new ClassNode();
ClassWriter writer = new ClassWriter(
ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS);
reader.accept(node, ClassReader.EXPAND_FRAMES);
System.out.printf("Name: %s", node.name);
node.accept(writer);
return writer.toByteArray();
}
}
This works... so far as my app doesn't break. But I never see a single debug output.
So what is going on?
To expand further if my agent just does
System.out.printf("Loaded %s\n", name);
return null;
The app still works and I can see the outputs. So I'm deeply confused.
To answer your first question how hard ASM can be: very hard.
To get to your actual point: you are probably getting an exception before reaching the print output. With a class file transformer, exceptions are suppressed if they escape the transform method. Did you try to wrap the code in a try-catch block to see if an exception is raised?
There is also a chance that you did not bundle ASM with your agent. In this case, you would get an error rather than an exception during your transformer's execution. Finally, I would try your agent without frame computation as this requires access to all referenced class files which can also be a source of problems.
So I appear to be hitting a weird edge case
ClassNode's internal fields are only instantiated after a full visit of the ClassReader has been conducted. As that is the case it is statically provable System.out.printf("Name: %s", node.name); will always return a NullPointerException at compile time.
So I believe the JVM simply doesn't load my ClassFileTransformer.
Either way I have the system working.

How to set/get a value from another JVM

I have a class Normal with the following code:
public class Normal {
private static String myStr = "Not working...";
private static boolean running = true;
public static void main(String[] args) {
while(running) {
System.out.println(myStr);
}
}
}
And I have another class named Injector in another project. Its purpose is to change the values of Normal even though they are not in the same JVM:
public class Injector {
public static void main(String[] args) {
String PID = //Gets PID, which works fine
VirtualMachine vm = VirtualMachine.attach(PID);
/*
Set/Get field values for classes in vm?
*/
}
}
What I want to do is change the values myStr and running in the class Normal to "Working!" and false respectively without changing the code in Normal (Only in Injector).
Thanks in advance
You'll need two JARs:
One is Java Agent that uses Reflection to change the field value. Java Agent's main class should have agentmain entry point.
public static void agentmain(String args, Instrumentation instr) throws Exception {
Class normalClass = Class.forName("Normal");
Field myStrField = normalClass.getDeclaredField("myStr");
myStrField.setAccessible(true);
myStrField.set(null, "Working!");
}
You'll have to add MANIFEST.MF with Agent-Class attribute and pack the agent into a jar file.
The second one is a utility that uses Dynamic Attach to inject the agent jar into the running VM. Let pid be the target Java process ID.
import com.sun.tools.attach.VirtualMachine;
...
VirtualMachine vm = VirtualMachine.attach(pid);
try {
vm.loadAgent(agentJarPath, "");
} finally {
vm.detach();
}
A bit more details in the article.

Can different Java applications use the same javaagent?

I hava a javaagent Jar simpleAgent.jar. I used it to redifine classes in it and I cached some classes to avoid redifine
public class Premain {
private static Instrumentation instrumentation;
private static final Map<String, Class> allLoadClassesMap = new ConcurrentHashMap<>();
public static void premain(String agentArgs, Instrumentation inst) {
instrumentation = inst;
cacheAllLoadedClasses("com.example");
}
public static void cacheAllLoadedClasses(String prfixName) {
try {
Class[] allLoadClasses = instrumentation.getAllLoadedClasses();
for (Class loadedClass : allLoadClasses) {
if (loadedClass.getName().startsWith(prfixName)) {
allLoadClassesMap.put(loadedClass.getName(), loadedClass);
}
}
logger.warn("Loaded Class Count " + allLoadClassesMap.size());
} catch (Exception e) {
logger.error("", e);
}
}
}
I have three different application app1.jar, app2.jar, app3.jar, so when I start the three application can I use the same agent jar? Eg.:
java -javaagent:simpleAgent.jar -jar app1.jar
java -javaagent:simpleAgent.jar -jar app2.jar
java -javaagent:simpleAgent.jar -jar app3.jar
I don't know the javaagent's implementation, so I was scared that using the same javaagent can trigger in app1 or app2 or app3 crash.
Each JVM instance is separate and does not "know" about other JVMs unless you do something in application level. So, generally the answer is "yes, you can use the same jar either javaagent or not for as many JVM instances as you want."
A Javaaget is treated by the VM similarly to jar files on the class path. Those files are read only, all state is contained in the running VM such that they are safely shared among multiple processes.

java instrumentation jar in eclipse - failure in manifest.mf

I am trying to use the java.lang.instrument.Instrumentation class which requires usage of the 'premain' class - a good descrip can be found on stack here.
The problem is that I have done this and am having trouble using it in another program. My class looks like this:
public class InstrumentationWrapper {
private static final String INSTR_KEY = "test.instrumentation";
private static Instrumentation instrumentation;
public static void premain(String options, Instrumentation inst) {
Properties props = System.getProperties();
if(props.get(INSTR_KEY) == null)
props.put(INSTR_KEY, inst);
}
public static Instrumentation getInstrumentation() {
if (instrumentation == null) {
instrumentation = (Instrumentation) System.getProperties().get(INSTR_KEY);
}
return instrumentation;
}
public static long getObjectSize(Object o) {
return instrumentation.getObjectSize(o);
}
public static long getSizeOfObjects (Collection<?> col) {
long cumSize = 0;
for (Object o : col) {
cumSize = getObjectSize (o);
}
return cumSize;
}
}
The manifest is in the Jar file as such:
$ jar -tf target/instrumentator-1.0.jar
META-INF/
META-INF/MANIFEST.MF
com/
com/testTools/
com/testTools/instrumentation/
com/testTools/instrumentation/InstrumentationWrapper.class
META-INF/maven/
META-INF/maven/com.netrecon.testTools/
META-INF/maven/com.netrecon.testTools/instrumentator/
META-INF/maven/com.netrecon.testTools/instrumentator/pom.xml
META-INF/maven/com.netrecon.testTools/instrumentator/pom.properties
and the MANIFEST.MF is just:
$ more src/resources/META-INF/MANIFEST.MF
Manifest-Version: 1.0
Premain-Class: com.testTools.instrumentation.InstrumentationWrapper
In the launch configuration in eclipse I get the following problem
Failed to find Premain-Class manifest attribute in Z:\workspace\<project>\testTools\instrumentor\target\instrumentator-1.0.jar
and the option is -javaagent:${workspace_loc:instrumentator}\target\instrumentator-1.0.jar
I am really unsure how I can get this to work - All I really need to do is have a test harness that will let me look at the memory foot print of an array. Any ideas?
Nothing jumps out at me, but if you want to inspect further you can write a quick class to open up your Jar file with java.util.jar.JarFile and programatically inspect the manifest. This will indicate whether the issue is somehow in the way you wrote your manifest (maybe a space in the wrong place or something) or the way it's getting loaded (Maybe there is a typo in the specification of the premain class?).

Categories