baksmali 首先执行的第一个main 函数

publicstaticvoidmain(String[]args)throwsIOException{Localelocale=newLocale("en","US");Locale.setDefault(locale);CommandLineParserparser=newPosixParser();CommandLinecommandLine;try{commandLine=parser.parse(options,args);}catch(ParseExceptionex){usage();return;}baksmaliOptionsoptions=newbaksmaliOptions();booleandisassemble=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;}StringinputDexFileName=remainingArgs[0];//打开目标文件FiledexFileFile=newFile(inputDexFileName);if(!dexFileFile.exists()){System.err.println("Can'tfindthefile"+inputDexFileName);System.exit(1);}//ReadinandparsethedexfileDexBackedDexFiledexFile=DexFileFactory.loadDexFile(dexFileFile,options.apiLevel);//重点1//主要判断odex文件的一些代码,省略...//反汇编dex文件,生成一个又一个的smali文件booleanerrorOccurred=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函数的分析主要有两点,需要重点研究一下,一个是

//ReadinandparsethedexfileDexBackedDexFiledexFile=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 函数如下:

publicstaticDexBackedDexFileloadDexFile(FiledexFile,@NonnullOpcodesopcodes)throwsIOException{...//首先判断文件是否为一个压缩文件,如果是的话解压缩后提取dex文件进行解析InputStreaminputStream=newBufferedInputStream(newFileInputStream(dexFile));try{returnDexBackedDexFile.fromInputStream(opcodes,inputStream);//重点1}catch(DexBackedDexFile.NotADexFileex){//justeatit}//Note:DexBackedDexFile.fromInputStreamwillresetinputStreambacktothesameposition,ifitfailstry{returnDexBackedOdexFile.fromInputStream(opcodes,inputStream);}catch(DexBackedOdexFile.NotAnOdexFileex){//justeatit}thrownewExceptionWithContext("%sisnotanapk,dexfileorodexfile.",dexFile.getPath());}



我们依然跟着重点1 进入到 DexBackedDexFile.fromInputStream(opcodes, inputStream); 这个函数

publicstaticDexBackedDexFilefromInputStream(@NonnullOpcodesopcodes,@NonnullInputStreamis)throwsIOException{if(!is.markSupported()){thrownewIllegalArgumentException("InputStreammustsupportmark");}is.mark(44);byte[]partialHeader=newbyte[44];try{ByteStreams.readFully(is,partialHeader);}catch(EOFExceptionex){thrownewNotADexFile("Fileistooshort");}finally{is.reset();}//验证一下魔幻数和dex文件头部verifyMagicAndByteOrder(partialHeader,0);byte[]buf=ByteStreams.toByteArray(is);returnnewDexBackedDexFile(opcodes,buf,0,false);//继续跟踪下去}privateDexBackedDexFile(Opcodesopcodes,@Nonnullbyte[]buf,intoffset,booleanverifyMagic){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文件的艰巨任务!

publicstaticbooleandisassembleDexFile(DexFiledexFile,finalbaksmaliOptionsoptions){...//根据传入的文件夹路径创建文件夹FileoutputDirectoryFile=newFile(options.outputDirectory);if(!outputDirectoryFile.exists()){if(!outputDirectoryFile.mkdirs()){System.err.println("Can'tcreatetheoutputdirectory"+options.outputDirectory);returnfalse;}}//排序并生成dex文件中的所有类定义实例到类定义的列表中//sorttheclasses,sothatifwe'reonacase-insensitivefilesystemandneedtohandleclasseswithfile//namecollisions,thenwe'llusethesamenameforeachclass,ifthedexfilegoesthroughmultiple//baksmali/smalicyclesforsomereason.Ifaclasswithacollidingnameisaddedorremoved,thefilenames//maystillchangeofcourseList<?extendsClassDef>classDefs=Ordering.natural().sortedCopy(dexFile.getClasses());//重点1if(!options.noAccessorComments){options.syntheticAccessorResolver=newSyntheticAccessorResolver(classDefs);}//生成文件的扩展名,为.smalifinalClassFileNameHandlerfileNameHandler=newClassFileNameHandler(outputDirectoryFile,".smali");//根据options.jobs的值来生成处理smali文件的线程数量ExecutorServiceexecutor=Executors.newFixedThreadPool(options.jobs);List<Future<Boolean>>tasks=Lists.newArrayList();for(finalClassDefclassDef:classDefs){tasks.add(executor.submit(newCallable<Boolean>(){@OverridepublicBooleancall()throwsException{returndisassembleClass(classDef,fileNameHandler,options);//回调的解析函数,重点2}}));}...}


可以看出来,这个函数主要做了这么几件事情

<*>创建了要生成smali文件的文件夹目录

<*>生成了解析dex文件所有的类实例

<*>开启多线程运行的机制,以类为单位来生成一个又一个的 smali文件,当然文件的扩展名名.smali



故事1

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

这个函数主要分为两部分

dexFile.getClasses() 这个函数其实是调用的是 DexBackedDexFile 这个类的 getClasses 方法

函数如下

publicSet<?extendsDexBackedClassDef>getClasses(){returnnewFixedSizeSet<DexBackedClassDef>(){@Nonnull@OverridepublicDexBackedClassDefreadItem(intindex){returnnewDexBackedClassDef(DexBackedDexFile.this,getClassDefItemOffset(index));}@Overridepublicintsize(){returnclassCount;}};}


其实就是返回一个 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));

publicintgetClassDefItemOffset(intclassIndex){if(classIndex<0||classIndex>=classCount){thrownewInvalidItemIndex(classIndex,"Classindexoutofbounds:%d",classIndex);}returnclassStartOffset+classIndex*ClassDefItem.ITEM_SIZE;}



很简单,就是从dex文件中找到 指定class的索引地址,dex文件中表示class的其实是个比较复杂的结构,需要好好理解一下,休息一下见下篇继续