Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(106)

Unified Diff: build/android/rezip/RezipApk.java

Issue 595933003: Re-invent page aligning libraries in APK file. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix for the android_webview_build Created 6 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « build/android/rezip.gyp ('k') | build/android/rezip/rezip.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: build/android/rezip/RezipApk.java
diff --git a/build/android/rezip/RezipApk.java b/build/android/rezip/RezipApk.java
new file mode 100644
index 0000000000000000000000000000000000000000..0349d3b5efa2e53b3e8c186a3c85b3eb7938ef7c
--- /dev/null
+++ b/build/android/rezip/RezipApk.java
@@ -0,0 +1,319 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+import java.util.regex.Pattern;
+import java.util.zip.CRC32;
+
+/**
+ * Command line tool used to page align non-compressed libraries (*.so) in APK files.
+ * Tool is designed so that running SignApk and/or zipalign on the resulting APK does not
+ * break the page alignment.
+ */
+class RezipApk {
+ // Alignment to use for non-compressed files (must match zipalign).
+ private static final int ALIGNMENT = 4;
+
+ // Alignment to use for non-compressed *.so files
+ private static final int LIBRARY_ALIGNMENT = 4096;
+
+ // Files matching this pattern are not copied to the output when adding alignment.
+ // When reordering and verifying the APK they are copied to the end of the file.
+ private static Pattern sMetaFilePattern =
+ Pattern.compile("^(META-INF/((.*)[.](SF|RSA|DSA)|com/android/otacert))|(" +
+ Pattern.quote(JarFile.MANIFEST_NAME) + ")$");
+
+ /**
+ * Wraps another output stream, counting the number of bytes written.
+ */
+ private static class CountingOutputStream extends OutputStream {
+ private long mCount = 0;
+ private OutputStream mOut;
+
+ public CountingOutputStream(OutputStream out) {
+ this.mOut = out;
+ }
+
+ /** Returns the number of bytes written. */
+ public long getCount() {
+ return mCount;
+ }
+
+ @Override public void write(byte[] b, int off, int len) throws IOException {
+ mOut.write(b, off, len);
+ mCount += len;
+ }
+
+ @Override public void write(int b) throws IOException {
+ mOut.write(b);
+ mCount++;
+ }
+
+ @Override public void close() throws IOException {
+ mOut.close();
+ }
+
+ @Override public void flush() throws IOException {
+ mOut.flush();
+ }
+ }
+
+ /**
+ * Sort filenames in natural string order, except that filenames matching
+ * the meta-file pattern are always after other files. This is so the manifest
+ * and signature are at the end of the file after any alignment file.
+ */
+ private static class FilenameComparator implements Comparator<String> {
+ @Override
+ public int compare(String o1, String o2) {
+ boolean o1Matches = sMetaFilePattern.matcher(o1).matches();
+ boolean o2Matches = sMetaFilePattern.matcher(o2).matches();
+ if (o1Matches != o2Matches) {
+ return o1Matches ? 1 : -1;
+ } else {
+ return o1.compareTo(o2);
+ }
+ }
+ }
+
+ // Build an ordered list of filenames. Using the same deterministic ordering used
+ // by SignApk. If omitMetaFiles is true do not include the META-INF files.
+ private static List<String> orderFilenames(JarFile jar, boolean omitMetaFiles) {
+ List<String> names = new ArrayList<String>();
+ for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements(); ) {
+ JarEntry entry = e.nextElement();
+ if (entry.isDirectory()) {
+ continue;
+ }
+ if (omitMetaFiles &&
+ sMetaFilePattern.matcher(entry.getName()).matches()) {
+ continue;
+ }
+ names.add(entry.getName());
+ }
+
+ // We sort the input entries by name. When present META-INF files
+ // are sorted to the end.
+ Collections.sort(names, new FilenameComparator());
+ return names;
+ }
+
+ /**
+ * Add a zero filled alignment file at this point in the zip file,
+ * The added file will be added before |name| and after |prevName|.
+ * The size of the alignment file is such that the location of the
+ * file |name| will be on a LIBRARY_ALIGNMENT boundary.
+ *
+ * Note this arrangement is devised so that running SignApk and/or zipalign on the resulting
+ * file will not alter the alignment.
+ *
+ * @param offset number of bytes into the output file at this point.
+ * @param timestamp time in millis since the epoch to include in the header.
+ * @param name the name of the library filename.
+ * @param prevName the name of the previous file in the archive (or null).
+ * @param out jar output stream to write the alignment file to.
+ *
+ * @throws IOException if the output file can not be written.
+ */
+ private static void addAlignmentFile(
+ long offset, long timestamp, String name, String prevName,
+ JarOutputStream out) throws IOException {
+
+ // Compute the start and alignment of the library, as if it was next.
+ int headerSize = JarFile.LOCHDR + name.length();
+ long libOffset = offset + headerSize;
+ int libNeeded = LIBRARY_ALIGNMENT - (int) (libOffset % LIBRARY_ALIGNMENT);
+ if (libNeeded == LIBRARY_ALIGNMENT) {
+ // Already aligned, no need to added alignment file.
+ return;
+ }
+
+ // Check that there is not another file between the library and the
+ // alignment file.
+ String alignName = name.substring(0, name.length() - 2) + "align";
+ if (prevName != null && prevName.compareTo(alignName) >= 0) {
+ throw new UnsupportedOperationException(
+ "Unable to insert alignment file, because there is "
+ + "another file in front of the file to be aligned. "
+ + "Other file: " + prevName + " Alignment file: " + alignName);
+ }
+
+ // Compute the size of the alignment file header.
+ headerSize = JarFile.LOCHDR + alignName.length();
+ // We are going to add an alignment file of type STORED. This file
+ // will itself induce a zipalign alignment adjustment.
+ int extraNeeded =
+ (ALIGNMENT - (int) ((offset + headerSize) % ALIGNMENT)) % ALIGNMENT;
+ headerSize += extraNeeded;
+
+ if (libNeeded < headerSize + 1) {
+ // The header was bigger than the alignment that we need, add another page.
+ libNeeded += LIBRARY_ALIGNMENT;
+ }
+ // Compute the size of the alignment file.
+ libNeeded -= headerSize;
+
+ // Build the header for the alignment file.
+ byte[] zeroBuffer = new byte[libNeeded];
+ JarEntry alignEntry = new JarEntry(alignName);
+ alignEntry.setMethod(JarEntry.STORED);
+ alignEntry.setSize(libNeeded);
+ alignEntry.setTime(timestamp);
+ CRC32 crc = new CRC32();
+ crc.update(zeroBuffer);
+ alignEntry.setCrc(crc.getValue());
+
+ if (extraNeeded != 0) {
+ alignEntry.setExtra(new byte[extraNeeded]);
+ }
+
+ // Output the alignment file.
+ out.putNextEntry(alignEntry);
+ out.write(zeroBuffer);
+ out.closeEntry();
+ out.flush();
+ }
+
+ /**
+ * Copy the contents of the input APK file to the output APK file. Uncompressed files
+ * will be aligned in the output stream. Uncompressed native code libraries (*.so)
+ * will be aligned on a page boundary. Page alignment is implemented by adding a
+ * zero filled file, regular alignment is implemented by adding a zero filled extra
+ * field to the zip file header. Care is take so that the output generated in the
+ * same way as SignApk. This is important so that running SignApk and zipalign on
+ * the output does not break the page alignment. The archive may not contain a "*.apk"
+ * as SignApk has special nested signing logic that we do not support.
+ *
+ * @param in The input APK File.
+ * @param out The output APK stream.
+ * @param countOut Counting output stream (to measure the current offset).
+ * @param addAlignment Whether to add the alignment file or just check.
+ *
+ * @throws IOException if the output file can not be written.
+ */
+ private static void copyAndAlignFiles(
+ JarFile in, JarOutputStream out, CountingOutputStream countOut,
+ boolean addAlignment) throws IOException {
+
+ List<String> names = orderFilenames(in, addAlignment);
+ long timestamp = System.currentTimeMillis();
+ byte[] buffer = new byte[4096];
+ boolean firstEntry = true;
+ String prevName = null;
+ for (String name : names) {
+ JarEntry inEntry = in.getJarEntry(name);
+ JarEntry outEntry = null;
+ if (name.endsWith(".apk")) {
+ throw new UnsupportedOperationException(
+ "Nested APKs are not supported: " + name);
+ }
+ if (inEntry.getMethod() == JarEntry.STORED) {
+ // Preserve the STORED method of the input entry.
+ outEntry = new JarEntry(inEntry);
+ outEntry.setExtra(null);
+ } else {
+ // Create a new entry so that the compressed len is recomputed.
+ outEntry = new JarEntry(name);
+ }
+ outEntry.setTime(timestamp);
+
+ long offset = countOut.getCount();
+ if (firstEntry) {
+ // The first entry in a jar file has an extra field of
+ // four bytes that you can't get rid of; any extra
+ // data you specify in the JarEntry is appended to
+ // these forced four bytes. This is JAR_MAGIC in
+ // JarOutputStream; the bytes are 0xfeca0000.
+ firstEntry = false;
+ offset += 4;
+ }
+ if (inEntry.getMethod() == JarEntry.STORED) {
+ if (name.endsWith(".so")) {
+ if (addAlignment) {
+ addAlignmentFile(offset, timestamp, name, prevName, out);
+ }
+ // We check that we did indeed get to a page boundary.
+ offset = countOut.getCount() + JarFile.LOCHDR + name.length();
+ if ((offset % LIBRARY_ALIGNMENT) != 0) {
+ throw new AssertionError(
+ "Library was not page aligned when verifying page alignment. "
+ + "Library name: " + name + " Expected alignment: " + LIBRARY_ALIGNMENT
+ + "Offset: " + offset + " Error: " + (offset % LIBRARY_ALIGNMENT));
+ }
+ } else {
+ offset += JarFile.LOCHDR + name.length();
+ int needed = (ALIGNMENT - (int) (offset % ALIGNMENT)) % ALIGNMENT;
+ if (needed != 0) {
+ outEntry.setExtra(new byte[needed]);
+ }
+ }
+ }
+ out.putNextEntry(outEntry);
+
+ int num;
+ InputStream data = in.getInputStream(inEntry);
+ while ((num = data.read(buffer)) > 0) {
+ out.write(buffer, 0, num);
+ }
+ out.closeEntry();
+ out.flush();
+ prevName = name;
+ }
+ }
+
+ private static void usage() {
+ System.err.println("Usage: prealignapk (addalignment|reorder) input.apk output.apk");
+ System.err.println(" addalignment - adds alignment file removes manifest and signature");
+ System.err.println(" reorder - re-creates canonical ordering and checks alignment");
+ System.exit(2);
+ }
+
+ public static void main(String[] args) throws IOException {
+ if (args.length != 3) usage();
+
+ boolean addAlignment = false;
+ if (args[0].equals("addalignment")) {
+ addAlignment = true;
+ } else if (args[0].equals("reorder")) {
+ addAlignment = false;
+ } else {
+ usage();
+ }
+
+ String inputFilename = args[1];
+ String outputFilename = args[2];
+
+ JarFile inputJar = null;
+ FileOutputStream outputFile = null;
+
+ try {
+ inputJar = new JarFile(new File(inputFilename), true);
+ outputFile = new FileOutputStream(outputFilename);
+
+ CountingOutputStream outCount = new CountingOutputStream(outputFile);
+ JarOutputStream outputJar = new JarOutputStream(outCount);
+
+ // Match the compression level used by SignApk.
+ outputJar.setLevel(9);
+
+ copyAndAlignFiles(inputJar, outputJar, outCount, addAlignment);
+ outputJar.close();
+ } finally {
+ if (inputJar != null) inputJar.close();
+ if (outputFile != null) outputFile.close();
+ }
+ }
+}
« no previous file with comments | « build/android/rezip.gyp ('k') | build/android/rezip/rezip.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698