dex2jar源码解析----解析dex文件<一>

Dex2jar命令在Dex2jarCmd.java文件中

    public static void main(String... args) {
        new Dex2jarCmd().doMain(args);
    }
这里调用它的toMain函数,可以传递一些参数选项,选项大概有:

    @Opt(opt = "e", longOpt = "exception-file", description = "detail exception file, default is $current_dir/[file-name]-error.zip", argName = "file")
    private Path exceptionFile;
    @Opt(opt = "f", longOpt = "force", hasArg = false, description = "force overwrite")
    private boolean forceOverwrite = false;
    @Opt(opt = "n", longOpt = "not-handle-exception", hasArg = false, description = "not handle any exceptions thrown by dex2jar")
    private boolean notHandleException = false;
    @Opt(opt = "o", longOpt = "output", description = "output .jar file, default is $current_dir/[file-name]-dex2jar.jar", argName = "out-jar-file")
    private Path output;

    @Opt(opt = "r", longOpt = "reuse-reg", hasArg = false, description = "reuse register while generate java .class file")
    private boolean reuseReg = false;

    @Opt(opt = "s", hasArg = false, description = "same with --topological-sort/-ts")
    private boolean topologicalSort1 = false;

    @Opt(opt = "ts", longOpt = "topological-sort", hasArg = false, description = "sort block by topological, that will generate more readable code, default enabled")
    private boolean topologicalSort = false;

    @Opt(opt = "d", longOpt = "debug-info", hasArg = false, description = "translate debug info")
    private boolean debugInfo = false;

    @Opt(opt = "p", longOpt = "print-ir", hasArg = false, description = "print ir to System.out")
    private boolean printIR = false;

    @Opt(opt = "os", longOpt = "optmize-synchronized", hasArg = false, description = "optimize-synchronized")
    private boolean optmizeSynchronized = false;

    @Opt(opt = "nc", longOpt = "no-code", hasArg = false, description = "")
    private boolean noCode = false;


Dex2jarCmd继承于BaseCmd

doMain函数实现在BaseCmd.java中

public void doMain(String... args) {
        try {
            initOptions();
            parseSetArgs(args);
            doCommandLine();
        } catch (HelpException e) {
            String msg = e.getMessage();
            if (msg != null && msg.length() > 0) {
                System.err.println("ERROR: " + msg);
            }
            usage();
        } catch (Exception e) {
            e.printStackTrace(System.err);
        }
    }
initOptions调用initOptionFromClass,传递的是当前的Class

