diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/ClassSection.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/ClassSection.java index 628bf9cc..356b5ff2 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/writer/ClassSection.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/ClassSection.java @@ -91,8 +91,5 @@ public interface ClassSection writer, DebugItem debugItem) throws IOException; } diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/DexWriter.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/DexWriter.java index 7646ae1c..34d8f199 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/writer/DexWriter.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/DexWriter.java @@ -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 debugWriter = - new DebugWriter(stringSection, typeSection, writer); + private static class CodeItemOffset { + @Nonnull MethodKey method; + int codeOffset; - for (ClassKey classKey: classSection.getSortedClasses()) { - Collection directMethods = classSection.getSortedDirectMethods(classKey); - Collection virtualMethods = classSection.getSortedVirtualMethods(classKey); - - Iterable methods = Iterables.concat(directMethods, virtualMethods); - - for (MethodKey methodKey: methods) { - Iterable debugItems = classSection.getDebugItems(methodKey); - Iterable 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 debugWriter = + new DebugWriter(stringSection, typeSection, offsetWriter); + + DexDataWriter codeWriter = new DexDataWriter(temp, 0); + + List> codeOffsets = Lists.newArrayList(); - writer.align(); - codeSectionOffset = writer.getPosition(); for (ClassKey classKey: classSection.getSortedClasses()) { Collection directMethods = classSection.getSortedDirectMethods(classKey); Collection virtualMethods = classSection.getSortedVirtualMethods(classKey); @@ -790,223 +727,307 @@ public abstract class DexWriter< Iterable methods = Iterables.concat(directMethods, virtualMethods); for (MethodKey methodKey: methods) { - Iterable 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 parameters = typeListSection.getTypes( - protoSection.getParameters(methodSection.getPrototype(methodKey))); - - List> 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, Integer> exceptionHandlerOffsetMap = Maps.newHashMap(); - for (TryBlock tryBlock: tryBlocks) { - exceptionHandlerOffsetMap.put(tryBlock.getExceptionHandlers(), 0); - } - DexDataWriter.writeUleb128(ehBuf, exceptionHandlerOffsetMap.size()); - - for (TryBlock 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, codeItemOffset)); } } } + + offsetWriter.align(); + codeSectionOffset = offsetWriter.getPosition(); + + temp.writeTo(offsetWriter); + temp.close(); + + for (CodeItemOffset codeOffset: codeOffsets) { + classSection.setCodeItemOffset(codeOffset.method, codeSectionOffset + codeOffset.codeOffset); + } + } + + private int writeDebugItem(@Nonnull DexDataWriter writer, + @Nonnull DebugWriter debugWriter, + @Nonnull MethodKey methodKey) throws IOException { + Iterable debugItems = classSection.getDebugItems(methodKey); + Iterable 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 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 parameters = typeListSection.getTypes( + protoSection.getParameters(methodSection.getPrototype(methodKey))); + + List> 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, Integer> exceptionHandlerOffsetMap = Maps.newHashMap(); + for (TryBlock tryBlock: tryBlocks) { + exceptionHandlerOffsetMap.put(tryBlock.getExceptionHandlers(), 0); + } + DexDataWriter.writeUleb128(ehBuf, exceptionHandlerOffsetMap.size()); + + for (TryBlock 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() { diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderClassPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderClassPool.java index baf1c038..8db77376 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderClassPool.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderClassPool.java @@ -345,14 +345,6 @@ public class BuilderClassPool implements ClassSection parameters, diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/io/DeferredOutputStream.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/io/DeferredOutputStream.java new file mode 100644 index 00000000..2259146f --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/io/DeferredOutputStream.java @@ -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; +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/io/DeferredOutputStreamFactory.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/io/DeferredOutputStreamFactory.java new file mode 100644 index 00000000..8204309f --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/io/DeferredOutputStreamFactory.java @@ -0,0 +1,7 @@ +package org.jf.dexlib2.writer.io; + +import java.io.IOException; + +public interface DeferredOutputStreamFactory { + DeferredOutputStream makeDeferredOutputStream() throws IOException; +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/io/DexDataStore.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/io/DexDataStore.java new file mode 100644 index 00000000..0ca6930f --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/io/DexDataStore.java @@ -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; +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/io/FileDataStore.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/io/FileDataStore.java new file mode 100644 index 00000000..38905df7 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/io/FileDataStore.java @@ -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(); + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/io/FileDeferredOutputStream.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/io/FileDeferredOutputStream.java new file mode 100644 index 00000000..0cccb04d --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/io/FileDeferredOutputStream.java @@ -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); + } + }; + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/io/MemoryDeferredOutputStream.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/io/MemoryDeferredOutputStream.java new file mode 100644 index 00000000..3de8a0e3 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/io/MemoryDeferredOutputStream.java @@ -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 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); + } + }; + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/ClassPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/ClassPool.java index bdce3701..c3ab4487 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/ClassPool.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/ClassPool.java @@ -465,14 +465,6 @@ public class ClassPool implements ClassSection writer, DebugItem debugItem) throws IOException { switch (debugItem.getDebugItemType()) { diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/DexPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/DexPool.java index 6128612f..fd3db366 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/DexPool.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/DexPool.java @@ -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 TRANSFORM = new Function() { @Override public PoolMethod apply(Method method) { diff --git a/smali/src/main/java/org/jf/smali/main.java b/smali/src/main/java/org/jf/smali/main.java index 77f2be6f..c6095bf3 100644 --- a/smali/src/main/java/org/jf/smali/main.java +++ b/smali/src/main/java/org/jf/smali/main.java @@ -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) diff --git a/util/src/main/java/org/jf/util/RandomAccessFileInputStream.java b/util/src/main/java/org/jf/util/RandomAccessFileInputStream.java new file mode 100644 index 00000000..4c62aed7 --- /dev/null +++ b/util/src/main/java/org/jf/util/RandomAccessFileInputStream.java @@ -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; + } +}