`
1025250620
  • 浏览: 230421 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Proguard源码分析(六)前文总结

 
阅读更多

目前,我们读了Proguard的代码,相信已经开始对访问者模式和装饰器模式有很深的理解,
现在我们再带着这两个模式认真的回顾下代码,因为只有这样,我们才能更好的进行下面的代码阅读。但是如果你还带着疑问,不妨看下前面的章节,或者看一些有关设计模式的书体会一下。
我们回到我们熟悉的入口Proguard类的execute方法中:
第一部分:读取(readinput)InputReader.execute:
 ClassFilter filter =  new ClassFilter(
new ClassReader(false,configuration.skipNonPublicLibraryClasses, //false
        configuration.skipNonPublicLibraryClassMembers, //true
        warningPrinter,
        new ClassPresenceFilter(
        programClassPool,
        duplicateClassPrinter,
        new ClassPoolFiller(programClassPool)))
                );
readInput("Reading program ",
                  configuration.programJars,
                  filter);
ClassFilter 我们可以用一个简单的类调用关系来表示:
filter = ClassFilter->ClassReader->ClassPresenceFilter->ClassPoolFiller
这是设计模式中的装饰器.
ClassFilter 继承于 FilteredDataEntryReader 并直接使用它的方法,只不过指定了它的匹配模式:
针对.class 文件.
ClassFilter 的作用是如果是.class文件的话就执行 ClassReader 操作,但是我们知道,文件读入的时候未必是class文件,也有可能是jar或者war或者文件夹.
那么它是怎么做的呢?这其实牵扯到它又是如何读入的呢?
Proguard中读入的过程调用
private void readInput(String          messagePrefix,
                           ClassPathEntry  classPathEntry,
                           DataEntryReader dataEntryReader)
其中dataEntryReader 参数就是上面的Filter.前文我们说过,对于不同的输入源,会采用不同的reader来读取,我们读入的既然是个jar,我们就会生成一个
JarReader来读取这个源,来看下jarreader是如何处理读取的:
 public void read(DataEntry dataEntry) throws IOException
    {
        ZipInputStream zipInputStream = new ZipInputStream(dataEntry.getInputStream());

        try
        {
            // Get all entries from the input jar.
            while (true)
            {
                // Can we get another entry?
                ZipEntry zipEntry = zipInputStream.getNextEntry();
                if (zipEntry == null)
                {
                    break;
                }

                // Delegate the actual reading to the data entry reader.
                dataEntryReader.read(new ZipDataEntry(dataEntry,
                                                      zipEntry,
                                                      zipInputStream));
            }
        }
        finally
        {
            dataEntry.closeInputStream();
        }
    }

ZipEntry就是以class为后缀的字节码文件,那么我们回到刚才,上面描述的这些通过ClassFilter的验证。那么就到了装饰器的第二层,
ClassReader.
ClassReader的目的就是为了读取,我们可以从它的构造器中看出:它是最基础的DataEntryReader,也就是说不包装任何的DataEntryReader。
在它的read方法中,它读入数据,专程不同的Clazz内部结构,通过不同的数据访问操作来访问它,当然不以装饰的方法来访问它,而是通过类似代理的结构来访问数据。
ClassReader 的最后一参数是一个classVisitor 我们跟到上面的实现类是一个装饰器:ClassPresenceFilter->ClassPoolFiller
我们说过ClassPresenceFilter的目的是为了去除重复.从
new ClassPresenceFilter(
        programClassPool,
        duplicateClassPrinter,
        new ClassPoolFiller(programClassPool))
可以看出ClassPresenceFilter 的目的是为了去除programClassPool 中的重复数据,如果有重复的class将通过duplicateClassPrinter 打印出来
。假如我们现在没有重复的class ,通过ClassPresenceFilter 的过滤,它传递给了ClassPoolFiller,它将这个字节码加入到代码池中。
好了,到这里我们的读入目的已经达到~接下來我们进入下一步:初始化;
===============================================================
===============================================================
===============================================================
第二部分:初始化 (initialize) Initializer.execute
初始化的过程第一步先会调用:
programClassPool.classesAccept(
            new ClassSuperHierarchyInitializer(programClassPool,
                                               libraryClassPool,
                                               classReferenceWarningPrinter,
                                               null));
ClassSuperHierarchyInitializer 类很纯粹,没有包装任何东西,所以看起来应该不费劲。
我们来看一下:
 public void visitProgramClass(ProgramClass programClass)
    {
        // Link to the super class.
        programClass.superClassConstantAccept(this);

        // Link to the interfaces.
        programClass.interfaceConstantsAccept(this);
    }
它实际上是关于常量的访问.
public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
    {
        classConstant.referencedClass =
            findClass(clazz.getName(), classConstant.getName(clazz));
    }
目的很明显,是为了给常量设置它的引用类。
private Clazz findClass(String referencingClassName, String name)
    {
        // First look for the class in the program class pool.
        Clazz clazz = programClassPool.getClass(name);

        // Otherwise look for the class in the library class pool.
        if (clazz == null)
        {
            clazz = libraryClassPool.getClass(name);

            if (clazz == null &&
                missingWarningPrinter != null)
            {
                // We didn't find the superclass or interface. Print a warning.
                missingWarningPrinter.print(referencingClassName,
                                            name,
                                            "Warning: " +
                                            ClassUtil.externalClassName(referencingClassName) +
                                            ": can't find superclass or interface " +
                                            ClassUtil.externalClassName(name));
            }
        }
        else if (dependencyWarningPrinter != null)
        {
            // The superclass or interface was found in the program class pool.
            // Print a warning.
            dependencyWarningPrinter.print(referencingClassName,
                                           name,
                                           "Warning: library class " +
                                           ClassUtil.externalClassName(referencingClassName) +
                                           " extends or implements program class " +
                                           ClassUtil.externalClassName(name));
        }

        return clazz;
    }
我们看到,它会找到这个类并返回给你。当然有人可能会问,为什么常量的引用关系要在这里被赋予呢?为什么不再Clazz文件读入的时候就进行初始化呢?~这个说实话我也不得而知,
希望能在后面的代码中得到答案。
好了常量初始化完成之后调用
programClassPool.classesAccept(
            new ClassReferenceInitializer(programClassPool,
                                          libraryClassPool,
                                          classReferenceWarningPrinter,
                                          programMemberReferenceWarningPrinter,
                                          libraryMemberReferenceWarningPrinter,
                                          null));
这应该是成员的初始化,或者说引用类型的初始化:
public void visitProgramClass(ProgramClass programClass)
    {
        programClass.constantPoolEntriesAccept(this);
        programClass.fieldsAccept(this);
        programClass.methodsAccept(this);
        programClass.attributesAccept(this);
    }

这里我们只说一个访问方式programClass.attributesAccept(this);
就行那些算做Attribute呢?我推荐一个文章,http://1025250620.iteye.com/admin/blogs/1971213 先了解下class的文件结构。
class文件的属性包含有code属性和文件表述属性:
class文件自带Source属性用来标记文件名,code属性里面可以附带局部变量表,linenumber表,或者异常表。
===============================================================
===============================================================
===============================================================
第三部分:打印seed (printSeeds) SeedPrinter.write
在打印之前:
programClassPool.classesAccept(new ClassCleaner());
libraryClassPool.classesAccept(new ClassCleaner());
先清除在libraryClassPool 和 programClassPool 中的标记。
 SimpleClassPrinter printer = new SimpleClassPrinter(false, ps); 是一个简单的格式化输出流。
然后调用
programClassPool.classesAcceptAlphabetically(new MultiClassVisitor(
            new ClassVisitor[]
            {
                new KeptClassFilter(printer),
                new AllMemberVisitor(new KeptMemberFilter(printer))
            }));
MultiClassVisitor 是一个装饰,用来迭代平级的KeptClassFilter ,AllMemberVisitor
可以看出,最后打印的结构一定是先打印KeptClassFilter 也就是类,后打印成员 AllMemberVisitor
我们看一下 KeptClassFilter 的过滤条件
public void visitProgramClass(ProgramClass programClass)
{
        if (KeepMarker.isKept(programClass))
        {
            classVisitor.visitProgramClass(programClass);
        }
}
是通过类是否被标记作为判断条件。
AllMemberVisitor 是表示用所有的成员也就是属性和方法访问,过滤条件是:
KeptMemberFilter
也就是方法和属性是否被标记。好的,我们最本质的问题就回归到如何标记。

KeepMarker keepMarker = new KeepMarker();
        ClassPoolVisitor classPoolvisitor =
            ClassSpecificationVisitorFactory.createClassPoolVisitor(configuration.keep,
                                                                    keepMarker,
                                                                    keepMarker,
                                                                    true,
                                                                    true,
                                                                    true);
        // Mark the seeds.
        programClassPool.accept(classPoolvisitor);
        libraryClassPool.accept(classPoolvisitor);
如果你已经了解了大概的流程应该知道是通过classPoolvisitor 这个对象来标记的:
我们看一下ClassSpecificationVisitorFactory。createClassPoolVisitor 方法
public static ClassPoolVisitor createClassPoolVisitor(List          keepClassSpecifications,
                                                          ClassVisitor  classVisitor,
                                                          MemberVisitor memberVisitor,
                                                          boolean       shrinking,
                                                          boolean       optimizing,
                                                          boolean       obfuscating)
    {
        MultiClassPoolVisitor multiClassPoolVisitor = new MultiClassPoolVisitor();

        if (keepClassSpecifications != null)
        {
            for (int index = 0; index < keepClassSpecifications.size(); index++)
            {
                KeepClassSpecification keepClassSpecification =
                    (KeepClassSpecification)keepClassSpecifications.get(index);

                if ((shrinking   && !keepClassSpecification.allowShrinking)
                    ||(optimizing  && !keepClassSpecification.allowOptimization)
                    ||(obfuscating && !keepClassSpecification.allowObfuscation))
                {
                    ClassPoolVisitor classPoolVisitor = createClassPoolVisitor(keepClassSpecification,
                            classVisitor,
                            memberVisitor);
                    multiClassPoolVisitor.addClassPoolVisitor(classPoolVisitor);
                }
            }
        }

        return multiClassPoolVisitor;
    }
它会针对不同的keep条件来生成不同的 ClassPoolVisitor ,这里我们的
ClassVisitor  classVisitor,MemberVisitor memberVisitor
都是KeepMarker。
跟到最后我们跟到返回的ClassPoolVisitor的实现类是:
(ClassPoolVisitor)new NamedClassVisitor(composedClassVisitor, className) :
(ClassPoolVisitor)new AllClassVisitor(composedClassVisitor);
读过我的keep那个章节的应该知道如果你使用的是通用符号*,那么返回的就是AllClassVisitor ,否则就是 NamedClassVisitor
不论是那一种最后调用的都是:composedClassVisitor
它会将ClassVisitor  classVisitor, MemberVisitor memberVisitor 组合起来
接下来,如果你是通用符号的话,也就是说:
if (className != null &&
            (extendsAnnotationType != null ||
             extendsClassName      != null ||
             containsWildCards(className)))
{
            composedClassVisitor =
                new ClassNameFilter(className, composedClassVisitor);

            // We'll have to visit all classes now.
            className = null;
}
则会将composedClassVisitor 包装个className的过滤器.
最后返回AllClassVisitor 对象。
如果它有继承配置,那么将在composedClassVisitor的基础上在增加 ClassHierarchyTraveler 用来传递到继承的标记。
最后SeedPrinter通过标记来区分打印。
===============================================================
===============================================================
===============================================================
第四部分:压缩 (shrink) Shrinker.execute
压缩使用的是UsageMarker 这个访问者,它的目的也是为了做标记。
先是对ClassSpecificationVisitorFactory.createClassPoolVisitor 做完标记,
然后再对其引用的
 new InnerUsageMarker(usageMarker),
                new AnnotationUsageMarker(usageMarker),
                new SignatureUsageMarker(usageMarker),
                new LocalVariableTypeUsageMarker(usageMarker)
做标记。





























0
0
分享到:
评论

相关推荐

    proguard6.2.2版本,里面附上了中文的使用教程

    资源是proguard6.2.2版本,里面附上了中文的使用教程,一看就懂,非常简单,不懂的可以私信问我。

    proguard6.2.2.zip/proguard6.2.2.rar/proguard6.2.2/proguard

    `retrace`是ProGuard的一个配套工具,它的主要功能是反混淆堆栈跟踪,当应用在运行时出现错误时,可以通过`retrace`将混淆后的堆栈信息还原为混淆前的形式,便于调试。 `buildscripts`和`gradle`目录可能涉及到构建...

    proguard 、使用说明

    **ProGuard 使用详解** ProGuard 是一款强大的 Java 字节码混淆工具,由 Eric Lafortune 开发,主要用于优化、压缩、混淆和预校验 Java 类文件。在 Android 开发中,ProGuard 被广泛应用于保护应用源代码,避免逆向...

    proguard支持JDK 1.8

    **ProGuard支持JDK 1.8:深入理解与应用** 在Android开发中,为了提高应用程序的安全性和优化代码,我们通常会使用代码混淆工具。ProGuard是一款强大的Java字节码混淆、优化、压缩和预检查工具,它能够帮助开发者...

    解决proguard混淆报错-Proguard5.1

    解决方案:找到proguard源码中proguard\src\proguard\classfile\ClassConstants.java类,然后修改ATTR_StackMapTable的值,将原来的的StackMapTable改为dummy.然后重新ant打包proguard。资源已经处理(源码+proguard...

    proguard最新版本proguard6.0.13

    ProGuard是一款广泛使用的Java代码混淆、优化和压缩工具,它能有效地减小应用程序的体积,提高安全性,并且在Android开发中扮演着至关重要的角色。在介绍ProGuard 6.0.13这个最新版本之前,我们先来了解一下ProGuard...

    Eclipse+ProGuard配置

    Eclipse+ProGuard配置 Eclipse 是一个基于 Java 的集成开发环境(IDE),ProGuard 是一个 Java 类库和应用程序的保护和优化工具。通过结合使用 Eclipse 和 ProGuard,可以对 Java 应用程序进行混淆、压缩和优化,...

    proguard6.2.2.rar

    2. **优化**:ProGuard会分析字节码,去除无用的类、方法和字段,同时优化剩余的字节码,使其运行更加高效。这一步骤不仅可以减小应用的大小,还可以提高运行速度。 3. **压缩**:在混淆和优化之后,ProGuard会删除...

    proguard-7.2.2.tar.gz

    **ProGuard**是一款广泛使用的Java代码...总结,ProGuard是Java和Android开发中的重要工具,用于提高代码安全性和优化应用性能。了解并熟练使用ProGuard的配置和工作原理,能够帮助开发者更好地管理和保护自己的代码。

    ProGuard_java_proguard_

    **ProGuard:Java代码混淆与优化工具** ProGuard 是一个强大的免费开源工具,主要用于Java程序的优化、混淆、预校验以及资源收缩。这个工具在软件发布时尤其有用,可以提高代码的安全性和难以逆向工程的程度,同时...

    proguard7.3.0

    ProGuard 7.3.0 是一款强大的Java代码优化、混淆和压缩工具,适用于各种Java应用程序,包括Android应用。此版本是ProGuard的最新更新,旨在解决从GitHub下载速度缓慢的问题,确保用户能够快速便捷地获取和使用该工具...

    proguard 文档

    ProGuard 是一款强大的 Java 类文件压缩、优化和混淆工具,广泛应用于 Android 开发中,用于减小程序体积、提高运行效率和增强代码安全性。它能够删除未使用的类、字段、方法和属性,优化字节码,以及对类、方法和...

    proguard 6.2.2魔改版

    proguard 6.2.2的魔改版本用于混淆app、jar、class等,将所有a,b,c等等的字符改成不可见字符,从而提高安全性。里面包括源码和编译好的jar包。编译源码使用core/build.sh,编译好的jar包在lib目录下。android app...

    java 源码加密 混淆 proguard 配置文件

    java 源码加密 混淆,proguard 配置文件,很详细,经测试可以用

    ProGuard4.8使用proguard来保护我们的代码

    **ProGuard 4.8** 是一款强大...总结,ProGuard 4.8 是一个强大的工具,通过混淆、优化和压缩Java代码,提高了程序的安全性和性能。在Android开发中,合理地配置和使用ProGuard是保护应用免受逆向工程攻击的重要手段。

    ProGuard工具包,Java代码混淆

    1. **预处理**:ProGuard首先对输入的类文件进行分析,识别出类、方法和字段的依赖关系。 2. **优化**:在预处理之后,ProGuard对字节码进行优化,例如删除无用的代码、合并相似的类和方法等,以减小代码体积并提高...

    重新打包的Proguard

    找到proguard源码中proguard\src\proguard\classfile\ClassConstants.java类,然后修改ATTR_StackMapTable的值,将原来的的StackMapTable改为dummy. 然后重新ant打包proguard,使用新的proguard来混淆就不会出现上面...

    proguard6.4.rar

    总结起来,ProGuard 6.4是一个强大的工具,能够帮助开发者保护代码、优化性能并减小应用大小。通过熟练掌握其配置和使用,我们可以确保我们的Java或Android项目更加健壮和安全。在不断变化的开发环境中,保持对最新...

    newProguard

    找到proguard源码中proguard\src\proguard\classfile\ClassConstants.java类,然后修改ATTR_StackMapTable的值,将原来的的StackMapTable改为dummy. 然后重新ant打包proguard,使用新的proguard来混淆就不会出现...

Global site tag (gtag.js) - Google Analytics