自己动手系列--打造反编译小工具(1)

点击蓝色字体“咖啡的小站”选择“设为星标”

及时获取咖啡最新的技术分享

图片来自摄影师:十一 | 专注儿童油画风摄影师

今年年初,咖啡在闲暇之余,制作了一款反编译小工具:SJD-GUI。美其名为反编译工具,实际是结合网上的小工具打包的一款软件。正愁分享些什么内容的时候,突然想到了这款已经被我尘封了很久的软件。其实里面也没有多少技术含量,分享的是一些思路,更多的是欢迎大家一起来参与讨论。

◆ ◆ ◆  ◆ ◆

01

背景

来源于早期咖啡在一次维护客户项目的过程中,从服务器上拉取了一个字节码文件,想反编译查看源代码。在客户机器上没有安装IDEA的情况下,第一时间想到的就是反编译工具:JD-GUI,一个橙色的小咖啡图标,相信很多同学都用过。绿色无需安装,功能完整,界面友好,成了大多数程序员反编译的首选。

不过今日在例行操作后,发现这款软件罢工了?

自动部署会报编译错误,排除;工具损坏?远程传送给同事试了一下JD-GUI反编译,同样的报错,继续排除;同事继续用idea,却能正常反编译,看来问题还是出在了这款工具上。

02

诊断

回想了一下,JD-GUI官方貌似很久之前就已经停止维护了,会不会对JDK后续版本支持不好?查看官网,果然问题出在这里:

因为项目所用jdk版本是java8,再追溯到代码源头,没错!就是Lambda的反编译支持问题。相信看到这里,可能会被不少同行笑话,如果你已经踩过坑,那么请忽略咖啡本节诊断过程的废话。

03

解忧

在用idea排除了项目问题之后,咖啡开始思考,还存不存在一款如JD-GUI好用但支持完整的反编译工具?最直接的方式就是开始谷歌,咖啡花了10到20分钟搜寻之后,发现并没有一款支持如JD-GUI友好的软件。友好支持JDK全版本反编译,界面不敢恭维;又如使用过程中莫名崩溃类的软件不胜枚举。最后一款反编译工具包,引起了咖啡的注意,名为:Procyon-decompiler.jar。
  Procyon-Decompiler支持JDK1.8类的反编译,在很多方面做得非常不错:

  • 字符串的Switch
  • 枚举声明方面
  • 注解方面
  • 匿名类、内部类
  • Java8新接口规范
  • Java8 Lambda表达式
  • Java8 方法传递

  另外它在反编译后能输出多种类型的结果,如Java源码、原始字节码、字节码AST。那么这款软件如何使用?这也是遗憾的地方。
  进入CMD界面,输入命令:java –jar Procyon-decompiler.jar路径 .class文件路径。
  演示代码:

public class DemoTest {    

打开cmd界面,执行:

代码一多,查看起来是比较累眼睛的。鉴于轻微的强迫症,咱包装一下,写个小工具吧。

04

造车

不过用C写客户端程序我是不会的。咱玩java的, swing也是可以的。原理很简单,用swing图形界面结合后端cmd命令执行。后端程序,直接借助jdk基础类库rt下的Runtime类,即可输出cmd执行文本结果。

Runtime rtme = Runtime.getRuntime();Process pcRes = rtme.exec(cmdStr);

粗糙地画一个swing界面,这个怎么界面画就不贴出来了,不会的问度娘。

响应事件绑定后端cmd程序的执行即可。

@Overridepublic void actionPerformed(ActionEvent e) {    // 绑定到选择文件,选择文件事件    String filename = "";    BufferedReader br = null;    if (e.getSource().equals(button2)) {        // 设定只能选择到文件        jfc.setFileSelectionMode(0);        // 此句是打开文件选择器界面的触发语句        int state = jfc.showOpenDialog(null);        // 撤销则返回        if (state == 1) {            return;        } else {            // f为选择到的文件            File f = jfc.getSelectedFile();            filename = f.getName().replace(".class", ".java");            text2.setText(f.getAbsolutePath());            text2.setName(filename);            try {                long d1 = new Date().getTime();                Runtime rt = Runtime.getRuntime();                System.out.println(getClassPath());                String cmdStr = "cmd.exe /c java -jar "+getClassPath()+"\\procyon-decompiler-0.5.30.jar "+ text2.getText();                br = new BufferedReader(new InputStreamReader(rt.exec(cmdStr).getInputStream()));                String line=null;                StringBuffer b=new StringBuffer();                while ((line=br.readLine())!=null) {                    b.append(line+"\n");                    }                String javaCode = b.toString();                textField.setTextByColor(javaCode);            } catch (Exception e1) {                e1.printStackTrace();            } finally{                try {                    br.close();                } catch (IOException e1) {                }            }        }    }}

    全部代码暂时就不贴了,最后分享出来。

    执行效果:

看的不舒服,没关系我们再做一下关键字处理。列出所有关键字(可能不全,也是网上摘来的)。

/*** 所有关键字*/private final static String[] KEYS = new String[]{    "abstract","assert","boolean","break","byte","case","catch","char","class",    "const","continue","default","do","double","else","enum","extends",    "final","finally","float","for","goto","if","implements","import",    "instanceof","int","interface","long","native","new","package",    "private","protected","public","return","strictfp","short","static",    "super","switch","synchronized","this","throw","throws","transient",    "try","void","volatile","while" };
/** * 设置关键字颜色 * @param key * @param start * @param length * @return */private int setKeyColor(String key, int start, int length) {    for (int i = 0; i < KEYS.length; i++) {        int liIndex = key.indexOf(KEYS[i]);        if (liIndex < 0) {            continue;        }        int liLength = liIndex + KEYS[i].length();        if (liLength == key.length()) {            //处理单独一个关键字的情况,例如:if else 等            if (liIndex == 0) {                meDoc.setCharacterAttributes(start, KEYS[i].length(),                        keyAttr, false);            } else {//处理关键字前面还有字符的情况,例如:)if ;else 等                char chTemp = key.charAt(liIndex - 1);                if (isCharacter(chTemp)) {                    meDoc.setCharacterAttributes(start + liIndex,                            KEYS[i].length(), keyAttr, false);                }            }        } else {            //处理关键字后面还有字符的情况,例如:if(  end;等            if (liIndex == 0) {                char chTemp = key.charAt(KEYS[i].length());                if (isCharacter(chTemp)) {                    meDoc.setCharacterAttributes(start, KEYS[i].length(),                            keyAttr, false);                }            } else {//处理关键字前面和后面都有字符的情况,例如:)if( 等                char chTemp = key.charAt(liIndex - 1);                char chTemp2 = key.charAt(liLength);                if (isCharacter(chTemp) && isCharacter(chTemp2)) {                    meDoc.setCharacterAttributes(start + liIndex,                            KEYS[i].length(), keyAttr, false);                }            }        }    }    return length + 1;}

最终效果如下:

看着舒服多了,虽然也只是个粗糙的包装工具,实用性也没有那么地强,不过解个燃眉之急还是阔以的。

05

尾声

感兴趣的同学可以自己动手试一下效果,下一节我们将介绍如何把当前的程序打包成一个随身带、随处执行的软件。如有疑问,可添加咖啡的私人微信号:shellery1988。

原创声明:原创文章,如需转载,请添加咖啡私人微信:shellery1988

备注:原创转载。咖啡看到了都会及时处理,感谢关注和支持!


Original url: Access
Created at: 2019-09-10 21:50:45
Category: default
Tags: none

请先后发表评论
  • 最新评论
  • 总共0条评论