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
17package dexfuzz.rawdex;
18
19import dexfuzz.Log;
20
21import java.io.FileNotFoundException;
22import java.io.IOException;
23import 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 */
30public 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