blob: ec75585389bce0566093e16322a21f0353b178e2 [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dexfuzz.rawdex;
import dexfuzz.Log;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* An extension to RandomAccessFile that allows reading/writing
* DEX files in little-endian form, the variable-length LEB format
* and also provides word-alignment functions.
*/
public class DexRandomAccessFile extends RandomAccessFile {
private OffsetTracker offsetTracker;
public OffsetTracker getOffsetTracker() {
return offsetTracker;
}
public void setOffsetTracker(OffsetTracker offsetTracker) {
this.offsetTracker = offsetTracker;
}
/**
* Constructor, passes straight on to RandomAccessFile currently.
* @param filename The file to open.
* @param mode Strings "r" or "rw" work best.
*/
public DexRandomAccessFile(String filename, String mode)
throws FileNotFoundException {
super(filename, mode);
}
/**
* @return A 16-bit number, read from the file as little-endian.
*/
public short readUShort() throws IOException {
int b1 = readUnsignedByte();
int b2 = readUnsignedByte();
return (short) ((b2 << 8) | b1);
}
/**
* @param value A 16-bit number to be written to the file in little-endian.
*/
public void writeUShort(short value) throws IOException {
int b1 = value & 0xff;
int b2 = (value & 0xff00) >> 8;
writeByte(b1);
writeByte(b2);
}
/**
* @return A 32-bit number, read from the file as little-endian.
*/
public int readUInt() throws IOException {
int b1 = readUnsignedByte();
int b2 = readUnsignedByte();
int b3 = readUnsignedByte();
int b4 = readUnsignedByte();
return (b4 << 24) | (b3 << 16) | (b2 << 8) | b1;
}
/**
* @param value A 32-bit number to be written to the file in little-endian.
*/
public void writeUInt(int value) throws IOException {
int b1 = value & 0xff;
writeByte(b1);
int b2 = (value & 0xff00) >> 8;
writeByte(b2);
int b3 = (value & 0xff0000) >> 16;
writeByte(b3);
int b4 = (value & 0xff000000) >> 24;
writeByte(b4);
}
/**
* @return An up to 32-bit number, read from the file in ULEB128 form.
*/
public int readUleb128() throws IOException {
int shift = 0;
int value = 0;
int rawByte = readUnsignedByte();
boolean done = false;
while (!done) {
// Get the lower seven bits.
// 0x7f = 0111 1111
value |= ((rawByte & 0x7f) << shift);
shift += 7;
// Check the 8th bit - if it's 0, we're done.
// 0x80 = 1000 0000
if ((rawByte & 0x80) == 0) {
done = true;
} else {
rawByte = readUnsignedByte();
}
}
return value;
}
/**
* @param value A 32-bit number to be written to the file in ULEB128 form.
*/
public void writeUleb128(int value) throws IOException {
if (value == 0) {
writeByte(0);
return;
}
while (value != 0) {
int marker = 1;
// If we're down to the last 7 bits, the marker will be 0.
if ((value & 0xffffff80) == 0) {
marker = 0;
}
// Get the lowest 7 bits, add on the marker in the high bit.
int nextByte = value & 0x7f | (marker << 7);
writeByte(nextByte);
value >>>= 7;
}
}
/**
* Write out ULEB128 value always using 5 bytes.
* A version of ULEB128 that will always write out 5 bytes, because this
* value will be patched later, and if we used a smaller encoding, the new value
* may overflow the previously selected encoding size.
* The largest encoding for 0 in ULEB128 would be:
* 0x80 0x80 0x80 0x80 0x00
* and for 1 would be:
* 0x81 0x80 0x80 0x80 0x00
*/
public void writeLargestUleb128(int value) throws IOException {
Log.debug("Writing " + value + " using the largest possible ULEB128 encoding.");
if (value == 0) {
writeByte(0x80);
writeByte(0x80);
writeByte(0x80);
writeByte(0x80);
writeByte(0x0);
return;
}
for (int i = 0; i < 5; i++) {
int marker = 1;
// If we're writing the 5th byte, the marker is 0.
if (i == 4) {
marker = 0;
}
// Get the lowest 7 bits, add on the marker in the high bit.
int nextByte = value & 0x7f | (marker << 7);
writeByte(nextByte);
value >>>= 7;
}
}
/**
* @return An up to 32-bit number, read from the file in SLEB128 form.
*/
public int readSleb128() throws IOException {
int shift = 0;
int value = 0;
int rawByte = readUnsignedByte();
boolean done = false;
boolean mustSignExtend = false;
while (!done) {
// Get the lower seven bits.
// 0x7f = 0111 1111
value |= ((rawByte & 0x7f) << shift);
shift += 7;
// Check the 8th bit - if it's 0, we're done.
// 0x80 = 1000 0000
if ((rawByte & 0x80) == 0) {
// Check the 7th bit - if it's a 1, we need to sign extend.
if ((rawByte & 0x60) != 0) {
mustSignExtend = true;
}
done = true;
} else {
rawByte = readUnsignedByte();
}
}
if (mustSignExtend) {
// Example:
// say we shifted 7 bits, we need
// to make all the upper 25 bits 1s.
// load a 1...
// 00000000 00000000 00000000 00000001
// << 7
// 00000000 00000000 00000000 10000000
// - 1
// 00000000 00000000 00000000 01111111
// ~
// 11111111 11111111 11111111 10000000
int upperOnes = ~((1 << shift) - 1);
value |= (upperOnes);
}
return value;
}
/**
* @param value A 32-bit number to be written to the file in SLEB128 form.
*/
public void writeSleb128(int value) throws IOException {
if (value == 0) {
writeByte(0);
return;
}
if (value > 0) {
writeUleb128(value);
return;
}
if (value == -1) {
writeByte(0x7f);
}
// When it's all 1s (0xffffffff), we're done!
while (value != 0xffffffff) {
int marker = 1;
// If we're down to the last 7 bits (i.e., shifting a further 7 is all 1s),
// the marker will be 0.
if ((value >> 7) == 0xffffffff) {
marker = 0;
}
// Get the lowest 7 bits, add on the marker in the high bit.
int nextByte = value & 0x7f | (marker << 7);
writeByte(nextByte);
value >>= 7;
}
}
/**
* In DEX format, strings are in MUTF-8 format, the first ULEB128 value is the decoded size
* (i.e., string.length), and then follows a null-terminated series of characters.
* @param decodedSize The ULEB128 value that should have been read just before this.
* @return The raw bytes of the string, not including the null character.
*/
public byte[] readDexUtf(int decodedSize) throws IOException {
// In the dex MUTF-8, the encoded size can never be larger than 3 times
// the actual string's length (which is the ULEB128 value just before this
// string, the "decoded size")
// Therefore, allocate as much space as we might need.
byte[] str = new byte[decodedSize * 3];
// Get our first byte.
int encodedSize = 0;
byte rawByte = readByte();
// Keep reading until we find the end marker.
while (rawByte != 0) {
str[encodedSize++] = rawByte;
rawByte = readByte();
}
// Copy everything we read into str into the correctly-sized actual string.
byte[] actualString = new byte[encodedSize];
for (int i = 0; i < encodedSize; i++) {
actualString[i] = str[i];
}
return actualString;
}
/**
* Writes out raw bytes that would have been read by readDexUTF().
* Will automatically write out the null-byte at the end.
* @param data Bytes to be written out.
*/
public void writeDexUtf(byte[] data) throws IOException {
write(data);
// Remember to add the end marker.
writeByte(0);
}
/**
* Align the file handle's seek pointer to the next N bytes.
* @param alignment N to align to.
*/
public void alignForwards(int alignment) throws IOException {
long offset = getFilePointer();
long mask = alignment - 1;
if ((offset & mask) != 0) {
int extra = alignment - (int) (offset & mask);
seek(offset + extra);
}
}
/**
* Align the file handle's seek pointer backwards to the previous N bytes.
* @param alignment N to align to.
*/
public void alignBackwards(int alignment) throws IOException {
long offset = getFilePointer();
long mask = alignment - 1;
if ((offset & mask) != 0) {
offset &= (~mask);
seek(offset);
}
}
}