编程语言

在介绍编译和反编译之前,我们先来简单介绍下编程语言(Programming Language)。编程语言(Programming Language)分为低级语言(Low-level Language)和高级语言(High-level Language)。

机器语言(Machine Language)和汇编语言(Assembly Language)属于低级语言,直接用计算机指令编写程序。

而C、C++、Java、Python等属于高级语言,用语句(Statement)编写程序,语句是计算机指令的抽象表示。

举个例子,同样一个语句用C语言、汇编语言和机器语言分别表示如下:

计算机只能对数字做运算,符号、声音、图像在计算机内部都要用数字表示,指令也不例外,上表中的机器语言完全由十六进制数字组成。最早的程序员都是直接用机器语言编程,但是很麻烦,需要查大量的表格来确定每个数字表示什么意思,编写出来的程序很不直观,而且容易出错,于是有了汇编语言,把机器语言中一组一组的数字用助记符(Mnemonic)表示,直接用这些助记符写出汇编程序,然后让汇编器(Assembler)去查表把助记符替换成数字,也就把汇编语言翻译成了机器语言。

但是,汇编语言用起来同样比较复杂,后面,就衍生出了Java、C、C++等高级语言。

什么是编译

上面提到语言有两种,一种低级语言,一种高级语言。可以这样简单的理解:低级语言是计算机认识的语言、高级语言是程序员认识的语言。

那么如何从高级语言转换成低级语言呢?这个过程其实就是编译。

从上面的例子还可以看出,C语言的语句和低级语言的指令之间不是简单的一一对应关系,一条a=b+1;语句要翻译成三条汇编或机器指令,这个过程称为编译(Compile),由编译器(Compiler)来完成,显然编译器的功能比汇编器要复杂得多。用C语言编写的程序必须经过编译转成机器指令才能被计算机执行,编译需要花一些时间,这是用高级语言编程的一个缺点,然而更多的是优点。首先,用C语言编程更容易,写出来的代码更紧凑,可读性更强,出了错也更容易改正。

将便于人编写、阅读、维护的高级计算机语言所写作的源代码程序,翻译为计算机能解读、运行的低阶机器语言的程序的过程就是编译。负责这一过程的处理的工具叫做编译器

现在我们知道了什么是编译,也知道了什么是编译器。不同的语言都有自己的编译器,Java语言中负责编译的编译器是一个命令:javac

javac是收录于JDK中的Java语言编译器。该工具可以将后缀名为.java的源文件编译为后缀名为.class的可以运行于Java虚拟机的字节码。

当我们写完一个HelloWorld.java文件后,我们可以使用javac HelloWorld.java命令来生成HelloWorld.class文件,这个class类型的文件是JVM可以识别的文件。通常我们认为这个过程叫做Java语言的编译。其实,class文件仍然不是机器能够识别的语言,因为机器只能识别机器语言,还需要JVM再将这种class文件类型字节码转换成机器可以识别的机器语言。

什么是反编译

反编译的过程与编译刚好相反,就是将已编译好的编程语言还原到未编译的状态,也就是找出程序语言的源代码。就是将机器看得懂的语言转换成程序员可以看得懂的语言。Java语言中的反编译一般指将class文件转换成java文件。

有了反编译工具,我们可以做很多事情,最主要的功能就是有了反编译工具,我们就能读得懂Java编译器生成的字节码。如果你想问读懂字节码有啥用,那么我可以很负责任的告诉你,好处大大的。比如我的博文几篇典型的原理性文章,都是通过反编译工具得到反编译后的代码分析得到的。如深入理解多线程(一)——Synchronized的实现原理、深度分析Java的枚举类型—-枚举的线程安全性及序列化问题、Java中的Switch对整型、字符型、字符串型的具体实现细节、Java的类型擦除等。我最近在GitChat写了一篇关于Java语法糖的文章,其中大部分内容都用到反编译工具来洞悉语法糖背后的原理。

Java反编译工具

本文主要介绍3个Java的反编译工具:javap、jad和cfr

