summaryrefslogtreecommitdiffstats
path: root/build/android/pylib/linker
diff options
context:
space:
mode:
authordigit@chromium.org <digit@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-10-21 21:40:44 +0000
committerdigit@chromium.org <digit@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-10-21 21:40:44 +0000
commit281a28973176ba69116db7b2b4254952cf1a7119 (patch)
treec4db13af593545a2dea1fb74d5c2ef77b54380a8 /build/android/pylib/linker
parent0947506a49369b5864ca92d6452d4ac955424189 (diff)
downloadchromium_src-281a28973176ba69116db7b2b4254952cf1a7119.zip
chromium_src-281a28973176ba69116db7b2b4254952cf1a7119.tar.gz
chromium_src-281a28973176ba69116db7b2b4254952cf1a7119.tar.bz2
Android: Add chrome-specific dynamic linker.
This patch adds a new Chrome-specific dynamic linker for Android, that implements RELRO section sharing in order to save about 1.3 MB of RAM per renderer process in content-based programs (ContentShell, ChromiumTestShell, Chrome, etc...) The linker is disabled by default. For more details, see the corresponding bug entry. This introduces a new test package (content_linker_test_apk) as well as a new test category. To build and test this feature, do the following: ninja -C out/Debug content_linker_test_apk build/android/test_runner.py linker BUG=287739 R=qsr@chromium.org Review URL: https://codereview.chromium.org/23717023 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@229921 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'build/android/pylib/linker')
-rw-r--r--build/android/pylib/linker/setup.py3
-rw-r--r--build/android/pylib/linker/test_case.py133
2 files changed, 127 insertions, 9 deletions
diff --git a/build/android/pylib/linker/setup.py b/build/android/pylib/linker/setup.py
index a13ea4d..4024fa7 100644
--- a/build/android/pylib/linker/setup.py
+++ b/build/android/pylib/linker/setup.py
@@ -27,7 +27,8 @@ def Setup(options, devices):
test_cases = [
test_case.LinkerLibraryAddressTest,
test_case.LinkerSharedRelroTest,
- test_case.LinkerRandomizationTest ]
+ test_case.LinkerRandomizationTest,
+ test_case.LinkerLowMemoryThresholdTest ]
low_memory_modes = [False, True]
all_tests = [t(is_low_memory=m) for t in test_cases for m in low_memory_modes]
diff --git a/build/android/pylib/linker/test_case.py b/build/android/pylib/linker/test_case.py
index b8a2566..3f8ea73 100644
--- a/build/android/pylib/linker/test_case.py
+++ b/build/android/pylib/linker/test_case.py
@@ -42,6 +42,7 @@ import subprocess
import tempfile
import time
+from pylib import constants
from pylib import android_commands
from pylib import flag_changer
from pylib.base import base_test_result
@@ -52,6 +53,17 @@ _PACKAGE_NAME='org.chromium.content_linker_test_apk'
_ACTIVITY_NAME='.ContentLinkerTestActivity'
_COMMAND_LINE_FILE='/data/local/tmp/content-linker-test-command-line'
+# Path to the Linker.java source file.
+_LINKER_JAVA_SOURCE_PATH = \
+ 'content/public/android/java/src/org/chromium/content/app/Linker.java'
+
+# A regular expression used to extract the browser shared RELRO configuration
+# from the Java source file above.
+_RE_LINKER_BROWSER_CONFIG = \
+ re.compile(r'.*BROWSER_SHARED_RELRO_CONFIG\s+=\s+' + \
+ 'BROWSER_SHARED_RELRO_CONFIG_(\S+)\s*;.*',
+ re.MULTILINE | re.DOTALL)
+
# Logcat filters used during each test. Only the 'chromium' one is really
# needed, but the logs are added to the TestResult in case of error, and
# it is handy to have the 'content_android_linker' ones as well when
@@ -67,6 +79,38 @@ re_library_address = re.compile(
r'(BROWSER|RENDERER)_LIBRARY_ADDRESS: (\S+) ([0-9A-Fa-f]+)')
+def _GetBrowserSharedRelroConfig():
+ """Returns a string corresponding to the Linker's configuration of shared
+ RELRO sections in the browser process. This parses the Java linker source
+ file to get the appropriate information.
+ Return:
+ None in case of error (e.g. could not locate the source file).
+ 'NEVER' if the browser process shall never use shared RELROs.
+ 'LOW_RAM_ONLY' if if uses it only on low-end devices.
+ 'ALWAYS' if it always uses a shared RELRO.
+ """
+ source_path = \
+ os.path.join(constants.DIR_SOURCE_ROOT, _LINKER_JAVA_SOURCE_PATH)
+ if not os.path.exists(source_path):
+ logging.error('Could not find linker source file: ' + source_path)
+ return None
+
+ with open(source_path) as f:
+ configs = _RE_LINKER_BROWSER_CONFIG.findall(f.read())
+ if not configs:
+ logging.error(
+ 'Can\'t find browser shared RELRO configuration value in ' + \
+ source_path)
+ return None
+
+ if configs[0] not in ['NEVER', 'LOW_RAM_ONLY', 'ALWAYS']:
+ logging.error('Unexpected browser config value: ' + configs[0])
+ return None
+
+ logging.info('Found linker browser shared RELRO config: ' + configs[0])
+ return configs[0]
+
+
def _WriteCommandLineFile(adb, command_line, command_line_file):
"""Create a command-line file on the device. This does not use FlagChanger
because its implementation assumes the device has 'su', and thus does
@@ -401,10 +445,14 @@ class LinkerLibraryAddressTest(LinkerTestCaseBase):
logging.error('Renderer libraries loaded at high addresses: %s', bad_libs)
return ResultType.FAIL, logs
- if self.is_low_memory:
- # For low-memory devices, the libraries must all be loaded at the same
- # addresses. This also implicitly checks that the browser libraries are at
- # low addresses.
+ browser_config = _GetBrowserSharedRelroConfig()
+ if not browser_config:
+ return ResultType.FAIL, 'Bad linker source configuration'
+
+ if browser_config == 'ALWAYS' or \
+ (browser_config == 'LOW_RAM_ONLY' and self.is_low_memory):
+ # The libraries must all be loaded at the same addresses. This also
+ # implicitly checks that the browser libraries are at low addresses.
addr_mismatches = []
for lib_name, lib_address in browser_libs.iteritems():
lib_address2 = renderer_libs[lib_name]
@@ -416,10 +464,10 @@ class LinkerLibraryAddressTest(LinkerTestCaseBase):
addr_mismatches)
return ResultType.FAIL, logs
- # For regular devices, check that libraries are loaded at 'high-addresses'.
+ # Otherwise, check that libraries are loaded at 'high-addresses'.
# Note that for low-memory devices, the previous checks ensure that they
# were loaded at low-addresses.
- if not self.is_low_memory:
+ else:
bad_libs = []
for lib_name, lib_address in browser_libs.iteritems():
if lib_address < memory_boundary:
@@ -473,9 +521,14 @@ class LinkerRandomizationTest(LinkerTestCaseBase):
renderer_status, renderer_logs = _CheckLoadAddressRandomization(
renderer_lib_map_list, 'Renderer')
+ browser_config = _GetBrowserSharedRelroConfig()
+ if not browser_config:
+ return ResultType.FAIL, 'Bad linker source configuration'
+
if not browser_status:
- if self.is_low_memory:
- return ResultType.FAIL, browser_logs
+ if browser_config == 'ALWAYS' or \
+ (browser_config == 'LOW_RAM_ONLY' and self.is_low_memory):
+ return ResultType.FAIL, browser_logs
# IMPORTANT NOTE: The system's ASLR implementation seems to be very poor
# when starting an activity process in a loop with "adb shell am start".
@@ -501,3 +554,67 @@ class LinkerRandomizationTest(LinkerTestCaseBase):
return ResultType.FAIL, renderer_logs
return ResultType.PASS, logs
+
+
+class LinkerLowMemoryThresholdTest(LinkerTestCaseBase):
+ """This test checks that the definitions for the low-memory device physical
+ RAM threshold are identical in the base/ and linker sources. Because these
+ two components should absolutely not depend on each other, it's difficult
+ to perform this check correctly at runtime inside the linker test binary
+ without introducing hairy dependency issues in the build, or complicated
+ plumbing at runtime.
+
+ To work-around this, this test looks directly into the sources for a
+ definition of the same constant that should look like:
+
+ #define ANDROID_LOW_MEMORY_DEVICE_THRESHOLD_MB <number>
+
+ And will check that the values for <number> are identical in all of
+ them."""
+
+ # A regular expression used to find the definition of the threshold in all
+ # sources:
+ _RE_THRESHOLD_DEFINITION = re.compile(
+ r'^\s*#\s*define\s+ANDROID_LOW_MEMORY_DEVICE_THRESHOLD_MB\s+(\d+)\s*$',
+ re.MULTILINE)
+
+ # The list of source files, relative to DIR_SOURCE_ROOT, which must contain
+ # a line that matches the re above.
+ _SOURCES_LIST = [
+ 'base/android/sys_utils.cc',
+ 'content/common/android/linker/linker_jni.cc' ]
+
+ def _RunTest(self, adb):
+ failure = False
+ values = []
+ # First, collect all the values in all input sources.
+ re = LinkerLowMemoryThresholdTest._RE_THRESHOLD_DEFINITION
+ for source in LinkerLowMemoryThresholdTest._SOURCES_LIST:
+ source_path = os.path.join(constants.DIR_SOURCE_ROOT, source);
+ if not os.path.exists(source_path):
+ logging.error('Missing source file: ' + source_path)
+ failure = True
+ continue
+ with open(source_path) as f:
+ source_text = f.read()
+ # For some reason, re.match() never works here.
+ source_values = re.findall(source_text)
+ if not source_values:
+ logging.error('Missing low-memory threshold definition in ' + \
+ source_path)
+ logging.error('Source:\n%s\n' % source_text)
+ failure = True
+ continue
+ values += source_values
+
+ # Second, check that they are all the same.
+ if not failure:
+ for value in values[1:]:
+ if value != values[0]:
+ logging.error('Value mismatch: ' + repr(values))
+ failure = True
+
+ if failure:
+ return ResultType.FAIL, 'Incorrect low-end memory threshold definitions!'
+
+ return ResultType.PASS, ''