现在的位置: 首页 > 综合 > 正文

混淆的艺术-(苍井空变凤姐)Proguard源码分析(三)Proguard配置解析~上

2017年12月06日 ⁄ 综合 ⁄ 共 17374字 ⁄ 字号 评论关闭
ConfigurationParser会将我们的 配置文件映射成为Configuration对象。这个功能的主要处理逻辑放在ConfigurationParser的parser中:         

if (ConfigurationConstants.AT_DIRECTIVE.startsWith(nextWord)

|| ConfigurationConstants.INCLUDE_DIRECTIVE

.startsWith(nextWord))

configuration.lastModified = parseIncludeArgument(configuration.lastModified);

在调用parseIncludeArgument的时候会往wordReader里面设置一个内置对象:

reader.includeWordReader(new FileWordReader(file));
这样WordReader就成为了FileWordReader的一个装饰器。这样我们就实现了多层嵌套代码的功能
而这个功能所使用的标签就是
ConfigurationConstants.AT_DIRECTIVE  -> @
ConfigurationConstants.INCLUDE_DIRECTIVE -> -include

if (ConfigurationConstants.BASE_DIRECTORY_DIRECTIVE

.startsWith(nextWord))

parseBaseDirectoryArgument();

这个条件的目的是为了设置base路径,它使用的标签是:
ConfigurationConstants.BASE_DIRECTORY_DIRECTIVE->-basedirectory
当我们系统的来看下这个解析的时候,我们会发现,不论你采用那种keep方式的配置都会调用到parseKeepClassSpecificationArguments 这个方法。

if (ConfigurationConstants.KEEP_OPTION.startsWith(nextWord))

configuration.keep = parseKeepClassSpecificationArguments(

configuration.keep, true, false, false);

else if (ConfigurationConstants.KEEP_CLASS_MEMBERS_OPTION

.startsWith(nextWord))

configuration.keep = parseKeepClassSpecificationArguments(

configuration.keep, false, false, false);

else if (ConfigurationConstants.KEEP_CLASSES_WITH_MEMBERS_OPTION

.startsWith(nextWord))

configuration.keep = parseKeepClassSpecificationArguments(

configuration.keep, false, true, false);

else if (ConfigurationConstants.KEEP_NAMES_OPTION

.startsWith(nextWord))

configuration.keep = parseKeepClassSpecificationArguments(

configuration.keep, true, false, true);

else if (ConfigurationConstants.KEEP_CLASS_MEMBER_NAMES_OPTION

.startsWith(nextWord))

configuration.keep = parseKeepClassSpecificationArguments(

configuration.keep, false, false, true);

else if (ConfigurationConstants.KEEP_CLASSES_WITH_MEMBER_NAMES_OPTION

.startsWith(nextWord))

configuration.keep = parseKeepClassSpecificationArguments(

configuration.keep, false, true, true);

我们先不深究这个方法的具体的参数含义,而是一步步来探索这个重要的方法的参数解析实现。

private List parseKeepClassSpecificationArguments(

List keepClassSpecifications, boolean markClasses/*true*/,

boolean markConditionally/*false*/, boolean allowShrinking/*false*/)

throws ParseException, IOException {

// Create a new List if necessary.

if (keepClassSpecifications == null) {

keepClassSpecifications = new ArrayList();

}

boolean allowOptimization = false;

boolean allowObfuscation = false;
while (true) {
readNextWord("keyword '" + ConfigurationConstants.CLASS_KEYWORD
+ "', '" + ClassConstants.EXTERNAL_ACC_INTERFACE
+ "', or '" + ClassConstants.EXTERNAL_ACC_ENUM + "'",
false, true);

if (!ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD
.equals(nextWord)) {
// Not a comma. Stop parsing the keep modifiers.
break;
}

readNextWord("keyword '"
+ ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION + "', '"
+ ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION
+ "', or '"
+ ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION + "'");

if (ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION
.startsWith(nextWord)) {
allowShrinking = true;
} else if (ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION
.startsWith(nextWord)) {
allowOptimization = true;
} else if (ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION
.startsWith(nextWord)) {
allowObfuscation = true;
} else {
throw new ParseException("Expecting keyword '"
+ ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION
+ "', '"
+ ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION
+ "', or '"
+ ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION
+ "' before " + reader.locationDescription());
}

}

// Read the class configuration.
ClassSpecification classSpecification = parseClassSpecificationArguments();

// Create and add the keep configuration.
keepClassSpecifications.add(new KeepClassSpecification(markClasses,
markConditionally, allowShrinking, allowOptimization,
allowObfuscation, classSpecification));

return keepClassSpecifications;

}