javap

javap是jdk自带的一个工具,可以对代码反编译,也可以查看java编译器生成的字节码。javap和其他两个反编译工具最大的区别是他生成的文件并不是java文件,也不像其他两个工具生成代码那样更容易理解。拿一段简单的代码举例,如我们想分析Java 7中的switch是如何支持String的,我们先有以下可以编译通过的源代码:

public class switchDemoString {
    public static void main(String[] args) {
        String str = "world";
        switch (str) {
            case "hello":
                System.out.println("hello");
                break;
            case "world":
                System.out.println("world");
                break;
            default:
                break;
        }
    }
}

执行以下两个命令:

javac switchDemoString.java
javap -c switchDemoString.class

生成代码如下:

public class com.hollis.suguar.switchDemoString {
  public com.hollis.suguar.switchDemoString();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String world
       2: astore_1
       3: aload_1
       4: astore_2
       5: iconst_m1
       6: istore_3
       7: aload_2
       8: invokevirtual #3                  // Method java/lang/String.hashCode:()I
      11: lookupswitch  {
              99162322: 36
             113318802: 50
               default: 61
          }
      36: aload_2
      37: ldc           #4                  // String hello
      39: invokevirtual #5                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      42: ifeq          61
      45: iconst_0
      46: istore_3
      47: goto          61
      50: aload_2
      51: ldc           #2                  // String world
      53: invokevirtual #5                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      56: ifeq          61
      59: iconst_1
      60: istore_3
      61: iload_3
      62: lookupswitch  {
                     0: 88
                     1: 99
               default: 110
          }
      88: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      91: ldc           #4                  // String hello
      93: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      96: goto          110
      99: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
     102: ldc           #2                  // String world
     104: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     107: goto          110
     110: return
}

我个人的理解,javap并没有将字节码反编译成java文件,而是生成了一种我们可以看得懂字节码。其实javap生成的文件仍然是字节码,只是程序员可以稍微看得懂一些。如果你对字节码有所掌握,还是可以看得懂以上的代码的。其实就是把String转成hashcode,然后进行比较。

个人认为,一般情况下我们会用到javap命令的时候不多,一般只有在真的需要看字节码的时候才会用到。但是字节码中间暴露的东西是最全的,你肯定有机会用到,比如我在分析synchronized的原理的时候就有是用到javap。通过javap生成的字节码,我发现synchronized底层依赖了ACC_SYNCHRONIZED标记和monitorentermonitorexit两个指令来实现同步。

jad

jad是一个比较不错的反编译工具,只要下载一个执行工具,就可以实现对class文件的反编译了。还是上面的源代码,使用jad反编译后内容如下:

命令:jad switchDemoString.class

public class switchDemoString
{
    public switchDemoString()
    {
    }
    public static void main(String args[])
    {
        String str = "world";
        String s;
        switch((s = str).hashCode())
        {
        default:
            break;
        case 99162322:
            if(s.equals("hello"))
                System.out.println("hello");
            break;
        case 113318802:
            if(s.equals("world"))
                System.out.println("world");
            break;
        }
    }
}

看,这个代码你肯定看的懂,因为这不就是标准的java的源代码么。这个就很清楚的可以看到原来字符串的switch是通过equals()hashCode()方法来实现的。

但是,jad已经很久不更新了,在对Java7生成的字节码进行反编译时,偶尔会出现不支持的问题,在对Java 8的lambda表达式反编译时就彻底失败。

CFR

jad很好用,但是无奈的是很久没更新了,所以只能用一款新的工具替代他,CFR是一个不错的选择,相比jad来说,他的语法可能会稍微复杂一些,但是好在他可以work。

如,我们使用cfr对刚刚的代码进行反编译。执行一下命令:

java -jar cfr_0_125.jar switchDemoString.class --decodestringswitch false

得到以下代码:

public class switchDemoString {
    public static void main(String[] arrstring) {
        String string;
        String string2 = string = "world";
        int n = -1;
        switch (string2.hashCode()) {
            case 99162322: {
                if (!string2.equals("hello")) break;
                n = 0;
                break;
            }
            case 113318802: {
                if (!string2.equals("world")) break;
                n = 1;
            }
        }
        switch (n) {
            case 0: {
                System.out.println("hello");
                break;
            }
            case 1: {
                System.out.println("world");
                break;
            }
        }
    }
}

通过这段代码也能得到字符串的switch是通过equals()hashCode()方法来实现的结论。

相比Jad来说,CFR有很多参数,还是刚刚的代码,如果我们使用以下命令,输出结果就会不同:

java -jar cfr_0_125.jar switchDemoString.class

public class switchDemoString {
    public static void main(String[] arrstring) {
        String string;
        switch (string = "world") {
            case "hello": {
                System.out.println("hello");
                break;
            }
            case "world": {
                System.out.println("world");
                break;
            }
        }
    }
}

所以--decodestringswitch表示对于switch支持string的细节进行解码。类似的还有--decodeenumswitch--decodefinally--decodelambdas等。在我的关于语法糖的文章中,我使用--decodelambdas对lambda表达式警进行了反编译。 源码:

public static void main(String... args) {
    List<String> strList = ImmutableList.of("Hollis", "公众号:Hollis", "博客:www.hollischuang.com");

    strList.forEach( s -> { System.out.println(s); } );
}

java -jar cfr_0_125.jar lambdaDemo.class --decodelambdas false反编译后代码:

public static /* varargs */ void main(String ... args) {
    ImmutableList strList = ImmutableList.of((Object)"Hollis", (Object)"\u516c\u4f17\u53f7\uff1aHollis", (Object)"\u535a\u5ba2\uff1awww.hollischuang.com");
    strList.forEach((Consumer<String>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda$main$0(java.lang.String ), (Ljava/lang/String;)V)());
}

private static /* synthetic */ void lambda$main$0(String s) {
    System.out.println(s);
}

CFR还有很多其他参数,均用于不同场景,读者可以使用java -jar cfr_0_125.jar --help进行了解。这里不逐一介绍了。

如何防止反编译

由于我们有工具可以对Class文件进行反编译,所以,对开发人员来说,如何保护Java程序就变成了一个非常重要的挑战。但是,魔高一尺、道高一丈。当然有对应的技术可以应对反编译咯。但是,这里还是要说明一点,和网络安全的防护一样,无论做出多少努力,其实都只是提高攻击者的成本而已。无法彻底防治。

典型的应对策略有以下几种:

  • 隔离Java程序

    • 让用户接触不到你的Class文件
  • 对Class文件进行加密
    • 提到破解难度
  • 代码混淆
    • 将代码转换成功能上等价,但是难于阅读和理解的形式

