Proguard是开源项目,下载地址:https://sourceforge.net/projects/proguard/
解压后目录一览
核心内容在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文件读取过程,所有操作的前提是得到程序类池。
后续所有步骤的基础就是操作读取到的programClassPool和libraryClassPool,ClassPool类是一组类的表示,通过TreeMap存储,它们可以按名称枚举或检索,也可以通过类访客访问。
下图是关于混淆步骤中的一个访问UML类图,首先说明在Proguard的每一个子程序步骤中都涉及大量的访问类,ClassRenamer只是其中之一。
混淆的核心需求就是对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的实现就不难理解。