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