Java 编译与反编译的更多相关文章

  1. Java 7 中的Switch 谈 Java版本更新和反编译知识

    Java 7 中的Switch 谈 Java版本更新和反编译知识          学习编程,享受生活,大家好,我是追寻梦的飞飞.今天主要讲述的是Java7中的更新Switch实现内部原理和JAD反编 ...

  2. Java代码的编译与反编译那些事儿

    原文:Java代码的编译与反编译那些事儿 编程语言 在介绍编译和反编译之前,我们先来简单介绍下编程语言(Programming Language).编程语言(Programming Language) ...

  3. Java| 编译和反编译

    原文链接: http://www.yveshe.com/articles/2018/05/01/1525172129089.html 什么是编程语言? 在介绍编译和反编译之前,我们先来简单介绍下编程语 ...

  4. 什么是Java代码的编译与反编译?(转)

    转自:http://java.tedu.cn/ask/203119.html Java代码的编译与反编译 一.什么是编译 1.利用编译程序从源语言编写的源程序产生目标程序的过程. 2.用编译程序产生目 ...

  5. 【技术累积】【点】【java】【3】编译和反编译

    闲聊 擦,打脸了,但打完了还是得继续写呗,水着水着看呗. 概述 理解的不深入,但是实用. 总而言之:编译,是将书写的代码翻译为机器能够理解的代码:反编译,则是相反的过程. 来源在于语言的等级,一般认为 ...

  6. java如何防止反编译(转)

    出处: java如何防止反编译 一些防止java代码被反编译的方法 综述(写在前面的废话) Java从诞生以来,其基因就是开放精神,也正因此,其可以得到广泛爱好者的支持和奉献,最终很快发展壮大,以至于 ...

  7. 【Android 应用开发】 Android APK 反编译 混淆 反编译后重编译

    反编译工具 : 总结了一下 linux, windows, mac 上的版本, 一起放到 CSDN 上下载; -- CSDN 下载地址 : http://download.csdn.net/detai ...

  8. 安卓apk的编译与反编译

    原文:https://blog.csdn.net/baidu_33870664/article/details/80186945 android基于java的,而java反编译工具很强悍,所以对正常a ...

  9. 用apktool工具进行apk的编译和反编译

    1.apktool下载安装 给一个2.0版的csdn地址:http://download.csdn.net/download/txj8612/7408775 下载后无需安装,直接解压缩,得到三个文件: ...

随机推荐

  1. 集合:Collection

    why ? when ? how ? what ? Java 集合框架图 由上图我们可以看到,Java 集合主要分为两类:Collection 和 Map. Collection 接口 遍历 Coll ...

  2. ORM优化

    orm优化数据库访问:https://docs.djangoproject.com/en/1.11/topics/db/optimization/ 一.QuerySet 可迭代 querysey=mo ...

  3. 洛谷 3178 [HAOI2015]树上操作

    [题解] 就是个树链剖分的模板题. #include<cstdio> #include<algorithm> #include<cstring> #define L ...

  4. [K/3Cloud]如何解决kdpkg无法部署到业务站点的问题

    自从下载了sp1后,就迫不急待的试用下,看看反馈的几个关键bug是否修复,可惜sp1安装后发现业务站点下的组件一个都没有被更新,这指定是有问题了,这真是让哥百思不得其解,真后悔在研发时没仔细研究下部署 ...

  5. 【BZOJ2342】双倍回文(manacher,并查集)

    题意: 思路:From http://blog.sina.com.cn/s/blog_8d5d2f04010196bh.html 首先我可以看出: (1)我们找到的串的本身也是一个回文串(显然) (2 ...

  6. java虚拟机(一)-java内存区域与内存溢出异常

    1.简述:java虚拟机在执行java程序的过程中,会把他所管理的内存分为以下几个区域, 1.1.程序计数器 1.2.虚拟机栈 1.3.本地方法栈 1.4.java堆 1.5.方法区 如图所示: 2. ...

  7. PostGIS学习相关术语

    POI 兴趣点(英语:point of interest,通常缩写成POI)乃是电子地图上的某个地标.景点,用以标示出该地所代表的政府部门.各行各业之商业机构(加油站.百货公司.超市.餐厅.酒店.便利 ...

  8. 用xtrabackup+binlog恢复误删除的数据库

    关键技术,数据库产生的二进制文件,在主库就是binlog在从库就是relay-log,用最新的物理备份可以新启动个新实例,可以模拟个从库,把主库的binlog复制到新的数据库实例上,利用主从复制和物理 ...

  9. Windows 10+Ubuntu 16.04双系统切换后时间不同步的问题解决

    主要时时区不对造成的,少了8个小时,解决方法: 在Ubuntu的终端下输入以下命令: sudo timedatectl set-local-rtc 1 参考: http://blog.csdn.net ...

  10. 交换机是干嘛的!!交换机如何学习MAC地址过程?

    1.它收到一个帧的时候,先检查源MAC地址,看看自己维护的一个地址表中有没有这个地址.如果有,则2:如果没有,则将这个MAC地址.进入的端口.进入的时间放入这个表中: 2.检查目的MAC地址,然后到该 ...