I have a simple application that creates and uses instances of ToChange
class in a loop:
public class ToChange {
private int i;
public ToChange(int i) {
this.i = i;
}
public void print() {
System.out.println(i);
}
}
public class MyApplication {
public static void main(String[] args) {
System.out.println("pid = " + ProcessHandle.current().pid());
int i = 0;
// This is an instance for the whole application lifecycle
var wholeAppInstance = new ToChange(0);
while (true) {
// Use the wholeAppInstance first
wholeAppInstance.print();
// Create and use a new instance in each loop
new ToChange(++i).print();
System.out.println("------------");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
While the application runs, there are such lines in the console:
0
1
------------
0
2
------------
0
3
------------
I also have an attach
application that loads an agent and attaches it to a running JVM.
It takes two arguments, the first is the pid
of a the running JVM process and the second is the path of the agent that will be attached:
// The main class of the attach application
public class Main {
public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
String processId = args[0];
String agentJar = args[1];
VirtualMachine virtualMachine = VirtualMachine.attach(processId);
try {
virtualMachine.loadAgent(agentJar);
} catch (Exception error) {
error.printStackTrace();
} finally {
virtualMachine.detach();
}
}
}
Finally, I have the agent, which redefines the ToChange
class:
public class MyAgentMain {
public static void agentmain(String arg, Instrumentation instrumentation) throws Exception {
System.out.println("Agent mark 1");
Class<?> toChange = Class.forName("org.abc.security.attach.application.ToChange");
System.out.println("Agent mark 2");
ByteArrayOutputStream output = new ByteArrayOutputStream();
try (InputStream input = MyAgentMain
.class
.getResourceAsStream("/org/abc/security/attach/application/ToChange.class")) {
System.out.println("input reference: " + input);
byte[] buffer = new byte[1024];
int length;
while ((length = input.read(buffer)) != -1) {
output.write(buffer, 0, length);
}
System.out.println("Agent mark 3");
} catch (Exception error) {
error.printStackTrace();
}
instrumentation.redefineClasses(new ClassDefinition(toChange, output.toByteArray()));
System.out.println("Agent mark 4");
}
}
The MANIFEST.MF of the agent contains the following:
Manifest-Version: 1.0
Created-By: Maven Jar Plugin 3.2.0
Build-Jdk-Spec: 11
Agent-Class: org.abc.security.attach.agent.MyAgentMain
Can-Redefine-Classes: true
And the ToChange
class is changed to the following:
public class ToChange {
private int i;
public ToChange(int i) {
this.i = i;
}
public void print() {
System.out.println("Changed:" + this.i);
}
}
I execute the attach application, passing the pid of the running MyApplication
and the agent location:
java --add-modules jdk.attach -jar attach-0.1.0-SNAPSHOT.jar $PID agent-0.1.0-SNAPSHOT.jar
I see in the MyApplication
console the println
s produced by the agent and there are no errors neither in the MyApplication
console, nor in the agent console.
However, after the agent completes, I still see the same output in MyApplication
:
------------
0
7
------------
Agent mark 1
Agent mark 2
input reference: java.io.BufferedInputStream@540e6237
Agent mark 3
Agent mark 4
0
8
------------
0
9
------------
I would expect that since there are no errors, the instrumentation succeeds, the ChangeTo
class gets redefined and the println
s are different.
However, this is not the case...
What I am doing (or expecting) wrong?
EDITED to answer my question
@johannes-kuhn 's reply gave me the idea to try changing the redefined class location in the agent, in order not to be under the same path with the original.
It seems that the "resources" of the final, instrumented MyApplication
remained unchanged, because the redefined class had the same path in the resources with the old one. Changing the location of the ToChange class in the agent resources, did the trick!
For clarity, here is the working MyAgentMain
:
public class MyAgentMain {
public static void agentmain(String arg, Instrumentation instrumentation) throws Exception {
System.out.println("Agent mark 1");
Class<?> license = Class.forName("org.abc.security.attach.application.ToChange");
System.out.println("Agent mark 2");
ByteArrayOutputStream output = new ByteArrayOutputStream();
try (InputStream input = MyAgentMain
.class
.getResourceAsStream("/ToChange.class")) { // THE NON-WORKING VERSION HAD /org/abc/security/attach/application/ToChange.class
System.out.println("input reference: " + input);
byte[] buffer = new byte[1024];
int length;
while ((length = input.read(buffer)) != -1) {
output.write(buffer, 0, length);
}
System.out.println("Agent mark 3");
} catch (Exception error) {
error.printStackTrace();
}
instrumentation.redefineClasses(new ClassDefinition(license, output.toByteArray()));
System.out.println("Agent mark 4");
}
}
question from:
https://stackoverflow.com/questions/65914608/java-cannot-redefine-class-during-instrumentation-on-attach