Generify the IO requirements for writing a dex file

The DexWriter implementations now write to a generic "DexDataStore", instead
of writing directly to a file.

Also, writing of the DebugItems and CodeItems are linked, with the code
items being written to a temporary location, and then the entire code item
section is written as a batch after the debug item section.
This commit is contained in:
Ben Gruver 2013-09-08 15:30:58 -07:00
parent 160449b83a
commit 99b46173c5
15 changed files with 659 additions and 359 deletions

View File

@ -91,8 +91,5 @@ public interface ClassSection<StringKey extends CharSequence, TypeKey extends Ch
void setCodeItemOffset(@Nonnull MethodKey key, int offset);
int getCodeItemOffset(@Nonnull MethodKey key);
void setDebugItemOffset(@Nonnull MethodKey key, int offset);
int getDebugItemOffset(@Nonnull MethodKey key);
void writeDebugItem(@Nonnull DebugWriter<StringKey, TypeKey> writer, DebugItem debugItem) throws IOException;
}

View File

@ -31,10 +31,7 @@
package org.jf.dexlib2.writer;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.google.common.collect.*;
import org.jf.dexlib2.AccessFlags;
import org.jf.dexlib2.ReferenceType;
import org.jf.dexlib2.base.BaseAnnotation;
@ -50,19 +47,22 @@ import org.jf.dexlib2.iface.instruction.formats.*;
import org.jf.dexlib2.iface.reference.*;
import org.jf.dexlib2.util.InstructionUtil;
import org.jf.dexlib2.util.MethodUtil;
import org.jf.dexlib2.writer.io.DeferredOutputStream;
import org.jf.dexlib2.writer.io.DeferredOutputStreamFactory;
import org.jf.dexlib2.writer.io.DexDataStore;
import org.jf.dexlib2.writer.io.MemoryDeferredOutputStream;
import org.jf.dexlib2.writer.util.TryListBuilder;
import org.jf.util.CollectionUtils;
import org.jf.util.ExceptionWithContext;
import org.jf.util.RandomAccessFileOutputStream;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
@ -189,14 +189,17 @@ public abstract class DexWriter<
classSection.getItems().size() * ClassDefItem.ITEM_SIZE;
}
public void writeTo(String path) throws IOException {
RandomAccessFile raf = new RandomAccessFile(path, "rw");
raf.setLength(0);
public void writeTo(@Nonnull DexDataStore dest) throws IOException {
this.writeTo(dest, MemoryDeferredOutputStream.getFactory());
}
public void writeTo(@Nonnull DexDataStore dest,
@Nonnull DeferredOutputStreamFactory tempFactory) throws IOException {
try {
int dataSectionOffset = getDataSectionOffset();
DexDataWriter headerWriter = outputAt(raf, 0);
DexDataWriter indexWriter = outputAt(raf, HeaderItem.ITEM_SIZE);
DexDataWriter offsetWriter = outputAt(raf, dataSectionOffset);
DexDataWriter headerWriter = outputAt(dest, 0);
DexDataWriter indexWriter = outputAt(dest, HeaderItem.ITEM_SIZE);
DexDataWriter offsetWriter = outputAt(dest, dataSectionOffset);
try {
writeStrings(indexWriter, offsetWriter);
writeTypes(indexWriter);
@ -209,8 +212,7 @@ public abstract class DexWriter<
writeAnnotationSets(offsetWriter);
writeAnnotationSetRefs(offsetWriter);
writeAnnotationDirectories(offsetWriter);
writeDebugItems(offsetWriter);
writeCodeItems(offsetWriter);
writeDebugAndCodeItems(offsetWriter, tempFactory.makeDeferredOutputStream());
writeClasses(indexWriter, offsetWriter);
writeMapItem(offsetWriter);
writeHeader(headerWriter, dataSectionOffset, offsetWriter.getPosition());
@ -219,15 +221,14 @@ public abstract class DexWriter<
indexWriter.close();
offsetWriter.close();
}
FileChannel fileChannel = raf.getChannel();
updateSignature(fileChannel);
updateChecksum(fileChannel);
updateSignature(dest);
updateChecksum(dest);
} finally {
raf.close();
dest.close();
}
}
private void updateSignature(FileChannel fileChannel) throws IOException {
private void updateSignature(@Nonnull DexDataStore dataStore) throws IOException {
MessageDigest md;
try {
md = MessageDigest.getInstance("SHA-1");
@ -235,14 +236,12 @@ public abstract class DexWriter<
throw new RuntimeException(ex);
}
ByteBuffer buffer = ByteBuffer.allocate(128 * 1024);
fileChannel.position(HeaderItem.HEADER_SIZE_OFFSET);
int bytesRead = fileChannel.read(buffer);
byte[] buffer = new byte[4 * 1024];
InputStream input = dataStore.readAt(HeaderItem.HEADER_SIZE_OFFSET);
int bytesRead = input.read(buffer);
while (bytesRead >= 0) {
buffer.rewind();
md.update(buffer);
buffer.clear();
bytesRead = fileChannel.read(buffer);
md.update(buffer, 0, bytesRead);
bytesRead = input.read(buffer);
}
byte[] signature = md.digest();
@ -251,38 +250,30 @@ public abstract class DexWriter<
}
// write signature
fileChannel.position(HeaderItem.SIGNATURE_OFFSET);
fileChannel.write(ByteBuffer.wrap(signature));
// flush
fileChannel.force(false);
OutputStream output = dataStore.outputAt(HeaderItem.SIGNATURE_OFFSET);
output.write(signature);
output.close();
}
private void updateChecksum(FileChannel fileChannel) throws IOException {
private void updateChecksum(@Nonnull DexDataStore dataStore) throws IOException {
Adler32 a32 = new Adler32();
ByteBuffer buffer = ByteBuffer.allocate(128 * 1024);
fileChannel.position(HeaderItem.SIGNATURE_OFFSET);
int bytesRead = fileChannel.read(buffer);
byte[] buffer = new byte[4 * 1024];
InputStream input = dataStore.readAt(HeaderItem.SIGNATURE_OFFSET);
int bytesRead = input.read(buffer);
while (bytesRead >= 0) {
a32.update(buffer.array(), 0, bytesRead);
buffer.clear();
bytesRead = fileChannel.read(buffer);
a32.update(buffer, 0, bytesRead);
bytesRead = input.read(buffer);
}
// write checksum, utilizing logic in DexWriter to write the integer value properly
fileChannel.position(HeaderItem.CHECKSUM_OFFSET);
int checksum = (int) a32.getValue();
ByteArrayOutputStream checksumBuf = new ByteArrayOutputStream();
DexDataWriter.writeInt(checksumBuf, checksum);
fileChannel.write(ByteBuffer.wrap(checksumBuf.toByteArray()));
// flush
fileChannel.force(false);
OutputStream output = dataStore.outputAt(HeaderItem.CHECKSUM_OFFSET);
DexDataWriter.writeInt(output, (int)a32.getValue());
output.close();
}
private static DexDataWriter outputAt(RandomAccessFile raf, int filePosition) throws IOException {
return new DexDataWriter(new RandomAccessFileOutputStream(raf, filePosition), filePosition);
private static DexDataWriter outputAt(DexDataStore dataStore, int filePosition) throws IOException {
return new DexDataWriter(dataStore.outputAt(filePosition), filePosition);
}
private void writeStrings(@Nonnull DexDataWriter indexWriter, @Nonnull DexDataWriter offsetWriter) throws IOException {
@ -708,81 +699,27 @@ public abstract class DexWriter<
}
}
private void writeDebugItems(@Nonnull DexDataWriter writer) throws IOException {
debugSectionOffset = writer.getPosition();
DebugWriter<StringKey, TypeKey> debugWriter =
new DebugWriter<StringKey, TypeKey>(stringSection, typeSection, writer);
private static class CodeItemOffset<MethodKey> {
@Nonnull MethodKey method;
int codeOffset;
for (ClassKey classKey: classSection.getSortedClasses()) {
Collection<? extends MethodKey> directMethods = classSection.getSortedDirectMethods(classKey);
Collection<? extends MethodKey> virtualMethods = classSection.getSortedVirtualMethods(classKey);
Iterable<MethodKey> methods = Iterables.concat(directMethods, virtualMethods);
for (MethodKey methodKey: methods) {
Iterable<? extends DebugItem> debugItems = classSection.getDebugItems(methodKey);
Iterable<? extends StringKey> parameterNames = classSection.getParameterNames(methodKey);
int parameterCount = 0;
if (parameterNames != null) {
int index = 0;
for (StringKey parameterName: parameterNames) {
index++;
if (parameterName != null) {
parameterCount = index;
}
}
}
if (debugItems == null && parameterCount == 0) {
continue;
}
numDebugInfoItems++;
classSection.setDebugItemOffset(methodKey, writer.getPosition());
int startingLineNumber = 0;
if (debugItems != null) {
for (org.jf.dexlib2.iface.debug.DebugItem debugItem: debugItems) {
if (debugItem instanceof LineNumber) {
startingLineNumber = ((LineNumber)debugItem).getLineNumber();
break;
}
}
}
writer.writeUleb128(startingLineNumber);
writer.writeUleb128(parameterCount);
if (parameterNames != null) {
int index = 0;
for (StringKey parameterName: parameterNames) {
if (index == parameterCount) {
break;
}
index++;
writer.writeUleb128(stringSection.getNullableItemIndex(parameterName) + 1);
}
}
if (debugItems != null) {
debugWriter.reset(startingLineNumber);
for (DebugItem debugItem: debugItems) {
classSection.writeDebugItem(debugWriter, debugItem);
}
}
// write an END_SEQUENCE opcode, to end the debug item
writer.write(0);
}
private CodeItemOffset(@Nonnull MethodKey method, int codeOffset) {
this.codeOffset = codeOffset;
this.method = method;
}
}
private void writeCodeItems(@Nonnull DexDataWriter writer) throws IOException {
private void writeDebugAndCodeItems(@Nonnull DexDataWriter offsetWriter,
@Nonnull DeferredOutputStream temp) throws IOException {
ByteArrayOutputStream ehBuf = new ByteArrayOutputStream();
debugSectionOffset = offsetWriter.getPosition();
DebugWriter<StringKey, TypeKey> debugWriter =
new DebugWriter<StringKey, TypeKey>(stringSection, typeSection, offsetWriter);
DexDataWriter codeWriter = new DexDataWriter(temp, 0);
List<CodeItemOffset<MethodKey>> codeOffsets = Lists.newArrayList();
writer.align();
codeSectionOffset = writer.getPosition();
for (ClassKey classKey: classSection.getSortedClasses()) {
Collection<? extends MethodKey> directMethods = classSection.getSortedDirectMethods(classKey);
Collection<? extends MethodKey> virtualMethods = classSection.getSortedVirtualMethods(classKey);
@ -790,223 +727,307 @@ public abstract class DexWriter<
Iterable<MethodKey> methods = Iterables.concat(directMethods, virtualMethods);
for (MethodKey methodKey: methods) {
Iterable<? extends Instruction> instructions = classSection.getInstructions(methodKey);
int debugItemOffset = classSection.getDebugItemOffset(methodKey);
int debugItemOffset = writeDebugItem(offsetWriter, debugWriter, methodKey);
int codeItemOffset = writeCodeItem(codeWriter, ehBuf, methodKey, debugItemOffset);
if (instructions == null && debugItemOffset == NO_OFFSET) {
continue;
}
numCodeItemItems++;
writer.align();
classSection.setCodeItemOffset(methodKey, writer.getPosition());
writer.writeUshort(classSection.getRegisterCount(methodKey));
boolean isStatic = AccessFlags.STATIC.isSet(classSection.getMethodAccessFlags(methodKey));
Collection<? extends TypeKey> parameters = typeListSection.getTypes(
protoSection.getParameters(methodSection.getPrototype(methodKey)));
List<? extends TryBlock<? extends ExceptionHandler>> tryBlocks = classSection.getTryBlocks(methodKey);
writer.writeUshort(MethodUtil.getParameterRegisterCount(parameters, isStatic));
if (instructions != null) {
tryBlocks = TryListBuilder.massageTryBlocks(tryBlocks);
int outParamCount = 0;
int codeUnitCount = 0;
for (Instruction instruction: instructions) {
codeUnitCount += instruction.getCodeUnits();
if (instruction.getOpcode().referenceType == ReferenceType.METHOD) {
ReferenceInstruction refInsn = (ReferenceInstruction)instruction;
MethodReference methodRef = (MethodReference)refInsn.getReference();
int paramCount = MethodUtil.getParameterRegisterCount(methodRef, InstructionUtil.isInvokeStatic(instruction.getOpcode()));
if (paramCount > outParamCount) {
outParamCount = paramCount;
}
}
}
writer.writeUshort(outParamCount);
writer.writeUshort(tryBlocks.size());
writer.writeInt(debugItemOffset);
InstructionWriter instructionWriter =
InstructionWriter.makeInstructionWriter(writer, stringSection, typeSection, fieldSection,
methodSection);
writer.writeInt(codeUnitCount);
for (Instruction instruction: instructions) {
switch (instruction.getOpcode().format) {
case Format10t:
instructionWriter.write((Instruction10t)instruction);
break;
case Format10x:
instructionWriter.write((Instruction10x)instruction);
break;
case Format11n:
instructionWriter.write((Instruction11n)instruction);
break;
case Format11x:
instructionWriter.write((Instruction11x)instruction);
break;
case Format12x:
instructionWriter.write((Instruction12x)instruction);
break;
case Format20bc:
instructionWriter.write((Instruction20bc)instruction);
break;
case Format20t:
instructionWriter.write((Instruction20t)instruction);
break;
case Format21c:
instructionWriter.write((Instruction21c)instruction);
break;
case Format21ih:
instructionWriter.write((Instruction21ih)instruction);
break;
case Format21lh:
instructionWriter.write((Instruction21lh)instruction);
break;
case Format21s:
instructionWriter.write((Instruction21s)instruction);
break;
case Format21t:
instructionWriter.write((Instruction21t)instruction);
break;
case Format22b:
instructionWriter.write((Instruction22b)instruction);
break;
case Format22c:
instructionWriter.write((Instruction22c)instruction);
break;
case Format22s:
instructionWriter.write((Instruction22s)instruction);
break;
case Format22t:
instructionWriter.write((Instruction22t)instruction);
break;
case Format22x:
instructionWriter.write((Instruction22x)instruction);
break;
case Format23x:
instructionWriter.write((Instruction23x)instruction);
break;
case Format30t:
instructionWriter.write((Instruction30t)instruction);
break;
case Format31c:
instructionWriter.write((Instruction31c)instruction);
break;
case Format31i:
instructionWriter.write((Instruction31i)instruction);
break;
case Format31t:
instructionWriter.write((Instruction31t)instruction);
break;
case Format32x:
instructionWriter.write((Instruction32x)instruction);
break;
case Format35c:
instructionWriter.write((Instruction35c)instruction);
break;
case Format3rc:
instructionWriter.write((Instruction3rc)instruction);
break;
case Format51l:
instructionWriter.write((Instruction51l)instruction);
break;
case ArrayPayload:
instructionWriter.write((ArrayPayload)instruction);
break;
case PackedSwitchPayload:
instructionWriter.write((PackedSwitchPayload)instruction);
break;
case SparseSwitchPayload:
instructionWriter.write((SparseSwitchPayload)instruction);
break;
default:
throw new ExceptionWithContext("Unsupported instruction format: %s",
instruction.getOpcode().format);
}
}
if (tryBlocks.size() > 0) {
writer.align();
// filter out unique lists of exception handlers
Map<List<? extends ExceptionHandler>, Integer> exceptionHandlerOffsetMap = Maps.newHashMap();
for (TryBlock<? extends ExceptionHandler> tryBlock: tryBlocks) {
exceptionHandlerOffsetMap.put(tryBlock.getExceptionHandlers(), 0);
}
DexDataWriter.writeUleb128(ehBuf, exceptionHandlerOffsetMap.size());
for (TryBlock<? extends ExceptionHandler> tryBlock: tryBlocks) {
int startAddress = tryBlock.getStartCodeAddress();
int endAddress = startAddress + tryBlock.getCodeUnitCount();
int tbCodeUnitCount = endAddress - startAddress;
writer.writeInt(startAddress);
writer.writeUshort(tbCodeUnitCount);
if (tryBlock.getExceptionHandlers().size() == 0) {
throw new ExceptionWithContext("No exception handlers for the try block!");
}
Integer offset = exceptionHandlerOffsetMap.get(tryBlock.getExceptionHandlers());
if (offset != 0) {
// exception handler has already been written out, just use it
writer.writeUshort(offset);
} else {
// if offset has not been set yet, we are about to write out a new exception handler
offset = ehBuf.size();
writer.writeUshort(offset);
exceptionHandlerOffsetMap.put(tryBlock.getExceptionHandlers(), offset);
// check if the last exception handler is a catch-all and adjust the size accordingly
int ehSize = tryBlock.getExceptionHandlers().size();
ExceptionHandler ehLast = tryBlock.getExceptionHandlers().get(ehSize-1);
if (ehLast.getExceptionType() == null) {
ehSize = ehSize * (-1) + 1;
}
// now let's layout the exception handlers, assuming that catch-all is always last
DexDataWriter.writeSleb128(ehBuf, ehSize);
for (ExceptionHandler eh : tryBlock.getExceptionHandlers()) {
TypeKey exceptionTypeKey = classSection.getExceptionType(eh);
int codeAddress = eh.getHandlerCodeAddress();
if (exceptionTypeKey != null) {
//regular exception handling
DexDataWriter.writeUleb128(ehBuf, typeSection.getItemIndex(exceptionTypeKey));
DexDataWriter.writeUleb128(ehBuf, codeAddress);
} else {
//catch-all
DexDataWriter.writeUleb128(ehBuf, codeAddress);
}
}
}
}
if (ehBuf.size() > 0) {
ehBuf.writeTo(writer);
ehBuf.reset();
}
}
} else {
// no instructions, all we have is the debug item offset
writer.writeUshort(0);
writer.writeUshort(0);
writer.writeInt(debugItemOffset);
writer.writeInt(0);
if (codeItemOffset != NO_OFFSET) {
codeOffsets.add(new CodeItemOffset<MethodKey>(methodKey, codeItemOffset));
}
}
}
offsetWriter.align();
codeSectionOffset = offsetWriter.getPosition();
temp.writeTo(offsetWriter);
temp.close();
for (CodeItemOffset<MethodKey> codeOffset: codeOffsets) {
classSection.setCodeItemOffset(codeOffset.method, codeSectionOffset + codeOffset.codeOffset);
}
}
private int writeDebugItem(@Nonnull DexDataWriter writer,
@Nonnull DebugWriter<StringKey, TypeKey> debugWriter,
@Nonnull MethodKey methodKey) throws IOException {
Iterable<? extends DebugItem> debugItems = classSection.getDebugItems(methodKey);
Iterable<? extends StringKey> parameterNames = classSection.getParameterNames(methodKey);
int parameterCount = 0;
if (parameterNames != null) {
int index = 0;
for (StringKey parameterName: parameterNames) {
index++;
if (parameterName != null) {
parameterCount = index;
}
}
}
if (debugItems == null && parameterCount == 0) {
return NO_OFFSET;
}
numDebugInfoItems++;
int debugItemOffset = writer.getPosition();
int startingLineNumber = 0;
if (debugItems != null) {
for (org.jf.dexlib2.iface.debug.DebugItem debugItem: debugItems) {
if (debugItem instanceof LineNumber) {
startingLineNumber = ((LineNumber)debugItem).getLineNumber();
break;
}
}
}
writer.writeUleb128(startingLineNumber);
writer.writeUleb128(parameterCount);
if (parameterNames != null) {
int index = 0;
for (StringKey parameterName: parameterNames) {
if (index == parameterCount) {
break;
}
index++;
writer.writeUleb128(stringSection.getNullableItemIndex(parameterName) + 1);
}
}
if (debugItems != null) {
debugWriter.reset(startingLineNumber);
for (DebugItem debugItem: debugItems) {
classSection.writeDebugItem(debugWriter, debugItem);
}
}
// write an END_SEQUENCE opcode, to end the debug item
writer.write(0);
return debugItemOffset;
}
private int writeCodeItem(@Nonnull DexDataWriter writer,
@Nonnull ByteArrayOutputStream ehBuf,
@Nonnull MethodKey methodKey,
int debugItemOffset) throws IOException {
Iterable<? extends Instruction> instructions = classSection.getInstructions(methodKey);
if (instructions == null && debugItemOffset == NO_OFFSET) {
return NO_OFFSET;
}
numCodeItemItems++;
writer.align();
int codeItemOffset = writer.getPosition();
classSection.setCodeItemOffset(methodKey, writer.getPosition());
writer.writeUshort(classSection.getRegisterCount(methodKey));
boolean isStatic = AccessFlags.STATIC.isSet(classSection.getMethodAccessFlags(methodKey));
Collection<? extends TypeKey> parameters = typeListSection.getTypes(
protoSection.getParameters(methodSection.getPrototype(methodKey)));
List<? extends TryBlock<? extends ExceptionHandler>> tryBlocks = classSection.getTryBlocks(methodKey);
writer.writeUshort(MethodUtil.getParameterRegisterCount(parameters, isStatic));
if (instructions != null) {
tryBlocks = TryListBuilder.massageTryBlocks(tryBlocks);
int outParamCount = 0;
int codeUnitCount = 0;
for (Instruction instruction: instructions) {
codeUnitCount += instruction.getCodeUnits();
if (instruction.getOpcode().referenceType == ReferenceType.METHOD) {
ReferenceInstruction refInsn = (ReferenceInstruction)instruction;
MethodReference methodRef = (MethodReference)refInsn.getReference();
int paramCount = MethodUtil.getParameterRegisterCount(methodRef, InstructionUtil.isInvokeStatic(instruction.getOpcode()));
if (paramCount > outParamCount) {
outParamCount = paramCount;
}
}
}
writer.writeUshort(outParamCount);
writer.writeUshort(tryBlocks.size());
writer.writeInt(debugItemOffset);
InstructionWriter instructionWriter =
InstructionWriter.makeInstructionWriter(writer, stringSection, typeSection, fieldSection,
methodSection);
writer.writeInt(codeUnitCount);
for (Instruction instruction: instructions) {
switch (instruction.getOpcode().format) {
case Format10t:
instructionWriter.write((Instruction10t)instruction);
break;
case Format10x:
instructionWriter.write((Instruction10x)instruction);
break;
case Format11n:
instructionWriter.write((Instruction11n)instruction);
break;
case Format11x:
instructionWriter.write((Instruction11x)instruction);
break;
case Format12x:
instructionWriter.write((Instruction12x)instruction);
break;
case Format20bc:
instructionWriter.write((Instruction20bc)instruction);
break;
case Format20t:
instructionWriter.write((Instruction20t)instruction);
break;
case Format21c:
instructionWriter.write((Instruction21c)instruction);
break;
case Format21ih:
instructionWriter.write((Instruction21ih)instruction);
break;
case Format21lh:
instructionWriter.write((Instruction21lh)instruction);
break;
case Format21s:
instructionWriter.write((Instruction21s)instruction);
break;
case Format21t:
instructionWriter.write((Instruction21t)instruction);
break;
case Format22b:
instructionWriter.write((Instruction22b)instruction);
break;
case Format22c:
instructionWriter.write((Instruction22c)instruction);
break;
case Format22s:
instructionWriter.write((Instruction22s)instruction);
break;
case Format22t:
instructionWriter.write((Instruction22t)instruction);
break;
case Format22x:
instructionWriter.write((Instruction22x)instruction);
break;
case Format23x:
instructionWriter.write((Instruction23x)instruction);
break;
case Format30t:
instructionWriter.write((Instruction30t)instruction);
break;
case Format31c:
instructionWriter.write((Instruction31c)instruction);
break;
case Format31i:
instructionWriter.write((Instruction31i)instruction);
break;
case Format31t:
instructionWriter.write((Instruction31t)instruction);
break;
case Format32x:
instructionWriter.write((Instruction32x)instruction);
break;
case Format35c:
instructionWriter.write((Instruction35c)instruction);
break;
case Format3rc:
instructionWriter.write((Instruction3rc)instruction);
break;
case Format51l:
instructionWriter.write((Instruction51l)instruction);
break;
case ArrayPayload:
instructionWriter.write((ArrayPayload)instruction);
break;
case PackedSwitchPayload:
instructionWriter.write((PackedSwitchPayload)instruction);
break;
case SparseSwitchPayload:
instructionWriter.write((SparseSwitchPayload)instruction);
break;
default:
throw new ExceptionWithContext("Unsupported instruction format: %s",
instruction.getOpcode().format);
}
}
if (tryBlocks.size() > 0) {
writer.align();
// filter out unique lists of exception handlers
Map<List<? extends ExceptionHandler>, Integer> exceptionHandlerOffsetMap = Maps.newHashMap();
for (TryBlock<? extends ExceptionHandler> tryBlock: tryBlocks) {
exceptionHandlerOffsetMap.put(tryBlock.getExceptionHandlers(), 0);
}
DexDataWriter.writeUleb128(ehBuf, exceptionHandlerOffsetMap.size());
for (TryBlock<? extends ExceptionHandler> tryBlock: tryBlocks) {
int startAddress = tryBlock.getStartCodeAddress();
int endAddress = startAddress + tryBlock.getCodeUnitCount();
int tbCodeUnitCount = endAddress - startAddress;
writer.writeInt(startAddress);
writer.writeUshort(tbCodeUnitCount);
if (tryBlock.getExceptionHandlers().size() == 0) {
throw new ExceptionWithContext("No exception handlers for the try block!");
}
Integer offset = exceptionHandlerOffsetMap.get(tryBlock.getExceptionHandlers());
if (offset != 0) {
// exception handler has already been written out, just use it
writer.writeUshort(offset);
} else {
// if offset has not been set yet, we are about to write out a new exception handler
offset = ehBuf.size();
writer.writeUshort(offset);
exceptionHandlerOffsetMap.put(tryBlock.getExceptionHandlers(), offset);
// check if the last exception handler is a catch-all and adjust the size accordingly
int ehSize = tryBlock.getExceptionHandlers().size();
ExceptionHandler ehLast = tryBlock.getExceptionHandlers().get(ehSize-1);
if (ehLast.getExceptionType() == null) {
ehSize = ehSize * (-1) + 1;
}
// now let's layout the exception handlers, assuming that catch-all is always last
DexDataWriter.writeSleb128(ehBuf, ehSize);
for (ExceptionHandler eh : tryBlock.getExceptionHandlers()) {
TypeKey exceptionTypeKey = classSection.getExceptionType(eh);
int codeAddress = eh.getHandlerCodeAddress();
if (exceptionTypeKey != null) {
//regular exception handling
DexDataWriter.writeUleb128(ehBuf, typeSection.getItemIndex(exceptionTypeKey));
DexDataWriter.writeUleb128(ehBuf, codeAddress);
} else {
//catch-all
DexDataWriter.writeUleb128(ehBuf, codeAddress);
}
}
}
}
if (ehBuf.size() > 0) {
ehBuf.writeTo(writer);
ehBuf.reset();
}
}
} else {
// no instructions, all we have is the debug item offset
writer.writeUshort(0);
writer.writeUshort(0);
writer.writeInt(debugItemOffset);
writer.writeInt(0);
}
return codeItemOffset;
}
private int calcNumItems() {

View File

@ -345,14 +345,6 @@ public class BuilderClassPool implements ClassSection<BuilderStringReference, Bu
return builderMethod.codeItemOffset;
}
@Override public void setDebugItemOffset(@Nonnull BuilderMethod builderMethod, int offset) {
builderMethod.debugInfoOffset = offset;
}
@Override public int getDebugItemOffset(@Nonnull BuilderMethod builderMethod) {
return builderMethod.debugInfoOffset;
}
@Nullable private BuilderStringReference checkStringReference(@Nullable StringReference stringReference) {
if (stringReference == null) {
return null;

View File

@ -49,7 +49,6 @@ public class BuilderMethod extends BaseMethodReference implements Method {
int annotationSetRefListOffset = DexWriter.NO_OFFSET;
int codeItemOffset = DexWriter.NO_OFFSET;
int debugInfoOffset = DexWriter.NO_OFFSET;
BuilderMethod(@Nonnull BuilderMethodReference methodReference,
@Nonnull List<? extends BuilderMethodParameter> parameters,

View File

@ -0,0 +1,8 @@
package org.jf.dexlib2.writer.io;
import java.io.IOException;
import java.io.OutputStream;
public abstract class DeferredOutputStream extends OutputStream {
public abstract void writeTo(OutputStream output) throws IOException;
}

View File

@ -0,0 +1,7 @@
package org.jf.dexlib2.writer.io;
import java.io.IOException;
public interface DeferredOutputStreamFactory {
DeferredOutputStream makeDeferredOutputStream() throws IOException;
}

View File

@ -0,0 +1,12 @@
package org.jf.dexlib2.writer.io;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public interface DexDataStore {
@Nonnull OutputStream outputAt(int offset);
@Nonnull InputStream readAt(int offset);
void close() throws IOException;
}

View File

@ -0,0 +1,28 @@
package org.jf.dexlib2.writer.io;
import org.jf.util.RandomAccessFileInputStream;
import org.jf.util.RandomAccessFileOutputStream;
import javax.annotation.Nonnull;
import java.io.*;
public class FileDataStore implements DexDataStore {
private final RandomAccessFile raf;
public FileDataStore(@Nonnull File file) throws FileNotFoundException, IOException {
this.raf = new RandomAccessFile(file, "rw");
this.raf.setLength(0);
}
@Nonnull @Override public OutputStream outputAt(int offset) {
return new RandomAccessFileOutputStream(raf, offset);
}
@Nonnull @Override public InputStream readAt(int offset) {
return new RandomAccessFileInputStream(raf, offset);
}
@Override public void close() throws IOException {
raf.close();
}
}

View File

@ -0,0 +1,104 @@
package org.jf.dexlib2.writer.io;
import com.google.common.io.ByteStreams;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.*;
/**
* A deferred output stream that uses a file as its backing store, with a in-memory intermediate buffer.
*/
public class FileDeferredOutputStream extends DeferredOutputStream {
private static final int DEFAULT_BUFFER_SIZE = 4 * 1024;
@Nonnull private final File backingFile;
@Nonnull private final NakedBufferedOutputStream output;
private int writtenBytes;
public FileDeferredOutputStream(@Nonnull File backingFile) throws FileNotFoundException {
this(backingFile, DEFAULT_BUFFER_SIZE);
}
public FileDeferredOutputStream(@Nonnull File backingFile, int bufferSize) throws FileNotFoundException {
this.backingFile = backingFile;
output = new NakedBufferedOutputStream(new FileOutputStream(backingFile), bufferSize);
}
@Override public void writeTo(@Nonnull OutputStream dest) throws IOException {
byte[] outBuf = output.getBuffer();
int count = output.getCount();
output.resetBuffer();
output.close();
// did we actually write something out to disk?
if (count != writtenBytes) {
InputStream fis = new FileInputStream(backingFile);
ByteStreams.copy(fis, dest);
backingFile.delete();
}
dest.write(outBuf, 0, count);
}
@Override public void write(int i) throws IOException {
output.write(i);
writtenBytes++;
}
@Override public void write(byte[] bytes) throws IOException {
output.write(bytes);
writtenBytes += bytes.length;
}
@Override public void write(byte[] bytes, int off, int len) throws IOException {
output.write(bytes, off, len);
writtenBytes += len;
}
@Override public void flush() throws IOException {
output.flush();
}
@Override public void close() throws IOException {
output.close();
}
private static class NakedBufferedOutputStream extends BufferedOutputStream {
public NakedBufferedOutputStream(OutputStream outputStream) {
super(outputStream);
}
public NakedBufferedOutputStream(OutputStream outputStream, int i) {
super(outputStream, i);
}
public int getCount() {
return count;
}
public void resetBuffer() {
count = 0;
}
public byte[] getBuffer() {
return buf;
}
}
@Nonnull
public static DeferredOutputStreamFactory getFactory(@Nullable File containingDirectory) {
return getFactory(containingDirectory, DEFAULT_BUFFER_SIZE);
}
@Nonnull
public static DeferredOutputStreamFactory getFactory(@Nullable final File containingDirectory,
final int bufferSize) {
return new DeferredOutputStreamFactory() {
@Override public DeferredOutputStream makeDeferredOutputStream() throws IOException {
File tempFile = File.createTempFile("dexlibtmp", null, containingDirectory);
return new FileDeferredOutputStream(tempFile, bufferSize);
}
};
}
}

View File

@ -0,0 +1,88 @@
package org.jf.dexlib2.writer.io;
import com.google.common.collect.Lists;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
/**
* A deferred output stream that is stored in memory
*/
public class MemoryDeferredOutputStream extends DeferredOutputStream {
private static final int DEFAULT_BUFFER_SIZE = 16 * 1024;
private final List<byte[]> buffers = Lists.newArrayList();
private byte[] currentBuffer;
private int currentPosition;
public MemoryDeferredOutputStream() {
this(DEFAULT_BUFFER_SIZE);
}
public MemoryDeferredOutputStream(int bufferSize) {
currentBuffer = new byte[bufferSize];
}
@Override public void writeTo(OutputStream output) throws IOException {
for (byte[] buffer: buffers) {
output.write(buffer);
}
if (currentPosition > 0) {
output.write(currentBuffer, 0, currentPosition);
}
buffers.clear();
currentPosition = 0;
}
@Override public void write(int i) throws IOException {
if (remaining() == 0) {
buffers.add(currentBuffer);
currentBuffer = new byte[currentBuffer.length];
currentPosition = 0;
}
currentBuffer[currentPosition++] = (byte)i;
}
@Override public void write(byte[] bytes) throws IOException {
write(bytes, 0, bytes.length);
}
@Override public void write(byte[] bytes, int offset, int length) throws IOException {
int remaining = remaining();
int written = 0;
while (length - written > 0) {
int toWrite = Math.min(remaining, length);
System.arraycopy(bytes, offset + written, currentBuffer, currentPosition, toWrite);
written += toWrite;
currentPosition += toWrite;
remaining = remaining();
if (remaining == 0) {
buffers.add(currentBuffer);
currentBuffer = new byte[currentBuffer.length];
currentPosition = 0;
remaining = currentBuffer.length;
}
}
}
private int remaining() {
return currentBuffer.length - currentPosition;
}
@Nonnull
public static DeferredOutputStreamFactory getFactory() {
return getFactory(DEFAULT_BUFFER_SIZE);
}
@Nonnull
public static DeferredOutputStreamFactory getFactory(final int bufferSize) {
return new DeferredOutputStreamFactory() {
@Override public DeferredOutputStream makeDeferredOutputStream() {
return new MemoryDeferredOutputStream(bufferSize);
}
};
}
}

View File

@ -465,14 +465,6 @@ public class ClassPool implements ClassSection<CharSequence, CharSequence,
return method.codeItemOffset;
}
@Override public void setDebugItemOffset(@Nonnull PoolMethod method, int offset) {
method.debugInfoOffset = offset;
}
@Override public int getDebugItemOffset(@Nonnull PoolMethod method) {
return method.debugInfoOffset;
}
@Override public void writeDebugItem(@Nonnull DebugWriter<CharSequence, CharSequence> writer,
DebugItem debugItem) throws IOException {
switch (debugItem.getDebugItemType()) {

View File

@ -40,10 +40,12 @@ import org.jf.dexlib2.iface.reference.*;
import org.jf.dexlib2.iface.value.*;
import org.jf.dexlib2.immutable.instruction.ImmutableInstructionFactory;
import org.jf.dexlib2.writer.DexWriter;
import org.jf.dexlib2.writer.io.FileDataStore;
import org.jf.dexlib2.writer.pool.ProtoPool.Key;
import org.jf.util.ExceptionWithContext;
import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Set;
@ -86,7 +88,7 @@ public class DexPool extends DexWriter<CharSequence, StringReference, CharSequen
for (ClassDef classDef: input.getClasses()) {
((ClassPool)dexPool.classSection).intern(classDef);
}
dexPool.writeTo(path);
dexPool.writeTo(new FileDataStore(new File(path)));
}
@Override protected void writeEncodedValue(@Nonnull InternalEncodedValueWriter writer,

View File

@ -48,7 +48,6 @@ class PoolMethod extends BaseMethodReference implements Method {
@Nonnull private final Method method;
protected int annotationSetRefListOffset = DexPool.NO_OFFSET;
protected int codeItemOffset = DexPool.NO_OFFSET;
protected int debugInfoOffset = DexPool.NO_OFFSET;
public static final Function<Method, PoolMethod> TRANSFORM = new Function<Method, PoolMethod>() {
@Override public PoolMethod apply(Method method) {

View File

@ -36,6 +36,7 @@ import org.antlr.runtime.tree.CommonTree;
import org.antlr.runtime.tree.CommonTreeNodeStream;
import org.apache.commons.cli.*;
import org.jf.dexlib2.writer.builder.DexBuilder;
import org.jf.dexlib2.writer.io.FileDataStore;
import org.jf.util.ConsoleUtil;
import org.jf.util.SmaliHelpFormatter;
@ -225,7 +226,7 @@ public class main {
System.exit(1);
}
dexBuilder.writeTo(outputDexFile);
dexBuilder.writeTo(new FileDataStore(new File(outputDexFile)));
} catch (RuntimeException ex) {
System.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:");
ex.printStackTrace();
@ -241,14 +242,14 @@ public class main {
File[] files = dir.listFiles();
if (files != null) {
for(File file: files) {
if (file.isDirectory()) {
getSmaliFilesInDir(file, smaliFiles);
} else if (file.getName().endsWith(".smali")) {
smaliFiles.add(file);
if (file.isDirectory()) {
getSmaliFilesInDir(file, smaliFiles);
} else if (file.getName().endsWith(".smali")) {
smaliFiles.add(file);
}
}
}
}
}
private static boolean assembleSmaliFile(File smaliFile, DexBuilder dexBuilder, boolean verboseErrors,
boolean printTokens, boolean allowOdex, int apiLevel)

View File

@ -0,0 +1,50 @@
package org.jf.util;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
public class RandomAccessFileInputStream extends InputStream {
private int filePosition;
@Nonnull private final RandomAccessFile raf;
public RandomAccessFileInputStream(@Nonnull RandomAccessFile raf, int filePosition) {
this.filePosition = filePosition;
this.raf = raf;
}
@Override public int read() throws IOException {
raf.seek(filePosition);
filePosition++;
return raf.read();
}
@Override public int read(byte[] bytes) throws IOException {
raf.seek(filePosition);
int bytesRead = raf.read(bytes);
filePosition += bytesRead;
return bytesRead;
}
@Override public int read(byte[] bytes, int offset, int length) throws IOException {
raf.seek(filePosition);
int bytesRead = raf.read(bytes, offset, length);
filePosition += bytesRead;
return bytesRead;
}
@Override public long skip(long l) throws IOException {
int skipBytes = Math.min((int)l, available());
filePosition += skipBytes;
return skipBytes;
}
@Override public int available() throws IOException {
return (int)raf.length() - filePosition;
}
@Override public boolean markSupported() {
return false;
}
}