Home | History | Annotate | Download | only in dexbacked
      1 /*
      2  * Copyright 2014, Google Inc.
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  *     * Redistributions of source code must retain the above copyright
     10  * notice, this list of conditions and the following disclaimer.
     11  *     * Redistributions in binary form must reproduce the above
     12  * copyright notice, this list of conditions and the following disclaimer
     13  * in the documentation and/or other materials provided with the
     14  * distribution.
     15  *     * Neither the name of Google Inc. nor the names of its
     16  * contributors may be used to endorse or promote products derived from
     17  * this software without specific prior written permission.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 
     32 package org.jf.dexlib2.dexbacked;
     33 
     34 import com.google.common.base.Function;
     35 import com.google.common.collect.ImmutableList;
     36 import com.google.common.collect.Iterators;
     37 import com.google.common.io.ByteStreams;
     38 import org.jf.dexlib2.Opcodes;
     39 import org.jf.dexlib2.dexbacked.OatFile.OatDexFile;
     40 import org.jf.dexlib2.dexbacked.OatFile.SymbolTable.Symbol;
     41 import org.jf.dexlib2.dexbacked.raw.HeaderItem;
     42 import org.jf.dexlib2.iface.MultiDexContainer;
     43 import org.jf.util.AbstractForwardSequentialList;
     44 
     45 import javax.annotation.Nonnull;
     46 import javax.annotation.Nullable;
     47 import java.io.EOFException;
     48 import java.io.IOException;
     49 import java.io.InputStream;
     50 import java.nio.charset.Charset;
     51 import java.util.AbstractList;
     52 import java.util.Arrays;
     53 import java.util.Iterator;
     54 import java.util.List;
     55 
     56 public class OatFile extends BaseDexBuffer implements MultiDexContainer<OatDexFile> {
     57     private static final byte[] ELF_MAGIC = new byte[] { 0x7f, 'E', 'L', 'F' };
     58     private static final byte[] OAT_MAGIC = new byte[] { 'o', 'a', 't', '\n' };
     59     private static final int MIN_ELF_HEADER_SIZE = 52;
     60 
     61     // These are the "known working" versions that I have manually inspected the source for.
     62     // Later version may or may not work, depending on what changed.
     63     private static final int MIN_OAT_VERSION = 56;
     64     private static final int MAX_OAT_VERSION = 86;
     65 
     66     public static final int UNSUPPORTED = 0;
     67     public static final int SUPPORTED = 1;
     68     public static final int UNKNOWN = 2;
     69 
     70     private final boolean is64bit;
     71     @Nonnull private final OatHeader oatHeader;
     72     @Nonnull private final Opcodes opcodes;
     73     @Nullable private final VdexProvider vdexProvider;
     74 
     75     public OatFile(@Nonnull byte[] buf) {
     76         this(buf, null);
     77     }
     78 
     79     public OatFile(@Nonnull byte[] buf, @Nullable VdexProvider vdexProvider) {
     80         super(buf);
     81 
     82         if (buf.length < MIN_ELF_HEADER_SIZE) {
     83             throw new NotAnOatFileException();
     84         }
     85 
     86         verifyMagic(buf);
     87 
     88         if (buf[4] == 1) {
     89             is64bit = false;
     90         } else if (buf[4] == 2) {
     91             is64bit = true;
     92         } else {
     93             throw new InvalidOatFileException(String.format("Invalid word-size value: %x", buf[5]));
     94         }
     95 
     96         OatHeader oatHeader = null;
     97         SymbolTable symbolTable = getSymbolTable();
     98         for (Symbol symbol: symbolTable.getSymbols()) {
     99             if (symbol.getName().equals("oatdata")) {
    100                 oatHeader = new OatHeader(symbol.getFileOffset());
    101                 break;
    102             }
    103         }
    104 
    105         if (oatHeader == null) {
    106             throw new InvalidOatFileException("Oat file has no oatdata symbol");
    107         }
    108         this.oatHeader = oatHeader;
    109 
    110         if (!oatHeader.isValid()) {
    111             throw new InvalidOatFileException("Invalid oat magic value");
    112         }
    113 
    114         this.opcodes = Opcodes.forArtVersion(oatHeader.getVersion());
    115         this.vdexProvider = vdexProvider;
    116     }
    117 
    118     private static void verifyMagic(byte[] buf) {
    119         for (int i = 0; i < ELF_MAGIC.length; i++) {
    120             if (buf[i] != ELF_MAGIC[i]) {
    121                 throw new NotAnOatFileException();
    122             }
    123         }
    124     }
    125 
    126     public static OatFile fromInputStream(@Nonnull InputStream is) throws IOException {
    127         return fromInputStream(is, null);
    128     }
    129 
    130     public static OatFile fromInputStream(@Nonnull InputStream is, @Nullable VdexProvider vdexProvider)
    131             throws IOException {
    132         if (!is.markSupported()) {
    133             throw new IllegalArgumentException("InputStream must support mark");
    134         }
    135         is.mark(4);
    136         byte[] partialHeader = new byte[4];
    137         try {
    138             ByteStreams.readFully(is, partialHeader);
    139         } catch (EOFException ex) {
    140             throw new NotAnOatFileException();
    141         } finally {
    142             is.reset();
    143         }
    144 
    145         verifyMagic(partialHeader);
    146 
    147         is.reset();
    148 
    149         byte[] buf = ByteStreams.toByteArray(is);
    150         return new OatFile(buf, vdexProvider);
    151     }
    152 
    153     public int getOatVersion() {
    154         return oatHeader.getVersion();
    155     }
    156 
    157     public int isSupportedVersion() {
    158         int version = getOatVersion();
    159         if (version < MIN_OAT_VERSION) {
    160             return UNSUPPORTED;
    161         }
    162         if (version <= MAX_OAT_VERSION) {
    163             return SUPPORTED;
    164         }
    165         return UNKNOWN;
    166     }
    167 
    168     @Nonnull
    169     public List<String> getBootClassPath() {
    170         if (getOatVersion() < 75) {
    171             return ImmutableList.of();
    172         }
    173         String bcp = oatHeader.getKeyValue("bootclasspath");
    174         if (bcp == null) {
    175             return ImmutableList.of();
    176         }
    177         return Arrays.asList(bcp.split(":"));
    178     }
    179 
    180     @Nonnull @Override public Opcodes getOpcodes() {
    181         return opcodes;
    182     }
    183 
    184     @Nonnull
    185     public List<OatDexFile> getDexFiles() {
    186         return new AbstractForwardSequentialList<OatDexFile>() {
    187             @Override public int size() {
    188                 return oatHeader.getDexFileCount();
    189             }
    190 
    191             @Nonnull @Override public Iterator<OatDexFile> iterator() {
    192                 return Iterators.transform(new DexEntryIterator(), new Function<DexEntry, OatDexFile>() {
    193                     @Nullable @Override public OatDexFile apply(DexEntry dexEntry) {
    194                         return dexEntry.getDexFile();
    195                     }
    196                 });
    197             }
    198         };
    199     }
    200 
    201     @Nonnull @Override public List<String> getDexEntryNames() throws IOException {
    202         return new AbstractForwardSequentialList<String>() {
    203             @Override public int size() {
    204                 return oatHeader.getDexFileCount();
    205             }
    206 
    207             @Nonnull @Override public Iterator<String> iterator() {
    208                 return Iterators.transform(new DexEntryIterator(), new Function<DexEntry, String>() {
    209                     @Nullable @Override public String apply(DexEntry dexEntry) {
    210                         return dexEntry.entryName;
    211                     }
    212                 });
    213             }
    214         };
    215     }
    216 
    217     @Nullable @Override public OatDexFile getEntry(@Nonnull String entryName) throws IOException {
    218         DexEntryIterator iterator = new DexEntryIterator();
    219         while (iterator.hasNext()) {
    220             DexEntry entry = iterator.next();
    221 
    222             if (entry.entryName.equals(entryName)) {
    223                 return entry.getDexFile();
    224             }
    225         }
    226         return null;
    227     }
    228 
    229     public class OatDexFile extends DexBackedDexFile implements MultiDexContainer.MultiDexFile {
    230         @Nonnull public final String filename;
    231 
    232         public OatDexFile(byte[] buf, int offset, @Nonnull String filename) {
    233             super(opcodes, buf, offset);
    234             this.filename = filename;
    235         }
    236 
    237         @Nonnull @Override public String getEntryName() {
    238             return filename;
    239         }
    240 
    241         @Nonnull @Override public OatFile getContainer() {
    242             return OatFile.this;
    243         }
    244 
    245         @Override public boolean hasOdexOpcodes() {
    246             return true;
    247         }
    248     }
    249 
    250     private class OatHeader {
    251         private final int headerOffset;
    252 
    253         public OatHeader(int offset) {
    254             this.headerOffset = offset;
    255         }
    256 
    257         public boolean isValid() {
    258             for (int i=0; i<OAT_MAGIC.length; i++) {
    259                 if (buf[headerOffset + i] != OAT_MAGIC[i]) {
    260                     return false;
    261                 }
    262             }
    263 
    264             for (int i=4; i<7; i++) {
    265                 if (buf[headerOffset + i] < '0' || buf[headerOffset + i] > '9') {
    266                     return false;
    267                 }
    268             }
    269 
    270             return buf[headerOffset + 7] == 0;
    271         }
    272 
    273         public int getVersion() {
    274             return Integer.valueOf(new String(buf, headerOffset + 4, 3));
    275         }
    276 
    277         public int getDexFileCount() {
    278             return readSmallUint(headerOffset + 20);
    279         }
    280 
    281         public int getKeyValueStoreSize() {
    282             if (getVersion() < MIN_OAT_VERSION) {
    283                 throw new IllegalStateException("Unsupported oat version");
    284             }
    285             int fieldOffset = 17 * 4;
    286             return readSmallUint(headerOffset + fieldOffset);
    287         }
    288 
    289         public int getHeaderSize() {
    290             if (getVersion() < MIN_OAT_VERSION) {
    291                 throw new IllegalStateException("Unsupported oat version");
    292             }
    293             return 18*4 + getKeyValueStoreSize();
    294         }
    295 
    296         @Nullable
    297         public String getKeyValue(@Nonnull String key) {
    298             int size = getKeyValueStoreSize();
    299 
    300             int offset = headerOffset + 18 * 4;
    301             int endOffset = offset + size;
    302 
    303             while (offset < endOffset) {
    304                 int keyStartOffset = offset;
    305                 while (offset < endOffset && buf[offset] != '\0') {
    306                     offset++;
    307                 }
    308                 if (offset >= endOffset) {
    309                     throw new InvalidOatFileException("Oat file contains truncated key value store");
    310                 }
    311                 int keyEndOffset = offset;
    312 
    313                 String k = new String(buf, keyStartOffset, keyEndOffset - keyStartOffset);
    314                 if (k.equals(key)) {
    315                     int valueStartOffset = ++offset;
    316                     while (offset < endOffset && buf[offset] != '\0') {
    317                         offset++;
    318                     }
    319                     if (offset >= endOffset) {
    320                         throw new InvalidOatFileException("Oat file contains truncated key value store");
    321                     }
    322                     int valueEndOffset = offset;
    323                     return new String(buf, valueStartOffset, valueEndOffset - valueStartOffset);
    324                 }
    325                 offset++;
    326             }
    327             return null;
    328         }
    329 
    330         public int getDexListStart() {
    331             return headerOffset + getHeaderSize();
    332         }
    333     }
    334 
    335     @Nonnull
    336     private List<SectionHeader> getSections() {
    337         final int offset;
    338         final int entrySize;
    339         final int entryCount;
    340         if (is64bit) {
    341             offset = readLongAsSmallUint(40);
    342             entrySize = readUshort(58);
    343             entryCount = readUshort(60);
    344         } else {
    345             offset = readSmallUint(32);
    346             entrySize = readUshort(46);
    347             entryCount = readUshort(48);
    348         }
    349 
    350         if (offset + (entrySize * entryCount) > buf.length) {
    351             throw new InvalidOatFileException("The ELF section headers extend past the end of the file");
    352         }
    353 
    354         return new AbstractList<SectionHeader>() {
    355             @Override public SectionHeader get(int index) {
    356                 if (index < 0 || index >= entryCount) {
    357                     throw new IndexOutOfBoundsException();
    358                 }
    359                 if (is64bit) {
    360                     return new SectionHeader64Bit(offset + (index * entrySize));
    361                 } else {
    362                     return new SectionHeader32Bit(offset + (index * entrySize));
    363                 }
    364             }
    365 
    366             @Override public int size() {
    367                 return entryCount;
    368             }
    369         };
    370     }
    371 
    372     @Nonnull
    373     private SymbolTable getSymbolTable() {
    374         for (SectionHeader header: getSections()) {
    375             if (header.getType() == SectionHeader.TYPE_DYNAMIC_SYMBOL_TABLE) {
    376                 return new SymbolTable(header);
    377             }
    378         }
    379         throw new InvalidOatFileException("Oat file has no symbol table");
    380     }
    381 
    382     @Nonnull
    383     private StringTable getSectionNameStringTable() {
    384         int index = readUshort(50);
    385         if (index == 0) {
    386             throw new InvalidOatFileException("There is no section name string table");
    387         }
    388 
    389         try {
    390             return new StringTable(getSections().get(index));
    391         } catch (IndexOutOfBoundsException ex) {
    392             throw new InvalidOatFileException("The section index for the section name string table is invalid");
    393         }
    394     }
    395 
    396     private abstract class SectionHeader {
    397         protected final int offset;
    398         public static final int TYPE_DYNAMIC_SYMBOL_TABLE = 11;
    399         public SectionHeader(int offset) { this.offset = offset; }
    400         @Nonnull public String getName() { return getSectionNameStringTable().getString(readSmallUint(offset)); }
    401         public int getType() { return readInt(offset + 4); }
    402         public abstract long getAddress();
    403         public abstract int getOffset();
    404         public abstract int getSize();
    405         public abstract int getLink();
    406         public abstract int getEntrySize();
    407     }
    408 
    409     private class SectionHeader32Bit extends SectionHeader {
    410         public SectionHeader32Bit(int offset) { super(offset); }
    411         @Override public long getAddress() { return readInt(offset + 12) & 0xFFFFFFFFL; }
    412         @Override public int getOffset() { return readSmallUint(offset + 16); }
    413         @Override public int getSize() { return readSmallUint(offset + 20); }
    414         @Override public int getLink() { return readSmallUint(offset + 24); }
    415         @Override public int getEntrySize() { return readSmallUint(offset + 36); }
    416     }
    417 
    418     private class SectionHeader64Bit extends SectionHeader {
    419         public SectionHeader64Bit(int offset) { super(offset); }
    420         @Override public long getAddress() { return readLong(offset + 16); }
    421         @Override public int getOffset() { return readLongAsSmallUint(offset + 24); }
    422         @Override public int getSize() { return readLongAsSmallUint(offset + 32); }
    423         @Override public int getLink() { return readSmallUint(offset + 40); }
    424         @Override public int getEntrySize() { return readLongAsSmallUint(offset + 56); }
    425     }
    426 
    427     class SymbolTable {
    428         @Nonnull private final StringTable stringTable;
    429         private final int offset;
    430         private final int entryCount;
    431         private final int entrySize;
    432 
    433         public SymbolTable(@Nonnull SectionHeader header) {
    434             try {
    435                 this.stringTable = new StringTable(getSections().get(header.getLink()));
    436             } catch (IndexOutOfBoundsException ex) {
    437                 throw new InvalidOatFileException("String table section index is invalid");
    438             }
    439             this.offset = header.getOffset();
    440             this.entrySize = header.getEntrySize();
    441             this.entryCount = header.getSize() / entrySize;
    442 
    443             if (offset + entryCount * entrySize > buf.length) {
    444                 throw new InvalidOatFileException("Symbol table extends past end of file");
    445             }
    446         }
    447 
    448         @Nonnull
    449         public List<Symbol> getSymbols() {
    450             return new AbstractList<Symbol>() {
    451                 @Override public Symbol get(int index) {
    452                     if (index < 0 || index >= entryCount) {
    453                         throw new IndexOutOfBoundsException();
    454                     }
    455                     if (is64bit) {
    456                         return new Symbol64(offset + index * entrySize);
    457                     } else {
    458                         return new Symbol32(offset + index * entrySize);
    459                     }
    460                 }
    461 
    462                 @Override public int size() {
    463                     return entryCount;
    464                 }
    465             };
    466         }
    467 
    468         public abstract class Symbol {
    469             protected final int offset;
    470             public Symbol(int offset) { this.offset = offset; }
    471             @Nonnull public abstract String getName();
    472             public abstract long getValue();
    473             public abstract int getSize();
    474             public abstract int getSectionIndex();
    475 
    476             public int getFileOffset() {
    477                 SectionHeader sectionHeader;
    478                 try {
    479                     sectionHeader = getSections().get(getSectionIndex());
    480                 } catch (IndexOutOfBoundsException ex) {
    481                     throw new InvalidOatFileException("Section index for symbol is out of bounds");
    482                 }
    483 
    484                 long sectionAddress = sectionHeader.getAddress();
    485                 int sectionOffset = sectionHeader.getOffset();
    486                 int sectionSize = sectionHeader.getSize();
    487 
    488                 long symbolAddress = getValue();
    489 
    490                 if (symbolAddress < sectionAddress || symbolAddress >= sectionAddress + sectionSize) {
    491                     throw new InvalidOatFileException("symbol address lies outside it's associated section");
    492                 }
    493 
    494                 long fileOffset = (sectionOffset + (getValue() - sectionAddress));
    495                 assert fileOffset <= Integer.MAX_VALUE;
    496                 return (int)fileOffset;
    497             }
    498         }
    499 
    500         public class Symbol32 extends Symbol {
    501             public Symbol32(int offset) { super(offset); }
    502 
    503             @Nonnull
    504             public String getName() { return stringTable.getString(readSmallUint(offset)); }
    505             public long getValue() { return readSmallUint(offset + 4); }
    506             public int getSize() { return readSmallUint(offset + 8); }
    507             public int getSectionIndex() { return readUshort(offset + 14); }
    508         }
    509 
    510         public class Symbol64 extends Symbol {
    511             public Symbol64(int offset) { super(offset); }
    512 
    513             @Nonnull
    514             public String getName() { return stringTable.getString(readSmallUint(offset)); }
    515             public long getValue() { return readLong(offset + 8); }
    516             public int getSize() { return readLongAsSmallUint(offset + 16); }
    517             public int getSectionIndex() { return readUshort(offset + 6); }
    518         }
    519     }
    520 
    521     private class StringTable {
    522         private final int offset;
    523         private final int size;
    524 
    525         public StringTable(@Nonnull SectionHeader header) {
    526             this.offset = header.getOffset();
    527             this.size = header.getSize();
    528 
    529             if (offset + size > buf.length) {
    530                 throw new InvalidOatFileException("String table extends past end of file");
    531             }
    532         }
    533 
    534         @Nonnull
    535         public String getString(int index) {
    536             if (index >= size) {
    537                 throw new InvalidOatFileException("String index is out of bounds");
    538             }
    539 
    540             int start = offset + index;
    541             int end = start;
    542             while (buf[end] != 0) {
    543                 end++;
    544                 if (end >= offset + size) {
    545                     throw new InvalidOatFileException("String extends past end of string table");
    546                 }
    547             }
    548 
    549             return new String(buf, start, end-start, Charset.forName("US-ASCII"));
    550         }
    551     }
    552 
    553     private class DexEntry {
    554         public final String entryName;
    555         public final byte[] buf;
    556         public final int dexOffset;
    557 
    558 
    559         public DexEntry(String entryName, byte[] buf, int dexOffset) {
    560             this.entryName = entryName;
    561             this.buf = buf;
    562             this.dexOffset = dexOffset;
    563         }
    564 
    565         public OatDexFile getDexFile() {
    566             return new OatDexFile(buf, dexOffset, entryName);
    567         }
    568     }
    569 
    570     private class DexEntryIterator implements Iterator<DexEntry> {
    571         int index = 0;
    572         int offset = oatHeader.getDexListStart();
    573 
    574         @Override public boolean hasNext() {
    575             return index < oatHeader.getDexFileCount();
    576         }
    577 
    578         @Override public DexEntry next() {
    579             int filenameLength = readSmallUint(offset);
    580             offset += 4;
    581 
    582             // TODO: what is the correct character encoding?
    583             String filename = new String(buf, offset, filenameLength, Charset.forName("US-ASCII"));
    584             offset += filenameLength;
    585 
    586             offset += 4; // checksum
    587 
    588             int dexOffset = readSmallUint(offset);
    589             offset += 4;
    590 
    591             byte[] buf;
    592             if (getOatVersion() >= 87 && vdexProvider != null && vdexProvider.getVdex() != null) {
    593                 buf = vdexProvider.getVdex();
    594             } else {
    595                 buf = OatFile.this.buf;
    596                 dexOffset += oatHeader.headerOffset;
    597             }
    598 
    599             if (getOatVersion() >= 75) {
    600                 offset += 4; // offset to class offsets table
    601             }
    602             if (getOatVersion() >= 73) {
    603                 offset += 4; // lookup table offset
    604             }
    605             if (getOatVersion() < 75) {
    606                 // prior to 75, the class offsets are included here directly
    607                 int classCount = readSmallUint(dexOffset + HeaderItem.CLASS_COUNT_OFFSET);
    608                 offset += 4 * classCount;
    609             }
    610 
    611             index++;
    612 
    613             return new DexEntry(filename, buf, dexOffset);
    614         }
    615 
    616         @Override public void remove() {
    617             throw new UnsupportedOperationException();
    618         }
    619     }
    620 
    621     public static class InvalidOatFileException extends RuntimeException {
    622         public InvalidOatFileException(String message) {
    623             super(message);
    624         }
    625     }
    626 
    627     public static class NotAnOatFileException extends RuntimeException {
    628         public NotAnOatFileException() {}
    629     }
    630 
    631     public interface VdexProvider {
    632         @Nullable
    633         byte[] getVdex();
    634     }
    635 }