美文网首页
Proguard源码分析

Proguard源码分析

作者: StudyForLong | 来源:发表于2019-09-30 07:37 被阅读0次

    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的实现就不难理解。

    相关文章

      网友评论

          本文标题:Proguard源码分析

          本文链接:https://www.haomeiwen.com/subject/dvrdyctx.html