Proguard源码分析

Proguard是开源项目,下载地址:https://sourceforge.net/projects/proguard/

解压后目录一览


截图.png

核心内容在core文件下,这是一个java项目,可直接导入eclipse中运行

程序入口在ProGuard.java的main方法中

/**
     * The main method for ProGuard.
     */
    public static void main(String[] args) {
        if (args.length == 0) {
            System.out.println(VERSION);
            System.out.println("Usage: java proguard.ProGuard [options ...]");
            System.exit(1);
        }

        // Create the default options.
        Configuration configuration = new Configuration();

        try {
            // Parse the options specified in the command line arguments.
                       // 以下是解析混淆配置到配置类中,args是配置内容
            // ConfigurationParser parser = new ConfigurationParser(args,
            //      System.getProperties());
                   //为了方便测试,改成直接读取配置文件方式,args[0]为配置文件本地路径
                   ConfigurationParser parser = new ConfigurationParser(new File(args[0]),
                    System.getProperties());
            try {
                parser.parse(configuration);
            } finally {
                parser.close();
            }

            // Execute ProGuard with these options.
            new ProGuard(configuration).execute();
        } catch (Exception ex) {
            if (configuration.verbose) {
                // Print a verbose stack trace.
                ex.printStackTrace();
            } else {
                // Print just the stack trace message.
                System.err.println("Error: " + ex.getMessage());
            }

            System.exit(1);
        }

        System.exit(0);
    }

从入口函数可知核心方法是execute,下面看下具体实现

/**
     * Performs all subsequent ProGuard operations.
     */
    public void execute() throws IOException {
        System.out.println(VERSION);
            //GPL许可协议检查
        GPL.check();

        if (configuration.printConfiguration != null) {
            printConfiguration();
        }
            //检查混淆配置是否正确
        new ConfigurationChecker(configuration).check();

        if (configuration.programJars != null
                && configuration.programJars.hasOutput()
                && new UpToDateChecker(configuration).check()) {
            return;
        }
            //指定是否应启用将类文件反向移植到另一个TargetClassVersion
            //(如果指定targetClassVersion=8,程序会对类加入8特性的变化,比如某些代码块改成lambda表达式)
        if (configuration.targetClassVersion != 0) {
            configuration.backport = true;
        }
            //读取input jar中所有class到类池中,重要入口,后续所有操作都要访问类池
        readInput();

        if (configuration.shrink || configuration.optimize
                || configuration.obfuscate || configuration.preverify) {
                    //从程序类中清除任何JSE预验证信息。
                    //实际是清除StackMapTable属性,在Java 6版本之后JVM在class文件中引入了栈图
                    //(StackMapTable)属性。作用是为了提高JVM在类型检查的验证过程的效率
                    //在字节码的Code属性中最多包含一个StackMapTable属性
            clearPreverification();
        }

        if (configuration.printSeeds != null || configuration.shrink
                || configuration.optimize || configuration.obfuscate
                || configuration.preverify || configuration.backport) {
                    //初始化所有类之间的交叉引用,执行一些基本检查,并收缩库类池。
                    //比如:初始化程序类的超类层次结构; 初始化程序类成员和属性的类引用;
            initialize();
        }

        if (configuration.obfuscate || configuration.optimize) {
                  //这个类访问者用优化的基元数组常量替换数组初始化指令。这些常数不受任何Java规范的支持,因此仅用于内部使用。
            introducePrimitiveArrayConstants();
        }

        if (configuration.backport) {
                  //将Java语言特性备份到指定的目标版本。
            backport();
        }

        if (configuration.addConfigurationDebugging) {
                  //添加配置日志代码,提供改进Proguard配置的建议。
            addConfigurationLogging();
        }

        if (configuration.printSeeds != null) {
                  //打印出在收缩和混淆步骤中用作种子的类和类成员
            printSeeds();
        }

        if (configuration.preverify || configuration.android) {
                  //执行子程序内联步骤
                  //在代码属性中内联本地子程序(JSR/RET)
                  //比如在字节码中finally中的子句就是一个子程序,让JVM跳转到子程序的操作码是jsr指令,
                  //JVM在子程序完成之后,调用ret指令,从子程序返回。
            inlineSubroutines();
        }

        if (configuration.shrink) {
                  //执行收缩步骤
            shrink();
        }

        if (configuration.optimize) {
            for (int optimizationPass = 0; optimizationPass < configuration.optimizationPasses; optimizationPass++) {
                           //执行优化步骤
                if (!optimize(optimizationPass + 1,
                        configuration.optimizationPasses)) {
                    // Stop optimizing if the code doesn't improve any further.
                    break;
                }

                // Shrink again, if we may.
                if (configuration.shrink) {
                    // Don't print any usage this time around.
                    configuration.printUsage = null;
                    configuration.whyAreYouKeeping = null;

                    shrink();
                }
            }
                    //在方法内联和类合并等优化之后,消除所有程序类的行号。
            linearizeLineNumbers();
        }

        if (configuration.obfuscate) {
                  //执行混淆处理步骤。
            obfuscate();
        }

        if (configuration.optimize || configuration.obfuscate) {
                  //将基元数组常量展开回传统的基元数组初始化代码。
            expandPrimitiveArrayConstants();
        }

        if (configuration.optimize) {
                  //修剪所有程序类的行号表属性
            trimLineNumbers();
        }

        if (configuration.targetClassVersion != 0) {
                  //设置程序类的目标版本
            target();
        }

        if (configuration.preverify) {
                  //执行预验证步骤。
            preverify();
        }

        if (configuration.shrink || configuration.optimize
                || configuration.obfuscate || configuration.preverify) {
                    //对所有程序类的元素排序。
            sortClassElements();
        }

        if (configuration.programJars.hasOutput()) {
                  //写入输出类文件。
            writeOutput();
        }

        if (configuration.dump != null) {
                  //打印出程序类的内容。
            dump();
        }
    }

