跳到正文
W Winse Blog
dev 10 min read

java反编译工具使用记录

# 工具

# 差异

JD-Core还是比较与时俱进的,对泛型/foreach等新的语法都支持。Jad则只支持到only 45.3(1.1), 46.0 and 47.0(1.3)。
任何事物都不是完美的,结合两个工具来看反编译后的源码能更好的回归源码的真相。

  • JD-Core解析双for循环是存在问题!结合jad可能更好的明白源码。
  • 内部类解析不够好,JD-Core相对错误少一些

# 对比

这里就[for循环]/[双层for循环]/[内部类]进行对比。

# 单for循环

源码

List<String> list = Arrays.asList("abc", "bcd");

public void testForEach() {
	for (String ele : list) { // for编译成字节码后使用iterator的形式
		System.out.println(ele);
	}
}

JD-Core v0.3.5 JD-GUI v0.6.2

List<String> list = Arrays.asList(new String[] { "abc", "bcd" });

public void testForEach() {
for (String ele : this.list)
	System.out.println(ele);
}

Jad v1.5.8e2.

public JavaDecompilerTest()
{
	list = Arrays.asList(new String[] {
		"abc", "bcd"
	});
}

public void testForEach()
{
	String ele;
	for(Iterator iterator = list.iterator(); iterator.hasNext(); System.out.println(ele))
		ele = (String)iterator.next();

}

List list;

# 双层for循环

源码

List<Map<String, String>> list = new ArrayList<Map<String, String>>();
{
	Map<String, String> map = new HashMap<String, String>();
	map.put("12", "23");
	list.add(map);
}

public void testForIndex() {
	for (int i = 0; i < list.size(); i++) {
		Map<String, String> map = list.get(i);
		for (String key : map.keySet())
			System.out.println(map.get(key));
	}
}

public void testForEach() {
	for (Map<String, String> map : list) {
		for (String key : map.keySet())
			System.out.println(map.get(key));
	}
}

JD-Core v0.3.5 JD-GUI v0.6.2

  List<Map<String, String>> list;

  public JavaDecompilerTest2()
  {
	this.list = new ArrayList();

	Map map = new HashMap();
	map.put("12", "23");
	this.list.add(map);
  }

  public void testForIndex() {
	for (int i = 0; i < this.list.size(); i++) {
	  Map map = (Map)this.list.get(i);
	  for (String key : map.keySet())
		System.out.println((String)map.get(key));
	}
  }

  public void testForEach()
  {
	Iterator localIterator2;
	for (Iterator localIterator1 = this.list.iterator(); localIterator1.hasNext(); 
	  localIterator2.hasNext())
	{
	  Map map = (Map)localIterator1.next();
	  localIterator2 = map.keySet().iterator(); continue; String key = (String)localIterator2.next();
	  System.out.println((String)map.get(key));
	}
  }

Jad v1.5.8e2.

public JavaDecompilerTest2()
{
	list = new ArrayList();
	Map map = new HashMap();
	map.put("12", "23");
	list.add(map);
}

public void testForIndex()
{
	for(int i = 0; i < list.size(); i++)
	{
		Map map = (Map)list.get(i);
		String key;
		for(Iterator iterator = map.keySet().iterator(); iterator.hasNext(); System.out.println((String)map.get(key)))
			key = (String)iterator.next();

	}

}

public void testForEach()
{
	for(Iterator iterator = list.iterator(); iterator.hasNext();)
	{
		Map map = (Map)iterator.next();
		String key;
		for(Iterator iterator1 = map.keySet().iterator(); iterator1.hasNext(); System.out.println((String)map.get(key)))
			key = (String)iterator1.next();

	}

}

List list;

# 内部类

源码

public class JavaDecompilerTest3 {

	private class Test2 {}
	
	Test2 test = new Test2();
	
}

JD-Core v0.3.5 JD-GUI v0.6.2

public class JavaDecompilerTest3
{
  JavaDecompilerTest3.Test2 test = new JavaDecompilerTest3.Test2(null);

  private class Test2
  {
	private Test2()
	{
	}
  }
}

Jad v1.5.8e2.

public class JavaDecompilerTest3
{
	private class Test2
	{

		final JavaDecompilerTest3 this$0;

		private Test2()
		{
			this$0 = JavaDecompilerTest3.this;
			super();
		}

		Test2(Test2 test2)
		{
			this();
		}
	}

	public JavaDecompilerTest3()
	{
		test = new Test2(null);
	}

	Test2 test;
}

# 后记

