官方文档对于dex中的class数据结构表示如下:





基本上就是这样了,再看

publicDexBackedClassDef(@NonnullDexBackedDexFiledexFile,intclassDefOffset){this.dexFile=dexFile;//classDefOffset是这个类结构体在dex文件中的偏移地址。this.classDefOffset=classDefOffset;//获取类的数据部分的偏移intclassDataOffset=dexFile.readSmallUint(classDefOffset+ClassDefItem.CLASS_DATA_OFFSET);if(classDataOffset==0){staticFieldsOffset=-1;staticFieldCount=0;instanceFieldCount=0;directMethodCount=0;virtualMethodCount=0;}else{//如果不等于0,则要读取各种变量,方法的个数保存到这个类的私有成员变量中,等到实际解析的时候//再来使用DexReaderreader=dexFile.readerAt(classDataOffset);staticFieldCount=reader.readSmallUleb128();instanceFieldCount=reader.readSmallUleb128();directMethodCount=reader.readSmallUleb128();virtualMethodCount=reader.readSmallUleb128();staticFieldsOffset=reader.getOffset();}}


这里再列出来 dex文件关于 class类数据的格式说明,以方便读者对代码的理解



ok 我们再回到

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

这条语句,通过对里面机制的了解,已经知道,其实这条语句完成以后,

List<? extends ClassDef> classDefs 这个变量已经保存了 dex文件中关于类的各种信息。



故事2

returndisassembleClass(classDef,fileNameHandler,options);privatestaticbooleandisassembleClass(ClassDefclassDef,ClassFileNameHandlerfileNameHandler,baksmaliOptionsoptions){//获取类名StringclassDescriptor=classDef.getType();//validatethatthedescriptorisformattedlikeweexpectif(classDescriptor.charAt(0)!='L'||classDescriptor.charAt(classDescriptor.length()-1)!=';'){System.err.println("Unrecognizedclassdescriptor-"+classDescriptor+"-skippingclass");returnfalse;}//生成相应要输入smali文件的位置信息FilesmaliFile=fileNameHandler.getUniqueFilenameForClass(classDescriptor);//createandinitializethetoplevelstringtemplateClassDefinitionclassDefinition=newClassDefinition(options,classDef);//重点1//writethedisassemblyWriterwriter=null;try{FilesmaliParent=smaliFile.getParentFile();if(!smaliParent.exists()){if(!smaliParent.mkdirs()){//checkagain,it'slikelyitwascreatedinadifferentthreadif(!smaliParent.exists()){System.err.println("Unabletocreatedirectory"+smaliParent.toString()+"-skippingclass");returnfalse;}}}if(!smaliFile.exists()){if(!smaliFile.createNewFile()){System.err.println("Unabletocreatefile"+smaliFile.toString()+"-skippingclass");returnfalse;}}BufferedWriterbufWriter=newBufferedWriter(newOutputStreamWriter(newFileOutputStream(smaliFile),"UTF8"));writer=newIndentingWriter(bufWriter);classDefinition.writeTo((IndentingWriter)writer);//重点2}catch(Exceptionex){System.err.println("\n\nErroroccurredwhiledisassemblingclass"+classDescriptor.replace('/','.')+"-skippingclass");ex.printStackTrace();//noinspectionResultOfMethodCallIgnoredsmaliFile.delete();returnfalse;}finally{if(writer!=null){try{writer.close();}catch(Throwableex){System.err.println("\n\nErroroccurredwhileclosingfile"+smaliFile.toString());ex.printStackTrace();}}}returntrue;}



这个函数有两个重点

ClassDefinition classDefinition = new ClassDefinition(options, classDef); // 重点1

classDefinition.writeTo((IndentingWriter)writer); //重点2


其实这两个重点调用完成以后,整个smali文件就已经生成了,所以我们就顺着前面的脚步跟进去,看看这两个重点到底做了什么事情

1 构造函数将 classdef 传入到 ClassDefinition 这个类中

publicClassDefinition(@NonnullbaksmaliOptionsoptions,@NonnullClassDefclassDef){this.options=options;this.classDef=classDef;fieldsSetInStaticConstructor=findFieldsSetInStaticConstructor();}

2 writeTo 将生成smali文件的各个元素给写入到 IndentingWriter writer 代表的smali文件中。

publicvoidwriteTo(IndentingWriterwriter)throwsIOException{writeClass(writer);writeSuper(writer);writeSourceFile(writer);writeInterfaces(writer);writeAnnotations(writer);Set<String>staticFields=writeStaticFields(writer);writeInstanceFields(writer,staticFields);Set<String>directMethods=writeDirectMethods(writer);writeVirtualMethods(writer,directMethods);}

到这里baksmali 源码的分析,大体框架已经完成。



当然还有很多细节了,其实主要涉及在 public void writeTo(IndentingWriter writer) 这个函数里面

我们举一个比较复杂的例子 Set<String> directMethods = writeDirectMethods(writer); 来代码跟踪一边,看看里面的做了什么,

基本上就搞清楚 里面做的事情了

privateSet<String>writeDirectMethods(IndentingWriterwriter)throwsIOException{booleanwroteHeader=false;Set<String>writtenMethods=newHashSet<String>();Iterable<?extendsMethod>directMethods;if(classDefinstanceofDexBackedClassDef){directMethods=((DexBackedClassDef)classDef).getDirectMethods(false);//重点1}else{directMethods=classDef.getDirectMethods();}for(Methodmethod:directMethods){if(!wroteHeader){writer.write("\n\n");writer.write("#directmethods");wroteHeader=true;}writer.write('\n');...MethodImplementationmethodImpl=method.getImplementation();if(methodImpl==null){MethodDefinition.writeEmptyMethodTo(methodWriter,method,options);}else{MethodDefinitionmethodDefinition=newMethodDefinition(this,method,methodImpl);//重点2methodDefinition.writeTo(methodWriter);//重点3}}returnwrittenMethods;}这个函数有三个重点directMethods=((DexBackedClassDef)classDef).getDirectMethods(false);//重点1publicIterable<?extendsDexBackedMethod>getDirectMethods(finalbooleanskipDuplicates){if(directMethodCount>0){//首先得到这个类中的direct方法的在dex文件中的偏移地址DexReaderreader=dexFile.readerAt(getDirectMethodsOffset());finalAnnotationsDirectoryannotationsDirectory=getAnnotationsDirectory();finalintmethodsStartOffset=reader.getOffset();//返回newIterable<DexBackedMethod>()给上层的调用函数,并且继承实现了//iterator()这个函数returnnewIterable<DexBackedMethod>(){@Nonnull@OverridepublicIterator<DexBackedMethod>iterator(){finalAnnotationsDirectory.AnnotationIteratormethodAnnotationIterator=annotationsDirectory.getMethodAnnotationIterator();finalAnnotationsDirectory.AnnotationIteratorparameterAnnotationIterator=annotationsDirectory.getParameterAnnotationIterator();//返回了newVariableSizeLookaheadIterator<DexBackedMethod>(dexFile,methodsStartOffset)//这个对象,里面继承实现了readNextItem这个方法,这个方法通过传入的方法开始偏移,从//dex文件中返回DexBackedMethod这个对象给上层returnnewVariableSizeLookaheadIterator<DexBackedMethod>(dexFile,methodsStartOffset){privateintcount;@NullableprivateMethodReferencepreviousMethod;privateintpreviousIndex;@Nullable@OverrideprotectedDexBackedMethodreadNextItem(@NonnullDexReaderreader){while(true){if(++count>directMethodCount){virtualMethodsOffset=reader.getOffset();returnnull;}//生成一个method的对象DexBackedMethoditem=newDexBackedMethod(reader,DexBackedClassDef.this,previousIndex,methodAnnotationIterator,parameterAnnotationIterator);//重点1MethodReferencecurrentMethod=previousMethod;MethodReferencenextMethod=ImmutableMethodReference.of(item);previousMethod=nextMethod;previousIndex=item.methodIndex;if(skipDuplicates&&currentMethod!=null&&currentMethod.equals(nextMethod)){continue;}returnitem;}}};}};}else{if(directMethodsOffset>0){virtualMethodsOffset=directMethodsOffset;}returnImmutableSet.of();}}

关于重点1

publicDexBackedMethod(@NonnullDexReaderreader,@NonnullDexBackedClassDefclassDef,intpreviousMethodIndex,@NonnullAnnotationsDirectory.AnnotationIteratormethodAnnotationIterator,@NonnullAnnotationsDirectory.AnnotationIteratorparamaterAnnotationIterator){this.dexFile=reader.dexBuf;this.classDef=classDef;//largevaluesmaybeusedfortheindexdelta,whichcausethecumulativeindextooverflowupon//addition,effectivelyallowingoutoforderentries.intmethodIndexDiff=reader.readLargeUleb128();this.methodIndex=methodIndexDiff+previousMethodIndex;this.accessFlags=reader.readSmallUleb128();this.codeOffset=reader.readSmallUleb128();this.methodAnnotationSetOffset=methodAnnotationIterator.seekTo(methodIndex);this.parameterAnnotationSetListOffset=paramaterAnnotationIterator.seekTo(methodIndex);}

根据官方文档,encoded_method Format 这种格式的数据结构


其实这个构造函数就是将 数据结构中要求的索引从dex文件中找到,保存到自己的私有成员变量当中

重点2

MethodImplementationmethodImpl=method.getImplementation();publicDexBackedMethodImplementationgetImplementation(){if(codeOffset>0){returnnewDexBackedMethodImplementation(dexFile,this,codeOffset);}returnnull;}

重点3

MethodDefinitionmethodDefinition=newMethodDefinition(this,method,methodImpl);publicMethodDefinition(@NonnullClassDefinitionclassDef,@NonnullMethodmethod,@NonnullMethodImplementationmethodImpl){this.classDef=classDef;this.method=method;this.methodImpl=methodImpl;//这里传入的method其实是DexBackedMethodtry{//TODO:whatabouttry/catchblocksinsidethedeadcode?thosewillneedtobecommentedouttoo.ugh.//methodImpl.getInstructions()其实是调用的是publicIterable<?extendsInstruction>getInstructions()//在DexBackedMethodImplementation这个类中实现的,主要是根据前面的偏移从dex文件中读取相应的指令数据//放在指令列表中instructions=ImmutableList.copyOf(methodImpl.getInstructions());methodParameters=ImmutableList.copyOf(method.getParameters());packedSwitchMap=newSparseIntArray(0);sparseSwitchMap=newSparseIntArray(0);instructionOffsetMap=newInstructionOffsetMap(instructions);for(inti=0;i<instructions.size();i++){Instructioninstruction=instructions.get(i);//处理switchcase指令Opcodeopcode=instruction.getOpcode();if(opcode==Opcode.PACKED_SWITCH){booleanvalid=true;intcodeOffset=instructionOffsetMap.getInstructionCodeOffset(i);inttargetOffset=codeOffset+((OffsetInstruction)instruction).getCodeOffset();try{targetOffset=findSwitchPayload(targetOffset,Opcode.PACKED_SWITCH_PAYLOAD);}catch(InvalidSwitchPayloadex){valid=false;}if(valid){packedSwitchMap.append(targetOffset,codeOffset);}}elseif(opcode==Opcode.SPARSE_SWITCH){booleanvalid=true;intcodeOffset=instructionOffsetMap.getInstructionCodeOffset(i);inttargetOffset=codeOffset+((OffsetInstruction)instruction).getCodeOffset();try{targetOffset=findSwitchPayload(targetOffset,Opcode.SPARSE_SWITCH_PAYLOAD);}catch(InvalidSwitchPayloadex){valid=false;//Theoffsettothepayloadinstructionwasinvalid.Nothingtodo,exceptthatwewon't//addthisinstructiontothemap.}if(valid){sparseSwitchMap.append(targetOffset,codeOffset);}}}}catch(Exceptionex){StringmethodString;try{methodString=ReferenceUtil.getMethodDescriptor(method);}catch(Exceptionex2){throwExceptionWithContext.withContext(ex,"Errorwhileprocessingmethod");}throwExceptionWithContext.withContext(ex,"Errorwhileprocessingmethod%s",methodString);}}

重点4

methodDefinition.writeTo(methodWriter);

这个函数其实也是十分复杂的一个函数,但是总体的思路,其实也是根据前面传递过来的数据,主要是索引值和偏移地址,来

将method里面的数据写回到 smali文件中去

由于篇幅的关系,这里就不在那么细节的分析 method的writeTo了,在看 method的writeTo方法的时候,需要

仔细理解一下 parameterRegisterCount 这个局部变量的赋值情况。总体来说java代码中非静态方法会自动为该函数加入一个参数

其实这个参数就相当于 this指针的作用,由于dalvik虚拟机中的寄存器都是32位的,所以对于 J和D也就是 long和Double类型的

其实每个参数是用两个寄存器表示的。

从下面的代码也能看出来

for(MethodParameterparameter:methodParameters){Stringtype=parameter.getType();writer.write(type);parameterRegisterCount++;if(TypeUtils.isWideType(type)){parameterRegisterCount++;}}

理解参数占用的寄存器数量是如何计算出来以后,就能很好的理解smali代码中关于p寄存器和v寄存器表示的规则了,并且为后续编写dex文件为函数添加寄存器的功能打下基础。

总之,baksmali对于写方法来说,基本上是最复杂的操作了,在理解了写入方法的操作以后,前面的操作的理解基本上应该不成问题。

到这里,基本上已经将baksmali的框架分析完成了。下一步 我们需要分析 smali框架了源代码了