红色部分我们可以加在keep后面作为辅助的混淆标志,这样我们可以总结出proguard配置文件写法的基本规律,一定要以keep*打头,但是allowshrinking,allowoptimization,allowobfuscation 这些属于跟keep一样的指令,但是一定要放在keep指令后面通过","分割。这些参数将直接影响配置。我们接着来看绿色的部分,这里我猜测KeepClassSpecification是一个装饰器,我们来看它的继承关系,发现它真的继承于ClassSpecification。那么为何要这么做呢?~其实我们在定义数据结构的时候,往往定义的是原始数据,一些非原始数据的东西我们可以通过装饰或者代理来包装。这样不会影响到原始的数据。如果我们来反过来思考的话,其实非常好理解,allowshrinking,allowoptimization,allowobfuscation 
这些是你的指令,而你的数据是定义在ClassSpecification。我们按照流程下来看的话,其实阅读这些代码并不费劲,可见我们在包装完这种类的keep之后我们需要开始解析类数据。这种流程是面向过程的一种过程。这样不知道各位是不是更好理解.其实我为何愿意看Proguard的源码呢?因为它的代码结构很简单,每个函数很少有超过200行代码的。虽然一个类的代码挺长,但是代码结构都是一种模式.看起来还是颇为惬意的一件事。(你可以试试Android的AMS源码~泪奔)

public ClassSpecification parseClassSpecificationArguments()

