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");
}
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