官方文檔對于dex中的class數(shù)據(jù)結(jié)構(gòu)表示如下:
創(chuàng)新互聯(lián)從2013年成立,先為江華等服務(wù)建站,江華等地企業(yè),進行企業(yè)商務(wù)咨詢服務(wù)。為江華企業(yè)網(wǎng)站制作PC+手機+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。基本上就是這樣了,再看
public DexBackedClassDef(@Nonnull DexBackedDexFile dexFile, int classDefOffset) { this.dexFile = dexFile; //classDefOffset 是這個類結(jié)構(gòu)體在dex文件中的偏移地址。 this.classDefOffset = classDefOffset; //獲取類的數(shù)據(jù)部分的偏移 int classDataOffset = dexFile.readSmallUint(classDefOffset + ClassDefItem.CLASS_DATA_OFFSET); if (classDataOffset == 0) { staticFieldsOffset = -1; staticFieldCount = 0; instanceFieldCount = 0; directMethodCount = 0; virtualMethodCount = 0; } else { //如果不等于0,則要讀取各種變量,方法的個數(shù) 保存到這個類的私有成員變量中,等到實際解析的時候 //再來使用 DexReader reader = dexFile.readerAt(classDataOffset); staticFieldCount = reader.readSmallUleb128(); instanceFieldCount = reader.readSmallUleb128(); directMethodCount = reader.readSmallUleb128(); virtualMethodCount = reader.readSmallUleb128(); staticFieldsOffset = reader.getOffset(); } }
這里再列出來 dex文件關(guān)于 class類數(shù)據(jù)的格式說明,以方便讀者對代碼的理解
ok 我們再回到
List<? extends ClassDef> classDefs = Ordering.natural().sortedCopy(dexFile.getClasses());
這條語句,通過對里面機制的了解,已經(jīng)知道,其實這條語句完成以后,
List<? extends ClassDef> classDefs 這個變量已經(jīng)保存了 dex文件中關(guān)于類的各種信息。
故事2
return disassembleClass(classDef, fileNameHandler, options); private static boolean disassembleClass(ClassDef classDef, ClassFileNameHandler fileNameHandler, baksmaliOptions options) { //獲取類名 String classDescriptor = classDef.getType(); //validate that the descriptor is formatted like we expect if (classDescriptor.charAt(0) != 'L' || classDescriptor.charAt(classDescriptor.length()-1) != ';') { System.err.println("Unrecognized class descriptor - " + classDescriptor + " - skipping class"); return false; } //生成相應(yīng)要輸入smali文件的位置信息 File smaliFile = fileNameHandler.getUniqueFilenameForClass(classDescriptor); //create and initialize the top level string template ClassDefinition classDefinition = new ClassDefinition(options, classDef); // 重點1 //write the disassembly Writer writer = null; try { File smaliParent = smaliFile.getParentFile(); if (!smaliParent.exists()) { if (!smaliParent.mkdirs()) { // check again, it's likely it was created in a different thread if (!smaliParent.exists()) { System.err.println("Unable to create directory " + smaliParent.toString() + " - skipping class"); return false; } } } if (!smaliFile.exists()){ if (!smaliFile.createNewFile()) { System.err.println("Unable to create file " + smaliFile.toString() + " - skipping class"); return false; } } BufferedWriter bufWriter = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(smaliFile), "UTF8")); writer = new IndentingWriter(bufWriter); classDefinition.writeTo((IndentingWriter)writer); //重點2 } catch (Exception ex) { System.err.println("\n\nError occurred while disassembling class " + classDescriptor.replace('/', '.') + " - skipping class"); ex.printStackTrace(); // noinspection ResultOfMethodCallIgnored smaliFile.delete(); return false; } finally { if (writer != null) { try { writer.close(); } catch (Throwable ex) { System.err.println("\n\nError occurred while closing file " + smaliFile.toString()); ex.printStackTrace(); } } } return true; }
這個函數(shù)有兩個重點
ClassDefinition classDefinition = new ClassDefinition(options, classDef); // 重點1
classDefinition.writeTo((IndentingWriter)writer); //重點2
其實這兩個重點調(diào)用完成以后,整個smali文件就已經(jīng)生成了,所以我們就順著前面的腳步跟進去,看看這兩個重點到底做了什么事情
1 構(gòu)造函數(shù)將 classdef 傳入到 ClassDefinition 這個類中
public ClassDefinition(@Nonnull baksmaliOptions options, @Nonnull ClassDef classDef) { this.options = options; this.classDef = classDef; fieldsSetInStaticConstructor = findFieldsSetInStaticConstructor(); }
2 writeTo 將生成smali文件的各個元素給寫入到 IndentingWriter writer 代表的smali文件中。
public void writeTo(IndentingWriter writer) throws IOException { 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 源碼的分析,大體框架已經(jīng)完成。
當然還有很多細節(jié)了,其實主要涉及在 public void writeTo(IndentingWriter writer) 這個函數(shù)里面
我們舉一個比較復(fù)雜的例子 Set<String> directMethods = writeDirectMethods(writer); 來代碼跟蹤一邊,看看里面的做了什么,
基本上就搞清楚 里面做的事情了
private Set<String> writeDirectMethods(IndentingWriter writer) throws IOException { boolean wroteHeader = false; Set<String> writtenMethods = new HashSet<String>(); Iterable<? extends Method> directMethods; if (classDef instanceof DexBackedClassDef) { directMethods = ((DexBackedClassDef)classDef).getDirectMethods(false); //重點1 } else { directMethods = classDef.getDirectMethods(); } for (Method method: directMethods) { if (!wroteHeader) { writer.write("\n\n"); writer.write("# direct methods"); wroteHeader = true; } writer.write('\n'); ... MethodImplementation methodImpl = method.getImplementation(); if (methodImpl == null) { MethodDefinition.writeEmptyMethodTo(methodWriter, method, options); } else { MethodDefinition methodDefinition = new MethodDefinition(this, method, methodImpl); //重點2 methodDefinition.writeTo(methodWriter); //重點3 } } return writtenMethods; } 這個函數(shù)有三個重點 directMethods = ((DexBackedClassDef)classDef).getDirectMethods(false); //重點1 public Iterable<? extends DexBackedMethod> getDirectMethods(final boolean skipDuplicates) { if (directMethodCount > 0) { //首先得到這個類中的 direct 方法的在dex文件中的偏移地址 DexReader reader = dexFile.readerAt(getDirectMethodsOffset()); final AnnotationsDirectory annotationsDirectory = getAnnotationsDirectory(); final int methodsStartOffset = reader.getOffset(); //返回 new Iterable<DexBackedMethod>()給上層的調(diào)用函數(shù),并且繼承實現(xiàn)了 //iterator() 這個函數(shù) return new Iterable<DexBackedMethod>() { @Nonnull @Override public Iterator<DexBackedMethod> iterator() { final AnnotationsDirectory.AnnotationIterator methodAnnotationIterator = annotationsDirectory.getMethodAnnotationIterator(); final AnnotationsDirectory.AnnotationIterator parameterAnnotationIterator = annotationsDirectory.getParameterAnnotationIterator(); //返回了 new VariableSizeLookaheadIterator<DexBackedMethod>(dexFile, methodsStartOffset) //這個對象,里面繼承實現(xiàn)了 readNextItem 這個方法,這個方法通過傳入的 方法開始偏移,從 //dex文件中 返回 DexBackedMethod 這個對象給上層 return new VariableSizeLookaheadIterator<DexBackedMethod>(dexFile, methodsStartOffset) { private int count; @Nullable private MethodReference previousMethod; private int previousIndex; @Nullable @Override protected DexBackedMethod readNextItem(@Nonnull DexReader reader) { while (true) { if (++count > directMethodCount) { virtualMethodsOffset = reader.getOffset(); return null; } // 生成一個 method的對象 DexBackedMethod item = new DexBackedMethod(reader, DexBackedClassDef.this, previousIndex, methodAnnotationIterator, parameterAnnotationIterator); //重點1 MethodReference currentMethod = previousMethod; MethodReference nextMethod = ImmutableMethodReference.of(item); previousMethod = nextMethod; previousIndex = item.methodIndex; if (skipDuplicates && currentMethod != null && currentMethod.equals(nextMethod)) { continue; } return item; } } }; } }; } else { if (directMethodsOffset > 0) { virtualMethodsOffset = directMethodsOffset; } return ImmutableSet.of(); } }
關(guān)于重點1
public DexBackedMethod(@Nonnull DexReader reader, @Nonnull DexBackedClassDef classDef, int previousMethodIndex, @Nonnull AnnotationsDirectory.AnnotationIterator methodAnnotationIterator, @Nonnull AnnotationsDirectory.AnnotationIterator paramaterAnnotationIterator) { this.dexFile = reader.dexBuf; this.classDef = classDef; // large values may be used for the index delta, which cause the cumulative index to overflow upon // addition, effectively allowing out of order entries. int methodIndexDiff = reader.readLargeUleb128(); this.methodIndex = methodIndexDiff + previousMethodIndex; this.accessFlags = reader.readSmallUleb128(); this.codeOffset = reader.readSmallUleb128(); this.methodAnnotationSetOffset = methodAnnotationIterator.seekTo(methodIndex); this.parameterAnnotationSetListOffset = paramaterAnnotationIterator.seekTo(methodIndex); }
根據(jù)官方文檔,encoded_method Format 這種格式的數(shù)據(jù)結(jié)構(gòu)
其實這個構(gòu)造函數(shù)就是將 數(shù)據(jù)結(jié)構(gòu)中要求的索引從dex文件中找到,保存到自己的私有成員變量當中
重點2
MethodImplementation methodImpl = method.getImplementation(); public DexBackedMethodImplementation getImplementation() { if (codeOffset > 0) { return new DexBackedMethodImplementation(dexFile, this, codeOffset); } return null; }
重點3
MethodDefinition methodDefinition = new MethodDefinition(this, method, methodImpl); public MethodDefinition(@Nonnull ClassDefinition classDef, @Nonnull Method method, @Nonnull MethodImplementation methodImpl) { this.classDef = classDef; this.method = method; this.methodImpl = methodImpl; //這里傳入的method其實是 DexBackedMethod try { //TODO: what about try/catch blocks inside the dead code? those will need to be commented out too. ugh. //methodImpl.getInstructions() 其實是調(diào)用的是 public Iterable<? extends Instruction> getInstructions() // 在 DexBackedMethodImplementation 這個類中實現(xiàn)的,主要是根據(jù)前面的偏移從dex文件中讀取相應(yīng)的指令數(shù)據(jù) //放在指令列表中 instructions = ImmutableList.copyOf(methodImpl.getInstructions()); methodParameters = ImmutableList.copyOf(method.getParameters()); packedSwitchMap = new SparseIntArray(0); sparseSwitchMap = new SparseIntArray(0); instructionOffsetMap = new InstructionOffsetMap(instructions); for (int i=0; i<instructions.size(); i++) { Instruction instruction = instructions.get(i); //處理switch case 指令 Opcode opcode = instruction.getOpcode(); if (opcode == Opcode.PACKED_SWITCH) { boolean valid = true; int codeOffset = instructionOffsetMap.getInstructionCodeOffset(i); int targetOffset = codeOffset + ((OffsetInstruction)instruction).getCodeOffset(); try { targetOffset = findSwitchPayload(targetOffset, Opcode.PACKED_SWITCH_PAYLOAD); } catch (InvalidSwitchPayload ex) { valid = false; } if (valid) { packedSwitchMap.append(targetOffset, codeOffset); } } else if (opcode == Opcode.SPARSE_SWITCH) { boolean valid = true; int codeOffset = instructionOffsetMap.getInstructionCodeOffset(i); int targetOffset = codeOffset + ((OffsetInstruction)instruction).getCodeOffset(); try { targetOffset = findSwitchPayload(targetOffset, Opcode.SPARSE_SWITCH_PAYLOAD); } catch (InvalidSwitchPayload ex) { valid = false; // The offset to the payload instruction was invalid. Nothing to do, except that we won't // add this instruction to the map. } if (valid) { sparseSwitchMap.append(targetOffset, codeOffset); } } } }catch (Exception ex) { String methodString; try { methodString = ReferenceUtil.getMethodDescriptor(method); } catch (Exception ex2) { throw ExceptionWithContext.withContext(ex, "Error while processing method"); } throw ExceptionWithContext.withContext(ex, "Error while processing method %s", methodString); } }
重點4
methodDefinition.writeTo(methodWriter);
這個函數(shù)其實也是十分復(fù)雜的一個函數(shù),但是總體的思路,其實也是根據(jù)前面?zhèn)鬟f過來的數(shù)據(jù),主要是索引值和偏移地址,來
將method里面的數(shù)據(jù)寫回到 smali文件中去
由于篇幅的關(guān)系,這里就不在那么細節(jié)的分析 method的writeTo了,在看 method的writeTo方法的時候,需要
仔細理解一下 parameterRegisterCount 這個局部變量的賦值情況。總體來說java代碼中非靜態(tài)方法會自動為該函數(shù)加入一個參數(shù)
其實這個參數(shù)就相當于 this指針的作用,由于dalvik虛擬機中的寄存器都是32位的,所以對于 J和D也就是 long和Double類型的
其實每個參數(shù)是用兩個寄存器表示的。
從下面的代碼也能看出來
for (MethodParameter parameter: methodParameters) { String type = parameter.getType(); writer.write(type); parameterRegisterCount++; if (TypeUtils.isWideType(type)) { parameterRegisterCount++; } }
理解參數(shù)占用的寄存器數(shù)量是如何計算出來以后,就能很好的理解smali代碼中關(guān)于p寄存器和v寄存器表示的規(guī)則了,并且為后續(xù)編寫dex文件為函數(shù)添加寄存器的功能打下基礎(chǔ)。
總之,baksmali對于寫方法來說,基本上是最復(fù)雜的操作了,在理解了寫入方法的操作以后,前面的操作的理解基本上應(yīng)該不成問題。
到這里,基本上已經(jīng)將baksmali的框架分析完成了。下一步 我們需要分析 smali框架了源代碼了
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機、免備案服務(wù)器”等云主機租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。
分享文章:baksmali和smali源碼分析(五)-創(chuàng)新互聯(lián)
瀏覽路徑:http://bm7419.com/article26/cdipcg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供企業(yè)網(wǎng)站制作、網(wǎng)站設(shè)計公司、動態(tài)網(wǎng)站、品牌網(wǎng)站制作、全網(wǎng)營銷推廣、外貿(mào)網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容