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 automodeoption.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 optionOption opt = optMap.get(s);//获取对应的OptionrequiredOpts.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))) {// dexreturn new DexFileReader(data);//dex文件} else if ("PK".equals(new String(data, 0, 2, StandardCharsets.ISO_8859_1))) {// ZIPTreeMap<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 onedexFileReaders.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 0x70skip(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_offskip(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//获取偏移和长度获取各个块的bufferstringIdIn = slice(in, string_ids_off, string_ids_size * 4);typeIdIn = slice(in, type_ids_off, type_ids_size * 4);protoIdIn = slice(in, proto_ids_off, proto_ids_size * 12);fieldIdIn = slice(in, field_ids_off, field_ids_size * 8);methoIdIn = slice(in, method_ids_off, method_ids_size * 8);classDefIn = slice(in, class_defs_off, class_defs_size * 32);//下面又定义了几个buffer后面使用之前会改变position位置in.position(0);annotationsDirectoryItemIn = in.duplicate().order(ByteOrder.LITTLE_ENDIAN);annotationSetItemIn = in.duplicate().order(ByteOrder.LITTLE_ENDIAN);annotationItemIn = in.duplicate().order(ByteOrder.LITTLE_ENDIAN);annotationSetRefListIn = in.duplicate().order(ByteOrder.LITTLE_ENDIAN);classDataIn = in.duplicate().order(ByteOrder.LITTLE_ENDIAN);codeItemIn = in.duplicate().order(ByteOrder.LITTLE_ENDIAN);stringDataIn = in.duplicate().order(ByteOrder.LITTLE_ENDIAN);encodedArrayItemIn = in.duplicate().order(ByteOrder.LITTLE_ENDIAN);typeListIn = in.duplicate().order(ByteOrder.LITTLE_ENDIAN);debugInfoIn = in.duplicate().order(ByteOrder.LITTLE_ENDIAN);}

这里主要是解析dex的头

回到前面,后主要是调用

Dex2jar.from(reader).withExceptionHandler(handler).reUseReg(reuseReg).topoLogicalSort().skipDebug(!debugInfo).optimizeSynchronized(this.optmizeSynchronized).printIR(printIR).noCode(noCode).to(file);

开始dex到jar的转换

from通过reader新建一个Dex2jar,后面会调用它的doTranslate开始进行dex2jar的转换

exceptionHandler用来在解析发生异常时进行处理

然后其他的都是一些解析过程中用到的选项的配置

