溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

baksmali和smali源碼分析(五)

發(fā)布時(shí)間:2020-06-29 20:03:56 來(lái)源:網(wǎng)絡(luò) 閱讀:1959 作者:sunzeduo 欄目:開(kāi)發(fā)技術(shù)

官方文檔對(duì)于dex中的class數(shù)據(jù)結(jié)構(gòu)表示如下:



baksmali和smali源碼分析(五)



基本上就是這樣了,再看

    public DexBackedClassDef(@Nonnull DexBackedDexFile dexFile,
                             int classDefOffset) {
        this.dexFile = dexFile;
        
        //classDefOffset 是這個(gè)類結(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,則要讀取各種變量,方法的個(gè)數(shù) 保存到這個(gè)類的私有成員變量中,等到實(shí)際解析的時(shí)候
            //再來(lái)使用
            DexReader reader = dexFile.readerAt(classDataOffset);
            staticFieldCount = reader.readSmallUleb128();
            instanceFieldCount = reader.readSmallUleb128();
            directMethodCount = reader.readSmallUleb128();
            virtualMethodCount = reader.readSmallUleb128();
            staticFieldsOffset = reader.getOffset();
        }

    }


這里再列出來(lái) dex文件關(guān)于 class類數(shù)據(jù)的格式說(shuō)明,以方便讀者對(duì)代碼的理解


