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); void setCodeItemOffset(@Nonnull MethodKey key, int offset);
int getCodeItemOffset(@Nonnull MethodKey key); 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; void writeDebugItem(@Nonnull DebugWriter<StringKey, TypeKey> writer, DebugItem debugItem) throws IOException;
} }

View File

@ -31,10 +31,7 @@
package org.jf.dexlib2.writer; package org.jf.dexlib2.writer;
import com.google.common.collect.Iterables; import com.google.common.collect.*;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import org.jf.dexlib2.AccessFlags; import org.jf.dexlib2.AccessFlags;
import org.jf.dexlib2.ReferenceType; import org.jf.dexlib2.ReferenceType;
import org.jf.dexlib2.base.BaseAnnotation; 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.iface.reference.*;
import org.jf.dexlib2.util.InstructionUtil; import org.jf.dexlib2.util.InstructionUtil;
import org.jf.dexlib2.util.MethodUtil; 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.dexlib2.writer.util.TryListBuilder;
import org.jf.util.CollectionUtils; import org.jf.util.CollectionUtils;
import org.jf.util.ExceptionWithContext; import org.jf.util.ExceptionWithContext;
import org.jf.util.RandomAccessFileOutputStream;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.RandomAccessFile; import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.*; import java.util.*;
@ -189,14 +189,17 @@ public abstract class DexWriter<
classSection.getItems().size() * ClassDefItem.ITEM_SIZE; classSection.getItems().size() * ClassDefItem.ITEM_SIZE;
} }
public void writeTo(String path) throws IOException { public void writeTo(@Nonnull DexDataStore dest) throws IOException {
RandomAccessFile raf = new RandomAccessFile(path, "rw"); this.writeTo(dest, MemoryDeferredOutputStream.getFactory());
raf.setLength(0); }
public void writeTo(@Nonnull DexDataStore dest,
@Nonnull DeferredOutputStreamFactory tempFactory) throws IOException {
try { try {
int dataSectionOffset = getDataSectionOffset(); int dataSectionOffset = getDataSectionOffset();
DexDataWriter headerWriter = outputAt(raf, 0); DexDataWriter headerWriter = outputAt(dest, 0);
DexDataWriter indexWriter = outputAt(raf, HeaderItem.ITEM_SIZE); DexDataWriter indexWriter = outputAt(dest, HeaderItem.ITEM_SIZE);
DexDataWriter offsetWriter = outputAt(raf, dataSectionOffset); DexDataWriter offsetWriter = outputAt(dest, dataSectionOffset);
try { try {
writeStrings(indexWriter, offsetWriter); writeStrings(indexWriter, offsetWriter);
writeTypes(indexWriter); writeTypes(indexWriter);
@ -209,8 +212,7 @@ public abstract class DexWriter<
writeAnnotationSets(offsetWriter); writeAnnotationSets(offsetWriter);
writeAnnotationSetRefs(offsetWriter); writeAnnotationSetRefs(offsetWriter);
writeAnnotationDirectories(offsetWriter); writeAnnotationDirectories(offsetWriter);
writeDebugItems(offsetWriter); writeDebugAndCodeItems(offsetWriter, tempFactory.makeDeferredOutputStream());
writeCodeItems(offsetWriter);
writeClasses(indexWriter, offsetWriter); writeClasses(indexWriter, offsetWriter);
writeMapItem(offsetWriter); writeMapItem(offsetWriter);
writeHeader(headerWriter, dataSectionOffset, offsetWriter.getPosition()); writeHeader(headerWriter, dataSectionOffset, offsetWriter.getPosition());
@ -219,15 +221,14 @@ public abstract class DexWriter<
indexWriter.close(); indexWriter.close();
offsetWriter.close(); offsetWriter.close();
} }
FileChannel fileChannel = raf.getChannel(); updateSignature(dest);
updateSignature(fileChannel); updateChecksum(dest);
updateChecksum(fileChannel);
} finally { } finally {
raf.close(); dest.close();
} }
} }
private void updateSignature(FileChannel fileChannel) throws IOException { private void updateSignature(@Nonnull DexDataStore dataStore) throws IOException {
MessageDigest md; MessageDigest md;
try { try {
md = MessageDigest.getInstance("SHA-1"); md = MessageDigest.getInstance("SHA-1");
@ -235,14 +236,12 @@ public abstract class DexWriter<
throw new RuntimeException(ex); throw new RuntimeException(ex);
} }
ByteBuffer buffer = ByteBuffer.allocate(128 * 1024); byte[] buffer = new byte[4 * 1024];
fileChannel.position(HeaderItem.HEADER_SIZE_OFFSET); InputStream input = dataStore.readAt(HeaderItem.HEADER_SIZE_OFFSET);
int bytesRead = fileChannel.read(buffer); int bytesRead = input.read(buffer);
while (bytesRead >= 0) { while (bytesRead >= 0) {
buffer.rewind(); md.update(buffer, 0, bytesRead);
md.update(buffer); bytesRead = input.read(buffer);
buffer.clear();
bytesRead = fileChannel.read(buffer);
} }
byte[] signature = md.digest(); byte[] signature = md.digest();
@ -251,38 +250,30 @@ public abstract class DexWriter<
} }
// write signature // write signature
fileChannel.position(HeaderItem.SIGNATURE_OFFSET); OutputStream output = dataStore.outputAt(HeaderItem.SIGNATURE_OFFSET);
fileChannel.write(ByteBuffer.wrap(signature)); output.write(signature);
output.close();
// flush
fileChannel.force(false);
} }
private void updateChecksum(FileChannel fileChannel) throws IOException { private void updateChecksum(@Nonnull DexDataStore dataStore) throws IOException {
Adler32 a32 = new Adler32(); Adler32 a32 = new Adler32();
ByteBuffer buffer = ByteBuffer.allocate(128 * 1024); byte[] buffer = new byte[4 * 1024];
fileChannel.position(HeaderItem.SIGNATURE_OFFSET); InputStream input = dataStore.readAt(HeaderItem.SIGNATURE_OFFSET);
int bytesRead = fileChannel.read(buffer); int bytesRead = input.read(buffer);
while (bytesRead >= 0) { while (bytesRead >= 0) {
a32.update(buffer.array(), 0, bytesRead); a32.update(buffer, 0, bytesRead);
buffer.clear(); bytesRead = input.read(buffer);
bytesRead = fileChannel.read(buffer);
} }
// write checksum, utilizing logic in DexWriter to write the integer value properly // write checksum, utilizing logic in DexWriter to write the integer value properly
fileChannel.position(HeaderItem.CHECKSUM_OFFSET); OutputStream output = dataStore.outputAt(HeaderItem.CHECKSUM_OFFSET);
int checksum = (int) a32.getValue(); DexDataWriter.writeInt(output, (int)a32.getValue());
ByteArrayOutputStream checksumBuf = new ByteArrayOutputStream(); output.close();
DexDataWriter.writeInt(checksumBuf, checksum);
fileChannel.write(ByteBuffer.wrap(checksumBuf.toByteArray()));
// flush
fileChannel.force(false);
} }
private static DexDataWriter outputAt(RandomAccessFile raf, int filePosition) throws IOException { private static DexDataWriter outputAt(DexDataStore dataStore, int filePosition) throws IOException {
return new DexDataWriter(new RandomAccessFileOutputStream(raf, filePosition), filePosition); return new DexDataWriter(dataStore.outputAt(filePosition), filePosition);
} }
private void writeStrings(@Nonnull DexDataWriter indexWriter, @Nonnull DexDataWriter offsetWriter) throws IOException { 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 { private static class CodeItemOffset<MethodKey> {
debugSectionOffset = writer.getPosition(); @Nonnull MethodKey method;
DebugWriter<StringKey, TypeKey> debugWriter = int codeOffset;
new DebugWriter<StringKey, TypeKey>(stringSection, typeSection, writer);
for (ClassKey classKey: classSection.getSortedClasses()) { private CodeItemOffset(@Nonnull MethodKey method, int codeOffset) {
Collection<? extends MethodKey> directMethods = classSection.getSortedDirectMethods(classKey); this.codeOffset = codeOffset;
Collection<? extends MethodKey> virtualMethods = classSection.getSortedVirtualMethods(classKey); this.method = method;
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 void writeCodeItems(@Nonnull DexDataWriter writer) throws IOException { private void writeDebugAndCodeItems(@Nonnull DexDataWriter offsetWriter,
@Nonnull DeferredOutputStream temp) throws IOException {
ByteArrayOutputStream ehBuf = new ByteArrayOutputStream(); 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()) { for (ClassKey classKey: classSection.getSortedClasses()) {
Collection<? extends MethodKey> directMethods = classSection.getSortedDirectMethods(classKey); Collection<? extends MethodKey> directMethods = classSection.getSortedDirectMethods(classKey);
Collection<? extends MethodKey> virtualMethods = classSection.getSortedVirtualMethods(classKey); Collection<? extends MethodKey> virtualMethods = classSection.getSortedVirtualMethods(classKey);
@ -790,223 +727,307 @@ public abstract class DexWriter<
Iterable<MethodKey> methods = Iterables.concat(directMethods, virtualMethods); Iterable<MethodKey> methods = Iterables.concat(directMethods, virtualMethods);
for (MethodKey methodKey: methods) { for (MethodKey methodKey: methods) {
Iterable<? extends Instruction> instructions = classSection.getInstructions(methodKey); int debugItemOffset = writeDebugItem(offsetWriter, debugWriter, methodKey);
int debugItemOffset = classSection.getDebugItemOffset(methodKey); int codeItemOffset = writeCodeItem(codeWriter, ehBuf, methodKey, debugItemOffset);
if (instructions == null && debugItemOffset == NO_OFFSET) { if (codeItemOffset != NO_OFFSET) {
continue; codeOffsets.add(new CodeItemOffset<MethodKey>(methodKey, codeItemOffset));
}
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);
} }
} }
} }
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() { private int calcNumItems() {

View File

@ -345,14 +345,6 @@ public class BuilderClassPool implements ClassSection<BuilderStringReference, Bu
return builderMethod.codeItemOffset; 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) { @Nullable private BuilderStringReference checkStringReference(@Nullable StringReference stringReference) {
if (stringReference == null) { if (stringReference == null) {
return null; return null;

View File

@ -49,7 +49,6 @@ public class BuilderMethod extends BaseMethodReference implements Method {
int annotationSetRefListOffset = DexWriter.NO_OFFSET; int annotationSetRefListOffset = DexWriter.NO_OFFSET;
int codeItemOffset = DexWriter.NO_OFFSET; int codeItemOffset = DexWriter.NO_OFFSET;
int debugInfoOffset = DexWriter.NO_OFFSET;
BuilderMethod(@Nonnull BuilderMethodReference methodReference, BuilderMethod(@Nonnull BuilderMethodReference methodReference,
@Nonnull List<? extends BuilderMethodParameter> parameters, @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; 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, @Override public void writeDebugItem(@Nonnull DebugWriter<CharSequence, CharSequence> writer,
DebugItem debugItem) throws IOException { DebugItem debugItem) throws IOException {
switch (debugItem.getDebugItemType()) { 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.iface.value.*;
import org.jf.dexlib2.immutable.instruction.ImmutableInstructionFactory; import org.jf.dexlib2.immutable.instruction.ImmutableInstructionFactory;
import org.jf.dexlib2.writer.DexWriter; import org.jf.dexlib2.writer.DexWriter;
import org.jf.dexlib2.writer.io.FileDataStore;
import org.jf.dexlib2.writer.pool.ProtoPool.Key; import org.jf.dexlib2.writer.pool.ProtoPool.Key;
import org.jf.util.ExceptionWithContext; import org.jf.util.ExceptionWithContext;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.Set; import java.util.Set;
@ -86,7 +88,7 @@ public class DexPool extends DexWriter<CharSequence, StringReference, CharSequen
for (ClassDef classDef: input.getClasses()) { for (ClassDef classDef: input.getClasses()) {
((ClassPool)dexPool.classSection).intern(classDef); ((ClassPool)dexPool.classSection).intern(classDef);
} }
dexPool.writeTo(path); dexPool.writeTo(new FileDataStore(new File(path)));
} }
@Override protected void writeEncodedValue(@Nonnull InternalEncodedValueWriter writer, @Override protected void writeEncodedValue(@Nonnull InternalEncodedValueWriter writer,

View File

@ -48,7 +48,6 @@ class PoolMethod extends BaseMethodReference implements Method {
@Nonnull private final Method method; @Nonnull private final Method method;
protected int annotationSetRefListOffset = DexPool.NO_OFFSET; protected int annotationSetRefListOffset = DexPool.NO_OFFSET;
protected int codeItemOffset = 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>() { public static final Function<Method, PoolMethod> TRANSFORM = new Function<Method, PoolMethod>() {
@Override public PoolMethod apply(Method method) { @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.antlr.runtime.tree.CommonTreeNodeStream;
import org.apache.commons.cli.*; import org.apache.commons.cli.*;
import org.jf.dexlib2.writer.builder.DexBuilder; import org.jf.dexlib2.writer.builder.DexBuilder;
import org.jf.dexlib2.writer.io.FileDataStore;
import org.jf.util.ConsoleUtil; import org.jf.util.ConsoleUtil;
import org.jf.util.SmaliHelpFormatter; import org.jf.util.SmaliHelpFormatter;
@ -225,7 +226,7 @@ public class main {
System.exit(1); System.exit(1);
} }
dexBuilder.writeTo(outputDexFile); dexBuilder.writeTo(new FileDataStore(new File(outputDexFile)));
} catch (RuntimeException ex) { } catch (RuntimeException ex) {
System.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:"); System.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:");
ex.printStackTrace(); ex.printStackTrace();
@ -241,14 +242,14 @@ public class main {
File[] files = dir.listFiles(); File[] files = dir.listFiles();
if (files != null) { if (files != null) {
for(File file: files) { for(File file: files) {
if (file.isDirectory()) { if (file.isDirectory()) {
getSmaliFilesInDir(file, smaliFiles); getSmaliFilesInDir(file, smaliFiles);
} else if (file.getName().endsWith(".smali")) { } else if (file.getName().endsWith(".smali")) {
smaliFiles.add(file); smaliFiles.add(file);
}
} }
} }
} }
}
private static boolean assembleSmaliFile(File smaliFile, DexBuilder dexBuilder, boolean verboseErrors, private static boolean assembleSmaliFile(File smaliFile, DexBuilder dexBuilder, boolean verboseErrors,
boolean printTokens, boolean allowOdex, int apiLevel) 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;
}
}