Add oat support in DexFileFactory

This commit is contained in:
Ben Gruver 2015-07-13 21:39:28 -07:00
parent 3ff884b1c3
commit 8920228819
10 changed files with 201 additions and 53 deletions

View File

@ -36,6 +36,7 @@ import org.jf.dexlib2.analysis.ClassPath;
import org.jf.dexlib2.analysis.InlineMethodResolver;
import org.jf.dexlib2.util.SyntheticAccessorResolver;
import javax.annotation.Nullable;
import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
@ -54,7 +55,7 @@ public class baksmaliOptions {
public int apiLevel = 15;
public String outputDirectory = "out";
public String dexEntry = "classes.dex";
@Nullable public String dexEntry = null;
public List<String> bootClassPathDirs = Lists.newArrayList();
public List<String> bootClassPathEntries = Lists.newArrayList();

View File

@ -31,9 +31,11 @@ package org.jf.baksmali;
import com.google.common.collect.Lists;
import org.apache.commons.cli.*;
import org.jf.dexlib2.DexFileFactory;
import org.jf.dexlib2.DexFileFactory.MultipleDexFilesException;
import org.jf.dexlib2.analysis.InlineMethodResolver;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
import org.jf.dexlib2.dexbacked.OatFile.OatDexFile;
import org.jf.util.ConsoleUtil;
import org.jf.util.SmaliHelpFormatter;
@ -256,10 +258,20 @@ public class main {
}
//Read in and parse the dex file
DexBackedDexFile dexFile = DexFileFactory.loadDexFile(dexFileFile, options.dexEntry,
options.apiLevel, options.experimental);
DexBackedDexFile dexFile = null;
try {
dexFile = DexFileFactory.loadDexFile(dexFileFile, options.dexEntry, options.apiLevel, options.experimental);
} catch (MultipleDexFilesException ex) {
System.err.println(String.format("%s contains multiple dex files. You must specify which one to " +
"disassemble with the -e option", dexFileFile.getName()));
System.err.println("Valid entries include:");
for (OatDexFile oatDexFile: ex.oatFile.getDexFiles()) {
System.err.println(oatDexFile.filename);
}
System.exit(1);
}
if (dexFile.isOdexFile()) {
if (dexFile.hasOdexOpcodes()) {
if (!options.deodex) {
System.err.println("Warning: You are disassembling an odex file without deodexing it. You");
System.err.println("won't be able to re-assemble the results unless you deodex it with the -x");
@ -547,8 +559,7 @@ public class main {
"/system/framework/services.jar",
"/system/framework/apache-xml.jar",
"/system/framework/filterfw.jar");
} else {
} else if (apiLevel < 21) {
// this is correct as of api 17/4.2.2
return Lists.newArrayList(
"/system/framework/core.jar",
@ -561,6 +572,22 @@ public class main {
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/apache-xml.jar");
} else { // api >= 21
// TODO: verify, add new ones?
return Lists.newArrayList(
"/system/framework/core-libart.jar",
"/system/framework/conscrypt.jar",
"/system/framework/okhttp.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/telephony-common.jar",
"/system/framework/voip-common.jar",
"/system/framework/ims-common.jar",
"/system/framework/mms-common.jar",
"/system/framework/android.policy.jar",
"/system/framework/apache-xml.jar");
}
}
}

View File

@ -31,51 +31,56 @@
package org.jf.dexlib2;
import com.google.common.base.MoreObjects;
import com.google.common.io.ByteStreams;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
import org.jf.dexlib2.dexbacked.OatFile;
import org.jf.dexlib2.dexbacked.OatFile.NotAnOatFileException;
import org.jf.dexlib2.dexbacked.OatFile.OatDexFile;
import org.jf.dexlib2.iface.DexFile;
import org.jf.dexlib2.writer.pool.DexPool;
import org.jf.util.ExceptionWithContext;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.*;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public final class DexFileFactory {
@Nonnull
public static DexBackedDexFile loadDexFile(String path, int api)
throws IOException {
public static DexBackedDexFile loadDexFile(@Nonnull String path, int api) throws IOException {
return loadDexFile(path, api, false);
}
@Nonnull
public static DexBackedDexFile loadDexFile(String path, int api, boolean experimental)
public static DexBackedDexFile loadDexFile(@Nonnull String path, int api, boolean experimental)
throws IOException {
return loadDexFile(new File(path), "classes.dex", Opcodes.forApi(api, experimental));
}
@Nonnull
public static DexBackedDexFile loadDexFile(File dexFile, int api) throws IOException {
public static DexBackedDexFile loadDexFile(@Nonnull File dexFile, int api) throws IOException {
return loadDexFile(dexFile, api, false);
}
@Nonnull
public static DexBackedDexFile loadDexFile(File dexFile, int api, boolean experimental)
public static DexBackedDexFile loadDexFile(@Nonnull File dexFile, int api, boolean experimental)
throws IOException {
return loadDexFile(dexFile, "classes.dex", Opcodes.forApi(api, experimental));
return loadDexFile(dexFile, null, Opcodes.forApi(api, experimental));
}
@Nonnull
public static DexBackedDexFile loadDexFile(File dexFile, String dexEntry, int api,
public static DexBackedDexFile loadDexFile(@Nonnull File dexFile, @Nullable String dexEntry, int api,
boolean experimental) throws IOException {
return loadDexFile(dexFile, dexEntry, Opcodes.forApi(api, experimental));
}
@Nonnull
public static DexBackedDexFile loadDexFile(File dexFile, String dexEntry,
@Nonnull Opcodes opcodes) throws IOException {
public static DexBackedDexFile loadDexFile(@Nonnull File dexFile, @Nullable String dexEntry,
@Nonnull Opcodes opcodes) throws IOException {
ZipFile zipFile = null;
boolean isZipFile = false;
try {
@ -83,16 +88,18 @@ public final class DexFileFactory {
// if we get here, it's safe to assume we have a zip file
isZipFile = true;
ZipEntry zipEntry = zipFile.getEntry(dexEntry);
String zipEntryName = MoreObjects.firstNonNull(dexEntry, "classes.dex");
ZipEntry zipEntry = zipFile.getEntry(zipEntryName);
if (zipEntry == null) {
throw new NoClassesDexException("zip file %s does not contain a classes.dex file", dexFile.getName());
throw new DexFileNotFound("zip file %s does not contain a %s file", dexFile.getName(), zipEntryName);
}
long fileLength = zipEntry.getSize();
if (fileLength < 40) {
throw new ExceptionWithContext(
"The " + dexEntry + " file in %s is too small to be a valid dex file", dexFile.getName());
throw new ExceptionWithContext("The %s file in %s is too small to be a valid dex file",
zipEntryName, dexFile.getName());
} else if (fileLength > Integer.MAX_VALUE) {
throw new ExceptionWithContext("The " + dexEntry + " file in %s is too large to read in", dexFile.getName());
throw new ExceptionWithContext("The %s file in %s is too large to read in",
zipEntryName, dexFile.getName());
}
byte[] dexBytes = new byte[(int)fileLength];
ByteStreams.readFully(zipFile.getInputStream(zipEntry), dexBytes);
@ -127,30 +134,93 @@ public final class DexFileFactory {
} catch (DexBackedOdexFile.NotAnOdexFile ex) {
// just eat it
}
OatFile oatFile = null;
try {
oatFile = OatFile.fromInputStream(inputStream);
} catch (NotAnOatFileException ex) {
// just eat it
}
if (oatFile != null) {
if (oatFile.isSupportedVersion() == OatFile.UNSUPPORTED) {
throw new UnsupportedOatVersionException(oatFile);
}
List<OatDexFile> oatDexFiles = oatFile.getDexFiles();
if (oatDexFiles.size() == 0) {
throw new DexFileNotFound("Oat file %s contains no dex files", dexFile.getName());
}
if (dexEntry == null) {
if (oatDexFiles.size() > 1) {
throw new MultipleDexFilesException(oatFile);
}
return oatDexFiles.get(0);
} else {
// first check for an exact match
for (OatDexFile oatDexFile : oatFile.getDexFiles()) {
if (oatDexFile.filename.equals(dexEntry)) {
return oatDexFile;
}
}
if (!dexEntry.contains("/")) {
for (OatDexFile oatDexFile : oatFile.getDexFiles()) {
File oatEntryFile = new File(oatDexFile.filename);
if (oatEntryFile.getName().equals(dexEntry)) {
return oatDexFile;
}
}
}
throw new DexFileNotFound("oat file %s does not contain a dex file named %s",
dexFile.getName(), dexEntry);
}
}
} finally {
inputStream.close();
}
throw new ExceptionWithContext("%s is not an apk, dex file or odex file.", dexFile.getPath());
throw new ExceptionWithContext("%s is not an apk, dex, odex or oat file.", dexFile.getPath());
}
public static void writeDexFile(String path, DexFile dexFile) throws IOException {
public static void writeDexFile(@Nonnull String path, @Nonnull DexFile dexFile) throws IOException {
DexPool.writeTo(path, dexFile);
}
private DexFileFactory() {}
public static class NoClassesDexException extends ExceptionWithContext {
public NoClassesDexException(Throwable cause) {
public static class DexFileNotFound extends ExceptionWithContext {
public DexFileNotFound(@Nullable Throwable cause) {
super(cause);
}
public NoClassesDexException(Throwable cause, String message, Object... formatArgs) {
public DexFileNotFound(@Nullable Throwable cause, @Nullable String message, Object... formatArgs) {
super(cause, message, formatArgs);
}
public NoClassesDexException(String message, Object... formatArgs) {
public DexFileNotFound(@Nullable String message, Object... formatArgs) {
super(message, formatArgs);
}
}
public static class MultipleDexFilesException extends ExceptionWithContext {
@Nonnull public final OatFile oatFile;
public MultipleDexFilesException(@Nonnull OatFile oatFile) {
super("Oat file has multiple dex files.");
this.oatFile = oatFile;
}
}
public static class UnsupportedOatVersionException extends ExceptionWithContext {
@Nonnull public final OatFile oatFile;
public UnsupportedOatVersionException(@Nonnull OatFile oatFile) {
super("Unsupported oat version: %d", oatFile.getOatVersion());
this.oatFile = oatFile;
}
}
}

View File

@ -39,6 +39,7 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.jf.dexlib2.DexFileFactory;
import org.jf.dexlib2.DexFileFactory.DexFileNotFound;
import org.jf.dexlib2.analysis.reflection.ReflectionClassDef;
import org.jf.dexlib2.iface.ClassDef;
import org.jf.dexlib2.iface.DexFile;
@ -58,6 +59,7 @@ public class ClassPath {
@Nonnull private final TypeProto unknownClass;
@Nonnull private HashMap<String, ClassDef> availableClasses = Maps.newHashMap();
private boolean checkPackagePrivateAccess;
public boolean isArt;
/**
* Creates a new ClassPath instance that can load classes from the given dex files
@ -82,15 +84,29 @@ public class ClassPath {
* Creates a new ClassPath instance that can load classes from the given dex files
*
* @param classPath An iterable of DexFile objects. When loading a class, these dex files will be searched in order
* @param checkPackagePrivateAccess Whether checkPackagePrivateAccess is needed, enabled for ONLY early API 17 by default
* @param checkPackagePrivateAccess Whether checkPackagePrivateAccess is needed, enabled for ONLY early API 17 by
* default
*/
public ClassPath(@Nonnull Iterable<DexFile> classPath, boolean checkPackagePrivateAccess) {
this(classPath, checkPackagePrivateAccess, false);
}
/**
* Creates a new ClassPath instance that can load classes from the given dex files
*
* @param classPath An iterable of DexFile objects. When loading a class, these dex files will be searched in order
* @param checkPackagePrivateAccess Whether checkPackagePrivateAccess is needed, enabled for ONLY early API 17 by
* default
* @param isArt Whether this is ClassPath is for ART
*/
public ClassPath(@Nonnull Iterable < DexFile > classPath, boolean checkPackagePrivateAccess, boolean isArt) {
// add fallbacks for certain special classes that must be present
Iterable<DexFile> dexFiles = Iterables.concat(classPath, Lists.newArrayList(getBasicClasses()));
unknownClass = new UnknownClassProto(this);
loadedClasses.put(unknownClass.getType(), unknownClass);
this.checkPackagePrivateAccess = checkPackagePrivateAccess;
this.isArt = isArt;
loadPrimitiveType("Z");
loadPrimitiveType("B");
@ -223,7 +239,7 @@ public class ClassPath {
} else {
try {
return DexFileFactory.loadDexFile(file, api, experimental);
} catch (DexFileFactory.NoClassesDexException ex) {
} catch (DexFileNotFound ex) {
// ignore and continue
} catch (Exception ex) {
throw ExceptionWithContext.withContext(ex,

View File

@ -37,13 +37,19 @@ import javax.annotation.Nonnull;
public class BaseDexBuffer {
@Nonnull /* package private */ final byte[] buf;
/* package private */ final int baseOffset;
public BaseDexBuffer(@Nonnull byte[] buf) {
this(buf, 0);
}
public BaseDexBuffer(@Nonnull byte[] buf, int offset) {
this.buf = buf;
this.baseOffset = offset;
}
public int readSmallUint(int offset) {
byte[] buf = this.buf;
offset += baseOffset;
int result = (buf[offset] & 0xff) |
((buf[offset+1] & 0xff) << 8) |
((buf[offset+2] & 0xff) << 16) |
@ -56,6 +62,7 @@ public class BaseDexBuffer {
public int readOptionalUint(int offset) {
byte[] buf = this.buf;
offset += baseOffset;
int result = (buf[offset] & 0xff) |
((buf[offset+1] & 0xff) << 8) |
((buf[offset+2] & 0xff) << 16) |
@ -68,16 +75,18 @@ public class BaseDexBuffer {
public int readUshort(int offset) {
byte[] buf = this.buf;
offset += baseOffset;
return (buf[offset] & 0xff) |
((buf[offset+1] & 0xff) << 8);
}
public int readUbyte(int offset) {
return buf[offset] & 0xff;
return buf[offset + baseOffset] & 0xff;
}
public long readLong(int offset) {
byte[] buf = this.buf;
offset += baseOffset;
return (buf[offset] & 0xff) |
((buf[offset+1] & 0xff) << 8) |
((buf[offset+2] & 0xff) << 16) |
@ -90,6 +99,7 @@ public class BaseDexBuffer {
public int readInt(int offset) {
byte[] buf = this.buf;
offset += baseOffset;
return (buf[offset] & 0xff) |
((buf[offset+1] & 0xff) << 8) |
((buf[offset+2] & 0xff) << 16) |
@ -98,12 +108,13 @@ public class BaseDexBuffer {
public int readShort(int offset) {
byte[] buf = this.buf;
offset += baseOffset;
return (buf[offset] & 0xff) |
(buf[offset+1] << 8);
}
public int readByte(int offset) {
return buf[offset];
return buf[baseOffset + offset];
}
@Nonnull
@ -115,4 +126,8 @@ public class BaseDexBuffer {
protected byte[] getBuf() {
return buf;
}
protected int getBaseOffset() {
return baseOffset;
}
}

View File

@ -49,7 +49,7 @@ public class BaseDexReader<T extends BaseDexBuffer> {
public void setOffset(int offset) { this.offset = offset; }
public int readSleb128() {
int end = offset;
int end = dexBuf.baseOffset + offset;
int currentByteValue;
int result;
byte[] buf = dexBuf.buf;
@ -84,7 +84,7 @@ public class BaseDexReader<T extends BaseDexBuffer> {
}
}
offset = end;
offset = end - dexBuf.baseOffset;
return result;
}
@ -93,7 +93,7 @@ public class BaseDexReader<T extends BaseDexBuffer> {
}
private int readUleb128(boolean allowLarge) {
int end = offset;
int end = dexBuf.baseOffset + offset;
int currentByteValue;
int result;
byte[] buf = dexBuf.buf;
@ -129,7 +129,7 @@ public class BaseDexReader<T extends BaseDexBuffer> {
}
}
offset = end;
offset = end - dexBuf.baseOffset;
return result;
}
@ -150,7 +150,7 @@ public class BaseDexReader<T extends BaseDexBuffer> {
* @return The unsigned value, reinterpreted as a signed int
*/
public int readBigUleb128() {
int end = offset;
int end = dexBuf.baseOffset + offset;
int currentByteValue;
int result;
byte[] buf = dexBuf.buf;
@ -179,12 +179,12 @@ public class BaseDexReader<T extends BaseDexBuffer> {
}
}
offset = end;
offset = end - dexBuf.baseOffset;
return result;
}
public void skipUleb128() {
int end = offset;
int end = dexBuf.baseOffset + offset;
byte currentByteValue;
byte[] buf = dexBuf.buf;
@ -206,7 +206,7 @@ public class BaseDexReader<T extends BaseDexBuffer> {
}
}
offset = end;
offset = end - dexBuf.baseOffset;
}
public int readSmallUint() {
@ -285,7 +285,7 @@ public class BaseDexReader<T extends BaseDexBuffer> {
public int readByte(int offset) { return dexBuf.readByte(offset); }
public int readSizedInt(int bytes) {
int o = offset;
int o = dexBuf.baseOffset + offset;
byte[] buf = dexBuf.buf;
int result;
@ -311,12 +311,12 @@ public class BaseDexReader<T extends BaseDexBuffer> {
default:
throw new ExceptionWithContext("Invalid size %d for sized int at offset 0x%x", bytes, offset);
}
offset = o + bytes;
offset = o + bytes - dexBuf.baseOffset;
return result;
}
public int readSizedSmallUint(int bytes) {
int o = offset;
int o = dexBuf.baseOffset + offset;
byte[] buf = dexBuf.buf;
int result = 0;
@ -341,12 +341,12 @@ public class BaseDexReader<T extends BaseDexBuffer> {
default:
throw new ExceptionWithContext("Invalid size %d for sized uint at offset 0x%x", bytes, offset);
}
offset = o + bytes;
offset = o + bytes - dexBuf.baseOffset;
return result;
}
public int readSizedRightExtendedInt(int bytes) {
int o = offset;
int o = dexBuf.baseOffset + offset;
byte[] buf = dexBuf.buf;
int result;
@ -373,12 +373,12 @@ public class BaseDexReader<T extends BaseDexBuffer> {
throw new ExceptionWithContext(
"Invalid size %d for sized, right extended int at offset 0x%x", bytes, offset);
}
offset = o + bytes;
offset = o + bytes - dexBuf.baseOffset;
return result;
}
public long readSizedRightExtendedLong(int bytes) {
int o = offset;
int o = dexBuf.baseOffset + offset;
byte[] buf = dexBuf.buf;
long result;
@ -439,12 +439,12 @@ public class BaseDexReader<T extends BaseDexBuffer> {
throw new ExceptionWithContext(
"Invalid size %d for sized, right extended long at offset 0x%x", bytes, offset);
}
offset = o + bytes;
offset = o + bytes - dexBuf.baseOffset;
return result;
}
public long readSizedLong(int bytes) {
int o = offset;
int o = dexBuf.baseOffset + offset;
byte[] buf = dexBuf.buf;
long result;
@ -505,13 +505,14 @@ public class BaseDexReader<T extends BaseDexBuffer> {
throw new ExceptionWithContext("Invalid size %d for sized long at offset 0x%x", bytes, offset);
}
offset = o + bytes;
offset = o + bytes - dexBuf.baseOffset;
return result;
}
public String readString(int utf16Length) {
int[] ret = new int[1];
String value = Utf8Utils.utf8BytesWithUtf16LengthToString(dexBuf.buf, offset, utf16Length, ret);
String value = Utf8Utils.utf8BytesWithUtf16LengthToString(
dexBuf.buf, dexBuf.baseOffset + offset, utf16Length, ret);
offset += ret[0];
return value;
}

View File

@ -62,7 +62,7 @@ public class DexBackedDexFile extends BaseDexBuffer implements DexFile {
private final int classStartOffset;
private DexBackedDexFile(Opcodes opcodes, @Nonnull byte[] buf, int offset, boolean verifyMagic) {
super(buf);
super(buf, offset);
this.opcodes = opcodes;
@ -121,10 +121,16 @@ public class DexBackedDexFile extends BaseDexBuffer implements DexFile {
return opcodes;
}
// Will only be true for a dalvik-style odex file
public boolean isOdexFile() {
return false;
}
// Will be true for both a dalvik-style odex file, and an art-style odex file embedded in an oat file
public boolean hasOdexOpcodes() {
return false;
}
@Nonnull
@Override
public Set<? extends DexBackedClassDef> getClasses() {

View File

@ -33,7 +33,6 @@ package org.jf.dexlib2.dexbacked;
import com.google.common.io.ByteStreams;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.dexbacked.raw.HeaderItem;
import org.jf.dexlib2.dexbacked.raw.OdexHeaderItem;
import org.jf.dexlib2.dexbacked.util.VariableSizeList;
@ -61,6 +60,10 @@ public class DexBackedOdexFile extends DexBackedDexFile {
return true;
}
@Override public boolean hasOdexOpcodes() {
return true;
}
public List<String> getDependencies() {
final int dexOffset = OdexHeaderItem.getDexOffset(odexBuf);
final int dependencyOffset = OdexHeaderItem.getDependenciesOffset(odexBuf) - dexOffset;

View File

@ -124,8 +124,12 @@ public class OatFile extends BaseDexBuffer {
return new OatFile(buf);
}
public int getOatVersion() {
return oatHeader.getVersion();
}
public int isSupportedVersion() {
int version = oatHeader.getVersion();
int version = getOatVersion();
if (version < MIN_OAT_VERSION) {
return UNSUPPORTED;
}
@ -187,6 +191,10 @@ public class OatFile extends BaseDexBuffer {
super(opcodes, OatFile.this.buf, offset);
this.filename = filename;
}
@Override public boolean hasOdexOpcodes() {
return true;
}
}
private class OatHeader {

View File

@ -59,7 +59,7 @@ public class RawDexFile extends DexBackedDexFile {
@Nonnull
public byte[] readByteRange(int start, int length) {
return Arrays.copyOfRange(getBuf(), start, start+length);
return Arrays.copyOfRange(getBuf(), getBaseOffset() + start, getBaseOffset() + start + length);
}
public int getMapOffset() {
@ -94,6 +94,7 @@ public class RawDexFile extends DexBackedDexFile {
}
public void writeAnnotations(@Nonnull Writer out, @Nonnull AnnotatedBytes annotatedBytes) throws IOException {
// TODO: need to pass in the offset
annotatedBytes.writeAnnotations(out, getBuf());
}
}