JDK自带的反编译工具 javap

xiaoxiao2021-03-01  8

前言

以前经常看一些文章使用 javac 反编译 class文件,然后生成一堆字节码,再一顿骚操作分析字节码,可谓是非常炫酷。这里有时间刚好也来玩玩JDK的 javap

javap 介绍

javap是 JDK自带的一个工具,可以将 class文件反编译成字节码,它并没有将class文件反编译成 java文件,但是依然反编译成程序员能读的格式。

下面举一个小例子,java源代码如下:

public class JavapTest2 { private String username; public void say(String username) { System.out.println("hi,"+username); } }

将其编译后,使用 javap来查询 JavapTest2的字节码

javac JavapTest2.java javap -p -v JavapTest2

生成的字节码如下:

Classfile ../JavapTest2.class Last modified 2018-8-31; size 608 bytes MD5 checksum 25f04ad8674616cb2f0e7fe9d35e6ab1 Compiled from "JavapTest2.java" public class com.pjmike.JVM.JavapTest2 minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #10.#21 // java/lang/Object."<init>":()V #2 = Fieldref #22.#23 // java/lang/System.out:Ljava/io/PrintStream; #3 = Class #24 // java/lang/StringBuilder #4 = Methodref #3.#21 // java/lang/StringBuilder."<init>":()V #5 = String #25 // hi, #6 = Methodref #3.#26 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/String Builder; #7 = Methodref #3.#27 // java/lang/StringBuilder.toString:()Ljava/lang/String; #8 = Methodref #28.#29 // java/io/PrintStream.println:(Ljava/lang/String;)V #9 = Class #30 // com/pjmike/JVM/JavapTest2 #10 = Class #31 // java/lang/Object #11 = Utf8 username #12 = Utf8 Ljava/lang/String; #13 = Utf8 <init> #14 = Utf8 ()V #15 = Utf8 Code #16 = Utf8 LineNumberTable #17 = Utf8 say #18 = Utf8 (Ljava/lang/String;)V #19 = Utf8 SourceFile #20 = Utf8 JavapTest2.java #21 = NameAndType #13:#14 // "<init>":()V #22 = Class #32 // java/lang/System #23 = NameAndType #33:#34 // out:Ljava/io/PrintStream; #24 = Utf8 java/lang/StringBuilder #25 = Utf8 hi, #26 = NameAndType #35:#36 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #27 = NameAndType #37:#38 // toString:()Ljava/lang/String; #28 = Class #39 // java/io/PrintStream #29 = NameAndType #40:#18 // println:(Ljava/lang/String;)V #30 = Utf8 com/pjmike/JVM/JavapTest2 #31 = Utf8 java/lang/Object #32 = Utf8 java/lang/System #33 = Utf8 out #34 = Utf8 Ljava/io/PrintStream; #35 = Utf8 append #36 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder; #37 = Utf8 toString #38 = Utf8 ()Ljava/lang/String; #39 = Utf8 java/io/PrintStream #40 = Utf8 println { private java.lang.String username; descriptor: Ljava/lang/String; flags: ACC_PRIVATE public com.pjmike.JVM.JavapTest2(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 7: 0 public void say(java.lang.String); descriptor: (Ljava/lang/String;)V flags: ACC_PUBLIC Code: stack=3, locals=2, args_size=2 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: new #3 // class java/lang/StringBuilder 6: dup 7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 10: ldc #5 // String hi, 12: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/ lang/StringBuilder; 15: aload_1 16: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/ lang/StringBuilder; 19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 22: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 25: return LineNumberTable: line 11: 0 line 12: 25 } SourceFile: "JavapTest2.java"

默认情况下 javap 会打印所有非私有的字段和方法,如下:

javap JavapTest2 Compiled from "JavapTest2.java" public class com.pjmike.JVM.JavapTest2 { public com.pjmike.JVM.JavapTest2(); public void say(java.lang.String); }

用 javap -help查看其选项:

用法: javap <options> <classes> 其中, 可能的选项包括: -help --help -? 输出此用法消息 -version 版本信息 -v -verbose 输出附加信息 -l 输出行号和本地变量表 -public 仅显示公共类和成员 -protected 显示受保护的/公共类和成员 -package 显示程序包/受保护的/公共类 和成员 (默认) -p -private 显示所有类和成员 -c 对代码进行反汇编 -s 输出内部类型签名 -sysinfo 显示正在处理的类的 系统信息 (路径, 大小, 日期, MD5 散列) -constants 显示最终常量 -classpath <path> 指定查找用户类文件的位置 -cp <path> 指定查找用户类文件的位置 -bootclasspath <path> 覆盖引导类文件的位置

从上面就可以看到 javap 选项的一些作用,在最开始的地方,我们使用了 javap -v -p JavapTest2 。加了 -p 选项后,还会打印私有的字段和方法,加上 -v 选项后,它会尽可能地打印出所有信息,如果只需要查询相关方法对应的字节码,可以使用 -c 代替 -v,代码如下:

Compiled from "JavapTest2.java" public class com.pjmike.JVM.JavapTest2 { private java.lang.String username; public com.pjmike.JVM.JavapTest2(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public void say(java.lang.String); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: new #3 // class java/lang/StringBuilder 6: dup 7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 10: ldc #5 // String hi, 12: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/la ng/StringBuilder; 15: aload_1 16: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/la ng/StringBuilder; 19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 22: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 25: return }

可以看出少了很多附加信息,让我们更加专心的去关注方法对应的字节码。

下面简要分析下 say方法中的打印语句,里面涉及了字符串的拼接操作:

首先是new 指令,创建类实例的指令,在Java源代码的字符串拼接,到了编译器在编译阶段使用 StringBuilder类进行优化 3: new #3 // class java/lang/StringBuilder 然后 invokespecial指令,用于调用实例初始化方法,将 StringBuilder对象初始化 7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V ldc将”hi”字符串常量加载到操作数栈,然后invokevirtual指令用于调用对象的实例方法,这里调用 StringBuilder的append()拼接字符串的方法 10: ldc #5 // String hi, 12: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/la ng/StringBuilder; 最后调用 StringBuilder的toString(),将拼接后的字符串输出 19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

以上非常简要的分析了 字符串拼接的字节码操作,更多关于字节码的指令介绍,请参阅相关文档

小结

关于 javap 以及相关字节码知识目前还是接触不多,这里只是简单玩一玩javap,更多字节码相关的知识以及其他反编译工具,如`jad,cfr等还需要后续进一步深入探究。

转载请注明原文地址: https://www.6miu.com/read-3100211.html

最新回复(0)