baksmali和smali源碼分析(四)

 baksmali 首先執行的第一個main 函數

    public static void main(String[] args) throws IOException {
        Locale locale = new Locale("en", "US");
        Locale.setDefault(locale);

        CommandLineParser parser = new PosixParser();
        CommandLine commandLine;

        try {
            commandLine = parser.parse(options, args);
        } catch (ParseException ex) {
            usage();
            return;
        }

        baksmaliOptions options = new baksmaliOptions();

        boolean disassemble = true;              // 需要反編譯
 
 				...
 				
 	//中間有一部分獲取命令行參數的代碼,暫時省略

        String[] remainingArgs = commandLine.getArgs();
        Option[] clOptions = commandLine.getOptions();

 				...


	//解析完成命令行參數
				
	//首先判斷機器cpu的個數,確定多個cpu能夠同時工作,以提高解析效率
        if (options.jobs <= 0) {
            options.jobs = Runtime.getRuntime().availableProcessors();
            if (options.jobs > 6) {
                options.jobs = 6;
            }
        }


	//判斷api的版本號,當大於17的時候,設置檢測包的私有訪問屬性
        if (options.apiLevel >= 17) {
            options.checkPackagePrivateAccess = true;
        }

        String inputDexFileName = remainingArgs[0];

	//打開目標文件
        File dexFileFile = new File(inputDexFileName);
        if (!dexFileFile.exists()) {
            System.err.println("Can't find the file " + inputDexFileName);
            System.exit(1);
        }
				
				
        //Read in and parse the dex file
        DexBackedDexFile dexFile = DexFileFactory.loadDexFile(dexFileFile, options.apiLevel);    // 重點1 
        
				
  	//主要判斷odex文件的一些代碼,省略
  		  ...
  

	//反彙編dex文件,生成一個又一個的smali文件
        boolean errorOccurred = false;
        if (disassemble) {
            errorOccurred = !baksmali.disassembleDexFile(dexFile, options);           //  重點2
        }

        if (doDump) {
            if (dumpFileName == null) {
                dumpFileName = commandLine.getOptionValue(inputDexFileName + ".dump");
            }
            dump.dump(dexFile, dumpFileName, options.apiLevel);
        }

        if (errorOccurred) {
            System.exit(1);
        }
    }



關於main函數的分析主要有兩點,需要重點研究一下,一個是

 //Read in and parse the dex file
DexBackedDexFile dexFile = DexFileFactory.loadDexFile(dexFileFile, options.apiLevel);    // 重點1

另外一個就是

errorOccurred = !baksmali.disassembleDexFile(dexFile, options);           //  重點2

我們首先看 DexFileFactory.loadDexFile(dexFileFile, options.apiLevel); 這個函數做了什麼事情



    public static DexBackedDexFile loadDexFile(String path, int api) throws IOException {

        return loadDexFile(new File(path), new Opcodes(api));

    }

    

    其中 new Opcodes(api) 根據傳入的api版本號 生成了 Opcodes 這個對象

    

    這個對象主要是將  dalvik 虛擬機所有的指令code 映射到 一張 hashmap中,索引是本身的指令名稱

    

    比如 move-result-wide, if-ne ,invoke-static/range 這些指令,而結果是相應的枚舉類,其實本身 Opcode 這個類將dalvik 虛擬機支持的指令進行了很好的代碼詮釋,在理解了整個代碼框架以後,可以重點關注一下

      

    真正調用的 loadDexFile 函數如下:

    public static DexBackedDexFile loadDexFile(File dexFile, @Nonnull Opcodes opcodes) throws IOException {

	...
	//首先判斷文件是否爲一個壓縮文件,如果是的話解壓縮後提取dex文件進行解析
				
        InputStream inputStream = new BufferedInputStream(new FileInputStream(dexFile));

        try {
            return DexBackedDexFile.fromInputStream(opcodes, inputStream);             // 重點 1
        } catch (DexBackedDexFile.NotADexFile ex) {
            // just eat it
        }

     // Note: DexBackedDexFile.fromInputStream will reset inputStream back to the same position, if it fails

        try {
            return DexBackedOdexFile.fromInputStream(opcodes, inputStream);
        } catch (DexBackedOdexFile.NotAnOdexFile ex) {
            // just eat it
        }

        throw new ExceptionWithContext("%s is not an apk, dex file or odex file.", dexFile.getPath());
    }



