2

1. 关于asmtools.jar

用途:使得 “.class文件 -> 字节码指令(类似汇编语言)文件 -> .class文件”,并可以修改“字节码指令文件” 改变一个“.class文件”的运行结果。并重新生成class文件

2. 获取方式asmtools.jar

环境准备:JDK version 8.0, Ant version 1.8 or later, mercurial

下载:hg clone http://hg.openjdk.java.net/code-tools/asmtools
编译:cd asmtools/build && ant 上面是下载打包方式,如果图懒省事,直接使用别人打包好的即可:

3. 使用案例

以郑雨迪老师在“深入拆解JVM”专栏https://time.geekbang.org/col... 中对boolean类型在jvm中的类型为例示例如何使用:

public class Foo {
 public static void main(String[] args) {
  boolean flag = true;
  if (flag) System.out.println("Hello, Java!");
  if (flag == true) System.out.println("Hello, JVM!");
 }
}

javac Foo.java 命令生成 Foo.class 文件, java Foo 命令运行 Foo.class 文件输出结果:

Hello, Java!
Hello, JVM!

由 class 文件生成 jasm 文件

如下命令将 class 文件中的内容转换为对应的 jasm 语法

java -jar asmtools.jar jdis Foo.class


执行结果为:

package  foo;

super public class Foo
        version 52:0
{


public Method "<init>":"()V"
        stack 1 locals 1
{
                aload_0;
                invokespecial   Method java/lang/Object."<init>":"()V";
                return;
}

public static Method main:"([Ljava/lang/String;)V"
        stack 2 locals 2
{
                iconst_1;
                istore_1;
                iload_1;
                ifeq    L14;
                getstatic       Field java/lang/System.out:"Ljava/io/PrintStream;";
                ldc     String "Hello, Java!";
                invokevirtual   Method java/io/PrintStream.println:"(Ljava/lang/String;)V";
        L14:    stack_frame_type append;
                locals_map int;
                iload_1;
                iconst_1;
                if_icmpne       L27;
                getstatic       Field java/lang/System.out:"Ljava/io/PrintStream;";
                ldc     String "Hello, JVM!";
                invokevirtual   Method java/io/PrintStream.println:"(Ljava/lang/String;)V";
        L27:    stack_frame_type same;
                return;
}

} // end Class Foo

可以到看到boolean在JVM实际上是作为int处理的
iconst_1 将1压入操作数栈
istore_1 将操作数栈顶保存至局部变量表1位置,
iload_1 再讲局部变量表1 位置加载到操作数栈顶
ifeq L14 判断栈顶位置是否为0 为零则跳转到L14

我们来尝试修改 将压入的1 改为2 看看什么效果
linux下java -jar asmtools.jar jdis Foo.class > Foo.jasm将上述结果输出到文件中。window下直接新建文件将上一个命令输出结果内容拷贝过去即可
将 Foo.class 中的 int i = 1; 修改成 int i = 2;,我们只需要替换 Foo.jasm 文件中的 iconst_1 为 iconst_2

由jsam文件生成class文件

将修改过后的Foo.jasm 执行命令生成class文件

java -jar asmtools.jar jasm Foo.jasm

此时再执行 java Foo 输出:

Hello, Java!

可见字节码已经被修改,至于为何第一个位false第二个为true,解释为:
ifeq 指令的逻辑是 判断栈顶元素是否为0 不为零纪委true了所以改为2 依然为true。
if_cmpne做整数比较,iconst_1是否等于flag(1)比较失败。

扩展
public class Foo {  
    static boolean boolValue;  
    public static void main(String[] args) {    
        boolValue = true; // 将这个true替换为2或者3,再看看打印结果   
        if (boolValue) System.out.println("Hello, Java!");    
        if (boolValue == true) System.out.println("Hello, JVM!"); 
    }
}

结果:
当替换为2的时候无输出
当替换为3的时候打印HelloJava及HelloJVM
是因为将boolean 保存在静态域中,指定了其类型为'Z',当修改为2时取低位最后一位为0,当修改为3时取低位最后一位为1
则说明boolean的掩码处理是取低位的最后一位
并发编程图谱.jpg

4.总结

主要就两条命令
由 class 文件生成 jasm 文件:java -jar asmtools.jar jdis Foo.class > Foo.jasm
由 jasm 文件生成 class 文件:java -jar asmtools.jar jasm Foo.jasm


mamba
4 声望0 粉丝