throws ParseException, IOException {

// Clear the annotation type.

String annotationType = null;

// Clear the class access modifiers.

int requiredSetClassAccessFlags = 0;

int requiredUnsetClassAccessFlags = 0;

// Parse the class annotations and access modifiers until the class

// keyword.
while (!ConfigurationConstants.CLASS_KEYWORD.equals(nextWord)) {
// Strip the negating sign, if any.
boolean negated = nextWord
.startsWith(ConfigurationConstants.NEGATOR_KEYWORD);

String strippedWord = negated ? nextWord.substring(1) : nextWord;

// Parse the class access modifiers.
int accessFlag = strippedWord
.equals(ClassConstants.EXTERNAL_ACC_PUBLIC) ? ClassConstants.INTERNAL_ACC_PUBLIC
: strippedWord.equals(ClassConstants.EXTERNAL_ACC_FINAL) ? ClassConstants.INTERNAL_ACC_FINAL
: strippedWord
.equals(ClassConstants.EXTERNAL_ACC_INTERFACE) ? ClassConstants.INTERNAL_ACC_INTERFACE
: strippedWord
.equals(ClassConstants.EXTERNAL_ACC_ABSTRACT) ? ClassConstants.INTERNAL_ACC_ABSTRACT
: strippedWord
.equals(ClassConstants.EXTERNAL_ACC_SYNTHETIC) ? ClassConstants.INTERNAL_ACC_SYNTHETIC
: strippedWord
.equals(ClassConstants.EXTERNAL_ACC_ANNOTATION) ? ClassConstants.INTERNAL_ACC_ANNOTATTION
: strippedWord
.equals(ClassConstants.EXTERNAL_ACC_ENUM) ? ClassConstants.INTERNAL_ACC_ENUM
: unknownAccessFlag();

// Is it an annotation modifier?
if (accessFlag == ClassConstants.INTERNAL_ACC_ANNOTATTION) {
// Already read the next word.
readNextWord("annotation type or keyword '"
+ ClassConstants.EXTERNAL_ACC_INTERFACE + "'", false,
false);

// Is the next word actually an annotation type?

if (!nextWord.equals(ClassConstants.EXTERNAL_ACC_INTERFACE)

&& !nextWord.equals(ClassConstants.EXTERNAL_ACC_ENUM)

&& !nextWord

.equals(ConfigurationConstants.CLASS_KEYWORD)) {

// Parse the annotation type.

annotationType = ListUtil.commaSeparatedString(

parseCommaSeparatedList("annotation type", false,

false, false, false, true, false, false,

true, null), false);

// Continue parsing the access modifier that we just read

// in the next cycle.

continue;

}

// Otherwise just handle the annotation modifier.

}

if (!negated) {

requiredSetClassAccessFlags |= accessFlag;

} else {

requiredUnsetClassAccessFlags |= accessFlag;

}

if ((requiredSetClassAccessFlags & requiredUnsetClassAccessFlags) != 0) {

throw new ParseException(

"Conflicting class access modifiers for '"

+ strippedWord + "' before "

+ reader.locationDescription());

}

if (strippedWord.equals(ClassConstants.EXTERNAL_ACC_INTERFACE)

|| strippedWord.equals(ClassConstants.EXTERNAL_ACC_ENUM)

|| strippedWord

.equals(ConfigurationConstants.CLASS_KEYWORD)) {

// The interface or enum keyword. Stop parsing the class flags.

break;

}

// Should we read the next word?
if (accessFlag != ClassConstants.INTERNAL_ACC_ANNOTATTION) {
readNextWord("keyword '" + ConfigurationConstants.CLASS_KEYWORD
+ "', '" + ClassConstants.EXTERNAL_ACC_INTERFACE
+ "', or '" + ClassConstants.EXTERNAL_ACC_ENUM + "'",
false, true);
}

}

// Parse the class name part.

String externalClassName = ListUtil.commaSeparatedString(

parseCommaSeparatedList("class name or interface name", true,

false, false, false, true, false, false, false, null),

false);

// For backward compatibility, allow a single "*" wildcard to match any

// class.

String className = ConfigurationConstants.ANY_CLASS_KEYWORD

.equals(externalClassName) ? null : ClassUtil

.internalClassName(externalClassName);

// Clear the annotation type and the class name of the extends part.

String extendsAnnotationType = null;

String extendsClassName = null;

if (!configurationEnd()) {

// Parse 'implements ...' or 'extends ...' part, if any.

if (ConfigurationConstants.IMPLEMENTS_KEYWORD.equals(nextWord)

|| ConfigurationConstants.EXTENDS_KEYWORD.equals(nextWord)) {

readNextWord("class name or interface name", false, true);

// Parse the annotation type, if any.

if (ConfigurationConstants.ANNOTATION_KEYWORD.equals(nextWord)) {

extendsAnnotationType = ListUtil.commaSeparatedString(

parseCommaSeparatedList("annotation type", true,

false, false, false, true, false, false,

true, null), false);

}

String externalExtendsClassName = ListUtil

.commaSeparatedString(

parseCommaSeparatedList(
"class name or interface name", false,
false, false, false, true, false,
false, false, null), false);

extendsClassName = ConfigurationConstants.ANY_CLASS_KEYWORD
.equals(externalExtendsClassName) ? null : ClassUtil
.internalClassName(externalExtendsClassName);
}
}

// !new class meta
ClassSpecification classSpecification = new ClassSpecification(
lastComments, requiredSetClassAccessFlags,
requiredUnsetClassAccessFlags, annotationType, className,
extendsAnnotationType, extendsClassName);

// Now add any class members to this class specification.

if (!configurationEnd()) {

// Check the class member opening part.
if (!ConfigurationConstants.OPEN_KEYWORD.equals(nextWord)) {
throw new ParseException("Expecting opening '"
+ ConfigurationConstants.OPEN_KEYWORD + "' at "
+ reader.locationDescription());
}

// Parse all class members.
while (true) {
readNextWord("class member description" + " or closing '"
+ ConfigurationConstants.CLOSE_KEYWORD + "'", false,
true);

if (nextWord.equals(ConfigurationConstants.CLOSE_KEYWORD)) {
// The closing brace. Stop parsing the class members.
readNextWord();

break;
}

parseMemberSpecificationArguments(externalClassName,
classSpecification);
}
}

return classSpecification;

}

