Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2013 The Chromium Authors. All rights reserved. | 1 # Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """Base class for linker-specific test cases. | 5 """Base class for linker-specific test cases. |
| 6 | 6 |
| 7 The custom dynamic linker can only be tested through a custom test case | 7 The custom dynamic linker can only be tested through a custom test case |
| 8 for various technical reasons: | 8 for various technical reasons: |
| 9 | 9 |
| 10 - It's an 'invisible feature', i.e. it doesn't expose a new API or | 10 - It's an 'invisible feature', i.e. it doesn't expose a new API or |
| (...skipping 14 matching lines...) Expand all Loading... | |
| 25 Linker code itself. | 25 Linker code itself. |
| 26 | 26 |
| 27 Host-driven tests have also been tried, but since they're really | 27 Host-driven tests have also been tried, but since they're really |
| 28 sub-classes of instrumentation tests, they didn't work well either. | 28 sub-classes of instrumentation tests, they didn't work well either. |
| 29 | 29 |
| 30 To build and run the linker tests, do the following: | 30 To build and run the linker tests, do the following: |
| 31 | 31 |
| 32 ninja -C out/Debug content_linker_test_apk | 32 ninja -C out/Debug content_linker_test_apk |
| 33 build/android/test_runner.py linker | 33 build/android/test_runner.py linker |
| 34 | 34 |
| 35 The core of the checks performed here are pretty simple: | |
| 36 | |
| 37 - Clear the logcat and start recording with an appropriate set of filters. | |
| 38 - Create the command-line appropriate for the test-case. | |
| 39 - Start the activity (always forcing a cold start). | |
| 40 - Every second, look at the current content of the filtered logcat lines | |
| 41 and look for instances of the following: | |
| 42 | |
| 43 BROWSER_LINKER_TEST: <status> | |
| 44 RENDERER_LINKER_TEST: <status> | |
| 45 | |
| 46 where <status> can be either FAIL or SUCCESS. These lines can appear | |
| 47 in any order in the logcat. Once both browser and renderer status are | |
| 48 found, stop the loop. Otherwise timeout after 30 seconds. | |
| 49 | |
| 50 Note that there can be other lines beginning with BROWSER_LINKER_TEST: | |
| 51 and RENDERER_LINKER_TEST:, but are not followed by a <status> code. | |
| 52 | |
| 53 - The test case passes if the <status> for both the browser and renderer | |
| 54 process are SUCCESS. Otherwise its a fail. | |
| 55 """ | 35 """ |
| 56 | 36 |
| 57 import logging | 37 import logging |
| 58 import os | 38 import os |
| 59 import re | 39 import re |
| 60 import StringIO | 40 import StringIO |
| 61 import subprocess | 41 import subprocess |
| 62 import tempfile | 42 import tempfile |
| 63 import time | 43 import time |
| 64 | 44 |
| 65 from pylib import android_commands | 45 from pylib import android_commands |
| 66 from pylib import flag_changer | 46 from pylib import flag_changer |
| 67 from pylib.base import base_test_result | 47 from pylib.base import base_test_result |
| 68 | 48 |
| 49 ResultType = base_test_result.ResultType | |
| 69 | 50 |
| 70 _PACKAGE_NAME='org.chromium.content_linker_test_apk' | 51 _PACKAGE_NAME='org.chromium.content_linker_test_apk' |
| 71 _ACTIVITY_NAME='.ContentLinkerTestActivity' | 52 _ACTIVITY_NAME='.ContentLinkerTestActivity' |
| 72 _COMMAND_LINE_FILE='/data/local/tmp/content-linker-test-command-line' | 53 _COMMAND_LINE_FILE='/data/local/tmp/content-linker-test-command-line' |
| 73 | 54 |
| 74 # Logcat filters used during each test. Only the 'chromium' one is really | 55 # Logcat filters used during each test. Only the 'chromium' one is really |
| 75 # needed, but the logs are added to the TestResult in case of error, and | 56 # needed, but the logs are added to the TestResult in case of error, and |
| 76 # it is handy to have the 'content_android_linker' ones as well when | 57 # it is handy to have the 'content_android_linker' ones as well when |
| 77 # troubleshooting. | 58 # troubleshooting. |
| 78 _LOGCAT_FILTERS = [ '*:s', 'chromium:v', 'content_android_linker:v' ] | 59 _LOGCAT_FILTERS = [ '*:s', 'chromium:v', 'content_android_linker:v' ] |
| 60 #_LOGCAT_FILTERS = [ '*:v' ] ## DEBUG | |
| 79 | 61 |
| 80 # Regular expression used to match status lines in logcat. | 62 # Regular expression used to match status lines in logcat. |
| 81 re_status_line = re.compile(r'(BROWSER|RENDERER)_LINKER_TEST: (FAIL|SUCCESS)') | 63 re_status_line = re.compile(r'(BROWSER|RENDERER)_LINKER_TEST: (FAIL|SUCCESS)') |
| 82 | 64 |
| 65 # Regular expression used to mach library load addresses in logcat. | |
| 66 re_library_address = re.compile( | |
| 67 r'(BROWSER|RENDERER)_LIBRARY_ADDRESS: (\S+) ([0-9A-Fa-f]+)') | |
| 68 | |
| 69 | |
| 70 def _WriteCommandLineFile(adb, command_line, command_line_file): | |
| 71 """Create a command-line file on the device. This does not use FlagChanger | |
| 72 because its implementation assumes the device has 'su', and thus does | |
| 73 not work at all with production devices.""" | |
|
bulach
2013/10/07 16:49:44
would this work on directory that's not writable b
digit1
2013/10/07 20:31:58
No, the main issue is that FlaChanger really runs
| |
| 74 adb.RunShellCommand('echo "%s" > %s' % (command_line, command_line_file)) | |
| 75 | |
| 76 | |
| 83 def _CheckLinkerTestStatus(logcat): | 77 def _CheckLinkerTestStatus(logcat): |
| 84 """Parse the content of |logcat| and checks for both a browser and | 78 """Parse the content of |logcat| and checks for both a browser and |
| 85 renderer status line. | 79 renderer status line. |
| 86 | 80 |
| 87 Args: | 81 Args: |
| 88 logcat: A string to parse. Can include line separators. | 82 logcat: A string to parse. Can include line separators. |
| 89 | 83 |
| 90 Returns: | 84 Returns: |
| 91 A tuple, result[0] is True if there is a complete match, then | 85 A tuple, result[0] is True if there is a complete match, then |
| 92 result[1] and result[2] will be True or False to reflect the | 86 result[1] and result[2] will be True or False to reflect the |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 105 else: | 99 else: |
| 106 assert False, 'Invalid process type ' + process_type | 100 assert False, 'Invalid process type ' + process_type |
| 107 | 101 |
| 108 if browser_found and renderer_found: | 102 if browser_found and renderer_found: |
| 109 return (True, browser_success, renderer_success) | 103 return (True, browser_success, renderer_success) |
| 110 | 104 |
| 111 # Didn't find anything. | 105 # Didn't find anything. |
| 112 return (False, None, None) | 106 return (False, None, None) |
| 113 | 107 |
| 114 | 108 |
| 115 def _CreateCommandLineFileOnDevice(adb, flags): | 109 def _WaitForLinkerTestStatus(adb, timeout): |
| 116 changer = flag_changer.FlagChanger(adb, _COMMAND_LINE_FILE) | 110 """Wait up to |timeout| seconds until the full linker test status lines appear |
| 117 changer.Set(flags) | 111 in the logcat being recorded with |adb|. |
| 118 | 112 Args: |
| 119 | 113 adb: An AndroidCommands instance. This assumes adb.StartRecordingLogcat() |
| 120 class LinkerTestCase(object): | 114 was called previously. |
| 115 timeout: Timeout in seconds. | |
| 116 Returns: | |
| 117 ResultType.TIMEOUT in case of timeout, ResulType.PASS if both status lines | |
| 118 report 'SUCCESS', or ResulType.FAIL otherwise. | |
| 119 """ | |
| 120 | |
| 121 | |
| 122 def _StartActivityAndWaitForLinkerTestStatus(adb, timeout): | |
| 123 """Force-start an activity and wait up to |timeout| seconds until the full | |
| 124 linker test status lines appear in the logcat, recorded through |adb|. | |
| 125 Args: | |
| 126 adb: An AndroidCommands instance. | |
| 127 timeout: Timeout in seconds | |
| 128 Returns: | |
| 129 A (status, logs) tuple, where status is a ResultType constant, and logs | |
| 130 if the final logcat output as a string. | |
| 131 """ | |
| 132 # 1. Start recording logcat with appropriate filters. | |
| 133 adb.StartRecordingLogcat(clear=True, filters=_LOGCAT_FILTERS) | |
| 134 | |
| 135 try: | |
| 136 # 2. Force-start activity. | |
| 137 adb.StartActivity(package=_PACKAGE_NAME, | |
| 138 activity=_ACTIVITY_NAME, | |
| 139 force_stop=True) | |
| 140 | |
| 141 # 3. Wait up to |timeout| seconds until the test status is in the logcat. | |
| 142 num_tries = 0 | |
| 143 max_tries = timeout | |
| 144 found = False | |
| 145 while num_tries < max_tries: | |
| 146 time.sleep(1) | |
| 147 num_tries += 1 | |
| 148 found, browser_ok, renderer_ok = _CheckLinkerTestStatus( | |
| 149 adb.GetCurrentRecordedLogcat()) | |
| 150 if found: | |
| 151 break | |
| 152 | |
| 153 finally: | |
| 154 logs = adb.StopRecordingLogcat() | |
| 155 | |
| 156 if num_tries >= max_tries: | |
| 157 return ResultType.TIMEOUT, logs | |
| 158 | |
| 159 if browser_ok and renderer_ok: | |
| 160 return ResultType.PASS, logs | |
| 161 | |
| 162 return ResultType.FAIL, logs | |
| 163 | |
| 164 | |
| 165 class LibraryLoadMap(dict): | |
| 166 """A helper class to pretty-print a map of library names to load addresses.""" | |
| 167 def __str__(self): | |
| 168 items = ['\'%s\': 0x%x' % (name, address) for \ | |
| 169 (name, address) in self.iteritems()] | |
| 170 return '{%s}' % (', '.join(items)) | |
| 171 | |
| 172 def __repr__(self): | |
| 173 return 'LibraryLoadMap(%s)' % self.__str__() | |
| 174 | |
| 175 | |
| 176 class AddressList(list): | |
| 177 """A helper class to pretty-print a list of load addresses.""" | |
| 178 def __str__(self): | |
| 179 items = ['0x%x' % address for address in self] | |
| 180 return '[%s]' % (', '.join(items)) | |
| 181 | |
| 182 def __repr__(self): | |
| 183 return 'AddressList(%s)' % self.__str__() | |
| 184 | |
| 185 | |
| 186 def _ExtractLibraryLoadAddressesFromLogcat(logs): | |
| 187 """Extract the names and addresses of shared libraries loaded in the | |
| 188 browser and renderer processes. | |
| 189 Args: | |
| 190 logs: A string containing logcat output. | |
| 191 Returns: | |
| 192 A tuple (browser_libs, renderer_libs), where each item is a map of | |
| 193 library names (strings) to library load addresses (ints), for the | |
| 194 browser and renderer processes, respectively. | |
| 195 """ | |
| 196 browser_libs = LibraryLoadMap() | |
| 197 renderer_libs = LibraryLoadMap() | |
| 198 for m in re_library_address.finditer(logs): | |
| 199 process_type, lib_name, lib_address = m.groups() | |
| 200 lib_address = int(lib_address, 16) | |
| 201 if process_type == 'BROWSER': | |
| 202 browser_libs[lib_name] = lib_address | |
| 203 elif process_type == 'RENDERER': | |
| 204 renderer_libs[lib_name] = lib_address | |
| 205 else: | |
| 206 assert False, 'Invalid process type' | |
| 207 | |
| 208 return browser_libs, renderer_libs | |
| 209 | |
| 210 | |
| 211 def _CheckLoadAddressRandomization(lib_map_list, process_type): | |
| 212 """Check that a map of library load addresses is random enough. | |
| 213 Args: | |
| 214 lib_map_list: a list of dictionaries that map library names (string) | |
| 215 to load addresses (int). Each item in the list corresponds to a | |
| 216 different run / process start. | |
| 217 process_type: a string describing the process type. | |
| 218 Returns: | |
| 219 (status, logs) tuple, where <status> is True iff the load addresses are | |
| 220 randomized, False otherwise, and <logs> is a string containing an error | |
| 221 message detailing the libraries that are not randomized properly. | |
| 222 """ | |
| 223 # Collect, for each library, its list of load addresses. | |
| 224 lib_addr_map = {} | |
| 225 for lib_map in lib_map_list: | |
| 226 for lib_name, lib_address in lib_map.iteritems(): | |
| 227 if lib_name not in lib_addr_map: | |
| 228 lib_addr_map[lib_name] = AddressList() | |
| 229 lib_addr_map[lib_name].append(lib_address) | |
| 230 | |
| 231 logging.info('%s library load map: %s' % (process_type, lib_addr_map)) | |
|
bulach
2013/10/07 16:49:44
nit: logging can take a format with vararg, so rat
| |
| 232 | |
| 233 # For each library, check the randomness of its load addresses. | |
| 234 bad_libs = {} | |
| 235 success = True | |
| 236 for lib_name, lib_address_list in lib_addr_map.iteritems(): | |
| 237 # If all addresses are different, skip to next item. | |
| 238 lib_address_set = set(lib_address_list) | |
| 239 # Consider that if there is more than one pair of identical addresses in | |
| 240 # the list, then randomization is broken. | |
| 241 if len(lib_address_set) < len(lib_address_list) - 1: | |
| 242 bad_libs[lib_name] = lib_address_list | |
| 243 | |
| 244 | |
| 245 if bad_libs: | |
| 246 return False, '%s libraries failed randomization: %s' % \ | |
| 247 (process_type, bad_libs) | |
| 248 | |
| 249 return True, '%s libraries properly randomized: %s' % \ | |
| 250 (process_type, lib_addr_map) | |
| 251 | |
| 252 | |
| 253 class LinkerTestCaseBase(object): | |
| 121 """Base class for linker test cases.""" | 254 """Base class for linker test cases.""" |
| 122 | 255 |
| 123 def __init__(self, test_name, is_low_memory=False): | 256 def __init__(self, is_low_memory=False): |
| 124 """Create a test case initialized to run |test_name|. | 257 """Create a test case. |
| 125 | |
| 126 Args: | 258 Args: |
| 127 test_name: The name of the method to run as the test. | |
| 128 is_low_memory: True to simulate a low-memory device, False otherwise. | 259 is_low_memory: True to simulate a low-memory device, False otherwise. |
| 129 """ | 260 """ |
| 130 self.test_name = test_name | 261 self.is_low_memory = is_low_memory |
| 262 if is_low_memory: | |
| 263 test_suffix = 'ForLowMemoryDevice' | |
| 264 else: | |
| 265 test_suffix = 'ForRegularDevice' | |
| 131 class_name = self.__class__.__name__ | 266 class_name = self.__class__.__name__ |
| 132 self.qualified_name = '%s.%s' % (class_name, self.test_name) | 267 self.qualified_name = '%s.%s' % (class_name, test_suffix) |
| 133 self.tagged_name = self.qualified_name | 268 self.tagged_name = self.qualified_name |
| 134 self.is_low_memory = is_low_memory | 269 |
| 270 def _RunTest(self, adb): | |
| 271 """Run the test, must be overriden. | |
| 272 Args: | |
| 273 adb: An AndroidCommands instance to the device. | |
| 274 Returns: | |
| 275 A (status, log) tuple, where <status> is a ResultType constant, and <log> | |
| 276 is the logcat output captured during the test in case of error, or None | |
| 277 in case of success. | |
| 278 """ | |
| 279 return ResultType.FAIL, 'Unimplemented _RunTest() method!' | |
| 135 | 280 |
| 136 def Run(self, device): | 281 def Run(self, device): |
| 282 """Run the test on a given device. | |
| 283 Args: | |
| 284 device: Name of target device where to run the test. | |
| 285 Returns: | |
| 286 A base_test_result.TestRunResult() instance. | |
| 287 """ | |
| 137 margin = 8 | 288 margin = 8 |
| 138 print '[ %-*s ] %s' % (margin, 'RUN', self.tagged_name) | 289 print '[ %-*s ] %s' % (margin, 'RUN', self.tagged_name) |
| 139 logging.info('Running linker test: %s', self.tagged_name) | 290 logging.info('Running linker test: %s', self.tagged_name) |
| 140 adb = android_commands.AndroidCommands(device) | 291 adb = android_commands.AndroidCommands(device) |
| 141 | 292 |
| 142 # 1. Write command-line file with appropriate options. | 293 # Create command-line file on device. |
| 143 command_line_flags = [] | 294 command_line_flags = '' |
| 144 if self.is_low_memory: | 295 if self.is_low_memory: |
| 145 command_line_flags.append('--low-memory-device') | 296 command_line_flags = '--low-memory-device' |
| 146 _CreateCommandLineFileOnDevice(adb, command_line_flags) | 297 _WriteCommandLineFile(adb, command_line_flags, _COMMAND_LINE_FILE) |
| 147 | 298 |
| 148 # 2. Start recording logcat with appropriate filters. | 299 # Run the test. |
| 149 adb.StartRecordingLogcat(clear=True, filters=_LOGCAT_FILTERS) | 300 status, logs = self._RunTest(adb) |
| 150 | 301 |
| 151 try: | 302 result_text = 'OK' |
| 152 # 3. Force-start activity. | 303 if status == ResultType.FAIL: |
| 153 adb.StartActivity(package=_PACKAGE_NAME, | 304 result_text = 'FAILED' |
| 154 activity=_ACTIVITY_NAME, | 305 elif status == ResultType.TIMEOUT: |
| 155 force_stop=True) | 306 result_text = 'TIMEOUT' |
| 156 | 307 print '[ %*s ] %s' % (margin, result_text, self.tagged_name) |
| 157 # 4. Wait up to 30 seconds until the linker test status is in the logcat. | |
| 158 max_tries = 30 | |
| 159 num_tries = 0 | |
| 160 found = False | |
| 161 logcat = None | |
| 162 while num_tries < max_tries: | |
| 163 time.sleep(1) | |
| 164 num_tries += 1 | |
| 165 found, browser_ok, renderer_ok = _CheckLinkerTestStatus( | |
| 166 adb.GetCurrentRecordedLogcat()) | |
| 167 if found: | |
| 168 break | |
| 169 | |
| 170 finally: | |
| 171 # Ensure the ADB polling process is always killed when | |
| 172 # the script is interrupted by the user with Ctrl-C. | |
| 173 logs = adb.StopRecordingLogcat() | |
| 174 | 308 |
| 175 results = base_test_result.TestRunResults() | 309 results = base_test_result.TestRunResults() |
| 176 | 310 results.AddResult( |
| 177 if num_tries >= max_tries: | 311 base_test_result.BaseTestResult( |
| 178 # Timeout | 312 self.tagged_name, |
| 179 print '[ %*s ] %s' % (margin, 'TIMEOUT', self.tagged_name) | 313 status, |
| 180 results.AddResult( | 314 logs)) |
| 181 base_test_result.BaseTestResult( | |
| 182 self.test_name, | |
| 183 base_test_result.ResultType.TIMEOUT, | |
| 184 logs)) | |
| 185 elif browser_ok and renderer_ok: | |
| 186 # Passed | |
| 187 logging.info( | |
| 188 'Logcat start ---------------------------------\n%s' + | |
| 189 'Logcat end -----------------------------------', logs) | |
| 190 print '[ %*s ] %s' % (margin, 'OK', self.tagged_name) | |
| 191 results.AddResult( | |
| 192 base_test_result.BaseTestResult( | |
| 193 self.test_name, | |
| 194 base_test_result.ResultType.PASS)) | |
| 195 else: | |
| 196 print '[ %*s ] %s' % (margin, 'FAILED', self.tagged_name) | |
| 197 # Failed | |
| 198 results.AddResult( | |
| 199 base_test_result.BaseTestResult( | |
| 200 self.test_name, | |
| 201 base_test_result.ResultType.FAIL, | |
| 202 logs)) | |
| 203 | 315 |
| 204 return results | 316 return results |
| 205 | 317 |
| 206 def __str__(self): | 318 def __str__(self): |
| 207 return self.tagged_name | 319 return self.tagged_name |
| 208 | 320 |
| 209 def __repr__(self): | 321 def __repr__(self): |
| 210 return self.tagged_name | 322 return self.tagged_name |
| 323 | |
| 324 | |
| 325 class LinkerSharedRelroTest(LinkerTestCaseBase): | |
| 326 """A linker test case to check the status of shared RELRO sections. | |
| 327 | |
| 328 The core of the checks performed here are pretty simple: | |
| 329 | |
| 330 - Clear the logcat and start recording with an appropriate set of filters. | |
| 331 - Create the command-line appropriate for the test-case. | |
| 332 - Start the activity (always forcing a cold start). | |
| 333 - Every second, look at the current content of the filtered logcat lines | |
| 334 and look for instances of the following: | |
| 335 | |
| 336 BROWSER_LINKER_TEST: <status> | |
| 337 RENDERER_LINKER_TEST: <status> | |
| 338 | |
| 339 where <status> can be either FAIL or SUCCESS. These lines can appear | |
| 340 in any order in the logcat. Once both browser and renderer status are | |
| 341 found, stop the loop. Otherwise timeout after 30 seconds. | |
| 342 | |
| 343 Note that there can be other lines beginning with BROWSER_LINKER_TEST: | |
| 344 and RENDERER_LINKER_TEST:, but are not followed by a <status> code. | |
| 345 | |
| 346 - The test case passes if the <status> for both the browser and renderer | |
| 347 process are SUCCESS. Otherwise its a fail. | |
| 348 """ | |
| 349 def _RunTest(self, adb): | |
| 350 # Wait up to 30 seconds until the linker test status is in the logcat. | |
| 351 return _StartActivityAndWaitForLinkerTestStatus(adb, timeout=30) | |
| 352 | |
| 353 | |
| 354 class LinkerLibraryAddressTest(LinkerTestCaseBase): | |
| 355 """A test case that verifies library load addresses. | |
| 356 | |
| 357 The point of this check is to ensure that the libraries are loaded | |
| 358 according to the following rules: | |
| 359 | |
| 360 - For low-memory devices, they should always be loaded at the same address | |
| 361 in both browser and renderer processes, both below 0x4000_0000. | |
| 362 | |
| 363 - For regular devices, the browser process should load libraries above | |
| 364 0x4000_0000, and renderer ones below it. | |
| 365 """ | |
| 366 def _RunTest(self, adb): | |
| 367 result, logs = _StartActivityAndWaitForLinkerTestStatus(adb, timeout=30) | |
| 368 | |
| 369 # Return immediately in case of timeout. | |
| 370 if result == ResultType.TIMEOUT: | |
| 371 return result, logs | |
| 372 | |
| 373 # Collect the library load addresses in the browser and renderer processes. | |
| 374 browser_libs, renderer_libs = _ExtractLibraryLoadAddressesFromLogcat(logs) | |
| 375 | |
| 376 logging.info('Browser libraries: %s' % browser_libs) | |
| 377 logging.info('Renderer libraries: %s' % renderer_libs) | |
|
bulach
2013/10/07 16:49:44
nit: as above, prefer , rather than % and some pla
| |
| 378 | |
| 379 # Check that the same libraries are loaded into both processes: | |
| 380 browser_set = set(browser_libs.keys()) | |
| 381 renderer_set = set(renderer_libs.keys()) | |
| 382 if browser_set != renderer_set: | |
| 383 logging.error('Library set mistmach browser=%s renderer=%s' % ( | |
| 384 browser_libs.keys(), renderer_libs.keys())) | |
| 385 return ResultType.FAIL, logs | |
| 386 | |
| 387 # And that there are not empty. | |
| 388 if not browser_set: | |
| 389 logging.error('No libraries loaded in any process!') | |
| 390 return ResultType.FAIL, logs | |
| 391 | |
| 392 # Check that the renderer libraries are loaded at 'low-addresses'. i.e. | |
| 393 # below 0x4000_0000, for every kind of device. | |
| 394 memory_boundary = 0x40000000 | |
| 395 bad_libs = [] | |
| 396 for lib_name, lib_address in renderer_libs.iteritems(): | |
| 397 if lib_address >= memory_boundary: | |
| 398 bad_libs.append((lib_name, lib_address)) | |
| 399 | |
| 400 if bad_libs: | |
| 401 logging.error('Renderer libraries loaded at high addresses: %s' % \ | |
| 402 bad_libs) | |
| 403 return ResultType.FAIL, logs | |
| 404 | |
| 405 if self.is_low_memory: | |
| 406 # For low-memory devices, the libraries must all be loaded at the same | |
| 407 # addresses. This also implicitly checks that the browser libraries are at | |
| 408 # low addresses. | |
| 409 addr_mismatches = [] | |
| 410 for lib_name, lib_address in browser_libs.iteritems(): | |
| 411 lib_address2 = renderer_libs[lib_name] | |
| 412 if lib_address != lib_address2: | |
| 413 addr_mismatches.append((lib_name, lib_address, lib_address2)) | |
| 414 | |
| 415 if addr_mismatches: | |
| 416 logging.error('Library load address mismatches: %s' % \ | |
| 417 addr_mismatches) | |
| 418 return ResultType.FAIL, logs | |
| 419 | |
| 420 # For regular devices, check that libraries are loaded at 'high-addresses'. | |
| 421 # Note that for low-memory devices, the previous checks ensure that they | |
| 422 # were loaded at low-addresses. | |
| 423 if not self.is_low_memory: | |
| 424 bad_libs = [] | |
| 425 for lib_name, lib_address in browser_libs.iteritems(): | |
| 426 if lib_address < memory_boundary: | |
| 427 bad_libs.append((lib_name, lib_address)) | |
| 428 | |
| 429 if bad_libs: | |
| 430 logging.error('Browser libraries loaded at low addresses: %s' % \ | |
| 431 repr(bad_libs)) | |
| 432 return ResultType.FAIL, logs | |
| 433 | |
| 434 # Everything's ok. | |
| 435 return ResultType.PASS, logs | |
| 436 | |
| 437 | |
| 438 class LinkerRandomizationTest(LinkerTestCaseBase): | |
| 439 """A linker test case to check that library load address randomization works | |
| 440 properly between successive starts of the test program/activity. | |
| 441 | |
| 442 This starts the activity several time (each time forcing a new process | |
| 443 creation) and compares the load addresses of the libraries in them to | |
| 444 detect that they have changed. | |
| 445 | |
| 446 In theory, two successive runs could (very rarely) use the same load | |
| 447 address, so loop 5 times and compare the values there. It is assumed | |
| 448 that if there are more than one pair of identical addresses, then the | |
| 449 load addresses are not random enough for this test. | |
| 450 """ | |
| 451 def _RunTest(self, adb): | |
| 452 max_loops = 5 | |
| 453 browser_lib_map_list = [] | |
| 454 renderer_lib_map_list = [] | |
| 455 logs_list = [] | |
| 456 for loop in range(max_loops): | |
| 457 # Start the activity. | |
| 458 result, logs = _StartActivityAndWaitForLinkerTestStatus(adb, timeout=30) | |
| 459 if result == ResultType.TIMEOUT: | |
| 460 # Something bad happened. Return immediately. | |
| 461 return result, logs | |
| 462 | |
| 463 # Collect library addresses. | |
| 464 browser_libs, renderer_libs = _ExtractLibraryLoadAddressesFromLogcat(logs) | |
| 465 browser_lib_map_list.append(browser_libs) | |
| 466 renderer_lib_map_list.append(renderer_libs) | |
| 467 logs_list.append(logs) | |
| 468 | |
| 469 # Check randomization in the browser libraries. | |
| 470 logs = '\n'.join(logs_list) | |
| 471 | |
| 472 browser_status, browser_logs = _CheckLoadAddressRandomization( | |
| 473 browser_lib_map_list, 'Browser') | |
| 474 | |
| 475 renderer_status, renderer_logs = _CheckLoadAddressRandomization( | |
| 476 renderer_lib_map_list, 'Renderer') | |
| 477 | |
| 478 if not browser_status: | |
| 479 if self.is_low_memory: | |
| 480 return ResultType.FAIL, browser_logs | |
| 481 | |
| 482 # IMPORTANT NOTE: The system's ASLR implementation seems to be very poor | |
| 483 # when starting an activity process in a loop with "adb shell am start". | |
| 484 # | |
| 485 # When simulating a regular device, loading libraries in the browser | |
| 486 # process uses a simple mmap(NULL, ...) to let the kernel device where to | |
| 487 # load the file (this is similar to what System.loadLibrary() does). | |
| 488 # | |
| 489 # Unfortunately, at least in the context of this test, doing so while | |
| 490 # restarting the activity with the activity manager very, very, often | |
| 491 # results in the system using the same load address for all 5 runs, or | |
| 492 # sometimes only 4 out of 5. | |
| 493 # | |
| 494 # This has been tested experimentally on both Android 4.1.2 and 4.3. | |
| 495 # | |
| 496 # Note that this behaviour doesn't seem to happen when starting an | |
| 497 # application 'normally', i.e. when using the application launcher to | |
| 498 # start the activity. | |
| 499 logging.info('Ignoring system\'s low randomization of browser libraries' + | |
| 500 ' for regular devices') | |
| 501 | |
| 502 if not renderer_status: | |
| 503 return ResultType.FAIL, renderer_logs | |
| 504 | |
| 505 return ResultType.PASS, logs | |
| OLD | NEW |