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.AsyncTask; 9 import android.os.SystemClock; 10 11 import org.chromium.base.CommandLine; 12 import org.chromium.base.ContextUtils; 13 import org.chromium.base.Log; 14 import org.chromium.base.TraceEvent; 15 import org.chromium.base.annotations.CalledByNative; 16 import org.chromium.base.annotations.JNINamespace; 17 import org.chromium.base.metrics.RecordHistogram; 18 19 import java.util.concurrent.atomic.AtomicBoolean; 20 21 import javax.annotation.Nullable; 22 23 /** 24 * This class provides functionality to load and register the native libraries. 25 * Callers are allowed to separate loading the libraries from initializing them. 26 * This may be an advantage for Android Webview, where the libraries can be loaded 27 * by the zygote process, but then needs per process initialization after the 28 * application processes are forked from the zygote process. 29 * 30 * The libraries may be loaded and initialized from any thread. Synchronization 31 * primitives are used to ensure that overlapping requests from different 32 * threads are handled sequentially. 33 * 34 * See also base/android/library_loader/library_loader_hooks.cc, which contains 35 * the native counterpart to this class. 36 */ 37 @JNINamespace("base::android") 38 public class LibraryLoader { 39 private static final String TAG = "LibraryLoader"; 40 41 // Set to true to enable debug logs. 42 private static final boolean DEBUG = false; 43 44 // Guards all access to the libraries 45 private static final Object sLock = new Object(); 46 47 // The singleton instance of NativeLibraryPreloader. 48 private static NativeLibraryPreloader sLibraryPreloader; 49 50 // The singleton instance of LibraryLoader. 51 private static volatile LibraryLoader sInstance; 52 53 // One-way switch becomes true when the libraries are loaded. 54 private boolean mLoaded; 55 56 // One-way switch becomes true when the Java command line is switched to 57 // native. 58 private boolean mCommandLineSwitched; 59 60 // One-way switch becomes true when the libraries are initialized ( 61 // by calling nativeLibraryLoaded, which forwards to LibraryLoaded(...) in 62 // library_loader_hooks.cc). 63 // Note that this member should remain a one-way switch, since it accessed from multiple 64 // threads without a lock. 65 private volatile boolean mInitialized; 66 67 // One-way switches recording attempts to use Relro sharing in the browser. 68 // The flags are used to report UMA stats later. 69 private boolean mIsUsingBrowserSharedRelros; 70 private boolean mLoadAtFixedAddressFailed; 71 72 // One-way switch becomes true if the Chromium library was loaded from the 73 // APK file directly. 74 private boolean mLibraryWasLoadedFromApk; 75 76 // The type of process the shared library is loaded in. 77 // This member can be accessed from multiple threads simultaneously, so it have to be 78 // final (like now) or be protected in some way (volatile of synchronized). 79 private final int mLibraryProcessType; 80 81 // One-way switch that becomes true once 82 // {@link asyncPrefetchLibrariesToMemory} has been called. 83 private final AtomicBoolean mPrefetchLibraryHasBeenCalled; 84 85 // The number of milliseconds it took to load all the native libraries, which 86 // will be reported via UMA. Set once when the libraries are done loading. 87 private long mLibraryLoadTimeMs; 88 89 // The return value of NativeLibraryPreloader.loadLibrary(), which will be reported 90 // via UMA, it is initialized to the invalid value which shouldn't showup in UMA 91 // report. 92 private int mLibraryPreloaderStatus = -1; 93 94 /** 95 * Set native library preloader, if set, the NativeLibraryPreloader.loadLibrary will be invoked 96 * before calling System.loadLibrary, this only applies when not using the chromium linker. 97 * 98 * @param loader the NativeLibraryPreloader, it shall only be set once and before the 99 * native library loaded. 100 */ setNativeLibraryPreloader(NativeLibraryPreloader loader)101 public static void setNativeLibraryPreloader(NativeLibraryPreloader loader) { 102 synchronized (sLock) { 103 assert sLibraryPreloader == null && (sInstance == null || !sInstance.mLoaded); 104 sLibraryPreloader = loader; 105 } 106 } 107 108 /** 109 * @param libraryProcessType the process the shared library is loaded in. refer to 110 * LibraryProcessType for possible values. 111 * @return LibraryLoader if existing, otherwise create a new one. 112 */ get(int libraryProcessType)113 public static LibraryLoader get(int libraryProcessType) throws ProcessInitException { 114 synchronized (sLock) { 115 if (sInstance != null) { 116 if (sInstance.mLibraryProcessType == libraryProcessType) return sInstance; 117 throw new ProcessInitException( 118 LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_LOAD_FAILED); 119 } 120 sInstance = new LibraryLoader(libraryProcessType); 121 return sInstance; 122 } 123 } 124 LibraryLoader(int libraryProcessType)125 private LibraryLoader(int libraryProcessType) { 126 mLibraryProcessType = libraryProcessType; 127 mPrefetchLibraryHasBeenCalled = new AtomicBoolean(); 128 } 129 130 /** 131 * This method blocks until the library is fully loaded and initialized. 132 * 133 * @param context The context in which the method is called. 134 */ ensureInitialized(Context context)135 public void ensureInitialized(Context context) throws ProcessInitException { 136 // TODO(wnwen): Move this call appropriately down to the tests that need it. 137 ContextUtils.initApplicationContext(context.getApplicationContext()); 138 synchronized (sLock) { 139 if (mInitialized) { 140 // Already initialized, nothing to do. 141 return; 142 } 143 loadAlreadyLocked(context); 144 initializeAlreadyLocked(); 145 } 146 } 147 148 /** 149 * Checks if library is fully loaded and initialized. 150 */ isInitialized()151 public static boolean isInitialized() { 152 return sInstance != null && sInstance.mInitialized; 153 } 154 155 /** 156 * Loads the library and blocks until the load completes. The caller is responsible 157 * for subsequently calling ensureInitialized(). 158 * May be called on any thread, but should only be called once. Note the thread 159 * this is called on will be the thread that runs the native code's static initializers. 160 * See the comment in doInBackground() for more considerations on this. 161 * 162 * @param context The context the code is running. 163 * 164 * @throws ProcessInitException if the native library failed to load. 165 */ loadNow(Context context)166 public void loadNow(Context context) throws ProcessInitException { 167 synchronized (sLock) { 168 loadAlreadyLocked(context); 169 } 170 } 171 172 /** 173 * initializes the library here and now: must be called on the thread that the 174 * native will call its "main" thread. The library must have previously been 175 * loaded with loadNow. 176 */ initialize()177 public void initialize() throws ProcessInitException { 178 synchronized (sLock) { 179 initializeAlreadyLocked(); 180 } 181 } 182 183 /** Prefetches the native libraries in a background thread. 184 * 185 * Launches an AsyncTask that, through a short-lived forked process, reads a 186 * part of each page of the native library. This is done to warm up the 187 * page cache, turning hard page faults into soft ones. 188 * 189 * This is done this way, as testing shows that fadvise(FADV_WILLNEED) is 190 * detrimental to the startup time. 191 */ asyncPrefetchLibrariesToMemory()192 public void asyncPrefetchLibrariesToMemory() { 193 final boolean coldStart = mPrefetchLibraryHasBeenCalled.compareAndSet(false, true); 194 new AsyncTask<Void, Void, Void>() { 195 @Override 196 protected Void doInBackground(Void... params) { 197 TraceEvent.begin("LibraryLoader.asyncPrefetchLibrariesToMemory"); 198 int percentage = nativePercentageOfResidentNativeLibraryCode(); 199 boolean success = false; 200 if (coldStart) { 201 success = nativeForkAndPrefetchNativeLibrary(); 202 if (!success) { 203 Log.w(TAG, "Forking a process to prefetch the native library failed."); 204 } 205 } 206 // As this runs in a background thread, it can be called before histograms are 207 // initialized. In this instance, histograms are dropped. 208 RecordHistogram.initialize(); 209 if (coldStart) { 210 RecordHistogram.recordBooleanHistogram("LibraryLoader.PrefetchStatus", success); 211 } 212 if (percentage != -1) { 213 String histogram = "LibraryLoader.PercentageOfResidentCodeBeforePrefetch" 214 + (coldStart ? ".ColdStartup" : ".WarmStartup"); 215 RecordHistogram.recordPercentageHistogram(histogram, percentage); 216 } 217 TraceEvent.end("LibraryLoader.asyncPrefetchLibrariesToMemory"); 218 return null; 219 } 220 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 221 } 222 223 // Helper for loadAlreadyLocked(). Load a native shared library with the Chromium linker. 224 // Sets UMA flags depending on the results of loading. loadLibrary(Linker linker, @Nullable String zipFilePath, String libFilePath)225 private void loadLibrary(Linker linker, @Nullable String zipFilePath, String libFilePath) { 226 if (linker.isUsingBrowserSharedRelros()) { 227 // If the browser is set to attempt shared RELROs then we try first with shared 228 // RELROs enabled, and if that fails then retry without. 229 mIsUsingBrowserSharedRelros = true; 230 try { 231 linker.loadLibrary(zipFilePath, libFilePath); 232 } catch (UnsatisfiedLinkError e) { 233 Log.w(TAG, "Failed to load native library with shared RELRO, retrying without"); 234 mLoadAtFixedAddressFailed = true; 235 linker.loadLibraryNoFixedAddress(zipFilePath, libFilePath); 236 } 237 } else { 238 // No attempt to use shared RELROs in the browser, so load as normal. 239 linker.loadLibrary(zipFilePath, libFilePath); 240 } 241 242 // Loaded successfully, so record if we loaded directly from an APK. 243 if (zipFilePath != null) { 244 mLibraryWasLoadedFromApk = true; 245 } 246 } 247 248 // Invoke either Linker.loadLibrary(...) or System.loadLibrary(...), triggering 249 // JNI_OnLoad in native code loadAlreadyLocked(Context context)250 private void loadAlreadyLocked(Context context) throws ProcessInitException { 251 try { 252 if (!mLoaded) { 253 assert !mInitialized; 254 255 long startTime = SystemClock.uptimeMillis(); 256 257 if (Linker.isUsed()) { 258 // Load libraries using the Chromium linker. 259 Linker linker = Linker.getInstance(); 260 linker.prepareLibraryLoad(); 261 262 for (String library : NativeLibraries.LIBRARIES) { 263 // Don't self-load the linker. This is because the build system is 264 // not clever enough to understand that all the libraries packaged 265 // in the final .apk don't need to be explicitly loaded. 266 if (linker.isChromiumLinkerLibrary(library)) { 267 if (DEBUG) Log.i(TAG, "ignoring self-linker load"); 268 continue; 269 } 270 271 // Determine where the library should be loaded from. 272 String zipFilePath = null; 273 String libFilePath = System.mapLibraryName(library); 274 if (Linker.isInZipFile()) { 275 // Load directly from the APK. 276 zipFilePath = context.getApplicationInfo().sourceDir; 277 Log.i(TAG, "Loading " + library + " from within " + zipFilePath); 278 } else { 279 // The library is in its own file. 280 Log.i(TAG, "Loading " + library); 281 } 282 283 // Load the library using this Linker. May throw UnsatisfiedLinkError. 284 loadLibrary(linker, zipFilePath, libFilePath); 285 } 286 287 linker.finishLibraryLoad(); 288 } else { 289 if (sLibraryPreloader != null) { 290 mLibraryPreloaderStatus = sLibraryPreloader.loadLibrary(context); 291 } 292 // Load libraries using the system linker. 293 for (String library : NativeLibraries.LIBRARIES) { 294 System.loadLibrary(library); 295 } 296 } 297 298 long stopTime = SystemClock.uptimeMillis(); 299 mLibraryLoadTimeMs = stopTime - startTime; 300 Log.i(TAG, String.format("Time to load native libraries: %d ms (timestamps %d-%d)", 301 mLibraryLoadTimeMs, 302 startTime % 10000, 303 stopTime % 10000)); 304 305 mLoaded = true; 306 } 307 } catch (UnsatisfiedLinkError e) { 308 throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_LOAD_FAILED, e); 309 } 310 // Check that the version of the library we have loaded matches the version we expect 311 Log.i(TAG, String.format( 312 "Expected native library version number \"%s\", " 313 + "actual native library version number \"%s\"", 314 NativeLibraries.sVersionNumber, 315 nativeGetVersionNumber())); 316 if (!NativeLibraries.sVersionNumber.equals(nativeGetVersionNumber())) { 317 throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_WRONG_VERSION); 318 } 319 } 320 321 // Returns whether the given split name is that of the ABI split. isAbiSplit(String splitName)322 private static boolean isAbiSplit(String splitName) { 323 // The split name for the ABI split is manually set in the build rules. 324 return splitName.startsWith("abi_"); 325 } 326 327 // The WebView requires the Command Line to be switched over before 328 // initialization is done. This is okay in the WebView's case since the 329 // JNI is already loaded by this point. switchCommandLineForWebView()330 public void switchCommandLineForWebView() { 331 synchronized (sLock) { 332 ensureCommandLineSwitchedAlreadyLocked(); 333 } 334 } 335 336 // Switch the CommandLine over from Java to native if it hasn't already been done. 337 // This must happen after the code is loaded and after JNI is ready (since after the 338 // switch the Java CommandLine will delegate all calls the native CommandLine). ensureCommandLineSwitchedAlreadyLocked()339 private void ensureCommandLineSwitchedAlreadyLocked() { 340 assert mLoaded; 341 if (mCommandLineSwitched) { 342 return; 343 } 344 nativeInitCommandLine(CommandLine.getJavaSwitchesOrNull()); 345 CommandLine.enableNativeProxy(); 346 mCommandLineSwitched = true; 347 348 // Ensure that native side application context is loaded and in sync with java side. Must do 349 // this here so webview also gets its application context set before fully initializing. 350 ContextUtils.initApplicationContextForNative(); 351 } 352 353 // Invoke base::android::LibraryLoaded in library_loader_hooks.cc initializeAlreadyLocked()354 private void initializeAlreadyLocked() throws ProcessInitException { 355 if (mInitialized) { 356 return; 357 } 358 359 ensureCommandLineSwitchedAlreadyLocked(); 360 361 if (!nativeLibraryLoaded()) { 362 Log.e(TAG, "error calling nativeLibraryLoaded"); 363 throw new ProcessInitException(LoaderErrors.LOADER_ERROR_FAILED_TO_REGISTER_JNI); 364 } 365 366 // From now on, keep tracing in sync with native. 367 TraceEvent.registerNativeEnabledObserver(); 368 369 // From this point on, native code is ready to use and checkIsReady() 370 // shouldn't complain from now on (and in fact, it's used by the 371 // following calls). 372 // Note that this flag can be accessed asynchronously, so any initialization 373 // must be performed before. 374 mInitialized = true; 375 } 376 377 // Called after all native initializations are complete. onNativeInitializationComplete(Context context)378 public void onNativeInitializationComplete(Context context) { 379 recordBrowserProcessHistogram(context); 380 } 381 382 // Record Chromium linker histogram state for the main browser process. Called from 383 // onNativeInitializationComplete(). recordBrowserProcessHistogram(Context context)384 private void recordBrowserProcessHistogram(Context context) { 385 if (Linker.getInstance().isUsed()) { 386 nativeRecordChromiumAndroidLinkerBrowserHistogram(mIsUsingBrowserSharedRelros, 387 mLoadAtFixedAddressFailed, 388 getLibraryLoadFromApkStatus(context), 389 mLibraryLoadTimeMs); 390 } 391 if (sLibraryPreloader != null) { 392 nativeRecordLibraryPreloaderBrowserHistogram(mLibraryPreloaderStatus); 393 } 394 } 395 396 // Returns the device's status for loading a library directly from the APK file. 397 // This method can only be called when the Chromium linker is used. getLibraryLoadFromApkStatus(Context context)398 private int getLibraryLoadFromApkStatus(Context context) { 399 assert Linker.getInstance().isUsed(); 400 401 if (mLibraryWasLoadedFromApk) { 402 return LibraryLoadFromApkStatusCodes.SUCCESSFUL; 403 } 404 405 // There were no libraries to be loaded directly from the APK file. 406 return LibraryLoadFromApkStatusCodes.UNKNOWN; 407 } 408 409 // Register pending Chromium linker histogram state for renderer processes. This cannot be 410 // recorded as a histogram immediately because histograms and IPC are not ready at the 411 // time it are captured. This function stores a pending value, so that a later call to 412 // RecordChromiumAndroidLinkerRendererHistogram() will record it correctly. registerRendererProcessHistogram(boolean requestedSharedRelro, boolean loadAtFixedAddressFailed)413 public void registerRendererProcessHistogram(boolean requestedSharedRelro, 414 boolean loadAtFixedAddressFailed) { 415 if (Linker.getInstance().isUsed()) { 416 nativeRegisterChromiumAndroidLinkerRendererHistogram(requestedSharedRelro, 417 loadAtFixedAddressFailed, 418 mLibraryLoadTimeMs); 419 } 420 if (sLibraryPreloader != null) { 421 nativeRegisterLibraryPreloaderRendererHistogram(mLibraryPreloaderStatus); 422 } 423 } 424 425 /** 426 * @return the process the shared library is loaded in, see the LibraryProcessType 427 * for possible values. 428 */ 429 @CalledByNative getLibraryProcessType()430 public static int getLibraryProcessType() { 431 if (sInstance == null) return LibraryProcessType.PROCESS_UNINITIALIZED; 432 return sInstance.mLibraryProcessType; 433 } 434 nativeInitCommandLine(String[] initCommandLine)435 private native void nativeInitCommandLine(String[] initCommandLine); 436 437 // Only methods needed before or during normal JNI registration are during System.OnLoad. 438 // nativeLibraryLoaded is then called to register everything else. This process is called 439 // "initialization". This method will be mapped (by generated code) to the LibraryLoaded 440 // definition in base/android/library_loader/library_loader_hooks.cc. 441 // 442 // Return true on success and false on failure. nativeLibraryLoaded()443 private native boolean nativeLibraryLoaded(); 444 445 // Method called to record statistics about the Chromium linker operation for the main 446 // browser process. Indicates whether the linker attempted relro sharing for the browser, 447 // and if it did, whether the library failed to load at a fixed address. Also records 448 // support for loading a library directly from the APK file, and the number of milliseconds 449 // it took to load the libraries. nativeRecordChromiumAndroidLinkerBrowserHistogram( boolean isUsingBrowserSharedRelros, boolean loadAtFixedAddressFailed, int libraryLoadFromApkStatus, long libraryLoadTime)450 private native void nativeRecordChromiumAndroidLinkerBrowserHistogram( 451 boolean isUsingBrowserSharedRelros, 452 boolean loadAtFixedAddressFailed, 453 int libraryLoadFromApkStatus, 454 long libraryLoadTime); 455 456 // Method called to record the return value of NativeLibraryPreloader.loadLibrary for the main 457 // browser process. nativeRecordLibraryPreloaderBrowserHistogram(int status)458 private native void nativeRecordLibraryPreloaderBrowserHistogram(int status); 459 460 // Method called to register (for later recording) statistics about the Chromium linker 461 // operation for a renderer process. Indicates whether the linker attempted relro sharing, 462 // and if it did, whether the library failed to load at a fixed address. Also records the 463 // number of milliseconds it took to load the libraries. nativeRegisterChromiumAndroidLinkerRendererHistogram( boolean requestedSharedRelro, boolean loadAtFixedAddressFailed, long libraryLoadTime)464 private native void nativeRegisterChromiumAndroidLinkerRendererHistogram( 465 boolean requestedSharedRelro, 466 boolean loadAtFixedAddressFailed, 467 long libraryLoadTime); 468 469 // Method called to register (for later recording) the return value of 470 // NativeLibraryPreloader.loadLibrary for a renderer process. nativeRegisterLibraryPreloaderRendererHistogram(int status)471 private native void nativeRegisterLibraryPreloaderRendererHistogram(int status); 472 473 // Get the version of the native library. This is needed so that we can check we 474 // have the right version before initializing the (rest of the) JNI. nativeGetVersionNumber()475 private native String nativeGetVersionNumber(); 476 477 // Finds the ranges corresponding to the native library pages, forks a new 478 // process to prefetch these pages and waits for it. The new process then 479 // terminates. This is blocking. nativeForkAndPrefetchNativeLibrary()480 private static native boolean nativeForkAndPrefetchNativeLibrary(); 481 482 // Returns the percentage of the native library code page that are currently reseident in 483 // memory. nativePercentageOfResidentNativeLibraryCode()484 private static native int nativePercentageOfResidentNativeLibraryCode(); 485 } 486