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