baksmali和smali源码分析(四)

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

    public static void main(String[] args) throws IOException {
        Locale locale = new Locale("en", "US");
        Locale.setDefault(locale);

        CommandLineParser parser = new PosixParser();
        CommandLine commandLine;

        try {
            commandLine = parser.parse(options, args);
        } catch (ParseException ex) {
            usage();
            return;
        }

        baksmaliOptions options = new baksmaliOptions();

        boolean disassemble = 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;
        }

        String inputDexFileName = remainingArgs[0];

	//打开目标文件
        File dexFileFile = new File(inputDexFileName);
        if (!dexFileFile.exists()) {
            System.err.println("Can‘t find the file " + inputDexFileName);
            System.exit(1);
        }

        //Read in and parse the dex file
        DexBackedDexFile dexFile = DexFileFactory.loadDexFile(dexFileFile, options.apiLevel);    // 重点1 
        

  	//主要判断odex文件的一些代码,省略
  		  ...
  

	//反汇编dex文件,生成一个又一个的smali文件
        boolean errorOccurred = 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函数的分析主要有两点,需要重点研究一下,一个是

 //Read in and parse the dex file
DexBackedDexFile dexFile = 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 函数如下:

    public static DexBackedDexFile loadDexFile(File dexFile, @Nonnull Opcodes opcodes) throws IOException {

	...
	//首先判断文件是否为一个压缩文件,如果是的话解压缩后提取dex文件进行解析

        InputStream inputStream = new BufferedInputStream(new FileInputStream(dexFile));

        try {
            return DexBackedDexFile.fromInputStream(opcodes, inputStream);             // 重点 1
        } catch (DexBackedDexFile.NotADexFile ex) {
            // just eat it
        }

     // Note: DexBackedDexFile.fromInputStream will reset inputStream back to the same position, if it fails

        try {
            return DexBackedOdexFile.fromInputStream(opcodes, inputStream);
        } catch (DexBackedOdexFile.NotAnOdexFile ex) {
            // just eat it
        }

        throw new ExceptionWithContext("%s is not an apk, dex file or odex file.", dexFile.getPath());
    }

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

    public static DexBackedDexFile fromInputStream(@Nonnull Opcodes opcodes, @Nonnull InputStream is)
            throws IOException {
        if (!is.markSupported()) {
            throw new IllegalArgumentException("InputStream must support mark");
        }
        is.mark(44);
        byte[] partialHeader = new byte[44];
        try {
            ByteStreams.readFully(is, partialHeader);
        } catch (EOFException ex) {
            throw new NotADexFile("File is too short");
        } finally {
            is.reset();
        }

	//验证一下魔幻数和dex文件头部
        verifyMagicAndByteOrder(partialHeader, 0);

        byte[] buf = ByteStreams.toByteArray(is);
        return new DexBackedDexFile(opcodes, buf, 0, false);   //继续跟踪下去
    }
    
    
    
    private DexBackedDexFile(Opcodes opcodes, @Nonnull byte[] buf, int offset, boolean verifyMagic) {
        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文件的艰巨任务!

    public static boolean disassembleDexFile(DexFile dexFile, final baksmaliOptions options) {

	...

	//根据传入的文件夹路径创建文件夹
        File outputDirectoryFile = new File(options.outputDirectory);
        if (!outputDirectoryFile.exists()) {
            if (!outputDirectoryFile.mkdirs()) {
                System.err.println("Can‘t create the output directory " + options.outputDirectory);
                return false;
            }
        }

	//排序并生成dex文件中的所有 类定义实例到类定义的列表中
        //sort the classes, so that if we‘re on a case-insensitive file system and need to handle classes with file
        //name collisions, then we‘ll use the same name for each class, if the dex file goes through multiple
        //baksmali/smali cycles for some reason. If a class with a colliding name is added or removed, the filenames
        //may still change of course
        List<? extends ClassDef> classDefs = Ordering.natural().sortedCopy(dexFile.getClasses());   //  重点1 

        if (!options.noAccessorComments) {
            options.syntheticAccessorResolver = new SyntheticAccessorResolver(classDefs);
        }

	//生成文件的扩展名,为.smali
        final ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(outputDirectoryFile, ".smali");

	//根据 options.jobs 的值来生成处理 smali文件的线程数量
        ExecutorService executor = Executors.newFixedThreadPool(options.jobs);
        List<Future<Boolean>> tasks = Lists.newArrayList();

        for (final ClassDef classDef: classDefs) {
            tasks.add(executor.submit(new Callable<Boolean>() {
                @Override public Boolean call() throws Exception {
                    return disassembleClass(classDef, fileNameHandler, options);    //回调的解析函数,重点2
                }
            }));
        }

				...
    }

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

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

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

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

故事1

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

这个函数主要分为两部分

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

函数如下

    public Set<? extends DexBackedClassDef> getClasses() {
        return new FixedSizeSet<DexBackedClassDef>() {
            @Nonnull
            @Override
            public DexBackedClassDef readItem(int index) {
                return new DexBackedClassDef(DexBackedDexFile.this, getClassDefItemOffset(index));
            }

            @Override
            public int size() {
                return classCount;
            }
        };
    }

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

    public int getClassDefItemOffset(int classIndex) {
        if (classIndex < 0 || classIndex >= classCount) {
            throw new InvalidItemIndex(classIndex, "Class index out of bounds: %d", classIndex);
        }
        return classStartOffset + classIndex*ClassDefItem.ITEM_SIZE;
    }

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

baksmali和smali源码分析(四)

时间: 2024-10-12 16:40:04

baksmali和smali源码分析(四)的相关文章

baksmali和smali源码分析(三)

baksmali 的源码分析 在baksmali进行源码分析之前,需要读者掌握一条主线,因为本身笔者只是由于项目需要用到这套源码,在工作之余的时间里面来进行学习也没有时间和精力熟读源码的每个文件每个方法,但是依据这条主线,至少能够猜出并且猜对baksmali里面的源码的文件大概的作用是什么,这样在修改问题和移植的时候才能做到游刃有余. 这条主线是,baksmali其实只是利用了dexlib2提供的接口,将dex文件读入到一块内存中,这块内存或者说数据结构开辟的大小是跟输入的dex文件相关的,而这

baksmali和smali源码分析(五)

官方文档对于dex中的class数据结构表示如下: class_idx uint index into the type_ids list for this class. This must be a class type, and not an array or primitive type. access_flags uint access flags for the class (public, final, etc.). See "access_flags Definitions&quo

baksmali和smali源码分析(二)

这一节,主要介绍一下 baksmali代码的框架. 我们经常在反编译android apk包的时候使用apktool这个工具,其实本身这个工具里面对于dex文件解析和重新生成就是使用的baksmali 和smali这两个jar包其中 baksmali是将 dex文件转换成便于阅读的smali文件的,具体使用命令如下:java -jar baksmali.jar classes.dex -o myout其中myout是输出的文件夹 而smali是将smali文件重新生成回 dex文件的具体使用的命

Nouveau源码分析(四):NVIDIA设备初始化之nouveau_drm_load (1)

Nouveau源码分析(四) probe函数成功返回之后,DRM模块就会调用struct drm_driver的load函数,对应nouveau的nouveau_drm_load. 这个函数虽然看起来不是特别长,但每一个调用的函数展开后就会变得非常长了! // /drivers/gpu/drm/nouveau/nouveau_drm.c 364 static int 365 nouveau_drm_load(struct drm_device *dev, unsigned long flags)

mybatis源码分析(四) mybatis与spring事务管理分析

mybatis源码分析(四) mybatis与spring事务管理分析 一丶从jdbc的角度理解什么是事务 从mysql获取一个连接之后, 默认是自动提交, 即执行完sql之后, 就会提交事务. 这种事务的范围是一条sql语句. 将该连接设置非自动提交, 可以执行多条sql语句, 然后由程序决定是提交事务, 还是回滚事务. 这也是我们常说的事务. Connection connection = dataSource.getConnection(); // connection.setTransa

ABP源码分析四十七:ABP中的异常处理

ABP 中异常处理的思路是很清晰的.一共五种类型的异常类. AbpInitializationException用于封装ABP初始化过程中出现的异常,只要抛出AbpInitializationException异常就可以,无须做额外处理.这类异常往往是需要维护人员介入分析的. 其他四个异常都在AbpController中被集中处理,处理分为两步:一,通过EventBus触发异常事件,相应的异常处理函数则处理异常.而针对AbpValidationException,UserFriendlyExce

ABP源码分析四十六:ABP ZERO中的Ldap模块

通过AD作为用户认证的数据源.整个管理用户认证逻辑就在LdapAuthenticationSource类中实现. LdapSettingProvider:定义LDAP的setting和提供DefautValue.主要提供配置访问AD数据库的账号信息. LdapSettings/ILdapSettings:通过settingManager获取LDAP settings AbpZeroLdapModuleConfig/IAbpZeroLdapModuleConfig: 提供激活Ldap认证的配置.

docker 源码分析 四(基于1.8.2版本),Docker镜像的获取和存储

前段时间一直忙些其他事情,docker源码分析的事情耽搁了,今天接着写,上一章了解了docker client 和 docker daemon(会启动一个http server)是C/S的结构,client端发出的命令由docker daemon接收并处理. 我们在运行docker的时候,可能会使用到docker run命令(当然通过Dockerfile运行docker build命令也是一样的)时,如果本地没有你需要的镜像,docker daemon首先会去下载你需要的docker镜像,然后存

ABP源码分析四十一:ZERO的Audit,Setting,Background Job

AuditLog: 继承自Entity<long>的实体类.封装AuditLog的信息. AuditingStore: 实现了IAuditingStore接口,实现了将AuditLog的信息保存到数据库的功能.其通过IRepository<AuditLog, long>实例完成对数据库的操作. BackgroundJobStore :  实现了IBackgroundJobStore接口,通过IRepository<BackgroundJobInfo, long>完成对B