dex2jar源码解析----smail转dex

本文介绍了如何使用SmaliCmd将smali文件转换为dex文件,涉及ANTLR工具对smali语法的识别,以及解析过程中Smali、DexFileWriter、DexMethodVisitor和SmaliCodeVisitor等组件的角色。详细阐述了从词法解析到方法和字段处理的整个转换流程。
摘要由CSDN通过智能技术生成

SmaliCmd用来将smail文件转换为dex文件

smail转dex用到了一个ANTLR 语言识别的一个工具 (ANother Tool for Language Recognition ) 来识别 smail语法,它定义了一个smail的文法文件


这里的Smail.g4就是定义的文法文件,使用的是g4版本,另外几个java文件是根据文法文件自动生成的


这是文法文件的一部分

关于ANTLR我也不是特别了解,主要是知道它在这里的用途就是识别smalil文件中的关键字,词法等


我们主要分析smail转为dex实现过程

@Syntax(cmd = "d2j-smali", syntax = "[options] [--] [<smali-file>|folder]*", desc = "assembles a set of smali files into a dex file", onlineHelp = "https://sourceforge.net/p/dex2jar/wiki/Smali")
public class SmaliCmd extends BaseCmd {
    @Opt(opt = "x", longOpt = "allow-odex-instructions", hasArg = false, description = "[not impl] allow odex instructions to be compiled into the dex file. Only a few instructions are supported - the ones that can exist in a dead code path and not cause dalvik to reject the class")
    private boolean allowOdexInstructions;
    @Opt(opt = "a", longOpt = "api-level", description = "[not impl] The numeric api-level of the file to generate, e.g. 14 for ICS. If not specified, it defaults to 14 (ICS).", argName = "API_LEVEL")
    private int apiLevel = 14;
    @Opt(opt = "v", longOpt = "version", hasArg = false, description = "prints the version then exits")
    private boolean showVersionThenExits;
    @Opt(opt = "o", longOpt = "output", description = "the name of the dex file that will be written. The default is out.dex", argName = "FILE")
    private Path output;
    @Opt(opt = "-", hasArg = false, description = "read smali from stdin")
    private boolean readSmaliFromStdin;

    public static void main(String[] args) {
        new SmaliCmd().doMain(args);
    }

    @Override
    protected void doCommandLine() throws Exception {

        if (showVersionThenExits) {
            System.out.println("smali 1.4.2p (https://sourceforge.net/p/dex2jar)");
            System.out.println("Copyright (c) 2009-2013 Panxiaobo (pxb1988@gmail.com)");
            System.out.println("Apache license (http://www.apache.org/licenses/LICENSE-2.0)");
            return;
        }

        if (!readSmaliFromStdin && remainingArgs.length < 1) {
            System.err.println("ERRPR: no file to process");
            return;
        }

        if (output == null) {
            output = new File("out.dex").toPath();
        }

        Smali smali = new Smali();
        DexFileWriter fw = new DexFileWriter();

        DexFileVisitor fv = new DexFileVisitor(fw) {
            @Override
            public void visitEnd() {// intercept the call to super
            }
        };

        if (readSmaliFromStdin) {
            smali.smaliFile("<stdin>", System.in, fv);
            System.err.println("smali <stdin> -> " + output);
        }

        for (String s : remainingArgs) {
            Path file = new File(s).toPath();
            if (!Files.exists(file)) {
                System.err.println("skip " + file + ", it is not a dir or a file");
            } else {
                System.err.println("smali " + s + " -> " + output);
                smali.smali(file, fv);
            }
        }

        fw.visitEnd();
        byte[] data = fw.toByteArray();
        Files.write(output, data);
    }
}
这里定义了一些选项,然后主要是里面的doCommandLine方法

定义了一个Smali  和DexFileWriter
然后主要是调用了smail的smail方法

public static void smali(Path base, final DexFileVisitor dfv) throws IOException {
        if (Files.isDirectory(base)) {
            Files.walkFileTree(base, new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                    Path fn = dir.getFileName();
                    if (fn != null && fn.toString().startsWith(".")) {
                        return FileVisitResult.SKIP_SUBTREE;
                    }
                    return super.preVisitDirectory(dir, attrs);
                }

                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    smaliFile(file, dfv);
                    return super.visitFile(file, attrs);
                }
            });
        } else if (Files.isRegularFile(base)) {
            smaliFile(base, dfv);
        }
    }
这里不是目录,走else分支

 public static void smaliFile(Path path, DexFileVisitor dcv) throws IOException {
        try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
            ANTLRInputStream is = new ANTLRInputStream(reader);
            is.name = path.toString();
            smali0(dcv, is);
        }
    }