可以看出整个Proguard过程,执行了非常多的步骤,每个步骤都需要对类进行访问操作。
以下是input jar文件读取过程,所有操作的前提是得到程序类池。


截图 (1).png

后续所有步骤的基础就是操作读取到的programClassPool和libraryClassPool,ClassPool类是一组类的表示,通过TreeMap存储,它们可以按名称枚举或检索,也可以通过类访客访问。
下图是关于混淆步骤中的一个访问UML类图,首先说明在Proguard的每一个子程序步骤中都涉及大量的访问类,ClassRenamer只是其中之一。


截图 (2).png

混淆的核心需求就是对class类做修改,从类图可以看出ProgramClass是我们的核心类,它就是Java类中数据的完整表示.。
public class ProgramClass implements Clazz
{
    private static final int[]           EMPTY_INTERFACES = new int[0];
    private static final ProgramField[]  EMPTY_FIELDS     = new ProgramField[0];
    private static final ProgramMethod[] EMPTY_METHODS    = new ProgramMethod[0];
    private static final Attribute[]     EMPTY_ATTRIBUTES = new Attribute[0];


    //  public int             u4magic;
    public int             u4version;
    public int             u2constantPoolCount;
    public Constant[]      constantPool;
    public int             u2accessFlags;
    public int             u2thisClass;
    public int             u2superClass;
    public int             u2interfacesCount;
    public int[]           u2interfaces;
    public int             u2fieldsCount;
    public ProgramField[]  fields;
    public int             u2methodsCount;
    public ProgramMethod[] methods;
    public int             u2attributesCount;
    public Attribute[]     attributes;

    /**
     * An extra field pointing to the subclasses of this class.
     * This field is filled out by the {@link ClassSubHierarchyInitializer}.
     */
    public Clazz[] subClasses;
    
    ....
    }

可以看到它的类字段就是一个Java Class文件结构字段,更多信息移步:Class文件结构解析

类型 描述 备注
u4 magic 魔数:0xCAFEBABE
u2 minor_version 小版本号
u2 major_version 主版本号
u2 constant_pool_count 常量池大小,从1开始
cp_info constant_pool[constant_pool_count - 1] 常量池信息
u2 access_flags 访问标志
u2 this_class 类索引
u2 super_class 父类索引
u2 interfaces_count 接口个数
u2 interfaces[interfaces_count] 接口类索引信息
u2 fields_count 字段数
field_info fields[fields_count] 字段表信息
u2 methods_count 方法数
method_info methods[methods_count] 方法表信息
u2 attributes_count 属性个数
attribute_info attributes[attributes_count] 属性表信息

再来看Proguard对于class文件的读写操作就会清晰明了
读取实现:

public void visitProgramClass(ProgramClass programClass)
    {
        // Read and check the magic number.
        int u4magic = dataInput.readInt();

        ClassUtil.checkMagicNumber(u4magic);

        // Read and check the version numbers.
        int u2minorVersion = dataInput.readUnsignedShort();
        int u2majorVersion = dataInput.readUnsignedShort();

        programClass.u4version = ClassUtil.internalClassVersion(u2majorVersion,
                                                                u2minorVersion);

        ClassUtil.checkVersionNumbers(programClass.u4version);

        // Read the constant pool. Note that the first entry is not used.
        programClass.u2constantPoolCount = dataInput.readUnsignedShort();

        programClass.constantPool = new Constant[programClass.u2constantPoolCount];
        for (int index = 1; index < programClass.u2constantPoolCount; index++)
        {
            Constant constant = createConstant();
            constant.accept(programClass, this);
            programClass.constantPool[index] = constant;

            // Long constants and double constants take up two entries in the
            // constant pool.
            int tag = constant.getTag();
            if (tag == ClassConstants.CONSTANT_Long ||
                tag == ClassConstants.CONSTANT_Double)
            {
                programClass.constantPool[++index] = null;
            }
        }

        // Read the general class information.
        programClass.u2accessFlags = dataInput.readUnsignedShort();
        programClass.u2thisClass   = dataInput.readUnsignedShort();
        programClass.u2superClass  = dataInput.readUnsignedShort();

        // Read the interfaces.
        programClass.u2interfacesCount = dataInput.readUnsignedShort();

        programClass.u2interfaces = new int[programClass.u2interfacesCount];
        for (int index = 0; index < programClass.u2interfacesCount; index++)
        {
            programClass.u2interfaces[index] = dataInput.readUnsignedShort();
        }

        // Read the fields.
        programClass.u2fieldsCount = dataInput.readUnsignedShort();

        programClass.fields = new ProgramField[programClass.u2fieldsCount];
        for (int index = 0; index < programClass.u2fieldsCount; index++)
        {
            ProgramField programField = new ProgramField();
            this.visitProgramField(programClass, programField);
            programClass.fields[index] = programField;
        }

        // Read the methods.
        programClass.u2methodsCount = dataInput.readUnsignedShort();

        programClass.methods = new ProgramMethod[programClass.u2methodsCount];
        for (int index = 0; index < programClass.u2methodsCount; index++)
        {
            ProgramMethod programMethod = new ProgramMethod();
            this.visitProgramMethod(programClass, programMethod);
            programClass.methods[index] = programMethod;
        }

        // Read the class attributes.
        programClass.u2attributesCount = dataInput.readUnsignedShort();

        programClass.attributes = new Attribute[programClass.u2attributesCount];
        for (int index = 0; index < programClass.u2attributesCount; index++)
        {
            Attribute attribute = createAttribute(programClass);
            attribute.accept(programClass, this);
            programClass.attributes[index] = attribute;
        }
    }

写入实现:

public void visitProgramClass(ProgramClass programClass)
    {
        // Write the magic number.
        dataOutput.writeInt(ClassConstants.MAGIC);

        // Write the version numbers.
        dataOutput.writeShort(ClassUtil.internalMinorClassVersion(programClass.u4version));
        dataOutput.writeShort(ClassUtil.internalMajorClassVersion(programClass.u4version));

        // Write the constant pool.
        dataOutput.writeUnsignedShort(programClass.u2constantPoolCount);

        programClass.constantPoolEntriesAccept(this);

        // Write the general class information.
        // Ignore the higher bits outside the short range - these are for
        // internal purposes only.
        dataOutput.writeUnsignedShort(programClass.u2accessFlags & 0xffff);
        dataOutput.writeUnsignedShort(programClass.u2thisClass);
        dataOutput.writeUnsignedShort(programClass.u2superClass);

        // Write the interfaces.
        dataOutput.writeUnsignedShort(programClass.u2interfacesCount);

        for (int index = 0; index < programClass.u2interfacesCount; index++)
        {
            dataOutput.writeUnsignedShort(programClass.u2interfaces[index]);
        }

        // Write the fields.
        dataOutput.writeUnsignedShort(programClass.u2fieldsCount);

        programClass.fieldsAccept(this);

        // Write the methods.
        dataOutput.writeUnsignedShort(programClass.u2methodsCount);

        programClass.methodsAccept(this);

        // Write the class attributes.
        dataOutput.writeUnsignedShort(programClass.u2attributesCount);

        programClass.attributesAccept(this);
    }

通过以上分析相信大家对Proguard的实现原理已经掌握了,如果还有疑问再来看下Proguard如何对class进行改名呢?

/**
 * 对类名进行修改
 */
 public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
    {
        // Update the Class entry if required.
        String newName = ClassObfuscator.newClassName(clazz);
        if (newName != null)
        {
            // Refer to a new Utf8 entry. 
            //类名存储在常量池中,对常量池添加一个常量,修改类名在常量池数组中的下标,指向到新类名
            classConstant.u2nameIndex =
                new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newName);
        }
   }
   
/**
 * 对方法名进行修改
 */
public void visitProgramMember(ProgramClass  programClass,
                                     ProgramMember programMember)
    {
        // Has the class member name changed?
        String name    = programMember.getName(programClass);
        String newName = MemberObfuscator.newMemberName(programMember);
        if (newName != null &&
            !newName.equals(name))
        {
            programMember.u2nameIndex =
                new ConstantPoolEditor(programClass).addUtf8Constant(newName);
        }
    }

清楚了class文件结构,对Proguard的实现就不难理解。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,084评论 1 32
  • 一、Python简介和环境搭建以及pip的安装 4课时实验课主要内容 【Python简介】: Python 是一个...
    _小老虎_阅读 5,719评论 0 10
  • 整理来自互联网 1,JDK:Java Development Kit,java的开发和运行环境,java的开发工具...
    Ncompass阅读 1,534评论 0 6
  • 一:java概述: 1,JDK:Java Development Kit,java的开发和运行环境,java的开发...
    慕容小伟阅读 1,763评论 0 10
  • 一:java概述:1,JDK:Java Development Kit,java的开发和运行环境,java的开发工...
    ZaneInTheSun阅读 2,629评论 0 11