to开始启动解析

    public void to(Path file) throws IOException {//把解析后的文件写入 .\classes-dex2jar.jarif (Files.exists(file) && Files.isDirectory(file)) {//已经存在或者为目录doTranslate(file);} else {try (FileSystem fs = createZip(file)) {doTranslate(fs.getPath("/"));}}}

最终调用doTranslate

  private void doTranslate(final Path dist) throws IOException {DexFileNode fileNode = new DexFileNode();//创建一个DexFileNode的访问者try {reader.accept(fileNode, readerConfig | DexFileReader.IGNORE_READ_EXCEPTION);} catch (Exception ex) {exceptionHandler.handleFileException(ex);}ClassVisitorFactory cvf = new ClassVisitorFactory() {@Overridepublic ClassVisitor create(final String name) {return new ClassVisitor(Opcodes.ASM4, new ClassWriter(ClassWriter.COMPUTE_MAXS)) {@Overridepublic void visitEnd() {super.visitEnd();ClassWriter cw = (ClassWriter) super.cv;byte[] data;try {// FIXME handle 'java.lang.RuntimeException: Method code too large!'data = cw.toByteArray();} catch (Exception ex) {System.err.println(String.format("ASM fail to generate .class file: %s", name));exceptionHandler.handleFileException(ex);return;}try {Path dist1 = dist.resolve(name + ".class");Path parent = dist1.getParent();if (parent != null && !Files.exists(parent)) {Files.createDirectories(parent);}Files.write(dist1, data);} catch (IOException e) {e.printStackTrace(System.err);}}};}};new ExDex2Asm(exceptionHandler) {public void convertCode(DexMethodNode methodNode, MethodVisitor mv) {if ((readerConfig & DexFileReader.SKIP_CODE) != 0 && methodNode.method.getName().equals("<clinit>")) {// also skip clinitreturn;}super.convertCode(methodNode, mv);}@Overridepublic void optimize(IrMethod irMethod) {T_cleanLabel.transform(irMethod);if (0 != (v3Config & V3.TOPOLOGICAL_SORT)) {// T_topologicalSort.transform(irMethod);}T_deadCode.transform(irMethod);T_removeLocal.transform(irMethod);T_removeConst.transform(irMethod);T_zero.transform(irMethod);if (T_npe.transformReportChanged(irMethod)) {T_deadCode.transform(irMethod);T_removeLocal.transform(irMethod);T_removeConst.transform(irMethod);}T_new.transform(irMethod);T_fillArray.transform(irMethod);T_agg.transform(irMethod);T_multiArray.transform(irMethod);T_voidInvoke.transform(irMethod);if (0 != (v3Config & V3.PRINT_IR)) {int i = 0;for (Stmt p : irMethod.stmts) {if (p.st == Stmt.ST.LABEL) {LabelStmt labelStmt = (LabelStmt) p;labelStmt.displayName = "L" + i++;}}System.out.println(irMethod);}T_type.transform(irMethod);T_unssa.transform(irMethod);T_ir2jRegAssign.transform(irMethod);T_trimEx.transform(irMethod);}@Overridepublic void ir2j(IrMethod irMethod, MethodVisitor mv) {new IR2JConverter(0 != (V3.OPTIMIZE_SYNCHRONIZED & v3Config)).convert(irMethod, mv);}}.convertDex(fileNode, cvf);}

这里开始主要是调用reader.accept进行dex文件的解析

然后后面的是把解析的dex文件转换为一个中间的IR格式,进行优化后再转换为jvm指令 进而输出到class文件,这个我们下一篇在看

我们先看accept方法

   @Overridepublic void accept(DexFileVisitor dv, int config) {//使用指定的访问者访问dex文件for (int cid = 0; cid < class_defs_size; cid++) {//总共有多少个类accept(dv, cid, config);}dv.visitEnd();}

这里根据前面读到的class的数量,调用另外一个accept函数访问该类

public void accept(DexFileVisitor dv, int classIdx, int config) {classDefIn.position(classIdx * 32);//一个类结构占32未int class_idx = classDefIn.getInt(); /* 类的类型,指向DexTypeId列表的索引 */int access_flags = classDefIn.getInt();/* 访问标志 */int superclass_idx = classDefIn.getInt(); /* 父类类型,指向DexTypeId列表的索引 */int interfaces_off = classDefIn.getInt();/* 接口,指向DexTypeList的偏移 */int source_file_idx = classDefIn.getInt();/* 源文件名,指向DexStringId列表的索引 */int annotations_off = classDefIn.getInt();/* 注解,指向DexAnnotationsDirectoryItem结构 */int class_data_off = classDefIn.getInt();/* 指向DexClassData结构的偏移 */int static_values_off = classDefIn.getInt(); /* 指向DexEncodedArray结构的偏移 */String className = getType(class_idx);//根据索引获取类名String superClassName = getType(superclass_idx);//根据索引获取父类名String[] interfaceNames = getTypeList(interfaces_off);//根据索引获取接口列表try {DexClassVisitor dcv = dv.visit(access_flags, className, superClassName, interfaceNames);if (dcv != null)// 不为null{acceptClass(dcv, source_file_idx, annotations_off, class_data_off, static_values_off, config);dcv.visitEnd();}} catch (Exception ex) {DexException dexException = new DexException(ex, "Error process class: [%d]%s", class_idx, className);if (0 != (config & IGNORE_READ_EXCEPTION)) {niceExceptionMessage(dexException, 0);} else {throw dexException;}}}

找了调用dv的visit,dv是DexFileNode

    public DexClassVisitor visit(int access_flags, String className, String superClass, String[] interfaceNames) {DexClassNode cn = new DexClassNode(access_flags, className, superClass, interfaceNames);//新建一个DexClassNodeclzs.add(cn);//添加到clzsreturn cn;}

新建一个DexClassNode

   public DexClassNode(int access, String className, String superClass, String[] interfaceNames) {super();this.access = access;this.className = className;this.superClass = superClass;this.interfaceNames = interfaceNames;}

回到前面的accept,调用acceptClass

private void acceptClass(DexClassVisitor dcv, int source_file_idx, int annotations_off, int class_data_off,int static_values_off, int config) {if ((config & SKIP_DEBUG) == 0) {//忽略调试// 获取源文件if (source_file_idx != -1) {dcv.visitSource(this.getString(source_file_idx));}}Map<Integer, Integer> fieldAnnotationPositions;Map<Integer, Integer> methodAnnotationPositions;Map<Integer, Integer> paramAnnotationPositions;if ((config & SKIP_ANNOTATION) == 0) {//是否忽略注释 用来保存方法或者参数的注解,后面解析要用到// 获取注解fieldAnnotationPositions = new HashMap<Integer, Integer>();methodAnnotationPositions = new HashMap<Integer, Integer>();paramAnnotationPositions = new HashMap<Integer, Integer>();if (annotations_off != 0) { // annotations_directory_item是否有注释annotationsDirectoryItemIn.position(annotations_off);//注解偏移int class_annotations_off = annotationsDirectoryItemIn.getInt();//类注解偏移int field_annotation_size = annotationsDirectoryItemIn.getInt();//字段注解数量int method_annotation_size = annotationsDirectoryItemIn.getInt();//方法注解数量int parameter_annotation_size = annotationsDirectoryItemIn.getInt();//参数注解数量for (int i = 0; i < field_annotation_size; i++) {//把注解对应的字段的idx和注解偏移对应起来int field_idx = annotationsDirectoryItemIn.getInt();int field_annotations_offset = annotationsDirectoryItemIn.getInt();fieldAnnotationPositions.put(field_idx, field_annotations_offset);}for (int i = 0; i < method_annotation_size; i++) {//把注解对应的方法的idx和注解偏移对应起来int method_idx = annotationsDirectoryItemIn.getInt();int method_annotation_offset = annotationsDirectoryItemIn.getInt();methodAnnotationPositions.put(method_idx, method_annotation_offset);}for (int i = 0; i < parameter_annotation_size; i++) {//把注解对应的参数变量的idx和注解偏移对应起来int method_idx = annotationsDirectoryItemIn.getInt();int parameter_annotation_offset = annotationsDirectoryItemIn.getInt();paramAnnotationPositions.put(method_idx, parameter_annotation_offset);}if (class_annotations_off != 0) {//读取类的注解try {read_annotation_set_item(class_annotations_off, dcv);} catch (Exception e) {throw new DexException("error on reading Annotation of class ", e);}}}} else {fieldAnnotationPositions = null;methodAnnotationPositions = null;paramAnnotationPositions = null;}if (class_data_off != 0) {//类的详细信息ByteBuffer in = classDataIn;in.position(class_data_off);//调整到class的data偏移处int static_fields = (int) readULeb128i(in);/* 静态字段个数 */int instance_fields = (int) readULeb128i(in);/* 实例字段个数 */int direct_methods = (int) readULeb128i(in); /* 直接方法个数 */int virtual_methods = (int) readULeb128i(in);/* 虚方法个数 */{int lastIndex = 0;{Object[] constant = null;if ((config & SKIP_FIELD_CONSTANT) == 0) {//是否忽略常量if (static_values_off != 0) {//读取常量,final修饰的constant = read_encoded_array_item(static_values_off);}}for (int i = 0; i < static_fields; i++) {//静态字段Object value = null;if (constant != null && i < constant.length) {value = constant[i];}lastIndex = acceptField(in, lastIndex, dcv, fieldAnnotationPositions, value, config);//获取静态字段}}lastIndex = 0;for (int i = 0; i < instance_fields; i++) {//获取实例字段lastIndex = acceptField(in, lastIndex, dcv, fieldAnnotationPositions, null, config);}lastIndex = 0;boolean firstMethod = true;for (int i = 0; i < direct_methods; i++) {//获取直接方法lastIndex = acceptMethod(in, lastIndex, dcv, methodAnnotationPositions, paramAnnotationPositions,config, firstMethod);firstMethod = false;}lastIndex = 0;firstMethod = true;for (int i = 0; i < virtual_methods; i++) {lastIndex = acceptMethod(in, lastIndex, dcv, methodAnnotationPositions, paramAnnotationPositions,config, firstMethod);firstMethod = false;}}}}

这里主要是依次调用acceptField解析字段,acceptMethod解析方法

先看acceptField

 private int acceptField(ByteBuffer in, int lastIndex, DexClassVisitor dcv,Map<Integer, Integer> fieldAnnotationPositions, Object value, int config) {int diff = (int) readULeb128i(in);int field_access_flags = (int) readULeb128i(in);int field_id = lastIndex + diff;Field field = getField(field_id);// //DexFieldVisitor dfv = dcv.visitField(field_access_flags, field, value);//访问一个字段 filed中保存了字段的原型,这里传递访问标志 和值if (dfv != null) {if ((config & SKIP_ANNOTATION) == 0) {//忽略字段注释Integer annotation_offset = fieldAnnotationPositions.get(field_id);if (annotation_offset != null) {try {read_annotation_set_item(annotation_offset, dfv);//把注释设置到字段} catch (Exception e) {throw new DexException(e, "while accept annotation in field:%s.", field.toString());}}}dfv.visitEnd();//访问结束}// //return field_id;}
    private Field getField(int id) {fieldIdIn.position(id * 8);//一个filed占8个字节int owner_idx = 0xFFFF & fieldIdIn.getShort();/* 类的类型,指向DexTypeId列表的索引 */int type_idx = 0xFFFF & fieldIdIn.getShort();/* 字段类型,指向DexTypeId列表的索引 */int name_idx = fieldIdIn.getInt(); /* 字段名,指向DexStringId列表的索引 */return new Field(getType(owner_idx), getString(name_idx), getType(type_idx));//创建一个Field字段 所属类 类型 名称}

这里要结合前面的dex文件结构,解析Field字段

下一步是acceptMethod

 private int acceptMethod(ByteBuffer in, int lastIndex, DexClassVisitor cv, Map<Integer, Integer> methodAnnos,Map<Integer, Integer> parameterAnnos, int config, boolean firstMethod) {int offset = in.position();int diff = (int) readULeb128i(in);int method_access_flags = (int) readULeb128i(in);/* 访问标志 */int code_off = (int) readULeb128i(in);/* 指向DexCode结构的偏移 */int method_id = lastIndex + diff;/* 指向DexMethodId的索引 */Method method = getMethod(method_id);//获取一个Method// issue 200, methods may have same signature, we only need to keep the first oneif (!firstMethod && diff == 0) { // detect a duplicated methodWARN("GLITCH: duplicated method %s @%08x", method.toString(), offset);if ((config & KEEP_ALL_METHODS) == 0) {WARN("WARN: skip method %s @%08x", method.toString(), offset);return method_id;}}// issue 195, a <clinit> or <init> but not marked as ACC_CONSTRUCTOR,if (0 == (method_access_flags & DexConstants.ACC_CONSTRUCTOR)&& (method.getName().equals("<init>") || method.getName().equals("<clinit>"))) {WARN("GLITCH: method %s @%08x not marked as ACC_CONSTRUCTOR", method.toString(), offset);}try {DexMethodVisitor dmv = cv.visitMethod(method_access_flags, method);if (dmv != null) {if ((config & SKIP_ANNOTATION) == 0) {//是否忽略Integer annotation_offset = methodAnnos.get(method_id);//方法的注释if (annotation_offset != null) {try {read_annotation_set_item(annotation_offset, dmv);} catch (Exception e) {throw new DexException(e, "while accept annotation in method:%s.", method.toString());}}Integer parameter_annotation_offset = parameterAnnos.get(method_id);//参数的注释if (parameter_annotation_offset != null) {try {read_annotation_set_ref_list(parameter_annotation_offset, dmv);} catch (Exception e) {throw new DexException(e, "while accept parameter annotation in method:%s.",method.toString());}}}if (code_off != 0) {//exCode结构boolean keep = true;if (0 != (SKIP_CODE & config)) {//是否忽略codekeep = 0 != (KEEP_CLINIT & config) && method.getName().equals("<clinit>");}if (keep) {DexCodeVisitor dcv = dmv.visitCode();if (dcv != null) {try {acceptCode(code_off, dcv, config, (method_access_flags & DexConstants.ACC_STATIC) != 0,method);//访问code} catch (Exception e) {throw new DexException(e, "while accept code in method:[%s] @%08x", method.toString(),code_off);}}}}dmv.visitEnd();}} catch (Exception e) {throw new DexException(e, "while accept method:[%s]", method.toString());}return method_id;}
 //根据methodid获取Methodprivate Method getMethod(int id) {methoIdIn.position(id * 8);//调整位置int owner_idx = 0xFFFF & methoIdIn.getShort(); /* 类的类型,指向DexTypeId列表的索引 */int proto_idx = 0xFFFF & methoIdIn.getShort(); /* 声明类型,指向DexProtoId列表的索引 */int name_idx = methoIdIn.getInt(); /* 方法名,指向DexStringId列表的索引 */String[] parameterTypes;String returnType;//DexProtoId占12个字节,跳过前面的shortyIdxprotoIdIn.position(proto_idx * 12 + 4); // move to position and skip shorty_idxint return_type_idx = protoIdIn.getInt();/* 返回类型  指向DexTypeId列表的索引 */int parameters_off = protoIdIn.getInt();/* 参数列表  指向DexTypeList的偏移 */returnType = getType(return_type_idx);parameterTypes = getTypeList(parameters_off);return new Method(getType(owner_idx), getString(name_idx), parameterTypes, returnType);//新建一个Method}

这里同样要根据前面分析的dex文件结构中的方法字段结构进行解析

然后调用

DexMethodVisitor dmv = cv.visitMethod(method_access_flags, method);
    @Override//访问方法,主要是添加访问标志public DexMethodVisitor visitMethod(int accessFlags, Method method) {if (methods == null) {methods = new ArrayList<DexMethodNode>();}DexMethodNode methodNode = new DexMethodNode(accessFlags, method);//新建一个DexMethodNodemethods.add(methodNode);return methodNode;}

对于Method,这里主要是解析里面的Code,acceptCode

/* package */void acceptCode(int code_off, DexCodeVisitor dcv, int config, boolean isStatic, Method method) {ByteBuffer in = codeItemIn;in.position(code_off);int registers_size = 0xFFFF & in.getShort();/* 使用的寄存器个数 */in.getShort();// ins_size ushort/* 参数个数 */in.getShort();// outs_size ushort/* 调用其他方法时使用的寄存器个数 */int tries_size = 0xFFFF & in.getShort();/* Try/Catch个数 */int debug_info_off = in.getInt();/* 指向调试信息的偏移 */int insns = in.getInt();/*指令集个数,以2字节为单位 */byte[] insnsArray = new byte[insns * 2];//一个指令占两位in.get(insnsArray); /* 指令集 */dcv.visitRegister(registers_size);//访问寄存器BitSet nextInsn = new BitSet();Map<Integer, DexLabel> labelsMap = new TreeMap<Integer, DexLabel>();Set<Integer> handlers = new HashSet<Integer>();// 处理异常处理if (tries_size > 0) {if ((insns & 0x01) != 0) {// skip paddingin.getShort();}findTryCatch(in, dcv, tries_size, insns, labelsMap, handlers);}// 处理debug信息if (debug_info_off != 0 && (0 == (config & SKIP_DEBUG))) {DexDebugVisitor ddv = dcv.visitDebug();if (ddv != null) {read_debug_info(debug_info_off, registers_size, isStatic, method, labelsMap, ddv);ddv.visitEnd();}}BitSet badOps = new BitSet();findLabels(insnsArray, nextInsn, badOps, labelsMap, handlers, method);acceptInsn(insnsArray, dcv, nextInsn, badOps, labelsMap);dcv.visitEnd();}

对于acceptCode,主要是调用findLabels找到里面的跳转xiangg 的标签

然后调用acceptInsn解析code里面的指令

先看findLabels

 //找到方法中所有指令的起始位置,如有跳转指令则添加到labelsMapprivate void findLabels(byte[] insns, BitSet nextBit, BitSet badOps, Map<Integer, DexLabel> labelsMap,Set<Integer> handlers,Method method) {Queue<Integer> q = new LinkedList<Integer>();q.add(0);//首先从索引0开始 第一个指令q.addAll(handlers);//handlers是前面异常处理添加的handlers.clear();while (!q.isEmpty()) {int offset = q.poll();if (nextBit.get(offset)) {continue;} else {nextBit.set(offset);}try {travelInsn(labelsMap, q, insns, offset);} catch (IndexOutOfBoundsException indexOutOfRange) {badOps.set(offset);WARN("GLITCH: %04x %s | not enough space for reading instruction", offset, method.toString());} catch (BadOpException badOp) {badOps.set(offset);WARN("GLITCH: %04x %s | %s", offset, method.toString(), badOp.getMessage());}}}

从0索引开始,依次解析里面包含的跳转相关的指令

主要是调用travelInsn

//分析当前指令,没有结束的话把下一条指令偏移加入队列,如果有跳转或者分支指令添加到labelsMap表private void travelInsn(Map<Integer, DexLabel> labelsMap, Queue<Integer> q, byte[] insns, int offset) {int u1offset = offset * 2;//指令是16的倍数,指令格式说明了该指令由多少个16位组成if (u1offset >= insns.length) {//已经大于指令长度throw new IndexOutOfBoundsException();}int opcode = 0xFF & insns[u1offset];//获取操作码索引(低8位)http://blog.csdn.net/hudashi/article/details/52184035Op op = null;if (opcode < Op.ops.length) {op = Op.ops[opcode];//获取对应的指令}if (op == null || op.format == null) {throw new BadOpException("zero-width instruction op=0x%02x", opcode);}int target;boolean canContinue = true;if (op.canBranch()) {//是否是跳转指令 labelsMap用来存放跳转指令的标签switch (op.format) {case kFmt10t:target = offset + insns[u1offset + 1];if (target < 0 || target * 2 > insns.length) {throw new BadOpException("jump out of insns %s -> %04x", op, target);}q.add(target);order(labelsMap, target);break;case kFmt20t:case kFmt21t://2个16位字节(4个byte 4 * 8) 1个 寄存器 t表示跳转分支指令target = offset + sshort(insns, u1offset + 2);//跳转的指令偏移,如果成功,则跳过下面不满足条件的指令 target表示跳转到的地址if (target < 0 || target * 2 > insns.length) {throw new BadOpException("jump out of insns %s -> %04x", op, target);}q.add(target);//添加到队列order(labelsMap, target);//添加到调整lablesMapbreak;case kFmt22t:target = offset + sshort(insns, u1offset + 2);int u = ubyte(insns, u1offset + 1);boolean cmpSameReg = (u & 0x0F) == ((u >> 4) & 0x0F);boolean skipTarget = false;if (cmpSameReg) {switch (op) {case IF_EQ:case IF_GE:case IF_LE:// means always jump, equals to gotocanContinue = false;break;case IF_NE:case IF_GT:case IF_LT:// means always not jumpskipTarget = true;break;default:break;}}if (!skipTarget) {if (target < 0 || target * 2 > insns.length) {throw new BadOpException("jump out of insns %s -> %04x", op, target);}q.add(target);order(labelsMap, target);}break;case kFmt30t:case kFmt31t:target = offset + sint(insns, u1offset + 2);if (target < 0 || target * 2 > insns.length) {throw new BadOpException("jump out of insns %s -> %04x", op, target);}q.add(target);order(labelsMap, target);break;default:break;}}if (op.canSwitch()) {//分支指令 labelsMap 也存分支标签order(labelsMap, offset + op.format.size);// 首先把当前指令的大小偏移+大小是下一个指令int u1SwitchData = 2 * (offset + sint(insns, u1offset + 2));// index table的偏移(32位  4*8)(offset+指令(8位一个byte)// +寄存器// (8位一个byte))if (u1SwitchData + 2 < insns.length) {switch (insns[u1SwitchData + 1]) {//根据该字段确定switch类型case 0x01: // packed-switch-data  case常量彼此相邻,使用了一个index索引表{int size = ushort(insns, u1SwitchData + 2);//case常量个数int b = u1SwitchData + 8;// targetsfor (int i = 0; i < size; i++) {//把case分支跳转添加到队列和labelsMaptarget = offset + sint(insns, b + i * 4);if (target < 0 || target * 2 > insns.length) {throw new BadOpException("jump out of insns %s -> %04x", op, target);}q.add(target);order(labelsMap, target);}break;}case 0x02:// sparse-switch-data case常量不相邻{int size = ushort(insns, u1SwitchData + 2);int b = u1SwitchData + 4 + 4 * size;// targetsfor (int i = 0; i < size; i++) {target = offset + sint(insns, b + i * 4);if (target < 0 || target * 2 > insns.length) {throw new BadOpException("jump out of insns %s -> %04x", op, target);}q.add(target);order(labelsMap, target);}break;}default:throw new BadOpException("bad payload for %s", op);}} else {throw new BadOpException("bad payload offset for %s", op);}}if (canContinue) {//是否继续int idx = Integer.MAX_VALUE;switch (op.indexType) {//索引类型case kIndexStringRef://字符串索引if (op.format == InstructionFormat.kFmt31c) {idx = uint(insns, u1offset + 2);} else {// otheridx = ushort(insns, u1offset + 2);}canContinue = idx >= 0 && idx < string_ids_size;break;case kIndexTypeRef://类型索引idx = ushort(insns, u1offset + 2);canContinue = idx < type_ids_size;break;case kIndexMethodRef://方法索引idx = ushort(insns, u1offset + 2);canContinue = idx < method_ids_size;break;case kIndexFieldRef://字段索引idx = ushort(insns, u1offset + 2);//读取字(指令偏移2字节)canContinue = idx < field_ids_size;//小于字段总数 则可以继续break;default:}if (!canContinue) {throw new BadOpException("index-out-of-range for %s index: %d", op, idx);}}if (canContinue && op.canContinue()) {//是否继续执行if (op == Op.NOP) {//是否是空操作switch (insns[u1offset + 1]) {case 0x00:q.add(offset + op.format.size);break;case 0x01: {int size = ushort(insns, u1offset + 2);q.add(offset + (size * 2) + 4);break;}case 0x02: {int size = ushort(insns, u1offset + 2);q.add(offset + (size * 4) + 2);break;}case 0x03: {int element_width = ushort(insns, u1offset + 2);int size = uint(insns, u1offset + 4);q.add(offset + (size * element_width + 1) / 2 + 4);break;}}} else {q.add(offset + op.format.size);//把指令偏移和大小之和添加到q}}}

这里主要是获取指令的操作码,根据操作码获取指令 的格式,是否是跳转,switch指令,指令的格式等

指令格式定义了如下类型

public enum InstructionFormat {//op表示一个8位的操作码// kFmt00x(0), // unknown format (also used for "breakpoint" opcode)kFmt10x(1), // op  该指令由一个16进制 使用0个寄存器 无额外数据kFmt12x(1), // op vA, vB 该指令由一个16进制 使用2个寄存器 无额外数据kFmt11n(1), // op vA, #+B 该指令由一个16进制 使用1个寄存器 有一个4位立即数kFmt11x(1), // op vAA  该指令由一个16进制 使用1个寄存器(占8位) 无额外数据kFmt10t(1), // op +AA   该指令由一个16进制 使用0个寄存器 跳转(占8位)// kFmt20bc(2), // [opt] op AA, thing@BBBBkFmt20t(2), // op +AAAA该指令由2个16进制 使用0个寄存器 跳转(占8位)kFmt22x(2), // op vAA, vBBBBkFmt21t(2), // op vAA, +BBBB 有2个16进制组成,使用一个寄存器,跳转分支指令kFmt21s(2), // op vAA, #+BBBBkFmt21h(2), // op vAA, #+BBBB00000[00000000]kFmt21c(2), // op vAA, thing@BBBBkFmt23x(2), // op vAA, vBB, vCCkFmt22b(2), // op vAA, vBB, #+CCkFmt22t(2), // op vA, vB, +CCCCkFmt22s(2), // op vA, vB, #+CCCCkFmt22c(2), // op vA, vB, thing@CCCC// kFmt22cs(2), // [opt] op vA, vB, field offset CCCCkFmt30t(3), // op +AAAAAAAAkFmt32x(3), // op vAAAA, vBBBBkFmt31i(3), // op vAA, #+BBBBBBBBkFmt31t(3), // op vAA, +BBBBBBBBkFmt31c(3), // op vAA, string@BBBBBBBBkFmt35c(3), // op {vC,vD,vE,vF,vG}, thing@BBBB// kFmt35ms(3), // [opt] invoke-virtual+superkFmt3rc(3), // op {vCCCC .. v(CCCC+AA-1)}, thing@BBBB// kFmt3rms(3), // [opt] invoke-virtual+super/rangekFmt51l(5), // op vAA, #+BBBBBBBBBBBBBBBB// kFmt35mi(3), // [opt] inline invoke// kFmt3rmi(3), // [opt] inline invoke/range;public int size;InstructionFormat(int size) {this.size = size;}
};

然后下面是所有指令的定义

public enum Op implements CFG {NOP(0x00, "nop", kFmt10x, kIndexNone, kInstrCanContinue, false), //MOVE(0x01, "move", kFmt12x, kIndexNone, kInstrCanContinue, true), //MOVE_FROM16(0x02, "move/from16", kFmt22x, kIndexNone, kInstrCanContinue, true), //MOVE_16(0x03, "move/16", kFmt32x, kIndexNone, kInstrCanContinue, true), //MOVE_WIDE(0x04, "move-wide", kFmt12x, kIndexNone, kInstrCanContinue, true), //MOVE_WIDE_FROM16(0x05, "move-wide/from16", kFmt22x, kIndexNone, kInstrCanContinue, true), //MOVE_WIDE_16(0x06, "move-wide/16", kFmt32x, kIndexNone, kInstrCanContinue, true), //MOVE_OBJECT(0x07, "move-object", kFmt12x, kIndexNone, kInstrCanContinue, true), //MOVE_OBJECT_FROM16(0x08, "move-object/from16", kFmt22x, kIndexNone, kInstrCanContinue, true), //MOVE_OBJECT_16(0x09, "move-object/16", kFmt32x, kIndexNone, kInstrCanContinue, true), //MOVE_RESULT(0x0a, "move-result", kFmt11x, kIndexNone, kInstrCanContinue, true), //MOVE_RESULT_WIDE(0x0b, "move-result-wide", kFmt11x, kIndexNone, kInstrCanContinue, true), //MOVE_RESULT_OBJECT(0x0c, "move-result-object", kFmt11x, kIndexNone, kInstrCanContinue, true), //MOVE_EXCEPTION(0x0d, "move-exception", kFmt11x, kIndexNone, kInstrCanContinue, true), //RETURN_VOID(0x0e, "return-void", kFmt10x, kIndexNone, kInstrCanReturn, false), //RETURN(0x0f, "return", kFmt11x, kIndexNone, kInstrCanReturn, false), //RETURN_WIDE(0x10, "return-wide", kFmt11x, kIndexNone, kInstrCanReturn, false), //RETURN_OBJECT(0x11, "return-object", kFmt11x, kIndexNone, kInstrCanReturn, false), //CONST_4(0x12, "const/4", kFmt11n, kIndexNone, kInstrCanContinue, true), //CONST_16(0x13, "const/16", kFmt21s, kIndexNone, kInstrCanContinue, true), //CONST(0x14, "const", kFmt31i, kIndexNone, kInstrCanContinue, true), //CONST_HIGH16(0x15, "const/high16", kFmt21h, kIndexNone, kInstrCanContinue, true), //CONST_WIDE_16(0x16, "const-wide/16", kFmt21s, kIndexNone, kInstrCanContinue, true), //CONST_WIDE_32(0x17, "const-wide/32", kFmt31i, kIndexNone, kInstrCanContinue, true), //CONST_WIDE(0x18, "const-wide", kFmt51l, kIndexNone, kInstrCanContinue, true), //CONST_WIDE_HIGH16(0x19, "const-wide/high16", kFmt21h, kIndexNone, kInstrCanContinue, true), //CONST_STRING(0x1a, "const-string", kFmt21c, kIndexStringRef, kInstrCanContinue | kInstrCanThrow, true), //CONST_STRING_JUMBO(0x1b, "const-string/jumbo", kFmt31c, kIndexStringRef, kInstrCanContinue | kInstrCanThrow, true), //CONST_CLASS(0x1c, "const-class", kFmt21c, kIndexTypeRef, kInstrCanContinue | kInstrCanThrow, true), //MONITOR_ENTER(0x1d, "monitor-enter", kFmt11x, kIndexNone, kInstrCanContinue | kInstrCanThrow, false), //MONITOR_EXIT(0x1e, "monitor-exit", kFmt11x, kIndexNone, kInstrCanContinue | kInstrCanThrow, false), //CHECK_CAST(0x1f, "check-cast", kFmt21c, kIndexTypeRef, kInstrCanContinue | kInstrCanThrow, true), //INSTANCE_OF(0x20, "instance-of", kFmt22c, kIndexTypeRef, kInstrCanContinue | kInstrCanThrow, true), //ARRAY_LENGTH(0x21, "array-length", kFmt12x, kIndexNone, kInstrCanContinue | kInstrCanThrow, true), //NEW_INSTANCE(0x22, "new-instance", kFmt21c, kIndexTypeRef, kInstrCanContinue | kInstrCanThrow, true), //NEW_ARRAY(0x23, "new-array", kFmt22c, kIndexTypeRef, kInstrCanContinue | kInstrCanThrow, true), //FILLED_NEW_ARRAY(0x24, "filled-new-array", kFmt35c, kIndexTypeRef, kInstrCanContinue | kInstrCanThrow, true), //FILLED_NEW_ARRAY_RANGE(0x25, "filled-new-array/range", kFmt3rc, kIndexTypeRef, kInstrCanContinue | kInstrCanThrow,true), //FILL_ARRAY_DATA(0x26, "fill-array-data", kFmt31t, kIndexNone, kInstrCanContinue, false), //THROW(0x27, "throw", kFmt11x, kIndexNone, kInstrCanThrow, false), //GOTO(0x28, "goto", kFmt10t, kIndexNone, kInstrCanBranch, false), //GOTO_16(0x29, "goto/16", kFmt20t, kIndexNone, kInstrCanBranch, false), //GOTO_32(0x2a, "goto/32", kFmt30t, kIndexNone, kInstrCanBranch, false), //PACKED_SWITCH(0x2b, "packed-switch", kFmt31t, kIndexNone, kInstrCanContinue | kInstrCanSwitch, false), //SPARSE_SWITCH(0x2c, "sparse-switch", kFmt31t, kIndexNone, kInstrCanContinue | kInstrCanSwitch, false), //CMPL_FLOAT(0x2d, "cmpl-float", kFmt23x, kIndexNone, kInstrCanContinue, false), //CMPG_FLOAT(0x2e, "cmpg-float", kFmt23x, kIndexNone, kInstrCanContinue, false), //CMPL_DOUBLE(0x2f, "cmpl-double", kFmt23x, kIndexNone, kInstrCanContinue, false), //CMPG_DOUBLE(0x30, "cmpg-double", kFmt23x, kIndexNone, kInstrCanContinue, false), //CMP_LONG(0x31, "cmp-long", kFmt23x, kIndexNone, kInstrCanContinue, false), //IF_EQ(0x32, "if-eq", kFmt22t, kIndexNone, kInstrCanBranch | kInstrCanContinue, false), //IF_NE(0x33, "if-ne", kFmt22t, kIndexNone, kInstrCanBranch | kInstrCanContinue, false), //IF_LT(0x34, "if-lt", kFmt22t, kIndexNone, kInstrCanBranch | kInstrCanContinue, false), //IF_GE(0x35, "if-ge", kFmt22t, kIndexNone, kInstrCanBranch | kInstrCanContinue, false), //IF_GT(0x36, "if-gt", kFmt22t, kIndexNone, kInstrCanBranch | kInstrCanContinue, false), //IF_LE(0x37, "if-le", kFmt22t, kIndexNone, kInstrCanBranch | kInstrCanContinue, false), //IF_EQZ(0x38, "if-eqz", kFmt21t, kIndexNone, kInstrCanBranch | kInstrCanContinue, false), //IF_NEZ(0x39, "if-nez", kFmt21t, kIndexNone, kInstrCanBranch | kInstrCanContinue, false), //IF_LTZ(0x3a, "if-ltz", kFmt21t, kIndexNone, kInstrCanBranch | kInstrCanContinue, false), //IF_GEZ(0x3b, "if-gez", kFmt21t, kIndexNone, kInstrCanBranch | kInstrCanContinue, false), //IF_GTZ(0x3c, "if-gtz", kFmt21t, kIndexNone, kInstrCanBranch | kInstrCanContinue, false), //IF_LEZ(0x3d, "if-lez", kFmt21t, kIndexNone, kInstrCanBranch | kInstrCanContinue, false), //
//    UNUSED_3E(0x3e, "unused-3e", null, kIndexUnknown, 0, false), //
//    UNUSED_3F(0x3f, "unused-3f", null, kIndexUnknown, 0, false), //
//    UNUSED_40(0x40, "unused-40", null, kIndexUnknown, 0, false), //
//    UNUSED_41(0x41, "unused-41", null, kIndexUnknown, 0, false), //
//    UNUSED_42(0x42, "unused-42", null, kIndexUnknown, 0, false), //
//    UNUSED_43(0x43, "unused-43", null, kIndexUnknown, 0, false), //AGET(0x44, "aget", kFmt23x, kIndexNone, kInstrCanContinue | kInstrCanThrow, true), //AGET_WIDE(0x45, "aget-wide", kFmt23x, kIndexNone, kInstrCanContinue | kInstrCanThrow, true), //AGET_OBJECT(0x46, "aget-object", kFmt23x, kIndexNone, kInstrCanContinue | kInstrCanThrow, true), //AGET_BOOLEAN(0x47, "aget-boolean", kFmt23x, kIndexNone, kInstrCanContinue | kInstrCanThrow, true), //AGET_BYTE(0x48, "aget-byte", kFmt23x, kIndexNone, kInstrCanContinue | kInstrCanThrow, true), //AGET_CHAR(0x49, "aget-char", kFmt23x, kIndexNone, kInstrCanContinue | kInstrCanThrow, true), //AGET_SHORT(0x4a, "aget-short", kFmt23x, kIndexNone, kInstrCanContinue | kInstrCanThrow, true), //APUT(0x4b, "aput", kFmt23x, kIndexNone, kInstrCanContinue | kInstrCanThrow, false), //APUT_WIDE(0x4c, "aput-wide", kFmt23x, kIndexNone, kInstrCanContinue | kInstrCanThrow, false), //APUT_OBJECT(0x4d, "aput-object", kFmt23x, kIndexNone, kInstrCanContinue | kInstrCanThrow, false), //APUT_BOOLEAN(0x4e, "aput-boolean", kFmt23x, kIndexNone, kInstrCanContinue | kInstrCanThrow, false), //APUT_BYTE(0x4f, "aput-byte", kFmt23x, kIndexNone, kInstrCanContinue | kInstrCanThrow, false), //APUT_CHAR(0x50, "aput-char", kFmt23x, kIndexNone, kInstrCanContinue | kInstrCanThrow, false), //APUT_SHORT(0x51, "aput-short", kFmt23x, kIndexNone, kInstrCanContinue | kInstrCanThrow, false), //IGET(0x52, "iget", kFmt22c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, true), //IGET_WIDE(0x53, "iget-wide", kFmt22c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, true), //IGET_OBJECT(0x54, "iget-object", kFmt22c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, true), //IGET_BOOLEAN(0x55, "iget-boolean", kFmt22c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, true), //IGET_BYTE(0x56, "iget-byte", kFmt22c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, true), //IGET_CHAR(0x57, "iget-char", kFmt22c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, true), //IGET_SHORT(0x58, "iget-short", kFmt22c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, true), //IPUT(0x59, "iput", kFmt22c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, false), //IPUT_WIDE(0x5a, "iput-wide", kFmt22c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, false), //IPUT_OBJECT(0x5b, "iput-object", kFmt22c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, false), //IPUT_BOOLEAN(0x5c, "iput-boolean", kFmt22c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, false), //IPUT_BYTE(0x5d, "iput-byte", kFmt22c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, false), //IPUT_CHAR(0x5e, "iput-char", kFmt22c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, false), //IPUT_SHORT(0x5f, "iput-short", kFmt22c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, false), //SGET(0x60, "sget", kFmt21c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, true), //SGET_WIDE(0x61, "sget-wide", kFmt21c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, true), //SGET_OBJECT(0x62, "sget-object", kFmt21c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, true), //SGET_BOOLEAN(0x63, "sget-boolean", kFmt21c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, true), //SGET_BYTE(0x64, "sget-byte", kFmt21c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, true), //SGET_CHAR(0x65, "sget-char", kFmt21c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, true), //SGET_SHORT(0x66, "sget-short", kFmt21c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, true), //SPUT(0x67, "sput", kFmt21c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, false), //SPUT_WIDE(0x68, "sput-wide", kFmt21c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, false), //SPUT_OBJECT(0x69, "sput-object", kFmt21c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, false), //SPUT_BOOLEAN(0x6a, "sput-boolean", kFmt21c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, false), //SPUT_BYTE(0x6b, "sput-byte", kFmt21c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, false), //SPUT_CHAR(0x6c, "sput-char", kFmt21c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, false), //SPUT_SHORT(0x6d, "sput-short", kFmt21c, kIndexFieldRef, kInstrCanContinue | kInstrCanThrow, false), //INVOKE_VIRTUAL(0x6e, "invoke-virtual", kFmt35c, kIndexMethodRef, kInstrCanContinue | kInstrCanThrow | kInstrInvoke,true), //INVOKE_SUPER(0x6f, "invoke-super", kFmt35c, kIndexMethodRef, kInstrCanContinue | kInstrCanThrow | kInstrInvoke,true), //INVOKE_DIRECT(0x70, "invoke-direct", kFmt35c, kIndexMethodRef, kInstrCanContinue | kInstrCanThrow | kInstrInvoke,true), //INVOKE_STATIC(0x71, "invoke-static", kFmt35c, kIndexMethodRef, kInstrCanContinue | kInstrCanThrow | kInstrInvoke,true), //INVOKE_INTERFACE(0x72, "invoke-interface", kFmt35c, kIndexMethodRef, kInstrCanContinue | kInstrCanThrow| kInstrInvoke, true), //
//    UNUSED_73(0x73, "unused-73", null, kIndexUnknown, 0, false), //INVOKE_VIRTUAL_RANGE(0x74, "invoke-virtual/range", kFmt3rc, kIndexMethodRef, kInstrCanContinue | kInstrCanThrow| kInstrInvoke, true), //INVOKE_SUPER_RANGE(0x75, "invoke-super/range", kFmt3rc, kIndexMethodRef, kInstrCanContinue | kInstrCanThrow| kInstrInvoke, true), //INVOKE_DIRECT_RANGE(0x76, "invoke-direct/range", kFmt3rc, kIndexMethodRef, kInstrCanContinue | kInstrCanThrow| kInstrInvoke, true), //INVOKE_STATIC_RANGE(0x77, "invoke-static/range", kFmt3rc, kIndexMethodRef, kInstrCanContinue | kInstrCanThrow| kInstrInvoke, true), //INVOKE_INTERFACE_RANGE(0x78, "invoke-interface/range", kFmt3rc, kIndexMethodRef, kInstrCanContinue | kInstrCanThrow| kInstrInvoke, true), //
//    UNUSED_79(0x79, "unused-79", null, kIndexUnknown, 0, false), //
//    UNUSED_7A(0x7a, "unused-7a", null, kIndexUnknown, 0, false), //NEG_INT(0x7b, "neg-int", kFmt12x, kIndexNone, kInstrCanContinue, true), //NOT_INT(0x7c, "not-int", kFmt12x, kIndexNone, kInstrCanContinue, true), //NEG_LONG(0x7d, "neg-long", kFmt12x, kIndexNone, kInstrCanContinue, true), //NOT_LONG(0x7e, "not-long", kFmt12x, kIndexNone, kInstrCanContinue, true), //NEG_FLOAT(0x7f, "neg-float", kFmt12x, kIndexNone, kInstrCanContinue, true), //NEG_DOUBLE(0x80, "neg-double", kFmt12x, kIndexNone, kInstrCanContinue, true), //INT_TO_LONG(0x81, "int-to-long", kFmt12x, kIndexNone, kInstrCanContinue, true), //INT_TO_FLOAT(0x82, "int-to-float", kFmt12x, kIndexNone, kInstrCanContinue, true), //INT_TO_DOUBLE(0x83, "int-to-double", kFmt12x, kIndexNone, kInstrCanContinue, true), //LONG_TO_INT(0x84, "long-to-int", kFmt12x, kIndexNone, kInstrCanContinue, true), //LONG_TO_FLOAT(0x85, "long-to-float", kFmt12x, kIndexNone, kInstrCanContinue, true), //LONG_TO_DOUBLE(0x86, "long-to-double", kFmt12x, kIndexNone, kInstrCanContinue, true), //FLOAT_TO_INT(0x87, "float-to-int", kFmt12x, kIndexNone, kInstrCanContinue, true), //FLOAT_TO_LONG(0x88, "float-to-long", kFmt12x, kIndexNone, kInstrCanContinue, true), //FLOAT_TO_DOUBLE(0x89, "float-to-double", kFmt12x, kIndexNone, kInstrCanContinue, true), //DOUBLE_TO_INT(0x8a, "double-to-int", kFmt12x, kIndexNone, kInstrCanContinue, true), //DOUBLE_TO_LONG(0x8b, "double-to-long", kFmt12x, kIndexNone, kInstrCanContinue, true), //DOUBLE_TO_FLOAT(0x8c, "double-to-float", kFmt12x, kIndexNone, kInstrCanContinue, true), //INT_TO_BYTE(0x8d, "int-to-byte", kFmt12x, kIndexNone, kInstrCanContinue, true), //INT_TO_CHAR(0x8e, "int-to-char", kFmt12x, kIndexNone, kInstrCanContinue, true), //INT_TO_SHORT(0x8f, "int-to-short", kFmt12x, kIndexNone, kInstrCanContinue, true), //ADD_INT(0x90, "add-int", kFmt23x, kIndexNone, kInstrCanContinue, true), //SUB_INT(0x91, "sub-int", kFmt23x, kIndexNone, kInstrCanContinue, true), //MUL_INT(0x92, "mul-int", kFmt23x, kIndexNone, kInstrCanContinue, true), //DIV_INT(0x93, "div-int", kFmt23x, kIndexNone, kInstrCanContinue | kInstrCanThrow, true), //REM_INT(0x94, "rem-int", kFmt23x, kIndexNone, kInstrCanContinue | kInstrCanThrow, true), //AND_INT(0x95, "and-int", kFmt23x, kIndexNone, kInstrCanContinue, true), //OR_INT(0x96, "or-int", kFmt23x, kIndexNone, kInstrCanContinue, true), //XOR_INT(0x97, "xor-int", kFmt23x, kIndexNone, kInstrCanContinue, true), //SHL_INT(0x98, "shl-int", kFmt23x, kIndexNone, kInstrCanContinue, true), //SHR_INT(0x99, "shr-int", kFmt23x, kIndexNone, kInstrCanContinue, true), //USHR_INT(0x9a, "ushr-int", kFmt23x, kIndexNone, kInstrCanContinue, true), //ADD_LONG(0x9b, "add-long", kFmt23x, kIndexNone, kInstrCanContinue, true), //SUB_LONG(0x9c, "sub-long", kFmt23x, kIndexNone, kInstrCanContinue, true), //MUL_LONG(0x9d, "mul-long", kFmt23x, kIndexNone, kInstrCanContinue, true), //DIV_LONG(0x9e, "div-long", kFmt23x, kIndexNone, kInstrCanContinue | kInstrCanThrow, true), //REM_LONG(0x9f, "rem-long", kFmt23x, kIndexNone, kInstrCanContinue | kInstrCanThrow, true), //AND_LONG(0xa0, "and-long", kFmt23x, kIndexNone, kInstrCanContinue, true), //OR_LONG(0xa1, "or-long", kFmt23x, kIndexNone, kInstrCanContinue, true), //XOR_LONG(0xa2, "xor-long", kFmt23x, kIndexNone, kInstrCanContinue, true), //SHL_LONG(0xa3, "shl-long", kFmt23x, kIndexNone, kInstrCanContinue, true), //SHR_LONG(0xa4, "shr-long", kFmt23x, kIndexNone, kInstrCanContinue, true), //USHR_LONG(0xa5, "ushr-long", kFmt23x, kIndexNone, kInstrCanContinue, true), //ADD_FLOAT(0xa6, "add-float", kFmt23x, kIndexNone, kInstrCanContinue, true), //SUB_FLOAT(0xa7, "sub-float", kFmt23x, kIndexNone, kInstrCanContinue, true), //MUL_FLOAT(0xa8, "mul-float", kFmt23x, kIndexNone, kInstrCanContinue, true), //DIV_FLOAT(0xa9, "div-float", kFmt23x, kIndexNone, kInstrCanContinue, true), //REM_FLOAT(0xaa, "rem-float", kFmt23x, kIndexNone, kInstrCanContinue, true), //ADD_DOUBLE(0xab, "add-double", kFmt23x, kIndexNone, kInstrCanContinue, true), //SUB_DOUBLE(0xac, "sub-double", kFmt23x, kIndexNone, kInstrCanContinue, true), //MUL_DOUBLE(0xad, "mul-double", kFmt23x, kIndexNone, kInstrCanContinue, true), //DIV_DOUBLE(0xae, "div-double", kFmt23x, kIndexNone, kInstrCanContinue, true), //REM_DOUBLE(0xaf, "rem-double", kFmt23x, kIndexNone, kInstrCanContinue, true), //ADD_INT_2ADDR(0xb0, "add-int/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //SUB_INT_2ADDR(0xb1, "sub-int/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //MUL_INT_2ADDR(0xb2, "mul-int/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //DIV_INT_2ADDR(0xb3, "div-int/2addr", kFmt12x, kIndexNone, kInstrCanContinue | kInstrCanThrow, true), //REM_INT_2ADDR(0xb4, "rem-int/2addr", kFmt12x, kIndexNone, kInstrCanContinue | kInstrCanThrow, true), //AND_INT_2ADDR(0xb5, "and-int/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //OR_INT_2ADDR(0xb6, "or-int/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //XOR_INT_2ADDR(0xb7, "xor-int/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //SHL_INT_2ADDR(0xb8, "shl-int/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //SHR_INT_2ADDR(0xb9, "shr-int/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //USHR_INT_2ADDR(0xba, "ushr-int/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //ADD_LONG_2ADDR(0xbb, "add-long/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //SUB_LONG_2ADDR(0xbc, "sub-long/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //MUL_LONG_2ADDR(0xbd, "mul-long/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //DIV_LONG_2ADDR(0xbe, "div-long/2addr", kFmt12x, kIndexNone, kInstrCanContinue | kInstrCanThrow, true), //REM_LONG_2ADDR(0xbf, "rem-long/2addr", kFmt12x, kIndexNone, kInstrCanContinue | kInstrCanThrow, true), //AND_LONG_2ADDR(0xc0, "and-long/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //OR_LONG_2ADDR(0xc1, "or-long/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //XOR_LONG_2ADDR(0xc2, "xor-long/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //SHL_LONG_2ADDR(0xc3, "shl-long/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //SHR_LONG_2ADDR(0xc4, "shr-long/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //USHR_LONG_2ADDR(0xc5, "ushr-long/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //ADD_FLOAT_2ADDR(0xc6, "add-float/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //SUB_FLOAT_2ADDR(0xc7, "sub-float/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //MUL_FLOAT_2ADDR(0xc8, "mul-float/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //DIV_FLOAT_2ADDR(0xc9, "div-float/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //REM_FLOAT_2ADDR(0xca, "rem-float/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //ADD_DOUBLE_2ADDR(0xcb, "add-double/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //SUB_DOUBLE_2ADDR(0xcc, "sub-double/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //MUL_DOUBLE_2ADDR(0xcd, "mul-double/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //DIV_DOUBLE_2ADDR(0xce, "div-double/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //REM_DOUBLE_2ADDR(0xcf, "rem-double/2addr", kFmt12x, kIndexNone, kInstrCanContinue, true), //ADD_INT_LIT16(0xd0, "add-int/lit16", kFmt22s, kIndexNone, kInstrCanContinue, true), //RSUB_INT(0xd1, "rsub-int", kFmt22s, kIndexNone, kInstrCanContinue, true), //MUL_INT_LIT16(0xd2, "mul-int/lit16", kFmt22s, kIndexNone, kInstrCanContinue, true), //DIV_INT_LIT16(0xd3, "div-int/lit16", kFmt22s, kIndexNone, kInstrCanContinue | kInstrCanThrow, true), //REM_INT_LIT16(0xd4, "rem-int/lit16", kFmt22s, kIndexNone, kInstrCanContinue | kInstrCanThrow, true), //AND_INT_LIT16(0xd5, "and-int/lit16", kFmt22s, kIndexNone, kInstrCanContinue, true), //OR_INT_LIT16(0xd6, "or-int/lit16", kFmt22s, kIndexNone, kInstrCanContinue, true), //XOR_INT_LIT16(0xd7, "xor-int/lit16", kFmt22s, kIndexNone, kInstrCanContinue, true), //ADD_INT_LIT8(0xd8, "add-int/lit8", kFmt22b, kIndexNone, kInstrCanContinue, true), //RSUB_INT_LIT8(0xd9, "rsub-int/lit8", kFmt22b, kIndexNone, kInstrCanContinue, true), //MUL_INT_LIT8(0xda, "mul-int/lit8", kFmt22b, kIndexNone, kInstrCanContinue, true), //DIV_INT_LIT8(0xdb, "div-int/lit8", kFmt22b, kIndexNone, kInstrCanContinue | kInstrCanThrow, true), //REM_INT_LIT8(0xdc, "rem-int/lit8", kFmt22b, kIndexNone, kInstrCanContinue | kInstrCanThrow, true), //AND_INT_LIT8(0xdd, "and-int/lit8", kFmt22b, kIndexNone, kInstrCanContinue, true), //OR_INT_LIT8(0xde, "or-int/lit8", kFmt22b, kIndexNone, kInstrCanContinue, true), //XOR_INT_LIT8(0xdf, "xor-int/lit8", kFmt22b, kIndexNone, kInstrCanContinue, true), //SHL_INT_LIT8(0xe0, "shl-int/lit8", kFmt22b, kIndexNone, kInstrCanContinue, true), //SHR_INT_LIT8(0xe1, "shr-int/lit8", kFmt22b, kIndexNone, kInstrCanContinue, true), //USHR_INT_LIT8(0xe2, "ushr-int/lit8", kFmt22b, kIndexNone, kInstrCanContinue, true), //BAD_OP(-1, "bad-opcode", null, kIndexNone, 0, false), //;public int opcode;public InstructionFormat format;/* package */InstructionIndexType indexType;/* package */int flags;public String displayName;public final static Op ops[] = new Op[256];public boolean changeFrame;static {Op[] ops = Op.ops;for (Op op : Op.values()) {if (op.opcode >= 0) {ops[op.opcode] = op;}}}public boolean canBranch() {return 0 != (flags & kInstrCanBranch);}public boolean canContinue() {return 0 != (flags & kInstrCanContinue);}public boolean canReturn() {return 0 != (flags & kInstrCanReturn);}public boolean canSwitch() {return 0 != (flags & kInstrCanSwitch);}public boolean canThrow() {return 0 != (flags & kInstrCanThrow);}Op(int op, String displayName, InstructionFormat fmt, InstructionIndexType indexType, int flags, boolean changeFrame) {this.opcode = op;this.displayName = displayName;this.format = fmt;this.indexType = indexType;this.flags = flags;}public String toString() {return displayName;}
}

是否可跳转,继续等
这样 我们就把dex文件中的字段 ,方法,方法中的代码都解析完了,dex中的指令都解析成了 DexStmtNode。包括dex指令中用到的寄存器等,都保存在响应的dexStmtNode指令中。

先分析到这里,后面在分析dex转换为IR,以及优化后在转jvm指令,并写入class文件打成jar 包

dex2jar源码解析----解析dex文件一相关推荐

  1. Python源码剖析[16] —— Pyc文件解析

    Python源码剖析[16] -- Pyc文件解析 2008-02-28 18:29:55|  分类: Python |举报 |字号 订阅 Python源码剖析 --Pyc文件解析 本文作者: Rob ...

  2. PTMs:QLoRA技巧之源码解读(qlora.py文件)—解析命令与加载参数→数据预处理→模型训练+评估+推理

    PTMs:QLoRA技巧之源码解读(qlora.py文件)-解析命令与加载参数→数据预处理→模型训练+评估+推理 目录 QLoRA技巧之源码解读(qlora.py文件)-解析命令与加载参数→数据预处理 ...

  3. 从源码角度解析Android中APK安装过程

    从源码角度解析Android中APK的安装过程 1. Android中APK简介 Android应用Apk的安装有如下四种方式: 1.1 系统应用安装 没有安装界面,在开机时自动完成 1.2 网络下载 ...

  4. 【TarsosDSP】TarsosDSP 简介 ( TarsosDSP 功能 | 相关链接 | 源码和相关资源收集 | TarsosDSP 示例应用 | TarsosDSP 源码路径解析 )

    文章目录 I . TarsosDSP 函数库简介 II . TarsosDSP 功能 III . TarsosDSP 相关资源链接 ( 官方资料 ) IV . TarsosDSP 源码和相关资源收集 ...

  5. php的setinc方法,thinkphp3.2.0 setInc方法 源码全面解析

    搜索热词 我们先来看一下setInc的官方示例: 需要一个字段和一个自增的值(默认为1) 我们通过下面这个例子来一步步分析他的底层是怎么实现的: class TestController extend ...

  6. Go netpoll I/O 多路复用构建原生网络模型之源码深度解析

    原文 Go netpoll I/O 多路复用构建原生网络模型之源码深度解析 导言 Go 基于 I/O multiplexing 和 goroutine 构建了一个简洁而高性能的原生网络模型(基于 Go ...

  7. Java LockSupport以及park、unpark方法源码深度解析

    介绍了JUC中的LockSupport阻塞工具以及park.unpark方法的底层原理,从Java层面深入至JVM层面. 文章目录 1 LockSupport的概述 2 LockSupport的特征和 ...

  8. android SDK-25事件分发机制--源码正确解析

    android SDK-25事件分发机制–源码正确解析 Android 事件分发分为View和ViewGroup的事件分发,ViewGroup比View过一个拦截判断,viewgroup可以拦截事件, ...

  9. 【笔记-vue】《imooc-vue.js高仿饿了么》、《imooc-vue 音乐app》、《imooc-vue.js源码全方位解析》

    20170709 - 20171128:<imooc-vue.js高仿饿了么> 一.第一章 课程简介 1-1课程简介 1.需求分析-脚手架工具-数据mock-架构设计-代码编写-自测-编译 ...

最新文章

  1. Ajax的异步,是鸡肋还是鸡排?
  2. /bin/bash: [xxxx]: command not found
  3. 浅谈bash shell的种类以及linux系统中的profile和bashrc配置文件
  4. 2020亚太杯数学建模_比赛 | 2020年APMCM亚太地区大学生数学建模竞赛
  5. GDB技巧:使用checkpoint解决难以复现的Bug
  6. C++ 继承 | 对象切割、菱形继承、虚继承、对象组合
  7. 机器人 瓷砖墙面清洗_墙壁清洁机器人解析
  8. VS2017离线下载 -- 如何让VS2017不占用C盘
  9. 台式电脑怎么组装步骤_台式机组装教程,详细教您台式机怎么组装
  10. 三层交换机如何实现不同网络的相互通信
  11. 日志追踪-Java字节码-类文件结构
  12. Python地学分析 — 通过GPS数据分析鸟类行踪 07
  13. VMware Workstation15.5下载安装教程(win10)
  14. HarmonyOS助力构建“食用菌智慧农场”
  15. Hybrid混合开发学习笔记(1)混合应用开发定义和常见问题
  16. python开发office插件_看完这篇Python操作PPT总结,从此使用Python玩转Office全家桶就没有压力了!...
  17. 一文给你解决linux内存源码分析- SLUB分配器概述(超详细)
  18. 我的曲院风荷-让自己慢下来(13)
  19. 提前招面试要注意什么-面试要注意事项
  20. 烂怂if-else代码优化方案 | 京东云技术团队

热门文章

  1. 机器学习---线性回归推导以及python实现
  2. 猫,路由器,宽带(光纤,ADSL),带宽的区别和联系
  3. 流处理引擎:ksql
  4. RLC元件上电压,电流关系
  5. 使用ffmpeg转换文件格式,及ffmpeg参数说明(转)
  6. 网站搭建之三(APMServ5.2.6)
  7. TX2 用文件IO的方式操作GPIO
  8. opc ua 用哪种语言编写_OPC UA是个什么东东
  9. POJ - 3264
  10. 《Test-Driven Development for Embedded C》读书笔记(三)