• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
OatFile(@onnull byte[] buf)75     public OatFile(@Nonnull byte[] buf) {
76         this(buf, null);
77     }
78 
OatFile(@onnull byte[] buf, @Nullable VdexProvider vdexProvider)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 
verifyMagic(byte[] buf)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 
fromInputStream(@onnull InputStream is)126     public static OatFile fromInputStream(@Nonnull InputStream is) throws IOException {
127         return fromInputStream(is, null);
128     }
129 
fromInputStream(@onnull InputStream is, @Nullable VdexProvider vdexProvider)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 
getOatVersion()153     public int getOatVersion() {
154         return oatHeader.getVersion();
155     }
156 
isSupportedVersion()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
getBootClassPath()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
getDexFiles()181     public List<OatDexFile> getDexFiles() {
182         return new AbstractForwardSequentialList<OatDexFile>() {
183             @Override public int size() {
184                 return oatHeader.getDexFileCount();
185             }
186 
187             @Nonnull @Override public Iterator<OatDexFile> iterator() {
188                 return Iterators.transform(new DexEntryIterator(), new Function<DexEntry, OatDexFile>() {
189                     @Nullable @Override public OatDexFile apply(DexEntry dexEntry) {
190                         return dexEntry.getDexFile();
191                     }
192                 });
193             }
194         };
195     }
196 
197     @Nonnull @Override public List<String> getDexEntryNames() throws IOException {
198         return new AbstractForwardSequentialList<String>() {
199             @Override public int size() {
200                 return oatHeader.getDexFileCount();
201             }
202 
203             @Nonnull @Override public Iterator<String> iterator() {
204                 return Iterators.transform(new DexEntryIterator(), new Function<DexEntry, String>() {
205                     @Nullable @Override public String apply(DexEntry dexEntry) {
206                         return dexEntry.entryName;
207                     }
208                 });
209             }
210         };
211     }
212 
213     @Nullable @Override public OatDexFile getEntry(@Nonnull String entryName) throws IOException {
214         DexEntryIterator iterator = new DexEntryIterator();
215         while (iterator.hasNext()) {
216             DexEntry entry = iterator.next();
217 
218             if (entry.entryName.equals(entryName)) {
219                 return entry.getDexFile();
220             }
221         }
222         return null;
223     }
224 
225     public class OatDexFile extends DexBackedDexFile implements MultiDexContainer.MultiDexFile {
226         @Nonnull public final String filename;
227 
228         public OatDexFile(byte[] buf, int offset, @Nonnull String filename) {
229             super(opcodes, buf, offset);
230             this.filename = filename;
231         }
232 
233         @Nonnull @Override public String getEntryName() {
234             return filename;
235         }
236 
237         @Nonnull @Override public OatFile getContainer() {
238             return OatFile.this;
239         }
240 
241         @Override public boolean hasOdexOpcodes() {
242             return true;
243         }
244     }
245 
246     private class OatHeader {
247         private final int headerOffset;
248         private final int keyValueStoreOffset;
249 
250         public OatHeader(int offset) {
251             this.headerOffset = offset;
252             if (getVersion() >= 127) {
253                 this.keyValueStoreOffset = 19 * 4;
254             } else {
255                 this.keyValueStoreOffset = 18 * 4;
256             }
257         }
258 
259         public boolean isValid() {
260             for (int i=0; i<OAT_MAGIC.length; i++) {
261                 if (buf[headerOffset + i] != OAT_MAGIC[i]) {
262                     return false;
263                 }
264             }
265 
266             for (int i=4; i<7; i++) {
267                 if (buf[headerOffset + i] < '0' || buf[headerOffset + i] > '9') {
268                     return false;
269                 }
270             }
271 
272             return buf[headerOffset + 7] == 0;
273         }
274 
275         public int getVersion() {
276             return Integer.valueOf(new String(buf, headerOffset + 4, 3));
277         }
278 
279         public int getDexFileCount() {
280             return readSmallUint(headerOffset + 20);
281         }
282 
283         public int getKeyValueStoreSize() {
284             if (getVersion() < MIN_OAT_VERSION) {
285                 throw new IllegalStateException("Unsupported oat version");
286             }
287             int fieldOffset = keyValueStoreOffset - 4;
288             return readSmallUint(headerOffset + fieldOffset);
289         }
290 
291         public int getHeaderSize() {
292             if (getVersion() < MIN_OAT_VERSION) {
293                 throw new IllegalStateException("Unsupported oat version");
294             }
295             return keyValueStoreOffset + getKeyValueStoreSize();
296         }
297 
298         @Nullable
299         public String getKeyValue(@Nonnull String key) {
300             int size = getKeyValueStoreSize();
301 
302             int offset = headerOffset + keyValueStoreOffset;
303             int endOffset = offset + size;
304 
305             while (offset < endOffset) {
306                 int keyStartOffset = offset;
307                 while (offset < endOffset && buf[offset] != '\0') {
308                     offset++;
309                 }
310                 if (offset >= endOffset) {
311                     throw new InvalidOatFileException("Oat file contains truncated key value store");
312                 }
313                 int keyEndOffset = offset;
314 
315                 String k = new String(buf, keyStartOffset, keyEndOffset - keyStartOffset);
316                 if (k.equals(key)) {
317                     int valueStartOffset = ++offset;
318                     while (offset < endOffset && buf[offset] != '\0') {
319                         offset++;
320                     }
321                     if (offset >= endOffset) {
322                         throw new InvalidOatFileException("Oat file contains truncated key value store");
323                     }
324                     int valueEndOffset = offset;
325                     return new String(buf, valueStartOffset, valueEndOffset - valueStartOffset);
326                 }
327                 offset++;
328             }
329             return null;
330         }
331 
332         public int getDexListStart() {
333             if (getVersion() >= 127) {
334                 return headerOffset + readSmallUint(headerOffset + (6 * 4));
335             } else {
336                 return headerOffset + getHeaderSize();
337             }
338         }
339     }
340 
341     @Nonnull
342     private List<SectionHeader> getSections() {
343         final int offset;
344         final int entrySize;
345         final int entryCount;
346         if (is64bit) {
347             offset = readLongAsSmallUint(40);
348             entrySize = readUshort(58);
349             entryCount = readUshort(60);
350         } else {
351             offset = readSmallUint(32);
352             entrySize = readUshort(46);
353             entryCount = readUshort(48);
354         }
355 
356         if (offset + (entrySize * entryCount) > buf.length) {
357             throw new InvalidOatFileException("The ELF section headers extend past the end of the file");
358         }
359 
360         return new AbstractList<SectionHeader>() {
361             @Override public SectionHeader get(int index) {
362                 if (index < 0 || index >= entryCount) {
363                     throw new IndexOutOfBoundsException();
364                 }
365                 if (is64bit) {
366                     return new SectionHeader64Bit(offset + (index * entrySize));
367                 } else {
368                     return new SectionHeader32Bit(offset + (index * entrySize));
369                 }
370             }
371 
372             @Override public int size() {
373                 return entryCount;
374             }
375         };
376     }
377 
378     @Nonnull
379     private SymbolTable getSymbolTable() {
380         for (SectionHeader header: getSections()) {
381             if (header.getType() == SectionHeader.TYPE_DYNAMIC_SYMBOL_TABLE) {
382                 return new SymbolTable(header);
383             }
384         }
385         throw new InvalidOatFileException("Oat file has no symbol table");
386     }
387 
388     @Nonnull
389     private StringTable getSectionNameStringTable() {
390         int index = readUshort(50);
391         if (index == 0) {
392             throw new InvalidOatFileException("There is no section name string table");
393         }
394 
395         try {
396             return new StringTable(getSections().get(index));
397         } catch (IndexOutOfBoundsException ex) {
398             throw new InvalidOatFileException("The section index for the section name string table is invalid");
399         }
400     }
401 
402     private abstract class SectionHeader {
403         protected final int offset;
404         public static final int TYPE_DYNAMIC_SYMBOL_TABLE = 11;
405         public SectionHeader(int offset) { this.offset = offset; }
406         @Nonnull public String getName() { return getSectionNameStringTable().getString(readSmallUint(offset)); }
407         public int getType() { return readInt(offset + 4); }
408         public abstract long getAddress();
409         public abstract int getOffset();
410         public abstract int getSize();
411         public abstract int getLink();
412         public abstract int getEntrySize();
413     }
414 
415     private class SectionHeader32Bit extends SectionHeader {
416         public SectionHeader32Bit(int offset) { super(offset); }
417         @Override public long getAddress() { return readInt(offset + 12) & 0xFFFFFFFFL; }
418         @Override public int getOffset() { return readSmallUint(offset + 16); }
419         @Override public int getSize() { return readSmallUint(offset + 20); }
420         @Override public int getLink() { return readSmallUint(offset + 24); }
421         @Override public int getEntrySize() { return readSmallUint(offset + 36); }
422     }
423 
424     private class SectionHeader64Bit extends SectionHeader {
425         public SectionHeader64Bit(int offset) { super(offset); }
426         @Override public long getAddress() { return readLong(offset + 16); }
427         @Override public int getOffset() { return readLongAsSmallUint(offset + 24); }
428         @Override public int getSize() { return readLongAsSmallUint(offset + 32); }
429         @Override public int getLink() { return readSmallUint(offset + 40); }
430         @Override public int getEntrySize() { return readLongAsSmallUint(offset + 56); }
431     }
432 
433     class SymbolTable {
434         @Nonnull private final StringTable stringTable;
435         private final int offset;
436         private final int entryCount;
437         private final int entrySize;
438 
439         public SymbolTable(@Nonnull SectionHeader header) {
440             try {
441                 this.stringTable = new StringTable(getSections().get(header.getLink()));
442             } catch (IndexOutOfBoundsException ex) {
443                 throw new InvalidOatFileException("String table section index is invalid");
444             }
445             this.offset = header.getOffset();
446             this.entrySize = header.getEntrySize();
447             this.entryCount = header.getSize() / entrySize;
448 
449             if (offset + entryCount * entrySize > buf.length) {
450                 throw new InvalidOatFileException("Symbol table extends past end of file");
451             }
452         }
453 
454         @Nonnull
455         public List<Symbol> getSymbols() {
456             return new AbstractList<Symbol>() {
457                 @Override public Symbol get(int index) {
458                     if (index < 0 || index >= entryCount) {
459                         throw new IndexOutOfBoundsException();
460                     }
461                     if (is64bit) {
462                         return new Symbol64(offset + index * entrySize);
463                     } else {
464                         return new Symbol32(offset + index * entrySize);
465                     }
466                 }
467 
468                 @Override public int size() {
469                     return entryCount;
470                 }
471             };
472         }
473 
474         public abstract class Symbol {
475             protected final int offset;
476             public Symbol(int offset) { this.offset = offset; }
477             @Nonnull public abstract String getName();
478             public abstract long getValue();
479             public abstract int getSize();
480             public abstract int getSectionIndex();
481 
482             public int getFileOffset() {
483                 SectionHeader sectionHeader;
484                 try {
485                     sectionHeader = getSections().get(getSectionIndex());
486                 } catch (IndexOutOfBoundsException ex) {
487                     throw new InvalidOatFileException("Section index for symbol is out of bounds");
488                 }
489 
490                 long sectionAddress = sectionHeader.getAddress();
491                 int sectionOffset = sectionHeader.getOffset();
492                 int sectionSize = sectionHeader.getSize();
493 
494                 long symbolAddress = getValue();
495 
496                 if (symbolAddress < sectionAddress || symbolAddress >= sectionAddress + sectionSize) {
497                     throw new InvalidOatFileException("symbol address lies outside it's associated section");
498                 }
499 
500                 long fileOffset = (sectionOffset + (getValue() - sectionAddress));
501                 assert fileOffset <= Integer.MAX_VALUE;
502                 return (int)fileOffset;
503             }
504         }
505 
506         public class Symbol32 extends Symbol {
507             public Symbol32(int offset) { super(offset); }
508 
509             @Nonnull
510             public String getName() { return stringTable.getString(readSmallUint(offset)); }
511             public long getValue() { return readSmallUint(offset + 4); }
512             public int getSize() { return readSmallUint(offset + 8); }
513             public int getSectionIndex() { return readUshort(offset + 14); }
514         }
515 
516         public class Symbol64 extends Symbol {
517             public Symbol64(int offset) { super(offset); }
518 
519             @Nonnull
520             public String getName() { return stringTable.getString(readSmallUint(offset)); }
521             public long getValue() { return readLong(offset + 8); }
522             public int getSize() { return readLongAsSmallUint(offset + 16); }
523             public int getSectionIndex() { return readUshort(offset + 6); }
524         }
525     }
526 
527     private class StringTable {
528         private final int offset;
529         private final int size;
530 
531         public StringTable(@Nonnull SectionHeader header) {
532             this.offset = header.getOffset();
533             this.size = header.getSize();
534 
535             if (offset + size > buf.length) {
536                 throw new InvalidOatFileException("String table extends past end of file");
537             }
538         }
539 
540         @Nonnull
541         public String getString(int index) {
542             if (index >= size) {
543                 throw new InvalidOatFileException("String index is out of bounds");
544             }
545 
546             int start = offset + index;
547             int end = start;
548             while (buf[end] != 0) {
549                 end++;
550                 if (end >= offset + size) {
551                     throw new InvalidOatFileException("String extends past end of string table");
552                 }
553             }
554 
555             return new String(buf, start, end-start, Charset.forName("US-ASCII"));
556         }
557     }
558 
559     private class DexEntry {
560         public final String entryName;
561         public final byte[] buf;
562         public final int dexOffset;
563 
564 
565         public DexEntry(String entryName, byte[] buf, int dexOffset) {
566             this.entryName = entryName;
567             this.buf = buf;
568             this.dexOffset = dexOffset;
569         }
570 
571         public OatDexFile getDexFile() {
572             return new OatDexFile(buf, dexOffset, entryName);
573         }
574     }
575 
576     private class DexEntryIterator implements Iterator<DexEntry> {
577         int index = 0;
578         int offset = oatHeader.getDexListStart();
579 
580         @Override public boolean hasNext() {
581             return index < oatHeader.getDexFileCount();
582         }
583 
584         @Override public DexEntry next() {
585             int filenameLength = readSmallUint(offset);
586             offset += 4;
587 
588             // TODO: what is the correct character encoding?
589             String filename = new String(buf, offset, filenameLength, Charset.forName("US-ASCII"));
590             offset += filenameLength;
591 
592             offset += 4; // checksum
593 
594             int dexOffset = readSmallUint(offset);
595             offset += 4;
596 
597             byte[] buf;
598             if (getOatVersion() >= 87 && vdexProvider != null && vdexProvider.getVdex() != null) {
599                 buf = vdexProvider.getVdex();
600             } else {
601                 buf = OatFile.this.buf;
602                 dexOffset += oatHeader.headerOffset;
603             }
604 
605             if (getOatVersion() >= 75) {
606                 offset += 4; // offset to class offsets table
607             }
608             if (getOatVersion() >= 73) {
609                 offset += 4; // lookup table offset
610             }
611             if (getOatVersion() >= 131) {
612                 offset += 4; // dex sections layout offset
613             }
614             if (getOatVersion() >= 127) {
615                 offset += 4; // method bss mapping offset
616             }
617             if (getOatVersion() < 75) {
618                 // prior to 75, the class offsets are included here directly
619                 int classCount = readSmallUint(dexOffset + HeaderItem.CLASS_COUNT_OFFSET);
620                 offset += 4 * classCount;
621             }
622 
623             index++;
624 
625             return new DexEntry(filename, buf, dexOffset);
626         }
627 
628         @Override public void remove() {
629             throw new UnsupportedOperationException();
630         }
631     }
632 
633     public static class InvalidOatFileException extends RuntimeException {
634         public InvalidOatFileException(String message) {
635             super(message);
636         }
637     }
638 
639     public static class NotAnOatFileException extends RuntimeException {
640         public NotAnOatFileException() {}
641     }
642 
643     public interface VdexProvider {
644         @Nullable
645         byte[] getVdex();
646     }
647 }