这里读书smail文件中的内容,并以它构造一个ANTLRInputStream流,然后调用smali0

    private static void smali0(DexFileVisitor dcv, CharStream is) throws IOException {
        SmaliLexer lexer = new SmaliLexer(is);
        CommonTokenStream ts = new CommonTokenStream(lexer);
        SmaliParser parser = new SmaliParser(ts);

        for (SmaliParser.SFileContext ctx : parser.sFiles().sFile()) {
            AntlrSmaliUtil.acceptFile(ctx, dcv);
        }
    }
用 ANTLRInputStream流 构造词法分析器 lexer,词法分析的作用是产生记号,用词法分析器 lexer 构造一个记号流 tokens,然后再使用 tokens 构造语法分析器 parser

这里解析出来的SmaliParser.SFileContext我们就可以当作以哥smail的文件上下文,包含了该smail文件的相关信息

public static void acceptFile(SmaliParser.SFileContext ctx, DexFileVisitor dexFileVisitor) {
        DexClassVisitor dexClassVisitor;
        String className = Utils.unEscapeId(ctx.className.getText());//获取类名
        int access = collectAccess(ctx.sAccList());//访问标志
        List<SmaliParser.SSuperContext> superContexts = ctx.sSuper();//超类
        String superClass = null;
        if (superContexts.size() > 0) {
            superClass = Utils.unEscapeId(superContexts.get(superContexts.size() - 1).name.getText());
        }
        List<SmaliParser.SInterfaceContext> itfs = ctx.sInterface();//接口
        String[] interfaceNames = null;
        if (itfs.size() > 0) {
            interfaceNames = new String[itfs.size()];
            for (int i = 0; i < itfs.size(); i++) {
                interfaceNames[i] = Utils.unEscapeId(itfs.get(i).name.getText());
            }
        }

        dexClassVisitor = dexFileVisitor.visit(access, className, superClass, interfaceNames);//根据前面的信息访问类

        List<SmaliParser.SSourceContext> sources = ctx.sSource();
        if (sources.size() > 0) {
            dexClassVisitor.visitSource(
                    Utils.unescapeStr(sources.get(sources.size() - 1).src.getText())
            );
        }
        acceptAnnotations(ctx.sAnnotation(), dexClassVisitor);//访问注解
        acceptField(ctx.sField(), className, dexClassVisitor);//访问字段
        acceptMethod(ctx.sMethod(), className, dexClassVisitor);//访问方法

        dexClassVisitor.visitEnd();
    }
AntlrSmaliUtil提供了一些访问类,字段等的静态方法

这里会根据前面的词法解析获取类的一些基本信息,然后调用DexFileVisitor的visitor访问这个类,并返回一个DexClassVisitor,我们看下visitor的实现

  public DexClassVisitor visit(int access_flags, String className, String superClass, String[] interfaceNames) {
        if (visitor == null) {
            return null;
        }
        return visitor.visit(access_flags, className, superClass, interfaceNames);
    }
  @Override
    public DexClassVisitor visit(int accessFlag, String name,
                                 String superClass, String[] itfClass) {
        ClassDefItem defItem = cp.putClassDefItem(accessFlag, name, superClass,
                itfClass);
        return new ClassWriter(defItem, cp);
    }

 public ClassDefItem putClassDefItem(int accessFlag, String name, String superClass, String[] itfClass) {
        TypeIdItem type = uniqType(name);
        if (classDefs.containsKey(type)) {
            throw new DexWriteException("dup clz: " + name);
        }
        ClassDefItem classDefItem = new ClassDefItem();
        classDefItem.accessFlags = accessFlag;
        classDefItem.clazz = type;
        if (superClass != null) {
            classDefItem.superclazz = uniqType(superClass);
        }
        if (itfClass != null && itfClass.length > 0) {
            classDefItem.interfaces = putTypeList(Arrays.asList(itfClass));
        }
        classDefs.put(type, classDefItem);
        return classDefItem;
    }
创建了一个ClassDefItem并添加到classDefs,以ClassDefItem创建了一个ClassWriter并返回。

回到acceptFile

接下来以前面创建的ClassWriter为参数,分别调用visitSource ,acceptAnnotations  acceptField acceptMethod 并最终调用它的visitEnd

 @Override
    public void visitSource(String file) {
        defItem.sourceFile = cp.uniqString(file);
    }

我们看acceptField

 public static void acceptField(SmaliParser.SFieldContext ctx, String className, DexClassVisitor dexClassVisitor) {
        Field field;
        Token fieldObj = ctx.fieldObj;//获取fileld Token
        if (fieldObj.getType() == SmaliLexer.FIELD_FULL) {
            field = Utils.parseFieldAndUnescape(fieldObj.getText());
        } else {
            field = Utils.parseFieldAndUnescape(className, fieldObj.getText());//返回一个Field
        }
        int access = collectAccess(ctx.sAccList());//access标志
        Object value = null;
        SmaliParser.SBaseValueContext vctx = ctx.sBaseValue();
        if (vctx != null) {
            value = parseBaseValue(vctx);
        }
        DexFieldVisitor dexFie
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值