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.io.ByteStreams;
     35 import org.jf.dexlib2.Opcodes;
     36 import org.jf.dexlib2.dexbacked.OatFile.SymbolTable.Symbol;
     37 import org.jf.dexlib2.dexbacked.raw.HeaderItem;
     38 import org.jf.util.AbstractForwardSequentialList;
     39 
     40 import javax.annotation.Nonnull;
     41 import java.io.EOFException;
     42 import java.io.IOException;
     43 import java.io.InputStream;
     44 import java.nio.charset.Charset;
     45 import java.util.AbstractList;
     46 import java.util.Iterator;
     47 import java.util.List;
     48 
     49 public class OatFile extends BaseDexBuffer {
     50     private static final byte[] ELF_MAGIC = new byte[] { 0x7f, 'E', 'L', 'F' };
     51     private static final byte[] OAT_MAGIC = new byte[] { 'o', 'a', 't', '\n' };
     52     private static final int MIN_ELF_HEADER_SIZE = 52;
     53 
     54     // These are the "known working" versions that I have manually inspected the source for.
     55     // Later version may or may not work, depending on what changed.
     56     private static final int MIN_OAT_VERSION = 56;
     57     private static final int MAX_OAT_VERSION = 71;
     58 
     59     public static final int UNSUPPORTED = 0;
     60     public static final int SUPPORTED = 1;
     61     public static final int UNKNOWN = 2;
     62 
     63     private final boolean is64bit;
     64     @Nonnull private final OatHeader oatHeader;
     65     @Nonnull private final Opcodes opcodes;
     66 
     67     public OatFile(@Nonnull byte[] buf) {
     68         super(buf);
     69 
     70         if (buf.length < MIN_ELF_HEADER_SIZE) {
     71             throw new NotAnOatFileException();
     72         }
     73 
     74         verifyMagic(buf);
     75 
     76         if (buf[4] == 1) {
     77             is64bit = false;
     78         } else if (buf[4] == 2) {
     79             is64bit = true;
     80         } else {
     81             throw new InvalidOatFileException(String.format("Invalid word-size value: %x", buf[5]));
     82         }
     83 
     84         OatHeader oatHeader = null;
     85         SymbolTable symbolTable = getSymbolTable();
     86         for (Symbol symbol: symbolTable.getSymbols()) {
     87             if (symbol.getName().equals("oatdata")) {
     88                 oatHeader = new OatHeader(symbol.getFileOffset());
     89                 break;
     90             }
     91         }
     92 
     93         if (oatHeader == null) {
     94             throw new InvalidOatFileException("Oat file has no oatdata symbol");
     95         }
     96         this.oatHeader = oatHeader;
     97 
     98         if (!oatHeader.isValid()) {
     99             throw new InvalidOatFileException("Invalid oat magic value");
    100         }
    101 
    102         this.opcodes = Opcodes.forArtVersion(oatHeader.getVersion());
    103     }
    104 
    105     private static void verifyMagic(byte[] buf) {
    106         for (int i = 0; i < ELF_MAGIC.length; i++) {
    107             if (buf[i] != ELF_MAGIC[i]) {
    108                 throw new NotAnOatFileException();
    109             }
    110         }
    111     }
    112 
    113     public static OatFile fromInputStream(@Nonnull InputStream is)
    114             throws IOException {
    115         if (!is.markSupported()) {
    116             throw new IllegalArgumentException("InputStream must support mark");
    117         }
    118         is.mark(4);
    119         byte[] partialHeader = new byte[4];
    120         try {
    121             ByteStreams.readFully(is, partialHeader);
    122         } catch (EOFException ex) {
    123             throw new NotAnOatFileException();
    124         } finally {
    125             is.reset();
    126         }
    127 
    128         verifyMagic(partialHeader);
    129 
    130         is.reset();
    131 
    132         byte[] buf = ByteStreams.toByteArray(is);
    133         return new OatFile(buf);
    134     }
    135 
    136     public int getOatVersion() {
    137         return oatHeader.getVersion();
    138     }
    139 
    140     public int isSupportedVersion() {
    141         int version = getOatVersion();
    142         if (version < MIN_OAT_VERSION) {
    143             return UNSUPPORTED;
    144         }
    145         if (version <= MAX_OAT_VERSION) {
    146             return SUPPORTED;
    147         }
    148         return UNKNOWN;
    149     }
    150 
    151     @Nonnull
    152     public List<OatDexFile> getDexFiles() {
    153         return new AbstractForwardSequentialList<OatDexFile>() {
    154             @Override public int size() {
    155                 return oatHeader.getDexFileCount();
    156             }
    157 
    158             @Nonnull @Override public Iterator<OatDexFile> iterator() {
    159                 return new Iterator<OatDexFile>() {
    160                     int index = 0;
    161                     int offset = oatHeader.getDexListStart();
    162 
    163                     @Override public boolean hasNext() {
    164                         return index < size();
    165                     }
    166 
    167                     @Override public OatDexFile next() {
    168                         int filenameLength = readSmallUint(offset);
    169                         offset += 4;
    170 
    171                         // TODO: what is the correct character encoding?
    172                         String filename = new String(buf, offset, filenameLength, Charset.forName("US-ASCII"));
    173                         offset += filenameLength;
    174 
    175                         offset += 4; // checksum
    176 
    177                         int dexOffset = readSmallUint(offset) + oatHeader.offset;
    178                         offset += 4;
    179 
    180                         int classCount = readSmallUint(dexOffset + HeaderItem.CLASS_COUNT_OFFSET);
    181                         offset += 4 * classCount;
    182 
    183                         index++;
    184 
    185                         return new OatDexFile(dexOffset, filename);
    186                     }
    187 
    188                     @Override public void remove() {
    189                         throw new UnsupportedOperationException();
    190                     }
    191                 };
    192             }
    193         };
    194     }
    195 
    196     public class OatDexFile extends DexBackedDexFile {
    197         @Nonnull public final String filename;
    198 
    199         public OatDexFile(int offset, @Nonnull String filename) {
    200             super(opcodes, OatFile.this.buf, offset);
    201             this.filename = filename;
    202         }
    203 
    204         public int getOatVersion() {
    205             return OatFile.this.getOatVersion();
    206         }
    207 
    208         @Override public boolean hasOdexOpcodes() {
    209             return true;
    210         }
    211     }
    212 
    213     private class OatHeader {
    214         private final int offset;
    215 
    216         public OatHeader(int offset) {
    217             this.offset = offset;
    218         }
    219 
    220         public boolean isValid() {
    221             for (int i=0; i<OAT_MAGIC.length; i++) {
    222                 if (buf[offset + i] != OAT_MAGIC[i]) {
    223                     return false;
    224                 }
    225             }
    226 
    227             for (int i=4; i<7; i++) {
    228                 if (buf[offset + i] < '0' || buf[offset + i] > '9') {
    229                     return false;
    230                 }
    231             }
    232 
    233             return buf[offset + 7] == 0;
    234         }
    235 
    236         public int getVersion() {
    237             return Integer.valueOf(new String(buf, offset + 4, 3));
    238         }
    239 
    240         public int getDexFileCount() {
    241             return readSmallUint(offset + 20);
    242         }
    243 
    244         public int getKeyValueStoreSize() {
    245             int version = getVersion();
    246             if (version < 56) {
    247                 throw new IllegalStateException("Unsupported oat version");
    248             }
    249             int fieldOffset = 17 * 4;
    250             return readSmallUint(offset + fieldOffset);
    251         }
    252 
    253         public int getHeaderSize() {
    254             int version = getVersion();
    255              if (version >= 56) {
    256                 return 18*4 + getKeyValueStoreSize();
    257             } else {
    258                 throw new IllegalStateException("Unsupported oat version");
    259             }
    260 
    261         }
    262 
    263         public int getDexListStart() {
    264             return offset + getHeaderSize();
    265         }
    266     }
    267 
    268     @Nonnull
    269     private List<SectionHeader> getSections() {
    270         final int offset;
    271         final int entrySize;
    272         final int entryCount;
    273         if (is64bit) {
    274             offset = readLongAsSmallUint(40);
    275             entrySize = readUshort(58);
    276             entryCount = readUshort(60);
    277         } else {
    278             offset = readSmallUint(32);
    279             entrySize = readUshort(46);
    280             entryCount = readUshort(48);
    281         }
    282 
    283         if (offset + (entrySize * entryCount) > buf.length) {
    284             throw new InvalidOatFileException("The ELF section headers extend past the end of the file");
    285         }
    286 
    287         return new AbstractList<SectionHeader>() {
    288             @Override public SectionHeader get(int index) {
    289                 if (index < 0 || index >= entryCount) {
    290                     throw new IndexOutOfBoundsException();
    291                 }
    292                 if (is64bit) {
    293                     return new SectionHeader64Bit(offset + (index * entrySize));
    294                 } else {
    295                     return new SectionHeader32Bit(offset + (index * entrySize));
    296                 }
    297             }
    298 
    299             @Override public int size() {
    300                 return entryCount;
    301             }
    302         };
    303     }
    304 
    305     @Nonnull
    306     private SymbolTable getSymbolTable() {
    307         for (SectionHeader header: getSections()) {
    308             if (header.getType() == SectionHeader.TYPE_DYNAMIC_SYMBOL_TABLE) {
    309                 return new SymbolTable(header);
    310             }
    311         }
    312         throw new InvalidOatFileException("Oat file has no symbol table");
    313     }
    314 
    315     @Nonnull
    316     private StringTable getSectionNameStringTable() {
    317         int index = readUshort(50);
    318         if (index == 0) {
    319             throw new InvalidOatFileException("There is no section name string table");
    320         }
    321 
    322         try {
    323             return new StringTable(getSections().get(index));
    324         } catch (IndexOutOfBoundsException ex) {
    325             throw new InvalidOatFileException("The section index for the section name string table is invalid");
    326         }
    327     }
    328 
    329     private abstract class SectionHeader {
    330         protected final int offset;
    331         public static final int TYPE_DYNAMIC_SYMBOL_TABLE = 11;
    332         public SectionHeader(int offset) { this.offset = offset; }
    333         @Nonnull public String getName() { return getSectionNameStringTable().getString(readSmallUint(offset)); }
    334         public int getType() { return readInt(offset + 4); }
    335         public abstract long getAddress();
    336         public abstract int getOffset();
    337         public abstract int getSize();
    338         public abstract int getLink();
    339         public abstract int getEntrySize();
    340     }
    341 
    342     private class SectionHeader32Bit extends SectionHeader {
    343         public SectionHeader32Bit(int offset) { super(offset); }
    344         @Override public long getAddress() { return readInt(offset + 12) & 0xFFFFFFFFL; }
    345         @Override public int getOffset() { return readSmallUint(offset + 16); }
    346         @Override public int getSize() { return readSmallUint(offset + 20); }
    347         @Override public int getLink() { return readSmallUint(offset + 24); }
    348         @Override public int getEntrySize() { return readSmallUint(offset + 36); }
    349     }
    350 
    351     private class SectionHeader64Bit extends SectionHeader {
    352         public SectionHeader64Bit(int offset) { super(offset); }
    353         @Override public long getAddress() { return readLong(offset + 16); }
    354         @Override public int getOffset() { return readLongAsSmallUint(offset + 24); }
    355         @Override public int getSize() { return readLongAsSmallUint(offset + 32); }
    356         @Override public int getLink() { return readSmallUint(offset + 40); }
    357         @Override public int getEntrySize() { return readLongAsSmallUint(offset + 56); }
    358     }
    359 
    360     class SymbolTable {
    361         @Nonnull private final StringTable stringTable;
    362         private final int offset;
    363         private final int entryCount;
    364         private final int entrySize;
    365 
    366         public SymbolTable(@Nonnull SectionHeader header) {
    367             try {
    368                 this.stringTable = new StringTable(getSections().get(header.getLink()));
    369             } catch (IndexOutOfBoundsException ex) {
    370                 throw new InvalidOatFileException("String table section index is invalid");
    371             }
    372             this.offset = header.getOffset();
    373             this.entrySize = header.getEntrySize();
    374             this.entryCount = header.getSize() / entrySize;
    375 
    376             if (offset + entryCount * entrySize > buf.length) {
    377                 throw new InvalidOatFileException("Symbol table extends past end of file");
    378             }
    379         }
    380 
    381         @Nonnull
    382         public List<Symbol> getSymbols() {
    383             return new AbstractList<Symbol>() {
    384                 @Override public Symbol get(int index) {
    385                     if (index < 0 || index >= entryCount) {
    386                         throw new IndexOutOfBoundsException();
    387                     }
    388                     if (is64bit) {
    389                         return new Symbol64(offset + index * entrySize);
    390                     } else {
    391                         return new Symbol32(offset + index * entrySize);
    392                     }
    393                 }
    394 
    395                 @Override public int size() {
    396                     return entryCount;
    397                 }
    398             };
    399         }
    400 
    401         public abstract class Symbol {
    402             protected final int offset;
    403             public Symbol(int offset) { this.offset = offset; }
    404             @Nonnull public abstract String getName();
    405             public abstract long getValue();
    406             public abstract int getSize();
    407             public abstract int getSectionIndex();
    408 
    409             public int getFileOffset() {
    410                 SectionHeader sectionHeader;
    411                 try {
    412                     sectionHeader = getSections().get(getSectionIndex());
    413                 } catch (IndexOutOfBoundsException ex) {
    414                     throw new InvalidOatFileException("Section index for symbol is out of bounds");
    415                 }
    416 
    417                 long sectionAddress = sectionHeader.getAddress();
    418                 int sectionOffset = sectionHeader.getOffset();
    419                 int sectionSize = sectionHeader.getSize();
    420 
    421                 long symbolAddress = getValue();
    422 
    423                 if (symbolAddress < sectionAddress || symbolAddress >= sectionAddress + sectionSize) {
    424                     throw new InvalidOatFileException("symbol address lies outside it's associated section");
    425                 }
    426 
    427                 long fileOffset = (sectionOffset + (getValue() - sectionAddress));
    428                 assert fileOffset <= Integer.MAX_VALUE;
    429                 return (int)fileOffset;
    430             }
    431         }
    432 
    433         public class Symbol32 extends Symbol {
    434             public Symbol32(int offset) { super(offset); }
    435 
    436             @Nonnull
    437             public String getName() { return stringTable.getString(readSmallUint(offset)); }
    438             public long getValue() { return readSmallUint(offset + 4); }
    439             public int getSize() { return readSmallUint(offset + 8); }
    440             public int getSectionIndex() { return readUshort(offset + 14); }
    441         }
    442 
    443         public class Symbol64 extends Symbol {
    444             public Symbol64(int offset) { super(offset); }
    445 
    446             @Nonnull
    447             public String getName() { return stringTable.getString(readSmallUint(offset)); }
    448             public long getValue() { return readLong(offset + 8); }
    449             public int getSize() { return readLongAsSmallUint(offset + 16); }
    450             public int getSectionIndex() { return readUshort(offset + 6); }
    451         }
    452     }
    453 
    454     private class StringTable {
    455         private final int offset;
    456         private final int size;
    457 
    458         public StringTable(@Nonnull SectionHeader header) {
    459             this.offset = header.getOffset();
    460             this.size = header.getSize();
    461 
    462             if (offset + size > buf.length) {
    463                 throw new InvalidOatFileException("String table extends past end of file");
    464             }
    465         }
    466 
    467         @Nonnull
    468         public String getString(int index) {
    469             if (index >= size) {
    470                 throw new InvalidOatFileException("String index is out of bounds");
    471             }
    472 
    473             int start = offset + index;
    474             int end = start;
    475             while (buf[end] != 0) {
    476                 end++;
    477                 if (end >= offset + size) {
    478                     throw new InvalidOatFileException("String extends past end of string table");
    479                 }
    480             }
    481 
    482             return new String(buf, start, end-start, Charset.forName("US-ASCII"));
    483         }
    484 
    485     }
    486 
    487     public static class InvalidOatFileException extends RuntimeException {
    488         public InvalidOatFileException(String message) {
    489             super(message);
    490         }
    491     }
    492 
    493     public static class NotAnOatFileException extends RuntimeException {
    494         public NotAnOatFileException() {}
    495     }
    496 }