Skip to content

Commit

Permalink
Merge pull request Col-E#436 from Col-E/instrumentation
Browse files Browse the repository at this point in the history
[fix] Try to get live version of classes instead of source
  • Loading branch information
Col-E authored Jan 4, 2022
2 parents f89a46b + e60caf9 commit 9fdc407
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 10 deletions.
1 change: 1 addition & 0 deletions src/main/java/me/coley/recaf/Recaf.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ private static void agent(String args, Instrumentation inst) {
}

init();
VMUtil.patchInstrumentation(inst);
// Log that we are an agent
info("Starting as agent...");
// Add instrument launch arg
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/me/coley/recaf/util/VMUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.sun.javafx.application.PlatformImpl;

import java.lang.instrument.Instrumentation;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import javafx.application.Platform;
Expand Down Expand Up @@ -293,4 +294,26 @@ private static void patchReflectionFilters() {
throw new RuntimeException("Unable to patch reflection filters", ex);
}
}

/**
* Attempts to make an {@link Instrumentation} capable
* of transforming/redefining classes.
*
* @param instrumentation
* Instrumentation to patch.
*/
public static void patchInstrumentation(Instrumentation instrumentation) {
try {
Class<?> klass = instrumentation.getClass();
Field field = klass.getDeclaredField("mEnvironmentSupportsRedefineClasses");
field.setAccessible(true);
field.setBoolean(instrumentation, true);
(field = klass.getDeclaredField("mEnvironmentSupportsRetransformClassesKnown")).setAccessible(true);
field.setBoolean(instrumentation, true);
(field = klass.getDeclaredField("mEnvironmentSupportsRetransformClasses")).setAccessible(true);
field.setBoolean(instrumentation, true);
} catch (NoSuchFieldException | IllegalAccessException ex) {
Log.error("Could not patch instrumentation instance:", ex);
}
}
}
33 changes: 23 additions & 10 deletions src/main/java/me/coley/recaf/workspace/InstrumentationResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ private void loadRuntimeClasses(Map<String, byte[]> map) throws IOException {
// iterate over loaded classes
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[8192];
Class<?>[] klass = {null};
int failedTransformations = 0;
// Let's skipp all Recaf's classes.
for(Class<?> c : instrumentation.getAllLoadedClasses()) {
if (ClasspathUtil.isRecafClass(c)) {
Expand All @@ -150,18 +152,29 @@ private void loadRuntimeClasses(Map<String, byte[]> map) throws IOException {
// Skip array types
if (name.contains("["))
continue;
String path = name.concat(".class");
ClassLoader loader = c.getClassLoader();
try(InputStream in = (loader != null) ?
loader.getResourceAsStream(path) :
ClassLoader.getSystemResourceAsStream(path)) {
if(in != null) {
out.reset();
getClasses().put(name, IOUtil.toByteArray(in, out, buffer));
getDirtyClasses().remove(name);
try {
klass[0] = c;
instrumentation.retransformClasses(klass);
} catch (UnmodifiableClassException ex) {
if (++failedTransformations < 5) {
Log.error("Could not get live version of a class {}:", name, ex);
}
String path = name.concat(".class");
ClassLoader loader = c.getClassLoader();
try(InputStream in = (loader != null) ?
loader.getResourceAsStream(path) :
ClassLoader.getSystemResourceAsStream(path)) {
if(in != null) {
out.reset();
getClasses().put(name, IOUtil.toByteArray(in, out, buffer));
getDirtyClasses().remove(name);
}
}
}
}
if (failedTransformations != 0) {
Log.error("Could not get live version for {} classes", failedTransformations);
}
}

/**
Expand Down Expand Up @@ -199,7 +212,7 @@ public byte[] transform(ClassLoader loader, String className,
Class<?> cls, ProtectionDomain domain, byte[] buffer) {
// This super odd way of getting the resource IS INTENTIONAL.
// If you choose to optimize this in the future verify it behaves the same.
InstrumentationResource res = null;
InstrumentationResource res;
try {
res = getInstance();
synchronized (this) {
Expand Down

0 comments on commit 9fdc407

Please sign in to comment.