如何使用 javassist 检测从特定 jar 加载的方法?
How do I instrument methods that are loaded from a specific jar with javassist?
我有一个示例 jar,我正在从磁盘加载到 class 池中。从那里我可以很容易地访问这个 class 中的方法并像你看到的那样对它们进行检测,就像我用 JsEval 方法所做的那样。
但是,在 Helloworld 示例中class 我希望能够检测其他库函数调用。在这个例子中,我试图检测来自 nashorn 脚本引擎的 eval 函数。但是,这不起作用。我能够很好地访问 class (pool.get),并且能够修补 eval 的方法。但是当我 运行 来自 cl.run() 的 SampleClass 时,这些方法的执行就像没有插入代码一样。我怀疑这与我用来执行 Sampleclass 的 class 加载程序有关,但我被卡住了。关于我在这里做错了什么有什么想法吗?
public class maventest {
public static void main(String[] args)
throws NotFoundException, CannotCompileException, Throwable
{
ClassPool pool = ClassPool.getDefault();
Loader cl = new Loader(pool);
//pool.importPackage(Test.class.getPackage().getName());
//Get the Jar from disk. This works and the method is instrumented.
pool.insertClassPath(
"Z:\HelloWorld\target\HelloWorld-1.0-SNAPSHOT-jar-with-dependencies.jar"
);
pool.importPackage("com.mycompany.helloworld");
//pool.appendClassPath();
CtClass helloworld = pool.get("com.mycompany.helloworld.SampleClass");
helloworld
.getDeclaredMethod("JsEval")
.insertBefore(
"System.out.println(\"Calling JsEval from within helloworld\n\");"
);
//This does not work.
//Attempt to instrument the eval function that is called from inside of HelloWorld
String classToLoad = "jdk.nashorn.api.scripting.NashornScriptEngine";
String constuctor_name = "eval";
CtClass nash = pool.get(classToLoad);
//Multiple eval calls.. Just instrument them all.
CtMethod[] meths = nash.getDeclaredMethods("eval");
for (CtMethod m : meths) {
m.insertBefore(
"System.out.println(\"Nashorn Scripting Engined eval called.\");"
);
}
//Execute the hello world class with null args
cl.run("com.mycompany.helloworld.SampleClass", null);
}
}
这是调用我希望检测的库函数的示例代码。
public class SampleClass {
public static void main(String[] args) throws IOException, NotFoundException {
JsEval("var greeting='hello world'; print(greeting) + greeting");
}
private static void JsEval(String js) {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
try {
Object result = engine.eval(js);
}
catch (ScriptException ex) {
Logger.getLogger(SampleClass.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
我知道这个问题有点老了,但仍然没有答案,我很好奇。
这不起作用的原因是 getDeclaredMethods("eval")
不在 superclasses 中搜索方法,如 Javadoc 中所述。您正在调用的方法,即采用单个 String
参数的方法在父 class AbstractScriptEngine
中定义,但不在 NashornScriptEngine
中。因此,要么您必须将目标 class 更改为真正定义方法的 class,要么您通过 getMethod(..)
或 getMethods()
搜索方法,这两者也是 return继承的方法。因为 getMethods()
不能采用方法名称参数,但 return 是所有方法,并且您必须在检测循环中再次按名称进行过滤,所以我建议您通过指定来挑出您真正想要检测的方法它的确切签名:
String classToLoad = "jdk.nashorn.api.scripting.NashornScriptEngine";
CtClass nash = pool.get(classToLoad);
CtMethod m = nash.getMethod(
"eval",
Descriptor.ofMethod(
pool.get("java.lang.Object"),
new CtClass[] { pool.get("java.lang.String") }
)
);
m.insertBefore("System.out.println(\"Nashorn Scripting Engined eval called.\");");
或者,如果 Descriptor.ofMethod(..)
对您来说太啰嗦,而您对描述符语法感到满意:
String classToLoad = "jdk.nashorn.api.scripting.NashornScriptEngine";
CtClass nash = pool.get(classToLoad);
CtMethod m = nash.getMethod("eval", "(Ljava/lang/String;)Ljava/lang/Object;");
m.insertBefore("System.out.println(\"Nashorn Scripting Engined eval called.\");");
现在您的控制台日志输出符合预期:
Calling JsEval from within helloworld
Warning: Nashorn engine is planned to be removed from a future JDK release
hello world
更新: 哎呀,我错过了你试图修改 bootstrap class 或更普遍的 class 的事实已经加载。在那种情况下,转换没有效果,除非您使用 Java 检测 API,即使用 ClassFileTransformer
,您可以将其集成到 Java 代理中(使用您最喜欢的网络搜索如果您不知道 Java 代理是什么以及如何构建代理)或动态附加。在此示例中,我使用微型 byte-buddy-agent
库将其热附加到 运行 JVM,这样我就可以向您展示效果。
一个超级简单的版本,它不是很通用,但被设计成只寻找 eval(String)
方法,看起来像这样:
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import jdk.nashorn.api.scripting.NashornScriptEngine;
import net.bytebuddy.agent.ByteBuddyAgent;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.ByteArrayInputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;
import java.util.logging.Level;
import java.util.logging.Logger;
public class MavenTest {
public static void main(String[] args) throws UnmodifiableClassException {
Instrumentation instrumentation = ByteBuddyAgent.install();
instrumentation.addTransformer(new ScriptEngineTransformer());
Class<?> targetClass = NashornScriptEngine.class;
// Go up the super class hierarchy, pretending we don't know the exact
// super class class in which the target method is defined
while (!targetClass.equals(Object.class)) {
instrumentation.retransformClasses(targetClass);
targetClass = targetClass.getSuperclass();
}
jsEval("var greeting='hello world'; print(greeting)");
}
private static void jsEval(String js) {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
try {
engine.eval(js);
}
catch (ScriptException ex) {
Logger.getLogger(MavenTest.class.getName()).log(Level.SEVERE, null, ex);
}
}
static class ScriptEngineTransformer implements ClassFileTransformer {
private static final ClassPool CLASS_POOL = ClassPool.getDefault();
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
CtClass targetClass;
try {
// Caveat: Do not just use 'classPool.get(className)' because we would miss previous transformations.
// It is necessary to really parse 'classfileBuffer'.
targetClass = CLASS_POOL.makeClass(new ByteArrayInputStream(classfileBuffer));
CtMethod evalMethod = targetClass.getDeclaredMethod("eval", new CtClass[] { CLASS_POOL.get("java.lang.String") });
targetClass.defrost();
evalMethod.insertBefore("System.out.println(\"Scripting engine eval(String) called\");");
}
catch (Exception e) {
return null;
}
byte[] transformedBytecode;
try {
transformedBytecode = targetClass.toBytecode();
}
catch (Exception e) {
e.printStackTrace();
return null;
}
return transformedBytecode;
}
}
}
您可能已经注意到我重命名了您的一些 class 和方法名称,以使其符合 Java 标准。
现在控制台日志是:
Warning: Nashorn engine is planned to be removed from a future JDK release
Scripting engine eval(String) called
hello world
我有一个示例 jar,我正在从磁盘加载到 class 池中。从那里我可以很容易地访问这个 class 中的方法并像你看到的那样对它们进行检测,就像我用 JsEval 方法所做的那样。
但是,在 Helloworld 示例中class 我希望能够检测其他库函数调用。在这个例子中,我试图检测来自 nashorn 脚本引擎的 eval 函数。但是,这不起作用。我能够很好地访问 class (pool.get),并且能够修补 eval 的方法。但是当我 运行 来自 cl.run() 的 SampleClass 时,这些方法的执行就像没有插入代码一样。我怀疑这与我用来执行 Sampleclass 的 class 加载程序有关,但我被卡住了。关于我在这里做错了什么有什么想法吗?
public class maventest {
public static void main(String[] args)
throws NotFoundException, CannotCompileException, Throwable
{
ClassPool pool = ClassPool.getDefault();
Loader cl = new Loader(pool);
//pool.importPackage(Test.class.getPackage().getName());
//Get the Jar from disk. This works and the method is instrumented.
pool.insertClassPath(
"Z:\HelloWorld\target\HelloWorld-1.0-SNAPSHOT-jar-with-dependencies.jar"
);
pool.importPackage("com.mycompany.helloworld");
//pool.appendClassPath();
CtClass helloworld = pool.get("com.mycompany.helloworld.SampleClass");
helloworld
.getDeclaredMethod("JsEval")
.insertBefore(
"System.out.println(\"Calling JsEval from within helloworld\n\");"
);
//This does not work.
//Attempt to instrument the eval function that is called from inside of HelloWorld
String classToLoad = "jdk.nashorn.api.scripting.NashornScriptEngine";
String constuctor_name = "eval";
CtClass nash = pool.get(classToLoad);
//Multiple eval calls.. Just instrument them all.
CtMethod[] meths = nash.getDeclaredMethods("eval");
for (CtMethod m : meths) {
m.insertBefore(
"System.out.println(\"Nashorn Scripting Engined eval called.\");"
);
}
//Execute the hello world class with null args
cl.run("com.mycompany.helloworld.SampleClass", null);
}
}
这是调用我希望检测的库函数的示例代码。
public class SampleClass {
public static void main(String[] args) throws IOException, NotFoundException {
JsEval("var greeting='hello world'; print(greeting) + greeting");
}
private static void JsEval(String js) {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
try {
Object result = engine.eval(js);
}
catch (ScriptException ex) {
Logger.getLogger(SampleClass.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
我知道这个问题有点老了,但仍然没有答案,我很好奇。
这不起作用的原因是 getDeclaredMethods("eval")
不在 superclasses 中搜索方法,如 Javadoc 中所述。您正在调用的方法,即采用单个 String
参数的方法在父 class AbstractScriptEngine
中定义,但不在 NashornScriptEngine
中。因此,要么您必须将目标 class 更改为真正定义方法的 class,要么您通过 getMethod(..)
或 getMethods()
搜索方法,这两者也是 return继承的方法。因为 getMethods()
不能采用方法名称参数,但 return 是所有方法,并且您必须在检测循环中再次按名称进行过滤,所以我建议您通过指定来挑出您真正想要检测的方法它的确切签名:
String classToLoad = "jdk.nashorn.api.scripting.NashornScriptEngine";
CtClass nash = pool.get(classToLoad);
CtMethod m = nash.getMethod(
"eval",
Descriptor.ofMethod(
pool.get("java.lang.Object"),
new CtClass[] { pool.get("java.lang.String") }
)
);
m.insertBefore("System.out.println(\"Nashorn Scripting Engined eval called.\");");
或者,如果 Descriptor.ofMethod(..)
对您来说太啰嗦,而您对描述符语法感到满意:
String classToLoad = "jdk.nashorn.api.scripting.NashornScriptEngine";
CtClass nash = pool.get(classToLoad);
CtMethod m = nash.getMethod("eval", "(Ljava/lang/String;)Ljava/lang/Object;");
m.insertBefore("System.out.println(\"Nashorn Scripting Engined eval called.\");");
现在您的控制台日志输出符合预期:
Calling JsEval from within helloworld
Warning: Nashorn engine is planned to be removed from a future JDK release
hello world
更新: 哎呀,我错过了你试图修改 bootstrap class 或更普遍的 class 的事实已经加载。在那种情况下,转换没有效果,除非您使用 Java 检测 API,即使用 ClassFileTransformer
,您可以将其集成到 Java 代理中(使用您最喜欢的网络搜索如果您不知道 Java 代理是什么以及如何构建代理)或动态附加。在此示例中,我使用微型 byte-buddy-agent
库将其热附加到 运行 JVM,这样我就可以向您展示效果。
一个超级简单的版本,它不是很通用,但被设计成只寻找 eval(String)
方法,看起来像这样:
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import jdk.nashorn.api.scripting.NashornScriptEngine;
import net.bytebuddy.agent.ByteBuddyAgent;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.ByteArrayInputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;
import java.util.logging.Level;
import java.util.logging.Logger;
public class MavenTest {
public static void main(String[] args) throws UnmodifiableClassException {
Instrumentation instrumentation = ByteBuddyAgent.install();
instrumentation.addTransformer(new ScriptEngineTransformer());
Class<?> targetClass = NashornScriptEngine.class;
// Go up the super class hierarchy, pretending we don't know the exact
// super class class in which the target method is defined
while (!targetClass.equals(Object.class)) {
instrumentation.retransformClasses(targetClass);
targetClass = targetClass.getSuperclass();
}
jsEval("var greeting='hello world'; print(greeting)");
}
private static void jsEval(String js) {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
try {
engine.eval(js);
}
catch (ScriptException ex) {
Logger.getLogger(MavenTest.class.getName()).log(Level.SEVERE, null, ex);
}
}
static class ScriptEngineTransformer implements ClassFileTransformer {
private static final ClassPool CLASS_POOL = ClassPool.getDefault();
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
CtClass targetClass;
try {
// Caveat: Do not just use 'classPool.get(className)' because we would miss previous transformations.
// It is necessary to really parse 'classfileBuffer'.
targetClass = CLASS_POOL.makeClass(new ByteArrayInputStream(classfileBuffer));
CtMethod evalMethod = targetClass.getDeclaredMethod("eval", new CtClass[] { CLASS_POOL.get("java.lang.String") });
targetClass.defrost();
evalMethod.insertBefore("System.out.println(\"Scripting engine eval(String) called\");");
}
catch (Exception e) {
return null;
}
byte[] transformedBytecode;
try {
transformedBytecode = targetClass.toBytecode();
}
catch (Exception e) {
e.printStackTrace();
return null;
}
return transformedBytecode;
}
}
}
您可能已经注意到我重命名了您的一些 class 和方法名称,以使其符合 Java 标准。
现在控制台日志是:
Warning: Nashorn engine is planned to be removed from a future JDK release
Scripting engine eval(String) called
hello world