Beginning of annotated dump implementation
diff --git a/baksmali/src/main/java/org/jf/baksmali/dump.java b/baksmali/src/main/java/org/jf/baksmali/dump.java
index f428642..afe4824 100644
--- a/baksmali/src/main/java/org/jf/baksmali/dump.java
+++ b/baksmali/src/main/java/org/jf/baksmali/dump.java
@@ -28,79 +28,39 @@
package org.jf.baksmali;
-import org.jf.dexlib.DexFile;
-import org.jf.dexlib.Util.ByteArrayAnnotatedOutput;
+import org.jf.dexlib2.dexbacked.DexBackedDexFile;
+import org.jf.util.ConsoleUtil;
-import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
public class dump {
- public static void dump(DexFile dexFile, String dumpFileName, String outputDexFileName, boolean sort)
+ public static void dump(DexBackedDexFile dexFile, String dumpFileName)
throws IOException {
- if (sort) {
- //sort all items, to guarantee a unique ordering
- dexFile.setSortAllItems(true);
- } else {
- //don't change the order
- dexFile.setInplace(true);
- }
-
- ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput();
-
if (dumpFileName != null) {
- out.enableAnnotations(120, true);
- }
-
- dexFile.place();
- dexFile.writeTo(out);
-
- //write the dump
- if (dumpFileName != null) {
- out.finishAnnotating();
FileWriter writer = null;
try {
writer = new FileWriter(dumpFileName);
- out.writeAnnotationsTo(writer);
+
+ int consoleWidth = ConsoleUtil.getConsoleWidth();
+ if (consoleWidth <= 0) {
+ consoleWidth = 120;
+ }
+
+ dexFile.dumpTo(writer, consoleWidth);
} catch (IOException ex) {
- System.err.println("\n\nThere was an error while dumping the dex file to " + dumpFileName);
- ex.printStackTrace();
+ System.err.println("There was an error while dumping the dex file to " + dumpFileName);
+ ex.printStackTrace(System.err);
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException ex) {
- System.err.println("\n\nThere was an error while closing the dump file " + dumpFileName);
- ex.printStackTrace();
- }
- }
- }
- }
-
- //rewrite the dex file
- if (outputDexFileName != null) {
- byte[] bytes = out.toByteArray();
-
- DexFile.calcSignature(bytes);
- DexFile.calcChecksum(bytes);
-
- FileOutputStream fileOutputStream = null;
- try {
- fileOutputStream = new FileOutputStream(outputDexFileName);
- fileOutputStream.write(bytes);
- } catch (IOException ex) {
- System.err.println("\n\nThere was an error while writing the dex file " + outputDexFileName);
- ex.printStackTrace();
- } finally {
- if (fileOutputStream != null) {
- try {
- fileOutputStream.close();
- } catch (IOException ex) {
- System.err.println("\n\nThere was an error while closing the dex file " + outputDexFileName);
- ex.printStackTrace();
+ System.err.println("There was an error while closing the dump file " + dumpFileName);
+ ex.printStackTrace(System.err);
}
}
}
diff --git a/baksmali/src/main/java/org/jf/baksmali/main.java b/baksmali/src/main/java/org/jf/baksmali/main.java
index f5b9f87..11fb69a 100644
--- a/baksmali/src/main/java/org/jf/baksmali/main.java
+++ b/baksmali/src/main/java/org/jf/baksmali/main.java
@@ -307,16 +307,18 @@
noAccessorComments, registerInfo, verify, ignoreErrors, inlineTable, checkPackagePrivateAccess);
}
- //TODO: uncomment
- /*if ((doDump || write) && !dexFile.isOdex()) {
+ // TODO: implement rewrite + optional sort functionality
+
+ // TODO: need to check if odex file
+ if (doDump) {
try
{
- dump.dump(dexFile, dumpFileName, outputDexFileName, sort);
+ dump.dump(dexFile, dumpFileName);
}catch (IOException ex) {
System.err.println("Error occured while writing dump file");
ex.printStackTrace();
}
- }*/
+ }
} catch (RuntimeException ex) {
System.err.println("\n\nUNEXPECTED TOP-LEVEL EXCEPTION:");
ex.printStackTrace();
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedDexFile.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedDexFile.java
index 6faf0b0..707c444 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedDexFile.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedDexFile.java
@@ -31,13 +31,19 @@
package org.jf.dexlib2.dexbacked;
+import org.jf.dexlib2.dexbacked.raw.HeaderItem;
+import org.jf.dexlib2.dexbacked.raw.StringIdItem;
+import org.jf.dexlib2.dexbacked.raw.TypeIdItem;
import org.jf.dexlib2.dexbacked.util.FixedSizeSet;
import org.jf.dexlib2.iface.DexFile;
+import org.jf.dexlib2.util.AnnotatedBytes;
import org.jf.util.ExceptionWithContext;
import org.jf.util.Utf8Utils;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import java.io.IOException;
+import java.io.Writer;
import java.util.Set;
public abstract class DexBackedDexFile extends BaseDexBuffer implements DexFile {
@@ -57,6 +63,8 @@
@Override @Nonnull public abstract DexReader readerAt(int offset);
+ public abstract void dumpTo(Writer out, int width) throws IOException;
+
public static class Impl extends DexBackedDexFile {
private final int stringCount;
private final int stringStartOffset;
@@ -71,33 +79,6 @@
private final int classCount;
private final int classStartOffset;
- private static final byte[][] MAGIC_VALUES= new byte[][] {
- new byte[]{0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x35, 0x00},
- new byte[]{0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x36, 0x00}};
-
- private static final int LITTLE_ENDIAN_TAG = 0x12345678;
- private static final int BIG_ENDIAN_TAG = 0x78563412;
-
- private static final int CHECKSUM_OFFSET = 8;
- private static final int SIGNATURE_OFFSET = 12;
- private static final int ENDIAN_TAG_OFFSET = 40;
- private static final int MAP_OFFSET = 52;
- private static final int STRING_COUNT_OFFSET = 56;
- private static final int STRING_START_OFFSET = 60;
- private static final int TYPE_COUNT_OFFSET = 64;
- private static final int TYPE_START_OFFSET = 68;
- private static final int PROTO_COUNT_OFFSET = 72;
- private static final int PROTO_START_OFFSET = 76;
- private static final int FIELD_COUNT_OFFSET = 80;
- private static final int FIELD_START_OFFSET = 84;
- private static final int METHOD_COUNT_OFFSET = 88;
- private static final int METHOD_START_OFFSET = 92;
- private static final int CLASS_COUNT_OFFSET = 96;
- private static final int CLASS_START_OFFSET = 100;
-
- private static final int SIGNATURE_SIZE = 20;
-
- private static final int STRING_ID_ITEM_SIZE = 4;
private static final int TYPE_ID_ITEM_SIZE = 4;
private static final int PROTO_ID_ITEM_SIZE = 12;
private static final int FIELD_ID_ITEM_SIZE = 8;
@@ -124,18 +105,18 @@
verifyMagic();
verifyEndian();
- stringCount = readSmallUint(STRING_COUNT_OFFSET);
- stringStartOffset = readSmallUint(STRING_START_OFFSET);
- typeCount = readSmallUint(TYPE_COUNT_OFFSET);
- typeStartOffset = readSmallUint(TYPE_START_OFFSET);
- protoCount = readSmallUint(PROTO_COUNT_OFFSET);
- protoStartOffset = readSmallUint(PROTO_START_OFFSET);
- fieldCount = readSmallUint(FIELD_COUNT_OFFSET);
- fieldStartOffset = readSmallUint(FIELD_START_OFFSET);
- methodCount = readSmallUint(METHOD_COUNT_OFFSET);
- methodStartOffset = readSmallUint(METHOD_START_OFFSET);
- classCount = readSmallUint(CLASS_COUNT_OFFSET);
- classStartOffset = readSmallUint(CLASS_START_OFFSET);
+ stringCount = readSmallUint(HeaderItem.STRING_COUNT_OFFSET);
+ stringStartOffset = readSmallUint(HeaderItem.STRING_START_OFFSET);
+ typeCount = readSmallUint(HeaderItem.TYPE_COUNT_OFFSET);
+ typeStartOffset = readSmallUint(HeaderItem.TYPE_START_OFFSET);
+ protoCount = readSmallUint(HeaderItem.PROTO_COUNT_OFFSET);
+ protoStartOffset = readSmallUint(HeaderItem.PROTO_START_OFFSET);
+ fieldCount = readSmallUint(HeaderItem.FIELD_COUNT_OFFSET);
+ fieldStartOffset = readSmallUint(HeaderItem.FIELD_START_OFFSET);
+ methodCount = readSmallUint(HeaderItem.METHOD_COUNT_OFFSET);
+ methodStartOffset = readSmallUint(HeaderItem.METHOD_START_OFFSET);
+ classCount = readSmallUint(HeaderItem.CLASS_COUNT_OFFSET);
+ classStartOffset = readSmallUint(HeaderItem.CLASS_START_OFFSET);
}
@Nonnull
@@ -156,7 +137,7 @@
}
private void verifyMagic() {
- outer: for (byte[] magic: MAGIC_VALUES) {
+ outer: for (byte[] magic: HeaderItem.MAGIC_VALUES) {
for (int i=0; i<magic.length; i++) {
if (buf[i] != magic[i]) {
continue outer;
@@ -172,13 +153,13 @@
}
private void verifyEndian() {
- int endian = readInt(ENDIAN_TAG_OFFSET);
- if (endian == BIG_ENDIAN_TAG) {
+ int endian = readInt(HeaderItem.ENDIAN_TAG_OFFSET);
+ if (endian == HeaderItem.BIG_ENDIAN_TAG) {
throw new ExceptionWithContext("dexlib does not currently support big endian dex files.");
- } else if (endian != LITTLE_ENDIAN_TAG) {
+ } else if (endian != HeaderItem.LITTLE_ENDIAN_TAG) {
StringBuilder sb = new StringBuilder("Invalid endian tag:");
for (int i=0; i<4; i++) {
- sb.append(String.format(" %02x", buf[ENDIAN_TAG_OFFSET+i]));
+ sb.append(String.format(" %02x", buf[HeaderItem.ENDIAN_TAG_OFFSET+i]));
}
throw new ExceptionWithContext(sb.toString());
}
@@ -188,7 +169,7 @@
if (stringIndex < 0 || stringIndex >= stringCount) {
throw new ExceptionWithContext("String index out of bounds: %d", stringIndex);
}
- return stringStartOffset + stringIndex*STRING_ID_ITEM_SIZE;
+ return stringStartOffset + stringIndex*StringIdItem.ITEM_SIZE;
}
public int getTypeIdItemOffset(int typeIndex) {
@@ -274,5 +255,13 @@
public DexReader readerAt(int offset) {
return new DexReader(this, offset);
}
+
+ public void dumpTo(Writer out, int width) throws IOException {
+ AnnotatedBytes annotatedBytes = new AnnotatedBytes(width);
+ HeaderItem.getSection().annotateSection(annotatedBytes, this, 1);
+ annotatedBytes.annotate(0, " ");
+ StringIdItem.getSection().annotateSection(annotatedBytes, this, stringCount);
+ annotatedBytes.writeAnnotations(out, buf);
+ }
}
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/HeaderItem.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/HeaderItem.java
new file mode 100644
index 0000000..a176fb3
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/HeaderItem.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2013, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.dexlib2.dexbacked.raw;
+
+import org.jf.dexlib2.dexbacked.DexBackedDexFile;
+import org.jf.dexlib2.util.AnnotatedBytes;
+import org.jf.util.StringUtils;
+
+import javax.annotation.Nonnull;
+
+public class HeaderItem {
+ public static final int ITEM_SIZE = 0x70;
+
+ public static final byte[][] MAGIC_VALUES= new byte[][] {
+ new byte[]{0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x35, 0x00},
+ new byte[]{0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x36, 0x00}};
+
+ public static final int LITTLE_ENDIAN_TAG = 0x12345678;
+ public static final int BIG_ENDIAN_TAG = 0x78563412;
+
+ public static final int CHECKSUM_OFFSET = 8;
+
+ public static final int SIGNATURE_OFFSET = 12;
+ public static final int ENDIAN_TAG_OFFSET = 40;
+
+ public static final int MAP_OFFSET = 52;
+
+ public static final int STRING_COUNT_OFFSET = 56;
+ public static final int STRING_START_OFFSET = 60;
+
+ public static final int TYPE_COUNT_OFFSET = 64;
+ public static final int TYPE_START_OFFSET = 68;
+
+ public static final int PROTO_COUNT_OFFSET = 72;
+ public static final int PROTO_START_OFFSET = 76;
+
+ public static final int FIELD_COUNT_OFFSET = 80;
+ public static final int FIELD_START_OFFSET = 84;
+
+ public static final int METHOD_COUNT_OFFSET = 88;
+ public static final int METHOD_START_OFFSET = 92;
+
+ public static final int CLASS_COUNT_OFFSET = 96;
+ public static final int CLASS_START_OFFSET = 100;
+
+ public static final int SIGNATURE_SIZE = 20;
+
+ public static Section getSection() {
+ return new Section() {
+ @Override
+ public void annotateSection(@Nonnull AnnotatedBytes out, @Nonnull DexBackedDexFile dexFile, int length) {
+ int startOffset = out.getCursor();
+ int headerSize;
+
+ out.annotate(0, "-----------------------------");
+ out.annotate(0, "header item");
+ out.annotate(0, "-----------------------------");
+ out.annotate(0, "");
+
+ StringBuilder magicBuilder = new StringBuilder();
+ for (int i=0; i<8; i++) {
+ magicBuilder.append((char)dexFile.readUbyte(startOffset + i));
+ }
+
+ out.annotate(8, "magic: %s", StringUtils.escapeString(magicBuilder.toString()));
+ out.annotate(4, "checksum");
+ out.annotate(20, "signature");
+ out.annotate(4, "file_size: %d", dexFile.readInt(out.getCursor()));
+
+ headerSize = dexFile.readInt(out.getCursor());
+ out.annotate(4, "header_size: %d", headerSize);
+
+ int endianTag = dexFile.readInt(out.getCursor());
+ out.annotate(4, "endian_tag: 0x%x (%s)", endianTag, getEndianText(endianTag));
+
+ out.annotate(4, "link_size: %d", dexFile.readInt(out.getCursor()));
+ out.annotate(4, "link_offset: 0x%x", dexFile.readInt(out.getCursor()));
+
+ out.annotate(4, "map_off: 0x%x", dexFile.readInt(out.getCursor()));
+
+ out.annotate(4, "string_ids_size: %d", dexFile.readInt(out.getCursor()));
+ out.annotate(4, "string_ids_off: 0x%x", dexFile.readInt(out.getCursor()));
+
+ out.annotate(4, "type_ids_size: %d", dexFile.readInt(out.getCursor()));
+ out.annotate(4, "type_ids_off: 0x%x", dexFile.readInt(out.getCursor()));
+
+ out.annotate(4, "proto_ids_size: %d", dexFile.readInt(out.getCursor()));
+ out.annotate(4, "proto_ids_off: 0x%x", dexFile.readInt(out.getCursor()));
+
+ out.annotate(4, "field_ids_size: %d", dexFile.readInt(out.getCursor()));
+ out.annotate(4, "field_ids_off: 0x%x", dexFile.readInt(out.getCursor()));
+
+ out.annotate(4, "method_ids_size: %d", dexFile.readInt(out.getCursor()));
+ out.annotate(4, "method_ids_off: 0x%x", dexFile.readInt(out.getCursor()));
+
+ out.annotate(4, "class_defs_size: %d", dexFile.readInt(out.getCursor()));
+ out.annotate(4, "class_defs_off: 0x%x", dexFile.readInt(out.getCursor()));
+
+ out.annotate(4, "data_size: %d", dexFile.readInt(out.getCursor()));
+ out.annotate(4, "data_off: 0x%x", dexFile.readInt(out.getCursor()));
+
+ if (headerSize > ITEM_SIZE) {
+ out.annotate(headerSize - ITEM_SIZE, "header padding");
+ }
+ }
+ };
+ }
+
+ private static String getEndianText(int endianTag) {
+ if (endianTag == LITTLE_ENDIAN_TAG) {
+ return "Little Endian";
+ }
+ if (endianTag == BIG_ENDIAN_TAG) {
+ return "Big Endian";
+ }
+ return "Invalid";
+ }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/Section.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/Section.java
new file mode 100644
index 0000000..0904738
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/Section.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.dexlib2.dexbacked.raw;
+
+import org.jf.dexlib2.dexbacked.DexBackedDexFile;
+import org.jf.dexlib2.util.AnnotatedBytes;
+
+import javax.annotation.Nonnull;
+
+public interface Section {
+
+ /**
+ * Write out annotations for this section
+ *
+ * @param out The AnnotatedBytes object to annotate to
+ * @param dexFile The DexBackedDexFile representing the dex file being annotated
+ * @param length The number of items in the section (from the header/map)
+ */
+ public void annotateSection(@Nonnull AnnotatedBytes out, @Nonnull DexBackedDexFile dexFile, int length);
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/StringIdItem.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/StringIdItem.java
new file mode 100644
index 0000000..90eaf24
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/StringIdItem.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2013, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.dexlib2.dexbacked.raw;
+
+import org.jf.dexlib2.dexbacked.DexBackedDexFile;
+import org.jf.dexlib2.util.AnnotatedBytes;
+import org.jf.util.StringUtils;
+
+import javax.annotation.Nonnull;
+
+public class StringIdItem {
+ public static final int ITEM_SIZE = 4;
+
+ public static Section getSection() {
+ return new Section() {
+ @Override
+ public void annotateSection(@Nonnull AnnotatedBytes out, @Nonnull DexBackedDexFile dexFile, int length) {
+ int startOffset = out.getCursor();
+ if (length > 0) {
+ out.annotate(0, "-----------------------------");
+ out.annotate(0, "string_id_item section");
+ out.annotate(0, "-----------------------------");
+ out.annotate(0, "");
+
+ for (int i=0; i<length; i++) {
+ out.annotate(0, "[%d] string_id_item", i);
+ out.indent();
+ annotateString(out, dexFile, i, startOffset + i * ITEM_SIZE);
+ out.deindent();
+ }
+ }
+ }
+ };
+ }
+
+ private static void annotateString(@Nonnull AnnotatedBytes out, @Nonnull DexBackedDexFile dexFile, int index,
+ int offset) {
+ int stringDataOffset = dexFile.readSmallUint(offset);
+ try {
+ String stringValue = dexFile.getString(index);
+ out.annotate(4, "string_data_item[0x%x]: \"%s\"", stringDataOffset, StringUtils.escapeString(stringValue));
+ return;
+ } catch (Exception ex) {
+ System.err.print("Error while resolving string value at index: ");
+ System.err.print(index);
+ ex.printStackTrace(System.err);
+ }
+
+ out.annotate(4, "string_id_item[0x%x]", stringDataOffset);
+ }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/util/AnnotatedBytes.java b/dexlib2/src/main/java/org/jf/dexlib2/util/AnnotatedBytes.java
new file mode 100644
index 0000000..e942c3f
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/util/AnnotatedBytes.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2013, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.dexlib2.util;
+
+import com.google.common.collect.Lists;
+import org.jf.util.Hex;
+import org.jf.util.TwoColumnOutput;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.List;
+
+/**
+ * Collects/presents a set of textual annotations, each associated with a range of bytes
+ */
+public class AnnotatedBytes {
+ @Nonnull private List<AnnotationItem> annotations = Lists.newArrayList();
+ private int cursor;
+ private int indentLevel;
+
+ /** >= 40 (if used); the desired maximum output width */
+ private int outputWidth;
+
+ /**
+ * >= 8 (if used); the number of bytes of hex output to use
+ * in annotations
+ */
+ private int hexCols = 8;
+
+ public AnnotatedBytes(int width) {
+ this.outputWidth = width;
+ }
+
+ /**
+ * Add an annotation of the given length at the current location.
+ *
+ * @param length the length of data being annotated
+ * @param msg the annotation message
+ * @param formatArgs format arguments to pass to String.format
+ */
+ public void annotate(int length, @Nonnull String msg, Object... formatArgs) {
+ annotations.add(new AnnotationItem(cursor, indentLevel, String.format(msg, formatArgs)));
+ cursor += length;
+ }
+
+ public void indent() {
+ indentLevel++;
+ }
+
+ public void deindent() {
+ indentLevel--;
+ if (indentLevel < 0) {
+ indentLevel = 0;
+ }
+ }
+
+ public int getCursor() {
+ return cursor;
+ }
+
+ private static class AnnotationItem {
+ public final int offset;
+ public final int indentLevel;
+ public final String annotation;
+
+ public AnnotationItem(int offset, int indentLevel, String annotation) {
+ this.offset = offset;
+ this.indentLevel = indentLevel;
+ this.annotation = annotation;
+ }
+ }
+
+ /**
+ * Gets the width of the right side containing the annotations
+ * @return
+ */
+ public int getAnnotationWidth() {
+ int leftWidth = 8 + (hexCols * 2) + (hexCols / 2);
+
+ return outputWidth - leftWidth;
+ }
+
+ /**
+ * Writes the annotated content of this instance to the given writer.
+ *
+ * @param out non-null; where to write to
+ */
+ public void writeAnnotations(Writer out, byte[] data) throws IOException {
+ int rightWidth = getAnnotationWidth();
+ int leftWidth = outputWidth - rightWidth - 1;
+
+ StringBuilder padding = new StringBuilder();
+ for (int i=0; i<1000; i++) {
+ padding.append(' ');
+ }
+
+ TwoColumnOutput twoc = new TwoColumnOutput(out, leftWidth, rightWidth, "|");
+ Writer left = twoc.getLeft();
+ Writer right = twoc.getRight();
+ int leftAt = 0; // left-hand byte output cursor
+ int rightAt = 0; // right-hand annotation index
+ int rightSz = annotations.size();
+
+ while ((leftAt < cursor) && (rightAt < rightSz)) {
+ AnnotationItem a = annotations.get(rightAt);
+ int start = a.offset;
+ int end;
+
+ if (rightAt + 1 < annotations.size()) {
+ end = annotations.get(rightAt+1).offset;
+ } else {
+ end = cursor;
+ }
+ String text;
+
+ // This is an area with an annotation.
+ text = padding.substring(0, a.indentLevel * 2) + a.annotation;
+ rightAt++;
+
+ left.write(Hex.dump(data, start, end - start, start, hexCols, 6));
+ right.write(text);
+ twoc.flush();
+ leftAt = end;
+ }
+
+ if (leftAt < cursor) {
+ // There is unannotated output at the end.
+ left.write(Hex.dump(data, leftAt, cursor - leftAt, leftAt,
+ hexCols, 6));
+ }
+
+ while (rightAt < rightSz) {
+ // There are zero-byte annotations at the end.
+ right.write(annotations.get(rightAt).annotation);
+ rightAt++;
+ }
+
+ twoc.flush();
+ }
+}
\ No newline at end of file
diff --git a/util/src/main/java/org/jf/util/TwoColumnOutput.java b/util/src/main/java/org/jf/util/TwoColumnOutput.java
new file mode 100644
index 0000000..6f923d4
--- /dev/null
+++ b/util/src/main/java/org/jf/util/TwoColumnOutput.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2013, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.util;
+
+import java.io.*;
+
+/**
+ * Class that takes a combined output destination and provides two
+ * output writers, one of which ends up writing to the left column and
+ * one which goes on the right.
+ */
+public final class TwoColumnOutput {
+ /** non-null; underlying writer for final output */
+ private final Writer out;
+
+ /** > 0; the left column width */
+ private final int leftWidth;
+
+ /** non-null; pending left column output */
+ private final StringBuffer leftBuf;
+
+ /** non-null; pending right column output */
+ private final StringBuffer rightBuf;
+
+ /** non-null; left column writer */
+ private final WrappedIndentingWriter leftColumn;
+
+ /** non-null; right column writer */
+ private final WrappedIndentingWriter rightColumn;
+
+ /**
+ * Turns the given two strings (with widths) and spacer into a formatted
+ * two-column string.
+ *
+ * @param s1 non-null; first string
+ * @param width1 > 0; width of the first column
+ * @param spacer non-null; spacer string
+ * @param s2 non-null; second string
+ * @param width2 > 0; width of the second column
+ * @return non-null; an appropriately-formatted string
+ */
+ public static String toString(String s1, int width1, String spacer,
+ String s2, int width2) {
+ int len1 = s1.length();
+ int len2 = s2.length();
+
+ StringWriter sw = new StringWriter((len1 + len2) * 3);
+ TwoColumnOutput twoOut =
+ new TwoColumnOutput(sw, width1, width2, spacer);
+
+ try {
+ twoOut.getLeft().write(s1);
+ twoOut.getRight().write(s2);
+ } catch (IOException ex) {
+ throw new RuntimeException("shouldn't happen", ex);
+ }
+
+ twoOut.flush();
+ return sw.toString();
+ }
+
+ /**
+ * Constructs an instance.
+ *
+ * @param out non-null; writer to send final output to
+ * @param leftWidth > 0; width of the left column, in characters
+ * @param rightWidth > 0; width of the right column, in characters
+ * @param spacer non-null; spacer string to sit between the two columns
+ */
+ public TwoColumnOutput(Writer out, int leftWidth, int rightWidth,
+ String spacer) {
+ if (out == null) {
+ throw new NullPointerException("out == null");
+ }
+
+ if (leftWidth < 1) {
+ throw new IllegalArgumentException("leftWidth < 1");
+ }
+
+ if (rightWidth < 1) {
+ throw new IllegalArgumentException("rightWidth < 1");
+ }
+
+ if (spacer == null) {
+ throw new NullPointerException("spacer == null");
+ }
+
+ StringWriter leftWriter = new StringWriter(1000);
+ StringWriter rightWriter = new StringWriter(1000);
+
+ this.out = out;
+ this.leftWidth = leftWidth;
+ this.leftBuf = leftWriter.getBuffer();
+ this.rightBuf = rightWriter.getBuffer();
+ this.leftColumn = new WrappedIndentingWriter(leftWriter, leftWidth);
+ this.rightColumn =
+ new WrappedIndentingWriter(rightWriter, rightWidth, spacer);
+ }
+
+ /**
+ * Constructs an instance.
+ *
+ * @param out non-null; stream to send final output to
+ * @param leftWidth >= 1; width of the left column, in characters
+ * @param rightWidth >= 1; width of the right column, in characters
+ * @param spacer non-null; spacer string to sit between the two columns
+ */
+ public TwoColumnOutput(OutputStream out, int leftWidth, int rightWidth,
+ String spacer) {
+ this(new OutputStreamWriter(out), leftWidth, rightWidth, spacer);
+ }
+
+ /**
+ * Gets the writer to use to write to the left column.
+ *
+ * @return non-null; the left column writer
+ */
+ public Writer getLeft() {
+ return leftColumn;
+ }
+
+ /**
+ * Gets the writer to use to write to the right column.
+ *
+ * @return non-null; the right column writer
+ */
+ public Writer getRight() {
+ return rightColumn;
+ }
+
+ /**
+ * Flushes the output. If there are more lines of pending output in one
+ * column, then the other column will get filled with blank lines.
+ */
+ public void flush() {
+ try {
+ appendNewlineIfNecessary(leftBuf, leftColumn);
+ appendNewlineIfNecessary(rightBuf, rightColumn);
+ outputFullLines();
+ flushLeft();
+ flushRight();
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ /**
+ * Outputs to the final destination as many full line pairs as
+ * there are in the pending output, removing those lines from
+ * their respective buffers. This method terminates when at
+ * least one of the two column buffers is empty.
+ */
+ private void outputFullLines() throws IOException {
+ for (;;) {
+ int leftLen = leftBuf.indexOf("\n");
+ if (leftLen < 0) {
+ return;
+ }
+
+ int rightLen = rightBuf.indexOf("\n");
+ if (rightLen < 0) {
+ return;
+ }
+
+ if (leftLen != 0) {
+ out.write(leftBuf.substring(0, leftLen));
+ }
+
+ if (rightLen != 0) {
+ writeSpaces(out, leftWidth - leftLen);
+ out.write(rightBuf.substring(0, rightLen));
+ }
+
+ out.write('\n');
+
+ leftBuf.delete(0, leftLen + 1);
+ rightBuf.delete(0, rightLen + 1);
+ }
+ }
+
+ /**
+ * Flushes the left column buffer, printing it and clearing the buffer.
+ * If the buffer is already empty, this does nothing.
+ */
+ private void flushLeft() throws IOException {
+ appendNewlineIfNecessary(leftBuf, leftColumn);
+
+ while (leftBuf.length() != 0) {
+ rightColumn.write('\n');
+ outputFullLines();
+ }
+ }
+
+ /**
+ * Flushes the right column buffer, printing it and clearing the buffer.
+ * If the buffer is already empty, this does nothing.
+ */
+ private void flushRight() throws IOException {
+ appendNewlineIfNecessary(rightBuf, rightColumn);
+
+ while (rightBuf.length() != 0) {
+ leftColumn.write('\n');
+ outputFullLines();
+ }
+ }
+
+ /**
+ * Appends a newline to the given buffer via the given writer, but
+ * only if it isn't empty and doesn't already end with one.
+ *
+ * @param buf non-null; the buffer in question
+ * @param out non-null; the writer to use
+ */
+ private static void appendNewlineIfNecessary(StringBuffer buf,
+ Writer out)
+ throws IOException {
+ int len = buf.length();
+
+ if ((len != 0) && (buf.charAt(len - 1) != '\n')) {
+ out.write('\n');
+ }
+ }
+
+ /**
+ * Writes the given number of spaces to the given writer.
+ *
+ * @param out non-null; where to write
+ * @param amt >= 0; the number of spaces to write
+ */
+ private static void writeSpaces(Writer out, int amt) throws IOException {
+ while (amt > 0) {
+ out.write(' ');
+ amt--;
+ }
+ }
+}
diff --git a/util/src/main/java/org/jf/util/WrappedIndentingWriter.java b/util/src/main/java/org/jf/util/WrappedIndentingWriter.java
new file mode 100644
index 0000000..eb1acda
--- /dev/null
+++ b/util/src/main/java/org/jf/util/WrappedIndentingWriter.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2013, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.util;
+
+import java.io.FilterWriter;
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Writer that wraps another writer and passes width-limited and
+ * optionally-prefixed output to its subordinate. When lines are
+ * wrapped they are automatically indented based on the start of the
+ * line.
+ */
+public final class WrappedIndentingWriter extends FilterWriter {
+ /** null-ok; optional prefix for every line */
+ private final String prefix;
+
+ /** > 0; the maximum output width */
+ private final int width;
+
+ /** > 0; the maximum indent */
+ private final int maxIndent;
+
+ /** >= 0; current output column (zero-based) */
+ private int column;
+
+ /** whether indent spaces are currently being collected */
+ private boolean collectingIndent;
+
+ /** >= 0; current indent amount */
+ private int indent;
+
+ /**
+ * Constructs an instance.
+ *
+ * @param out non-null; writer to send final output to
+ * @param width >= 0; the maximum output width (not including
+ * <code>prefix</code>), or <code>0</code> for no maximum
+ * @param prefix non-null; the prefix for each line
+ */
+ public WrappedIndentingWriter(Writer out, int width, String prefix) {
+ super(out);
+
+ if (out == null) {
+ throw new NullPointerException("out == null");
+ }
+
+ if (width < 0) {
+ throw new IllegalArgumentException("width < 0");
+ }
+
+ if (prefix == null) {
+ throw new NullPointerException("prefix == null");
+ }
+
+ this.width = (width != 0) ? width : Integer.MAX_VALUE;
+ this.maxIndent = width >> 1;
+ this.prefix = (prefix.length() == 0) ? null : prefix;
+
+ bol();
+ }
+
+ /**
+ * Constructs a no-prefix instance.
+ *
+ * @param out non-null; writer to send final output to
+ * @param width >= 0; the maximum output width (not including
+ * <code>prefix</code>), or <code>0</code> for no maximum
+ */
+ public WrappedIndentingWriter(Writer out, int width) {
+ this(out, width, "");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void write(int c) throws IOException {
+ synchronized (lock) {
+ if (collectingIndent) {
+ if (c == ' ') {
+ indent++;
+ if (indent >= maxIndent) {
+ indent = maxIndent;
+ collectingIndent = false;
+ }
+ } else {
+ collectingIndent = false;
+ }
+ }
+
+ if ((column == width) && (c != '\n')) {
+ out.write('\n');
+ column = 0;
+ /*
+ * Note: No else, so this should fall through to the next
+ * if statement.
+ */
+ }
+
+ if (column == 0) {
+ if (prefix != null) {
+ out.write(prefix);
+ }
+
+ if (!collectingIndent) {
+ for (int i = 0; i < indent; i++) {
+ out.write(' ');
+ }
+ column = indent;
+ }
+ }
+
+ out.write(c);
+
+ if (c == '\n') {
+ bol();
+ } else {
+ column++;
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void write(char[] cbuf, int off, int len) throws IOException {
+ synchronized (lock) {
+ while (len > 0) {
+ write(cbuf[off]);
+ off++;
+ len--;
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void write(String str, int off, int len) throws IOException {
+ synchronized (lock) {
+ while (len > 0) {
+ write(str.charAt(off));
+ off++;
+ len--;
+ }
+ }
+ }
+
+ /**
+ * Indicates that output is at the beginning of a line.
+ */
+ private void bol() {
+ column = 0;
+ collectingIndent = (maxIndent != 0);
+ indent = 0;
+ }
+}