您好,登錄后才能下訂單哦!
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的其實是個比較復雜的結構,需要好好理解一下,休息一下見下篇繼續
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。