Update to Chromium //base at Chromium commit 01cb97b2e09618bbc3a60c7348f0a844eea20547.
This gets us to sometime during August 24, 2015.
TBR=jamesr@chromium.org
Review URL: https://codereview.chromium.org/2043183002 .
diff --git a/BUILD.gn b/BUILD.gn
index 2709e90..c646464 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -764,6 +764,11 @@
# Windows.
if (is_win) {
+ sources += [
+ "profiler/win32_stack_frame_unwinder.cc",
+ "profiler/win32_stack_frame_unwinder.h",
+ ]
+
sources -= [
"message_loop/message_pump_libevent.cc",
"strings/string16.cc",
@@ -1247,6 +1252,7 @@
"files/file_util_proxy_unittest.cc",
"files/file_util_unittest.cc",
"files/important_file_writer_unittest.cc",
+ "files/memory_mapped_file_unittest.cc",
"files/scoped_temp_dir_unittest.cc",
"gmock_unittest.cc",
"guid_unittest.cc",
@@ -1306,6 +1312,7 @@
"metrics/field_trial_unittest.cc",
"metrics/histogram_base_unittest.cc",
"metrics/histogram_delta_serialization_unittest.cc",
+ "metrics/histogram_macros_unittest.cc",
"metrics/histogram_snapshot_manager_unittest.cc",
"metrics/histogram_unittest.cc",
"metrics/sample_map_unittest.cc",
@@ -1378,6 +1385,7 @@
"test/histogram_tester_unittest.cc",
"test/icu_test_util.cc",
"test/icu_test_util.h",
+ "test/test_pending_task_unittest.cc",
"test/test_reg_util_win_unittest.cc",
"test/trace_event_analyzer_unittest.cc",
"test/user_action_tester_unittest.cc",
diff --git a/DEPS b/DEPS
index c632e35..6d91c8d 100644
--- a/DEPS
+++ b/DEPS
@@ -4,6 +4,7 @@
"+third_party/apple_apsl",
"+third_party/libevent",
"+third_party/dmg_fp",
+ "+third_party/lss",
"+third_party/mach_override",
"+third_party/modp_b64",
"+third_party/tcmalloc",
diff --git a/allocator/BUILD.gn b/allocator/BUILD.gn
index 8944ad4..5a1e9df 100644
--- a/allocator/BUILD.gn
+++ b/allocator/BUILD.gn
@@ -4,6 +4,13 @@
import("//build/config/allocator.gni")
+declare_args() {
+ # Provide a way to force disable debugallocation in Debug builds,
+ # e.g. for profiling (it's more rare to profile Debug builds,
+ # but people sometimes need to do that).
+ enable_debugallocation = is_debug
+}
+
# Only executables and not libraries should depend on the allocator target;
# only the application (the final executable) knows what allocator makes sense.
# This "allocator" meta-target will forward to the default allocator according
@@ -32,6 +39,16 @@
}
}
+config("tcmalloc_flags") {
+ if (enable_debugallocation) {
+ defines = [
+ # Use debugallocation for Debug builds to catch problems early
+ # and cleanly, http://crbug.com/30715 .
+ "TCMALLOC_FOR_DEBUGALLOCATION",
+ ]
+ }
+}
+
# This config and libc modification are only used on Windows.
if (is_win) {
import("//build/config/win/visual_studio_version.gni")
@@ -195,7 +212,10 @@
]
configs -= [ "//build/config/compiler:chromium_code" ]
- configs += [ "//build/config/compiler:no_chromium_code" ]
+ configs += [
+ "//build/config/compiler:no_chromium_code",
+ ":tcmalloc_flags",
+ ]
deps = []
diff --git a/android/java/src/org/chromium/base/library_loader/LegacyLinker.java b/android/java/src/org/chromium/base/library_loader/LegacyLinker.java
index da5ddab..8e6776a 100644
--- a/android/java/src/org/chromium/base/library_loader/LegacyLinker.java
+++ b/android/java/src/org/chromium/base/library_loader/LegacyLinker.java
@@ -35,15 +35,10 @@
// Log tag for this class.
private static final String TAG = "cr.library_loader";
- // Name of the library that contains our JNI code.
- private static final String LINKER_JNI_LIBRARY = "chromium_android_linker";
-
// Becomes true after linker initialization.
private boolean mInitialized = false;
// Set to true if this runs in the browser process. Disabled by initServiceProcess().
- // TODO(petrcermak): This flag can be incorrectly set to false (even though this might run in
- // the browser process) on low-memory devices.
private boolean mInBrowserProcess = true;
// Becomes true to indicate this process needs to wait for a shared RELRO in
@@ -57,19 +52,26 @@
// The map of all RELRO sections either created or used in this process.
private Bundle mSharedRelros = null;
- // Current common random base load address.
- private long mBaseLoadAddress = 0;
+ // Current common random base load address. A value of -1 indicates not yet initialized.
+ private long mBaseLoadAddress = -1;
// Current fixed-location load address for the next library called by loadLibrary().
- private long mCurrentLoadAddress = 0;
+ // A value of -1 indicates not yet initialized.
+ private long mCurrentLoadAddress = -1;
// Becomes true once prepareLibraryLoad() has been called.
private boolean mPrepareLibraryLoadCalled = false;
// The map of libraries that are currently loaded in this process.
- protected HashMap<String, LibInfo> mLoadedLibraries = null;
+ private HashMap<String, LibInfo> mLoadedLibraries = null;
- // Used internally to initialize the linker's static data. Assume lock is held.
+ // Private singleton constructor, and singleton factory method.
+ private LegacyLinker() {}
+ static Linker create() {
+ return new LegacyLinker();
+ }
+
+ // Used internally to initialize the linker's data. Assume lock is held.
private void ensureInitializedLocked() {
assert Thread.holdsLock(mLock);
@@ -78,17 +80,8 @@
}
if (NativeLibraries.sUseLinker) {
- if (DEBUG) {
- Log.i(TAG, "Loading lib" + LINKER_JNI_LIBRARY + ".so");
- }
- try {
- System.loadLibrary(LINKER_JNI_LIBRARY);
- } catch (UnsatisfiedLinkError e) {
- // In a component build, the ".cr" suffix is added to each library name.
- Log.w(TAG, "Couldn't load lib" + LINKER_JNI_LIBRARY + ".so, "
- + "trying lib" + LINKER_JNI_LIBRARY + ".cr.so");
- System.loadLibrary(LINKER_JNI_LIBRARY + ".cr");
- }
+ // Load libchromium_android_linker.so.
+ loadLinkerJNILibrary();
if (mMemoryDeviceConfig == MEMORY_DEVICE_CONFIG_INIT) {
if (SysUtils.isLowEndDevice()) {
@@ -137,7 +130,9 @@
// Only GYP targets that are APKs and have the 'use_chromium_linker' variable
// defined as 1 will use this linker. For all others (the default), the
// auto-generated NativeLibraries.sUseLinker variable will be false.
- if (!NativeLibraries.sUseLinker) return false;
+ if (!NativeLibraries.sUseLinker) {
+ return false;
+ }
synchronized (mLock) {
ensureInitializedLocked();
@@ -158,15 +153,6 @@
}
/**
- * Call this method to determine if the chromium project must load
- * the library directly from the zip file.
- */
- @Override
- public boolean isInZipFile() {
- return NativeLibraries.sUseLibraryInZipFile;
- }
-
- /**
* Call this method just before loading any native shared libraries in this process.
*/
@Override
@@ -175,6 +161,7 @@
Log.i(TAG, "prepareLibraryLoad() called");
}
synchronized (mLock) {
+ ensureInitializedLocked();
mPrepareLibraryLoadCalled = true;
if (mInBrowserProcess) {
@@ -196,6 +183,7 @@
Log.i(TAG, "finishLibraryLoad() called");
}
synchronized (mLock) {
+ ensureInitializedLocked();
if (DEBUG) {
Log.i(TAG,
String.format(Locale.US,
@@ -232,7 +220,8 @@
try {
mLock.wait();
} catch (InterruptedException ie) {
- // no-op
+ // Restore the thread's interrupt status.
+ Thread.currentThread().interrupt();
}
}
useSharedRelrosLocked(mSharedRelros);
@@ -242,28 +231,9 @@
}
}
- if (NativeLibraries.sEnableLinkerTests && mTestRunnerClassName != null) {
- // The TestRunner implementation must be instantiated _after_
- // all libraries are loaded to ensure that its native methods
- // are properly registered.
- if (DEBUG) {
- Log.i(TAG, "Instantiating " + mTestRunnerClassName);
- }
- TestRunner testRunner = null;
- try {
- testRunner = (TestRunner) Class.forName(mTestRunnerClassName).newInstance();
- } catch (Exception e) {
- Log.e(TAG, "Could not extract test runner class name", e);
- testRunner = null;
- }
- if (testRunner != null) {
- if (!testRunner.runChecks(mMemoryDeviceConfig, mInBrowserProcess)) {
- Log.wtf(TAG, "Linker runtime tests failed in this process!!");
- assert false;
- } else {
- Log.i(TAG, "All linker tests passed!");
- }
- }
+ // If testing, run tests now that all libraries are loaded and initialized.
+ if (NativeLibraries.sEnableLinkerTests) {
+ runTestRunnerClassForTesting(mMemoryDeviceConfig, mInBrowserProcess);
}
}
if (DEBUG) {
@@ -276,6 +246,7 @@
* used in this process. If initServiceProcess() was previously called,
* finishLibraryLoad() will not exit until this method is called in another
* thread with a non-null value.
+ *
* @param bundle The Bundle instance containing a map of shared RELRO sections
* to use in this process.
*/
@@ -309,6 +280,7 @@
/**
* Call this to retrieve the shared RELRO sections created in this process,
* after loading all libraries.
+ *
* @return a new Bundle instance, or null if RELRO sharing is disabled on
* this system, or if initServiceProcess() was called previously.
*/
@@ -353,6 +325,7 @@
* Call this method before loading any libraries to indicate that this
* process is ready to reuse shared RELRO sections from another one.
* Typically used when starting service processes.
+ *
* @param baseLoadAddress the base library load address to use.
*/
@Override
@@ -375,6 +348,7 @@
* Retrieve the base load address of all shared RELRO sections.
* This also enforces the creation of shared RELRO sections in
* prepareLibraryLoad(), which can later be retrieved with getSharedRelros().
+ *
* @return a common, random base load address, or 0 if RELRO sharing is
* disabled.
*/
@@ -399,12 +373,11 @@
// Used internally to lazily setup the common random base load address.
private void setupBaseLoadAddressLocked() {
assert Thread.holdsLock(mLock);
- if (mBaseLoadAddress == 0) {
- long address = computeRandomBaseLoadAddress();
- mBaseLoadAddress = address;
- mCurrentLoadAddress = address;
- if (address == 0) {
- // If the computed address is 0, there are issues with finding enough
+ if (mBaseLoadAddress == -1) {
+ mBaseLoadAddress = getRandomBaseLoadAddress();
+ mCurrentLoadAddress = mBaseLoadAddress;
+ if (mBaseLoadAddress == 0) {
+ // If the random address is 0 there are issues with finding enough
// free address space, so disable RELRO shared / fixed load addresses.
Log.w(TAG, "Disabling shared RELROs due address space pressure");
mBrowserUsesSharedRelro = false;
@@ -413,35 +386,6 @@
}
}
- /**
- * Compute a random base load address at which to place loaded libraries.
- * @return new base load address, or 0 if the system does not support
- * RELRO sharing.
- */
- private long computeRandomBaseLoadAddress() {
- // nativeGetRandomBaseLoadAddress() returns an address at which it has previously
- // successfully mapped an area of the given size, on the basis that we will be
- // able, with high probability, to map our library into it.
- //
- // One issue with this is that we do not yet know the size of the library that
- // we will load is. So here we pass a value that we expect will always be larger
- // than that needed. If it is smaller the library mapping may still succeed. The
- // other issue is that although highly unlikely, there is no guarantee that
- // something else does not map into the area we are going to use between here and
- // when we try to map into it.
- //
- // The above notes mean that all of this is probablistic. It is however okay to do
- // because if, worst case and unlikely, we get unlucky in our choice of address,
- // the back-out and retry without the shared RELRO in the ChildProcessService will
- // keep things running.
- final long maxExpectedBytes = 192 * 1024 * 1024;
- final long address = nativeGetRandomBaseLoadAddress(maxExpectedBytes);
- if (DEBUG) {
- Log.i(TAG, String.format(Locale.US, "Random native base load address: 0x%x", address));
- }
- return address;
- }
-
// Used for debugging only.
private void dumpBundle(Bundle bundle) {
if (DEBUG) {
@@ -453,6 +397,7 @@
* Use the shared RELRO section from a Bundle received form another process.
* Call this after calling setBaseLoadAddress() then loading all libraries
* with loadLibrary().
+ *
* @param bundle Bundle instance generated with createSharedRelroBundle() in
* another process.
*/
@@ -504,6 +449,8 @@
}
/**
+ * Implements loading a native shared library with the Chromium linker.
+ *
* Load a native shared library with the Chromium linker. If the zip file
* is not null, the shared library must be uncompressed and page aligned
* inside the zipfile. Note the crazy linker treats libraries and files as
@@ -512,13 +459,16 @@
*
* @param zipFilePath The path of the zip file containing the library (or null).
* @param libFilePath The path of the library (possibly in the zip file).
+ * @param isFixedAddressPermitted If true, uses a fixed load address if one was
+ * supplied, otherwise ignores the fixed address and loads wherever available.
*/
@Override
- public void loadLibrary(@Nullable String zipFilePath, String libFilePath) {
+ void loadLibraryImpl(
+ @Nullable String zipFilePath, String libFilePath, boolean isFixedAddressPermitted) {
if (DEBUG) {
- Log.i(TAG, "loadLibrary: " + zipFilePath + ", " + libFilePath);
+ Log.i(TAG, "loadLibraryImpl: " + zipFilePath + ", " + libFilePath + ", "
+ + isFixedAddressPermitted);
}
-
synchronized (mLock) {
ensureInitializedLocked();
@@ -541,9 +491,19 @@
LibInfo libInfo = new LibInfo();
long loadAddress = 0;
- if ((mInBrowserProcess && mBrowserUsesSharedRelro) || mWaitForSharedRelros) {
- // Load the library at a fixed address.
- loadAddress = mCurrentLoadAddress;
+ if (isFixedAddressPermitted) {
+ if ((mInBrowserProcess && mBrowserUsesSharedRelro) || mWaitForSharedRelros) {
+ // Load the library at a fixed address.
+ loadAddress = mCurrentLoadAddress;
+
+ // For multiple libraries, ensure we stay within reservation range.
+ if (loadAddress > mBaseLoadAddress + ADDRESS_SPACE_RESERVATION) {
+ String errorMessage =
+ "Load address outside reservation, for: " + libFilePath;
+ Log.e(TAG, errorMessage);
+ throw new UnsatisfiedLinkError(errorMessage);
+ }
+ }
}
String sharedRelRoName = libFilePath;
@@ -570,9 +530,10 @@
// Where <library-name> is the library name, and <address> is the hexadecimal load
// address.
if (NativeLibraries.sEnableLinkerTests) {
- Log.i(TAG, String.format(Locale.US, "%s_LIBRARY_ADDRESS: %s %x",
- mInBrowserProcess ? "BROWSER" : "RENDERER", libFilePath,
- libInfo.mLoadAddress));
+ String tag =
+ mInBrowserProcess ? "BROWSER_LIBRARY_ADDRESS" : "RENDERER_LIBRARY_ADDRESS";
+ Log.i(TAG, String.format(
+ Locale.US, "%s: %s %x", tag, libFilePath, libInfo.mLoadAddress));
}
if (mInBrowserProcess) {
@@ -590,12 +551,13 @@
}
}
- if (mCurrentLoadAddress != 0) {
- // Compute the next current load address. If mBaseLoadAddress
+ if (loadAddress != 0 && mCurrentLoadAddress != 0) {
+ // Compute the next current load address. If mCurrentLoadAddress
// is not 0, this is an explicit library load address. Otherwise,
// this is an explicit load address for relocated RELRO sections
// only.
- mCurrentLoadAddress = libInfo.mLoadAddress + libInfo.mLoadSize;
+ mCurrentLoadAddress =
+ libInfo.mLoadAddress + libInfo.mLoadSize + BREAKPAD_GUARD_REGION_BYTES;
}
mLoadedLibraries.put(sharedRelRoName, libInfo);
@@ -606,17 +568,8 @@
}
/**
- * Determine whether a library is the linker library. Also deal with the
- * component build that adds a .cr suffix to the name.
- */
- @Override
- public boolean isChromiumLinkerLibrary(String library) {
- return library.equals(LINKER_JNI_LIBRARY) || library.equals(LINKER_JNI_LIBRARY + ".cr");
- }
-
- /**
* Move activity from the native thread to the main UI thread.
- * Called from native code on its own thread. Posts a callback from
+ * Called from native code on its own thread. Posts a callback from
* the UI thread back to native code.
*
* @param opaque Opaque argument.
@@ -634,12 +587,14 @@
/**
* Native method to run callbacks on the main UI thread.
* Supplied by the crazy linker and called by postCallbackOnMainThread.
+ *
* @param opaque Opaque crazy linker arguments.
*/
private static native void nativeRunCallbackOnUiThread(long opaque);
/**
* Native method used to load a library.
+ *
* @param library Platform specific library name (e.g. libfoo.so)
* @param loadAddress Explicit load address, or 0 for randomized one.
* @param libInfo If not null, the mLoadAddress and mLoadSize fields
@@ -651,6 +606,7 @@
/**
* Native method used to load a library which is inside a zipfile.
+ *
* @param zipfileName Filename of the zip file containing the library.
* @param library Platform specific library name (e.g. libfoo.so)
* @param loadAddress Explicit load address, or 0 for randomized one.
@@ -659,7 +615,7 @@
* @return true for success, false otherwise.
*/
private static native boolean nativeLoadLibraryInZipFile(
- String zipfileName, String libraryName, long loadAddress, LibInfo libInfo);
+ @Nullable String zipfileName, String libraryName, long loadAddress, LibInfo libInfo);
/**
* Native method used to create a shared RELRO section.
@@ -667,6 +623,7 @@
* nativeLoadLibrary(), this creates the RELRO for it. Otherwise,
* this loads a new temporary library at the specified address,
* creates and extracts the RELRO section from it, then unloads it.
+ *
* @param library Library name.
* @param loadAddress load address, which can be different from the one
* used to load the library in the current process!
@@ -679,20 +636,10 @@
/**
* Native method used to use a shared RELRO section.
+ *
* @param library Library name.
* @param libInfo A LibInfo instance containing valid RELRO information
* @return true on success.
*/
private static native boolean nativeUseSharedRelro(String library, LibInfo libInfo);
-
- /**
- * Return a random address that should be free to be mapped with the given size.
- * Maps an area of size bytes, and if successful then unmaps it and returns
- * the address of the area allocated by the system (with ASLR). The idea is
- * that this area should remain free of other mappings until we map our library
- * into it.
- * @param sizeBytes Size of area in bytes to search for.
- * @return address to pass to future mmap, or 0 on error.
- */
- private static native long nativeGetRandomBaseLoadAddress(long sizeBytes);
}
diff --git a/android/java/src/org/chromium/base/library_loader/LibraryLoader.java b/android/java/src/org/chromium/base/library_loader/LibraryLoader.java
index e4130ba..e581300 100644
--- a/android/java/src/org/chromium/base/library_loader/LibraryLoader.java
+++ b/android/java/src/org/chromium/base/library_loader/LibraryLoader.java
@@ -186,7 +186,33 @@
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
- // Invoke System.loadLibrary(...), triggering JNI_OnLoad in native code
+ // Helper for loadAlreadyLocked(). Load a native shared library with the Chromium linker.
+ // Sets UMA flags depending on the results of loading.
+ private void loadLibrary(Linker linker, @Nullable String zipFilePath, String libFilePath) {
+ if (linker.isUsingBrowserSharedRelros()) {
+ // If the browser is set to attempt shared RELROs then we try first with shared
+ // RELROs enabled, and if that fails then retry without.
+ mIsUsingBrowserSharedRelros = true;
+ try {
+ linker.loadLibrary(zipFilePath, libFilePath);
+ } catch (UnsatisfiedLinkError e) {
+ Log.w(TAG, "Failed to load native library with shared RELRO, retrying without");
+ mLoadAtFixedAddressFailed = true;
+ linker.loadLibraryNoFixedAddress(zipFilePath, libFilePath);
+ }
+ } else {
+ // No attempt to use shared RELROs in the browser, so load as normal.
+ linker.loadLibrary(zipFilePath, libFilePath);
+ }
+
+ // Loaded successfully, so record if we loaded directly from an APK.
+ if (zipFilePath != null) {
+ mLibraryWasLoadedFromApk = true;
+ }
+ }
+
+ // Invoke either Linker.loadLibrary(...) or System.loadLibrary(...), triggering
+ // JNI_OnLoad in native code
private void loadAlreadyLocked(Context context) throws ProcessInitException {
try {
if (!mLoaded) {
@@ -217,30 +243,14 @@
if (linker.isInZipFile()) {
// Load directly from the APK.
zipFilePath = apkFilePath;
- Log.i(TAG,
- "Loading " + library + " directly from within " + apkFilePath);
+ Log.i(TAG, "Loading " + library + " from within " + apkFilePath);
} else {
// The library is in its own file.
Log.i(TAG, "Loading " + library);
}
- // Load the library.
- boolean isLoaded = false;
- if (linker.isUsingBrowserSharedRelros()) {
- mIsUsingBrowserSharedRelros = true;
- try {
- loadLibrary(zipFilePath, libFilePath);
- isLoaded = true;
- } catch (UnsatisfiedLinkError e) {
- Log.w(TAG, "Failed to load native library with shared RELRO, "
- + "retrying without");
- linker.disableSharedRelros();
- mLoadAtFixedAddressFailed = true;
- }
- }
- if (!isLoaded) {
- loadLibrary(zipFilePath, libFilePath);
- }
+ // Load the library using this Linker. May throw UnsatisfiedLinkError.
+ loadLibrary(linker, zipFilePath, libFilePath);
}
linker.finishLibraryLoad();
@@ -295,15 +305,6 @@
return appInfo.sourceDir;
}
- // Load a native shared library with the Chromium linker. If the zip file
- // path is not null, the library is loaded directly from the zip file.
- private void loadLibrary(@Nullable String zipFilePath, String libFilePath) {
- Linker.getInstance().loadLibrary(zipFilePath, libFilePath);
- if (zipFilePath != null) {
- mLibraryWasLoadedFromApk = true;
- }
- }
-
// The WebView requires the Command Line to be switched over before
// initialization is done. This is okay in the WebView's case since the
// JNI is already loaded by this point.
diff --git a/android/java/src/org/chromium/base/library_loader/Linker.java b/android/java/src/org/chromium/base/library_loader/Linker.java
index c769339..ee62570 100644
--- a/android/java/src/org/chromium/base/library_loader/Linker.java
+++ b/android/java/src/org/chromium/base/library_loader/Linker.java
@@ -56,7 +56,7 @@
*
* - Whether the browser process loads its native libraries at the same
* addresses as the service ones (to save RAM by sharing the RELRO too)
- * depends on the configuration variable BROWSER_SHARED_RELRO_CONFIG below.
+ * depends on the configuration variable BROWSER_SHARED_RELRO_CONFIG.
*
* Not using fixed library addresses in the browser process is preferred
* for regular devices since it maintains the efficacy of ASLR as an
@@ -67,20 +67,14 @@
* it read-write (e.g. by calling mmap() or mprotect()) and modify its
* content, altering values seen in other service processes.
*
- * - Unfortunately, certain Android systems use an old, buggy kernel, that
- * doesn't check Ashmem region permissions correctly. See CVE-2011-1149
- * for details. This linker probes the system on startup and will completely
- * disable shared RELROs if it detects the problem. For the record, this is
- * common for Android emulator system images (which are still based on 2.6.29)
- *
- * - Once the RELRO ashmem region is mapped into a service process' address
- * space, the corresponding file descriptor is immediately closed. The
+ * - Once the RELRO ashmem region or file is mapped into a service process's
+ * address space, the corresponding file descriptor is immediately closed. The
* file descriptor is kept opened in the browser process, because a copy needs
* to be sent to each new potential service process.
*
* - The common library load addresses are randomized for each instance of
- * the program on the device. See computeRandomBaseLoadAddress() for more
- * details on how this is computed.
+ * the program on the device. See getRandomBaseLoadAddress() for more
+ * details on how this is obtained.
*
* - When loading several libraries in service processes, a simple incremental
* approach from the original random base load address is used. This is
@@ -93,8 +87,8 @@
* Here's an explanation of how this class is supposed to be used:
*
* - Native shared libraries should be loaded with Linker.loadLibrary(),
- * instead of System.loadLibrary(). The two functions take the same parameter
- * and should behave the same (at a high level).
+ * instead of System.loadLibrary(). The two functions should behave the same
+ * (at a high level).
*
* - Before loading any library, prepareLibraryLoad() should be called.
*
@@ -143,9 +137,9 @@
* a Binder call (note that the Bundle includes file descriptors and cannot
* be added as an Intent extra).
*
- * - In a service process, finishLibraryLoad() will block until the RELRO
- * section Bundle is received. This is typically done by calling
- * useSharedRelros() from another thread.
+ * - In a service process, finishLibraryLoad() and/or loadLibrary() may
+ * block until the RELRO section Bundle is received. This is typically
+ * done by calling useSharedRelros() from another thread.
*
* This method also ensures the process uses the shared RELROs.
*/
@@ -153,6 +147,41 @@
// Log tag for this class.
private static final String TAG = "cr.library_loader";
+ // Constants used to control the behaviour of the browser process with
+ // regards to the shared RELRO section. Not applicable to ModernLinker.
+ // NEVER -> The browser never uses it itself.
+ // LOW_RAM_ONLY -> It is only used on devices with low RAM.
+ // ALWAYS -> It is always used.
+ // NOTE: These names are known and expected by the Linker test scripts.
+ public static final int BROWSER_SHARED_RELRO_CONFIG_NEVER = 0;
+ public static final int BROWSER_SHARED_RELRO_CONFIG_LOW_RAM_ONLY = 1;
+ public static final int BROWSER_SHARED_RELRO_CONFIG_ALWAYS = 2;
+
+ // Configuration variable used to control how the browser process uses the
+ // shared RELRO. Only change this while debugging linker-related issues.
+ // Not used by ModernLinker.
+ // NOTE: This variable's name is known and expected by the Linker test scripts.
+ public static final int BROWSER_SHARED_RELRO_CONFIG =
+ BROWSER_SHARED_RELRO_CONFIG_LOW_RAM_ONLY;
+
+ // Constants used to control the memory device config. Can be set explicitly
+ // by setMemoryDeviceConfigForTesting(). Not applicable to ModernLinker.
+ // INIT -> Value is undetermined (will check at runtime).
+ // LOW -> This is a low-memory device.
+ // NORMAL -> This is not a low-memory device.
+ public static final int MEMORY_DEVICE_CONFIG_INIT = 0;
+ public static final int MEMORY_DEVICE_CONFIG_LOW = 1;
+ public static final int MEMORY_DEVICE_CONFIG_NORMAL = 2;
+
+ // Indicates if this is a low-memory device or not. The default is to
+ // determine this by probing the system at runtime, but this can be forced
+ // for testing by calling setMemoryDeviceConfigForTesting().
+ // Not used by ModernLinker.
+ protected int mMemoryDeviceConfig = MEMORY_DEVICE_CONFIG_INIT;
+
+ // Name of the library that contains our JNI code.
+ protected static final String LINKER_JNI_LIBRARY = "chromium_android_linker";
+
// Set to true to enable debug logs.
protected static final boolean DEBUG = false;
@@ -163,34 +192,26 @@
// Guards all access to the linker.
protected final Object mLock = new Object();
- // Constants used to control the behaviour of the browser process with
- // regards to the shared RELRO section.
- // NEVER -> The browser never uses it itself.
- // LOW_RAM_ONLY -> It is only used on devices with low RAM.
- // ALWAYS -> It is always used.
+ // The name of a class that implements TestRunner.
+ private String mTestRunnerClassName = null;
+
+ // Size of reserved Breakpad guard region. Should match the value of
+ // kBreakpadGuardRegionBytes on the JNI side. Used when computing the load
+ // addresses of multiple loaded libraries. Set to 0 to disable the guard.
+ protected static final int BREAKPAD_GUARD_REGION_BYTES = 16 * 1024 * 1024;
+
+ // Size of the area requested when using ASLR to obtain a random load address.
+ // Should match the value of kAddressSpaceReservationSize on the JNI side.
+ // Used when computing the load addresses of multiple loaded libraries to
+ // ensure that we don't try to load outside the area originally requested.
+ protected static final int ADDRESS_SPACE_RESERVATION = 192 * 1024 * 1024;
+
+ // Constants used to indicate a given Linker implementation, for testing.
+ // LEGACY -> Always uses the LegacyLinker implementation.
+ // MODERN -> Always uses the ModernLinker implementation.
// NOTE: These names are known and expected by the Linker test scripts.
- public static final int BROWSER_SHARED_RELRO_CONFIG_NEVER = 0;
- public static final int BROWSER_SHARED_RELRO_CONFIG_LOW_RAM_ONLY = 1;
- public static final int BROWSER_SHARED_RELRO_CONFIG_ALWAYS = 2;
-
- // Configuration variable used to control how the browser process uses the
- // shared RELRO. Only change this while debugging linker-related issues.
- // NOTE: This variable's name is known and expected by the Linker test scripts.
- public static final int BROWSER_SHARED_RELRO_CONFIG =
- BROWSER_SHARED_RELRO_CONFIG_LOW_RAM_ONLY;
-
- // Constants used to control the value of sMemoryDeviceConfig.
- // INIT -> Value is undetermined (will check at runtime).
- // LOW -> This is a low-memory device.
- // NORMAL -> This is not a low-memory device.
- public static final int MEMORY_DEVICE_CONFIG_INIT = 0;
- public static final int MEMORY_DEVICE_CONFIG_LOW = 1;
- public static final int MEMORY_DEVICE_CONFIG_NORMAL = 2;
-
- // Indicates if this is a low-memory device or not. The default is to
- // determine this by probing the system at runtime, but this can be forced
- // for testing by calling setMemoryDeviceConfig().
- protected int mMemoryDeviceConfig = MEMORY_DEVICE_CONFIG_INIT;
+ public static final int LINKER_IMPLEMENTATION_LEGACY = 1;
+ public static final int LINKER_IMPLEMENTATION_MODERN = 2;
// Singleton.
private static Linker sSingleton = null;
@@ -199,19 +220,111 @@
// Protected singleton constructor.
protected Linker() {}
- // Get singleton instance.
+ /**
+ * Get singleton instance. Returns either a LegacyLinker or a ModernLinker.
+ *
+ * @return the Linker implementation instance.
+ */
public static final Linker getInstance() {
synchronized (sSingletonLock) {
if (sSingleton == null) {
- // TODO(simonb): Extend later to return either a LegacyLinker
- // or a ModernLinker instance.
- sSingleton = new LegacyLinker();
+ // TODO(simonb): Check version once the Android M build version
+ // code becomes available.
+ // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.<ANDROID-M>) {
+ if (false) {
+ sSingleton = ModernLinker.create();
+ } else {
+ sSingleton = LegacyLinker.create();
+ }
+ Log.i(TAG, "Using linker: " + sSingleton.getClass().getName());
}
return sSingleton;
}
}
/**
+ * Check that native library linker tests are enabled.
+ * If not enabled, calls to testing functions will fail with an assertion
+ * error.
+ *
+ * @return true if native library linker tests are enabled.
+ */
+ public static boolean areLinkerTestsEnabled() {
+ return NativeLibraries.sEnableLinkerTests;
+ }
+
+ /**
+ * Assert for testing.
+ * Hard assertion. Cannot be disabled. Used only by testing methods.
+ */
+ private static void assertForTesting(boolean flag) {
+ if (!flag) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Assert NativeLibraries.sEnableLinkerTests is true.
+ * Hard assertion that we are in a testing context. Cannot be disabled. The
+ * test methods in this module permit injection of runnable code by class
+ * name. To protect against both malicious and accidental use of these
+ * methods, we ensure that NativeLibraries.sEnableLinkerTests is true when
+ * any is called.
+ */
+ private static void assertLinkerTestsAreEnabled() {
+ if (!NativeLibraries.sEnableLinkerTests) {
+ throw new AssertionError("Testing method called in non-testing context");
+ }
+ }
+
+ /**
+ * Set Linker implementation type.
+ * For testing. Sets either a LegacyLinker or a ModernLinker. Must be called
+ * before getInstance().
+ *
+ * @param type LINKER_IMPLEMENTATION_LEGACY or LINKER_IMPLEMENTATION_MODERN
+ */
+ public static final void setLinkerImplementationForTesting(int type) {
+ // Sanity check. This method may only be called during tests.
+ assertLinkerTestsAreEnabled();
+ assertForTesting(
+ type == LINKER_IMPLEMENTATION_LEGACY || type == LINKER_IMPLEMENTATION_MODERN);
+
+ synchronized (sSingletonLock) {
+ assertForTesting(sSingleton == null);
+
+ if (type == LINKER_IMPLEMENTATION_MODERN) {
+ sSingleton = ModernLinker.create();
+ } else if (type == LINKER_IMPLEMENTATION_LEGACY) {
+ sSingleton = LegacyLinker.create();
+ } else {
+ return;
+ }
+ Log.i(TAG, "Forced linker: " + sSingleton.getClass().getName());
+ }
+ }
+
+ /**
+ * Get Linker implementation type.
+ * For testing.
+ *
+ * @return LINKER_IMPLEMENTATION_LEGACY or LINKER_IMPLEMENTATION_MODERN
+ */
+ public int getLinkerImplementationForTesting() {
+ // Sanity check. This method may only be called during tests.
+ assertLinkerTestsAreEnabled();
+ assertForTesting(sSingleton != null);
+
+ if (sSingleton instanceof ModernLinker) {
+ return LINKER_IMPLEMENTATION_MODERN;
+ } else if (sSingleton instanceof LegacyLinker) {
+ return LINKER_IMPLEMENTATION_LEGACY;
+ }
+ Log.e(TAG, "Invalid linker: " + sSingleton.getClass().getName());
+ return 0;
+ }
+
+ /**
* A public interface used to run runtime linker tests after loading
* libraries. Should only be used to implement the linker unit tests,
* which is controlled by the value of NativeLibraries.sEnableLinkerTests
@@ -220,32 +333,30 @@
public interface TestRunner {
/**
* Run runtime checks and return true if they all pass.
+ *
* @param memoryDeviceConfig The current memory device configuration.
* @param inBrowserProcess true iff this is the browser process.
+ * @return true if all checks pass.
*/
public boolean runChecks(int memoryDeviceConfig, boolean inBrowserProcess);
}
- // The name of a class that implements TestRunner.
- String mTestRunnerClassName = null;
-
/**
* Set the TestRunner by its class name. It will be instantiated at
* runtime after all libraries are loaded.
+ *
* @param testRunnerClassName null or a String for the class name of the
* TestRunner to use.
*/
- public void setTestRunnerClassName(String testRunnerClassName) {
+ public void setTestRunnerClassNameForTesting(String testRunnerClassName) {
if (DEBUG) {
- Log.i(TAG, "setTestRunnerByClassName(" + testRunnerClassName + ") called");
+ Log.i(TAG, "setTestRunnerByClassNameForTesting(" + testRunnerClassName + ") called");
}
- if (!NativeLibraries.sEnableLinkerTests) {
- // Ignore this in production code to prevent malevolent runtime injection.
- return;
- }
+ // Sanity check. This method may only be called during tests.
+ assertLinkerTestsAreEnabled();
synchronized (mLock) {
- assert mTestRunnerClassName == null;
+ assertForTesting(mTestRunnerClassName == null);
mTestRunnerClassName = testRunnerClassName;
}
}
@@ -254,42 +365,193 @@
* Call this to retrieve the name of the current TestRunner class name
* if any. This can be useful to pass it from the browser process to
* child ones.
+ *
* @return null or a String holding the name of the class implementing
- * the TestRunner set by calling setTestRunnerClassName() previously.
+ * the TestRunner set by calling setTestRunnerClassNameForTesting() previously.
*/
- public String getTestRunnerClassName() {
+ public String getTestRunnerClassNameForTesting() {
+ // Sanity check. This method may only be called during tests.
+ assertLinkerTestsAreEnabled();
+
synchronized (mLock) {
return mTestRunnerClassName;
}
}
/**
+ * Instantiate and run the current TestRunner, if any. The TestRunner implementation
+ * must be instantiated _after_ all libraries are loaded to ensure that its
+ * native methods are properly registered.
+ *
+ * @param memoryDeviceConfig LegacyLinker memory config, or 0 if unused
+ * @param inBrowserProcess true if in the browser process
+ */
+ protected void runTestRunnerClassForTesting(int memoryDeviceConfig, boolean inBrowserProcess) {
+ if (DEBUG) {
+ Log.i(TAG, "runTestRunnerClassForTesting called");
+ }
+ // Sanity check. This method may only be called during tests.
+ assertLinkerTestsAreEnabled();
+
+ synchronized (mLock) {
+ if (mTestRunnerClassName == null) {
+ return;
+ }
+ if (DEBUG) {
+ Log.i(TAG, "Instantiating " + mTestRunnerClassName);
+ }
+ TestRunner testRunner = null;
+ try {
+ testRunner = (TestRunner) Class.forName(mTestRunnerClassName).newInstance();
+ } catch (Exception e) {
+ Log.e(TAG, "Could not instantiate test runner class by name", e);
+ testRunner = null;
+ }
+ if (testRunner != null) {
+ if (!testRunner.runChecks(memoryDeviceConfig, inBrowserProcess)) {
+ Log.wtf(TAG, "Linker runtime tests failed in this process!!");
+ assertForTesting(false);
+ } else {
+ Log.i(TAG, "All linker tests passed!");
+ }
+ }
+ }
+ }
+
+ /**
* Call this method before any other Linker method to force a specific
* memory device configuration. Should only be used for testing.
- * @param memoryDeviceConfig either MEMORY_DEVICE_CONFIG_LOW or MEMORY_DEVICE_CONFIG_NORMAL.
+ *
+ * @param memoryDeviceConfig MEMORY_DEVICE_CONFIG_LOW or MEMORY_DEVICE_CONFIG_NORMAL.
*/
- public void setMemoryDeviceConfig(int memoryDeviceConfig) {
+ public void setMemoryDeviceConfigForTesting(int memoryDeviceConfig) {
if (DEBUG) {
- Log.i(TAG, "setMemoryDeviceConfig(" + memoryDeviceConfig + ") called");
+ Log.i(TAG, "setMemoryDeviceConfigForTesting(" + memoryDeviceConfig + ") called");
}
- // Sanity check. This method should only be called during tests.
- assert NativeLibraries.sEnableLinkerTests;
+ // Sanity check. This method may only be called during tests.
+ assertLinkerTestsAreEnabled();
+ assertForTesting(memoryDeviceConfig == MEMORY_DEVICE_CONFIG_LOW
+ || memoryDeviceConfig == MEMORY_DEVICE_CONFIG_NORMAL);
+
synchronized (mLock) {
- assert mMemoryDeviceConfig == MEMORY_DEVICE_CONFIG_INIT;
- assert memoryDeviceConfig == MEMORY_DEVICE_CONFIG_LOW
- || memoryDeviceConfig == MEMORY_DEVICE_CONFIG_NORMAL;
+ assertForTesting(mMemoryDeviceConfig == MEMORY_DEVICE_CONFIG_INIT);
+
+ mMemoryDeviceConfig = memoryDeviceConfig;
if (DEBUG) {
- if (memoryDeviceConfig == MEMORY_DEVICE_CONFIG_LOW) {
+ if (mMemoryDeviceConfig == MEMORY_DEVICE_CONFIG_LOW) {
Log.i(TAG, "Simulating a low-memory device");
} else {
Log.i(TAG, "Simulating a regular-memory device");
}
}
- mMemoryDeviceConfig = memoryDeviceConfig;
}
}
/**
+ * Determine whether a library is the linker library. Also deal with the
+ * component build that adds a .cr suffix to the name.
+ *
+ * @param library the name of the library.
+ * @return true is the library is the Linker's own JNI library.
+ */
+ public boolean isChromiumLinkerLibrary(String library) {
+ return library.equals(LINKER_JNI_LIBRARY) || library.equals(LINKER_JNI_LIBRARY + ".cr");
+ }
+
+ /**
+ * Load the Linker JNI library. Throws UnsatisfiedLinkError on error.
+ * In a component build, the suffix ".cr" is added to each library name, so
+ * if the initial load fails we retry with a suffix.
+ */
+ protected void loadLinkerJNILibrary() {
+ String lib_name = "lib" + LINKER_JNI_LIBRARY + ".so";
+ if (DEBUG) {
+ Log.i(TAG, "Loading " + lib_name);
+ }
+ try {
+ System.loadLibrary(LINKER_JNI_LIBRARY);
+ } catch (UnsatisfiedLinkError e) {
+ Log.w(TAG, "Couldn't load " + lib_name + ", trying " + lib_name + ".so");
+ System.loadLibrary(LINKER_JNI_LIBRARY + ".cr");
+ }
+ }
+
+ /**
+ * Obtain a random base load address at which to place loaded libraries.
+ *
+ * @return new base load address
+ */
+ protected long getRandomBaseLoadAddress() {
+ // nativeGetRandomBaseLoadAddress() returns an address at which it has previously
+ // successfully mapped an area larger than the largest library we expect to load,
+ // on the basis that we will be able, with high probability, to map our library
+ // into it.
+ //
+ // One issue with this is that we do not yet know the size of the library that
+ // we will load is. If it is smaller than the size we used to obtain a random
+ // address the library mapping may still succeed. The other issue is that
+ // although highly unlikely, there is no guarantee that something else does not
+ // map into the area we are going to use between here and when we try to map into it.
+ //
+ // The above notes mean that all of this is probablistic. It is however okay to do
+ // because if, worst case and unlikely, we get unlucky in our choice of address,
+ // the back-out and retry without the shared RELRO in the ChildProcessService will
+ // keep things running.
+ final long address = nativeGetRandomBaseLoadAddress();
+ if (DEBUG) {
+ Log.i(TAG, String.format(Locale.US, "Random native base load address: 0x%x", address));
+ }
+ return address;
+ }
+
+ /**
+ * Load a native shared library with the Chromium linker. If the zip file
+ * is not null, the shared library must be uncompressed and page aligned
+ * inside the zipfile. Note the crazy linker treats libraries and files as
+ * equivalent, so you can only open one library in a given zip file. The
+ * library must not be the Chromium linker library.
+ *
+ * @param zipFilePath The path of the zip file containing the library (or null).
+ * @param libFilePath The path of the library (possibly in the zip file).
+ */
+ public void loadLibrary(@Nullable String zipFilePath, String libFilePath) {
+ if (DEBUG) {
+ Log.i(TAG, "loadLibrary: " + zipFilePath + ", " + libFilePath);
+ }
+ final boolean isFixedAddressPermitted = true;
+ loadLibraryImpl(zipFilePath, libFilePath, isFixedAddressPermitted);
+ }
+
+ /**
+ * Load a native shared library with the Chromium linker, ignoring any
+ * requested fixed address for RELRO sharing. If the zip file
+ * is not null, the shared library must be uncompressed and page aligned
+ * inside the zipfile. Note the crazy linker treats libraries and files as
+ * equivalent, so you can only open one library in a given zip file. The
+ * library must not be the Chromium linker library.
+ *
+ * @param zipFilePath The path of the zip file containing the library (or null).
+ * @param libFilePath The path of the library (possibly in the zip file).
+ */
+ public void loadLibraryNoFixedAddress(@Nullable String zipFilePath, String libFilePath) {
+ if (DEBUG) {
+ Log.i(TAG, "loadLibraryAtAnyAddress: " + zipFilePath + ", " + libFilePath);
+ }
+ final boolean isFixedAddressPermitted = false;
+ loadLibraryImpl(zipFilePath, libFilePath, isFixedAddressPermitted);
+ }
+
+ /**
+ * Call this method to determine if the chromium project must load the library
+ * directly from a zip file.
+ */
+ public boolean isInZipFile() {
+ // The auto-generated NativeLibraries.sUseLibraryInZipFile variable will be true
+ // if the library remains embedded in the APK zip file on the target.
+ return NativeLibraries.sUseLibraryInZipFile;
+ }
+
+ /**
* Call this method to determine if this chromium project must
* use this linker. If not, System.loadLibrary() should be used to load
* libraries instead.
@@ -303,20 +565,12 @@
public abstract boolean isUsingBrowserSharedRelros();
/**
- * Call this method to determine if the chromium project must load
- * the library directly from the zip file.
- */
- public abstract boolean isInZipFile();
-
- /**
* Call this method just before loading any native shared libraries in this process.
*/
public abstract void prepareLibraryLoad();
/**
* Call this method just after loading all native shared libraries in this process.
- * Note that when in a service process, this will block until the RELRO bundle is
- * received, i.e. when another thread calls useSharedRelros().
*/
public abstract void finishLibraryLoad();
@@ -325,6 +579,7 @@
* used in this process. If initServiceProcess() was previously called,
* finishLibraryLoad() will not exit until this method is called in another
* thread with a non-null value.
+ *
* @param bundle The Bundle instance containing a map of shared RELRO sections
* to use in this process.
*/
@@ -333,6 +588,7 @@
/**
* Call this to retrieve the shared RELRO sections created in this process,
* after loading all libraries.
+ *
* @return a new Bundle instance, or null if RELRO sharing is disabled on
* this system, or if initServiceProcess() was called previously.
*/
@@ -348,6 +604,7 @@
* Call this method before loading any libraries to indicate that this
* process is ready to reuse shared RELRO sections from another one.
* Typically used when starting service processes.
+ *
* @param baseLoadAddress the base library load address to use.
*/
public abstract void initServiceProcess(long baseLoadAddress);
@@ -356,34 +613,28 @@
* Retrieve the base load address of all shared RELRO sections.
* This also enforces the creation of shared RELRO sections in
* prepareLibraryLoad(), which can later be retrieved with getSharedRelros().
+ *
* @return a common, random base load address, or 0 if RELRO sharing is
* disabled.
*/
public abstract long getBaseLoadAddress();
/**
- * Load a native shared library with the Chromium linker. If the zip file
- * is not null, the shared library must be uncompressed and page aligned
- * inside the zipfile. Note the crazy linker treats libraries and files as
- * equivalent, so you can only open one library in a given zip file. The
- * library must not be the Chromium linker library.
+ * Implements loading a native shared library with the Chromium linker.
*
* @param zipFilePath The path of the zip file containing the library (or null).
* @param libFilePath The path of the library (possibly in the zip file).
+ * @param isFixedAddressPermitted If true, uses a fixed load address if one was
+ * supplied, otherwise ignores the fixed address and loads wherever available.
*/
- public abstract void loadLibrary(@Nullable String zipFilePath, String libFilePath);
-
- /**
- * Determine whether a library is the linker library. Also deal with the
- * component build that adds a .cr suffix to the name.
- */
- public abstract boolean isChromiumLinkerLibrary(String library);
+ abstract void loadLibraryImpl(
+ @Nullable String zipFilePath, String libFilePath, boolean isFixedAddressPermitted);
/**
* Record information for a given library.
* IMPORTANT: Native code knows about this class's fields, so
* don't change them without modifying the corresponding C++ sources.
- * Also, the LibInfo instance owns the ashmem file descriptor.
+ * Also, the LibInfo instance owns the shared RELRO file descriptor.
*/
public static class LibInfo implements Parcelable {
@@ -432,7 +683,7 @@
fd.writeToParcel(out, 0);
fd.close();
} catch (java.io.IOException e) {
- Log.e(TAG, "Cant' write LibInfo file descriptor to parcel", e);
+ Log.e(TAG, "Can't write LibInfo file descriptor to parcel", e);
}
}
}
@@ -479,7 +730,7 @@
@AccessedByNative
public long mRelroSize; // page-aligned size in memory, or 0.
@AccessedByNative
- public int mRelroFd; // ashmem file descriptor, or -1
+ public int mRelroFd; // shared RELRO file descriptor, or -1
}
// Create a Bundle from a map of LibInfo objects.
@@ -488,7 +739,6 @@
for (Map.Entry<String, LibInfo> entry : map.entrySet()) {
bundle.putParcelable(entry.getKey(), entry.getValue());
}
-
return bundle;
}
@@ -508,4 +758,15 @@
entry.getValue().close();
}
}
+
+ /**
+ * Return a random address that should be free to be mapped with the given size.
+ * Maps an area large enough for the largest library we might attempt to load,
+ * and if successful then unmaps it and returns the address of the area allocated
+ * by the system (with ASLR). The idea is that this area should remain free of
+ * other mappings until we map our library into it.
+ *
+ * @return address to pass to future mmap, or 0 on error.
+ */
+ private static native long nativeGetRandomBaseLoadAddress();
}
diff --git a/android/java/src/org/chromium/base/library_loader/ModernLinker.java b/android/java/src/org/chromium/base/library_loader/ModernLinker.java
new file mode 100644
index 0000000..9bbdaba
--- /dev/null
+++ b/android/java/src/org/chromium/base/library_loader/ModernLinker.java
@@ -0,0 +1,504 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.library_loader;
+
+import android.os.Bundle;
+import android.os.SystemClock;
+
+import org.chromium.base.Log;
+import org.chromium.base.PathUtils;
+
+import java.util.HashMap;
+import java.util.Locale;
+
+import javax.annotation.Nullable;
+
+/*
+ * For more, see Technical note, Security considerations, and the explanation
+ * of how this class is supposed to be used in Linker.java.
+ */
+
+/**
+ * Provides a concrete implementation of the Chromium Linker.
+ *
+ * This Linker implementation uses the Android M and later system linker to map and then
+ * run Chrome for Android.
+ *
+ * For more on the operations performed by the Linker, see {@link Linker}.
+ */
+class ModernLinker extends Linker {
+ // Log tag for this class.
+ private static final String TAG = "cr.library_loader";
+
+ // Becomes true after linker initialization.
+ private boolean mInitialized = false;
+
+ // Becomes true to indicate this process needs to wait for a shared RELRO in LibraryLoad().
+ private boolean mWaitForSharedRelros = false;
+
+ // The map of all RELRO sections either created or used in this process.
+ private HashMap<String, LibInfo> mSharedRelros = null;
+
+ // Cached Bundle representation of the RELRO sections map for transfer across processes.
+ private Bundle mSharedRelrosBundle = null;
+
+ // Set to true if this runs in the browser process. Disabled by initServiceProcess().
+ private boolean mInBrowserProcess = true;
+
+ // Current common random base load address. A value of -1 indicates not yet initialized.
+ private long mBaseLoadAddress = -1;
+
+ // Current fixed-location load address for the next library called by loadLibrary().
+ // Initialized to mBaseLoadAddress in prepareLibraryLoad(), and then adjusted as each
+ // library is loaded by loadLibrary().
+ private long mCurrentLoadAddress = -1;
+
+ // Becomes true once prepareLibraryLoad() has been called.
+ private boolean mPrepareLibraryLoadCalled = false;
+
+ // The map of libraries that are currently loaded in this process.
+ private HashMap<String, LibInfo> mLoadedLibraries = null;
+
+ // The directory used to hold shared RELRO data files. Set up by prepareLibraryLoad().
+ private String mDataDirectory = null;
+
+ // Private singleton constructor, and singleton factory method.
+ private ModernLinker() {}
+ static Linker create() {
+ return new ModernLinker();
+ }
+
+ // Used internally to initialize the linker's data. Assume lock is held.
+ private void ensureInitializedLocked() {
+ assert Thread.holdsLock(mLock);
+ assert NativeLibraries.sUseLinker;
+
+ // On first call, load libchromium_android_linker.so.
+ if (!mInitialized) {
+ loadLinkerJNILibrary();
+ mInitialized = true;
+ }
+ }
+
+ /**
+ * Call this method to determine if this chromium project must
+ * use this linker. If not, System.loadLibrary() should be used to load
+ * libraries instead.
+ */
+ @Override
+ public boolean isUsed() {
+ // Only GYP targets that are APKs and have the 'use_chromium_linker' variable
+ // defined as 1 will use this linker. For all others (the default), the
+ // auto-generated NativeLibraries.sUseLinker variable will be false.
+ return NativeLibraries.sUseLinker;
+ }
+
+ /**
+ * Call this method to determine if the linker will try to use shared RELROs
+ * for the browser process.
+ */
+ @Override
+ public boolean isUsingBrowserSharedRelros() {
+ // This Linker does not attempt to share RELROS between the browser and
+ // the renderers, but only between renderers.
+ return false;
+ }
+
+ /**
+ * Call this method just before loading any native shared libraries in this process.
+ * Loads the Linker's JNI library, and initializes the variables involved in the
+ * implementation of shared RELROs.
+ */
+ @Override
+ public void prepareLibraryLoad() {
+ if (DEBUG) {
+ Log.i(TAG, "prepareLibraryLoad() called");
+ }
+ assert NativeLibraries.sUseLinker;
+
+ synchronized (mLock) {
+ assert !mPrepareLibraryLoadCalled;
+ ensureInitializedLocked();
+
+ // If in the browser, generate a random base load address for service processes
+ // and create an empty shared RELROs map. For service processes, the shared
+ // RELROs map must remain null until set by useSharedRelros().
+ if (mInBrowserProcess) {
+ setupBaseLoadAddressLocked();
+ mSharedRelros = new HashMap<String, LibInfo>();
+ }
+
+ // Create an empty loaded libraries map.
+ mLoadedLibraries = new HashMap<String, LibInfo>();
+
+ // Retrieve the data directory from base.
+ mDataDirectory = PathUtils.getDataDirectory(null);
+
+ // Start the current load address at the base load address.
+ mCurrentLoadAddress = mBaseLoadAddress;
+
+ mPrepareLibraryLoadCalled = true;
+ }
+ }
+
+ /**
+ * Call this method just after loading all native shared libraries in this process.
+ * If not in the browser, closes the LibInfo entries used for RELRO sharing.
+ */
+ @Override
+ public void finishLibraryLoad() {
+ if (DEBUG) {
+ Log.i(TAG, "finishLibraryLoad() called");
+ }
+
+ synchronized (mLock) {
+ assert mPrepareLibraryLoadCalled;
+
+ // Close shared RELRO file descriptors if not in the browser.
+ if (!mInBrowserProcess && mSharedRelros != null) {
+ closeLibInfoMap(mSharedRelros);
+ mSharedRelros = null;
+ }
+
+ // If testing, run tests now that all libraries are loaded and initialized.
+ if (NativeLibraries.sEnableLinkerTests) {
+ runTestRunnerClassForTesting(0, mInBrowserProcess);
+ }
+ }
+ }
+
+ // Used internally to wait for shared RELROs. Returns once useSharedRelros() has been
+ // called to supply a valid shared RELROs bundle.
+ private void waitForSharedRelrosLocked() {
+ if (DEBUG) {
+ Log.i(TAG, "waitForSharedRelros called");
+ }
+ assert Thread.holdsLock(mLock);
+
+ // Return immediately if shared RELROs are already available.
+ if (mSharedRelros != null) {
+ return;
+ }
+
+ // Wait until notified by useSharedRelros() that shared RELROs have arrived.
+ long startTime = DEBUG ? SystemClock.uptimeMillis() : 0;
+ // Note: The additional synchronized block is present only to silence Findbugs.
+ // Without it, Findbugs reports a false positive:
+ // RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE: Redundant nullcheck of value known to be null
+ synchronized (mLock) {
+ while (mSharedRelros == null) {
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ // Restore the thread's interrupt status.
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ if (DEBUG) {
+ Log.i(TAG, String.format(Locale.US, "Time to wait for shared RELRO: %d ms",
+ SystemClock.uptimeMillis() - startTime));
+ }
+ }
+
+ /**
+ * Call this to send a Bundle containing the shared RELRO sections to be
+ * used in this process. If initServiceProcess() was previously called,
+ * libraryLoad() will wait until this method is called in another
+ * thread with a non-null value.
+ *
+ * @param bundle The Bundle instance containing a map of shared RELRO sections
+ * to use in this process.
+ */
+ @Override
+ public void useSharedRelros(Bundle bundle) {
+ if (DEBUG) {
+ Log.i(TAG, "useSharedRelros() called with " + bundle);
+ }
+
+ synchronized (mLock) {
+ mSharedRelros = createLibInfoMapFromBundle(bundle);
+ mLock.notifyAll();
+ }
+ }
+
+ /**
+ * Call this to retrieve the shared RELRO sections created in this process,
+ * after loading all libraries.
+ *
+ * @return a new Bundle instance, or null if RELRO sharing is disabled on
+ * this system, or if initServiceProcess() was called previously.
+ */
+ @Override
+ public Bundle getSharedRelros() {
+ if (DEBUG) {
+ Log.i(TAG, "getSharedRelros() called");
+ }
+ synchronized (mLock) {
+ if (!mInBrowserProcess) {
+ if (DEBUG) {
+ Log.i(TAG, "Not in browser, so returning null Bundle");
+ }
+ return null;
+ }
+
+ // Create a new Bundle containing RELRO section information for all the shared
+ // RELROs created while loading libraries.
+ if (mSharedRelrosBundle == null && mSharedRelros != null) {
+ mSharedRelrosBundle = createBundleFromLibInfoMap(mSharedRelros);
+ if (DEBUG) {
+ Log.i(TAG, "Shared RELRO bundle created from map: " + mSharedRelrosBundle);
+ }
+ }
+ if (DEBUG) {
+ Log.i(TAG, "Returning " + mSharedRelrosBundle);
+ }
+ return mSharedRelrosBundle;
+ }
+ }
+
+ /**
+ * Call this method before loading any libraries to indicate that this
+ * process shall neither create or reuse shared RELRO sections.
+ */
+ @Override
+ public void disableSharedRelros() {
+ if (DEBUG) {
+ Log.i(TAG, "disableSharedRelros() called");
+ }
+ synchronized (mLock) {
+ assert !mPrepareLibraryLoadCalled;
+
+ // Mark this as a service process, and disable wait for shared RELRO.
+ mInBrowserProcess = false;
+ mWaitForSharedRelros = false;
+ }
+ }
+
+ /**
+ * Call this method before loading any libraries to indicate that this
+ * process is ready to reuse shared RELRO sections from another one.
+ * Typically used when starting service processes.
+ *
+ * @param baseLoadAddress the base library load address to use.
+ */
+ @Override
+ public void initServiceProcess(long baseLoadAddress) {
+ if (DEBUG) {
+ Log.i(TAG,
+ String.format(Locale.US, "initServiceProcess(0x%x) called", baseLoadAddress));
+ }
+ synchronized (mLock) {
+ assert !mPrepareLibraryLoadCalled;
+
+ // Mark this as a service process, and flag wait for shared RELRO.
+ // Save the base load address passed in.
+ mInBrowserProcess = false;
+ mWaitForSharedRelros = true;
+ mBaseLoadAddress = baseLoadAddress;
+ }
+ }
+
+ /**
+ * Retrieve the base load address for libraries that share RELROs.
+ *
+ * @return a common, random base load address, or 0 if RELRO sharing is
+ * disabled.
+ */
+ @Override
+ public long getBaseLoadAddress() {
+ synchronized (mLock) {
+ ensureInitializedLocked();
+ setupBaseLoadAddressLocked();
+ if (DEBUG) {
+ Log.i(TAG, String.format(Locale.US, "getBaseLoadAddress() returns 0x%x",
+ mBaseLoadAddress));
+ }
+ return mBaseLoadAddress;
+ }
+ }
+
+ // Used internally to lazily setup the common random base load address.
+ private void setupBaseLoadAddressLocked() {
+ assert Thread.holdsLock(mLock);
+
+ // No-op if the base load address is already set up.
+ if (mBaseLoadAddress == -1) {
+ mBaseLoadAddress = getRandomBaseLoadAddress();
+ }
+ if (mBaseLoadAddress == 0) {
+ // If the random address is 0 there are issues with finding enough
+ // free address space, so disable RELRO shared / fixed load addresses.
+ Log.w(TAG, "Disabling shared RELROs due address space pressure");
+ mWaitForSharedRelros = false;
+ }
+ }
+
+ /**
+ * Load a native shared library with the Chromium linker. If the zip file
+ * is not null, the shared library must be uncompressed and page aligned
+ * inside the zipfile. Note the crazy linker treats libraries and files as
+ * equivalent, so you can only open one library in a given zip file. The
+ * library must not be the Chromium linker library.
+ *
+ * If asked to wait for shared RELROs, this function will block library loads
+ * until the shared RELROs bundle is received by useSharedRelros().
+ *
+ * @param zipFilePath The path of the zip file containing the library (or null).
+ * @param libFilePath The path of the library (possibly in the zip file).
+ * @param isFixedAddressPermitted If true, uses a fixed load address if one was
+ * supplied, otherwise ignores the fixed address and loads wherever available.
+ */
+ @Override
+ void loadLibraryImpl(
+ @Nullable String zipFilePath, String libFilePath, boolean isFixedAddressPermitted) {
+ if (DEBUG) {
+ Log.i(TAG, "loadLibraryImpl: " + zipFilePath + ", " + libFilePath + ", "
+ + isFixedAddressPermitted);
+ }
+
+ synchronized (mLock) {
+ assert mPrepareLibraryLoadCalled;
+
+ String dlopenExtPath;
+ if (zipFilePath != null) {
+ // The android_dlopen_ext() function understands strings with the format
+ // <zip_path>!/<file_path> to represent the file_path element within the zip
+ // file at zip_path. This enables directly loading from APK. We add the
+ // "crazy." prefix to the path in the zip file to prevent the Android package
+ // manager from seeing this as a library and so extracting it from the APK.
+ String cpu_abi = nativeGetCpuAbi();
+ dlopenExtPath = zipFilePath + "!/lib/" + cpu_abi + "/crazy." + libFilePath;
+ } else {
+ // Not loading from APK directly, so simply pass the library name to
+ // android_dlopen_ext().
+ dlopenExtPath = libFilePath;
+ }
+
+ if (mLoadedLibraries.containsKey(dlopenExtPath)) {
+ if (DEBUG) {
+ Log.i(TAG, "Not loading " + libFilePath + " twice");
+ }
+ return;
+ }
+
+ // If not in the browser, shared RELROs are not disabled, and fixed addresses
+ // have not been disallowed, load the library at a fixed address. Otherwise,
+ // load anywhere.
+ long loadAddress = 0;
+ if (!mInBrowserProcess && mWaitForSharedRelros && isFixedAddressPermitted) {
+ loadAddress = mCurrentLoadAddress;
+
+ // For multiple libraries, ensure we stay within reservation range.
+ if (loadAddress > mBaseLoadAddress + ADDRESS_SPACE_RESERVATION) {
+ String errorMessage = "Load address outside reservation, for: " + libFilePath;
+ Log.e(TAG, errorMessage);
+ throw new UnsatisfiedLinkError(errorMessage);
+ }
+ }
+
+ LibInfo libInfo = new LibInfo();
+
+ if (mInBrowserProcess && mCurrentLoadAddress != 0) {
+ // We are in the browser, and with a current load address that indicates that
+ // there is enough address space for shared RELRO to operate. Create the
+ // shared RELRO, and store it in the map.
+ String relroPath = mDataDirectory + "/RELRO:" + libFilePath;
+ if (nativeCreateSharedRelro(
+ dlopenExtPath, mCurrentLoadAddress, relroPath, libInfo)) {
+ mSharedRelros.put(dlopenExtPath, libInfo);
+ } else {
+ String errorMessage = "Unable to create shared relro: " + relroPath;
+ Log.w(TAG, errorMessage);
+ }
+ } else if (!mInBrowserProcess && mCurrentLoadAddress != 0 && mWaitForSharedRelros) {
+ // We are in a service process, again with a current load address that is
+ // suitable for shared RELRO, and we are to wait for shared RELROs. So
+ // do that, then use the map we receive to provide libinfo for library load.
+ waitForSharedRelrosLocked();
+ if (mSharedRelros.containsKey(dlopenExtPath)) {
+ libInfo = mSharedRelros.get(dlopenExtPath);
+ }
+ }
+
+ // Load the library. In the browser, loadAddress is 0, so nativeLoadLibrary()
+ // will load without shared RELRO. Otherwise, it uses shared RELRO if the attached
+ // libInfo is usable.
+ if (!nativeLoadLibrary(dlopenExtPath, loadAddress, libInfo)) {
+ String errorMessage = "Unable to load library: " + dlopenExtPath;
+ Log.e(TAG, errorMessage);
+ throw new UnsatisfiedLinkError(errorMessage);
+ }
+
+ // Print the load address to the logcat when testing the linker. The format
+ // of the string is expected by the Python test_runner script as one of:
+ // BROWSER_LIBRARY_ADDRESS: <library-name> <address>
+ // RENDERER_LIBRARY_ADDRESS: <library-name> <address>
+ // Where <library-name> is the library name, and <address> is the hexadecimal load
+ // address.
+ if (NativeLibraries.sEnableLinkerTests) {
+ String tag =
+ mInBrowserProcess ? "BROWSER_LIBRARY_ADDRESS" : "RENDERER_LIBRARY_ADDRESS";
+ Log.i(TAG, String.format(
+ Locale.US, "%s: %s %x", tag, libFilePath, libInfo.mLoadAddress));
+ }
+
+ if (loadAddress != 0 && mCurrentLoadAddress != 0) {
+ // Compute the next current load address. If mCurrentLoadAddress
+ // is not 0, this is an explicit library load address.
+ mCurrentLoadAddress =
+ libInfo.mLoadAddress + libInfo.mLoadSize + BREAKPAD_GUARD_REGION_BYTES;
+ }
+
+ mLoadedLibraries.put(dlopenExtPath, libInfo);
+ if (DEBUG) {
+ Log.i(TAG, "Library details " + libInfo.toString());
+ }
+ }
+ }
+
+ /**
+ * Native method to return the CPU ABI.
+ * Obtaining it from the linker's native code means that we always correctly
+ * match the loaded library's ABI to the linker's ABI.
+ *
+ * @return CPU ABI string.
+ */
+ private static native String nativeGetCpuAbi();
+
+ /**
+ * Native method used to load a library.
+ *
+ * @param dlopenExtPath For load from APK, the path to the enclosing
+ * zipfile concatenated with "!/" and the path to the library within the zipfile;
+ * otherwise the platform specific library name (e.g. libfoo.so).
+ * @param loadAddress Explicit load address, or 0 for randomized one.
+ * @param libInfo If not null, the mLoadAddress and mLoadSize fields
+ * of this LibInfo instance will set on success.
+ * @return true for success, false otherwise.
+ */
+ private static native boolean nativeLoadLibrary(
+ String dlopenExtPath, long loadAddress, LibInfo libInfo);
+
+ /**
+ * Native method used to create a shared RELRO section.
+ * Creates a shared RELRO file for the given library. Done by loading a
+ * a new temporary library at the specified address, saving the RELRO section
+ * from it, then unloading.
+ *
+ * @param dlopenExtPath For load from APK, the path to the enclosing
+ * zipfile concatenated with "!/" and the path to the library within the zipfile;
+ * otherwise the platform specific library name (e.g. libfoo.so).
+ * @param loadAddress load address, which can be different from the one
+ * used to load the library in the current process!
+ * @param relroPath Path to the shared RELRO file for this library.
+ * @param libInfo libInfo instance. On success, the mRelroStart, mRelroSize
+ * and mRelroFd will be set.
+ * @return true on success, false otherwise.
+ */
+ private static native boolean nativeCreateSharedRelro(
+ String dlopenExtPath, long loadAddress, String relroPath, LibInfo libInfo);
+}
diff --git a/android/javatests/src/org/chromium/base/ObserverListTest.java b/android/javatests/src/org/chromium/base/ObserverListTest.java
index 973682b..94a9c71 100644
--- a/android/javatests/src/org/chromium/base/ObserverListTest.java
+++ b/android/javatests/src/org/chromium/base/ObserverListTest.java
@@ -9,6 +9,7 @@
import org.chromium.base.test.util.Feature;
+import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
@@ -70,7 +71,9 @@
}
}
+ @SuppressWarnings("ElementsCountedInLoop")
private static <T> int getSizeOfIterable(Iterable<T> iterable) {
+ if (iterable instanceof Collection<?>) return ((Collection<?>) iterable).size();
int num = 0;
for (T el : iterable) num++;
return num;
diff --git a/android/linker/BUILD.gn b/android/linker/BUILD.gn
index 043bfc6..9c23a63 100644
--- a/android/linker/BUILD.gn
+++ b/android/linker/BUILD.gn
@@ -9,7 +9,13 @@
# GYP: //base/base.gyp:chromium_android_linker
shared_library("chromium_android_linker") {
sources = [
+ "android_dlext.h",
"legacy_linker_jni.cc",
+ "legacy_linker_jni.h",
+ "linker_jni.cc",
+ "linker_jni.h",
+ "modern_linker_jni.cc",
+ "modern_linker_jni.h",
]
# The NDK contains the crazy_linker here:
diff --git a/android/linker/android_dlext.h b/android/linker/android_dlext.h
new file mode 100644
index 0000000..4900f21
--- /dev/null
+++ b/android/linker/android_dlext.h
@@ -0,0 +1,77 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Definitions for android_dlopen_ext().
+//
+// This function was added for Android L-MR1 and made available in android-21
+// but we currently build Chromium with android-16. Placing the declarations
+// we need here allows code that uses android_dlopen_ext() to build with
+// android-16. At runtime we check the target's SDK_INT to ensure that we
+// are on a system new enough to offer this function, and also only access
+// it with dlsym so that the runtime linker on pre-Android L-MR1 targets will
+// not complain about a missing symbol when loading our library.
+//
+// Details below taken from:
+// third_party/android_tools/ndk/platforms/android-21
+// /arch-arm/usr/include/android/dlext.h
+//
+// Although taken specifically from arch-arm, there are no architecture-
+// specific elements in dlext.h. All android-21/arch-* directories contain
+// identical copies of dlext.h.
+
+#ifndef BASE_ANDROID_LINKER_ANDROID_DLEXT_H_
+#define BASE_ANDROID_LINKER_ANDROID_DLEXT_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+/* bitfield definitions for android_dlextinfo.flags */
+enum {
+ /* When set, the reserved_addr and reserved_size fields must point to an
+ * already-reserved region of address space which will be used to load the
+ * library if it fits. If the reserved region is not large enough, the load
+ * will fail.
+ */
+ ANDROID_DLEXT_RESERVED_ADDRESS = 0x1,
+
+ /* As DLEXT_RESERVED_ADDRESS, but if the reserved region is not large enough,
+ * the linker will choose an available address instead.
+ */
+ ANDROID_DLEXT_RESERVED_ADDRESS_HINT = 0x2,
+
+ /* When set, write the GNU RELRO section of the mapped library to relro_fd
+ * after relocation has been performed, to allow it to be reused by another
+ * process loading the same library at the same address. This implies
+ * ANDROID_DLEXT_USE_RELRO.
+ */
+ ANDROID_DLEXT_WRITE_RELRO = 0x4,
+
+ /* When set, compare the GNU RELRO section of the mapped library to relro_fd
+ * after relocation has been performed, and replace any relocated pages that
+ * are identical with a version mapped from the file.
+ */
+ ANDROID_DLEXT_USE_RELRO = 0x8,
+
+ /* Instruct dlopen to use library_fd instead of opening file by name.
+ * The filename parameter is still used to identify the library.
+ */
+ ANDROID_DLEXT_USE_LIBRARY_FD = 0x10,
+
+ /* Mask of valid bits */
+ ANDROID_DLEXT_VALID_FLAG_BITS = ANDROID_DLEXT_RESERVED_ADDRESS |
+ ANDROID_DLEXT_RESERVED_ADDRESS_HINT |
+ ANDROID_DLEXT_WRITE_RELRO |
+ ANDROID_DLEXT_USE_RELRO |
+ ANDROID_DLEXT_USE_LIBRARY_FD,
+};
+
+typedef struct {
+ uint64_t flags;
+ void* reserved_addr;
+ size_t reserved_size;
+ int relro_fd;
+ int library_fd;
+} android_dlextinfo;
+
+#endif // BASE_ANDROID_LINKER_ANDROID_DLEXT_H_
diff --git a/android/linker/legacy_linker_jni.cc b/android/linker/legacy_linker_jni.cc
index bc677c4..e4c0455 100644
--- a/android/linker/legacy_linker_jni.cc
+++ b/android/linker/legacy_linker_jni.cc
@@ -2,254 +2,26 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// This is the Android-specific Chromium linker, a tiny shared library
-// implementing a custom dynamic linker that can be used to load the
-// real Chromium libraries (e.g. libcontentshell.so).
-
-// The main point of this linker is to be able to share the RELRO
-// section of libcontentshell.so (or equivalent) between the browser and
-// renderer process.
+// This is the version of the Android-specific Chromium linker that uses
+// the crazy linker to load libraries.
// This source code *cannot* depend on anything from base/ or the C++
// STL, to keep the final library small, and avoid ugly dependency issues.
-#include <android/log.h>
+#include "legacy_linker_jni.h"
+
#include <crazy_linker.h>
#include <fcntl.h>
#include <jni.h>
#include <limits.h>
#include <stdlib.h>
-#include <sys/mman.h>
#include <unistd.h>
-// See commentary in crazy_linker_elf_loader.cpp for the effect of setting
-// this. If changing there, change here also.
-//
-// For more, see:
-// https://crbug.com/504410
-#define RESERVE_BREAKPAD_GUARD_REGION 1
+#include "linker_jni.h"
-// Set this to 1 to enable debug traces to the Android log.
-// Note that LOG() from "base/logging.h" cannot be used, since it is
-// in base/ which hasn't been loaded yet.
-#define DEBUG 0
-
-#define TAG "chromium_android_linker"
-
-#if DEBUG
-#define LOG_INFO(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
-#else
-#define LOG_INFO(...) ((void)0)
-#endif
-#define LOG_ERROR(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
-
-#define UNUSED __attribute__((unused))
-
+namespace chromium_android_linker {
namespace {
-// A simply scoped UTF String class that can be initialized from
-// a Java jstring handle. Modeled like std::string, which cannot
-// be used here.
-class String {
- public:
- String(JNIEnv* env, jstring str);
-
- ~String() {
- if (ptr_)
- ::free(ptr_);
- }
-
- const char* c_str() const { return ptr_ ? ptr_ : ""; }
- size_t size() const { return size_; }
-
- private:
- char* ptr_;
- size_t size_;
-};
-
-String::String(JNIEnv* env, jstring str) {
- size_ = env->GetStringUTFLength(str);
- ptr_ = static_cast<char*>(::malloc(size_ + 1));
-
- // Note: This runs before browser native code is loaded, and so cannot
- // rely on anything from base/. This means that we must use
- // GetStringUTFChars() and not base::android::ConvertJavaStringToUTF8().
- //
- // GetStringUTFChars() suffices because the only strings used here are
- // paths to APK files or names of shared libraries, all of which are
- // plain ASCII, defined and hard-coded by the Chromium Android build.
- //
- // For more: see
- // https://crbug.com/508876
- //
- // Note: GetStringUTFChars() returns Java UTF-8 bytes. This is good
- // enough for the linker though.
- const char* bytes = env->GetStringUTFChars(str, NULL);
- ::memcpy(ptr_, bytes, size_);
- ptr_[size_] = '\0';
-
- env->ReleaseStringUTFChars(str, bytes);
-}
-
-// Return true iff |address| is a valid address for the target CPU.
-bool IsValidAddress(jlong address) {
- return static_cast<jlong>(static_cast<size_t>(address)) == address;
-}
-
-// Find the jclass JNI reference corresponding to a given |class_name|.
-// |env| is the current JNI environment handle.
-// On success, return true and set |*clazz|.
-bool InitClassReference(JNIEnv* env, const char* class_name, jclass* clazz) {
- *clazz = env->FindClass(class_name);
- if (!*clazz) {
- LOG_ERROR("Could not find class for %s", class_name);
- return false;
- }
- return true;
-}
-
-// Initialize a jfieldID corresponding to the field of a given |clazz|,
-// with name |field_name| and signature |field_sig|.
-// |env| is the current JNI environment handle.
-// On success, return true and set |*field_id|.
-bool InitFieldId(JNIEnv* env,
- jclass clazz,
- const char* field_name,
- const char* field_sig,
- jfieldID* field_id) {
- *field_id = env->GetFieldID(clazz, field_name, field_sig);
- if (!*field_id) {
- LOG_ERROR("Could not find ID for field '%s'", field_name);
- return false;
- }
- LOG_INFO("%s: Found ID %p for field '%s'", __FUNCTION__, *field_id,
- field_name);
- return true;
-}
-
-// Initialize a jmethodID corresponding to the static method of a given
-// |clazz|, with name |method_name| and signature |method_sig|.
-// |env| is the current JNI environment handle.
-// On success, return true and set |*method_id|.
-bool InitStaticMethodId(JNIEnv* env,
- jclass clazz,
- const char* method_name,
- const char* method_sig,
- jmethodID* method_id) {
- *method_id = env->GetStaticMethodID(clazz, method_name, method_sig);
- if (!*method_id) {
- LOG_ERROR("Could not find ID for static method '%s'", method_name);
- return false;
- }
- LOG_INFO("%s: Found ID %p for static method '%s'", __FUNCTION__, *method_id,
- method_name);
- return true;
-}
-
-// Initialize a jfieldID corresponding to the static field of a given |clazz|,
-// with name |field_name| and signature |field_sig|.
-// |env| is the current JNI environment handle.
-// On success, return true and set |*field_id|.
-bool InitStaticFieldId(JNIEnv* env,
- jclass clazz,
- const char* field_name,
- const char* field_sig,
- jfieldID* field_id) {
- *field_id = env->GetStaticFieldID(clazz, field_name, field_sig);
- if (!*field_id) {
- LOG_ERROR("Could not find ID for static field '%s'", field_name);
- return false;
- }
- LOG_INFO("%s: Found ID %p for static field '%s'", __FUNCTION__, *field_id,
- field_name);
- return true;
-}
-
-// Initialize a jint corresponding to the static integer field of a class
-// with class name |class_name| and field name |field_name|.
-// |env| is the current JNI environment handle.
-// On success, return true and set |*value|.
-bool InitStaticInt(JNIEnv* env,
- const char* class_name,
- const char* field_name,
- jint* value) {
- jclass clazz;
- if (!InitClassReference(env, class_name, &clazz))
- return false;
-
- jfieldID field_id;
- if (!InitStaticFieldId(env, clazz, field_name, "I", &field_id))
- return false;
-
- *value = env->GetStaticIntField(clazz, field_id);
- LOG_INFO("%s: Found value %d for class '%s', static field '%s'", __FUNCTION__,
- *value, class_name, field_name);
-
- return true;
-}
-
-// A class used to model the field IDs of the org.chromium.base.Linker
-// LibInfo inner class, used to communicate data with the Java side
-// of the linker.
-struct LibInfo_class {
- jfieldID load_address_id;
- jfieldID load_size_id;
- jfieldID relro_start_id;
- jfieldID relro_size_id;
- jfieldID relro_fd_id;
-
- // Initialize an instance.
- bool Init(JNIEnv* env) {
- jclass clazz;
- if (!InitClassReference(
- env, "org/chromium/base/library_loader/Linker$LibInfo", &clazz)) {
- return false;
- }
-
- return InitFieldId(env, clazz, "mLoadAddress", "J", &load_address_id) &&
- InitFieldId(env, clazz, "mLoadSize", "J", &load_size_id) &&
- InitFieldId(env, clazz, "mRelroStart", "J", &relro_start_id) &&
- InitFieldId(env, clazz, "mRelroSize", "J", &relro_size_id) &&
- InitFieldId(env, clazz, "mRelroFd", "I", &relro_fd_id);
- }
-
- void SetLoadInfo(JNIEnv* env,
- jobject library_info_obj,
- size_t load_address,
- size_t load_size) {
- env->SetLongField(library_info_obj, load_address_id, load_address);
- env->SetLongField(library_info_obj, load_size_id, load_size);
- }
-
- // Use this instance to convert a RelroInfo reference into
- // a crazy_library_info_t.
- void GetRelroInfo(JNIEnv* env,
- jobject library_info_obj,
- size_t* relro_start,
- size_t* relro_size,
- int* relro_fd) {
- *relro_start = static_cast<size_t>(
- env->GetLongField(library_info_obj, relro_start_id));
-
- *relro_size =
- static_cast<size_t>(env->GetLongField(library_info_obj, relro_size_id));
-
- *relro_fd = env->GetIntField(library_info_obj, relro_fd_id);
- }
-
- void SetRelroInfo(JNIEnv* env,
- jobject library_info_obj,
- size_t relro_start,
- size_t relro_size,
- int relro_fd) {
- env->SetLongField(library_info_obj, relro_start_id, relro_start);
- env->SetLongField(library_info_obj, relro_size_id, relro_size);
- env->SetIntField(library_info_obj, relro_fd_id, relro_fd);
- }
-};
-
-static LibInfo_class s_lib_info_fields;
-
// Retrieve the SDK build version and pass it into the crazy linker. This
// needs to be done early in initialization, before any other crazy linker
// code is run.
@@ -261,8 +33,7 @@
return false;
crazy_set_sdk_build_version(static_cast<int>(value));
- LOG_INFO("%s: Set SDK build version to %d", __FUNCTION__,
- static_cast<int>(value));
+ LOG_INFO("Set SDK build version to %d", static_cast<int>(value));
return true;
}
@@ -270,9 +41,9 @@
// The linker uses a single crazy_context_t object created on demand.
// There is no need to protect this against concurrent access, locking
// is already handled on the Java side.
-static crazy_context_t* s_crazy_context;
-
crazy_context_t* GetCrazyContext() {
+ static crazy_context_t* s_crazy_context = nullptr;
+
if (!s_crazy_context) {
// Create new context.
s_crazy_context = crazy_context_create();
@@ -290,7 +61,7 @@
// on scope exit, unless Release() has been called.
class ScopedLibrary {
public:
- ScopedLibrary() : lib_(NULL) {}
+ ScopedLibrary() : lib_(nullptr) {}
~ScopedLibrary() {
if (lib_)
@@ -303,7 +74,7 @@
crazy_library_t* Release() {
crazy_library_t* ret = lib_;
- lib_ = NULL;
+ lib_ = nullptr;
return ret;
}
@@ -311,18 +82,17 @@
crazy_library_t* lib_;
};
-namespace {
-
template <class LibraryOpener>
bool GenericLoadLibrary(JNIEnv* env,
const char* library_name,
jlong load_address,
jobject lib_info_obj,
const LibraryOpener& opener) {
+ LOG_INFO("Called for %s, at address 0x%llx", library_name, load_address);
crazy_context_t* context = GetCrazyContext();
if (!IsValidAddress(load_address)) {
- LOG_ERROR("%s: Invalid address 0x%llx", __FUNCTION__, load_address);
+ LOG_ERROR("Invalid address 0x%llx", load_address);
return false;
}
@@ -336,8 +106,8 @@
crazy_library_info_t info;
if (!crazy_library_get_info(library.Get(), context, &info)) {
- LOG_ERROR("%s: Could not get library information for %s: %s", __FUNCTION__,
- library_name, crazy_context_get_error(context));
+ LOG_ERROR("Could not get library information for %s: %s", library_name,
+ crazy_context_get_error(context));
return false;
}
@@ -346,7 +116,7 @@
s_lib_info_fields.SetLoadInfo(env, lib_info_obj, info.load_address,
info.load_size);
- LOG_INFO("%s: Success loading library %s", __FUNCTION__, library_name);
+ LOG_INFO("Success loading library %s", library_name);
return true;
}
@@ -362,7 +132,7 @@
const char* library_name,
crazy_context_t* context) const {
if (!crazy_library_open(library, library_name, context)) {
- LOG_ERROR("%s: Could not open %s: %s", __FUNCTION__, library_name,
+ LOG_ERROR("Could not open %s: %s", library_name,
crazy_context_get_error(context));
return false;
}
@@ -386,20 +156,19 @@
crazy_context_t* context) const {
if (!crazy_library_open_in_zip_file(library, zip_file_, library_name,
context)) {
- LOG_ERROR("%s: Could not open %s in zip file %s: %s", __FUNCTION__,
- library_name, zip_file_, crazy_context_get_error(context));
+ LOG_ERROR("Could not open %s in zip file %s: %s", library_name, zip_file_,
+ crazy_context_get_error(context));
return false;
}
return true;
}
-} // unnamed namespace
-
// Load a library with the chromium linker. This will also call its
// JNI_OnLoad() method, which shall register its methods. Note that
// lazy native method resolution will _not_ work after this, because
// Dalvik uses the system's dlsym() which won't see the new library,
// so explicit registration is mandatory.
+//
// |env| is the current JNI environment handle.
// |clazz| is the static class handle for org.chromium.base.Linker,
// and is ignored here.
@@ -415,6 +184,7 @@
jobject lib_info_obj) {
String lib_name(env, library_name);
FileLibraryOpener opener;
+
return GenericLoadLibrary(env, lib_name.c_str(),
static_cast<size_t>(load_address), lib_info_obj,
opener);
@@ -451,6 +221,7 @@
String zipfile_name_str(env, zipfile_name);
String lib_name(env, library_name);
ZipLibraryOpener opener(zipfile_name_str.c_str());
+
return GenericLoadLibrary(env, lib_name.c_str(),
static_cast<size_t>(load_address), lib_info_obj,
opener);
@@ -481,7 +252,7 @@
void RunCallbackOnUiThread(JNIEnv* env, jclass clazz, jlong arg) {
crazy_callback_t* callback = reinterpret_cast<crazy_callback_t*>(arg);
- LOG_INFO("%s: Called back from java with handler %p, opaque %p", __FUNCTION__,
+ LOG_INFO("Called back from java with handler %p, opaque %p",
callback->handler, callback->opaque);
crazy_callback_run(callback);
@@ -517,8 +288,8 @@
crazy_callback_t* callback = new crazy_callback_t();
*callback = *callback_request;
- LOG_INFO("%s: Calling back to java with handler %p, opaque %p", __FUNCTION__,
- callback->handler, callback->opaque);
+ LOG_INFO("Calling back to java with handler %p, opaque %p", callback->handler,
+ callback->opaque);
jlong arg = static_cast<jlong>(reinterpret_cast<uintptr_t>(callback));
@@ -543,16 +314,16 @@
jobject lib_info_obj) {
String lib_name(env, library_name);
- LOG_INFO("%s: Called for %s", __FUNCTION__, lib_name.c_str());
+ LOG_INFO("Called for %s", lib_name.c_str());
if (!IsValidAddress(load_address)) {
- LOG_ERROR("%s: Invalid address 0x%llx", __FUNCTION__, load_address);
+ LOG_ERROR("Invalid address 0x%llx", load_address);
return false;
}
ScopedLibrary library;
if (!crazy_library_find_by_name(lib_name.c_str(), library.GetPtr())) {
- LOG_ERROR("%s: Could not find %s", __FUNCTION__, lib_name.c_str());
+ LOG_ERROR("Could not find %s", lib_name.c_str());
return false;
}
@@ -564,8 +335,8 @@
if (!crazy_library_create_shared_relro(
library.Get(), context, static_cast<size_t>(load_address),
&relro_start, &relro_size, &relro_fd)) {
- LOG_ERROR("%s: Could not create shared RELRO sharing for %s: %s\n",
- __FUNCTION__, lib_name.c_str(), crazy_context_get_error(context));
+ LOG_ERROR("Could not create shared RELRO sharing for %s: %s\n",
+ lib_name.c_str(), crazy_context_get_error(context));
return false;
}
@@ -580,12 +351,11 @@
jobject lib_info_obj) {
String lib_name(env, library_name);
- LOG_INFO("%s: called for %s, lib_info_ref=%p", __FUNCTION__, lib_name.c_str(),
- lib_info_obj);
+ LOG_INFO("Called for %s, lib_info_ref=%p", lib_name.c_str(), lib_info_obj);
ScopedLibrary library;
if (!crazy_library_find_by_name(lib_name.c_str(), library.GetPtr())) {
- LOG_ERROR("%s: Could not find %s", __FUNCTION__, lib_name.c_str());
+ LOG_ERROR("Could not find %s", lib_name.c_str());
return false;
}
@@ -596,48 +366,21 @@
s_lib_info_fields.GetRelroInfo(env, lib_info_obj, &relro_start, &relro_size,
&relro_fd);
- LOG_INFO("%s: library=%s relro start=%p size=%p fd=%d", __FUNCTION__,
- lib_name.c_str(), (void*)relro_start, (void*)relro_size, relro_fd);
+ LOG_INFO("library=%s relro start=%p size=%p fd=%d", lib_name.c_str(),
+ (void*)relro_start, (void*)relro_size, relro_fd);
if (!crazy_library_use_shared_relro(library.Get(), context, relro_start,
relro_size, relro_fd)) {
- LOG_ERROR("%s: Could not use shared RELRO for %s: %s", __FUNCTION__,
- lib_name.c_str(), crazy_context_get_error(context));
+ LOG_ERROR("Could not use shared RELRO for %s: %s", lib_name.c_str(),
+ crazy_context_get_error(context));
return false;
}
- LOG_INFO("%s: Library %s using shared RELRO section!", __FUNCTION__,
- lib_name.c_str());
+ LOG_INFO("Library %s using shared RELRO section!", lib_name.c_str());
return true;
}
-jlong GetRandomBaseLoadAddress(JNIEnv* env, jclass clazz, jlong bytes) {
-#if RESERVE_BREAKPAD_GUARD_REGION
- // Add a Breakpad guard region. 16Mb should be comfortably larger than
- // the largest relocation packer saving we expect to encounter.
- static const size_t kBreakpadGuardRegionBytes = 16 * 1024 * 1024;
- bytes += kBreakpadGuardRegionBytes;
-#endif
-
- void* address =
- mmap(NULL, bytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- if (address == MAP_FAILED) {
- LOG_INFO("%s: Random base load address not determinable\n", __FUNCTION__);
- return 0;
- }
- munmap(address, bytes);
-
-#if RESERVE_BREAKPAD_GUARD_REGION
- // Allow for a Breakpad guard region ahead of the returned address.
- address = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(address) +
- kBreakpadGuardRegionBytes);
-#endif
-
- LOG_INFO("%s: Random base load address is %p\n", __FUNCTION__, address);
- return static_cast<jlong>(reinterpret_cast<uintptr_t>(address));
-}
-
const JNINativeMethod kNativeMethods[] = {
{"nativeLoadLibrary",
"("
@@ -677,53 +420,32 @@
")"
"Z",
reinterpret_cast<void*>(&UseSharedRelro)},
- {"nativeGetRandomBaseLoadAddress",
- "("
- "J"
- ")"
- "J",
- reinterpret_cast<void*>(&GetRandomBaseLoadAddress)},
};
} // namespace
-// JNI_OnLoad() hook called when the linker library is loaded through
-// the regular System.LoadLibrary) API. This shall save the Java VM
-// handle and initialize LibInfo fields.
-jint JNI_OnLoad(JavaVM* vm, void* reserved) {
- LOG_INFO("%s: Entering", __FUNCTION__);
- // Get new JNIEnv
- JNIEnv* env;
- if (JNI_OK != vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_4)) {
- LOG_ERROR("Could not create JNIEnv");
- return -1;
- }
+bool LegacyLinkerJNIInit(JavaVM* vm, JNIEnv* env) {
+ LOG_INFO("Entering");
// Initialize SDK version info.
- LOG_INFO("%s: Retrieving SDK version info", __FUNCTION__);
+ LOG_INFO("Retrieving SDK version info");
if (!InitSDKVersionInfo(env))
- return -1;
+ return false;
// Register native methods.
jclass linker_class;
if (!InitClassReference(env, "org/chromium/base/library_loader/LegacyLinker",
&linker_class))
- return -1;
+ return false;
- LOG_INFO("%s: Registering native methods", __FUNCTION__);
+ LOG_INFO("Registering native methods");
env->RegisterNatives(linker_class, kNativeMethods,
sizeof(kNativeMethods) / sizeof(kNativeMethods[0]));
- // Find LibInfo field ids.
- LOG_INFO("%s: Caching field IDs", __FUNCTION__);
- if (!s_lib_info_fields.Init(env)) {
- return -1;
- }
-
// Resolve and save the Java side Linker callback class and method.
- LOG_INFO("%s: Resolving callback bindings", __FUNCTION__);
+ LOG_INFO("Resolving callback bindings");
if (!s_java_callback_bindings.Init(env, linker_class)) {
- return -1;
+ return false;
}
// Save JavaVM* handle into context.
@@ -732,8 +454,9 @@
// Register the function that the crazy linker can call to post code
// for later execution.
- crazy_context_set_callback_poster(context, &PostForLaterExecution, NULL);
+ crazy_context_set_callback_poster(context, &PostForLaterExecution, nullptr);
- LOG_INFO("%s: Done", __FUNCTION__);
- return JNI_VERSION_1_4;
+ return true;
}
+
+} // namespace chromium_android_linker
diff --git a/android/linker/legacy_linker_jni.h b/android/linker/legacy_linker_jni.h
new file mode 100644
index 0000000..fcacf12
--- /dev/null
+++ b/android/linker/legacy_linker_jni.h
@@ -0,0 +1,21 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_LINKER_LEGACY_LINKER_JNI_H_
+#define BASE_ANDROID_LINKER_LEGACY_LINKER_JNI_H_
+
+#include <jni.h>
+
+namespace chromium_android_linker {
+
+// JNI_OnLoad() initialization hook for the legacy linker.
+// Sets up JNI and other initializations for native linker code.
+// |vm| is the Java VM handle passed to JNI_OnLoad().
+// |env| is the current JNI environment handle.
+// On success, returns true.
+extern bool LegacyLinkerJNIInit(JavaVM* vm, JNIEnv* env);
+
+} // namespace chromium_android_linker
+
+#endif // BASE_ANDROID_LINKER_LEGACY_LINKER_JNI_H_
diff --git a/android/linker/linker_jni.cc b/android/linker/linker_jni.cc
new file mode 100644
index 0000000..4db8c3f
--- /dev/null
+++ b/android/linker/linker_jni.cc
@@ -0,0 +1,237 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This is the Android-specific Chromium linker, a tiny shared library
+// implementing a custom dynamic linker that can be used to load the
+// real Chromium libraries.
+
+// The main point of this linker is to be able to share the RELRO
+// section of libchrome.so (or equivalent) between renderer processes.
+
+// This source code *cannot* depend on anything from base/ or the C++
+// STL, to keep the final library small, and avoid ugly dependency issues.
+
+#include "linker_jni.h"
+
+#include <sys/mman.h>
+#include <jni.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "legacy_linker_jni.h"
+#include "modern_linker_jni.h"
+
+namespace chromium_android_linker {
+
+// Variable containing LibInfo for the loaded library.
+LibInfo_class s_lib_info_fields;
+
+// Simple scoped UTF String class constructor.
+String::String(JNIEnv* env, jstring str) {
+ size_ = env->GetStringUTFLength(str);
+ ptr_ = static_cast<char*>(::malloc(size_ + 1));
+
+ // Note: This runs before browser native code is loaded, and so cannot
+ // rely on anything from base/. This means that we must use
+ // GetStringUTFChars() and not base::android::ConvertJavaStringToUTF8().
+ //
+ // GetStringUTFChars() suffices because the only strings used here are
+ // paths to APK files or names of shared libraries, all of which are
+ // plain ASCII, defined and hard-coded by the Chromium Android build.
+ //
+ // For more: see
+ // https://crbug.com/508876
+ //
+ // Note: GetStringUTFChars() returns Java UTF-8 bytes. This is good
+ // enough for the linker though.
+ const char* bytes = env->GetStringUTFChars(str, nullptr);
+ ::memcpy(ptr_, bytes, size_);
+ ptr_[size_] = '\0';
+
+ env->ReleaseStringUTFChars(str, bytes);
+}
+
+// Find the jclass JNI reference corresponding to a given |class_name|.
+// |env| is the current JNI environment handle.
+// On success, return true and set |*clazz|.
+bool InitClassReference(JNIEnv* env, const char* class_name, jclass* clazz) {
+ *clazz = env->FindClass(class_name);
+ if (!*clazz) {
+ LOG_ERROR("Could not find class for %s", class_name);
+ return false;
+ }
+ return true;
+}
+
+// Initialize a jfieldID corresponding to the field of a given |clazz|,
+// with name |field_name| and signature |field_sig|.
+// |env| is the current JNI environment handle.
+// On success, return true and set |*field_id|.
+bool InitFieldId(JNIEnv* env,
+ jclass clazz,
+ const char* field_name,
+ const char* field_sig,
+ jfieldID* field_id) {
+ *field_id = env->GetFieldID(clazz, field_name, field_sig);
+ if (!*field_id) {
+ LOG_ERROR("Could not find ID for field '%s'", field_name);
+ return false;
+ }
+ LOG_INFO("Found ID %p for field '%s'", *field_id, field_name);
+ return true;
+}
+
+// Initialize a jmethodID corresponding to the static method of a given
+// |clazz|, with name |method_name| and signature |method_sig|.
+// |env| is the current JNI environment handle.
+// On success, return true and set |*method_id|.
+bool InitStaticMethodId(JNIEnv* env,
+ jclass clazz,
+ const char* method_name,
+ const char* method_sig,
+ jmethodID* method_id) {
+ *method_id = env->GetStaticMethodID(clazz, method_name, method_sig);
+ if (!*method_id) {
+ LOG_ERROR("Could not find ID for static method '%s'", method_name);
+ return false;
+ }
+ LOG_INFO("Found ID %p for static method '%s'", *method_id, method_name);
+ return true;
+}
+
+// Initialize a jfieldID corresponding to the static field of a given |clazz|,
+// with name |field_name| and signature |field_sig|.
+// |env| is the current JNI environment handle.
+// On success, return true and set |*field_id|.
+bool InitStaticFieldId(JNIEnv* env,
+ jclass clazz,
+ const char* field_name,
+ const char* field_sig,
+ jfieldID* field_id) {
+ *field_id = env->GetStaticFieldID(clazz, field_name, field_sig);
+ if (!*field_id) {
+ LOG_ERROR("Could not find ID for static field '%s'", field_name);
+ return false;
+ }
+ LOG_INFO("Found ID %p for static field '%s'", *field_id, field_name);
+ return true;
+}
+
+// Initialize a jint corresponding to the static integer field of a class
+// with class name |class_name| and field name |field_name|.
+// |env| is the current JNI environment handle.
+// On success, return true and set |*value|.
+bool InitStaticInt(JNIEnv* env,
+ const char* class_name,
+ const char* field_name,
+ jint* value) {
+ jclass clazz;
+ if (!InitClassReference(env, class_name, &clazz))
+ return false;
+
+ jfieldID field_id;
+ if (!InitStaticFieldId(env, clazz, field_name, "I", &field_id))
+ return false;
+
+ *value = env->GetStaticIntField(clazz, field_id);
+ LOG_INFO("Found value %d for class '%s', static field '%s'", *value,
+ class_name, field_name);
+
+ return true;
+}
+
+// Use Android ASLR to create a random address into which we expect to be
+// able to load libraries. Note that this is probabilistic; we unmap the
+// address we get from mmap and assume we can re-map into it later. This
+// works the majority of the time. If it doesn't, client code backs out and
+// then loads the library normally at any available address.
+// |env| is the current JNI environment handle, and |clazz| a class.
+// Returns the address selected by ASLR, or 0 on error.
+jlong GetRandomBaseLoadAddress(JNIEnv* env, jclass clazz) {
+ size_t bytes = kAddressSpaceReservationSize;
+
+#if RESERVE_BREAKPAD_GUARD_REGION
+ // Pad the requested address space size for a Breakpad guard region.
+ bytes += kBreakpadGuardRegionBytes;
+#endif
+
+ void* address =
+ mmap(nullptr, bytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (address == MAP_FAILED) {
+ LOG_INFO("Random base load address not determinable");
+ return 0;
+ }
+ munmap(address, bytes);
+
+#if RESERVE_BREAKPAD_GUARD_REGION
+ // Allow for a Breakpad guard region ahead of the returned address.
+ address = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(address) +
+ kBreakpadGuardRegionBytes);
+#endif
+
+ LOG_INFO("Random base load address is %p", address);
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(address));
+}
+
+namespace {
+
+const JNINativeMethod kNativeMethods[] = {
+ {"nativeGetRandomBaseLoadAddress",
+ "("
+ ")"
+ "J",
+ reinterpret_cast<void*>(&GetRandomBaseLoadAddress)},
+};
+
+// JNI_OnLoad() initialization hook.
+bool LinkerJNIInit(JavaVM* vm, JNIEnv* env) {
+ LOG_INFO("Entering");
+
+ // Register native methods.
+ jclass linker_class;
+ if (!InitClassReference(env, "org/chromium/base/library_loader/Linker",
+ &linker_class))
+ return false;
+
+ LOG_INFO("Registering native methods");
+ env->RegisterNatives(linker_class, kNativeMethods,
+ sizeof(kNativeMethods) / sizeof(kNativeMethods[0]));
+
+ // Find LibInfo field ids.
+ LOG_INFO("Caching field IDs");
+ if (!s_lib_info_fields.Init(env)) {
+ return false;
+ }
+
+ return true;
+}
+
+// JNI_OnLoad() hook called when the linker library is loaded through
+// the regular System.LoadLibrary) API. This shall save the Java VM
+// handle and initialize LibInfo fields.
+jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+ LOG_INFO("Entering");
+ // Get new JNIEnv
+ JNIEnv* env;
+ if (JNI_OK != vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_4)) {
+ LOG_ERROR("Could not create JNIEnv");
+ return -1;
+ }
+
+ // Initialize linker base and implementations.
+ if (!LinkerJNIInit(vm, env) || !LegacyLinkerJNIInit(vm, env) ||
+ !ModernLinkerJNIInit(vm, env)) {
+ return -1;
+ }
+
+ LOG_INFO("Done");
+ return JNI_VERSION_1_4;
+}
+
+} // namespace
+} // namespace chromium_android_linker
+
+jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+ return chromium_android_linker::JNI_OnLoad(vm, reserved);
+}
diff --git a/android/linker/linker_jni.h b/android/linker/linker_jni.h
new file mode 100644
index 0000000..55dc6f9
--- /dev/null
+++ b/android/linker/linker_jni.h
@@ -0,0 +1,206 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This is the Android-specific Chromium linker, a tiny shared library
+// implementing a custom dynamic linker that can be used to load the
+// real Chromium libraries.
+
+// The main point of this linker is to be able to share the RELRO
+// section of libcontentshell.so (or equivalent) between the browser and
+// renderer process.
+
+// This source code *cannot* depend on anything from base/ or the C++
+// STL, to keep the final library small, and avoid ugly dependency issues.
+
+#ifndef BASE_ANDROID_LINKER_LINKER_JNI_H_
+#define BASE_ANDROID_LINKER_LINKER_JNI_H_
+
+#include <android/log.h>
+#include <jni.h>
+#include <stdlib.h>
+
+// Set this to 1 to enable debug traces to the Android log.
+// Note that LOG() from "base/logging.h" cannot be used, since it is
+// in base/ which hasn't been loaded yet.
+#define DEBUG 0
+
+#define TAG "cr.chromium_android_linker"
+
+#if DEBUG
+#define LOG_INFO(FORMAT, ...) \
+ __android_log_print(ANDROID_LOG_INFO, TAG, "%s: " FORMAT, __FUNCTION__, \
+ ##__VA_ARGS__)
+#else
+#define LOG_INFO(FORMAT, ...) ((void)0)
+#endif
+#define LOG_ERROR(FORMAT, ...) \
+ __android_log_print(ANDROID_LOG_ERROR, TAG, "%s: " FORMAT, __FUNCTION__, \
+ ##__VA_ARGS__)
+
+#define UNUSED __attribute__((unused))
+
+// See commentary in crazy_linker_elf_loader.cpp for the effect of setting
+// this. If changing there, change here also.
+//
+// For more, see:
+// https://crbug.com/504410
+#define RESERVE_BREAKPAD_GUARD_REGION 1
+
+namespace chromium_android_linker {
+
+// Larger than the largest library we might attempt to load.
+static const size_t kAddressSpaceReservationSize = 192 * 1024 * 1024;
+
+// Size of any Breakpad guard region. 16MB is comfortably larger than the
+// ~6MB relocation packing of the current 64-bit libchrome.so, the largest we
+// expect to encounter.
+#if RESERVE_BREAKPAD_GUARD_REGION
+static const size_t kBreakpadGuardRegionBytes = 16 * 1024 * 1024;
+#endif
+
+// A simple scoped UTF String class that can be initialized from
+// a Java jstring handle. Modeled like std::string, which cannot
+// be used here.
+class String {
+ public:
+ String(JNIEnv* env, jstring str);
+
+ inline ~String() { ::free(ptr_); }
+
+ inline const char* c_str() const { return ptr_ ? ptr_ : ""; }
+ inline size_t size() const { return size_; }
+
+ private:
+ char* ptr_;
+ size_t size_;
+};
+
+// Return true iff |address| is a valid address for the target CPU.
+inline bool IsValidAddress(jlong address) {
+ return static_cast<jlong>(static_cast<size_t>(address)) == address;
+}
+
+// Find the jclass JNI reference corresponding to a given |class_name|.
+// |env| is the current JNI environment handle.
+// On success, return true and set |*clazz|.
+extern bool InitClassReference(JNIEnv* env,
+ const char* class_name,
+ jclass* clazz);
+
+// Initialize a jfieldID corresponding to the field of a given |clazz|,
+// with name |field_name| and signature |field_sig|.
+// |env| is the current JNI environment handle.
+// On success, return true and set |*field_id|.
+extern bool InitFieldId(JNIEnv* env,
+ jclass clazz,
+ const char* field_name,
+ const char* field_sig,
+ jfieldID* field_id);
+
+// Initialize a jmethodID corresponding to the static method of a given
+// |clazz|, with name |method_name| and signature |method_sig|.
+// |env| is the current JNI environment handle.
+// On success, return true and set |*method_id|.
+extern bool InitStaticMethodId(JNIEnv* env,
+ jclass clazz,
+ const char* method_name,
+ const char* method_sig,
+ jmethodID* method_id);
+
+// Initialize a jfieldID corresponding to the static field of a given |clazz|,
+// with name |field_name| and signature |field_sig|.
+// |env| is the current JNI environment handle.
+// On success, return true and set |*field_id|.
+extern bool InitStaticFieldId(JNIEnv* env,
+ jclass clazz,
+ const char* field_name,
+ const char* field_sig,
+ jfieldID* field_id);
+
+// Initialize a jint corresponding to the static integer field of a class
+// with class name |class_name| and field name |field_name|.
+// |env| is the current JNI environment handle.
+// On success, return true and set |*value|.
+extern bool InitStaticInt(JNIEnv* env,
+ const char* class_name,
+ const char* field_name,
+ jint* value);
+
+// Use Android ASLR to create a random library load address.
+// |env| is the current JNI environment handle, and |clazz| a class.
+// Returns the address selected by ASLR.
+extern jlong GetRandomBaseLoadAddress(JNIEnv* env, jclass clazz);
+
+// A class used to model the field IDs of the org.chromium.base.Linker
+// LibInfo inner class, used to communicate data with the Java side
+// of the linker.
+struct LibInfo_class {
+ jfieldID load_address_id;
+ jfieldID load_size_id;
+ jfieldID relro_start_id;
+ jfieldID relro_size_id;
+ jfieldID relro_fd_id;
+
+ // Initialize an instance.
+ bool Init(JNIEnv* env) {
+ jclass clazz;
+ if (!InitClassReference(
+ env, "org/chromium/base/library_loader/Linker$LibInfo", &clazz)) {
+ return false;
+ }
+
+ return InitFieldId(env, clazz, "mLoadAddress", "J", &load_address_id) &&
+ InitFieldId(env, clazz, "mLoadSize", "J", &load_size_id) &&
+ InitFieldId(env, clazz, "mRelroStart", "J", &relro_start_id) &&
+ InitFieldId(env, clazz, "mRelroSize", "J", &relro_size_id) &&
+ InitFieldId(env, clazz, "mRelroFd", "I", &relro_fd_id);
+ }
+
+ void SetLoadInfo(JNIEnv* env,
+ jobject library_info_obj,
+ size_t load_address,
+ size_t load_size) {
+ env->SetLongField(library_info_obj, load_address_id, load_address);
+ env->SetLongField(library_info_obj, load_size_id, load_size);
+ }
+
+ void SetRelroInfo(JNIEnv* env,
+ jobject library_info_obj,
+ size_t relro_start,
+ size_t relro_size,
+ int relro_fd) {
+ env->SetLongField(library_info_obj, relro_start_id, relro_start);
+ env->SetLongField(library_info_obj, relro_size_id, relro_size);
+ env->SetIntField(library_info_obj, relro_fd_id, relro_fd);
+ }
+
+ // Use this instance to convert a RelroInfo reference into
+ // a crazy_library_info_t.
+ void GetRelroInfo(JNIEnv* env,
+ jobject library_info_obj,
+ size_t* relro_start,
+ size_t* relro_size,
+ int* relro_fd) {
+ if (relro_start) {
+ *relro_start = static_cast<size_t>(
+ env->GetLongField(library_info_obj, relro_start_id));
+ }
+
+ if (relro_size) {
+ *relro_size = static_cast<size_t>(
+ env->GetLongField(library_info_obj, relro_size_id));
+ }
+
+ if (relro_fd) {
+ *relro_fd = env->GetIntField(library_info_obj, relro_fd_id);
+ }
+ }
+};
+
+// Variable containing LibInfo for the loaded library.
+extern LibInfo_class s_lib_info_fields;
+
+} // namespace chromium_android_linker
+
+#endif // BASE_ANDROID_LINKER_LINKER_JNI_H_
diff --git a/android/linker/modern_linker_jni.cc b/android/linker/modern_linker_jni.cc
new file mode 100644
index 0000000..8661636
--- /dev/null
+++ b/android/linker/modern_linker_jni.cc
@@ -0,0 +1,595 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This is the version of the Android-specific Chromium linker that uses
+// the Android M and later system linker to load libraries.
+
+// This source code *cannot* depend on anything from base/ or the C++
+// STL, to keep the final library small, and avoid ugly dependency issues.
+
+#include "modern_linker_jni.h"
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <jni.h>
+#include <limits.h>
+#include <link.h>
+#include <string.h>
+
+#include "android_dlext.h"
+#include "linker_jni.h"
+
+#define PAGE_START(x) ((x)&PAGE_MASK)
+#define PAGE_END(x) PAGE_START((x) + (PAGE_SIZE - 1))
+
+namespace chromium_android_linker {
+namespace {
+
+// Record of the Java VM passed to JNI_OnLoad().
+static JavaVM* s_java_vm = nullptr;
+
+// Get the CPU ABI string for which the linker is running.
+//
+// The returned string is used to construct the path to libchrome.so when
+// loading directly from APK.
+//
+// |env| is the current JNI environment handle.
+// |clazz| is the static class handle for org.chromium.base.Linker,
+// and is ignored here.
+// Returns the CPU ABI string for which the linker is running.
+jstring GetCpuAbi(JNIEnv* env, jclass clazz) {
+#if defined(__arm__) && defined(__ARM_ARCH_7A__)
+ static const char* kCurrentABI = "armeabi-v7a";
+#elif defined(__arm__)
+ static const char* kCurrentABI = "armeabi";
+#elif defined(__i386__)
+ static const char* kCurrentABI = "x86";
+#elif defined(__mips__)
+ static const char* kCurrentABI = "mips";
+#elif defined(__x86_64__)
+ static const char* kCurrentABI = "x86_64";
+#elif defined(__aarch64__)
+ static const char* kCurrentABI = "arm64-v8a";
+#else
+#error "Unsupported target abi"
+#endif
+ return env->NewStringUTF(kCurrentABI);
+}
+
+// Convenience wrapper around dlsym() on the main executable. Returns
+// the address of the requested symbol, or nullptr if not found. Status
+// is available from dlerror().
+void* Dlsym(const char* symbol_name) {
+ static void* handle = nullptr;
+
+ if (!handle)
+ handle = dlopen(nullptr, RTLD_NOW);
+
+ void* result = dlsym(handle, symbol_name);
+ return result;
+}
+
+// dl_iterate_phdr() wrapper, accessed via dlsym lookup. Done this way.
+// so that this code compiles for Android versions that are too early to
+// offer it. Checks in LibraryLoader.java should ensure that we
+// never reach here at runtime on Android versions that are too old to
+// supply dl_iterate_phdr; that is, earlier than Android M. Returns
+// false if no dl_iterate_phdr() is available, otherwise true with the
+// return value from dl_iterate_phdr() in |status|.
+bool DlIteratePhdr(int (*callback)(dl_phdr_info*, size_t, void*),
+ void* data,
+ int* status) {
+ using DlIteratePhdrCallback = int (*)(dl_phdr_info*, size_t, void*);
+ using DlIteratePhdrFunctionPtr = int (*)(DlIteratePhdrCallback, void*);
+ static DlIteratePhdrFunctionPtr function_ptr = nullptr;
+
+ if (!function_ptr) {
+ function_ptr =
+ reinterpret_cast<DlIteratePhdrFunctionPtr>(Dlsym("dl_iterate_phdr"));
+ if (!function_ptr) {
+ LOG_ERROR("dlsym: dl_iterate_phdr: %s", dlerror());
+ return false;
+ }
+ }
+
+ *status = (*function_ptr)(callback, data);
+ return true;
+}
+
+// Convenience struct wrapper round android_dlextinfo.
+struct AndroidDlextinfo {
+ AndroidDlextinfo(int flags,
+ void* reserved_addr,
+ size_t reserved_size,
+ int relro_fd) {
+ memset(&extinfo, 0, sizeof(extinfo));
+ extinfo.flags = flags;
+ extinfo.reserved_addr = reserved_addr;
+ extinfo.reserved_size = reserved_size;
+ extinfo.relro_fd = relro_fd;
+ }
+
+ android_dlextinfo extinfo;
+};
+
+// android_dlopen_ext() wrapper, accessed via dlsym lookup. Returns false
+// if no android_dlopen_ext() is available, otherwise true with the return
+// value from android_dlopen_ext() in |status|.
+bool AndroidDlopenExt(const char* filename,
+ int flag,
+ const AndroidDlextinfo* dlextinfo,
+ void** status) {
+ using DlopenExtFunctionPtr =
+ void* (*)(const char*, int, const android_dlextinfo*);
+ static DlopenExtFunctionPtr function_ptr = nullptr;
+
+ if (!function_ptr) {
+ function_ptr =
+ reinterpret_cast<DlopenExtFunctionPtr>(Dlsym("android_dlopen_ext"));
+ if (!function_ptr) {
+ LOG_ERROR("dlsym: android_dlopen_ext: %s", dlerror());
+ return false;
+ }
+ }
+
+ const android_dlextinfo* extinfo = &dlextinfo->extinfo;
+ LOG_INFO(
+ "android_dlopen_ext:"
+ " flags=0x%llx, reserved_addr=%p, reserved_size=%d, relro_fd=%d",
+ extinfo->flags, extinfo->reserved_addr, extinfo->reserved_size,
+ extinfo->relro_fd);
+
+ *status = (*function_ptr)(filename, flag, extinfo);
+ return true;
+}
+
+// Callback data for FindLoadedLibrarySize().
+struct CallbackData {
+ explicit CallbackData(void* address) : load_address(address), load_size(0) {}
+
+ const void* load_address;
+ size_t load_size;
+};
+
+// Callback for dl_iterate_phdr(). Read phdrs to identify whether or not
+// this library's load address matches the |load_address| passed in
+// |data|. If yes, pass back load size via |data|. A non-zero return value
+// terminates iteration.
+int FindLoadedLibrarySize(dl_phdr_info* info, size_t size UNUSED, void* data) {
+ CallbackData* callback_data = reinterpret_cast<CallbackData*>(data);
+
+ // Use max and min vaddr to compute the library's load size.
+ ElfW(Addr) min_vaddr = ~0;
+ ElfW(Addr) max_vaddr = 0;
+
+ bool is_matching = false;
+ for (size_t i = 0; i < info->dlpi_phnum; ++i) {
+ const ElfW(Phdr)* phdr = &info->dlpi_phdr[i];
+ if (phdr->p_type != PT_LOAD)
+ continue;
+
+ // See if this segment's load address matches what we passed to
+ // android_dlopen_ext as extinfo.reserved_addr.
+ void* load_addr = reinterpret_cast<void*>(info->dlpi_addr + phdr->p_vaddr);
+ if (load_addr == callback_data->load_address)
+ is_matching = true;
+
+ if (phdr->p_vaddr < min_vaddr)
+ min_vaddr = phdr->p_vaddr;
+ if (phdr->p_vaddr + phdr->p_memsz > max_vaddr)
+ max_vaddr = phdr->p_vaddr + phdr->p_memsz;
+ }
+
+ // If this library matches what we seek, return its load size.
+ if (is_matching) {
+ callback_data->load_size = PAGE_END(max_vaddr) - PAGE_START(min_vaddr);
+ return true;
+ }
+
+ return false;
+}
+
+// Helper class for anonymous memory mapping.
+class ScopedAnonymousMmap {
+ public:
+ ScopedAnonymousMmap(void* addr, size_t size);
+
+ ~ScopedAnonymousMmap() { munmap(addr_, size_); }
+
+ void* GetAddr() const { return effective_addr_; }
+ void Release() {
+ addr_ = nullptr;
+ size_ = 0;
+ effective_addr_ = nullptr;
+ }
+
+ private:
+ void* addr_;
+ size_t size_;
+
+ // The effective_addr_ is the address seen by client code. It may or may
+ // not be the same as addr_, the real start of the anonymous mapping.
+ void* effective_addr_;
+};
+
+// ScopedAnonymousMmap constructor. |addr| is a requested mapping address, or
+// zero if any address will do, and |size| is the size of mapping required.
+ScopedAnonymousMmap::ScopedAnonymousMmap(void* addr, size_t size) {
+#if RESERVE_BREAKPAD_GUARD_REGION
+ // Increase size to extend the address reservation mapping so that it will
+ // also include a guard region from load_bias_ to start_addr. If loading
+ // at a fixed address, move our requested address back by the guard region
+ // size.
+ size += kBreakpadGuardRegionBytes;
+ if (addr) {
+ if (addr < reinterpret_cast<void*>(kBreakpadGuardRegionBytes)) {
+ LOG_ERROR("Fixed address %p is too low to accommodate Breakpad guard",
+ addr);
+ addr_ = MAP_FAILED;
+ size_ = 0;
+ return;
+ }
+ addr = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(addr) -
+ kBreakpadGuardRegionBytes);
+ }
+ LOG_INFO("Added %d to size, for Breakpad guard", kBreakpadGuardRegionBytes);
+#endif
+
+ addr_ = mmap(addr, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (addr_ != MAP_FAILED) {
+ size_ = size;
+ } else {
+ LOG_INFO("mmap failed: %s", strerror(errno));
+ size_ = 0;
+ }
+ effective_addr_ = addr_;
+
+#if RESERVE_BREAKPAD_GUARD_REGION
+ // If we increased size to accommodate a Breakpad guard region, move
+ // the effective address, if valid, upwards by the size of the guard region.
+ if (addr_ == MAP_FAILED)
+ return;
+ if (addr_ < reinterpret_cast<void*>(kBreakpadGuardRegionBytes)) {
+ LOG_ERROR("Map address %p is too low to accommodate Breakpad guard", addr_);
+ effective_addr_ = MAP_FAILED;
+ } else {
+ effective_addr_ = reinterpret_cast<void*>(
+ reinterpret_cast<uintptr_t>(addr_) + kBreakpadGuardRegionBytes);
+ }
+#endif
+}
+
+// Helper for LoadLibrary(). Return the actual size of the library loaded
+// at |addr| in |load_size|. Returns false if the library appears not to
+// be loaded.
+bool GetLibraryLoadSize(void* addr, size_t* load_size) {
+ LOG_INFO("Called for %p", addr);
+
+ // Find the real load size for the library loaded at |addr|.
+ CallbackData callback_data(addr);
+ int status = 0;
+ if (!DlIteratePhdr(&FindLoadedLibrarySize, &callback_data, &status)) {
+ LOG_ERROR("No dl_iterate_phdr function found");
+ return false;
+ }
+ if (!status) {
+ LOG_ERROR("Failed to find library at address %p", addr);
+ return false;
+ }
+
+ *load_size = callback_data.load_size;
+ return true;
+}
+
+// Helper for LoadLibrary(). We reserve an address space larger than
+// needed. After library loading we want to trim that reservation to only
+// what is needed.
+bool ResizeReservedAddressSpace(void* addr,
+ size_t reserved_size,
+ size_t load_size) {
+ LOG_INFO("Called for %p, reserved %d, loaded %d", addr, reserved_size,
+ load_size);
+
+ if (reserved_size < load_size) {
+ LOG_ERROR("WARNING: library reservation was too small");
+ return true;
+ }
+
+ // Unmap the part of the reserved address space that is beyond the end of
+ // the loaded library data.
+ void* unmap =
+ reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(addr) + load_size);
+ size_t length = reserved_size - load_size;
+ if (munmap(unmap, length) == -1) {
+ LOG_ERROR("Failed to unmap %d at %p", static_cast<int>(length), unmap);
+ return false;
+ }
+
+ return true;
+}
+
+// Load a library with the chromium linker, using android_dlopen_ext().
+//
+// android_dlopen_ext() understands how to directly load from a zipfile,
+// based on the format of |dlopen_ext_path|. If it contains a "!/" separator
+// then the string indicates <zip_path>!/<file_path> and indicates the
+// file_path element within the zip file at zip_path. A library in a
+// zipfile must be uncompressed and page aligned. The library is expected
+// to be lib/<abi_tag>/crazy.<basename>. The <abi_tag> used will be the
+// same as the abi for this linker. The "crazy." prefix is included
+// so that the Android Package Manager doesn't extract the library into
+// /data/app-lib.
+//
+// If |dlopen_ext_path| contains no "!/" separator then android_dlopen_ext()
+// assumes that it is a normal path to a standalone library file.
+//
+// Loading the library will also call its JNI_OnLoad() method, which
+// shall register its methods. Note that lazy native method resolution
+// will _not_ work after this, because Dalvik uses the system's dlsym()
+// which won't see the new library, so explicit registration is mandatory.
+// Load a library with the chromium linker. This will also call its
+// JNI_OnLoad() method, which shall register its methods. Note that
+// lazy native method resolution will _not_ work after this, because
+// Dalvik uses the system's dlsym() which won't see the new library,
+// so explicit registration is mandatory.
+//
+// |env| is the current JNI environment handle.
+// |clazz| is the static class handle for org.chromium.base.Linker,
+// and is ignored here.
+// |dlopen_ext_path| is the library identifier (e.g. libfoo.so).
+// |load_address| is an explicit load address.
+// |relro_path| is the path to the file into which RELRO data is held.
+// |lib_info_obj| is a LibInfo handle used to communicate information
+// with the Java side.
+// Return true on success.
+jboolean LoadLibrary(JNIEnv* env,
+ jclass clazz,
+ jstring dlopen_ext_path,
+ jlong load_address,
+ jobject lib_info_obj) {
+ String dlopen_library_path(env, dlopen_ext_path);
+ LOG_INFO("Called for %s, at address 0x%llx", dlopen_library_path.c_str(),
+ load_address);
+
+ if (!IsValidAddress(load_address)) {
+ LOG_ERROR("Invalid address 0x%llx", load_address);
+ return false;
+ }
+
+ const size_t size = kAddressSpaceReservationSize;
+ void* wanted_addr = reinterpret_cast<void*>(load_address);
+
+ // Reserve the address space into which we load the library.
+ ScopedAnonymousMmap mapping(wanted_addr, size);
+ void* addr = mapping.GetAddr();
+ if (addr == MAP_FAILED) {
+ LOG_ERROR("Failed to reserve space for load");
+ return false;
+ }
+ if (wanted_addr && addr != wanted_addr) {
+ LOG_ERROR("Failed to obtain fixed address for load");
+ return false;
+ }
+
+ // Build dlextinfo to load the library into the reserved space, using
+ // the shared RELRO if supplied and if its start address matches addr.
+ int relro_fd = -1;
+ int flags = ANDROID_DLEXT_RESERVED_ADDRESS;
+ if (wanted_addr && lib_info_obj) {
+ void* relro_start;
+ s_lib_info_fields.GetRelroInfo(env, lib_info_obj,
+ reinterpret_cast<size_t*>(&relro_start),
+ nullptr, &relro_fd);
+ if (relro_fd != -1 && relro_start == addr) {
+ flags |= ANDROID_DLEXT_USE_RELRO;
+ }
+ }
+ AndroidDlextinfo dlextinfo(flags, addr, size, relro_fd);
+
+ // Load the library into the reserved space.
+ const char* path = dlopen_library_path.c_str();
+ void* handle = nullptr;
+ if (!AndroidDlopenExt(path, RTLD_NOW, &dlextinfo, &handle)) {
+ LOG_ERROR("No android_dlopen_ext function found");
+ return false;
+ }
+ if (handle == nullptr) {
+ LOG_ERROR("android_dlopen_ext: %s", dlerror());
+ return false;
+ }
+
+ // After loading, trim the mapping to match the library's actual size.
+ size_t load_size = 0;
+ if (!GetLibraryLoadSize(addr, &load_size)) {
+ LOG_ERROR("Unable to find size for load at %p", addr);
+ return false;
+ }
+ if (!ResizeReservedAddressSpace(addr, size, load_size)) {
+ LOG_ERROR("Unable to resize reserved address mapping");
+ return false;
+ }
+
+ // Locate and then call the loaded library's JNI_OnLoad() function. Check
+ // that it returns a usable JNI version.
+ using JNI_OnLoadFunctionPtr = int (*)(void* vm, void* reserved);
+ auto jni_onload =
+ reinterpret_cast<JNI_OnLoadFunctionPtr>(dlsym(handle, "JNI_OnLoad"));
+ if (jni_onload == nullptr) {
+ LOG_ERROR("dlsym: JNI_OnLoad: %s", dlerror());
+ return false;
+ }
+
+ int jni_version = (*jni_onload)(s_java_vm, nullptr);
+ if (jni_version < JNI_VERSION_1_4) {
+ LOG_ERROR("JNI version is invalid: %d", jni_version);
+ return false;
+ }
+
+ // Release mapping before returning so that we do not unmap reserved space.
+ mapping.Release();
+
+ // Note the load address and load size in the supplied libinfo object.
+ const size_t cast_addr = reinterpret_cast<size_t>(addr);
+ s_lib_info_fields.SetLoadInfo(env, lib_info_obj, cast_addr, load_size);
+
+ LOG_INFO("Success loading library %s", dlopen_library_path.c_str());
+ return true;
+}
+
+// Create a shared RELRO file for a library, using android_dlopen_ext().
+//
+// Loads the library similarly to LoadLibrary() above, by reserving address
+// space and then using android_dlopen_ext() to load into the reserved
+// area. Adds flags to android_dlopen_ext() to saved the library's RELRO
+// memory into the given file path, then unload the library and returns.
+//
+// Does not call JNI_OnLoad() or otherwise execute any code from the library.
+//
+// |env| is the current JNI environment handle.
+// |clazz| is the static class handle for org.chromium.base.Linker,
+// and is ignored here.
+// |dlopen_ext_path| is the library identifier (e.g. libfoo.so).
+// |load_address| is an explicit load address.
+// |relro_path| is the path to the file into which RELRO data is written.
+// |lib_info_obj| is a LibInfo handle used to communicate information
+// with the Java side.
+// Return true on success.
+jboolean CreateSharedRelro(JNIEnv* env,
+ jclass clazz,
+ jstring dlopen_ext_path,
+ jlong load_address,
+ jstring relro_path,
+ jobject lib_info_obj) {
+ String dlopen_library_path(env, dlopen_ext_path);
+ LOG_INFO("Called for %s, at address 0x%llx", dlopen_library_path.c_str(),
+ load_address);
+
+ if (!IsValidAddress(load_address) || load_address == 0) {
+ LOG_ERROR("Invalid address 0x%llx", load_address);
+ return false;
+ }
+
+ const size_t size = kAddressSpaceReservationSize;
+ void* wanted_addr = reinterpret_cast<void*>(load_address);
+
+ // Reserve the address space into which we load the library.
+ ScopedAnonymousMmap mapping(wanted_addr, size);
+ void* addr = mapping.GetAddr();
+ if (addr == MAP_FAILED) {
+ LOG_ERROR("Failed to reserve space for load");
+ return false;
+ }
+ if (addr != wanted_addr) {
+ LOG_ERROR("Failed to obtain fixed address for load");
+ return false;
+ }
+
+ // Open the shared RELRO file for write. Overwrites any prior content.
+ String shared_relro_path(env, relro_path);
+ const char* filepath = shared_relro_path.c_str();
+ unlink(filepath);
+ int relro_fd = open(filepath, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
+ if (relro_fd == -1) {
+ LOG_ERROR("open: %s: %s", filepath, strerror(errno));
+ return false;
+ }
+
+ // Use android_dlopen_ext() to create the shared RELRO.
+ const int flags = ANDROID_DLEXT_RESERVED_ADDRESS | ANDROID_DLEXT_WRITE_RELRO;
+ AndroidDlextinfo dlextinfo(flags, addr, size, relro_fd);
+
+ const char* path = dlopen_library_path.c_str();
+ void* handle = nullptr;
+ if (!AndroidDlopenExt(path, RTLD_NOW, &dlextinfo, &handle)) {
+ LOG_ERROR("No android_dlopen_ext function found");
+ close(relro_fd);
+ return false;
+ }
+ if (handle == nullptr) {
+ LOG_ERROR("android_dlopen_ext: %s", dlerror());
+ close(relro_fd);
+ return false;
+ }
+
+ // Unload the library from this address. The reserved space is
+ // automatically unmapped on exit from this function.
+ dlclose(handle);
+
+ // Reopen the shared RELFO fd in read-only mode. This ensures that nothing
+ // can write to it through the RELRO fd that we return in libinfo.
+ close(relro_fd);
+ relro_fd = open(filepath, O_RDONLY);
+ if (relro_fd == -1) {
+ LOG_ERROR("open: %s: %s", filepath, strerror(errno));
+ return false;
+ }
+
+ // Delete the directory entry for the RELRO file. The fd we hold ensures
+ // that its data remains intact.
+ if (unlink(filepath) == -1) {
+ LOG_ERROR("unlink: %s: %s", filepath, strerror(errno));
+ return false;
+ }
+
+ // Note the shared RELRO fd in the supplied libinfo object. In this
+ // implementation the RELRO start is set to the library's load address,
+ // and the RELRO size is unused.
+ const size_t cast_addr = reinterpret_cast<size_t>(addr);
+ s_lib_info_fields.SetRelroInfo(env, lib_info_obj, cast_addr, 0, relro_fd);
+
+ LOG_INFO("Success creating shared RELRO %s", shared_relro_path.c_str());
+ return true;
+}
+
+const JNINativeMethod kNativeMethods[] = {
+ {"nativeGetCpuAbi",
+ "("
+ ")"
+ "Ljava/lang/String;",
+ reinterpret_cast<void*>(&GetCpuAbi)},
+ {"nativeLoadLibrary",
+ "("
+ "Ljava/lang/String;"
+ "J"
+ "Lorg/chromium/base/library_loader/Linker$LibInfo;"
+ ")"
+ "Z",
+ reinterpret_cast<void*>(&LoadLibrary)},
+ {"nativeCreateSharedRelro",
+ "("
+ "Ljava/lang/String;"
+ "J"
+ "Ljava/lang/String;"
+ "Lorg/chromium/base/library_loader/Linker$LibInfo;"
+ ")"
+ "Z",
+ reinterpret_cast<void*>(&CreateSharedRelro)},
+};
+
+} // namespace
+
+bool ModernLinkerJNIInit(JavaVM* vm, JNIEnv* env) {
+ LOG_INFO("Entering");
+
+ // Register native methods.
+ jclass linker_class;
+ if (!InitClassReference(env, "org/chromium/base/library_loader/ModernLinker",
+ &linker_class))
+ return false;
+
+ LOG_INFO("Registering native methods");
+ env->RegisterNatives(linker_class, kNativeMethods,
+ sizeof(kNativeMethods) / sizeof(kNativeMethods[0]));
+
+ // Record the Java VM handle.
+ s_java_vm = vm;
+
+ return true;
+}
+
+} // namespace chromium_android_linker
diff --git a/android/linker/modern_linker_jni.h b/android/linker/modern_linker_jni.h
new file mode 100644
index 0000000..74eb586
--- /dev/null
+++ b/android/linker/modern_linker_jni.h
@@ -0,0 +1,21 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_LINKER_MODERN_LINKER_JNI_H_
+#define BASE_ANDROID_LINKER_MODERN_LINKER_JNI_H_
+
+#include <jni.h>
+
+namespace chromium_android_linker {
+
+// JNI_OnLoad() initialization hook for the modern linker.
+// Sets up JNI and other initializations for native linker code.
+// |vm| is the Java VM handle passed to JNI_OnLoad().
+// |env| is the current JNI environment handle.
+// On success, returns true.
+extern bool ModernLinkerJNIInit(JavaVM* vm, JNIEnv* env);
+
+} // namespace chromium_android_linker
+
+#endif // BASE_ANDROID_LINKER_MODERN_LINKER_JNI_H_
diff --git a/callback_internal.h b/callback_internal.h
index fefd7a2..b6353dc 100644
--- a/callback_internal.h
+++ b/callback_internal.h
@@ -17,9 +17,6 @@
#include "base/memory/scoped_ptr.h"
#include "base/template_util.h"
-template <typename T>
-class ScopedVector;
-
namespace base {
namespace internal {
class CallbackBase;
diff --git a/file_descriptor_posix.h b/file_descriptor_posix.h
index 376ad39..2a36611 100644
--- a/file_descriptor_posix.h
+++ b/file_descriptor_posix.h
@@ -14,9 +14,16 @@
// We introduct a special structure for file descriptors in order that we are
// able to use template specialisation to special-case their handling.
//
-// WARNING: (Chromium only) There are subtleties to consider if serialising
-// these objects over IPC. See comments in ipc/ipc_message_utils.h
-// above the template specialisation for this structure.
+// IMPORTANT: This is primarily intended for use when sending file descriptors
+// over IPC. Even if |auto_close| is true, base::FileDescriptor does NOT close()
+// |fd| when going out of scope. Instead, a consumer of a base::FileDescriptor
+// must invoke close() on |fd| if |auto_close| is true.
+//
+// In the case of IPC, the the IPC subsystem knows to close() |fd| after sending
+// a message that contains a base::FileDescriptor if auto_close == true. On the
+// other end, the receiver must make sure to close() |fd| after it has finished
+// processing the IPC message. See the IPC::ParamTraits<> specialization in
+// ipc/ipc_message_utils.h for all the details.
// -----------------------------------------------------------------------------
struct FileDescriptor {
FileDescriptor() : fd(-1), auto_close(false) {}
diff --git a/files/file_path_watcher_linux.cc b/files/file_path_watcher_linux.cc
index 50827ac..6dfc0a6 100644
--- a/files/file_path_watcher_linux.cc
+++ b/files/file_path_watcher_linux.cc
@@ -28,6 +28,7 @@
#include "base/memory/scoped_ptr.h"
#include "base/posix/eintr_wrapper.h"
#include "base/single_thread_task_runner.h"
+#include "base/stl_util.h"
#include "base/synchronization/lock.h"
#include "base/thread_task_runner_handle.h"
#include "base/threading/thread.h"
diff --git a/i18n/case_conversion_unittest.cc b/i18n/case_conversion_unittest.cc
index dc5bc1f..ee795bc 100644
--- a/i18n/case_conversion_unittest.cc
+++ b/i18n/case_conversion_unittest.cc
@@ -5,6 +5,7 @@
#include "base/i18n/case_conversion.h"
#include "base/i18n/rtl.h"
#include "base/strings/utf_string_conversions.h"
+#include "base/test/icu_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/icu/source/i18n/unicode/usearch.h"
@@ -58,7 +59,7 @@
const string16 expected_lower(WideToUTF16(L"\x69\x131"));
const string16 expected_upper(WideToUTF16(L"\x49\x49"));
- std::string default_locale(uloc_getDefault());
+ test::ScopedRestoreICUDefaultLocale restore_locale;
i18n::SetICUDefaultLocale("en_US");
string16 result = ToLower(mixed);
@@ -77,8 +78,6 @@
result = ToUpper(mixed);
EXPECT_EQ(expected_upper_turkish, result);
-
- SetICUDefaultLocale(default_locale.data());
}
TEST(CaseConversionTest, FoldCase) {
@@ -97,7 +96,7 @@
const string16 turkish(WideToUTF16(L"\x49\x131"));
const string16 turkish_expected(WideToUTF16(L"\x69\x131"));
- std::string default_locale(uloc_getDefault());
+ test::ScopedRestoreICUDefaultLocale restore_locale;
i18n::SetICUDefaultLocale("en_US");
EXPECT_EQ(turkish_expected, FoldCase(turkish));
diff --git a/i18n/number_formatting_unittest.cc b/i18n/number_formatting_unittest.cc
index 3b0718d..dc6de2b 100644
--- a/i18n/number_formatting_unittest.cc
+++ b/i18n/number_formatting_unittest.cc
@@ -7,7 +7,9 @@
#include "base/i18n/number_formatting.h"
#include "base/i18n/rtl.h"
#include "base/strings/utf_string_conversions.h"
+#include "base/test/icu_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/icu/source/i18n/unicode/usearch.h"
namespace base {
namespace {
@@ -27,6 +29,8 @@
{-42, "-42", "-42"},
};
+ test::ScopedRestoreICUDefaultLocale restore_locale;
+
for (size_t i = 0; i < arraysize(cases); ++i) {
i18n::SetICUDefaultLocale("en");
testing::ResetFormatters();
@@ -72,6 +76,7 @@
{-42.7, 3, "-42.700", "-42,700"},
};
+ test::ScopedRestoreICUDefaultLocale restore_locale;
for (size_t i = 0; i < arraysize(cases); ++i) {
i18n::SetICUDefaultLocale("en");
testing::ResetFormatters();
diff --git a/i18n/time_formatting_unittest.cc b/i18n/time_formatting_unittest.cc
index 9385628..7b7d4d5 100644
--- a/i18n/time_formatting_unittest.cc
+++ b/i18n/time_formatting_unittest.cc
@@ -7,6 +7,7 @@
#include "base/i18n/rtl.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/utf_string_conversions.h"
+#include "base/test/icu_test_util.h"
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/icu/source/common/unicode/uversion.h"
@@ -41,6 +42,7 @@
TEST(TimeFormattingTest, TimeFormatTimeOfDayDefault12h) {
// Test for a locale defaulted to 12h clock.
// As an instance, we use third_party/icu/source/data/locales/en.txt.
+ test::ScopedRestoreICUDefaultLocale restore_locale;
i18n::SetICUDefaultLocale("en_US");
Time time(Time::FromLocalExploded(kTestDateTimeExploded));
@@ -76,6 +78,7 @@
TEST(TimeFormattingTest, TimeFormatTimeOfDayDefault24h) {
// Test for a locale defaulted to 24h clock.
// As an instance, we use third_party/icu/source/data/locales/en_GB.txt.
+ test::ScopedRestoreICUDefaultLocale restore_locale;
i18n::SetICUDefaultLocale("en_GB");
Time time(Time::FromLocalExploded(kTestDateTimeExploded));
@@ -111,6 +114,7 @@
TEST(TimeFormattingTest, TimeFormatTimeOfDayJP) {
// Test for a locale that uses different mark than "AM" and "PM".
// As an instance, we use third_party/icu/source/data/locales/ja.txt.
+ test::ScopedRestoreICUDefaultLocale restore_locale;
i18n::SetICUDefaultLocale("ja_JP");
Time time(Time::FromLocalExploded(kTestDateTimeExploded));
@@ -144,6 +148,7 @@
TEST(TimeFormattingTest, TimeFormatDateUS) {
// See third_party/icu/source/data/locales/en.txt.
// The date patterns are "EEEE, MMMM d, y", "MMM d, y", and "M/d/yy".
+ test::ScopedRestoreICUDefaultLocale restore_locale;
i18n::SetICUDefaultLocale("en_US");
Time time(Time::FromLocalExploded(kTestDateTimeExploded));
@@ -166,6 +171,7 @@
TEST(TimeFormattingTest, TimeFormatDateGB) {
// See third_party/icu/source/data/locales/en_GB.txt.
// The date patterns are "EEEE, d MMMM y", "d MMM y", and "dd/MM/yyyy".
+ test::ScopedRestoreICUDefaultLocale restore_locale;
i18n::SetICUDefaultLocale("en_GB");
Time time(Time::FromLocalExploded(kTestDateTimeExploded));
diff --git a/memory/BUILD.gn b/memory/BUILD.gn
index 1bbbc7a..4250631 100644
--- a/memory/BUILD.gn
+++ b/memory/BUILD.gn
@@ -49,6 +49,8 @@
if (is_ios) {
sources -= [
"discardable_shared_memory.cc",
+ "discardable_shared_memory.h",
+ "shared_memory.h",
"shared_memory_posix.cc",
]
}
diff --git a/message_loop/message_loop.cc b/message_loop/message_loop.cc
index 51db741..f350566 100644
--- a/message_loop/message_loop.cc
+++ b/message_loop/message_loop.cc
@@ -475,7 +475,7 @@
HistogramEvent(kTaskRunEvent);
- TRACE_TASK_EXECUTION("toplevel", pending_task);
+ TRACE_TASK_EXECUTION("MessageLoop::RunTask", pending_task);
FOR_EACH_OBSERVER(TaskObserver, task_observers_,
WillProcessTask(pending_task));
diff --git a/prefs/json_pref_store.h b/prefs/json_pref_store.h
index 0be7702..d9dcdbd 100644
--- a/prefs/json_pref_store.h
+++ b/prefs/json_pref_store.h
@@ -15,7 +15,6 @@
#include "base/files/important_file_writer.h"
#include "base/gtest_prod_util.h"
#include "base/memory/scoped_ptr.h"
-#include "base/memory/scoped_vector.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/prefs/base_prefs_export.h"
diff --git a/process/launch_win.cc b/process/launch_win.cc
index 9515041..09a4536 100644
--- a/process/launch_win.cc
+++ b/process/launch_win.cc
@@ -175,6 +175,8 @@
// It should be ERROR_INVALID_HANDLE at this point, which means the
// browser was likely not started from a console.
AllocConsole();
+ } else {
+ return;
}
}
diff --git a/process/process_util_unittest.cc b/process/process_util_unittest.cc
index cf83aa7..08144f2 100644
--- a/process/process_util_unittest.cc
+++ b/process/process_util_unittest.cc
@@ -59,6 +59,9 @@
#include <malloc/malloc.h>
#include "base/mac/mac_util.h"
#endif
+#if defined(OS_ANDROID)
+#include "third_party/lss/linux_syscall_support.h"
+#endif
using base::FilePath;
@@ -226,7 +229,19 @@
MULTIPROCESS_TEST_MAIN(CrashingChildProcess) {
WaitToDie(ProcessUtilTest::GetSignalFilePath(kSignalFileCrash).c_str());
-#if defined(OS_POSIX)
+#if defined(OS_ANDROID)
+ // Android L+ expose signal and sigaction symbols that override the system
+ // ones. There is a bug in these functions where a request to set the handler
+ // to SIG_DFL is ignored. In that case, an infinite loop is entered as the
+ // signal is repeatedly sent to the crash dump signal handler.
+ // To work around this, directly call the system's sigaction.
+ struct kernel_sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sys_sigemptyset(&sa.sa_mask);
+ sa.sa_handler_ = SIG_DFL;
+ sa.sa_flags = SA_RESTART;
+ sys_rt_sigaction(SIGSEGV, &sa, NULL, sizeof(kernel_sigset_t));
+#elif defined(OS_POSIX)
// Have to disable to signal handler for segv so we can get a crash
// instead of an abnormal termination through the crash dump handler.
::signal(SIGSEGV, SIG_DFL);
diff --git a/profiler/native_stack_sampler_win.cc b/profiler/native_stack_sampler_win.cc
index 0718904..6cc6515 100644
--- a/profiler/native_stack_sampler_win.cc
+++ b/profiler/native_stack_sampler_win.cc
@@ -10,6 +10,7 @@
#include "base/logging.h"
#include "base/profiler/native_stack_sampler.h"
+#include "base/profiler/win32_stack_frame_unwinder.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
@@ -19,6 +20,8 @@
namespace base {
+// Stack recording functions --------------------------------------------------
+
namespace {
// Walks the stack represented by |context| from the current frame downwards,
@@ -26,41 +29,13 @@
int RecordStack(CONTEXT* context,
int max_stack_size,
const void* instruction_pointers[],
- bool* last_frame_is_unknown_function) {
+ Win32StackFrameUnwinder* frame_unwinder) {
#ifdef _WIN64
- *last_frame_is_unknown_function = false;
-
int i = 0;
for (; (i < max_stack_size) && context->Rip; ++i) {
- // Try to look up unwind metadata for the current function.
- ULONG64 image_base;
- PRUNTIME_FUNCTION runtime_function =
- RtlLookupFunctionEntry(context->Rip, &image_base, nullptr);
-
instruction_pointers[i] = reinterpret_cast<const void*>(context->Rip);
-
- if (runtime_function) {
- KNONVOLATILE_CONTEXT_POINTERS nvcontext = {};
- void* handler_data;
- ULONG64 establisher_frame;
- RtlVirtualUnwind(0, image_base, context->Rip, runtime_function, context,
- &handler_data, &establisher_frame, &nvcontext);
- } else {
- // If we don't have a RUNTIME_FUNCTION, then in theory this should be a
- // leaf function whose frame contains only a return address, at
- // RSP. However, crash data also indicates that some third party libraries
- // do not provide RUNTIME_FUNCTION information for non-leaf functions. We
- // could manually unwind the stack in the former case, but attempting to
- // do so in the latter case would produce wrong results and likely crash,
- // so just bail out.
- //
- // Ad hoc runs with instrumentation show that ~5% of stack traces end with
- // a valid leaf function. To avoid selectively omitting these traces it
- // makes sense to ultimately try to distinguish these two cases and
- // selectively unwind the stack for legitimate leaf functions. For the
- // purposes of avoiding crashes though, just ignore them all for now.
- return i;
- }
+ if (!frame_unwinder->TryUnwind(context))
+ return i + 1;
}
return i;
#else
@@ -75,11 +50,8 @@
// arrays.
void FindModuleHandlesForAddresses(const void* const addresses[],
HMODULE module_handles[],
- int stack_depth,
- bool last_frame_is_unknown_function) {
- const int module_frames =
- last_frame_is_unknown_function ? stack_depth - 1 : stack_depth;
- for (int i = 0; i < module_frames; ++i) {
+ int stack_depth) {
+ for (int i = 0; i < stack_depth; ++i) {
HMODULE module_handle = NULL;
if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
reinterpret_cast<LPCTSTR>(addresses[i]),
@@ -129,6 +101,8 @@
return WideToUTF8(build_id);
}
+// ScopedDisablePriorityBoost -------------------------------------------------
+
// Disables priority boost on a thread for the lifetime of the object.
class ScopedDisablePriorityBoost {
public:
@@ -171,8 +145,9 @@
// vectors make it too easy to subtly allocate memory.
int SuspendThreadAndRecordStack(HANDLE thread_handle,
int max_stack_size,
- const void* instruction_pointers[],
- bool* last_frame_is_unknown_function) {
+ const void* instruction_pointers[]) {
+ Win32StackFrameUnwinder frame_unwinder;
+
if (::SuspendThread(thread_handle) == -1)
return 0;
@@ -180,9 +155,8 @@
CONTEXT thread_context = {0};
thread_context.ContextFlags = CONTEXT_FULL;
if (::GetThreadContext(thread_handle, &thread_context)) {
- stack_depth =
- RecordStack(&thread_context, max_stack_size, instruction_pointers,
- last_frame_is_unknown_function);
+ stack_depth = RecordStack(&thread_context, max_stack_size,
+ instruction_pointers, &frame_unwinder);
}
// Disable the priority boost that the thread would otherwise receive on
@@ -202,6 +176,8 @@
return stack_depth;
}
+// NativeStackSamplerWin ------------------------------------------------------
+
class NativeStackSamplerWin : public NativeStackSampler {
public:
explicit NativeStackSamplerWin(win::ScopedHandle thread_handle);
@@ -264,12 +240,10 @@
const void* instruction_pointers[max_stack_size] = {0};
HMODULE module_handles[max_stack_size] = {0};
- bool last_frame_is_unknown_function = false;
int stack_depth = SuspendThreadAndRecordStack(
- thread_handle_.Get(), max_stack_size, instruction_pointers,
- &last_frame_is_unknown_function);
+ thread_handle_.Get(), max_stack_size, instruction_pointers);
FindModuleHandlesForAddresses(instruction_pointers, module_handles,
- stack_depth, last_frame_is_unknown_function);
+ stack_depth);
CopyToSample(instruction_pointers, module_handles, stack_depth, sample,
current_modules_);
FreeModuleHandles(stack_depth, module_handles);
diff --git a/profiler/win32_stack_frame_unwinder.cc b/profiler/win32_stack_frame_unwinder.cc
new file mode 100644
index 0000000..c3809a6
--- /dev/null
+++ b/profiler/win32_stack_frame_unwinder.cc
@@ -0,0 +1,210 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/profiler/win32_stack_frame_unwinder.h"
+
+#include "base/containers/hash_tables.h"
+#include "base/memory/singleton.h"
+#include "base/stl_util.h"
+
+namespace base {
+
+// LeafUnwindBlacklist --------------------------------------------------------
+
+namespace {
+
+// Records modules that are known to have functions that violate the Microsoft
+// x64 calling convention and would be dangerous to manually unwind if
+// encountered as the last frame on the call stack. Functions like these have
+// been observed in injected third party modules that either do not provide
+// function unwind information, or do not provide the required function prologue
+// and epilogue. The former case was observed in several AV products and the
+// latter in a WndProc function associated with Actual Window
+// Manager/aimemb64.dll. See https://crbug.com/476422.
+class LeafUnwindBlacklist {
+ public:
+ static LeafUnwindBlacklist* GetInstance();
+
+ // This function does not allocate memory and is safe to call between
+ // SuspendThread and ResumeThread.
+ bool IsBlacklisted(const void* module) const;
+
+ // Allocates memory. Must be invoked only after ResumeThread, otherwise we
+ // risk deadlocking on a heap lock held by a suspended thread.
+ void AddModuleToBlacklist(const void* module);
+
+ private:
+ friend struct DefaultSingletonTraits<LeafUnwindBlacklist>;
+
+ LeafUnwindBlacklist();
+ ~LeafUnwindBlacklist();
+
+ // The set of modules known to have functions that violate the Microsoft x64
+ // calling convention.
+ base::hash_set<const void*> blacklisted_modules_;
+
+ DISALLOW_COPY_AND_ASSIGN(LeafUnwindBlacklist);
+};
+
+// static
+LeafUnwindBlacklist* LeafUnwindBlacklist::GetInstance() {
+ // Leaky for shutdown performance.
+ return Singleton<LeafUnwindBlacklist,
+ LeakySingletonTraits<LeafUnwindBlacklist>>::get();
+}
+
+bool LeafUnwindBlacklist::IsBlacklisted(const void* module) const {
+ return ContainsKey(blacklisted_modules_, module);
+}
+
+void LeafUnwindBlacklist::AddModuleToBlacklist(const void* module) {
+ CHECK(module);
+ blacklisted_modules_.insert(module);
+}
+
+LeafUnwindBlacklist::LeafUnwindBlacklist() {}
+LeafUnwindBlacklist::~LeafUnwindBlacklist() {}
+
+} // namespace
+
+// Win32StackFrameUnwinder ----------------------------------------------------
+
+Win32StackFrameUnwinder::UnwindFunctions::~UnwindFunctions() {}
+Win32StackFrameUnwinder::UnwindFunctions::UnwindFunctions() {}
+
+Win32StackFrameUnwinder::Win32UnwindFunctions::Win32UnwindFunctions() {}
+
+PRUNTIME_FUNCTION
+Win32StackFrameUnwinder::Win32UnwindFunctions::LookupFunctionEntry(
+ DWORD64 program_counter,
+ PDWORD64 image_base) {
+#ifdef _WIN64
+ return RtlLookupFunctionEntry(program_counter, image_base, nullptr);
+#else
+ NOTREACHED();
+ return nullptr;
+#endif
+}
+
+void Win32StackFrameUnwinder::Win32UnwindFunctions::VirtualUnwind(
+ DWORD64 image_base,
+ DWORD64 program_counter,
+ PRUNTIME_FUNCTION runtime_function,
+ CONTEXT* context) {
+#ifdef _WIN64
+ void* handler_data;
+ ULONG64 establisher_frame;
+ KNONVOLATILE_CONTEXT_POINTERS nvcontext = {};
+ RtlVirtualUnwind(0, image_base, program_counter, runtime_function, context,
+ &handler_data, &establisher_frame, &nvcontext);
+#else
+ NOTREACHED();
+#endif
+}
+
+Win32StackFrameUnwinder::Win32StackFrameUnwinder()
+ : Win32StackFrameUnwinder(&win32_unwind_functions_) {}
+
+Win32StackFrameUnwinder::~Win32StackFrameUnwinder() {
+ if (pending_blacklisted_module_) {
+ LeafUnwindBlacklist::GetInstance()->AddModuleToBlacklist(
+ pending_blacklisted_module_);
+ }
+}
+
+bool Win32StackFrameUnwinder::TryUnwind(CONTEXT* context) {
+#ifdef _WIN64
+ CHECK(!at_top_frame_ || unwind_info_present_for_all_frames_);
+ CHECK(!pending_blacklisted_module_);
+
+ ULONG64 image_base;
+ // Try to look up unwind metadata for the current function.
+ PRUNTIME_FUNCTION runtime_function =
+ unwind_functions_->LookupFunctionEntry(context->Rip, &image_base);
+
+ if (runtime_function) {
+ unwind_functions_->VirtualUnwind(image_base, context->Rip, runtime_function,
+ context);
+ at_top_frame_ = false;
+ } else {
+ // RtlLookupFunctionEntry didn't find unwind information. This could mean
+ // the code at the instruction pointer is in:
+ //
+ // 1. a true leaf function (i.e. a function that neither calls a function,
+ // nor allocates any stack space itself) in which case the return
+ // address is at RSP, or
+ //
+ // 2. a function that doesn't adhere to the Microsoft x64 calling
+ // convention, either by not providing the required unwind information,
+ // or by not having the prologue or epilogue required for unwinding;
+ // this case has been observed in crash data in injected third party
+ // DLLs.
+ //
+ // In valid code, case 1 can only occur (by definition) as the last frame
+ // on the stack. This happens in about 5% of observed stacks and can
+ // easily be unwound by popping RSP and using it as the next frame's
+ // instruction pointer.
+ //
+ // Case 2 can occur anywhere on the stack, and attempting to unwind the
+ // stack will result in treating whatever value happens to be on the stack
+ // at RSP as the next frame's instruction pointer. This is certainly wrong
+ // and very likely to lead to crashing by deferencing invalid pointers in
+ // the next RtlVirtualUnwind call.
+ //
+ // If we see case 2 at a location not the last frame, and all the previous
+ // frame had valid unwind information, then this is definitely bad code.
+ // We blacklist the module as untrustable for unwinding if we encounter a
+ // function in it that doesn't have unwind information.
+
+ if (at_top_frame_) {
+ at_top_frame_ = false;
+
+ // We are at the end of the stack. It's very likely that we're in case 1
+ // since the vast majority of code adheres to the Microsoft x64 calling
+ // convention. But there's a small chance we might be unlucky and be in
+ // case 2. If this module is known to have bad code according to the
+ // leaf unwind blacklist, stop here, otherwise manually unwind.
+ if (LeafUnwindBlacklist::GetInstance()->IsBlacklisted(
+ reinterpret_cast<const void*>(image_base))) {
+ return false;
+ }
+
+ context->Rip = context->Rsp;
+ context->Rsp += 8;
+ unwind_info_present_for_all_frames_ = false;
+ } else {
+ // We're not at the end of the stack. This frame is untrustworthy and we
+ // can't safely unwind from here.
+ if (unwind_info_present_for_all_frames_) {
+ // Unwind information was present for all previous frames, so we can
+ // be confident this is case 2. Record the module to be blacklisted.
+ pending_blacklisted_module_ = reinterpret_cast<const void*>(image_base);
+ } else {
+ // We started off on a function without unwind information. It's very
+ // likely that all frames up to this point have been good, and this
+ // frame is case 2. But it's possible that the initial frame was case
+ // 2 but hadn't been blacklisted yet, and we've started to go off into
+ // the weeds. Since we can't be sure, just bail out without
+ // blacklisting the module; chances are we'll later encounter the same
+ // function on a stack with full unwind information.
+ }
+ return false;
+ }
+ }
+
+ return true;
+#else
+ NOTREACHED();
+ return false;
+#endif
+}
+
+Win32StackFrameUnwinder::Win32StackFrameUnwinder(
+ UnwindFunctions* unwind_functions)
+ : at_top_frame_(true),
+ unwind_info_present_for_all_frames_(true),
+ pending_blacklisted_module_(nullptr),
+ unwind_functions_(unwind_functions) {}
+
+} // namespace base
diff --git a/profiler/win32_stack_frame_unwinder.h b/profiler/win32_stack_frame_unwinder.h
new file mode 100644
index 0000000..2d80d63
--- /dev/null
+++ b/profiler/win32_stack_frame_unwinder.h
@@ -0,0 +1,84 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_PROFILER_WIN32_STACK_FRAME_UNWINDER_H_
+#define BASE_PROFILER_WIN32_STACK_FRAME_UNWINDER_H_
+
+#include <windows.h>
+
+#include "base/base_export.h"
+#include "base/macros.h"
+
+namespace base {
+
+#if !defined(_WIN64)
+// Allows code to compile for x86. Actual support for x86 will require either
+// refactoring these interfaces or separate architecture-specific interfaces.
+using PRUNTIME_FUNCTION = void*;
+#endif // !defined(_WIN64)
+
+// Instances of this class are expected to be created and destroyed for each
+// stack unwinding, outside of SuspendThread/ResumeThread.
+class BASE_EXPORT Win32StackFrameUnwinder {
+ public:
+ // Interface for Win32 unwind-related functionality this class depends
+ // on. Provides a seam for testing.
+ class BASE_EXPORT UnwindFunctions {
+ public:
+ virtual ~UnwindFunctions();
+
+ virtual PRUNTIME_FUNCTION LookupFunctionEntry(DWORD64 program_counter,
+ PDWORD64 image_base) = 0;
+ virtual void VirtualUnwind(DWORD64 image_base,
+ DWORD64 program_counter,
+ PRUNTIME_FUNCTION runtime_function,
+ CONTEXT* context) = 0;
+
+ protected:
+ UnwindFunctions();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(UnwindFunctions);
+ };
+
+ class BASE_EXPORT Win32UnwindFunctions : public UnwindFunctions {
+ public:
+ Win32UnwindFunctions();
+
+ PRUNTIME_FUNCTION LookupFunctionEntry(DWORD64 program_counter,
+ PDWORD64 image_base) override;
+
+ void VirtualUnwind(DWORD64 image_base,
+ DWORD64 program_counter,
+ PRUNTIME_FUNCTION runtime_function,
+ CONTEXT* context) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Win32UnwindFunctions);
+ };
+
+ Win32StackFrameUnwinder();
+ ~Win32StackFrameUnwinder();
+
+ bool TryUnwind(CONTEXT* context);
+
+ private:
+ // This function is for test purposes only.
+ Win32StackFrameUnwinder(UnwindFunctions* unwind_functions);
+ friend class Win32StackFrameUnwinderTest;
+
+ // State associated with each stack unwinding.
+ bool at_top_frame_;
+ bool unwind_info_present_for_all_frames_;
+ const void* pending_blacklisted_module_;
+
+ Win32UnwindFunctions win32_unwind_functions_;
+ UnwindFunctions* const unwind_functions_;
+
+ DISALLOW_COPY_AND_ASSIGN(Win32StackFrameUnwinder);
+};
+
+} // namespace base
+
+#endif // BASE_PROFILER_WIN32_STACK_FRAME_UNWINDER_H_
diff --git a/profiler/win32_stack_frame_unwinder_unittest.cc b/profiler/win32_stack_frame_unwinder_unittest.cc
new file mode 100644
index 0000000..0ea55c3
--- /dev/null
+++ b/profiler/win32_stack_frame_unwinder_unittest.cc
@@ -0,0 +1,230 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/profiler/win32_stack_frame_unwinder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+class TestUnwindFunctions : public Win32StackFrameUnwinder::UnwindFunctions {
+ public:
+ TestUnwindFunctions();
+
+ PRUNTIME_FUNCTION LookupFunctionEntry(DWORD64 program_counter,
+ PDWORD64 image_base) override;
+ void VirtualUnwind(DWORD64 image_base,
+ DWORD64 program_counter,
+ PRUNTIME_FUNCTION runtime_function,
+ CONTEXT* context) override;
+
+ void SetNoUnwindInfoForNextFrame();
+ void SetImageBaseForNextFrame(DWORD64 image_base);
+
+ private:
+ enum { kRuntimeFunctionPointerIncrement = 1, kImageBaseIncrement = 1 << 20 };
+
+ static const PRUNTIME_FUNCTION kNonNullRuntimeFunctionPointer;
+
+ DWORD64 supplied_program_counter_;
+ DWORD64 custom_image_base_;
+ DWORD64 next_image_base_;
+ bool next_lookup_returns_null_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestUnwindFunctions);
+};
+
+// This value is opaque to Win32StackFrameUnwinder.
+const PRUNTIME_FUNCTION TestUnwindFunctions::kNonNullRuntimeFunctionPointer =
+ reinterpret_cast<PRUNTIME_FUNCTION>(8);
+
+TestUnwindFunctions::TestUnwindFunctions()
+ : supplied_program_counter_(0),
+ custom_image_base_(0),
+ next_image_base_(kImageBaseIncrement),
+ next_lookup_returns_null_(false) {}
+
+PRUNTIME_FUNCTION TestUnwindFunctions::LookupFunctionEntry(
+ DWORD64 program_counter,
+ PDWORD64 image_base) {
+ supplied_program_counter_ = program_counter;
+ if (custom_image_base_) {
+ *image_base = custom_image_base_;
+ custom_image_base_ = 0;
+ } else {
+ *image_base = next_image_base_;
+ next_image_base_ += kImageBaseIncrement;
+ }
+ if (next_lookup_returns_null_) {
+ next_lookup_returns_null_ = false;
+ return nullptr;
+ }
+
+ return kNonNullRuntimeFunctionPointer;
+}
+
+void TestUnwindFunctions::VirtualUnwind(DWORD64 image_base,
+ DWORD64 program_counter,
+ PRUNTIME_FUNCTION runtime_function,
+ CONTEXT* context) {
+ EXPECT_EQ(next_image_base_ - kImageBaseIncrement, image_base);
+ EXPECT_EQ(supplied_program_counter_, program_counter);
+ // This function should only be called when LookupFunctionEntry returns a
+ // non-null value.
+ EXPECT_EQ(kNonNullRuntimeFunctionPointer, runtime_function);
+}
+
+void TestUnwindFunctions::SetNoUnwindInfoForNextFrame() {
+ next_lookup_returns_null_ = true;
+}
+
+void TestUnwindFunctions::SetImageBaseForNextFrame(DWORD64 image_base) {
+ next_image_base_ = image_base;
+}
+
+} // namespace
+
+class Win32StackFrameUnwinderTest : public testing::Test {
+ protected:
+ Win32StackFrameUnwinderTest() {}
+
+ // This exists so that Win32StackFrameUnwinder's constructor can be private
+ // with a single friend declaration of this test fixture.
+ scoped_ptr<Win32StackFrameUnwinder> CreateUnwinder();
+
+ TestUnwindFunctions unwind_functions_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Win32StackFrameUnwinderTest);
+};
+
+scoped_ptr<Win32StackFrameUnwinder>
+Win32StackFrameUnwinderTest::CreateUnwinder() {
+ return make_scoped_ptr(new Win32StackFrameUnwinder(&unwind_functions_));
+}
+
+// Checks the case where all frames have unwind information.
+TEST_F(Win32StackFrameUnwinderTest, FramesWithUnwindInfo) {
+ scoped_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder();
+ CONTEXT context = {0};
+ EXPECT_TRUE(unwinder->TryUnwind(&context));
+ EXPECT_TRUE(unwinder->TryUnwind(&context));
+ EXPECT_TRUE(unwinder->TryUnwind(&context));
+}
+
+// Checks that the CONTEXT's stack pointer gets popped when the top frame has no
+// unwind information.
+TEST_F(Win32StackFrameUnwinderTest, FrameAtTopWithoutUnwindInfo) {
+ scoped_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder();
+ CONTEXT context = {0};
+ const DWORD64 original_rsp = 128;
+ context.Rsp = original_rsp;
+ unwind_functions_.SetNoUnwindInfoForNextFrame();
+ EXPECT_TRUE(unwinder->TryUnwind(&context));
+ EXPECT_EQ(original_rsp, context.Rip);
+ EXPECT_EQ(original_rsp + 8, context.Rsp);
+
+ EXPECT_TRUE(unwinder->TryUnwind(&context));
+ EXPECT_TRUE(unwinder->TryUnwind(&context));
+}
+
+// Checks that a frame below the top of the stack with missing unwind info
+// results in blacklisting the module.
+TEST_F(Win32StackFrameUnwinderTest, BlacklistedModule) {
+ const DWORD64 image_base_for_module_with_bad_function = 1024;
+ {
+ // First stack, with a bad function below the top of the stack.
+ scoped_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder();
+ CONTEXT context = {0};
+ EXPECT_TRUE(unwinder->TryUnwind(&context));
+
+ unwind_functions_.SetNoUnwindInfoForNextFrame();
+ unwind_functions_.SetImageBaseForNextFrame(
+ image_base_for_module_with_bad_function);
+ EXPECT_FALSE(unwinder->TryUnwind(&context));
+ }
+
+ {
+ // Second stack; check that a function at the top of the stack without
+ // unwind info from the previously-seen module is blacklisted.
+ scoped_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder();
+ CONTEXT context = {0};
+ unwind_functions_.SetNoUnwindInfoForNextFrame();
+ unwind_functions_.SetImageBaseForNextFrame(
+ image_base_for_module_with_bad_function);
+ EXPECT_FALSE(unwinder->TryUnwind(&context));
+ }
+
+ {
+ // Third stack; check that a function at the top of the stack *with* unwind
+ // info from the previously-seen module is not blacklisted. Then check that
+ // functions below the top of the stack with unwind info are not
+ // blacklisted, regardless of whether they are in the previously-seen
+ // module.
+ scoped_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder();
+ CONTEXT context = {0};
+ unwind_functions_.SetImageBaseForNextFrame(
+ image_base_for_module_with_bad_function);
+ EXPECT_TRUE(unwinder->TryUnwind(&context));
+
+ EXPECT_TRUE(unwinder->TryUnwind(&context));
+
+ unwind_functions_.SetImageBaseForNextFrame(
+ image_base_for_module_with_bad_function);
+ EXPECT_TRUE(unwinder->TryUnwind(&context));
+ }
+
+ {
+ // Fourth stack; check that a function at the top of the stack without
+ // unwind info and not from the previously-seen module is not
+ // blacklisted. Then check that functions below the top of the stack with
+ // unwind info are not blacklisted, regardless of whether they are in the
+ // previously-seen module.
+ scoped_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder();
+ CONTEXT context = {0};
+ unwind_functions_.SetNoUnwindInfoForNextFrame();
+ EXPECT_TRUE(unwinder->TryUnwind(&context));
+
+ EXPECT_TRUE(unwinder->TryUnwind(&context));
+
+ unwind_functions_.SetImageBaseForNextFrame(
+ image_base_for_module_with_bad_function);
+ EXPECT_TRUE(unwinder->TryUnwind(&context));
+ }
+}
+
+// Checks that a frame below the top of the stack with missing unwind info does
+// not result in blacklisting the module if the first frame also was missing
+// unwind info. This ensures we don't blacklist an innocent module because the
+// first frame was bad but we didn't know it at the time.
+TEST_F(Win32StackFrameUnwinderTest, ModuleFromQuestionableFrameNotBlacklisted) {
+ const DWORD64 image_base_for_questionable_module = 2048;
+ {
+ // First stack, with both the first and second frames missing unwind info.
+ scoped_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder();
+ CONTEXT context = {0};
+ unwind_functions_.SetNoUnwindInfoForNextFrame();
+ EXPECT_TRUE(unwinder->TryUnwind(&context));
+
+ unwind_functions_.SetNoUnwindInfoForNextFrame();
+ unwind_functions_.SetImageBaseForNextFrame(
+ image_base_for_questionable_module);
+ EXPECT_FALSE(unwinder->TryUnwind(&context));
+ }
+
+ {
+ // Second stack; check that the questionable module was not blacklisted.
+ scoped_ptr<Win32StackFrameUnwinder> unwinder = CreateUnwinder();
+ CONTEXT context = {0};
+ unwind_functions_.SetNoUnwindInfoForNextFrame();
+ unwind_functions_.SetImageBaseForNextFrame(
+ image_base_for_questionable_module);
+ EXPECT_TRUE(unwinder->TryUnwind(&context));
+ }
+}
+
+} // namespace base
diff --git a/strings/string_util.h b/strings/string_util.h
index 2cea8c4..fccf726 100644
--- a/strings/string_util.h
+++ b/strings/string_util.h
@@ -46,19 +46,6 @@
return result;
}
-// TODO(mark) http://crbug.com/472900 crashpad shouldn't use base while
-// being DEPSed in. This backwards-compat hack is provided until crashpad is
-// updated.
-#if defined(OS_WIN)
-inline int strcasecmp(const char* s1, const char* s2) {
- return _stricmp(s1, s2);
-}
-#else // Posix
-inline int strcasecmp(const char* string1, const char* string2) {
- return ::strcasecmp(string1, string2);
-}
-#endif
-
// BSD-style safe and consistent string copy functions.
// Copies |src| to |dst|, where |dst_size| is the total allocated size of |dst|.
// Copies at most |dst_size|-1 characters, and always NULL terminates |dst|, as
diff --git a/test/BUILD.gn b/test/BUILD.gn
index c2a1f33..bd7a21c 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -37,7 +37,6 @@
"histogram_tester.h",
"ios/wait_util.h",
"ios/wait_util.mm",
- "launcher/test_launcher.h",
"launcher/test_result.cc",
"launcher/test_result.h",
"launcher/test_results_tracker.h",
@@ -125,6 +124,7 @@
} else {
sources += [
"launcher/test_launcher.cc",
+ "launcher/test_launcher.h",
"launcher/test_results_tracker.cc",
"launcher/unit_test_launcher.cc",
"multiprocess_test.cc",
diff --git a/test/android/javatests/src/org/chromium/base/test/BaseActivityInstrumentationTestCase.java b/test/android/javatests/src/org/chromium/base/test/BaseActivityInstrumentationTestCase.java
index 9ce432b..b0c0d77 100644
--- a/test/android/javatests/src/org/chromium/base/test/BaseActivityInstrumentationTestCase.java
+++ b/test/android/javatests/src/org/chromium/base/test/BaseActivityInstrumentationTestCase.java
@@ -18,13 +18,12 @@
/**
* Base class for all Activity-based Instrumentation tests.
*
- * This class currently does nothing.
- *
* @param <T> The Activity type.
*/
public class BaseActivityInstrumentationTestCase<T extends Activity>
extends ActivityInstrumentationTestCase2<T> implements Parameterizable {
private Parameter.Reader mParameterReader;
+ private Map<String, BaseParameter> mAvailableParameters;
/**
* Creates a instance for running tests against an Activity of the given class.
@@ -36,14 +35,35 @@
}
/**
- * Creates the list of available parameters that inherited classes can use.
+ * Creates the {@link Map} of available parameters for the test to use.
*
- * @return a list of {@link BaseParameter} objects to set as the available parameters.
+ * @return a {@link Map} of {@link BaseParameter} objects.
+ */
+ protected Map<String, BaseParameter> createAvailableParameters() {
+ Map<String, BaseParameter> availableParameters = new HashMap<>();
+ availableParameters.put(
+ MethodParameter.PARAMETER_TAG, new MethodParameter(getParameterReader()));
+ return availableParameters;
+ }
+
+ /**
+ * Gets the {@link Map} of available parameters that inherited classes can use.
+ *
+ * @return a {@link Map} of {@link BaseParameter} objects to set as the available parameters.
*/
public Map<String, BaseParameter> getAvailableParameters() {
- Map<String, BaseParameter> parameters = new HashMap<>();
- parameters.put(MethodParameter.PARAMETER_TAG, new MethodParameter(getParameterReader()));
- return parameters;
+ return mAvailableParameters;
+ }
+
+ /**
+ * Gets a specific parameter from the current test.
+ *
+ * @param parameterTag a string with the name of the {@link BaseParameter} we want.
+ * @return a parameter that extends {@link BaseParameter} that has the matching parameterTag.
+ */
+ @SuppressWarnings("unchecked")
+ public <T extends BaseParameter> T getAvailableParameter(String parameterTag) {
+ return (T) mAvailableParameters.get(parameterTag);
}
/**
@@ -53,6 +73,7 @@
*/
public void setParameterReader(Parameter.Reader parameterReader) {
mParameterReader = parameterReader;
+ mAvailableParameters = createAvailableParameters();
}
/**
diff --git a/test/android/javatests/src/org/chromium/base/test/util/TestThread.java b/test/android/javatests/src/org/chromium/base/test/util/TestThread.java
index 93c23f7..4f62969 100644
--- a/test/android/javatests/src/org/chromium/base/test/util/TestThread.java
+++ b/test/android/javatests/src/org/chromium/base/test/util/TestThread.java
@@ -43,7 +43,7 @@
*/
public class TestThread extends Thread {
- private Object mThreadReadyLock;
+ private final Object mThreadReadyLock;
private AtomicBoolean mThreadReady;
private Handler mMainThreadHandler;
private Handler mTestThreadHandler;
diff --git a/test/android/javatests/src/org/chromium/base/test/util/parameter/Parameterizable.java b/test/android/javatests/src/org/chromium/base/test/util/parameter/Parameterizable.java
index b271430..f0eb97d 100644
--- a/test/android/javatests/src/org/chromium/base/test/util/parameter/Parameterizable.java
+++ b/test/android/javatests/src/org/chromium/base/test/util/parameter/Parameterizable.java
@@ -10,6 +10,25 @@
* An interface to implement on test cases to run {@link ParameterizedTest}s.
*/
public interface Parameterizable {
+ /**
+ * Gets the {@link Map} of available parameters for the test to use.
+ *
+ * @return a {@link Map} of {@link BaseParameter} objects.
+ */
Map<String, BaseParameter> getAvailableParameters();
+
+ /**
+ * Setter method for {@link Parameter.Reader}.
+ *
+ * @param parameterReader the {@link Parameter.Reader} to set.
+ */
void setParameterReader(Parameter.Reader parameterReader);
+
+ /**
+ * Gets a specific parameter from the current test.
+ *
+ * @param parameterTag a string with the name of the {@link BaseParameter} we want.
+ * @return a parameter that extends {@link BaseParameter} that has the matching parameterTag.
+ */
+ <T extends BaseParameter> T getAvailableParameter(String parameterTag);
}
diff --git a/threading/sequenced_worker_pool.cc b/threading/sequenced_worker_pool.cc
index 7bbca92..772cc01 100644
--- a/threading/sequenced_worker_pool.cc
+++ b/threading/sequenced_worker_pool.cc
@@ -619,9 +619,11 @@
// The trace_id is used for identifying the task in about:tracing.
sequenced.trace_id = trace_id_++;
- TRACE_EVENT_FLOW_BEGIN0(TRACE_DISABLED_BY_DEFAULT("toplevel.flow"),
- "SequencedWorkerPool::PostTask",
- TRACE_ID_MANGLE(GetTaskTraceID(sequenced, static_cast<void*>(this))));
+ TRACE_EVENT_WITH_FLOW0(
+ TRACE_DISABLED_BY_DEFAULT("toplevel.flow"),
+ "SequencedWorkerPool::Inner::PostTask",
+ TRACE_ID_MANGLE(GetTaskTraceID(sequenced, static_cast<void*>(this))),
+ TRACE_EVENT_FLAG_FLOW_OUT);
sequenced.sequence_task_number = LockedGetNextSequenceTaskNumber();
@@ -754,12 +756,12 @@
GetWorkStatus status =
GetWork(&task, &wait_time, &delete_these_outside_lock);
if (status == GET_WORK_FOUND) {
- TRACE_EVENT_FLOW_END0(TRACE_DISABLED_BY_DEFAULT("toplevel.flow"),
- "SequencedWorkerPool::PostTask",
- TRACE_ID_MANGLE(GetTaskTraceID(task, static_cast<void*>(this))));
- TRACE_EVENT2("toplevel", "SequencedWorkerPool::ThreadLoop",
- "src_file", task.posted_from.file_name(),
- "src_func", task.posted_from.function_name());
+ TRACE_EVENT_WITH_FLOW2(
+ TRACE_DISABLED_BY_DEFAULT("toplevel.flow"),
+ "SequencedWorkerPool::Inner::ThreadLoop",
+ TRACE_ID_MANGLE(GetTaskTraceID(task, static_cast<void*>(this))),
+ TRACE_EVENT_FLAG_FLOW_IN, "src_file", task.posted_from.file_name(),
+ "src_func", task.posted_from.function_name());
int new_thread_id = WillRunWorkerTask(task);
{
AutoUnlock unlock(lock_);
diff --git a/trace_event/memory_dump_manager.cc b/trace_event/memory_dump_manager.cc
index 6c13179..6d46584 100644
--- a/trace_event/memory_dump_manager.cc
+++ b/trace_event/memory_dump_manager.cc
@@ -93,12 +93,12 @@
}
MemoryDumpManager::MemoryDumpManager()
- : did_unregister_dump_provider_(false),
- delegate_(nullptr),
+ : delegate_(nullptr),
memory_tracing_enabled_(0),
tracing_process_id_(kInvalidTracingProcessId),
system_allocator_pool_name_(nullptr),
- skip_core_dumpers_auto_registration_for_testing_(false) {
+ skip_core_dumpers_auto_registration_for_testing_(false),
+ disable_periodic_dumps_for_testing_(false) {
g_next_guid.GetNext(); // Make sure that first guid is not zero.
}
@@ -152,7 +152,16 @@
const scoped_refptr<SingleThreadTaskRunner>& task_runner) {
MemoryDumpProviderInfo mdp_info(mdp, task_runner);
AutoLock lock(lock_);
- dump_providers_.insert(mdp_info);
+ auto iter_new = dump_providers_.insert(mdp_info);
+
+ // If there was a previous entry, replace it with the new one. This is to deal
+ // with the case where a dump provider unregisters itself and then re-
+ // registers before a memory dump happens, so its entry was still in the
+ // collection but flagged |unregistered|.
+ if (!iter_new.second) {
+ dump_providers_.erase(iter_new.first);
+ dump_providers_.insert(mdp_info);
+ }
}
void MemoryDumpManager::RegisterDumpProvider(MemoryDumpProvider* mdp) {
@@ -183,8 +192,7 @@
<< "The MemoryDumpProvider attempted to unregister itself in a racy way. "
<< "Please file a crbug.";
- dump_providers_.erase(mdp_iter);
- did_unregister_dump_provider_ = true;
+ mdp_iter->unregistered = true;
}
void MemoryDumpManager::RequestGlobalDump(MemoryDumpType dump_type,
@@ -228,7 +236,6 @@
scoped_ptr<ProcessMemoryDumpAsyncState> pmd_async_state;
{
AutoLock lock(lock_);
- did_unregister_dump_provider_ = false;
pmd_async_state.reset(new ProcessMemoryDumpAsyncState(
args, dump_providers_.begin(), session_state_, callback));
}
@@ -268,18 +275,10 @@
bool skip_dump = false;
{
AutoLock lock(lock_);
- // In the unlikely event that a dump provider was unregistered while
- // dumping, abort the dump, as that would make |next_dump_provider| invalid.
- // Registration, on the other hand, is safe as per std::set<> contract.
- if (did_unregister_dump_provider_) {
- return AbortDumpLocked(pmd_async_state->callback,
- pmd_async_state->task_runner,
- pmd_async_state->req_args.dump_guid);
- }
- auto* mdp_info = &*pmd_async_state->next_dump_provider;
+ auto mdp_info = pmd_async_state->next_dump_provider;
mdp = mdp_info->dump_provider;
- if (mdp_info->disabled) {
+ if (mdp_info->disabled || mdp_info->unregistered) {
skip_dump = true;
} else if (mdp_info->task_runner &&
!mdp_info->task_runner->BelongsToCurrentThread()) {
@@ -318,12 +317,7 @@
{
AutoLock lock(lock_);
- if (did_unregister_dump_provider_) {
- return AbortDumpLocked(pmd_async_state->callback,
- pmd_async_state->task_runner,
- pmd_async_state->req_args.dump_guid);
- }
- auto* mdp_info = &*pmd_async_state->next_dump_provider;
+ auto mdp_info = pmd_async_state->next_dump_provider;
if (dump_successful) {
mdp_info->consecutive_failures = 0;
} else if (!skip_dump) {
@@ -334,6 +328,9 @@
}
++pmd_async_state->next_dump_provider;
finalize = pmd_async_state->next_dump_provider == dump_providers_.end();
+
+ if (mdp_info->unregistered)
+ dump_providers_.erase(mdp_info);
}
if (!skip_dump && !dump_successful) {
@@ -427,9 +424,11 @@
// TODO(primiano): This is a temporary hack to disable periodic memory dumps
// when running memory benchmarks until they can be enabled/disabled in
// base::trace_event::TraceConfig. See https://goo.gl/5Hj3o0.
+ // The same mechanism should be used to disable periodic dumps in tests.
if (delegate_->IsCoordinatorProcess() &&
!CommandLine::ForCurrentProcess()->HasSwitch(
- "enable-memory-benchmarking")) {
+ "enable-memory-benchmarking") &&
+ !disable_periodic_dumps_for_testing_) {
g_periodic_dumps_count = 0;
periodic_dump_timer_.Start(FROM_HERE,
TimeDelta::FromMilliseconds(kDumpIntervalMs),
@@ -454,7 +453,8 @@
: dump_provider(dump_provider),
task_runner(task_runner),
consecutive_failures(0),
- disabled(false) {}
+ disabled(false),
+ unregistered(false) {}
MemoryDumpManager::MemoryDumpProviderInfo::~MemoryDumpProviderInfo() {
}
diff --git a/trace_event/memory_dump_manager.h b/trace_event/memory_dump_manager.h
index 5c75e4e..dfaf3a1 100644
--- a/trace_event/memory_dump_manager.h
+++ b/trace_event/memory_dump_manager.h
@@ -97,12 +97,19 @@
return system_allocator_pool_name_;
};
+ // Tells the initialization phase to skip scheduling periodic memory dumps.
+ void DisablePeriodicDumpsForTesting() {
+ disable_periodic_dumps_for_testing_ = true;
+ }
+
private:
friend struct DefaultDeleter<MemoryDumpManager>; // For the testing instance.
friend struct DefaultSingletonTraits<MemoryDumpManager>;
friend class MemoryDumpManagerDelegate;
friend class MemoryDumpManagerTest;
FRIEND_TEST_ALL_PREFIXES(MemoryDumpManagerTest, DisableFailingDumpers);
+ FRIEND_TEST_ALL_PREFIXES(MemoryDumpManagerTest,
+ UnregisterDumperFromThreadWhileDumping);
// Descriptor struct used to hold information about registered MDPs. It is
// deliberately copyable, in order to allow it to be used as std::set value.
@@ -123,6 +130,11 @@
// as can be safely changed without impacting the order within the set.
mutable int consecutive_failures;
mutable bool disabled;
+
+ // When a dump provider unregisters, it is flagged as |unregistered| and it
+ // is removed only upon the next memory dump. This is to avoid altering the
+ // |dump_providers_| collection while a dump is in progress.
+ mutable bool unregistered;
};
using MemoryDumpProviderInfoSet = std::set<MemoryDumpProviderInfo>;
@@ -189,10 +201,6 @@
// affinity (MDPs belonging to the same thread are adjacent).
MemoryDumpProviderInfoSet dump_providers_;
- // Flag used to signal that some provider was removed from |dump_providers_|
- // and therefore the current memory dump (if any) should be aborted.
- bool did_unregister_dump_provider_;
-
// Shared among all the PMDs to keep state scoped to the tracing session.
scoped_refptr<MemoryDumpSessionState> session_state_;
@@ -219,6 +227,11 @@
// Skips the auto-registration of the core dumpers during Initialize().
bool skip_core_dumpers_auto_registration_for_testing_;
+ // When true, the initialization phase does not start the periodic memory
+ // dumps.
+ // TODO(primiano): This should go into TraceConfig. https://goo.gl/5Hj3o0.
+ bool disable_periodic_dumps_for_testing_;
+
DISALLOW_COPY_AND_ASSIGN(MemoryDumpManager);
};
diff --git a/trace_event/memory_dump_manager_unittest.cc b/trace_event/memory_dump_manager_unittest.cc
index 9835415..6fb8c32 100644
--- a/trace_event/memory_dump_manager_unittest.cc
+++ b/trace_event/memory_dump_manager_unittest.cc
@@ -8,6 +8,7 @@
#include "base/memory/scoped_vector.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
+#include "base/test/test_io_thread.h"
#include "base/thread_task_runner_handle.h"
#include "base/threading/thread.h"
#include "base/trace_event/memory_dump_provider.h"
@@ -16,6 +17,7 @@
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
+using testing::AtMost;
using testing::Between;
using testing::Invoke;
using testing::Return;
@@ -282,6 +284,55 @@
DisableTracing();
}
+// Verify that whether OnMemoryDump is called depends only on the current
+// registration state and not on previous registrations and dumps.
+TEST_F(MemoryDumpManagerTest, RegistrationConsistency) {
+ MockDumpProvider mdp;
+
+ mdm_->RegisterDumpProvider(&mdp);
+
+ {
+ EXPECT_CALL(mdp, OnMemoryDump(_, _)).Times(1);
+ EnableTracing(kTraceCategory);
+ mdm_->RequestGlobalDump(MemoryDumpType::EXPLICITLY_TRIGGERED,
+ high_detail_args);
+ DisableTracing();
+ }
+
+ mdm_->UnregisterDumpProvider(&mdp);
+
+ {
+ EXPECT_CALL(mdp, OnMemoryDump(_, _)).Times(0);
+ EnableTracing(kTraceCategory);
+ mdm_->RequestGlobalDump(MemoryDumpType::EXPLICITLY_TRIGGERED,
+ high_detail_args);
+ DisableTracing();
+ }
+
+ mdm_->RegisterDumpProvider(&mdp);
+ mdm_->UnregisterDumpProvider(&mdp);
+
+ {
+ EXPECT_CALL(mdp, OnMemoryDump(_, _)).Times(0);
+ EnableTracing(kTraceCategory);
+ mdm_->RequestGlobalDump(MemoryDumpType::EXPLICITLY_TRIGGERED,
+ high_detail_args);
+ DisableTracing();
+ }
+
+ mdm_->RegisterDumpProvider(&mdp);
+ mdm_->UnregisterDumpProvider(&mdp);
+ mdm_->RegisterDumpProvider(&mdp);
+
+ {
+ EXPECT_CALL(mdp, OnMemoryDump(_, _)).Times(1);
+ EnableTracing(kTraceCategory);
+ mdm_->RequestGlobalDump(MemoryDumpType::EXPLICITLY_TRIGGERED,
+ high_detail_args);
+ DisableTracing();
+ }
+}
+
// Checks that the MemoryDumpManager respects the thread affinity when a
// MemoryDumpProvider specifies a task_runner(). The test starts creating 8
// threads and registering a MemoryDumpProvider on each of them. At each
@@ -433,6 +484,61 @@
DisableTracing();
}
+// Verify that the dump does not abort when unregistering a provider while
+// dumping from a different thread than the dumping thread.
+TEST_F(MemoryDumpManagerTest, UnregisterDumperFromThreadWhileDumping) {
+ ScopedVector<TestIOThread> threads;
+ ScopedVector<MockDumpProvider> mdps;
+
+ for (int i = 0; i < 2; i++) {
+ threads.push_back(new TestIOThread(TestIOThread::kAutoStart));
+ mdps.push_back(new MockDumpProvider(threads.back()->task_runner()));
+ mdm_->RegisterDumpProvider(mdps.back(), threads.back()->task_runner());
+ }
+
+ int on_memory_dump_call_count = 0;
+ RunLoop run_loop;
+
+ // When OnMemoryDump is called on either of the dump providers, it will
+ // unregister the other one.
+ for (MockDumpProvider* mdp : mdps) {
+ int other_idx = (mdps.front() == mdp);
+ TestIOThread* other_thread = threads[other_idx];
+ MockDumpProvider* other_mdp = mdps[other_idx];
+ auto on_dump = [this, other_thread, other_mdp, &on_memory_dump_call_count](
+ const MemoryDumpArgs& args, ProcessMemoryDump* pmd) {
+ other_thread->PostTaskAndWait(
+ FROM_HERE, base::Bind(&MemoryDumpManager::UnregisterDumpProvider,
+ base::Unretained(&*mdm_), other_mdp));
+ on_memory_dump_call_count++;
+ return true;
+ };
+
+ // OnMemoryDump is called once for the provider that dumps first, and zero
+ // times for the other provider.
+ EXPECT_CALL(*mdp, OnMemoryDump(_, _))
+ .Times(AtMost(1))
+ .WillOnce(Invoke(on_dump));
+ }
+
+ last_callback_success_ = false;
+ MemoryDumpCallback callback =
+ Bind(&MemoryDumpManagerTest::DumpCallbackAdapter, Unretained(this),
+ MessageLoop::current()->task_runner(), run_loop.QuitClosure());
+
+ EnableTracing(kTraceCategory);
+ MemoryDumpRequestArgs request_args = {0, MemoryDumpType::EXPLICITLY_TRIGGERED,
+ high_detail_args};
+ mdm_->CreateProcessDump(request_args, callback);
+
+ run_loop.Run();
+
+ ASSERT_EQ(1, on_memory_dump_call_count);
+ ASSERT_EQ(true, last_callback_success_);
+
+ DisableTracing();
+}
+
// Ensures that a NACK callback is invoked if RequestGlobalDump is called when
// tracing is not enabled.
TEST_F(MemoryDumpManagerTest, CallbackCalledOnFailure) {
diff --git a/trace_event/trace_buffer.cc b/trace_event/trace_buffer.cc
index 33cb60f..a2e4f14 100644
--- a/trace_event/trace_buffer.cc
+++ b/trace_event/trace_buffer.cc
@@ -4,6 +4,7 @@
#include "base/trace_event/trace_buffer.h"
+#include "base/memory/scoped_vector.h"
#include "base/trace_event/trace_event_impl.h"
namespace base {
diff --git a/trace_event/trace_event_impl.h b/trace_event/trace_event_impl.h
index 936ca4e..1d5b06d 100644
--- a/trace_event/trace_event_impl.h
+++ b/trace_event/trace_event_impl.h
@@ -16,7 +16,6 @@
#include "base/containers/hash_tables.h"
#include "base/gtest_prod_util.h"
#include "base/memory/ref_counted_memory.h"
-#include "base/memory/scoped_vector.h"
#include "base/observer_list.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_util.h"