baksmali和smali源码分析(五)-创新互联

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

创新互联从2013年成立,先为江华等服务建站,江华等地企业,进行企业商务咨询服务。为江华企业网站制作PC+手机+微官网三网同步一站式服务解决您的所有建站问题。

baksmali和smali源码分析(五)

基本上就是这样了,再看

    public DexBackedClassDef(@Nonnull DexBackedDexFile dexFile,
                             int classDefOffset) {
        this.dexFile = dexFile;
        
        //classDefOffset 是这个类结构体在dex文件中的偏移地址。
        this.classDefOffset = classDefOffset;

				//获取类的数据部分的偏移
        int classDataOffset = dexFile.readSmallUint(classDefOffset + ClassDefItem.CLASS_DATA_OFFSET);
        if (classDataOffset == 0) {
            staticFieldsOffset = -1;
            staticFieldCount = 0;
            instanceFieldCount = 0;
            directMethodCount = 0;
            virtualMethodCount = 0;
        } else {
        
            //如果不等于0,则要读取各种变量,方法的个数 保存到这个类的私有成员变量中,等到实际解析的时候
            //再来使用
            DexReader reader = dexFile.readerAt(classDataOffset);
            staticFieldCount = reader.readSmallUleb128();
            instanceFieldCount = reader.readSmallUleb128();
            directMethodCount = reader.readSmallUleb128();
            virtualMethodCount = reader.readSmallUleb128();
            staticFieldsOffset = reader.getOffset();
        }

    }

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

baksmali和smali源码分析(五)

   ok 我们再回到

  List classDefs = Ordering.natural().sortedCopy(dexFile.getClasses());

 

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

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

   故事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;
        }

				//生成相应要输入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;
    }  	

  这个函数有两个重点

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

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

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

  1 构造函数将 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 staticFields = writeStaticFields(writer);
        writeInstanceFields(writer, staticFields);
        Set directMethods = writeDirectMethods(writer);
        writeVirtualMethods(writer, directMethods);
    }

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

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

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

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

  private Set writeDirectMethods(IndentingWriter writer) throws IOException {
        boolean wroteHeader = false;
        Set writtenMethods = new HashSet();
        Iterable 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;
    }    
    
    
    这个函数有三个重点
    
    directMethods = ((DexBackedClassDef)classDef).getDirectMethods(false);           //重点1 
    
    
    public Iterable 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()给上层的调用函数,并且继承实现了
//iterator() 这个函数
            return new Iterable() {
                @Nonnull
                @Override
                public Iterator iterator() {
                    final AnnotationsDirectory.AnnotationIterator methodAnnotationIterator =
                            annotationsDirectory.getMethodAnnotationIterator();
                    final AnnotationsDirectory.AnnotationIterator parameterAnnotationIterator =
                            annotationsDirectory.getParameterAnnotationIterator();
//返回了 new VariableSizeLookaheadIterator(dexFile, methodsStartOffset)
//这个对象,里面继承实现了 readNextItem 这个方法,这个方法通过传入的 方法开始偏移,从
//dex文件中 返回 DexBackedMethod 这个对象给上层
                    return new VariableSizeLookaheadIterator(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();
        }
    }

  关于重点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);
    }

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

   baksmali和smali源码分析(五)

  其实这个构造函数就是将 数据结构中要求的索引从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() 其实是调用的是 public Iterable getInstructions()
// 在 DexBackedMethodImplementation 这个类中实现的,主要是根据前面的偏移从dex文件中读取相应的指令数据
//放在指令列表中
            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

  重点4

  methodDefinition.writeTo(methodWriter);

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

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

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

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

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

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

  从下面的代码也能看出来

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

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

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

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

另外有需要云服务器可以了解下创新互联scvps.cn,海内外云服务器15元起步,三天无理由+7*72小时售后在线,公司持有idc许可证,提供“云服务器、裸金属服务器、高防服务器、香港服务器、美国服务器、虚拟主机、免备案服务器”等云主机租用服务以及企业上云的综合解决方案,具有“安全稳定、简单易用、服务可用性高、性价比高”等特点与优势,专为企业上云打造定制,能够满足用户丰富、多元化的应用场景需求。


本文名称:baksmali和smali源码分析(五)-创新互联
当前链接:http://hbruida.cn/article/cdipcg.html