• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 package org.chromium.base.library_loader;
6 
7 import android.content.Context;
8 import android.os.SystemClock;
9 import android.util.Log;
10 
11 import org.chromium.base.CommandLine;
12 import org.chromium.base.JNINamespace;
13 import org.chromium.base.SysUtils;
14 import org.chromium.base.TraceEvent;
15 
16 /**
17  * This class provides functionality to load and register the native libraries.
18  * Callers are allowed to separate loading the libraries from initializing them.
19  * This may be an advantage for Android Webview, where the libraries can be loaded
20  * by the zygote process, but then needs per process initialization after the
21  * application processes are forked from the zygote process.
22  *
23  * The libraries may be loaded and initialized from any thread. Synchronization
24  * primitives are used to ensure that overlapping requests from different
25  * threads are handled sequentially.
26  *
27  * See also base/android/library_loader/library_loader_hooks.cc, which contains
28  * the native counterpart to this class.
29  */
30 @JNINamespace("base::android")
31 public class LibraryLoader {
32     private static final String TAG = "LibraryLoader";
33 
34     // Guards all access to the libraries
35     private static final Object sLock = new Object();
36 
37     // One-way switch becomes true when the libraries are loaded.
38     private static boolean sLoaded = false;
39 
40     // One-way switch becomes true when the Java command line is switched to
41     // native.
42     private static boolean sCommandLineSwitched = false;
43 
44     // One-way switch becomes true when the libraries are initialized (
45     // by calling nativeLibraryLoaded, which forwards to LibraryLoaded(...) in
46     // library_loader_hooks.cc).
47     private static boolean sInitialized = false;
48 
49     // One-way switch becomes true if the system library loading failed,
50     // and the right native library was found and loaded by the hack.
51     // The flag is used to report UMA stats later.
52     private static boolean sNativeLibraryHackWasUsed = false;
53 
54     /**
55      * The same as ensureInitialized(null, false), should only be called
56      * by non-browser processes.
57      *
58      * @throws ProcessInitException
59      */
ensureInitialized()60     public static void ensureInitialized() throws ProcessInitException {
61         ensureInitialized(null, false);
62     }
63 
64     /**
65      *  This method blocks until the library is fully loaded and initialized.
66      *
67      *  @param context The context in which the method is called, the caller
68      *    may pass in a null context if it doesn't know in which context it
69      *    is running, or it doesn't need to work around the issue
70      *    http://b/13216167.
71      *
72      *    When the context is not null and native library was not extracted
73      *    by Android package manager, the LibraryLoader class
74      *    will extract the native libraries from APK. This is a hack used to
75      *    work around some Sony devices with the following platform bug:
76      *    http://b/13216167.
77      *
78      *  @param shouldDeleteOldWorkaroundLibraries The flag tells whether the method
79      *    should delete the old workaround libraries or not.
80      */
ensureInitialized( Context context, boolean shouldDeleteOldWorkaroundLibraries)81     public static void ensureInitialized(
82             Context context, boolean shouldDeleteOldWorkaroundLibraries)
83             throws ProcessInitException {
84         synchronized (sLock) {
85             if (sInitialized) {
86                 // Already initialized, nothing to do.
87                 return;
88             }
89             loadAlreadyLocked(context, shouldDeleteOldWorkaroundLibraries);
90             initializeAlreadyLocked();
91         }
92     }
93 
94     /**
95      * Checks if library is fully loaded and initialized.
96      */
isInitialized()97     public static boolean isInitialized() {
98         synchronized (sLock) {
99             return sInitialized;
100         }
101     }
102 
103     /**
104      * The same as loadNow(null, false), should only be called by
105      * non-browser process.
106      *
107      * @throws ProcessInitException
108      */
loadNow()109     public static void loadNow() throws ProcessInitException {
110         loadNow(null, false);
111     }
112 
113     /**
114      * Loads the library and blocks until the load completes. The caller is responsible
115      * for subsequently calling ensureInitialized().
116      * May be called on any thread, but should only be called once. Note the thread
117      * this is called on will be the thread that runs the native code's static initializers.
118      * See the comment in doInBackground() for more considerations on this.
119      *
120      * @param context The context the code is running, or null if it doesn't have one.
121      * @param shouldDeleteOldWorkaroundLibraries The flag tells whether the method
122      *   should delete the old workaround libraries or not.
123      *
124      * @throws ProcessInitException if the native library failed to load.
125      */
loadNow(Context context, boolean shouldDeleteOldWorkaroundLibraries)126     public static void loadNow(Context context, boolean shouldDeleteOldWorkaroundLibraries)
127             throws ProcessInitException {
128         synchronized (sLock) {
129             loadAlreadyLocked(context, shouldDeleteOldWorkaroundLibraries);
130         }
131     }
132 
133     /**
134      * initializes the library here and now: must be called on the thread that the
135      * native will call its "main" thread. The library must have previously been
136      * loaded with loadNow.
137      */
initialize()138     public static void initialize() throws ProcessInitException {
139         synchronized (sLock) {
140             initializeAlreadyLocked();
141         }
142     }
143 
144     // Invoke System.loadLibrary(...), triggering JNI_OnLoad in native code
loadAlreadyLocked( Context context, boolean shouldDeleteOldWorkaroundLibraries)145     private static void loadAlreadyLocked(
146             Context context, boolean shouldDeleteOldWorkaroundLibraries)
147             throws ProcessInitException {
148         try {
149             if (!sLoaded) {
150                 assert !sInitialized;
151 
152                 long startTime = SystemClock.uptimeMillis();
153                 boolean useChromiumLinker = Linker.isUsed();
154 
155                 if (useChromiumLinker) {
156                     // Load libraries using the Chromium linker.
157                     Linker.prepareLibraryLoad();
158 
159                     for (String library : NativeLibraries.LIBRARIES) {
160                         String zipfile = null;
161                         if (Linker.isInZipFile()) {
162                             zipfile = context.getApplicationInfo().sourceDir;
163                             Log.i(TAG, "Loading " + library + " from within " + zipfile);
164                         } else {
165                             Log.i(TAG, "Loading: " + library);
166                         }
167 
168                         boolean isLoaded = false;
169                         if (Linker.isUsingBrowserSharedRelros()) {
170                             try {
171                                 if (zipfile != null) {
172                                     Linker.loadLibraryInZipFile(zipfile, library);
173                                 } else {
174                                     Linker.loadLibrary(library);
175                                 }
176                                 isLoaded = true;
177                             } catch (UnsatisfiedLinkError e) {
178                                 Log.w(TAG, "Failed to load native library with shared RELRO, " +
179                                       "retrying without");
180                                 Linker.disableSharedRelros();
181                             }
182                         }
183                         if (!isLoaded) {
184                             if (zipfile != null) {
185                                 Linker.loadLibraryInZipFile(zipfile, library);
186                             } else {
187                                 Linker.loadLibrary(library);
188                             }
189                         }
190                     }
191 
192                     Linker.finishLibraryLoad();
193                 } else {
194                     // Load libraries using the system linker.
195                     for (String library : NativeLibraries.LIBRARIES) {
196                         try {
197                             System.loadLibrary(library);
198                         } catch (UnsatisfiedLinkError e) {
199                             if (context != null
200                                 && LibraryLoaderHelper.tryLoadLibraryUsingWorkaround(context,
201                                                                                      library)) {
202                                 sNativeLibraryHackWasUsed = true;
203                             } else {
204                                 throw e;
205                             }
206                         }
207                     }
208                 }
209 
210                 if (context != null
211                     && shouldDeleteOldWorkaroundLibraries
212                     && !sNativeLibraryHackWasUsed) {
213                     LibraryLoaderHelper.deleteWorkaroundLibrariesAsynchronously(
214                         context);
215                 }
216 
217                 long stopTime = SystemClock.uptimeMillis();
218                 Log.i(TAG, String.format("Time to load native libraries: %d ms (timestamps %d-%d)",
219                         stopTime - startTime,
220                         startTime % 10000,
221                         stopTime % 10000));
222 
223                 sLoaded = true;
224             }
225         } catch (UnsatisfiedLinkError e) {
226             throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_LOAD_FAILED, e);
227         }
228         // Check that the version of the library we have loaded matches the version we expect
229         Log.i(TAG, String.format(
230                 "Expected native library version number \"%s\"," +
231                         "actual native library version number \"%s\"",
232                 NativeLibraries.VERSION_NUMBER,
233                 nativeGetVersionNumber()));
234         if (!NativeLibraries.VERSION_NUMBER.equals(nativeGetVersionNumber())) {
235             throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_WRONG_VERSION);
236         }
237     }
238 
239     // The WebView requires the Command Line to be switched over before
240     // initialization is done. This is okay in the WebView's case since the
241     // JNI is already loaded by this point.
switchCommandLineForWebView()242     public static void switchCommandLineForWebView() {
243         synchronized (sLock) {
244             ensureCommandLineSwitchedAlreadyLocked();
245         }
246     }
247 
248     // Switch the CommandLine over from Java to native if it hasn't already been done.
249     // This must happen after the code is loaded and after JNI is ready (since after the
250     // switch the Java CommandLine will delegate all calls the native CommandLine).
ensureCommandLineSwitchedAlreadyLocked()251     private static void ensureCommandLineSwitchedAlreadyLocked() {
252         assert sLoaded;
253         if (sCommandLineSwitched) {
254             return;
255         }
256         nativeInitCommandLine(CommandLine.getJavaSwitchesOrNull());
257         CommandLine.enableNativeProxy();
258         sCommandLineSwitched = true;
259     }
260 
261     // Invoke base::android::LibraryLoaded in library_loader_hooks.cc
initializeAlreadyLocked()262     private static void initializeAlreadyLocked() throws ProcessInitException {
263         if (sInitialized) {
264             return;
265         }
266 
267         // Setup the native command line if necessary.
268         if (!sCommandLineSwitched) {
269             nativeInitCommandLine(CommandLine.getJavaSwitchesOrNull());
270         }
271 
272         if (!nativeLibraryLoaded()) {
273             Log.e(TAG, "error calling nativeLibraryLoaded");
274             throw new ProcessInitException(LoaderErrors.LOADER_ERROR_FAILED_TO_REGISTER_JNI);
275         }
276         // From this point on, native code is ready to use and checkIsReady()
277         // shouldn't complain from now on (and in fact, it's used by the
278         // following calls).
279         sInitialized = true;
280 
281         // The Chrome JNI is registered by now so we can switch the Java
282         // command line over to delegating to native if it's necessary.
283         if (!sCommandLineSwitched) {
284             CommandLine.enableNativeProxy();
285             sCommandLineSwitched = true;
286         }
287 
288         // From now on, keep tracing in sync with native.
289         TraceEvent.registerNativeEnabledObserver();
290     }
291 
292     // Called after all native initializations are complete.
onNativeInitializationComplete()293     public static void onNativeInitializationComplete() {
294         // Record histogram for the Chromium linker.
295         if (Linker.isUsed()) {
296             nativeRecordChromiumAndroidLinkerHistogram(Linker.loadAtFixedAddressFailed(),
297                     SysUtils.isLowEndDevice());
298         }
299 
300         nativeRecordNativeLibraryHack(sNativeLibraryHackWasUsed);
301     }
302 
nativeInitCommandLine(String[] initCommandLine)303     private static native void nativeInitCommandLine(String[] initCommandLine);
304 
305     // Only methods needed before or during normal JNI registration are during System.OnLoad.
306     // nativeLibraryLoaded is then called to register everything else.  This process is called
307     // "initialization".  This method will be mapped (by generated code) to the LibraryLoaded
308     // definition in base/android/library_loader/library_loader_hooks.cc.
309     //
310     // Return true on success and false on failure.
nativeLibraryLoaded()311     private static native boolean nativeLibraryLoaded();
312 
313     // Method called to record statistics about the Chromium linker operation,
314     // i.e. whether the library failed to be loaded at a fixed address, and
315     // whether the device is 'low-memory'.
nativeRecordChromiumAndroidLinkerHistogram( boolean loadedAtFixedAddressFailed, boolean isLowMemoryDevice)316     private static native void nativeRecordChromiumAndroidLinkerHistogram(
317             boolean loadedAtFixedAddressFailed,
318             boolean isLowMemoryDevice);
319 
320     // Get the version of the native library. This is needed so that we can check we
321     // have the right version before initializing the (rest of the) JNI.
nativeGetVersionNumber()322     private static native String nativeGetVersionNumber();
323 
nativeRecordNativeLibraryHack(boolean usedHack)324     private static native void nativeRecordNativeLibraryHack(boolean usedHack);
325 }
326