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 |