baksmali和smali源碼分析(五)


   ok 我們?cè)倩氐?/p>

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

   

  這條語(yǔ)句,通過(guò)對(duì)里面機(jī)制的了解,已經(jīng)知道,其實(shí)這條語(yǔ)句完成以后, 

  List<? extends ClassDef> classDefs  這個(gè)變量已經(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);  // 重點(diǎn)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);    //重點(diǎn)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;
    }  	



    這個(gè)函數(shù)有兩個(gè)重點(diǎn)

    

    ClassDefinition classDefinition = new ClassDefinition(options, classDef);  // 重點(diǎn)1 

    

    classDefinition.writeTo((IndentingWriter)writer);    //重點(diǎn)2 


    其實(shí)這兩個(gè)重點(diǎn)調(diào)用完成以后,整個(gè)smali文件就已經(jīng)生成了,所以我們就順著前面的腳步跟進(jìn)去,看看這兩個(gè)重點(diǎn)到底做了什么事情

    

    1 構(gòu)造函數(shù)將 classdef 傳入到 ClassDefinition 這個(gè)類中

 

   public ClassDefinition(@Nonnull baksmaliOptions options, @Nonnull ClassDef classDef) {
        this.options = options;
        this.classDef = classDef;
        fieldsSetInStaticConstructor = findFieldsSetInStaticConstructor();
    }

    

    2 writeTo 將生成smali文件的各個(gè)元素給寫(xiě)入到 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)完成。



    當(dāng)然還有很多細(xì)節(jié)了,其實(shí)主要涉及在 public void writeTo(IndentingWriter writer) 這個(gè)函數(shù)里面

    

    我們舉一個(gè)比較復(fù)雜的例子 Set<String> directMethods = writeDirectMethods(writer); 來(lái)代碼跟蹤一邊,看看里面的做了什么,

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

    

 

  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);           //重點(diǎn)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);  //重點(diǎn)2 
                methodDefinition.writeTo(methodWriter);           //重點(diǎn)3 
            }
        }
        return writtenMethods;
    }    
    
    
    這個(gè)函數(shù)有三個(gè)重點(diǎn)
    
    directMethods = ((DexBackedClassDef)classDef).getDirectMethods(false);           //重點(diǎn)1 
    
    
    public Iterable<? extends DexBackedMethod> getDirectMethods(final boolean skipDuplicates) {
        if (directMethodCount > 0) {
        
        //首先得到這個(gè)類中的 direct 方法的在dex文件中的偏移地址
            DexReader reader = dexFile.readerAt(getDirectMethodsOffset());
            final AnnotationsDirectory annotationsDirectory = getAnnotationsDirectory();
            final int methodsStartOffset = reader.getOffset();
//返回 new Iterable<DexBackedMethod>()給上層的調(diào)用函數(shù),并且繼承實(shí)現(xiàn)了
//iterator() 這個(gè)函數(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)
//這個(gè)對(duì)象,里面繼承實(shí)現(xiàn)了 readNextItem 這個(gè)方法,這個(gè)方法通過(guò)傳入的 方法開(kāi)始偏移,從
//dex文件中 返回 DexBackedMethod 這個(gè)對(duì)象給上層
                    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;
                                }
// 生成一個(gè) method的對(duì)象
                                DexBackedMethod item = new DexBackedMethod(reader, DexBackedClassDef.this,
                                        previousIndex, methodAnnotationIterator, parameterAnnotationIterator);   //重點(diǎn)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)于重點(diǎ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)

    baksmali和smali源碼分析(五)


    

    其實(shí)這個(gè)構(gòu)造函數(shù)就是將 數(shù)據(jù)結(jié)構(gòu)中要求的索引從dex文件中找到,保存到自己的私有成員變量當(dāng)中

    

    重點(diǎn)2 

 

  MethodImplementation methodImpl = method.getImplementation();
     public DexBackedMethodImplementation getImplementation() {
        if (codeOffset > 0) {
            return new DexBackedMethodImplementation(dexFile, this, codeOffset);
        }
        return null;
    }

    

    

    重點(diǎn)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其實(shí)是 DexBackedMethod
        try {
            //TODO: what about try/catch blocks inside the dead code? those will need to be commented out too. ugh.
//methodImpl.getInstructions() 其實(shí)是調(diào)用的是 public Iterable<? extends Instruction> getInstructions()
// 在 DexBackedMethodImplementation 這個(gè)類中實(shí)現(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);
        }
    }

    

    

    重點(diǎn)4 

    

    methodDefinition.writeTo(methodWriter);

    這個(gè)函數(shù)其實(shí)也是十分復(fù)雜的一個(gè)函數(shù),但是總體的思路,其實(shí)也是根據(jù)前面?zhèn)鬟f過(guò)來(lái)的數(shù)據(jù),主要是索引值和偏移地址,來(lái)

    將method里面的數(shù)據(jù)寫(xiě)回到 smali文件中去

    

    

    由于篇幅的關(guān)系,這里就不在那么細(xì)節(jié)的分析 method的writeTo了,在看 method的writeTo方法的時(shí)候,需要

    仔細(xì)理解一下 parameterRegisterCount 這個(gè)局部變量的賦值情況。總體來(lái)說(shuō)java代碼中非靜態(tài)方法會(huì)自動(dòng)為該函數(shù)加入一個(gè)參數(shù)

    其實(shí)這個(gè)參數(shù)就相當(dāng)于 this指針的作用,由于dalvik虛擬機(jī)中的寄存器都是32位的,所以對(duì)于 J和D也就是 long和Double類型的

    其實(shí)每個(gè)參數(shù)是用兩個(gè)寄存器表示的。

    

    從下面的代碼也能看出來(lái)

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

    

    理解參數(shù)占用的寄存器數(shù)量是如何計(jì)算出來(lái)以后,就能很好的理解smali代碼中關(guān)于p寄存器和v寄存器表示的規(guī)則了,并且為后續(xù)編寫(xiě)dex文件為函數(shù)添加寄存器的功能打下基礎(chǔ)。

    

    總之,baksmali對(duì)于寫(xiě)方法來(lái)說(shuō),基本上是最復(fù)雜的操作了,在理解了寫(xiě)入方法的操作以后,前面的操作的理解基本上應(yīng)該不成問(wèn)題。

    

    

    到這里,基本上已經(jīng)將baksmali的框架分析完成了。下一步 我們需要分析 smali框架了源代碼了


向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI