Proguard是開源項目,下載地址:https://sourceforge.net/projects/proguard/
解壓后目錄一覽
核心內(nèi)容在core文件下揖庄,這是一個java項目栗菜,可直接導(dǎo)入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是配置內(nèi)容
// 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);
}
從入口函數(shù)可知核心方法是execute,下面看下具體實現(xiàn)
/**
* Performs all subsequent ProGuard operations.
*/
public void execute() throws IOException {
System.out.println(VERSION);
//GPL許可協(xié)議檢查
GPL.check();
if (configuration.printConfiguration != null) {
printConfiguration();
}
//檢查混淆配置是否正確
new ConfigurationChecker(configuration).check();
if (configuration.programJars != null
&& configuration.programJars.hasOutput()
&& new UpToDateChecker(configuration).check()) {
return;
}
//指定是否應(yīng)啟用將類文件反向移植到另一個TargetClassVersion
//(如果指定targetClassVersion=8,程序會對類加入8特性的變化禁炒,比如某些代碼塊改成lambda表達式)
if (configuration.targetClassVersion != 0) {
configuration.backport = true;
}
//讀取input jar中所有class到類池中,重要入口而咆,后續(xù)所有操作都要訪問類池
readInput();
if (configuration.shrink || configuration.optimize
|| configuration.obfuscate || configuration.preverify) {
//從程序類中清除任何JSE預(yù)驗證信息。
//實際是清除StackMapTable屬性幕袱,在Java 6版本之后JVM在class文件中引入了棧圖
//(StackMapTable)屬性暴备。作用是為了提高JVM在類型檢查的驗證過程的效率
//在字節(jié)碼的Code屬性中最多包含一個StackMapTable屬性
clearPreverification();
}
if (configuration.printSeeds != null || configuration.shrink
|| configuration.optimize || configuration.obfuscate
|| configuration.preverify || configuration.backport) {
//初始化所有類之間的交叉引用,執(zhí)行一些基本檢查们豌,并收縮庫類池涯捻。
//比如:初始化程序類的超類層次結(jié)構(gòu); 初始化程序類成員和屬性的類引用;
initialize();
}
if (configuration.obfuscate || configuration.optimize) {
//這個類訪問者用優(yōu)化的基元數(shù)組常量替換數(shù)組初始化指令。這些常數(shù)不受任何Java規(guī)范的支持望迎,因此僅用于內(nèi)部使用障癌。
introducePrimitiveArrayConstants();
}
if (configuration.backport) {
//將Java語言特性備份到指定的目標版本。
backport();
}
if (configuration.addConfigurationDebugging) {
//添加配置日志代碼辩尊,提供改進Proguard配置的建議涛浙。
addConfigurationLogging();
}
if (configuration.printSeeds != null) {
//打印出在收縮和混淆步驟中用作種子的類和類成員
printSeeds();
}
if (configuration.preverify || configuration.android) {
//執(zhí)行子程序內(nèi)聯(lián)步驟
//在代碼屬性中內(nèi)聯(lián)本地子程序(JSR/RET)
//比如在字節(jié)碼中finally中的子句就是一個子程序,讓JVM跳轉(zhuǎn)到子程序的操作碼是jsr指令,
//JVM在子程序完成之后蝗拿,調(diào)用ret指令晾捏,從子程序返回。
inlineSubroutines();
}
if (configuration.shrink) {
//執(zhí)行收縮步驟
shrink();
}
if (configuration.optimize) {
for (int optimizationPass = 0; optimizationPass < configuration.optimizationPasses; optimizationPass++) {
//執(zhí)行優(yōu)化步驟
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();
}
}
//在方法內(nèi)聯(lián)和類合并等優(yōu)化之后哀托,消除所有程序類的行號惦辛。
linearizeLineNumbers();
}
if (configuration.obfuscate) {
//執(zhí)行混淆處理步驟。
obfuscate();
}
if (configuration.optimize || configuration.obfuscate) {
//將基元數(shù)組常量展開回傳統(tǒng)的基元數(shù)組初始化代碼仓手。
expandPrimitiveArrayConstants();
}
if (configuration.optimize) {
//修剪所有程序類的行號表屬性
trimLineNumbers();
}
if (configuration.targetClassVersion != 0) {
//設(shè)置程序類的目標版本
target();
}
if (configuration.preverify) {
//執(zhí)行預(yù)驗證步驟胖齐。
preverify();
}
if (configuration.shrink || configuration.optimize
|| configuration.obfuscate || configuration.preverify) {
//對所有程序類的元素排序。
sortClassElements();
}
if (configuration.programJars.hasOutput()) {
//寫入輸出類文件嗽冒。
writeOutput();
}
if (configuration.dump != null) {
//打印出程序類的內(nèi)容呀伙。
dump();
}
}
可以看出整個Proguard過程,執(zhí)行了非常多的步驟添坊,每個步驟都需要對類進行訪問操作剿另。
以下是input jar文件讀取過程,所有操作的前提是得到程序類池贬蛙。
后續(xù)所有步驟的基礎(chǔ)就是操作讀取到的programClassPool和libraryClassPool雨女,ClassPool類是一組類的表示,通過TreeMap存儲阳准,它們可以按名稱枚舉或檢索氛堕,也可以通過類訪客訪問。
下圖是關(guān)于混淆步驟中的一個訪問UML類圖野蝇,首先說明在Proguard的每一個子程序步驟中都涉及大量的訪問類讼稚,ClassRenamer只是其中之一。
混淆的核心需求就是對class類做修改绕沈,從類圖可以看出ProgramClass是我們的核心類锐想,它就是Java類中數(shù)據(jù)的完整表示.。
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文件結(jié)構(gòu)字段乍狐,更多信息移步:Class文件結(jié)構(gòu)解析
類型 | 描述 | 備注 |
---|---|---|
u4 | magic | 魔數(shù):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 | 接口個數(shù) |
u2 | interfaces[interfaces_count] | 接口類索引信息 |
u2 | fields_count | 字段數(shù) |
field_info | fields[fields_count] | 字段表信息 |
u2 | methods_count | 方法數(shù) |
method_info | methods[methods_count] | 方法表信息 |
u2 | attributes_count | 屬性個數(shù) |
attribute_info | attributes[attributes_count] | 屬性表信息 |
再來看Proguard對于class文件的讀寫操作就會清晰明了
讀取實現(xiàn):
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;
}
}
寫入實現(xiàn):
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的實現(xiàn)原理已經(jīng)掌握了,如果還有疑問再來看下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.
//類名存儲在常量池中蝉稳,對常量池添加一個常量抒蚜,修改類名在常量池數(shù)組中的下標掘鄙,指向到新類名
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文件結(jié)構(gòu),對Proguard的實現(xiàn)就不難理解嗡髓。