工具javap可以查看class的方法签名,通过jd-gui和jad可以反编译得到源码,如果代码没有混淆的话,多半就能了解代码的功能了。

# 读jdk7的try-resource的字节码

先了解下字节码的规范:Compiling for the Java Virtual Machine

$ javap -public -c -v ByteCodeTest.class
Classfile /E:/test/target/test-classes/com/ByteCodeTest.class
  Last modified 2016-5-6; size 992 bytes
  MD5 checksum ee7912d638a98f9a4a61d726e08c08f0
  Compiled from "ByteCodeTest.java"
public class com.ByteCodeTest
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Class              #2             // com/ByteCodeTest
   #2 = Utf8               com/ByteCodeTest
   #3 = Class              #4             // java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Methodref          #3.#9          // java/lang/Object."<init>":()V
   #9 = NameAndType        #5:#6          // "<init>":()V
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/ByteCodeTest;
  #14 = Utf8               tryResourceWithFinally
  #15 = Utf8               Exceptions
  #16 = Class              #17            // java/io/FileNotFoundException
  #17 = Utf8               java/io/FileNotFoundException
  #18 = Class              #19            // java/io/IOException
  #19 = Utf8               java/io/IOException
  #20 = Utf8               RuntimeVisibleAnnotations
  #21 = Utf8               Lorg/junit/Test;
  #22 = Class              #23            // java/io/FileInputStream
  #23 = Utf8               java/io/FileInputStream
  #24 = String             #25            //
  #25 = Utf8
  #26 = Methodref          #22.#27        // java/io/FileInputStream."<init>":(Ljava/lang/String;)V
  #27 = NameAndType        #5:#28         // "<init>":(Ljava/lang/String;)V
  #28 = Utf8               (Ljava/lang/String;)V
  #29 = Fieldref           #30.#32        // java/lang/System.out:Ljava/io/PrintStream;
  #30 = Class              #31            // java/lang/System
  #31 = Utf8               java/lang/System
  #32 = NameAndType        #33:#34        // out:Ljava/io/PrintStream;
  #33 = Utf8               out
  #34 = Utf8               Ljava/io/PrintStream;
  #35 = String             #36            // ing
  #36 = Utf8               ing
  #37 = Methodref          #38.#40        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #38 = Class              #39            // java/io/PrintStream
  #39 = Utf8               java/io/PrintStream
  #40 = NameAndType        #41:#28        // println:(Ljava/lang/String;)V
  #41 = Utf8               println
  #42 = Methodref          #43.#45        // java/io/InputStream.close:()V
  #43 = Class              #44            // java/io/InputStream
  #44 = Utf8               java/io/InputStream
  #45 = NameAndType        #46:#6         // close:()V
  #46 = Utf8               close
  #47 = Methodref          #48.#50        // java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
  #48 = Class              #49            // java/lang/Throwable
  #49 = Utf8               java/lang/Throwable
  #50 = NameAndType        #51:#52        // addSuppressed:(Ljava/lang/Throwable;)V
  #51 = Utf8               addSuppressed
  #52 = Utf8               (Ljava/lang/Throwable;)V
  #53 = Utf8               in
  #54 = Utf8               Ljava/io/InputStream;
  #55 = Utf8               StackMapTable
  #56 = Utf8               SourceFile
  #57 = Utf8               ByteCodeTest.java
{
  public com.ByteCodeTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #8                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 10: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/ByteCodeTest;

  public void tryResourceWithFinally() throws java.io.FileNotFoundException, java.io.IOException;
    descriptor: ()V
    flags: ACC_PUBLIC
    Exceptions:
      throws java.io.FileNotFoundException, java.io.IOException
    RuntimeVisibleAnnotations:
      0: #21()
    Code:
      stack=3, locals=4, args_size=1
         0: aconst_null
         1: astore_1
         2: aconst_null
         3: astore_2
         4: new           #22                 // class java/io/FileInputStream
         7: dup
         8: ldc           #24                 // String
        10: invokespecial #26                 // Method java/io/FileInputStream."<init>":(Ljava/lang/String;)V
        13: astore_3
        14: getstatic     #29                 // Field java/lang/System.out:Ljava/io/PrintStream;
        17: ldc           #35                 // String ing
        19: invokevirtual #37                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        22: aload_3
        23: ifnull        66
        26: aload_3
        27: invokevirtual #42                 // Method java/io/InputStream.close:()V
        30: goto          66
        33: astore_1
        34: aload_3
        35: ifnull        42
        38: aload_3
        39: invokevirtual #42                 // Method java/io/InputStream.close:()V
        42: aload_1
        43: athrow
        44: astore_2
        45: aload_1
        46: ifnonnull     54
        49: aload_2
        50: astore_1
        51: goto          64
        54: aload_1
        55: aload_2
        56: if_acmpeq     64
        59: aload_1
        60: aload_2
        61: invokevirtual #47                 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
        64: aload_1
        65: athrow
        66: return
      Exception table:
         from    to  target type
            14    22    33   any
             4    44    44   any
      LineNumberTable:
        line 14: 0
        line 15: 14
        line 16: 22
        line 17: 66
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      67     0  this   Lcom/ByteCodeTest;
           14      28     3    in   Ljava/io/InputStream;
      StackMapTable: number_of_entries = 6
        frame_type = 255 /* full_frame */
          offset_delta = 33
          locals = [ class com/ByteCodeTest, class java/lang/Throwable, class java/lang/Throwable, class java/io/InputStream ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 8
        frame_type = 65 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]
        frame_type = 9 /* same */
        frame_type = 9 /* same */
        frame_type = 249 /* chop */
          offset_delta = 1
}
SourceFile: "ByteCodeTest.java"

  public void tryResourceWithFinally() throws FileNotFoundException, IOException {
    try (InputStream in = new FileInputStream("")) {
      System.out.println("ing");
    }
  }