这个代码的结构就是为了解决类结构的问题。我们知道按照java的规范,类定义的前面可以有一定数量的修饰语,可能用来修饰作用域,可能用来修饰类的类型。这段代码自然对这些做了兼容。在class文件中,通过位标志来解释类中的各种参数。Proguard沿用了这种做法。红色部分就是为了生成这些类的元数据.只不过例外的事情是可以在这些东西前面增加“!”标签,代表“非”的操作。这里我们说明一下,Proguard对于一些错误是保持沉默的,或者说是支持一些无关的写法。比如对于注解你可以写成@com.test.anno
这种具体的类,或者是@class @interface @anum都是可以的,而对于@class @interface @anum
的处理逻辑都是一样的,简单一点就是跳过(紫色代码段)。我们看到中间部分的绿色部分代码,这个时候 keep ![public,private,protected][class interface @anno anum] name就解析完成。 实际上这个已经是一个完整的keep配置了(类似-keep  class com.test.Class2);这里提一下,每个配置项的结束是通过@符号或者-符号作为标志的。我们可以在classname之后再增加implement和extends这类的关键字,Proguard还是一如既往的不做区分。好了到了这一部,我们足够数据定义一个完整的类的元数据。接下来就是蓝色部分的参数解析;抛开一些无关紧要的代码我们直接看重点:

private void parseMemberSpecificationArguments(String externalClassName,

ClassSpecification classSpecification) throws ParseException,

