有什么方法可以打印调用 instance/Static 方法的对象引用,使用字节码检测
Is there any way to print the object ref that called an instance/Static method, Using byte-code instrumentation
我在某处读到,每当 "invokevirtual" 调用一个方法时,
对象引用从栈顶获取,后面是参数。
我需要以某种方式打印对象引用。可能吗?
所以,我不会为您做这件事,因为实际的代码既烦人又乏味,如果您真的很感兴趣,您应该自己学习如何做。但我会尽力提供帮助并为您提供一些指导。
首先,您需要阅读 ASM tutorials here。
我下面要写的字节码格式来自ASMIfier,因为它更清晰。我将完全忽略 javap,因为它更加迂腐和详细,但如果您想知道它实际向您展示了什么,那么您应该阅读 Java ClassFile format.
实际上,无论如何你都应该先这样做,只是为了确保你的背景知识有所充实。
所以,这是您想要做的事情的简要说明。您将要编写一个 ClassWriter 来查找 INVOKEVIRTUAL 操作码的实例。
invokevirtual 以相反的顺序从堆栈中弹出值,因此最后一个参数在前,您最后调用的对象。您所指的 #38 也不是对象,它是对常量池的引用,其中包含方法名称和方法描述符对,用作元数据,因为 JVM 是类型安全的。
假设您有此代码:
包装样本;
public class JavaSimpleHelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
如果你运行ASMIFier反对它,你会得到类似这样的主要方法(为简洁起见删减上下文)
public static main([Ljava/lang/String;)V
L0
LINENUMBER 6 L0
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "Hello World"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L1
LINENUMBER 7 L1
RETURN
L2
LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
MAXSTACK = 2
MAXLOCALS = 1
因此,您实施某种静态转储方法 (public static final dump( Object o ) ),并编写一个 class 重组字节码的访问者。
您可以使用方法描述符来确定您需要在之前的堆栈推送指令(ALOAD、LDC、)中插入多深的 DUP/INVOKE 以打印您的方法对象目标。例如,System.out.println 的方法描述符是 [Ljava/lang/String;]V,这意味着该方法采用字符串数组和 returns void。所以你需要在堆栈中返回 1 来找到对象目标。反过来,您的字节码将如下所示:
快乐的字节码乱七八糟
public static main([Ljava/lang/String;)V
L0
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
DUP
INVOKESTATIC my/staticutil/ClassThatDumps.dump (Ljava/lang/Object;)V
LDC "Hello World"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
RETURN
L1
LOCALVARIABLE args [Ljava/lang/String; L0 L1 0
MAXSTACK = 2
MAXLOCALS = 1
我在某处读到,每当 "invokevirtual" 调用一个方法时, 对象引用从栈顶获取,后面是参数。 我需要以某种方式打印对象引用。可能吗?
所以,我不会为您做这件事,因为实际的代码既烦人又乏味,如果您真的很感兴趣,您应该自己学习如何做。但我会尽力提供帮助并为您提供一些指导。
首先,您需要阅读 ASM tutorials here。
我下面要写的字节码格式来自ASMIfier,因为它更清晰。我将完全忽略 javap,因为它更加迂腐和详细,但如果您想知道它实际向您展示了什么,那么您应该阅读 Java ClassFile format.
实际上,无论如何你都应该先这样做,只是为了确保你的背景知识有所充实。
所以,这是您想要做的事情的简要说明。您将要编写一个 ClassWriter 来查找 INVOKEVIRTUAL 操作码的实例。
invokevirtual 以相反的顺序从堆栈中弹出值,因此最后一个参数在前,您最后调用的对象。您所指的 #38 也不是对象,它是对常量池的引用,其中包含方法名称和方法描述符对,用作元数据,因为 JVM 是类型安全的。
假设您有此代码:
包装样本;
public class JavaSimpleHelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
如果你运行ASMIFier反对它,你会得到类似这样的主要方法(为简洁起见删减上下文)
public static main([Ljava/lang/String;)V
L0
LINENUMBER 6 L0
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "Hello World"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L1
LINENUMBER 7 L1
RETURN
L2
LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
MAXSTACK = 2
MAXLOCALS = 1
因此,您实施某种静态转储方法 (public static final dump( Object o ) ),并编写一个 class 重组字节码的访问者。
您可以使用方法描述符来确定您需要在之前的堆栈推送指令(ALOAD、LDC、)中插入多深的 DUP/INVOKE 以打印您的方法对象目标。例如,System.out.println 的方法描述符是 [Ljava/lang/String;]V,这意味着该方法采用字符串数组和 returns void。所以你需要在堆栈中返回 1 来找到对象目标。反过来,您的字节码将如下所示:
快乐的字节码乱七八糟
public static main([Ljava/lang/String;)V
L0
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
DUP
INVOKESTATIC my/staticutil/ClassThatDumps.dump (Ljava/lang/Object;)V
LDC "Hello World"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
RETURN
L1
LOCALVARIABLE args [Ljava/lang/String; L0 L1 0
MAXSTACK = 2
MAXLOCALS = 1