Home | History | Annotate | Download | only in rawdex
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package dexfuzz.rawdex;
     18 
     19 import dexfuzz.Log;
     20 
     21 import java.io.FileNotFoundException;
     22 import java.io.IOException;
     23 import java.io.RandomAccessFile;
     24 
     25 /**
     26  * An extension to RandomAccessFile that allows reading/writing
     27  * DEX files in little-endian form, the variable-length LEB format
     28  * and also provides word-alignment functions.
     29  */
     30 public class DexRandomAccessFile extends RandomAccessFile {
     31   private OffsetTracker offsetTracker;
     32 
     33   public OffsetTracker getOffsetTracker() {
     34     return offsetTracker;
     35   }
     36 
     37   public void setOffsetTracker(OffsetTracker offsetTracker) {
     38     this.offsetTracker = offsetTracker;
     39   }
     40 
     41   /**
     42    * Constructor, passes straight on to RandomAccessFile currently.
     43    * @param filename The file to open.
     44    * @param mode Strings "r" or "rw" work best.
     45    */
     46   public DexRandomAccessFile(String filename, String mode)
     47       throws FileNotFoundException {
     48     super(filename, mode);
     49   }
     50 
     51   /**
     52    * @return A 16-bit number, read from the file as little-endian.
     53    */
     54   public short readUShort() throws IOException {
     55     int b1 = readUnsignedByte();
     56     int b2 = readUnsignedByte();
     57     return (short) ((b2 << 8) | b1);
     58   }
     59 
     60   /**
     61    * @param value A 16-bit number to be written to the file in little-endian.
     62    */
     63   public void writeUShort(short value) throws IOException {
     64     int b1 = value & 0xff;
     65     int b2 = (value & 0xff00) >> 8;
     66     writeByte(b1);
     67     writeByte(b2);
     68   }
     69 
     70   /**
     71    * @return A 32-bit number, read from the file as little-endian.
     72    */
     73   public int readUInt() throws IOException {
     74     int b1 = readUnsignedByte();
     75     int b2 = readUnsignedByte();
     76     int b3 = readUnsignedByte();
     77     int b4 = readUnsignedByte();
     78     return (b4 << 24) | (b3 << 16) | (b2 << 8) | b1;
     79   }
     80 
     81   /**
     82    * @param value A 32-bit number to be written to the file in little-endian.
     83    */
     84   public void writeUInt(int value) throws IOException {
     85     int b1 = value & 0xff;
     86     writeByte(b1);
     87     int b2 = (value & 0xff00) >> 8;
     88     writeByte(b2);
     89     int b3 = (value & 0xff0000) >> 16;
     90     writeByte(b3);
     91     int b4 = (value & 0xff000000) >> 24;
     92     writeByte(b4);
     93   }
     94 
     95   /**
     96    * @return An up to 32-bit number, read from the file in ULEB128 form.
     97    */
     98   public int readUleb128() throws IOException {
     99     int shift = 0;
    100     int value = 0;
    101     int rawByte = readUnsignedByte();
    102     boolean done = false;
    103     while (!done) {
    104       // Get the lower seven bits.
    105       // 0x7f = 0111 1111
    106       value |= ((rawByte & 0x7f) << shift);
    107       shift += 7;
    108       // Check the 8th bit - if it's 0, we're done.
    109       // 0x80 = 1000 0000
    110       if ((rawByte & 0x80) == 0) {
    111         done = true;
    112       } else {
    113         rawByte = readUnsignedByte();
    114       }
    115     }
    116     return value;
    117   }
    118 
    119   /**
    120    * @param value A 32-bit number to be written to the file in ULEB128 form.
    121    */
    122   public void writeUleb128(int value) throws IOException {
    123     if (value == 0) {
    124       writeByte(0);
    125       return;
    126     }
    127 
    128     while (value != 0) {
    129       int marker = 1;
    130       // If we're down to the last 7 bits, the marker will be 0.
    131       if ((value & 0xffffff80) == 0) {
    132         marker = 0;
    133       }
    134       // Get the lowest 7 bits, add on the marker in the high bit.
    135       int nextByte = value & 0x7f | (marker << 7);
    136       writeByte(nextByte);
    137       value >>>= 7;
    138     }
    139   }
    140 
    141   /**
    142    * Write out ULEB128 value always using 5 bytes.
    143    * A version of ULEB128 that will always write out 5 bytes, because this
    144    * value will be patched later, and if we used a smaller encoding, the new value
    145    * may overflow the previously selected encoding size.
    146    * The largest encoding for 0 in ULEB128 would be:
    147    *   0x80 0x80 0x80 0x80 0x00
    148    * and for 1 would be:
    149    *   0x81 0x80 0x80 0x80 0x00
    150    */
    151   public void writeLargestUleb128(int value) throws IOException {
    152     Log.debug("Writing " + value + " using the largest possible ULEB128 encoding.");
    153     if (value == 0) {
    154       writeByte(0x80);
    155       writeByte(0x80);
    156       writeByte(0x80);
    157       writeByte(0x80);
    158       writeByte(0x0);
    159       return;
    160     }
    161 
    162     for (int i = 0; i < 5; i++) {
    163       int marker = 1;
    164       // If we're writing the 5th byte, the marker is 0.
    165       if (i == 4) {
    166         marker = 0;
    167       }
    168       // Get the lowest 7 bits, add on the marker in the high bit.
    169       int nextByte = value & 0x7f | (marker << 7);
    170       writeByte(nextByte);
    171       value >>>= 7;
    172     }
    173   }
    174 
    175   /**
    176    * @return An up to 32-bit number, read from the file in SLEB128 form.
    177    */
    178   public int readSleb128() throws IOException {
    179     int shift = 0;
    180     int value = 0;
    181     int rawByte = readUnsignedByte();
    182     boolean done = false;
    183     boolean mustSignExtend = false;
    184     while (!done) {
    185       // Get the lower seven bits.
    186       // 0x7f = 0111 1111
    187       value |= ((rawByte & 0x7f) << shift);
    188       shift += 7;
    189       // Check the 8th bit - if it's 0, we're done.
    190       // 0x80 = 1000 0000
    191       if ((rawByte & 0x80) == 0) {
    192         // Check the 7th bit - if it's a 1, we need to sign extend.
    193         if ((rawByte & 0x60) != 0) {
    194           mustSignExtend = true;
    195         }
    196         done = true;
    197       } else {
    198         rawByte = readUnsignedByte();
    199       }
    200     }
    201     if (mustSignExtend) {
    202       // Example:
    203       // say we shifted 7 bits, we need
    204       // to make all the upper 25 bits 1s.
    205       // load a 1...
    206       // 00000000 00000000 00000000 00000001
    207       // << 7
    208       // 00000000 00000000 00000000 10000000
    209       // - 1
    210       // 00000000 00000000 00000000 01111111
    211       // ~
    212       // 11111111 11111111 11111111 10000000
    213       int upperOnes = ~((1 << shift) - 1);
    214       value |= (upperOnes);
    215     }
    216     return value;
    217   }
    218 
    219   /**
    220    * @param value A 32-bit number to be written to the file in SLEB128 form.
    221    */
    222   public void writeSleb128(int value) throws IOException {
    223     if (value == 0) {
    224       writeByte(0);
    225       return;
    226     }
    227     if (value > 0) {
    228       writeUleb128(value);
    229       return;
    230     }
    231     if (value == -1) {
    232       writeByte(0x7f);
    233     }
    234 
    235     // When it's all 1s (0xffffffff), we're done!
    236     while (value != 0xffffffff) {
    237       int marker = 1;
    238       // If we're down to the last 7 bits (i.e., shifting a further 7 is all 1s),
    239       // the marker will be 0.
    240       if ((value >> 7) == 0xffffffff) {
    241         marker = 0;
    242       }
    243       // Get the lowest 7 bits, add on the marker in the high bit.
    244       int nextByte = value & 0x7f | (marker << 7);
    245       writeByte(nextByte);
    246       value >>= 7;
    247     }
    248   }
    249 
    250   /**
    251    * In DEX format, strings are in MUTF-8 format, the first ULEB128 value is the decoded size
    252    * (i.e., string.length), and then follows a null-terminated series of characters.
    253    * @param decodedSize The ULEB128 value that should have been read just before this.
    254    * @return The raw bytes of the string, not including the null character.
    255    */
    256   public byte[] readDexUtf(int decodedSize) throws IOException {
    257     // In the dex MUTF-8, the encoded size can never be larger than 3 times
    258     // the actual string's length (which is the ULEB128 value just before this
    259     // string, the "decoded size")
    260 
    261     // Therefore, allocate as much space as we might need.
    262     byte[] str = new byte[decodedSize * 3];
    263 
    264     // Get our first byte.
    265     int encodedSize = 0;
    266     byte rawByte = readByte();
    267 
    268     // Keep reading until we find the end marker.
    269     while (rawByte != 0) {
    270       str[encodedSize++] = rawByte;
    271       rawByte = readByte();
    272     }
    273 
    274     // Copy everything we read into str into the correctly-sized actual string.
    275     byte[] actualString = new byte[encodedSize];
    276     for (int i = 0; i < encodedSize; i++) {
    277       actualString[i] = str[i];
    278     }
    279 
    280     return actualString;
    281   }
    282 
    283   /**
    284    * Writes out raw bytes that would have been read by readDexUTF().
    285    * Will automatically write out the null-byte at the end.
    286    * @param data Bytes to be written out.
    287    */
    288   public void writeDexUtf(byte[] data) throws IOException {
    289     write(data);
    290     // Remember to add the end marker.
    291     writeByte(0);
    292   }
    293 
    294   /**
    295    * Align the file handle's seek pointer to the next N bytes.
    296    * @param alignment N to align to.
    297    */
    298   public void alignForwards(int alignment) throws IOException {
    299     long offset = getFilePointer();
    300     long mask = alignment - 1;
    301     if ((offset & mask) != 0) {
    302       int extra = alignment - (int) (offset & mask);
    303       seek(offset + extra);
    304     }
    305   }
    306 
    307   /**
    308    * Align the file handle's seek pointer backwards to the previous N bytes.
    309    * @param alignment N to align to.
    310    */
    311   public void alignBackwards(int alignment) throws IOException {
    312     long offset = getFilePointer();
    313     long mask = alignment - 1;
    314     if ((offset & mask) != 0) {
    315       offset &= (~mask);
    316       seek(offset);
    317     }
    318   }
    319 }
    320