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 24 matching lines...) Expand all Loading... |
35 """ | 35 """ |
36 | 36 |
37 import logging | 37 import logging |
38 import os | 38 import os |
39 import re | 39 import re |
40 import StringIO | 40 import StringIO |
41 import subprocess | 41 import subprocess |
42 import tempfile | 42 import tempfile |
43 import time | 43 import time |
44 | 44 |
| 45 from pylib import constants |
45 from pylib import android_commands | 46 from pylib import android_commands |
46 from pylib import flag_changer | 47 from pylib import flag_changer |
47 from pylib.base import base_test_result | 48 from pylib.base import base_test_result |
48 | 49 |
49 ResultType = base_test_result.ResultType | 50 ResultType = base_test_result.ResultType |
50 | 51 |
51 _PACKAGE_NAME='org.chromium.content_linker_test_apk' | 52 _PACKAGE_NAME='org.chromium.content_linker_test_apk' |
52 _ACTIVITY_NAME='.ContentLinkerTestActivity' | 53 _ACTIVITY_NAME='.ContentLinkerTestActivity' |
53 _COMMAND_LINE_FILE='/data/local/tmp/content-linker-test-command-line' | 54 _COMMAND_LINE_FILE='/data/local/tmp/content-linker-test-command-line' |
54 | 55 |
| 56 # Path to the Linker.java source file. |
| 57 _LINKER_JAVA_SOURCE_PATH = \ |
| 58 'content/public/android/java/src/org/chromium/content/app/Linker.java' |
| 59 |
| 60 # A regular expression used to extract the browser shared RELRO configuration |
| 61 # from the Java source file above. |
| 62 _RE_LINKER_BROWSER_CONFIG = \ |
| 63 re.compile(r'.*BROWSER_SHARED_RELRO_CONFIG\s+=\s+' + \ |
| 64 'BROWSER_SHARED_RELRO_CONFIG_(\S+)\s*;.*', |
| 65 re.MULTILINE | re.DOTALL) |
| 66 |
55 # Logcat filters used during each test. Only the 'chromium' one is really | 67 # Logcat filters used during each test. Only the 'chromium' one is really |
56 # needed, but the logs are added to the TestResult in case of error, and | 68 # needed, but the logs are added to the TestResult in case of error, and |
57 # it is handy to have the 'content_android_linker' ones as well when | 69 # it is handy to have the 'content_android_linker' ones as well when |
58 # troubleshooting. | 70 # troubleshooting. |
59 _LOGCAT_FILTERS = [ '*:s', 'chromium:v', 'content_android_linker:v' ] | 71 _LOGCAT_FILTERS = [ '*:s', 'chromium:v', 'content_android_linker:v' ] |
60 #_LOGCAT_FILTERS = [ '*:v' ] ## DEBUG | 72 #_LOGCAT_FILTERS = [ '*:v' ] ## DEBUG |
61 | 73 |
62 # Regular expression used to match status lines in logcat. | 74 # Regular expression used to match status lines in logcat. |
63 re_status_line = re.compile(r'(BROWSER|RENDERER)_LINKER_TEST: (FAIL|SUCCESS)') | 75 re_status_line = re.compile(r'(BROWSER|RENDERER)_LINKER_TEST: (FAIL|SUCCESS)') |
64 | 76 |
65 # Regular expression used to mach library load addresses in logcat. | 77 # Regular expression used to mach library load addresses in logcat. |
66 re_library_address = re.compile( | 78 re_library_address = re.compile( |
67 r'(BROWSER|RENDERER)_LIBRARY_ADDRESS: (\S+) ([0-9A-Fa-f]+)') | 79 r'(BROWSER|RENDERER)_LIBRARY_ADDRESS: (\S+) ([0-9A-Fa-f]+)') |
68 | 80 |
69 | 81 |
| 82 def _GetBrowserSharedRelroConfig(): |
| 83 """Returns a string corresponding to the Linker's configuration of shared |
| 84 RELRO sections in the browser process. This parses the Java linker source |
| 85 file to get the appropriate information. |
| 86 Return: |
| 87 None in case of error (e.g. could not locate the source file). |
| 88 'NEVER' if the browser process shall never use shared RELROs. |
| 89 'LOW_RAM_ONLY' if if uses it only on low-end devices. |
| 90 'ALWAYS' if it always uses a shared RELRO. |
| 91 """ |
| 92 source_path = \ |
| 93 os.path.join(constants.DIR_SOURCE_ROOT, _LINKER_JAVA_SOURCE_PATH) |
| 94 if not os.path.exists(source_path): |
| 95 logging.error('Could not find linker source file: ' + source_path) |
| 96 return None |
| 97 |
| 98 with open(source_path) as f: |
| 99 configs = _RE_LINKER_BROWSER_CONFIG.findall(f.read()) |
| 100 if not configs: |
| 101 logging.error( |
| 102 'Can\'t find browser shared RELRO configuration value in ' + \ |
| 103 source_path) |
| 104 return None |
| 105 |
| 106 if configs[0] not in ['NEVER', 'LOW_RAM_ONLY', 'ALWAYS']: |
| 107 logging.error('Unexpected browser config value: ' + configs[0]) |
| 108 return None |
| 109 |
| 110 logging.info('Found linker browser shared RELRO config: ' + configs[0]) |
| 111 return configs[0] |
| 112 |
| 113 |
70 def _WriteCommandLineFile(adb, command_line, command_line_file): | 114 def _WriteCommandLineFile(adb, command_line, command_line_file): |
71 """Create a command-line file on the device. This does not use FlagChanger | 115 """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 | 116 because its implementation assumes the device has 'su', and thus does |
73 not work at all with production devices.""" | 117 not work at all with production devices.""" |
74 adb.RunShellCommand('echo "%s" > %s' % (command_line, command_line_file)) | 118 adb.RunShellCommand('echo "%s" > %s' % (command_line, command_line_file)) |
75 | 119 |
76 | 120 |
77 def _CheckLinkerTestStatus(logcat): | 121 def _CheckLinkerTestStatus(logcat): |
78 """Parse the content of |logcat| and checks for both a browser and | 122 """Parse the content of |logcat| and checks for both a browser and |
79 renderer status line. | 123 renderer status line. |
(...skipping 314 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
394 memory_boundary = 0x40000000 | 438 memory_boundary = 0x40000000 |
395 bad_libs = [] | 439 bad_libs = [] |
396 for lib_name, lib_address in renderer_libs.iteritems(): | 440 for lib_name, lib_address in renderer_libs.iteritems(): |
397 if lib_address >= memory_boundary: | 441 if lib_address >= memory_boundary: |
398 bad_libs.append((lib_name, lib_address)) | 442 bad_libs.append((lib_name, lib_address)) |
399 | 443 |
400 if bad_libs: | 444 if bad_libs: |
401 logging.error('Renderer libraries loaded at high addresses: %s', bad_libs) | 445 logging.error('Renderer libraries loaded at high addresses: %s', bad_libs) |
402 return ResultType.FAIL, logs | 446 return ResultType.FAIL, logs |
403 | 447 |
404 if self.is_low_memory: | 448 browser_config = _GetBrowserSharedRelroConfig() |
405 # For low-memory devices, the libraries must all be loaded at the same | 449 if not browser_config: |
406 # addresses. This also implicitly checks that the browser libraries are at | 450 return ResultType.FAIL, 'Bad linker source configuration' |
407 # low addresses. | 451 |
| 452 if browser_config == 'ALWAYS' or \ |
| 453 (browser_config == 'LOW_RAM_ONLY' and self.is_low_memory): |
| 454 # The libraries must all be loaded at the same addresses. This also |
| 455 # implicitly checks that the browser libraries are at low addresses. |
408 addr_mismatches = [] | 456 addr_mismatches = [] |
409 for lib_name, lib_address in browser_libs.iteritems(): | 457 for lib_name, lib_address in browser_libs.iteritems(): |
410 lib_address2 = renderer_libs[lib_name] | 458 lib_address2 = renderer_libs[lib_name] |
411 if lib_address != lib_address2: | 459 if lib_address != lib_address2: |
412 addr_mismatches.append((lib_name, lib_address, lib_address2)) | 460 addr_mismatches.append((lib_name, lib_address, lib_address2)) |
413 | 461 |
414 if addr_mismatches: | 462 if addr_mismatches: |
415 logging.error('Library load address mismatches: %s', | 463 logging.error('Library load address mismatches: %s', |
416 addr_mismatches) | 464 addr_mismatches) |
417 return ResultType.FAIL, logs | 465 return ResultType.FAIL, logs |
418 | 466 |
419 # For regular devices, check that libraries are loaded at 'high-addresses'. | 467 # Otherwise, check that libraries are loaded at 'high-addresses'. |
420 # Note that for low-memory devices, the previous checks ensure that they | 468 # Note that for low-memory devices, the previous checks ensure that they |
421 # were loaded at low-addresses. | 469 # were loaded at low-addresses. |
422 if not self.is_low_memory: | 470 else: |
423 bad_libs = [] | 471 bad_libs = [] |
424 for lib_name, lib_address in browser_libs.iteritems(): | 472 for lib_name, lib_address in browser_libs.iteritems(): |
425 if lib_address < memory_boundary: | 473 if lib_address < memory_boundary: |
426 bad_libs.append((lib_name, lib_address)) | 474 bad_libs.append((lib_name, lib_address)) |
427 | 475 |
428 if bad_libs: | 476 if bad_libs: |
429 logging.error('Browser libraries loaded at low addresses: %s', bad_libs) | 477 logging.error('Browser libraries loaded at low addresses: %s', bad_libs) |
430 return ResultType.FAIL, logs | 478 return ResultType.FAIL, logs |
431 | 479 |
432 # Everything's ok. | 480 # Everything's ok. |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
466 | 514 |
467 # Check randomization in the browser libraries. | 515 # Check randomization in the browser libraries. |
468 logs = '\n'.join(logs_list) | 516 logs = '\n'.join(logs_list) |
469 | 517 |
470 browser_status, browser_logs = _CheckLoadAddressRandomization( | 518 browser_status, browser_logs = _CheckLoadAddressRandomization( |
471 browser_lib_map_list, 'Browser') | 519 browser_lib_map_list, 'Browser') |
472 | 520 |
473 renderer_status, renderer_logs = _CheckLoadAddressRandomization( | 521 renderer_status, renderer_logs = _CheckLoadAddressRandomization( |
474 renderer_lib_map_list, 'Renderer') | 522 renderer_lib_map_list, 'Renderer') |
475 | 523 |
| 524 browser_config = _GetBrowserSharedRelroConfig() |
| 525 if not browser_config: |
| 526 return ResultType.FAIL, 'Bad linker source configuration' |
| 527 |
476 if not browser_status: | 528 if not browser_status: |
477 if self.is_low_memory: | 529 if browser_config == 'ALWAYS' or \ |
478 return ResultType.FAIL, browser_logs | 530 (browser_config == 'LOW_RAM_ONLY' and self.is_low_memory): |
| 531 return ResultType.FAIL, browser_logs |
479 | 532 |
480 # IMPORTANT NOTE: The system's ASLR implementation seems to be very poor | 533 # IMPORTANT NOTE: The system's ASLR implementation seems to be very poor |
481 # when starting an activity process in a loop with "adb shell am start". | 534 # when starting an activity process in a loop with "adb shell am start". |
482 # | 535 # |
483 # When simulating a regular device, loading libraries in the browser | 536 # When simulating a regular device, loading libraries in the browser |
484 # process uses a simple mmap(NULL, ...) to let the kernel device where to | 537 # process uses a simple mmap(NULL, ...) to let the kernel device where to |
485 # load the file (this is similar to what System.loadLibrary() does). | 538 # load the file (this is similar to what System.loadLibrary() does). |
486 # | 539 # |
487 # Unfortunately, at least in the context of this test, doing so while | 540 # Unfortunately, at least in the context of this test, doing so while |
488 # restarting the activity with the activity manager very, very, often | 541 # restarting the activity with the activity manager very, very, often |
489 # results in the system using the same load address for all 5 runs, or | 542 # results in the system using the same load address for all 5 runs, or |
490 # sometimes only 4 out of 5. | 543 # sometimes only 4 out of 5. |
491 # | 544 # |
492 # This has been tested experimentally on both Android 4.1.2 and 4.3. | 545 # This has been tested experimentally on both Android 4.1.2 and 4.3. |
493 # | 546 # |
494 # Note that this behaviour doesn't seem to happen when starting an | 547 # Note that this behaviour doesn't seem to happen when starting an |
495 # application 'normally', i.e. when using the application launcher to | 548 # application 'normally', i.e. when using the application launcher to |
496 # start the activity. | 549 # start the activity. |
497 logging.info('Ignoring system\'s low randomization of browser libraries' + | 550 logging.info('Ignoring system\'s low randomization of browser libraries' + |
498 ' for regular devices') | 551 ' for regular devices') |
499 | 552 |
500 if not renderer_status: | 553 if not renderer_status: |
501 return ResultType.FAIL, renderer_logs | 554 return ResultType.FAIL, renderer_logs |
502 | 555 |
503 return ResultType.PASS, logs | 556 return ResultType.PASS, logs |
| 557 |
| 558 |
| 559 class LinkerLowMemoryThresholdTest(LinkerTestCaseBase): |
| 560 """This test checks that the definitions for the low-memory device physical |
| 561 RAM threshold are identical in the base/ and linker sources. Because these |
| 562 two components should absolutely not depend on each other, it's difficult |
| 563 to perform this check correctly at runtime inside the linker test binary |
| 564 without introducing hairy dependency issues in the build, or complicated |
| 565 plumbing at runtime. |
| 566 |
| 567 To work-around this, this test looks directly into the sources for a |
| 568 definition of the same constant that should look like: |
| 569 |
| 570 #define ANDROID_LOW_MEMORY_DEVICE_THRESHOLD_MB <number> |
| 571 |
| 572 And will check that the values for <number> are identical in all of |
| 573 them.""" |
| 574 |
| 575 # A regular expression used to find the definition of the threshold in all |
| 576 # sources: |
| 577 _RE_THRESHOLD_DEFINITION = re.compile( |
| 578 r'^\s*#\s*define\s+ANDROID_LOW_MEMORY_DEVICE_THRESHOLD_MB\s+(\d+)\s*$', |
| 579 re.MULTILINE) |
| 580 |
| 581 # The list of source files, relative to DIR_SOURCE_ROOT, which must contain |
| 582 # a line that matches the re above. |
| 583 _SOURCES_LIST = [ |
| 584 'base/android/sys_utils.cc', |
| 585 'content/common/android/linker/linker_jni.cc' ] |
| 586 |
| 587 def _RunTest(self, adb): |
| 588 failure = False |
| 589 values = [] |
| 590 # First, collect all the values in all input sources. |
| 591 re = LinkerLowMemoryThresholdTest._RE_THRESHOLD_DEFINITION |
| 592 for source in LinkerLowMemoryThresholdTest._SOURCES_LIST: |
| 593 source_path = os.path.join(constants.DIR_SOURCE_ROOT, source); |
| 594 if not os.path.exists(source_path): |
| 595 logging.error('Missing source file: ' + source_path) |
| 596 failure = True |
| 597 continue |
| 598 with open(source_path) as f: |
| 599 source_text = f.read() |
| 600 # For some reason, re.match() never works here. |
| 601 source_values = re.findall(source_text) |
| 602 if not source_values: |
| 603 logging.error('Missing low-memory threshold definition in ' + \ |
| 604 source_path) |
| 605 logging.error('Source:\n%s\n' % source_text) |
| 606 failure = True |
| 607 continue |
| 608 values += source_values |
| 609 |
| 610 # Second, check that they are all the same. |
| 611 if not failure: |
| 612 for value in values[1:]: |
| 613 if value != values[0]: |
| 614 logging.error('Value mismatch: ' + repr(values)) |
| 615 failure = True |
| 616 |
| 617 if failure: |
| 618 return ResultType.FAIL, 'Incorrect low-end memory threshold definitions!' |
| 619 |
| 620 return ResultType.PASS, '' |
OLD | NEW |