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

Side by Side Diff: base/android/java/src/org/chromium/base/Linker.java

Issue 23717023: Android: Add chrome-specific dynamic linker. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix compile error (previous patch was a mistake). Created 7 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2012 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 package org.chromium.base;
6
7 import java.util.HashMap;
8 import java.util.Map;
9 import java.util.Random;
10 import java.util.Set;
11
12 import android.content.Context;
13 import android.os.Bundle;
14 import android.os.Parcel;
15 import android.os.Parcelable;
16 import android.os.ParcelFileDescriptor;
17 import android.util.Log;
18
19 /**
20 * This is support code for the custom dynamic linker used in Android
21 * to reduce RAM by sharing the RELRO section between the browser and
22 * renderer processes.
23 *
24 * To make this feature work, native shared libraries must be loaded
25 * at exactly the same addresses in all processes. This works as follows:
26 *
27 * The first process will:
28 *
29 * - Call Linker.initRelroSharing() before anything else.
30 *
31 * - Use Linker.loadLibrary() to load libraries. It acts exactly like
32 * System.loadLibrary(), but also creates shared RELRO sections for
33 * each library it loads.
34 *
35 * - Calls Linker.getBaseLoadAddress() and send it to any other process
36 * that want to use the shared RELRO sections.
37 *
38 * - When all libraries are loaded, call Linker.getRelroBundle() and
39 * send it to other processes as well. Note that the Bundle contains
40 * file descriptors, hence it cannot be saved to disk, or put into
41 * an Intent.
42 *
43 * Meanwhile, each other process will:
44 *
45 * - Receive the load address retrieved in the first one through
46 * Linker.getBaseLoadAddress() and call Linker.setBaseLoadAddress()
47 * with its value, before loading any library.
48 *
49 * - Call Linker.loadLibrary() to load libraries.
50 *
51 * - Receive the Bundle object from the first process, and apply it
52 * locally with Linker.applyRelroBundle(). This will check the state
53 * of all libraries, and automatically reuse the RELRO sections
54 * of those that were loaded at the correct address.
55 *
56 * Once this is completed, the RELRO sections will be shared, reducing
57 * overall RAM usage in each non-first process.
58 */
59 public class Linker {
bulach 2013/09/10 14:21:39 so given the options: liblinker.so libbase_lin
60
61 // Log tag for this class.
62 private static final String TAG = "crazy_linker";
63
64 // Set to true to enable debug logs.
65 private static final boolean DEBUG = true;
66
67 // Becomes true after linker initialization.
68 private static boolean sInitialized = false;
69
70 // Set to true to when shared RELRO creation is enabled in this process.
71 private static boolean sRelroSharingEnabled = false;
72
73 // Current base load address.
74 private static long sBaseLoadAddress = 0;
75
76 // Current load address for the next library called by loadLibrary().
77 private static long sCurrentLoadAddress = 0;
78
79 private static Object sLock = new Object();
80
81 /**
82 * Call this method once to enable shared RELRO creation in the
83 * initial process. This ensures that all libraries loaded through
84 * loadLibrary() will have a shared RELRO created for them.
85 */
86 public static void initRelroSharing() {
87 synchronized (sLock) {
88 // If the base load address is not initialized, choose a random base address. This
89 // should only happen on the first process. Other processes should s et the base load
90 // address to the same one as the first process before trying to loa d any library.
91 if (getBaseLoadAddress() == 0) {
92 if (DEBUG) Log.i(TAG, "Initializing Relro sharing.");
93 Linker.randomizeBaseLoadAddress();
94 Linker.enableRelroSharing();
95 }
96 }
97 }
98
99 /**
100 * Compute a random base load address under 0x4000_0000 that will be
101 * used to load libraries with shared RELROs.
102 */
103 private static void randomizeBaseLoadAddress() {
104
105 if (DEBUG)
106 Log.i(TAG, "Linker.randomizeBaseLoadAddress() called");
107
108 // Ensure offset is between 0 and 64 MB exclusive.
109 Random r = new Random();
110
111 // The kernel ASLR feature will place randomized mappings starting
112 // from this address. Never try to load anything above this
113 // explicitely to avoid random conflicts.
114 final long baseAddressLimit = 0x40000000;
115
116 // Start loading libraries from this base address.
117 final long baseAddress = 0x20000000;
118
119 // Maximum randomized base address value. Used to ensure a margin
120 // of 192 MB below baseAddressLimit.
121 final long baseAddressMax = baseAddressLimit - 192 * 1024 * 1024;
122
123 // There is no way to get this from Java APIs, and it's not
124 // possible to call JNI here since no shared library has been
125 // loaded yet. All current Android platforms have a 4 KB page
126 // size at the moment. Correct this in the future when this is
127 // no longer true.
128 final long pageSize = 4096;
129
130 // Get a proper random page-aligned offset.
131 final int offset = r.nextInt((int)((baseAddressMax - baseAddress) / page Size));
132 setBaseLoadAddress(baseAddress + offset * pageSize);
133 }
134
135 /**
136 * Enable shared RELRO sections in this process. Any library loaded
137 * through loadLibrary() will have a shared RELRO created by the
138 * linker. This also applies to libraries loaded before this call.
139 */
140 private static void enableRelroSharing() {
141 if (DEBUG) Log.i(TAG, "Linker.enableRelroSharing() called");
142
143 synchronized (sLock) {
144 if (!sRelroSharingEnabled) {
145 if (sLoadedLibraries != null) {
146 // If some libraries were already loaded, enable RELRO
147 // sharing for them too.
148 for (Map.Entry<String, LibInfo> entry : sLoadedLibraries.ent rySet()) {
149 String libName = entry.getKey();
150 LibInfo libInfo = entry.getValue();
151 if (!nativeEnableSharedRelro(libName, libInfo))
152 Log.w(TAG, "Could not enable RELRO sharing for " + l ibName);
153 }
154 }
155 sRelroSharingEnabled = true;
156 }
157 }
158 }
159
160 /**
161 * Retrieve the base library load address. If initRelroSharing() or
162 * or setBaseLoadAddress() was called, this retrieves the corresponding
163 * base load address. Otherwise, 0. Typically called in the initial
164 * process to pass it to the other ones.
165 * @return The current base load address.
166 */
167 public static long getBaseLoadAddress() {
168 if (DEBUG) Log.i(TAG, "Linker.getBaseLoadAddress() called => " + sBaseLo adAddress);
169
170 synchronized (sLock) {
171 return sBaseLoadAddress;
172 }
173 }
174
175 /**
176 * Set the base library load address. This is call to enforce a base
177 * load address, typically in non-initial processes. This must be called
178 * before loading any library with loadLibrary().
179 * @param baseLoadAddress The new base load address.
180 */
181 public static void setBaseLoadAddress(long baseLoadAddress) {
182 if (DEBUG) Log.i(TAG, "Linker.setBaseLoadAddress(" + baseLoadAddress + " ) called");
183
184 synchronized (sLock) {
185 sBaseLoadAddress = baseLoadAddress;
186 sCurrentLoadAddress = baseLoadAddress;
187 }
188 }
189
190 /**
191 * Call this method to retrieve a Bundle containing RELRO sharing
192 * information about the libraries currently loaded in this process.
193 * Should be used in the initial process only.
194 *
195 * @return null if initRelroSharing() was not called, or no libraries
196 * were loaded. Otherwise a new Bundle that must be passed to any
197 * other process, which in turn should call applyRelroBundle() with
198 * it. Note that the Bundle contains file descriptors and thus cannot
199 * be written to disk or added to an Intent.
200 */
201 public static Bundle getRelroBundle() {
202 if (DEBUG) Log.i(TAG, "Linker.getRelroBundle() called");
203
204 synchronized (sLock) {
205
206 if (!sRelroSharingEnabled || sLoadedLibraries == null) {
207 if (DEBUG) Log.i(TAG, "Nothing to share!?");
208 return null;
209 }
210
211 Bundle bundle = sLoadedLibraries.toBundle();
212 if (DEBUG) Log.i(TAG, "Bundle has " + bundle.size() + " items.");
213 return bundle;
214 }
215 }
216
217 /**
218 * Apply a Bundle generated by getRelroBundle() to the libraries
219 * loaded in this process. Should be used in non-initial processes only.
220 * @param bundle A Bundle reference. Can be null.
221 */
222 public static void applyRelroBundle(Bundle bundle) {
223 if (DEBUG) Log.i(TAG, "Linker.applyRelroBundle() called");
224
225 synchronized (sLock) {
226 if (bundle == null) {
227 if (DEBUG) Log.i(TAG, "null Bundle!?");
228 return;
229 }
230
231 sRelroLibraries = new LibInfoMap(bundle);
232 if (DEBUG) {
233 Log.i(TAG, "Bundle has " + sRelroLibraries.size() + " items");
234 for (Map.Entry<String, LibInfo> entry : sRelroLibraries.entrySet ()) {
235 String libName = entry.getKey();
236 LibInfo libInfo = entry.getValue();
237 Log.i(TAG, " library " + libName + ": " + libInfo.toString( ));
238 }
239 }
240
241 if (sLoadedLibraries != null) {
242 // Apply the RELRO section to all libraries that were already
243 // loaded, if any.
244 for (Map.Entry<String, LibInfo> entry : sRelroLibraries.entrySet() ) {
245 String libName = entry.getKey();
246 LibInfo libInfo = entry.getValue();
247
248 if (sLoadedLibraries.get(libName) != null) {
249 sLoadedLibraries.put(libName, libInfo);
250 if (!nativeUseSharedRelro(libName, libInfo))
251 Log.w(TAG, "Could not use shared RELRO section for " + libName);
252 }
253 }
254 }
255
256 if (DEBUG) Log.i(TAG, "Linker.applyRelroBundle() exiting");
257 }
258 }
259
260 /**
261 * Load a native shared library with the Chromium linker.
262 * If neither initSharedRelro() or readFromBundle() were called
263 * previously, this uses the standard linker (i.e. System.loadLibrary()).
264 *
265 * @param library The library's base name.
266 * @throws UnsatisfiedLinkError if the library does not exist.
267 */
268 public static void loadLibrary(String library) {
269 if (DEBUG) Log.i(TAG, "loadLibrary: " + library);
270
271 synchronized (sLock) {
272 // Don't self-load the linker. This is because the build system is
273 // not clever enough to understand that all the libraries packaged
274 // in the final .apk don't need to be explicitely loaded.
275 if (library.startsWith("crazy_linker")) {
276 if (DEBUG) Log.i(TAG, "ignoring self-linker load");
277 return;
278 }
279
280 if (!sInitialized) {
281 initRelroSharing();
282
283 if (DEBUG) Log.i(TAG, "Loading libcrazy_linker.so");
284 try {
285 System.loadLibrary("crazy_linker");
286 } catch (UnsatisfiedLinkError e) {
287 // In a component build, the library name is crazy_linker.cr
288 System.loadLibrary("crazy_linker.cr");
289 }
290 sInitialized = true;
291 }
292
293 String libName = System.mapLibraryName(library);
294
295 if (sLoadedLibraries == null)
296 sLoadedLibraries = new LibInfoMap();
297
298 if (sLoadedLibraries.containsKey(libName)) {
299 if (DEBUG) Log.i(TAG, "Not loading " + libName + " twice");
300 return;
301 }
302
303 LibInfo libInfo = new LibInfo();
304 LibInfo relroLibInfo = null;
305 long loadAddress = sCurrentLoadAddress;
306
307 if (sRelroLibraries != null) {
308 relroLibInfo = sRelroLibraries.get(libName);
309 if (relroLibInfo != null) {
310 loadAddress = relroLibInfo.mLoadAddress;
311 if (DEBUG) Log.i(TAG, "Loading library " + libName + " at " + loadAddress);
312 }
313 }
314
315 if (!nativeLoadLibrary(libName, loadAddress, libInfo)) {
316 Log.e(TAG, "Unable to load library: " + libName);
317 throw new UnsatisfiedLinkError("Unable to load library: " + libN ame);
318 }
319 // Ensure next library will be loaded at an appropriate address.
320 sCurrentLoadAddress = libInfo.mLoadAddress + libInfo.mLoadSize;
321
322 if (sRelroSharingEnabled) {
323 if (!nativeEnableSharedRelro(libName, libInfo))
324 Log.w(TAG, "Could not enable RELRO sharing for " + libName);
325 } else if (relroLibInfo != null) {
326 if (!nativeUseSharedRelro(libName, relroLibInfo))
327 Log.w(TAG, "Could not use RELRO sharing for " + libName);
328 else {
329 // Replace libInfo with relroLibInfo since it contains
330 // actual RELRO information.
331 libInfo = relroLibInfo;
332 }
333 }
334
335 sLoadedLibraries.put(libName, libInfo);
336 if (DEBUG) Log.i(TAG, "Library details " + libInfo.toString());
337 }
338 }
339
340 /**
341 * Native method used to load a library.
342 * @param library Platform specific library name (e.g. libfoo.so)
343 * @param loadAddress Explicit load address, or 0 for randomized one.
344 * @param relro_info If not null, this LibInfo instance will be populated
345 * with the library's information.
346 */
347 private static native boolean nativeLoadLibrary(String library,
348 long loadAddress,
349 LibInfo libInfo);
350
351 /**
352 * Native method used to enable RELRO section sharing. To be called
353 * in the browser process only.
354 * @param library Library name.
355 * @param libInfo LibInfo instance populated on success.
356 * @return true on success.
357 */
358 private static native boolean nativeEnableSharedRelro(String library,
359 LibInfo libInfo);
360
361 /**
362 * Native method used to use RELRO section sharing. To be called
363 * in service processes only.
364 * @param library Library name.
365 * @param libInfo A LibInfo instance containing valid RELRO information
366 * @return true on success.
367 */
368 private static native boolean nativeUseSharedRelro(String library,
369 LibInfo libInfo);
370
371 /**
372 * Record information for a given library.
373 * IMPORTANT: Native code knows anout this class's fields, so
374 * don't change them without modifying base/android/linker/crazy_linker.cpp too.
375 */
376 public static class LibInfo implements Parcelable {
377
378 public LibInfo() {
379 mLoadAddress = 0;
380 mLoadSize = 0;
381 mRelroStart = 0;
382 mRelroSize = 0;
383 mRelroFd = -1;
384 }
385
386 // from Parcelable
387 public LibInfo(Parcel in) {
388 mLoadAddress = in.readLong();
389 mLoadSize = in.readLong();
390 mRelroStart = in.readLong();
391 mRelroSize = in.readLong();
392 mRelroFd = in.readFileDescriptor().detachFd();
393 }
394
395 // from Parcelable
396 @Override
397 public void writeToParcel(Parcel out, int flags) {
398 if (mRelroFd >= 0) {
399 out.writeLong(mLoadAddress);
400 out.writeLong(mLoadSize);
401 out.writeLong(mRelroStart);
402 out.writeLong(mRelroSize);
403 ParcelFileDescriptor fd = ParcelFileDescriptor.adoptFd(mRelroFd) ;
404 fd.writeToParcel(out, 0);
405 fd.detachFd();
406 }
407 }
408
409 // from Parcelable
410 @Override
411 public int describeContents() {
412 return Parcelable.CONTENTS_FILE_DESCRIPTOR;
413 }
414
415 // from Parcelable
416 public static final Parcelable.Creator<LibInfo> CREATOR
417 = new Parcelable.Creator<LibInfo>() {
418 public LibInfo createFromParcel(Parcel in) {
419 return new LibInfo(in);
420 }
421
422 public LibInfo[] newArray(int size) {
423 return new LibInfo[size];
424 }
425 };
426
427 public String toString() {
428 return String.format("[load=%x-%x relro=%x-%x fd=%d]",
429 mLoadAddress,
430 mLoadAddress + mLoadSize,
431 mRelroStart,
432 mRelroStart + mRelroSize,
433 mRelroFd);
434 }
435
436 public long mLoadAddress; // page-aligned load address.
bulach 2013/09/10 14:21:39 perhaps just to be safe, repeat the comment here:
digit1 2013/09/10 16:40:28 Done.
437 public long mLoadSize; // page-aligned load size.
438 public long mRelroStart; // page-aligned address in memory, or 0 if non e.
439 public long mRelroSize; // page-aligned size in memory, or 0.
440 public int mRelroFd; // ashmem file descriptor, or -1
441 }
442
443 /**
444 * A class that maps library names to LibInfo objects, with support
445 * for reading and writing from/to Bundles.
446 */
447 private static class LibInfoMap extends HashMap<String, LibInfo> {
bulach 2013/09/10 14:21:39 given the limits on number of classes, could this
digit1 2013/09/10 16:40:28 I see, I've removed the LibInfoMap class.
448 public LibInfoMap() {
449 super();
450 }
451
452 // Serialize into a Bundle that can be passed to a different
453 // process through Binder. Note that this includes file descriptors,
454 // this it can't be serialized to disk or added to an Intent.
455 public Bundle toBundle() {
456 Bundle bundle = new Bundle(size());
457 for (Map.Entry<String, LibInfo> entry : entrySet())
458 bundle.putParcelable(entry.getKey(), entry.getValue());
459
460 return bundle;
461 }
462
463 // Initialized from a Bundle like the one generated from toBundle().
464 // @param bundle Input bundle.
465 public LibInfoMap(Bundle bundle) {
466 super();
467
468 // Ensure the bundle uses the application's class loader,
469 // not the framework one which doesn't know anything about
470 // LibInfo.
471 bundle.setClassLoader(getClass().getClassLoader());
472
473 for (String library : bundle.keySet()) {
474 LibInfo libInfo = bundle.getParcelable(library);
475 put(library, libInfo);
476 }
477 }
478 }
479
480 // The list of libraries that are currently loaded in this process.
481 private static LibInfoMap sLoadedLibraries = null;
482
483 // The list of libraries that can share their RELRO section.
484 private static LibInfoMap sRelroLibraries = null;
485 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698