IOException {

// Clear the annotation name.

String annotationType = null;

// Parse the class member access modifiers, if any.

int requiredSetMemberAccessFlags = 0;

int requiredUnsetMemberAccessFlags = 0;

while (!configurationEnd(true)) {

// Parse the annotation type, if any.

if (ConfigurationConstants.ANNOTATION_KEYWORD.equals(nextWord)) {

annotationType = ListUtil.commaSeparatedString(

parseCommaSeparatedList("annotation type", true, false,

false, false, true, false, false, true, null),

false);

continue;

}

String strippedWord = nextWord.startsWith("!") ? nextWord

.substring(1) : nextWord;

// Parse the class member access modifiers.

int accessFlag = strippedWord

.equals(ClassConstants.EXTERNAL_ACC_PUBLIC) ? ClassConstants.INTERNAL_ACC_PUBLIC

: strippedWord.equals(ClassConstants.EXTERNAL_ACC_PRIVATE) ? ClassConstants.INTERNAL_ACC_PRIVATE

: strippedWord

.equals(ClassConstants.EXTERNAL_ACC_PROTECTED) ? ClassConstants.INTERNAL_ACC_PROTECTED

: strippedWord

.equals(ClassConstants.EXTERNAL_ACC_STATIC) ? ClassConstants.INTERNAL_ACC_STATIC

: strippedWord

.equals(ClassConstants.EXTERNAL_ACC_FINAL) ? ClassConstants.INTERNAL_ACC_FINAL

: strippedWord

.equals(ClassConstants.EXTERNAL_ACC_SYNCHRONIZED) ? ClassConstants.INTERNAL_ACC_SYNCHRONIZED

: strippedWord

.equals(ClassConstants.EXTERNAL_ACC_VOLATILE) ? ClassConstants.INTERNAL_ACC_VOLATILE

: strippedWord

.equals(ClassConstants.EXTERNAL_ACC_TRANSIENT) ? ClassConstants.INTERNAL_ACC_TRANSIENT

: strippedWord

.equals(ClassConstants.EXTERNAL_ACC_BRIDGE) ? ClassConstants.INTERNAL_ACC_BRIDGE

: strippedWord

.equals(ClassConstants.EXTERNAL_ACC_VARARGS) ? ClassConstants.INTERNAL_ACC_VARARGS

: strippedWord

.equals(ClassConstants.EXTERNAL_ACC_NATIVE) ? ClassConstants.INTERNAL_ACC_NATIVE

: strippedWord

.equals(ClassConstants.EXTERNAL_ACC_ABSTRACT) ? ClassConstants.INTERNAL_ACC_ABSTRACT

: strippedWord

.equals(ClassConstants.EXTERNAL_ACC_STRICT) ? ClassConstants.INTERNAL_ACC_STRICT

: strippedWord

.equals(ClassConstants.EXTERNAL_ACC_SYNTHETIC) ? ClassConstants.INTERNAL_ACC_SYNTHETIC

: 0;

if (accessFlag == 0) {

// Not a class member access modifier. Stop parsing them.

break;

}

if (strippedWord.equals(nextWord)) {

requiredSetMemberAccessFlags |= accessFlag;

} else {

requiredUnsetMemberAccessFlags |= accessFlag;

}

// Make sure the user doesn't try to set and unset the same

// access flags simultaneously.

if ((requiredSetMemberAccessFlags & requiredUnsetMemberAccessFlags) != 0) {

throw new ParseException(

"Conflicting class member access modifiers for "

+ reader.locationDescription());

}

readNextWord("class member description");

}

// Parse the class member type and name part.

// Did we get a special wildcard?

if (ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD.equals(nextWord)

|| ConfigurationConstants.ANY_FIELD_KEYWORD.equals(nextWord)

|| ConfigurationConstants.ANY_METHOD_KEYWORD.equals(nextWord)) {

// Act according to the type of wildcard..

if (ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD

.equals(nextWord)) {

checkFieldAccessFlags(requiredSetMemberAccessFlags,

requiredUnsetMemberAccessFlags);

checkMethodAccessFlags(requiredSetMemberAccessFlags,

requiredUnsetMemberAccessFlags);

classSpecification.addField(new MemberSpecification(

requiredSetMemberAccessFlags,

requiredUnsetMemberAccessFlags, annotationType, null,

null));

classSpecification.addMethod(new MemberSpecification(

requiredSetMemberAccessFlags,

requiredUnsetMemberAccessFlags, annotationType, null,

null));

} else if (ConfigurationConstants.ANY_FIELD_KEYWORD

.equals(nextWord)) {

checkFieldAccessFlags(requiredSetMemberAccessFlags,

requiredUnsetMemberAccessFlags);

classSpecification.addField(new MemberSpecification(

requiredSetMemberAccessFlags,

requiredUnsetMemberAccessFlags, annotationType, null,

null));

} else if (ConfigurationConstants.ANY_METHOD_KEYWORD

.equals(nextWord)) {

checkMethodAccessFlags(requiredSetMemberAccessFlags,

requiredUnsetMemberAccessFlags);

classSpecification.addMethod(new MemberSpecification(

requiredSetMemberAccessFlags,

requiredUnsetMemberAccessFlags, annotationType, null,

null));

}

// We still have to read the closing separator.

readNextWord("separator '"

+ ConfigurationConstants.SEPARATOR_KEYWORD + "'");

if (!ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord)) {

throw new ParseException("Expecting separator '"

+ ConfigurationConstants.SEPARATOR_KEYWORD

+ "' before " + reader.locationDescription());

}

} else {
// Make sure we have a proper type.

checkJavaIdentifier("java type");

String type = nextWord;

readNextWord("class member name");

String name = nextWord;

// Did we get just one word before the opening parenthesis?

if (ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD.equals(name)) {

// This must be a constructor then.

// Make sure the type is a proper constructor name.

if (!(type.equals(ClassConstants.INTERNAL_METHOD_NAME_INIT)

|| type.equals(externalClassName) || type

.equals(ClassUtil

.externalShortClassName(externalClassName)))) {

throw new ParseException("Expecting type and name "

+ "instead of just '" + type + "' before "

+ reader.locationDescription());

}

// Assign the fixed constructor type and name.

type = ClassConstants.EXTERNAL_TYPE_VOID;

name = ClassConstants.INTERNAL_METHOD_NAME_INIT;

} else {

// It's not a constructor.

// Make sure we have a proper name.

checkJavaIdentifier("class member name");

// Read the opening parenthesis or the separating

// semi-colon.

readNextWord("opening '"

+ ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD

+ "' or separator '"

+ ConfigurationConstants.SEPARATOR_KEYWORD + "'");

}

// Are we looking at a field, a method, or something else?

if (ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord)) {

// It's a field.

checkFieldAccessFlags(requiredSetMemberAccessFlags,

requiredUnsetMemberAccessFlags);

// We already have a field descriptor.

String descriptor = ClassUtil.internalType(type);

// Add the field.

classSpecification.addField(new MemberSpecification(

requiredSetMemberAccessFlags,

requiredUnsetMemberAccessFlags, annotationType, name,

descriptor));

} else if (ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD

.equals(nextWord)) {

// It's a method.

checkMethodAccessFlags(requiredSetMemberAccessFlags,

requiredUnsetMemberAccessFlags);

// Parse the method arguments.

String descriptor = ClassUtil.internalMethodDescriptor(

type,

parseCommaSeparatedList("argument", true, true, true,

false, true, false, false, false, null));

if (!ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD

.equals(nextWord)) {

throw new ParseException("Expecting separating '"

+ ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD

+ "' or closing '"

+ ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD

+ "' before " + reader.locationDescription());

}

// Read the separator after the closing parenthesis.

readNextWord("separator '"

+ ConfigurationConstants.SEPARATOR_KEYWORD + "'");

if (!ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord)) {

throw new ParseException("Expecting separator '"

+ ConfigurationConstants.SEPARATOR_KEYWORD

+ "' before " + reader.locationDescription());

}

