`
1025250620
  • 浏览: 225631 次
  • 性别: 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版本,里面附上了中文的使用教程,一看就懂,非常简单,不懂的可以私信问我。

    proguard 文档

    proguard 中文文档 描述了proguard.cfg中标签的用法,本人找了好久才找到的文档

    解决proguard混淆报错-Proguard5.1

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

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

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

    proguard6.2.2.rar

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

    proguard最新版本proguard6.0.13

    proguard6.0.13最新版本的资源。proguard6.0.13最新版本的资源。

    proguard 6.2.2魔改版

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

    重新打包的Proguard

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

    newProguard

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

    proguard-proguard6.2.2.zip

    1.支持ant 使用proguard标签 2.支持windows环境混淆代码 3.支持Java8+ 新版本6x系列不再提供编译好的jar包 需要自己手动编译,7系列使用的gradle编译管理 国内不太好用,6x系列基本满足Java8+ 的代码混淆,具体可以...

    Eclipse+ProGuard配置

    Eclipse+ProGuard配置 Eclipse+ProGuard配置

    ProGuard_java_proguard_

    This ProGuard used to obfuscate Java source code.

    proguard6.4.rar

    proguard6.4.rar

    Proguard5.2.1 资源 使用教程

    java代码混洗工具 proGuard 最新Proguard5.2.1混淆器,以及ProGuard使用教程

    proguard混淆打包工具

    proguard混淆打包工具proguard混淆打包工具proguard混淆打包工具

    proguard6.2.2.zip

    proguard6.2.2(201912月8日版本)最新版,解决java版本太高无法匹配的问题,解压后替换AndriodSDK\sdk\tools\proguard目录即可,亲测可用.注意不支持中文目录

    proguard-7.2.2.tar.gz

    proguard-7.2.2.tar.gz

    proguard4.8混淆工具

    proguard4.8混淆工具

    Java代码混淆工具 Proguard4.10(官方免费下载)

    proguard4.10 官方版软件,免费下载 1、官网地址:http://proguard.sourceforge.net/ 截止2013-12-15 官网正式版本为4.10, 提供...2、为了保护源码不外泄,义无反顾,我们只能选择混淆,那么Proguard,就是不错的选择!

Global site tag (gtag.js) - Google Analytics