我們依然跟着重點1 進入到 DexBackedDexFile.fromInputStream(opcodes, inputStream);  這個函數

    public static DexBackedDexFile fromInputStream(@Nonnull Opcodes opcodes, @Nonnull InputStream is)
            throws IOException {
        if (!is.markSupported()) {
            throw new IllegalArgumentException("InputStream must support mark");
        }
        is.mark(44);
        byte[] partialHeader = new byte[44];
        try {
            ByteStreams.readFully(is, partialHeader);
        } catch (EOFException ex) {
            throw new NotADexFile("File is too short");
        } finally {
            is.reset();
        }

	//驗證一下魔幻數和dex文件頭部
        verifyMagicAndByteOrder(partialHeader, 0);

        byte[] buf = ByteStreams.toByteArray(is);
        return new DexBackedDexFile(opcodes, buf, 0, false);   //繼續跟蹤下去
    }
    
    
    
    private DexBackedDexFile(Opcodes opcodes, @Nonnull byte[] buf, int offset, boolean verifyMagic) {
        super(buf);

        this.opcodes = opcodes;

        if (verifyMagic) {
            verifyMagicAndByteOrder(buf, offset);
        }

        stringCount = readSmallUint(HeaderItem.STRING_COUNT_OFFSET);
        stringStartOffset = readSmallUint(HeaderItem.STRING_START_OFFSET);
        typeCount = readSmallUint(HeaderItem.TYPE_COUNT_OFFSET);
        typeStartOffset = readSmallUint(HeaderItem.TYPE_START_OFFSET);
        protoCount = readSmallUint(HeaderItem.PROTO_COUNT_OFFSET);
        protoStartOffset = readSmallUint(HeaderItem.PROTO_START_OFFSET);
        fieldCount = readSmallUint(HeaderItem.FIELD_COUNT_OFFSET);
        fieldStartOffset = readSmallUint(HeaderItem.FIELD_START_OFFSET);
        methodCount = readSmallUint(HeaderItem.METHOD_COUNT_OFFSET);
        methodStartOffset = readSmallUint(HeaderItem.METHOD_START_OFFSET);
        classCount = readSmallUint(HeaderItem.CLASS_COUNT_OFFSET);
        classStartOffset = readSmallUint(HeaderItem.CLASS_START_OFFSET);
    }


    其實這個函數很簡單,就是通過傳入的文件流通過dex文件頭找到了 dex 文件中的各個索引表的起始地址,索引數量等信息,然後返回一個實例對象給上層,以方便後面的調用

    

    注:這裏需要對dex文件的格式有一定的瞭解,讀者可以查閱相關的文檔。



    分析完了

     DexBackedDexFile dexFile = DexFileFactory.loadDexFile(dexFileFile, options.apiLevel);

     

    這條語句,我們得到了 DexBackedDexFile 類的一個實例對象,這個對象裏面包含什麼東西,總結一下有以下內容

    

    <*>   private final Opcodes opcodes;  要解析dex文件的dalvik虛擬機的指令集合

    <*>   這個dex文件中各種索引的開始地址,索引個數等信息

          比如

                        private final int protoCount;

    private final int protoStartOffset;    

    這兩個成員變量主要就是爲後面的方法列表提供彈藥,保存的是在這個dex文件中實現或者調用的方法信息的字段

    比如 你的dex裏面有個這樣的方法,

    int testcall(String test)

    那麼在 這個表中一定有一個 IL  類型的函數原型,其中 I表示返回類型爲 int,L 表示這個函數有一個參數,並且參數是一個對象類型

    具體是什麼對象呢,在這個表中其實是根據偏移來保存對象的類型的,本身proto這個表中並不提供方法信息的,而是爲方法提供函數調用原型,略爲有點繞,不過習慣了就好。

   

   

    <*>   dex文件的文件流,以便再進行深入的查詢


    ok,我們再回到main函數,看後面的一個關鍵調用

    

    errorOccurred = !baksmali.disassembleDexFile(dexFile, options);

    這個調用總體說來,就是完成了 將dex文件轉換成一個一個smali文件的艱鉅任務!

    public static boolean disassembleDexFile(DexFile dexFile, final baksmaliOptions options) {

	...
				
	//根據傳入的文件夾路徑創建文件夾
        File outputDirectoryFile = new File(options.outputDirectory);
        if (!outputDirectoryFile.exists()) {
            if (!outputDirectoryFile.mkdirs()) {
                System.err.println("Can't create the output directory " + options.outputDirectory);
                return false;
            }
        }


	//排序並生成dex文件中的所有 類定義實例到類定義的列表中
        //sort the classes, so that if we're on a case-insensitive file system and need to handle classes with file
        //name collisions, then we'll use the same name for each class, if the dex file goes through multiple
        //baksmali/smali cycles for some reason. If a class with a colliding name is added or removed, the filenames
        //may still change of course
        List<? extends ClassDef> classDefs = Ordering.natural().sortedCopy(dexFile.getClasses());   //  重點1 

        if (!options.noAccessorComments) {
            options.syntheticAccessorResolver = new SyntheticAccessorResolver(classDefs);
        }
				
	//生成文件的擴展名,爲.smali
        final ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(outputDirectoryFile, ".smali");

	//根據 options.jobs 的值來生成處理 smali文件的線程數量
        ExecutorService executor = Executors.newFixedThreadPool(options.jobs);
        List<Future<Boolean>> tasks = Lists.newArrayList();

        for (final ClassDef classDef: classDefs) {
            tasks.add(executor.submit(new Callable<Boolean>() {
                @Override public Boolean call() throws Exception {
                    return disassembleClass(classDef, fileNameHandler, options);    //回調的解析函數,重點2
                }
            }));
        }

				...
    }


    可以看出來,這個函數主要做了這麼幾件事情

    

    <*>創建了要生成smali文件的文件夾目錄

    <*>生成了解析dex文件所有的類實例

    <*>開啓多線程運行的機制,以類爲單位來生成一個又一個的 smali文件,當然文件的擴展名名.smali



    故事1 

    List<? extends ClassDef> classDefs = Ordering.natural().sortedCopy(dexFile.getClasses());   //  重點1 

    

    這個函數主要分爲兩部分

    

    dexFile.getClasses() 這個函數其實是調用的是 DexBackedDexFile  這個類的 getClasses 方法

    

    函數如下

    public Set<? extends DexBackedClassDef> getClasses() {
        return new FixedSizeSet<DexBackedClassDef>() {
            @Nonnull
            @Override
            public DexBackedClassDef readItem(int index) {
                return new DexBackedClassDef(DexBackedDexFile.this, getClassDefItemOffset(index));
            }

            @Override
            public int size() {
                return classCount;
            }
        };
    }


    其實就是返回一個 new FixedSizeSet<DexBackedClassDef>() 這個匿名類,然後

    Ordering.natural().sortedCopy(new FixedSizeSet<DexBackedClassDef>()),這個方法會在內部調用到

    

    new FixedSizeSet<DexBackedClassDef>() 這個類中的繼承的兩個方法 readItem 和 size,其中

    readItem 這個方法,根據傳進來的index值來實例化 DexBackedClassDef 類,加入到    

    List<? extends ClassDef> classDefs 這個列表中去

    

    我們再來看 這條語句

    return new DexBackedClassDef(DexBackedDexFile.this, getClassDefItemOffset(index)); 

    public int getClassDefItemOffset(int classIndex) {
        if (classIndex < 0 || classIndex >= classCount) {
            throw new InvalidItemIndex(classIndex, "Class index out of bounds: %d", classIndex);
        }
        return classStartOffset + classIndex*ClassDefItem.ITEM_SIZE;
    }



    很簡單,就是從dex文件中找到 指定class的索引地址,dex文件中表示class的其實是個比較複雜的結構,需要好好理解一下,休息一下見下篇繼續




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章