异常eclipse提示啥直接抛出来,不要 throws Exception ,不然前面的 astore_1/astore_2 你就看不懂了!!!

解析:要看懂try-catch-finally,必须关注 Exception table

any a1 = null
any a2 = null
any a3/in = new FileInputStream("")

System.out.println("ing")

	catch-1(33):
	// noop
	if(a3/in != null){
	in.close()
	}
	rethrow a1

if(a3/in != null){
in.close()
}

catch-2(44):
// noop
if(a3/in != null){
in.close()
}
if(a1 == null){
  // noop
  a1 = a2
  rethrow a1
} 

if (a1 == a2){
  rethrow a1
} 

a1.addSuppressed(a2)
rethrow a1

在添加自定义的finally:

$ javap -public -c ByteCodeTest.class
Compiled from "ByteCodeTest.java"
public class com.ByteCodeTest {
  public com.ByteCodeTest();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public void tryResourceWithFinally() throws java.io.FileNotFoundException, java.io.IOException;
    Code:
       0: aconst_null
       1: astore_1
       2: aconst_null
       3: astore_2
       4: new           #22                 // class java/io/FileInputStream
       7: dup
       8: ldc           #24                 // String
      10: invokespecial #26                 // Method java/io/FileInputStream."<init>":(Ljava/lang/String;)V
      13: astore_3
      14: getstatic     #29                 // Field java/lang/System.out:Ljava/io/PrintStream;
      17: ldc           #35                 // String ing
      19: invokevirtual #37                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      22: aload_3
      23: ifnull        79
      26: aload_3
      27: invokevirtual #42                 // Method java/io/InputStream.close:()V
      30: goto          79
      33: astore_1
      34: aload_3
      35: ifnull        42
      38: aload_3
      39: invokevirtual #42                 // Method java/io/InputStream.close:()V
      42: aload_1
      43: athrow
      44: astore_2
      45: aload_1
      46: ifnonnull     54
      49: aload_2
      50: astore_1
      51: goto          64
      54: aload_1
      55: aload_2
      56: if_acmpeq     64
      59: aload_1
      60: aload_2
      61: invokevirtual #47                 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
      64: aload_1
      65: athrow
      66: astore        4
      68: getstatic     #29                 // Field java/lang/System.out:Ljava/io/PrintStream;
      71: ldc           #53                 // String finish
      73: invokevirtual #37                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      76: aload         4
      78: athrow
      79: getstatic     #29                 // Field java/lang/System.out:Ljava/io/PrintStream;
      82: ldc           #53                 // String finish
      84: invokevirtual #37                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      87: return
    Exception table:
       from    to  target type
          14    22    33   any
           4    44    44   any
           0    66    66   any
}

  public void tryResourceWithFinally() throws FileNotFoundException, IOException {
    try (InputStream in = new FileInputStream("")) {
      System.out.println("ing");
    } finally {
      System.out.println("finish");
    }
  }

在异常表里面多了一条记录,比上面的复杂点。

–END

在 GitHub 上讨论

欢迎通过 GitHub Issue 留言或反馈。每条讨论都会关联到对应文章的源文件路径。

2013-10-24-java-decompiler-tools.md

Related posts