/*
* Copyright (c) 2009-2012 Panxiaobo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.googlecode.dex2jar.reader;
import com.googlecode.dex2jar.DexException;
import com.googlecode.dex2jar.DexOpcodes;
import com.googlecode.dex2jar.Field;
import com.googlecode.dex2jar.Method;
import com.googlecode.dex2jar.reader.io.ArrayDataIn;
import com.googlecode.dex2jar.reader.io.DataIn;
import com.googlecode.dex2jar.reader.io.OffsetedDataIn;
import com.googlecode.dex2jar.visitors.*;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UTFDataFormatException;
import java.nio.charset.Charset;
import java.util.*;
import java.util.logging.Logger;
/**
* Open and read a dex file.this is the entrance of dex-reader. to read a dex/odex, use the following code:
* <p/>
* <pre>
* DexFileVisitor visitor = new xxxFileVisitor();
* DexFileReader reader = new DexFileReader(dexFile);
* reader.accept(reader);
* </pre>
*
* @author <a href="mailto:pxb1988@gmail.com">Panxiaobo</a>
* @version $Rev$
*/
public class DexFileReader {
private static final byte[] DEX_FILE_MAGIC = new byte[]{0x64, 0x65, 0x78};
private static final byte[] ODEX_FILE_MAGIC = new byte[]{0x64, 0x65, 0x79};
private static final byte[] VERSION_035 = new byte[]{0x30, 0x33, 0x35};
private static final byte[] VERSION_036 = new byte[]{0x30, 0x33, 0x36};
/* default */static final int ENDIAN_CONSTANT = 0x12345678;
/* default */static final int REVERSE_ENDIAN_CONSTANT = 0x78563412;
private static final int DEFAULT_API_LEVEL = 13;
private static final Logger log = Logger.getLogger(DexFileReader.class.getName());
private int class_defs_off;
private int class_defs_size;
private int field_ids_off;
private int field_ids_size;
private DataIn in;
private int method_ids_off;
private int method_ids_size;
private int proto_ids_off;
private int proto_ids_size;
private int string_ids_off;
private int string_ids_size;
private int type_ids_off;
private int type_ids_size;
/**
* skip debug infos in dex file.
*/
public static final int SKIP_DEBUG = 1;
/**
* skip code info in dex file, this indicate {@link #SKIP_DEBUG}
*/
public static final int SKIP_CODE = 1 << 2;
/**
* skip annotation info in dex file.
*/
public static final int SKIP_ANNOTATION = 1 << 3;
/**
* skip field constant in dex file.
*/
public static final int SKIP_FIELD_CONSTANT = 1 << 4;
private final boolean odex;
private DataIn odex_in;
private int odex_depsOffset;
/* package */ int apiLevel = DEFAULT_API_LEVEL;
private boolean apiLevelSetted = false;
/**
* read the dex file from byte array, if the byte array is a zip stream, it will return the content of classes.dex
* in the zip stream.
*
* @param data
* @return
* @throws IOException
*/
public static byte[] readDex(byte[] data) throws IOException {
if ("de".equals(new String(data, 0, 2))) {// dex/y
return data;
} else if ("PK".equals(new String(data, 0, 2))) {// ZIP
ZipExtractor ze;
try {
// if we got commons-compress in the classpath
Class.forName("org.apache.commons.compress.archivers.zip.ZipArchiveInputStream");
ze = new CCZipExtractor();
} catch (ClassNotFoundException e) {
ze = new ZipExtractor();
}
return ze.extract(data, "classes.dex");
}
throw new RuntimeException("the src file not a .dex, .odex or zip file");
}
public ArrayList<String> loadStrings() {
ArrayList<String> stringList = new ArrayList<String>(string_ids_size);
for (int sid = 0; sid < this.string_ids_size; sid++) {
stringList.add(getString(sid));
}
return stringList;
}
/**
* read the dex file from file, if the file is a zip file, it will return the content of classes.dex in the zip
* file.
*
* @param file
* @return
* @throws IOException
*/
public static byte[] readDex(File file) throws IOException {
return readDex(FileUtils.readFileToByteArray(file));
}
/**
* read dex from a {@link DataIn}.
*
* @param in
*/
public DexFileReader(DataIn in) {
in.move(0);
byte[] magic = in.readBytes(3);
if (Arrays.equals(magic, DEX_FILE_MAGIC)) {
odex = false;
} else if (Arrays.equals(magic, ODEX_FILE_MAGIC)) {
odex = true;
odex_in = in;
} else {
throw new DexException("not support magic.");
}
in.skip(1);// 0x0A
byte[] version = in.readBytes(3);
if (!Arrays.equals(version, VERSION_035) && !Arrays.equals(version, VERSION_036)) {
throw new DexException("not support version.");
}
in.skip(1);// 0x00
if (odex) {
int base = in.readIntx();// odex_dexOffset
in.skip(4);// odex_dexLength
odex_depsOffset = in.readIntx();
in = new OffsetedDataIn(in, base);
in.skip(8);// skip head;
}
// skip uint checksum
// and 20 bytes signature
// and uint file_size
// and uint header_size 0x70
in.skip(4 + 20 + 4 + 4);
int endian_tag = in.readUIntx();
if (endian_tag != ENDIAN_CONSTANT) {
throw new DexException("not support endian_tag");
}
this.in = in;
// skip uint link_size
// and uint link_off
// and uint map_off
in.skip(4 + 4 + 4);
string_ids_size = in.readUIntx();
string_ids_off = in.readUIntx();
type_ids_size = in.readUIntx();
type_ids_off = in.readUIntx();
proto_ids_size = in.readUIntx();
proto_ids_off = in.readUIntx();
field_ids_size = in.readUIntx();
field_ids_off = in.readUIntx();
method_ids_size = in.readUIntx();
method_ids_off = in.readUIntx();
class_defs_size = in.readUIntx();
class_defs_off = in.readUIntx();
// skip uint data_size data_off
}
private static final boolean isLittleEndian = true;
static private DataIn opDataIn(byte[] data) {
try {
return new ArrayDataIn(readDex(data), isLittleEndian);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new DexException(e);
}
}
/**
* read dex from a byte array
*
* @param data a dex/odex file or a zip file contains classes.dex
*/
public DexFileReader(byte[] data) {
this(opDataIn(data));
}
/**
* read dex from a file
*
* @param file a dex/odex file or a zip file contains classes.dex
* @throws IOException
*/
public DexFileReader(File file) throws IOException {
this(FileUtils.readFileToByteArray(file));
}
/**
* read dex from a {@link InputStream}
*
* @param in a dex/odex file or a zip file contains classes.dex
* @throws IOException
*/
public DexFileReader(InputStream in) throws IOException {
this(IOUtils.toByteArray(in));
}
/**
* equals to {@link #accept(DexFileVisitor, int)} with 0 as config
*
* @param dv
*/
public void accept(DexFileVisitor dv) {
this.accept(dv, 0);
}
/**
* Makes the given visitor visit the dex file.
*
* @param dexFileVisitor visitor
* @param config config flags, {@link #SKIP_CODE}, {@link #SKIP_DEBUG}, {@link #SKIP_ANNOTATION},
* {@link #SKIP_FIELD_CONSTANT}
*/
public void accept(DexFileVisitor dexFileVisitor, int config) {
if (odex && !apiLevelSetted) {
log.warning("read an odex file without setting the apiLevel, use " + DEFAULT_API_LEVEL + " as default.");
}
if (odex && dexFileVisitor instanceof OdexFileVisitor) {
DataIn in = this.odex_in;
OdexFileVisitor odv = (OdexFileVisitor) dexFileVisitor;
in.pushMove(odex_depsOffset);
try {
in.skip(4 * 3);// skip modificationTime,crc,dalvikBuild
int size = in.readIntx();
for (int i = 0; i < size; i++) {
int length = in.readIntx();
odv.visitDepedence(new String(in.readBytes(length), 0, length - 1, UTF8), in.readBytes(20));
}
} finally {
in.pop();
}
}
DataIn in = this.in;
for (int cid = 0; cid < class_defs_size; cid++) {
int idxOffset = this.class_defs_off + cid * 32;
in.pushMove(idxOffset);
String className;
try {
className = this.getType(in.readUIntx());
int access_flags = in.readUIntx();
int superclass_idx = in.readUIntx();
String superClassName = superclass_idx == -1 ? null : this.getType(superclass_idx);
// 获取接口
String[] interfaceNames = null;
{
int interfaces_off = in.readUIntx();
if (interfaces_off != 0) {
in.pushMove(interfaces_off);
try {
int size = in.readUIntx();
interfaceNames = new String[size];
for (int i = 0; i < size; i++) {
interfaceNames[i] = getType(in.readUShortx());
}
} finally {
in.pop();
}
}
}
DexClassVisitor dexClassVisitor = dexFileVisitor.visit(access_flags, className, superClassName, interfaceNames);
if (dexClassVisitor != null)// 不处理
{
acceptClass(dexFileVisitor, dexClassVisitor, className, config);
}
} finally {
in.pop();
}
}
dexFileVisitor.visitEnd();
}
public void visitClass(DexFileVisitor dv, int cid, int config) {
DataIn in = this.in;
int idxOffset = this.class_defs_off + cid * 32;
in.pushMove(idxOffset);
String className;
try {
className = this.getType(in.readUIntx());
int access_flags = in.readUIntx();
int superclass_idx = in.readUIntx();
String superClassName = superclass_idx == -1 ? null : this.getType(superclass_idx);
// 获取接口
String[] interfaceNames = null;
{
int interfaces_off = in.readUIntx();
if (interfaces_off != 0) {
in.pushMove(interfaces_off);
try {
int size = in.readUIntx();
interfaceNames = new String[size];
for (int i = 0; i < size; i++) {
interfaceNames[i] = getType(in.readUShortx());
}
} finally {
in.pop();
}
}
}
DexClassVisitor dcv = dv.visit(access_flags, className, superClassName, interfaceNames);
// 不处理
if (dcv != null) {
try {
acceptClass(dv, dcv, className, config);
// acceptClass(dcv, className, config);
} catch (Exception e) {
System.out.println("Can't Skip Class!!!!");
}
}
} finally {
in.pop();
}
}
private void acceptClass(DexFileVisitor dexFileVisitor, DexClassVisitor dexClassVisitor,
String className, int config) {
DataIn in = this.in;
int source_file_idx = in.readUIntx();
if ((config & SKIP_DEBUG) == 0) {
// 获取源文件
if (source_file_idx != -1) {
dexClassVisitor.visitSource(this.getString(source_file_idx));
}
}
int annotations_off = in.readUIntx();
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) {
in.pushMove(annotations_off);
try {
int class_annotations_off = in.readUIntx();
if (class_annotations_off != 0) {
in.pushMove(class_annotations_off);
try {
DexAnnotationReader.accept(this, in, dexClassVisitor);
} catch (Exception e) {
throw new RuntimeException("error on reading Annotation of class " + className, e);
} finally {
in.pop();
}
}
int field_annotation_size = in.readUIntx();
int method_annotation_size = in.readUIntx();
int parameter_annotation_size = in.readUIntx();
for (int i = 0; i < field_annotation_size; i++) {
int field_idx = in.readUIntx();
int field_annotations_offset = in.readUIntx();
fieldAnnotationPositions.put(field_idx, field_annotations_offset);
}
for (int i = 0; i < method_annotation_size; i++) {
int method_idx = in.readUIntx();
int method_annotation_offset = in.readUIntx();
methodAnnotationPositions.put(method_idx, method_annotation_offset);
}
for (int i = 0; i < parameter_annotation_size; i++) {
int method_idx = in.readUIntx();
int parameter_annotation_offset = in.readUIntx();
paramAnnotationPositions.put(method_idx, parameter_annotation_offset);
}
} finally {
in.pop();
}
}
} else {
fieldAnnotationPositions = null;
methodAnnotationPositions = null;
paramAnnotationPositions = null;
}
int class_data_off = in.readUIntx();
int static_values_off = in.readUIntx();
if (class_data_off != 0) {
in.pushMove(class_data_off);
try {
int static_fields = (int) in.readULeb128();
int instance_fields = (int) in.readULeb128();
int direct_methods = (int) in.readULeb128();
int virtual_methods = (int) in.readULeb128();
{
int lastIndex = 0;
{
Object[] constant = null;
if ((config & SKIP_FIELD_CONSTANT) == 0) {
if (static_values_off != 0) {
in.pushMove(static_values_off);
try {
int size = (int) in.readULeb128();
constant = new Object[size];
for (int i = 0; i < size; i++) {
constant[i] = Constant.ReadConstant(this, in);
}
} finally {
in.pop();
}
}
}
for (int i = 0; i < static_fields; i++) {
Object value = null;
if (constant != null && i < constant.length) {
value = constant[i];
}
lastIndex = acceptField(lastIndex, dexClassVisitor, fieldAnnotationPositions, value, config);
}
}
lastIndex = 0;
for (int i = 0; i < instance_fields; i++) {
lastIndex = acceptField(lastIndex, dexClassVisitor, fieldAnnotationPositions, null, config);
}
lastIndex = 0;
for (int i = 0; i < direct_methods; i++) {
int j = acceptMethod(lastIndex, dexClassVisitor, methodAnnotationPositions, paramAnnotationPositions,
config);
if (j != -1)
lastIndex = j;
}
lastIndex = 0;
for (int i = 0; i < virtual_methods; i++) {
int j = acceptMethod(lastIndex, dexClassVisitor, methodAnnotationPositions, paramAnnotationPositions,
config);
if (j == -1) {
break;
}
if (j != -1)
lastIndex = j;
}
}
} finally {
in.pop();
}
}
dexClassVisitor.visitEnd();
}
/* default */
Field getField(int id) {
if (id >= this.field_ids_size || id < 0) {
System.out.println("this field " + id + " Id out of bound");
return null;
// throw new IllegalArgumentException("Id out of bound");
}
DataIn in = this.in;
int idxOffset = this.field_ids_off + id * 8;
in.pushMove(idxOffset);
try {
int owner_idx = in.readUShortx();
int type_idx = in.readUShortx();
int name_idx = in.readUIntx();
return new Field(getType(owner_idx), getString(name_idx), getType(type_idx));
} finally {
in.pop();
}
}
/* default */
Method getMethod(int method_idx) {
if (method_idx >= this.method_ids_size || method_idx < 0) {
System.out.println("this method " + method_idx + " Id out of bound");
return null;
// throw new IllegalArgumentException("Id out of bound");
}
DataIn in = this.in;
int idxOffset = this.method_ids_off + method_idx * 8;
in.pushMove(idxOffset);
try {
int owner_idx = in.readUShortx();
int proto_idx = in.readUShortx();
int name_idx = in.readUIntx();
String[] parameterTypes;
String returnType;
{
if (proto_idx >= proto_ids_size) {
System.out.println("this method proto_idx " + proto_idx + " Id out of bound");
return null;
// throw new IllegalArgumentException("Id out of bound");
}
int proto_off = this.proto_ids_off + proto_idx * 12;
in.pushMove(proto_off);
try {
in.skip(4);// skip shorty_idx uint
int return_type_idx = in.readUIntx();
int parameters_off = in.readUIntx();
returnType = getType(return_type_idx);
if (parameters_off != 0) {
in.pushMove(parameters_off);
try {
int size = in.readUIntx();
parameterTypes = new String[size];
for (int i = 0; i < size; i++) {
parameterTypes[i] = getType(in.readUShortx());
}
} finally {
in.pop();
}
} else {
parameterTypes = new String[0];
}
} finally {
in.pop();
}
}
return new Method(getType(owner_idx), getString(name_idx), parameterTypes, returnType);
} finally {
in.pop();
}
}
/**
* 一个String id为4字节
*/
/* default */
String getString(int id) {
if (id >= this.string_ids_size || id < 0) {
System.out.println("this string id " + id + " Id out of bound");
return null;
// throw new IllegalArgumentException("Id out of bound");
}
DataIn in = this.in;
int idxOffset = this.string_ids_off + id * 4;
in.pushMove(idxOffset);
try {
int offset = in.readIntx();
in.pushMove(offset);
try {
int length = (int) in.readULeb128();
StringBuilder buff = new StringBuilder((int) (length * 1.5));
return Mutf8.decode(in, buff);
} catch (UTFDataFormatException e) {
System.out.println("fail to load string " + id + offset);
return null;
// throw new DexException(e, "fail to load string %d@%08x", id, offset);
} finally {
in.pop();
}
} finally {
in.pop();
}
}
private static final Charset UTF8 = Charset.forName("UTF-8");
/* default */
String getType(int id) {
if (id == -1) {
return null;
}
if (id >= this.type_ids_size || id < 0) {
System.out.println("this type id " + id + " Id out of bound");
return null;
// throw new IllegalArgumentException("Id out of bound");
}
DataIn in = this.in;
int idxOffset = this.type_ids_off + id * 4;
in.pushMove(idxOffset);
try {
int offset = in.readIntx();
return this.getString(offset);
} finally {
in.pop();
}
}
/**
* set the apiLevel to read a dex file
*
* @param apiLevel
*/
public final void setApiLevel(int apiLevel) {
apiLevelSetted = true;
this.apiLevel = apiLevel;
}
/**
* 访问成员
*
* @param lastIndex
* @param dcv
* @param fieldAnnotationPositions
* @param value
* @return
*/
/* default */int acceptField(int lastIndex, DexClassVisitor dcv, Map<Integer, Integer> fieldAnnotationPositions,
Object value, int config) {
DataIn in = this.in;
int diff = (int) in.readULeb128();
int field_id = lastIndex + diff;
Field field = getField(field_id);
int field_access_flags = (int) in.readULeb128();
// //////////////////////////////////////////////////////////////
DexFieldVisitor dfv = dcv.visitField(field_access_flags, field, value);
if (dfv != null) {
if ((config & SKIP_ANNOTATION) == 0) {
Integer annotation_offset = fieldAnnotationPositions.get(field_id);
if (annotation_offset != null) {
in.pushMove(annotation_offset);
try {
DexAnnotationReader.accept(this, in, dfv);
} catch (Exception e) {
System.out.println("while accept annotation in field:%s." + field.toString());
throw new DexException(e, "while accept annotation in field:%s.", field.toString());
} finally {
in.pop();
}
}
}
dfv.visitEnd();
}
// //////////////////////////////////////////////////////////////
return field_id;
}
/**
* 访问方法
*
* @param lastIndex
* @param cv
* @param methodAnnos
* @param parameterAnnos
* @return 返回方法ID,如果返回-1,则说明dex经过特殊处理,直接忽略该方法。
*/
/* default */int acceptMethod(int lastIndex, DexClassVisitor cv, Map<Integer, Integer> methodAnnos,
Map<Integer, Integer> parameterAnnos, int config) {
DataIn in = this.in;
int diff = (int) in.readULeb128();
int method_access_flags = (int) in.readULeb128();
int code_off = (int) in.readULeb128();
int method_id = lastIndex + diff;
Method method = getMethod(method_id);
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) {
in.pushMove(annotation_offset);
try {
DexAnnotationReader.accept(this, in, dmv);
} catch (Exception e) {
System.out.println("while accept annotation in method: : " + method.toString());
return -1;
} finally {
in.pop();
}
}
Integer parameter_annotation_offset = parameterAnnos.get(method_id);
if (parameter_annotation_offset != null) {
in.pushMove(parameter_annotation_offset);
try {
int sizeJ = in.readUIntx();
for (int j = 0; j < sizeJ; j++) {
int field_annotation_offset = in.readUIntx();
in.pushMove(field_annotation_offset);
try {
DexAnnotationAble dpav = dmv.visitParameterAnnotation(j);
if (dpav != null) {
DexAnnotationReader.accept(this, in, dpav);
}
} catch (Exception e) {
System.out.println("while accept parameter annotation in method: : " + method.toString());
return -1;
} finally {
in.pop();
}
}
} finally {
in.pop();
}
}
}
if (code_off != 0 && (0 == (SKIP_CODE & config))) {
in.pushMove(code_off);
try {
DexCodeVisitor dcv = dmv.visitCode();
if (dcv != null) {
try {
int tmp = new DexCodeReader(this, in, (0 != (DexOpcodes.ACC_STATIC & method_access_flags)),
method).accept(dcv, config);
if (tmp == -1) {
return -1;
}
} catch (Exception e) {
System.out.println("while accept code in method : " + method.toString());
return -1;
}
}
} finally {
in.pop();
}
}
dmv.visitEnd();
}
} catch (Exception e) {
System.out.println("acgmohu - while accept method : " + method.toString());
return -1;
// throw new DexException(e, "while accept method:[%s]", method.toString());
}
return method_id;
}
/**
* @return true if try to read an odex file
*/
public final boolean isOdex() {
return odex;
}
/**
* the size of class in dex file
*
* @return class_defs_size
*/
public final int getClassSize() {
return class_defs_size;
}
}