// Add the method.

classSpecification.addMethod(new MemberSpecification(

requiredSetMemberAccessFlags,

requiredUnsetMemberAccessFlags, annotationType, name,

descriptor));

} else {

// It doesn't look like a field or a method.

throw new ParseException("Expecting opening '"

+ ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD

+ "' or separator '"

+ ConfigurationConstants.SEPARATOR_KEYWORD

+ "' before " + reader.locationDescription());

}

}

}

我们发现解析属性和方法的代码跟解析类元数据的方法非常相似,其实在java中定义类成员和定义类的方式本身就很相似。但这里对Any的定义增加了两种:

public static final String ANY_CLASS_MEMBER_KEYWORD = "*";
public static final String ANY_FIELD_KEYWORD = "<fields>";
public static final String ANY_METHOD_KEYWORD = "<methods>";


可以看到实际上就是比类定义的数据结构多了属性的任意匹配和方法匹配,其实还多了属性的修饰符。比如private final 注解这一类,除了比较传统的public static private protected 这种的关键字说明意外,还增加了“!”用来表示非的逻辑关系。这里要特别说明的一点是由于作用域和static说明是采用位标志的方式存在,因此可以不在意顺序但是!一定要放在条件的开头部分。而且在属性定义中,也支持了分号为结尾的定义方式。红色代码部分是不采用通配符的方式来定义方法或者属性说明,我们看到,对于方法而言,Proguard将为其生成一个方法的描述符号,用来唯一标识该方法。

待续。。。

                                                                                                                                                      --非子墨

抱歉!评论已关闭.