Proguard源碼分析

Proguard是開源項目,下載地址:https://sourceforge.net/projects/proguard/

解壓后目錄一覽


截圖.png

核心內(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文件讀取過程,所有操作的前提是得到程序類池贬蛙。


截圖 (1).png

后續(xù)所有步驟的基礎(chǔ)就是操作讀取到的programClassPool和libraryClassPool雨女,ClassPool類是一組類的表示,通過TreeMap存儲阳准,它們可以按名稱枚舉或檢索氛堕,也可以通過類訪客訪問。
下圖是關(guān)于混淆步驟中的一個訪問UML類圖野蝇,首先說明在Proguard的每一個子程序步驟中都涉及大量的訪問類讼稚,ClassRenamer只是其中之一。


截圖 (2).png

混淆的核心需求就是對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)就不難理解嗡髓。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載操漠,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市浊伙,隨后出現(xiàn)的幾起案子撞秋,更是在濱河造成了極大的恐慌,老刑警劉巖嚣鄙,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吻贿,死亡現(xiàn)場離奇詭異,居然都是意外死亡哑子,警方通過查閱死者的電腦和手機舅列,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來卧蜓,“玉大人帐要,你說我怎么就攤上這事∶旨椋” “怎么了榨惠?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長盛霎。 經(jīng)常有香客問我赠橙,道長,這世上最難降的妖魔是什么摩渺? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任简烤,我火速辦了婚禮,結(jié)果婚禮上摇幻,老公的妹妹穿的比我還像新娘横侦。我一直安慰自己,他們只是感情好绰姻,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布枉侧。 她就那樣靜靜地躺著,像睡著了一般狂芋。 火紅的嫁衣襯著肌膚如雪榨馁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天帜矾,我揣著相機與錄音翼虫,去河邊找鬼。 笑死屡萤,一個胖子當著我的面吹牛珍剑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播死陆,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼招拙,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起别凤,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤饰序,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后规哪,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體求豫,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年诉稍,在試婚紗的時候發(fā)現(xiàn)自己被綠了注祖。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡均唉,死狀恐怖是晨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情舔箭,我是刑警寧澤罩缴,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站层扶,受9級特大地震影響箫章,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜镜会,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一檬寂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧戳表,春花似錦桶至、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至价涝,卻和暖如春女蜈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背色瘩。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工伪窖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人居兆。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓覆山,卻偏偏與公主長得像,于是被迫代替她去往敵國和親史辙。 傳聞我的和親對象是個殘疾皇子汹买,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容