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

Side by Side Diff: build/android/rezip/PrealignApk.java

Issue 595933003: Re-invent page aligning libraries in APK file. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 2 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 import java.io.File;
6 import java.io.FileOutputStream;
7 import java.io.IOException;
8 import java.io.InputStream;
9 import java.io.OutputStream;
10 import java.util.ArrayList;
11 import java.util.Collections;
12 import java.util.Comparator;
13 import java.util.Enumeration;
14 import java.util.List;
15 import java.util.jar.JarEntry;
16 import java.util.jar.JarFile;
17 import java.util.jar.JarOutputStream;
18 import java.util.regex.Pattern;
19 import java.util.zip.CRC32;
20
21 /**
22 * Command line tool used to page align non-compressed libraries (*.so) in APK f iles.
23 * Tool is designed so that running SignApk and/or zipalign on the resulting APK does not
24 * break the page alignment.
25 */
26 class PrealignApk {
rmcilroy 2014/09/29 11:38:32 I'm not sure Prealign is a very informative name f
Anton 2014/09/29 16:30:17 Done
rmcilroy 2014/09/30 09:36:32 I would prefer that there were only one tool (and
Anton 2014/09/30 10:30:01 Acknowledged.
27 // Alignment to use for non-compressed files (must match zipalign).
28 private static final int ALIGNMENT = 4;
29
30 // Alignment to use for non-compressed *.so files
31 private static final int LIBRARY_ALIGNMENT = 4096;
32
33 // Files matching this pattern are not copied to the output when adding alig nment.
34 // When reordering and verifying the APK they are copied to the end of the f ile.
35 private static Pattern sMetaFilePattern =
36 Pattern.compile("^(META-INF/((.*)[.](SF|RSA|DSA)|com/android/otacert ))|(" +
37 Pattern.quote(JarFile.MANIFEST_NAME) + ")$");
38
39 /**
40 * Wraps another output stream, counting the number of bytes written.
41 */
42 private static class CountingOutputStream extends OutputStream {
43 private long mCount = 0;
44 private OutputStream mOut;
45
46 public CountingOutputStream(OutputStream out) {
47 this.mOut = out;
48 }
49
50 /** Returns the number of bytes written. */
51 public long getCount() {
52 return mCount;
53 }
54
55 @Override public void write(byte[] b, int off, int len) throws IOExcepti on {
56 mOut.write(b, off, len);
57 mCount += len;
58 }
59
60 @Override public void write(int b) throws IOException {
61 mOut.write(b);
62 mCount++;
63 }
64
65 @Override public void close() throws IOException {
66 mOut.close();
67 }
68
69 @Override public void flush() throws IOException {
70 mOut.flush();
71 }
72 }
73
74 /**
75 * Sort filenames in natural string order, except that filenames matching
76 * the meta-file pattern are always after other files. This is so the manife st
77 * and signature are at the end of the file after any alignment file.
78 */
79 private static class FilenameComparator implements Comparator<String> {
80 @Override
81 public int compare(String o1, String o2) {
82 boolean o1Matches = sMetaFilePattern.matcher(o1).matches();
83 boolean o2Matches = sMetaFilePattern.matcher(o2).matches();
84 if (o1Matches != o2Matches) {
85 return o1Matches ? 1 : -1;
86 } else {
87 return o1.compareTo(o2);
88 }
89 }
90 }
91
92 // Build an ordered list of filenames. Using the same deterministic ordering used
93 // by SignApk. If addAlignment is true do not include the META-INF files.
94 private static List<String> orderFilenames(JarFile jar, boolean addAlignment ) {
rmcilroy 2014/09/29 11:38:32 /s/addAlignment/omitMetaFiles
Anton 2014/09/29 16:30:17 Done.
95 List<String> names = new ArrayList<String>();
96 for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements(); ) {
97 JarEntry entry = e.nextElement();
98 if (entry.isDirectory()) {
99 continue;
100 }
101 if (addAlignment &&
102 sMetaFilePattern.matcher(entry.getName()).matches()) {
103 continue;
104 }
105 names.add(entry.getName());
106 }
107
108 // We sort the input entries by name. When present META-INF files
109 // are sorted to the end.
110 Collections.sort(names, new FilenameComparator());
111 return names;
112 }
113
114 /**
115 * If |addAlignment| is true add a zero filled alignment file at this point in the zip file,
116 * otherwise verify that the alignment is already correct. The added file wi ll be added
117 * before |name| and after |prevName|. The size of the alignment file is suc h that the
118 * location of the file |name| will be on a LIBRARY_ALIGNMENT boundary.
119 *
120 * Note this arrangement is devised so that running SignApk and/or zipalign on the resulting
121 * file will not alter the alignment.
122 *
123 * @param offset number of bytes into the output file at this point.
124 * @param timestamp time in millis since the epoch to include in the header.
125 * @param name the name of the library filename.
126 * @param prevName the name of the previous file in the archive (or null).
127 * @param out jar output stream to write the alignment file to.
128 * @param addAlignment true to add the alignment file, false to verify align ment.
129 *
130 * @throws IOException if the output file can not be written.
131 */
132 private static void addAlignmentFile(
133 long offset, long timestamp, String name, String prevName,
134 JarOutputStream out, boolean addAlignment) throws IOException {
135
136 // Compute the start and alignment of the library, as if it was next.
137 int headerSize = JarFile.LOCHDR + name.length();
138 long libOffset = offset + headerSize;
139 int libNeeded = LIBRARY_ALIGNMENT - (int) (libOffset % LIBRARY_ALIGNMENT );
140 if (libNeeded == LIBRARY_ALIGNMENT) {
141 // Already aligned, no need to added alignment file.
142 return;
143 }
144
145 if (!addAlignment) {
146 // If we are not adding alignment then it should already be correct.
147 throw new AssertionError(
148 "Library was not page aligned when verifying page alignment. "
149 + "Library name: " + name + " Expected alignment: " + LIBRARY_AL IGNMENT
150 + "Offset: " + offset + " Error: " + (libOffset % LIBRARY_ALIGNM ENT));
151 }
152
153 // Check that there is not another file between the library and the
154 // alignment file.
155 String alignName = name.substring(0, name.length() - 2) + "align";
156 if (prevName != null && prevName.compareTo(alignName) >= 0) {
157 throw new UnsupportedOperationException(
158 "Unable to insert alignment file, because there is "
159 + "another file in front of the file to be aligned. "
160 + "Other file: " + prevName + " Alignment file: " + alignName);
161 }
162
163 // Compute the size of the alignment file header.
164 headerSize = JarFile.LOCHDR + alignName.length();
165 // We are going to add an alignment file of type STORED. This file
166 // will itself induce a zipalign alignment adjustment.
167 int extraNeeded =
168 (ALIGNMENT - (int) ((offset + headerSize) % ALIGNMENT)) % ALIGNM ENT;
169 headerSize += extraNeeded;
170
171 if (libNeeded < headerSize + 1) {
172 // The header was bigger than the alignment that we need, add anothe r page.
173 libNeeded += LIBRARY_ALIGNMENT;
174 }
175 // Compute the size of the alignment file.
176 libNeeded -= headerSize;
177
178 // Build the header for the alignment file.
179 byte[] zeroBuffer = new byte[libNeeded];
180 JarEntry alignEntry = new JarEntry(alignName);
181 alignEntry.setMethod(JarEntry.STORED);
182 alignEntry.setSize(libNeeded);
183 alignEntry.setTime(timestamp);
184 CRC32 crc = new CRC32();
185 crc.update(zeroBuffer);
186 alignEntry.setCrc(crc.getValue());
187
188 if (extraNeeded != 0) {
189 alignEntry.setExtra(new byte[extraNeeded]);
190 }
191
192 // Output the alignment file.
193 out.putNextEntry(alignEntry);
194 out.write(zeroBuffer);
195 out.closeEntry();
196 out.flush();
197 }
198
199 /**
200 * Copy the contents of the input APK file to the output APK file. Uncompres sed files
201 * will be aligned in the output stream. Uncompressed native code libraries (*.so)
202 * will be aligned on a page boundary. Page alignment is implemented by addi ng a
203 * zero filled file, regular alignment is implemented by adding a zero fille d extra
204 * field to the zip file header. Care is take so that the output generated i n the
205 * same way as SignApk. This is important so that running SignApk and zipali gn on
206 * the output does not break the page alignment. The archive may not contain a "*.apk"
207 * as SignApk has special nested signing logic that we do not support.
208 *
209 * @param manifest The Manifest naming all files to copy
210 * @param in The input APK File.
211 * @param out The output APK stream.
212 * @param countOut Counting output stream (to measure the current offset).
213 * @param timestamp The timestamp (millis since epoch) for all entries in th e output.
214 * @param addAlignment Whether to add the alignment file or just check.
215 *
216 * @throws IOException if the output file can not be written.
217 */
218 private static void copyAndAlignFiles(
219 List<String> names, JarFile in, JarOutputStream out, CountingOutputStrea m countOut,
220 long timestamp, boolean addAlignment) throws IOException {
221
222 byte[] buffer = new byte[4096];
223 boolean firstEntry = true;
224 String prevName = null;
225 for (String name : names) {
226 JarEntry inEntry = in.getJarEntry(name);
227 JarEntry outEntry = null;
228 if (name.endsWith(".apk")) {
229 throw new UnsupportedOperationException(
230 "Nested APKs are not supported: " + name);
231 }
232 if (inEntry.getMethod() == JarEntry.STORED) {
233 // Preserve the STORED method of the input entry.
234 outEntry = new JarEntry(inEntry);
235 outEntry.setExtra(null);
236 } else {
237 // Create a new entry so that the compressed len is recomputed.
238 outEntry = new JarEntry(name);
239 }
240 outEntry.setTime(timestamp);
241
242 long offset = countOut.getCount();
243 if (firstEntry) {
244 // The first entry in a jar file has an extra field of
245 // four bytes that you can't get rid of; any extra
246 // data you specify in the JarEntry is appended to
247 // these forced four bytes. This is JAR_MAGIC in
248 // JarOutputStream; the bytes are 0xfeca0000.
249 firstEntry = false;
250 offset += 4;
251 }
252 if (inEntry.getMethod() == JarEntry.STORED) {
253 if (LIBRARY_ALIGNMENT > 0 && name.endsWith(".so")) {
254 addAlignmentFile(
255 offset, timestamp, name, prevName, out, addAlignment);
rmcilroy 2014/09/29 11:38:32 nit - could we just do the addAlignmentFile method
Anton 2014/09/29 16:30:17 Done.
256 if (addAlignment) {
257 // We check that we did indeed get to a page boundary.
258 offset = countOut.getCount() + JarFile.LOCHDR + name.len gth();
259 if ((offset % LIBRARY_ALIGNMENT) != 0) {
260 throw new AssertionError(
261 "File was not aligned after adding alignment fil e. "
262 + "offset = " + offset
263 + " LIBRARY_ALIGNMENT = " + LIBRARY_ALIGNMENT);
264 }
265 }
266 } else if (ALIGNMENT > 0) {
rmcilroy 2014/09/29 11:38:32 is it worth having this check given ALIGNMENT is a
Anton 2014/09/29 16:30:17 Removed. Compiler will take them out anyway, but t
267 offset += JarFile.LOCHDR + name.length();
268 int needed = (ALIGNMENT - (int) (offset % ALIGNMENT)) % ALIG NMENT;
269 if (needed != 0) {
270 outEntry.setExtra(new byte[needed]);
271 }
272 }
273 }
274 out.putNextEntry(outEntry);
275
276 int num;
277 InputStream data = in.getInputStream(inEntry);
278 while ((num = data.read(buffer)) > 0) {
279 out.write(buffer, 0, num);
280 }
281 out.closeEntry();
282 out.flush();
283 prevName = name;
284 }
285 }
286
287 private static void usage() {
288 System.err.println("Usage: prealignapk (addalignment|reorder) input.apk output.apk");
289 System.err.println(" addalignment - adds alignment file removes manifes t and signature");
290 System.err.println(" reorder - re-creates canonical ordering check s alignment");
rmcilroy 2014/09/29 11:38:32 /s/re-creates canonical ordering checks alignment/
Anton 2014/09/29 16:30:17 Done.
291 System.exit(2);
292 }
293
294 public static void main(String[] args) {
295 if (args.length != 3) usage();
296
297 boolean addAlignment = false;
298 if (args[0].equals("addalignment")) {
299 addAlignment = true;
300 } else if (args[0].equals("reorder")) {
301 addAlignment = false;
302 } else {
303 usage();
304 }
305
306 String inputFilename = args[1];
307 String outputFilename = args[2];
308
309 JarFile inputJar = null;
310 FileOutputStream outputFile = null;
311
312 try {
313 inputJar = new JarFile(new File(inputFilename), false); // Don't ve rify.
rmcilroy 2014/09/29 11:38:32 Is there a reason not to verify? If so, could you
Anton 2014/09/29 16:30:17 I am not sure of the merits of verifying existing
314 outputFile = new FileOutputStream(outputFilename);
315
316 CountingOutputStream outCount = new CountingOutputStream(outputFile) ;
317 JarOutputStream outputJar = new JarOutputStream(outCount);
318
319 // Match the compression level used by SignApk.
320 outputJar.setLevel(9);
321
322 long timestamp = System.currentTimeMillis();
323
324 copyAndAlignFiles(
325 orderFilenames(inputJar, addAlignment), inputJar, outputJar, out Count,
326 timestamp, addAlignment);
rmcilroy 2014/09/29 11:38:32 nit - the "List<String> names" and "timestamp" arg
Anton 2014/09/29 16:30:17 Done.
327 outputJar.close();
328 } catch (Exception e) {
329 e.printStackTrace();
330 System.exit(1);
331 } finally {
332 try {
333 if (inputJar != null) inputJar.close();
334 if (outputFile != null) outputFile.close();
335 } catch (IOException e) {
336 e.printStackTrace();
337 System.exit(1);
338 }
339 }
340 }
341 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698