美文网首页
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