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