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 static org.chromium.base.metrics.CachedMetrics.EnumeratedHistogramSample; 8 9 import android.annotation.SuppressLint; 10 import android.content.Context; 11 import android.os.Build; 12 import android.os.Build.VERSION_CODES; 13 import android.os.Process; 14 import android.os.StrictMode; 15 import android.os.SystemClock; 16 import android.support.annotation.NonNull; 17 import android.support.v4.content.ContextCompat; 18 import android.system.Os; 19 20 import org.chromium.base.AsyncTask; 21 import org.chromium.base.BuildConfig; 22 import org.chromium.base.BuildInfo; 23 import org.chromium.base.CommandLine; 24 import org.chromium.base.ContextUtils; 25 import org.chromium.base.FileUtils; 26 import org.chromium.base.Log; 27 import org.chromium.base.SysUtils; 28 import org.chromium.base.TraceEvent; 29 import org.chromium.base.VisibleForTesting; 30 import org.chromium.base.annotations.JNINamespace; 31 import org.chromium.base.annotations.MainDex; 32 import org.chromium.base.metrics.RecordHistogram; 33 34 import java.io.File; 35 import java.io.IOException; 36 import java.io.InputStream; 37 import java.util.concurrent.atomic.AtomicBoolean; 38 import java.util.zip.ZipFile; 39 40 import javax.annotation.Nullable; 41 42 /** 43 * This class provides functionality to load and register the native libraries. 44 * Callers are allowed to separate loading the libraries from initializing them. 45 * This may be an advantage for Android Webview, where the libraries can be loaded 46 * by the zygote process, but then needs per process initialization after the 47 * application processes are forked from the zygote process. 48 * 49 * The libraries may be loaded and initialized from any thread. Synchronization 50 * primitives are used to ensure that overlapping requests from different 51 * threads are handled sequentially. 52 * 53 * See also base/android/library_loader/library_loader_hooks.cc, which contains 54 * the native counterpart to this class. 55 */ 56 @MainDex 57 @JNINamespace("base::android") 58 public class LibraryLoader { 59 private static final String TAG = "LibraryLoader"; 60 61 // Set to true to enable debug logs. 62 private static final boolean DEBUG = false; 63 64 // Experience shows that on some devices, the PackageManager fails to properly extract 65 // native shared libraries to the /data partition at installation or upgrade time, 66 // which creates all kind of chaos (https://crbug.com/806998). 67 // 68 // We implement a fallback when we detect the issue by manually extracting the library 69 // into Chromium's own data directory, then retrying to load the new library from here. 70 // 71 // This will work for any device running K-. Starting with Android L, render processes 72 // cannot access the file system anymore, and extraction will always fail for them. 73 // However, the issue doesn't seem to appear in the field for Android L. 74 // 75 // Also, starting with M, the issue doesn't exist if shared libraries are stored 76 // uncompressed in the APK (as Chromium does), because the system linker can access them 77 // directly, and the PackageManager will thus never extract them in the first place. 78 static public final boolean PLATFORM_REQUIRES_NATIVE_FALLBACK_EXTRACTION = 79 Build.VERSION.SDK_INT <= VERSION_CODES.KITKAT; 80 81 // Location of extracted native libraries. 82 private static final String LIBRARY_DIR = "native_libraries"; 83 84 // SharedPreferences key for "don't prefetch libraries" flag 85 private static final String DONT_PREFETCH_LIBRARIES_KEY = "dont_prefetch_libraries"; 86 87 private static final EnumeratedHistogramSample sRelinkerCountHistogram = 88 new EnumeratedHistogramSample("ChromiumAndroidLinker.RelinkerFallbackCount", 2); 89 90 // The singleton instance of LibraryLoader. Never null (not final for tests). 91 private static LibraryLoader sInstance = new LibraryLoader(); 92 93 // One-way switch becomes true when the libraries are initialized ( 94 // by calling nativeLibraryLoaded, which forwards to LibraryLoaded(...) in 95 // library_loader_hooks.cc). 96 // Note that this member should remain a one-way switch, since it accessed from multiple 97 // threads without a lock. 98 private volatile boolean mInitialized; 99 100 // One-way switch that becomes true once 101 // {@link asyncPrefetchLibrariesToMemory} has been called. 102 private final AtomicBoolean mPrefetchLibraryHasBeenCalled = new AtomicBoolean(); 103 104 // Guards all fields below. 105 private final Object mLock = new Object(); 106 107 private NativeLibraryPreloader mLibraryPreloader; 108 private boolean mLibraryPreloaderCalled; 109 110 // One-way switch becomes true when the libraries are loaded. 111 private boolean mLoaded; 112 113 // One-way switch becomes true when the Java command line is switched to 114 // native. 115 private boolean mCommandLineSwitched; 116 117 // One-way switches recording attempts to use Relro sharing in the browser. 118 // The flags are used to report UMA stats later. 119 private boolean mIsUsingBrowserSharedRelros; 120 private boolean mLoadAtFixedAddressFailed; 121 122 // One-way switch becomes true if the Chromium library was loaded from the 123 // APK file directly. 124 private boolean mLibraryWasLoadedFromApk; 125 126 // The type of process the shared library is loaded in. 127 private @LibraryProcessType int mLibraryProcessType; 128 129 // The number of milliseconds it took to load all the native libraries, which 130 // will be reported via UMA. Set once when the libraries are done loading. 131 private long mLibraryLoadTimeMs; 132 133 // The return value of NativeLibraryPreloader.loadLibrary(), which will be reported 134 // via UMA, it is initialized to the invalid value which shouldn't showup in UMA 135 // report. 136 private int mLibraryPreloaderStatus = -1; 137 138 /** 139 * Call this method to determine if this chromium project must 140 * use this linker. If not, System.loadLibrary() should be used to load 141 * libraries instead. 142 */ useCrazyLinker()143 public static boolean useCrazyLinker() { 144 // TODO(digit): Remove this early return GVR is loadable. 145 // A non-monochrome APK (such as ChromePublic.apk or ChromeModernPublic.apk) on N+ cannot 146 // use the Linker because the latter is incompatible with the GVR library. Fall back 147 // to using System.loadLibrary() or System.load() at the cost of no RELRO sharing. 148 // 149 // A non-monochrome APK (such as ChromePublic.apk) can be installed on N+ in these 150 // circumstances: 151 // * installing APK manually 152 // * after OTA from M to N 153 // * side-installing Chrome (possibly from another release channel) 154 // * Play Store bugs leading to incorrect APK flavor being installed 155 // 156 if (Build.VERSION.SDK_INT >= VERSION_CODES.N) return false; 157 158 // The auto-generated NativeLibraries.sUseLinker variable will be true if the 159 // build has not explicitly disabled Linker features. 160 return NativeLibraries.sUseLinker; 161 } 162 163 /** 164 * Call this method to determine if the chromium project must load the library 165 * directly from a zip file. 166 */ isInZipFile()167 private static boolean isInZipFile() { 168 // The auto-generated NativeLibraries.sUseLibraryInZipFile variable will be true 169 // iff the library remains embedded in the APK zip file on the target. 170 return NativeLibraries.sUseLibraryInZipFile; 171 } 172 173 /** 174 * Set native library preloader, if set, the NativeLibraryPreloader.loadLibrary will be invoked 175 * before calling System.loadLibrary, this only applies when not using the chromium linker. 176 * 177 * @param loader the NativeLibraryPreloader, it shall only be set once and before the 178 * native library loaded. 179 */ setNativeLibraryPreloader(NativeLibraryPreloader loader)180 public void setNativeLibraryPreloader(NativeLibraryPreloader loader) { 181 synchronized (mLock) { 182 assert mLibraryPreloader == null && !mLoaded; 183 mLibraryPreloader = loader; 184 } 185 } 186 getInstance()187 public static LibraryLoader getInstance() { 188 return sInstance; 189 } 190 LibraryLoader()191 private LibraryLoader() {} 192 193 /** 194 * This method blocks until the library is fully loaded and initialized. 195 * 196 * @param processType the process the shared library is loaded in. 197 */ ensureInitialized(@ibraryProcessType int processType)198 public void ensureInitialized(@LibraryProcessType int processType) throws ProcessInitException { 199 synchronized (mLock) { 200 if (mInitialized) { 201 // Already initialized, nothing to do. 202 return; 203 } 204 loadAlreadyLocked(ContextUtils.getApplicationContext()); 205 initializeAlreadyLocked(processType); 206 } 207 } 208 209 /** 210 * Calls native library preloader (see {@link #setNativeLibraryPreloader}) with the app 211 * context. If there is no preloader set, this function does nothing. 212 * Preloader is called only once, so calling it explicitly via this method means 213 * that it won't be (implicitly) called during library loading. 214 */ preloadNow()215 public void preloadNow() { 216 preloadNowOverrideApplicationContext(ContextUtils.getApplicationContext()); 217 } 218 219 /** 220 * Similar to {@link #preloadNow}, but allows specifying app context to use. 221 */ preloadNowOverrideApplicationContext(Context appContext)222 public void preloadNowOverrideApplicationContext(Context appContext) { 223 synchronized (mLock) { 224 if (!useCrazyLinker()) { 225 preloadAlreadyLocked(appContext); 226 } 227 } 228 } 229 preloadAlreadyLocked(Context appContext)230 private void preloadAlreadyLocked(Context appContext) { 231 try (TraceEvent te = TraceEvent.scoped("LibraryLoader.preloadAlreadyLocked")) { 232 // Preloader uses system linker, we shouldn't preload if Chromium linker is used. 233 assert !useCrazyLinker(); 234 if (mLibraryPreloader != null && !mLibraryPreloaderCalled) { 235 mLibraryPreloaderStatus = mLibraryPreloader.loadLibrary(appContext); 236 mLibraryPreloaderCalled = true; 237 } 238 } 239 } 240 241 /** 242 * Checks if library is fully loaded and initialized. 243 */ isInitialized()244 public boolean isInitialized() { 245 return mInitialized; 246 } 247 248 /** 249 * Loads the library and blocks until the load completes. The caller is responsible 250 * for subsequently calling ensureInitialized(). 251 * May be called on any thread, but should only be called once. Note the thread 252 * this is called on will be the thread that runs the native code's static initializers. 253 * See the comment in doInBackground() for more considerations on this. 254 * 255 * @throws ProcessInitException if the native library failed to load. 256 */ loadNow()257 public void loadNow() throws ProcessInitException { 258 loadNowOverrideApplicationContext(ContextUtils.getApplicationContext()); 259 } 260 261 /** 262 * Override kept for callers that need to load from a different app context. Do not use unless 263 * specifically required to load from another context that is not the current process's app 264 * context. 265 * 266 * @param appContext The overriding app context to be used to load libraries. 267 * @throws ProcessInitException if the native library failed to load with this context. 268 */ loadNowOverrideApplicationContext(Context appContext)269 public void loadNowOverrideApplicationContext(Context appContext) throws ProcessInitException { 270 synchronized (mLock) { 271 if (mLoaded && appContext != ContextUtils.getApplicationContext()) { 272 throw new IllegalStateException("Attempt to load again from alternate context."); 273 } 274 loadAlreadyLocked(appContext); 275 } 276 } 277 278 /** 279 * Initializes the library here and now: must be called on the thread that the 280 * native will call its "main" thread. The library must have previously been 281 * loaded with loadNow. 282 * 283 * @param processType the process the shared library is loaded in. 284 */ initialize(@ibraryProcessType int processType)285 public void initialize(@LibraryProcessType int processType) throws ProcessInitException { 286 synchronized (mLock) { 287 initializeAlreadyLocked(processType); 288 } 289 } 290 291 /** 292 * Disables prefetching for subsequent runs. The value comes from "DontPrefetchLibraries" 293 * finch experiment, and is pushed on every run. I.e. the effect of the finch experiment 294 * lags by one run, which is the best we can do considering that prefetching happens way 295 * before finch is initialized. Note that since LibraryLoader is in //base, it can't depend 296 * on ChromeFeatureList, and has to rely on external code pushing the value. 297 * 298 * @param dontPrefetch whether not to prefetch libraries 299 */ setDontPrefetchLibrariesOnNextRuns(boolean dontPrefetch)300 public static void setDontPrefetchLibrariesOnNextRuns(boolean dontPrefetch) { 301 ContextUtils.getAppSharedPreferences() 302 .edit() 303 .putBoolean(DONT_PREFETCH_LIBRARIES_KEY, dontPrefetch) 304 .apply(); 305 } 306 307 /** 308 * @return whether not to prefetch libraries (see setDontPrefetchLibrariesOnNextRun()). 309 */ isNotPrefetchingLibraries()310 private static boolean isNotPrefetchingLibraries() { 311 // This might be the first time getAppSharedPreferences() is used, so relax strict mode 312 // to allow disk reads. 313 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); 314 try { 315 return ContextUtils.getAppSharedPreferences().getBoolean( 316 DONT_PREFETCH_LIBRARIES_KEY, false); 317 } finally { 318 StrictMode.setThreadPolicy(oldPolicy); 319 } 320 } 321 322 /** Prefetches the native libraries in a background thread. 323 * 324 * Launches an AsyncTask that, through a short-lived forked process, reads a 325 * part of each page of the native library. This is done to warm up the 326 * page cache, turning hard page faults into soft ones. 327 * 328 * This is done this way, as testing shows that fadvise(FADV_WILLNEED) is 329 * detrimental to the startup time. 330 */ asyncPrefetchLibrariesToMemory()331 public void asyncPrefetchLibrariesToMemory() { 332 SysUtils.logPageFaultCountToTracing(); 333 if (isNotPrefetchingLibraries()) return; 334 335 final boolean coldStart = mPrefetchLibraryHasBeenCalled.compareAndSet(false, true); 336 337 // Collection should start close to the native library load, but doesn't have 338 // to be simultaneous with it. Also, don't prefetch in this case, as this would 339 // skew the results. 340 if (coldStart && CommandLine.getInstance().hasSwitch("log-native-library-residency")) { 341 // nativePeriodicallyCollectResidency() sleeps, run it on another thread, 342 // and not on the AsyncTask thread pool. 343 new Thread(LibraryLoader::nativePeriodicallyCollectResidency).start(); 344 return; 345 } 346 347 new LibraryPrefetchTask(coldStart).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 348 } 349 350 private static class LibraryPrefetchTask extends AsyncTask<Void, Void, Void> { 351 private final boolean mColdStart; 352 LibraryPrefetchTask(boolean coldStart)353 public LibraryPrefetchTask(boolean coldStart) { 354 mColdStart = coldStart; 355 } 356 357 @Override doInBackground(Void... params)358 protected Void doInBackground(Void... params) { 359 try (TraceEvent e = TraceEvent.scoped("LibraryLoader.asyncPrefetchLibrariesToMemory")) { 360 int percentage = nativePercentageOfResidentNativeLibraryCode(); 361 // Arbitrary percentage threshold. If most of the native library is already 362 // resident (likely with monochrome), don't bother creating a prefetch process. 363 boolean prefetch = mColdStart && percentage < 90; 364 if (prefetch) { 365 nativeForkAndPrefetchNativeLibrary(); 366 } 367 if (percentage != -1) { 368 String histogram = "LibraryLoader.PercentageOfResidentCodeBeforePrefetch" 369 + (mColdStart ? ".ColdStartup" : ".WarmStartup"); 370 RecordHistogram.recordPercentageHistogram(histogram, percentage); 371 } 372 } 373 return null; 374 } 375 } 376 377 // Helper for loadAlreadyLocked(). Load a native shared library with the Chromium linker. 378 // Sets UMA flags depending on the results of loading. 379 private void loadLibraryWithCustomLinkerAlreadyLocked( 380 Linker linker, @Nullable String zipFilePath, String libFilePath) { 381 assert Thread.holdsLock(mLock); 382 if (linker.isUsingBrowserSharedRelros()) { 383 // If the browser is set to attempt shared RELROs then we try first with shared 384 // RELROs enabled, and if that fails then retry without. 385 mIsUsingBrowserSharedRelros = true; 386 try { 387 linker.loadLibrary(libFilePath); 388 } catch (UnsatisfiedLinkError e) { 389 Log.w(TAG, "Failed to load native library with shared RELRO, retrying without"); 390 mLoadAtFixedAddressFailed = true; 391 linker.loadLibraryNoFixedAddress(libFilePath); 392 } 393 } else { 394 // No attempt to use shared RELROs in the browser, so load as normal. 395 linker.loadLibrary(libFilePath); 396 } 397 398 // Loaded successfully, so record if we loaded directly from an APK. 399 if (zipFilePath != null) { 400 mLibraryWasLoadedFromApk = true; 401 } 402 } 403 404 static void incrementRelinkerCountHitHistogram() { 405 sRelinkerCountHistogram.record(1); 406 } 407 408 static void incrementRelinkerCountNotHitHistogram() { 409 sRelinkerCountHistogram.record(0); 410 } 411 412 // Experience shows that on some devices, the system sometimes fails to extract native libraries 413 // at installation or update time from the APK. This function will extract the library and 414 // return the extracted file path. 415 static String getExtractedLibraryPath(Context appContext, String libName) { 416 assert PLATFORM_REQUIRES_NATIVE_FALLBACK_EXTRACTION; 417 Log.w(TAG, "Failed to load libName %s, attempting fallback extraction then trying again", 418 libName); 419 String libraryEntry = LibraryLoader.makeLibraryPathInZipFile(libName, false, false); 420 return extractFileIfStale(appContext, libraryEntry, makeLibraryDirAndSetPermission()); 421 } 422 423 // Invoke either Linker.loadLibrary(...), System.loadLibrary(...) or System.load(...), 424 // triggering JNI_OnLoad in native code. 425 // TODO(crbug.com/635567): Fix this properly. 426 @SuppressLint({"DefaultLocale", "NewApi", "UnsafeDynamicallyLoadedCode"}) 427 private void loadAlreadyLocked(Context appContext) throws ProcessInitException { 428 try (TraceEvent te = TraceEvent.scoped("LibraryLoader.loadAlreadyLocked")) { 429 if (!mLoaded) { 430 assert !mInitialized; 431 432 long startTime = SystemClock.uptimeMillis(); 433 434 if (useCrazyLinker()) { 435 // Load libraries using the Chromium linker. 436 Linker linker = Linker.getInstance(); 437 438 String apkFilePath = 439 isInZipFile() ? appContext.getApplicationInfo().sourceDir : null; 440 linker.prepareLibraryLoad(apkFilePath); 441 442 for (String library : NativeLibraries.LIBRARIES) { 443 // Don't self-load the linker. This is because the build system is 444 // not clever enough to understand that all the libraries packaged 445 // in the final .apk don't need to be explicitly loaded. 446 if (linker.isChromiumLinkerLibrary(library)) { 447 if (DEBUG) Log.i(TAG, "ignoring self-linker load"); 448 continue; 449 } 450 451 // Determine where the library should be loaded from. 452 String libFilePath = System.mapLibraryName(library); 453 if (apkFilePath != null) { 454 Log.i(TAG, " Loading " + library + " from within " + apkFilePath); 455 } else { 456 Log.i(TAG, "Loading " + library); 457 } 458 459 try { 460 // Load the library using this Linker. May throw UnsatisfiedLinkError. 461 loadLibraryWithCustomLinkerAlreadyLocked( 462 linker, apkFilePath, libFilePath); 463 incrementRelinkerCountNotHitHistogram(); 464 } catch (UnsatisfiedLinkError e) { 465 if (!isInZipFile() 466 && PLATFORM_REQUIRES_NATIVE_FALLBACK_EXTRACTION) { 467 loadLibraryWithCustomLinkerAlreadyLocked( 468 linker, null, getExtractedLibraryPath(appContext, library)); 469 incrementRelinkerCountHitHistogram(); 470 } else { 471 Log.e(TAG, "Unable to load library: " + library); 472 throw(e); 473 } 474 } 475 } 476 477 linker.finishLibraryLoad(); 478 } else { 479 setEnvForNative(); 480 preloadAlreadyLocked(appContext); 481 482 // If the libraries are located in the zip file, assert that the device API 483 // level is M or higher. On devices lower than M, the libraries should 484 // always be loaded by Linker. 485 assert !isInZipFile() || Build.VERSION.SDK_INT >= VERSION_CODES.M; 486 487 // Load libraries using the system linker. 488 for (String library : NativeLibraries.LIBRARIES) { 489 try { 490 if (!isInZipFile()) { 491 // The extract and retry logic isn't needed because this path is 492 // used only for local development. 493 System.loadLibrary(library); 494 } else { 495 // Load directly from the APK. 496 boolean is64Bit = Process.is64Bit(); 497 String zipFilePath = appContext.getApplicationInfo().sourceDir; 498 // In API level 23 and above, it’s possible to open a .so file 499 // directly from the APK of the path form 500 // "my_zip_file.zip!/libs/libstuff.so". See: 501 // https://android.googlesource.com/platform/bionic/+/master/android-changes-for-ndk-developers.md#opening-shared-libraries-directly-from-an-apk 502 String libraryName = zipFilePath + "!/" 503 + makeLibraryPathInZipFile(library, true, is64Bit); 504 Log.i(TAG, "libraryName: " + libraryName); 505 System.load(libraryName); 506 } 507 } catch (UnsatisfiedLinkError e) { 508 Log.e(TAG, "Unable to load library: " + library); 509 throw(e); 510 } 511 } 512 } 513 514 long stopTime = SystemClock.uptimeMillis(); 515 mLibraryLoadTimeMs = stopTime - startTime; 516 Log.i(TAG, String.format("Time to load native libraries: %d ms (timestamps %d-%d)", 517 mLibraryLoadTimeMs, 518 startTime % 10000, 519 stopTime % 10000)); 520 521 mLoaded = true; 522 } 523 } catch (UnsatisfiedLinkError e) { 524 throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_LOAD_FAILED, e); 525 } 526 } 527 528 /** 529 * @param library The library name that is looking for. 530 * @param crazyPrefix true iff adding crazy linker prefix to the file name. 531 * @param is64Bit true if the caller think it's run on a 64 bit device. 532 * @return the library path name in the zip file. 533 */ 534 @NonNull makeLibraryPathInZipFile( String library, boolean crazyPrefix, boolean is64Bit)535 public static String makeLibraryPathInZipFile( 536 String library, boolean crazyPrefix, boolean is64Bit) { 537 // Determine the ABI string that Android uses to find native libraries. Values are described 538 // in: https://developer.android.com/ndk/guides/abis.html 539 // The 'armeabi' is omitted here because it is not supported in Chrome/WebView, while Cronet 540 // and Cast load the native library via other paths. 541 String cpuAbi; 542 switch (NativeLibraries.sCpuFamily) { 543 case NativeLibraries.CPU_FAMILY_ARM: 544 cpuAbi = is64Bit ? "arm64-v8a" : "armeabi-v7a"; 545 break; 546 case NativeLibraries.CPU_FAMILY_X86: 547 cpuAbi = is64Bit ? "x86_64" : "x86"; 548 break; 549 case NativeLibraries.CPU_FAMILY_MIPS: 550 cpuAbi = is64Bit ? "mips64" : "mips"; 551 break; 552 default: 553 throw new RuntimeException("Unknown CPU ABI for native libraries"); 554 } 555 556 // When both the Chromium linker and zip-uncompressed native libraries are used, 557 // the build system renames the native shared libraries with a 'crazy.' prefix 558 // (e.g. "/lib/armeabi-v7a/libfoo.so" -> "/lib/armeabi-v7a/crazy.libfoo.so"). 559 // 560 // This prevents the package manager from extracting them at installation/update time 561 // to the /data directory. The libraries can still be accessed directly by the Chromium 562 // linker from the APK. 563 String crazyPart = crazyPrefix ? "crazy." : ""; 564 return String.format("lib/%s/%s%s", cpuAbi, crazyPart, System.mapLibraryName(library)); 565 } 566 567 // The WebView requires the Command Line to be switched over before 568 // initialization is done. This is okay in the WebView's case since the 569 // JNI is already loaded by this point. switchCommandLineForWebView()570 public void switchCommandLineForWebView() { 571 synchronized (mLock) { 572 ensureCommandLineSwitchedAlreadyLocked(); 573 } 574 } 575 576 // Switch the CommandLine over from Java to native if it hasn't already been done. 577 // This must happen after the code is loaded and after JNI is ready (since after the 578 // switch the Java CommandLine will delegate all calls the native CommandLine). ensureCommandLineSwitchedAlreadyLocked()579 private void ensureCommandLineSwitchedAlreadyLocked() { 580 assert mLoaded; 581 if (mCommandLineSwitched) { 582 return; 583 } 584 CommandLine.enableNativeProxy(); 585 mCommandLineSwitched = true; 586 } 587 588 // Invoke base::android::LibraryLoaded in library_loader_hooks.cc initializeAlreadyLocked(@ibraryProcessType int processType)589 private void initializeAlreadyLocked(@LibraryProcessType int processType) 590 throws ProcessInitException { 591 if (mInitialized) { 592 if (mLibraryProcessType != processType) { 593 throw new ProcessInitException( 594 LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_LOAD_FAILED); 595 } 596 return; 597 } 598 mLibraryProcessType = processType; 599 600 ensureCommandLineSwitchedAlreadyLocked(); 601 602 if (!nativeLibraryLoaded(mLibraryProcessType)) { 603 Log.e(TAG, "error calling nativeLibraryLoaded"); 604 throw new ProcessInitException(LoaderErrors.LOADER_ERROR_FAILED_TO_REGISTER_JNI); 605 } 606 607 // Check that the version of the library we have loaded matches the version we expect 608 Log.i(TAG, String.format("Expected native library version number \"%s\", " 609 + "actual native library version number \"%s\"", 610 NativeLibraries.sVersionNumber, nativeGetVersionNumber())); 611 if (!NativeLibraries.sVersionNumber.equals(nativeGetVersionNumber())) { 612 throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_WRONG_VERSION); 613 } 614 615 // From now on, keep tracing in sync with native. 616 TraceEvent.registerNativeEnabledObserver(); 617 618 if (processType == LibraryProcessType.PROCESS_BROWSER 619 && PLATFORM_REQUIRES_NATIVE_FALLBACK_EXTRACTION) { 620 // Perform the detection and deletion of obsolete native libraries on a background 621 // background thread. 622 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { 623 @Override 624 public void run() { 625 final String suffix = BuildInfo.getInstance().extractedFileSuffix; 626 final File[] files = getLibraryDir().listFiles(); 627 if (files == null) return; 628 629 for (File file : files) { 630 // NOTE: Do not simply look for <suffix> at the end of the file. 631 // 632 // Extracted library files have names like 'libfoo.so<suffix>', but 633 // extractFileIfStale() will use FileUtils.copyFileStreamAtomicWithBuffer() 634 // to create them, and this method actually uses a transient temporary file 635 // named like 'libfoo.so<suffix>.tmp' to do that. These temporary files, if 636 // detected here, should be preserved; hence the reason why contains() is 637 // used below. 638 if (!file.getName().contains(suffix)) { 639 String fileName = file.getName(); 640 if (!file.delete()) { 641 Log.w(TAG, "Unable to remove %s", fileName); 642 } else { 643 Log.i(TAG, "Removed obsolete file %s", fileName); 644 } 645 } 646 } 647 } 648 }); 649 } 650 651 // From this point on, native code is ready to use and checkIsReady() 652 // shouldn't complain from now on (and in fact, it's used by the 653 // following calls). 654 // Note that this flag can be accessed asynchronously, so any initialization 655 // must be performed before. 656 mInitialized = true; 657 } 658 659 // Called after all native initializations are complete. onNativeInitializationComplete()660 public void onNativeInitializationComplete() { 661 synchronized (mLock) { 662 recordBrowserProcessHistogramAlreadyLocked(); 663 } 664 } 665 666 // Record Chromium linker histogram state for the main browser process. Called from 667 // onNativeInitializationComplete(). recordBrowserProcessHistogramAlreadyLocked()668 private void recordBrowserProcessHistogramAlreadyLocked() { 669 assert Thread.holdsLock(mLock); 670 if (useCrazyLinker()) { 671 nativeRecordChromiumAndroidLinkerBrowserHistogram(mIsUsingBrowserSharedRelros, 672 mLoadAtFixedAddressFailed, 673 mLibraryWasLoadedFromApk ? LibraryLoadFromApkStatusCodes.SUCCESSFUL 674 : LibraryLoadFromApkStatusCodes.UNKNOWN, 675 mLibraryLoadTimeMs); 676 } 677 if (mLibraryPreloader != null) { 678 nativeRecordLibraryPreloaderBrowserHistogram(mLibraryPreloaderStatus); 679 } 680 } 681 682 // Register pending Chromium linker histogram state for renderer processes. This cannot be 683 // recorded as a histogram immediately because histograms and IPC are not ready at the 684 // time it are captured. This function stores a pending value, so that a later call to 685 // RecordChromiumAndroidLinkerRendererHistogram() will record it correctly. registerRendererProcessHistogram(boolean requestedSharedRelro, boolean loadAtFixedAddressFailed)686 public void registerRendererProcessHistogram(boolean requestedSharedRelro, 687 boolean loadAtFixedAddressFailed) { 688 synchronized (mLock) { 689 if (useCrazyLinker()) { 690 nativeRegisterChromiumAndroidLinkerRendererHistogram( 691 requestedSharedRelro, loadAtFixedAddressFailed, mLibraryLoadTimeMs); 692 } 693 if (mLibraryPreloader != null) { 694 nativeRegisterLibraryPreloaderRendererHistogram(mLibraryPreloaderStatus); 695 } 696 } 697 } 698 699 /** 700 * Override the library loader (normally with a mock) for testing. 701 * @param loader the mock library loader. 702 */ 703 @VisibleForTesting setLibraryLoaderForTesting(LibraryLoader loader)704 public static void setLibraryLoaderForTesting(LibraryLoader loader) { 705 sInstance = loader; 706 } 707 708 /** 709 * Configure ubsan using $UBSAN_OPTIONS. This function needs to be called before any native 710 * libraries are loaded because ubsan reads its configuration from $UBSAN_OPTIONS when the 711 * native library is loaded. 712 */ setEnvForNative()713 public static void setEnvForNative() { 714 // The setenv API was added in L. On older versions of Android, we should still see ubsan 715 // reports, but they will not have stack traces. 716 if (BuildConfig.IS_UBSAN && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 717 try { 718 // This value is duplicated in build/android/pylib/constants/__init__.py. 719 Os.setenv("UBSAN_OPTIONS", 720 "print_stacktrace=1 stack_trace_format='#%n pc %o %m' " 721 + "handle_segv=0 handle_sigbus=0 handle_sigfpe=0", 722 true); 723 } catch (Exception e) { 724 Log.w(TAG, "failed to set UBSAN_OPTIONS", e); 725 } 726 } 727 } 728 729 // Android system sometimes fails to extract libraries from APK (https://crbug.com/806998). 730 // This function manually extract libraries as a fallback. 731 @SuppressLint({"SetWorldReadable"}) extractFileIfStale( Context appContext, String pathWithinApk, File destDir)732 private static String extractFileIfStale( 733 Context appContext, String pathWithinApk, File destDir) { 734 assert PLATFORM_REQUIRES_NATIVE_FALLBACK_EXTRACTION; 735 736 String apkPath = appContext.getApplicationInfo().sourceDir; 737 String fileName = 738 (new File(pathWithinApk)).getName() + BuildInfo.getInstance().extractedFileSuffix; 739 File libraryFile = new File(destDir, fileName); 740 741 if (!libraryFile.exists()) { 742 try (ZipFile zipFile = new ZipFile(apkPath); 743 InputStream inputStream = 744 zipFile.getInputStream(zipFile.getEntry(pathWithinApk))) { 745 if (zipFile.getEntry(pathWithinApk) == null) 746 throw new RuntimeException("Cannot find ZipEntry" + pathWithinApk); 747 748 FileUtils.copyFileStreamAtomicWithBuffer( 749 inputStream, libraryFile, new byte[16 * 1024]); 750 libraryFile.setReadable(true, false); 751 libraryFile.setExecutable(true, false); 752 } catch (IOException e) { 753 throw new RuntimeException(e); 754 } 755 } 756 return libraryFile.getAbsolutePath(); 757 } 758 759 // Ensure the extracted native libraries is created with the right permissions. makeLibraryDirAndSetPermission()760 private static File makeLibraryDirAndSetPermission() { 761 if (!ContextUtils.isIsolatedProcess()) { 762 File cacheDir = ContextCompat.getCodeCacheDir(ContextUtils.getApplicationContext()); 763 File libDir = new File(cacheDir, LIBRARY_DIR); 764 cacheDir.mkdir(); 765 cacheDir.setExecutable(true, false); 766 libDir.mkdir(); 767 libDir.setExecutable(true, false); 768 } 769 return getLibraryDir(); 770 } 771 772 // Return File object for the directory containing extracted native libraries. getLibraryDir()773 private static File getLibraryDir() { 774 return new File( 775 ContextCompat.getCodeCacheDir(ContextUtils.getApplicationContext()), LIBRARY_DIR); 776 } 777 778 // Only methods needed before or during normal JNI registration are during System.OnLoad. 779 // nativeLibraryLoaded is then called to register everything else. This process is called 780 // "initialization". This method will be mapped (by generated code) to the LibraryLoaded 781 // definition in base/android/library_loader/library_loader_hooks.cc. 782 // 783 // Return true on success and false on failure. nativeLibraryLoaded(@ibraryProcessType int processType)784 private native boolean nativeLibraryLoaded(@LibraryProcessType int processType); 785 786 // Method called to record statistics about the Chromium linker operation for the main 787 // browser process. Indicates whether the linker attempted relro sharing for the browser, 788 // and if it did, whether the library failed to load at a fixed address. Also records 789 // support for loading a library directly from the APK file, and the number of milliseconds 790 // it took to load the libraries. nativeRecordChromiumAndroidLinkerBrowserHistogram( boolean isUsingBrowserSharedRelros, boolean loadAtFixedAddressFailed, int libraryLoadFromApkStatus, long libraryLoadTime)791 private native void nativeRecordChromiumAndroidLinkerBrowserHistogram( 792 boolean isUsingBrowserSharedRelros, 793 boolean loadAtFixedAddressFailed, 794 int libraryLoadFromApkStatus, 795 long libraryLoadTime); 796 797 // Method called to record the return value of NativeLibraryPreloader.loadLibrary for the main 798 // browser process. nativeRecordLibraryPreloaderBrowserHistogram(int status)799 private native void nativeRecordLibraryPreloaderBrowserHistogram(int status); 800 801 // Method called to register (for later recording) statistics about the Chromium linker 802 // operation for a renderer process. Indicates whether the linker attempted relro sharing, 803 // and if it did, whether the library failed to load at a fixed address. Also records the 804 // number of milliseconds it took to load the libraries. nativeRegisterChromiumAndroidLinkerRendererHistogram( boolean requestedSharedRelro, boolean loadAtFixedAddressFailed, long libraryLoadTime)805 private native void nativeRegisterChromiumAndroidLinkerRendererHistogram( 806 boolean requestedSharedRelro, 807 boolean loadAtFixedAddressFailed, 808 long libraryLoadTime); 809 810 // Method called to register (for later recording) the return value of 811 // NativeLibraryPreloader.loadLibrary for a renderer process. nativeRegisterLibraryPreloaderRendererHistogram(int status)812 private native void nativeRegisterLibraryPreloaderRendererHistogram(int status); 813 814 // Get the version of the native library. This is needed so that we can check we 815 // have the right version before initializing the (rest of the) JNI. nativeGetVersionNumber()816 private native String nativeGetVersionNumber(); 817 818 // Finds the ranges corresponding to the native library pages, forks a new 819 // process to prefetch these pages and waits for it. The new process then 820 // terminates. This is blocking. nativeForkAndPrefetchNativeLibrary()821 private static native void nativeForkAndPrefetchNativeLibrary(); 822 823 // Returns the percentage of the native library code page that are currently reseident in 824 // memory. nativePercentageOfResidentNativeLibraryCode()825 private static native int nativePercentageOfResidentNativeLibraryCode(); 826 827 // Periodically logs native library residency from this thread. nativePeriodicallyCollectResidency()828 private static native void nativePeriodicallyCollectResidency(); 829 } 830