protected void initOptionFromClass(Class<?> clz) {
        if (clz == null) {
            return;
        } else {
            initOptionFromClass(clz.getSuperclass());
        }

        Syntax syntax = clz.getAnnotation(Syntax.class);
        if (syntax != null) {
            this.cmdLineSyntax = syntax.syntax();
            this.cmdName = syntax.cmd();
            this.desc = syntax.desc();
            this.onlineHelp = syntax.onlineHelp();
        }

        Field[] fs = clz.getDeclaredFields();
        for (Field f : fs) {
            Opt opt = f.getAnnotation(Opt.class);
            if (opt != null) {
                f.setAccessible(true);
                Option option = new Option();
                option.field = f;
                option.description = opt.description();
                option.hasArg = opt.hasArg();
                option.required = opt.required();
                if ("".equals(opt.longOpt()) && "".equals(opt.opt())) {   // into automode
                    option.longOpt = fromCamel(f.getName());
                    if (f.getType().equals(boolean.class)) {
                        option.hasArg=false;
                        try {
                            if (f.getBoolean(this)) {
                                throw new RuntimeException("the value of " + f + " must be false, as it is declared as no args");
                            }
                        } catch (IllegalAccessException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    checkConflict(option, "--" + option.longOpt);
                    continue;
                }
                if (!opt.hasArg()) {
                    if (!f.getType().equals(boolean.class)) {
                        throw new RuntimeException("the type of " + f
                                + " must be boolean, as it is declared as no args");
                    }

                    try {
                        if (f.getBoolean(this)) {
                            throw new RuntimeException("the value of " + f + " must be false, as it is declared as no args");
                        }
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }
                }
                boolean haveLongOpt = false;
                if (!"".equals(opt.longOpt())) {
                    option.longOpt = opt.longOpt();
                    checkConflict(option, "--" + option.longOpt);
                    haveLongOpt = true;
                }
                if (!"".equals(opt.argName())) {
                    option.argName = opt.argName();
                }
                if (!"".equals(opt.opt())) {
                    option.opt = opt.opt();
                    checkConflict(option, "-" + option.opt);
                } else {
                    if (!haveLongOpt) {
                        throw new RuntimeException("opt or longOpt is not set in @Opt(...) " + f);
                    }
                }
            }
        }
    }
这里主要是解析里面的Syntax和Opt注解,对于opt主要是解析代码中定义的opt,把相应的信息保存到optMap,如是否是必须的,是否有参数等

回到doMain

parseSetArgs继续解析我们穿进去的参数

protected void parseSetArgs(String... args) throws IllegalArgumentException, IllegalAccessException {
        this.orginalArgs = args;
        List<String> remainsOptions = new ArrayList<String>();
        Set<Option> requiredOpts = collectRequriedOptions(optMap);//收集必须的参数
        Option needArgOpt = null;
        for (String s : args) {
            if (needArgOpt != null) {//是否需要参数
                needArgOpt.field.set(this, convert(s, needArgOpt.field.getType()));
                needArgOpt = null;
            } else if (s.startsWith("-")) {// its a short or long option
                Option opt = optMap.get(s);//获取对应的Option
                requiredOpts.remove(opt);//从requiredOpts移除该选项
                if (opt == null) {
                    System.err.println("ERROR: Unrecognized option: " + s);
                    throw new HelpException();
                } else {
                    if (opt.hasArg) {
                        needArgOpt = opt;
                    } else {
                        opt.field.set(this, true);
                    }
                }
            } else {
                remainsOptions.add(s);
            }
        }

        if (needArgOpt != null) {//命令行解析失败
            System.err.println("ERROR: Option " + needArgOpt.getOptAndLongOpt() + " need an argument value");
            throw new HelpException();
        }
        this.remainingArgs = remainsOptions.toArray(new String[remainsOptions.size()]);
        if (this.printHelp) {
            throw new HelpException();
        }
        if (!requiredOpts.isEmpty()) {//必须选项列表不为空,说明条件未满足
            StringBuilder sb = new StringBuilder();
            sb.append("ERROR: Options: ");
            boolean first = true;
            for (Option option : requiredOpts) {
                if (first) {
                    first = false;
                } else {
                    sb.append(" and ");
                }
                sb.append(option.getOptAndLongOpt());
            }
            sb.append(" is required");
            System.err.println(sb.toString());
            throw new HelpException();
        }

    }
parseSetArgs首先收集必须的参数

然后对于我们传进去的参数,依次解析各个选项,如果是必须的选项,则从前面收集的必须选项中移除,表明该必须选项已经存在


最后doMain调用doCommandLine执行dex2jar的解析操作,doCommandLine有子类实现

  protected void doCommandLine() throws Exception {
        if (remainingArgs.length == 0) {//没有剩余的参数了?
            usage();
            return;
        }

        if ((exceptionFile != null || output != null) && remainingArgs.length != 1) {//-e/-o只能有一个文件
            System.err.println("-e/-o can only used with one file");
            return;
        }
        if (debugInfo && reuseReg) {//这两个选项不能同时使用
            System.err.println("-d/-r can not use together");
            return;
        }

        Path currentDir = new File(".").toPath();//获取当前目录

        if (output != null) {//输出文件是否存在
            if (Files.exists(output) && !forceOverwrite) {
                System.err.println(output + " exists, use --force to overwrite");
                return;
            }
        } else {
            for (String fileName : remainingArgs) {
                Path file = currentDir.resolve(getBaseName(new File(fileName).toPath()) + "-dex2jar.jar");
                //输出文件的名字,如果文件已经存在 则说明要覆盖写--force参数
                if (Files.exists(file) && !forceOverwrite) {
                    System.err.println(file + " exists, use   to overwrite");
                    return;
                }
            }
        }

        for (String fileName : remainingArgs) {
            // long baseTS = System.currentTimeMillis();
            String baseName = getBaseName(new File(fileName).toPath());//去掉后缀之后的名字
            Path file = output == null ? currentDir.resolve(baseName + "-dex2jar.jar") : output;//输出文件名
            System.err.println("dex2jar " + fileName + " -> " + file);

            BaseDexFileReader reader = MultiDexFileReader.open(Files.readAllBytes(new File(fileName).toPath()));
            BaksmaliBaseDexExceptionHandler handler = notHandleException ? null : new BaksmaliBaseDexExceptionHandler();
            Dex2jar.from(reader).withExceptionHandler(handler).reUseReg(reuseReg).topoLogicalSort()
                    .skipDebug(!debugInfo).optimizeSynchronized(this.optmizeSynchronized).printIR(printIR)
                    .noCode(noCode).to(file);

            if (!notHandleException) {
                if (handler.hasException()) {
                    Path errorFile = exceptionFile == null ? currentDir.resolve(baseName + "-error.zip")
                            : exceptionFile;
                    System.err.println("Detail Error Information in File " + errorFile);
                    System.err.println(BaksmaliBaseDexExceptionHandler.REPORT_MESSAGE);
                    handler.dump(errorFile, orginalArgs);
                }
            }
            // long endTS = System.currentTimeMillis();
            // System.err.println(String.format("%.2f", (float) (endTS - baseTS) / 1000));
        }
    }

这里进行一些检查,MultiDexFileReader.open根据文件的后缀新建合适的reader

public static BaseDexFileReader open(byte[] data) throws IOException {
        if (data.length < 3) {
            throw new IOException("File too small to be a dex/zip");
        }
        if ("dex".equals(new String(data, 0, 3, StandardCharsets.ISO_8859_1))) {// dex
            return new DexFileReader(data);//dex文件
        } else if ("PK".equals(new String(data, 0, 2, StandardCharsets.ISO_8859_1))) {// ZIP
            TreeMap<String, DexFileReader> dexFileReaders = new TreeMap<>();
            try (ZipFile zipFile = new ZipFile(data)) {
                for (ZipEntry e : zipFile.entries()) {
                    String entryName = e.getName();
                    if (entryName.startsWith("classes") && entryName.endsWith(".dex")) {
                        if (!dexFileReaders.containsKey(entryName)) { // only the first one
                            dexFileReaders.put(entryName, new DexFileReader(toByteArray(zipFile.getInputStream(e))));
                        }
                    }
                }
            }
            if (dexFileReaders.size() == 0) {
                throw new IOException("Can not find classes.dex in zip file");
            } else if (dexFileReaders.size() == 1) {
                return dexFileReaders.firstEntry().getValue();
            } else {
                return new MultiDexFileReader(dexFileReaders.values());
            }
        }
        throw new IOException("the src file not a .dex or zip file");
    }


我们看下reader的构造函数

public DexFileReader(ByteBuffer in) {
        in.position(0);
        in = in.asReadOnlyBuffer().order(ByteOrder.LITTLE_ENDIAN);//小端模式
        int magic = in.getInt() & 0x00FFFFFF;
        if (magic == MAGIC_DEX) {//dex
            ;
        } else if (magic == MAGIC_ODEX) {
            throw new DexException("Not support odex");
        } else {
            throw new DexException("not support magic.");
        }
        int version = in.getInt() & 0x00FFFFFF;//版本号
        if (version != MAGIC_035 && version != MAGIC_036) {
            throw new DexException("not support version.");
        }

        // skip uint checksum
        // and 20 bytes signature
        // and uint file_size
        // and uint header_size 0x70
        skip(in, 4 + 20 + 4 + 4);

        int endian_tag = in.getInt();
        if (endian_tag != ENDIAN_CONSTANT) {
            throw new DexException("not support endian_tag");
        }

        // skip uint link_size
        // and uint link_off
        // and uint map_off
        skip(in, 4 + 4 + 4);
        //获取各个区段的大小和偏移
        string_ids_size = in.getInt();
        int string_ids_off = in.getInt();
        type_ids_size = in.getInt();
        int type_ids_off = in.getInt();
        int proto_ids_size = in.getInt();
        int proto_ids_off = in.getInt();
        field_ids_size = in.getInt();
        int field_ids_off = in.getInt();
        method_ids_size = in.getInt();
        int method_ids_off = in.getInt();
        class_defs_size = in.getInt();
        int class_defs_off = in.getInt();
        // skip uint data_size data_off
        //获取偏移和长度获取各个块的buffer
        stringIdIn = slice(in, string_ids_off, string_ids_size * 4);
        typeIdIn = slice(in, type_ids_off, type
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值