sanitycheck: native: Add option to enable UBSAN

Add option for native platform to enable undefined behaviour sanitizer.

Signed-off-by: Christian Taedcke <christian.taedcke@lemonbeat.com>
This commit is contained in:
Christian Taedcke 2020-07-06 16:00:57 +02:00 committed by Anas Nashif
commit 3dbe9f2960
3 changed files with 37 additions and 13 deletions

View file

@ -337,6 +337,7 @@ class BinaryHandler(Handler):
self.valgrind = False self.valgrind = False
self.lsan = False self.lsan = False
self.asan = False self.asan = False
self.ubsan = False
self.coverage = False self.coverage = False
def try_kill_process_by_pid(self): def try_kill_process_by_pid(self):
@ -414,6 +415,10 @@ class BinaryHandler(Handler):
if not self.lsan: if not self.lsan:
env["ASAN_OPTIONS"] += "detect_leaks=0" env["ASAN_OPTIONS"] += "detect_leaks=0"
if self.ubsan:
env["UBSAN_OPTIONS"] = "log_path=stdout:halt_on_error=1:" + \
env.get("UBSAN_OPTIONS", "")
with subprocess.Popen(command, stdout=subprocess.PIPE, with subprocess.Popen(command, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, cwd=self.build_dir, env=env) as proc: stderr=subprocess.PIPE, cwd=self.build_dir, env=env) as proc:
logger.debug("Spawning BinaryHandler Thread for %s" % self.name) logger.debug("Spawning BinaryHandler Thread for %s" % self.name)
@ -1623,7 +1628,7 @@ class TestInstance(DisablePyTestCollectionMixin):
self.run = not self.build_only self.run = not self.build_only
return return
def create_overlay(self, platform, enable_asan=False, enable_coverage=False, coverage_platform=[]): def create_overlay(self, platform, enable_asan=False, enable_ubsan=False, enable_coverage=False, coverage_platform=[]):
# Create this in a "sanitycheck/" subdirectory otherwise this # Create this in a "sanitycheck/" subdirectory otherwise this
# will pass this overlay to kconfig.py *twice* and kconfig.cmake # will pass this overlay to kconfig.py *twice* and kconfig.cmake
# will silently give that second time precedence over any # will silently give that second time precedence over any
@ -1647,6 +1652,10 @@ class TestInstance(DisablePyTestCollectionMixin):
if platform.type == "native": if platform.type == "native":
content = content + "\nCONFIG_ASAN=y" content = content + "\nCONFIG_ASAN=y"
if enable_ubsan:
if platform.type == "native":
content = content + "\nCONFIG_UBSAN=y"
f.write(content) f.write(content)
return content return content
@ -1896,6 +1905,7 @@ class ProjectBuilder(FilterBuilder):
self.lsan = kwargs.get('lsan', False) self.lsan = kwargs.get('lsan', False)
self.asan = kwargs.get('asan', False) self.asan = kwargs.get('asan', False)
self.ubsan = kwargs.get('ubsan', False)
self.valgrind = kwargs.get('valgrind', False) self.valgrind = kwargs.get('valgrind', False)
self.extra_args = kwargs.get('extra_args', []) self.extra_args = kwargs.get('extra_args', [])
self.device_testing = kwargs.get('device_testing', False) self.device_testing = kwargs.get('device_testing', False)
@ -1962,6 +1972,7 @@ class ProjectBuilder(FilterBuilder):
handler.asan = self.asan handler.asan = self.asan
handler.valgrind = self.valgrind handler.valgrind = self.valgrind
handler.lsan = self.lsan handler.lsan = self.lsan
handler.ubsan = self.ubsan
handler.coverage = self.coverage handler.coverage = self.coverage
handler.binary = os.path.join(instance.build_dir, "zephyr", "zephyr.exe") handler.binary = os.path.join(instance.build_dir, "zephyr", "zephyr.exe")
@ -2168,7 +2179,7 @@ class ProjectBuilder(FilterBuilder):
overlays = extract_overlays(args) overlays = extract_overlays(args)
if (self.testcase.extra_configs or self.coverage or if (self.testcase.extra_configs or self.coverage or
self.asan): self.asan or self.ubsan):
overlays.append(os.path.join(instance.build_dir, overlays.append(os.path.join(instance.build_dir,
"sanitycheck", "testcase_extra.conf")) "sanitycheck", "testcase_extra.conf"))
@ -2274,6 +2285,7 @@ class TestSuite(DisablePyTestCollectionMixin):
self.device_testing = False self.device_testing = False
self.fixtures = [] self.fixtures = []
self.enable_coverage = False self.enable_coverage = False
self.enable_ubsan = False
self.enable_lsan = False self.enable_lsan = False
self.enable_asan = False self.enable_asan = False
self.enable_valgrind = False self.enable_valgrind = False
@ -2620,7 +2632,7 @@ class TestSuite(DisablePyTestCollectionMixin):
self.device_testing, self.device_testing,
self.fixtures self.fixtures
) )
instance.create_overlay(platform, self.enable_asan, self.enable_coverage, self.coverage_platform) instance.create_overlay(platform, self.enable_asan, self.enable_ubsan, self.enable_coverage, self.coverage_platform)
instance_list.append(instance) instance_list.append(instance)
self.add_instances(instance_list) self.add_instances(instance_list)
@ -2815,7 +2827,7 @@ class TestSuite(DisablePyTestCollectionMixin):
self.add_instances(instance_list) self.add_instances(instance_list)
for _, case in self.instances.items(): for _, case in self.instances.items():
case.create_overlay(case.platform, self.enable_asan, self.enable_coverage, self.coverage_platform) case.create_overlay(case.platform, self.enable_asan, self.enable_ubsan, self.enable_coverage, self.coverage_platform)
self.discards = discards self.discards = discards
self.selected_platforms = set(p.platform.name for p in self.instances.values()) self.selected_platforms = set(p.platform.name for p in self.instances.values())
@ -2876,6 +2888,7 @@ class TestSuite(DisablePyTestCollectionMixin):
test, test,
lsan=self.enable_lsan, lsan=self.enable_lsan,
asan=self.enable_asan, asan=self.enable_asan,
ubsan=self.enable_ubsan,
coverage=self.enable_coverage, coverage=self.enable_coverage,
extra_args=self.extra_args, extra_args=self.extra_args,
device_testing=self.device_testing, device_testing=self.device_testing,

View file

@ -641,6 +641,14 @@ structure in the main Zephyr tree: boards/<arch>/<board_name>/""")
configuration and when --enable-asan is given. configuration and when --enable-asan is given.
""") """)
parser.add_argument(
"--enable-ubsan", action="store_true",
help="""Enable undefined behavior sanitizer to check for undefined
behaviour during program execution. It uses an optional runtime library
to provide better error diagnostics. This option only works with host
binaries such as those generated for the native_posix configuration.
""")
parser.add_argument("--enable-coverage", action="store_true", parser.add_argument("--enable-coverage", action="store_true",
help="Enable code coverage using gcov.") help="Enable code coverage using gcov.")
@ -777,6 +785,7 @@ def main():
suite.fixtures = options.fixture suite.fixtures = options.fixture
suite.enable_asan = options.enable_asan suite.enable_asan = options.enable_asan
suite.enable_lsan = options.enable_lsan suite.enable_lsan = options.enable_lsan
suite.enable_ubsan = options.enable_ubsan
suite.enable_coverage = options.enable_coverage suite.enable_coverage = options.enable_coverage
suite.enable_valgrind = options.enable_valgrind suite.enable_valgrind = options.enable_valgrind
suite.coverage_platform = options.coverage_platform suite.coverage_platform = options.coverage_platform

View file

@ -53,16 +53,18 @@ def test_check_build_or_run(class_testsuite, monkeypatch, all_testcases_dict, pl
assert testinstance.build_only and not testinstance.run assert testinstance.build_only and not testinstance.run
TESTDATA_2 = [ TESTDATA_2 = [
(True, True, ["demo_board_2"], "native", '\nCONFIG_COVERAGE=y\nCONFIG_COVERAGE_DUMP=y\nCONFIG_ASAN=y'), (True, True, True, ["demo_board_2"], "native", '\nCONFIG_COVERAGE=y\nCONFIG_COVERAGE_DUMP=y\nCONFIG_ASAN=y\nCONFIG_UBSAN=y'),
(False, True, ["demo_board_2"], 'native', '\nCONFIG_COVERAGE=y\nCONFIG_COVERAGE_DUMP=y'), (True, False, True, ["demo_board_2"], "native", '\nCONFIG_COVERAGE=y\nCONFIG_COVERAGE_DUMP=y\nCONFIG_ASAN=y'),
(True, True, ["demo_board_2"], 'mcu', '\nCONFIG_COVERAGE=y\nCONFIG_COVERAGE_DUMP=y'), (False, False, True, ["demo_board_2"], 'native', '\nCONFIG_COVERAGE=y\nCONFIG_COVERAGE_DUMP=y'),
(False, False, ["demo_board_2"], 'native', ''), (True, False, True, ["demo_board_2"], 'mcu', '\nCONFIG_COVERAGE=y\nCONFIG_COVERAGE_DUMP=y'),
(False, True, ['demo_board_1'], 'native', ''), (False, False, False, ["demo_board_2"], 'native', ''),
(True, False, ["demo_board_2"], 'native', '\nCONFIG_ASAN=y'), (False, False, True, ['demo_board_1'], 'native', ''),
(True, False, False, ["demo_board_2"], 'native', '\nCONFIG_ASAN=y'),
(False, True, False, ["demo_board_2"], 'native', '\nCONFIG_UBSAN=y'),
] ]
@pytest.mark.parametrize("enable_asan, enable_coverage, coverage_platform, platform_type, expected_content", TESTDATA_2) @pytest.mark.parametrize("enable_asan, enable_ubsan, enable_coverage, coverage_platform, platform_type, expected_content", TESTDATA_2)
def test_create_overlay(class_testsuite, all_testcases_dict, platforms_list, enable_asan, enable_coverage, coverage_platform, platform_type, expected_content): def test_create_overlay(class_testsuite, all_testcases_dict, platforms_list, enable_asan, enable_ubsan, enable_coverage, coverage_platform, platform_type, expected_content):
"""Test correct content is written to testcase_extra.conf based on if conditions """Test correct content is written to testcase_extra.conf based on if conditions
TO DO: Add extra_configs to the input list""" TO DO: Add extra_configs to the input list"""
class_testsuite.testcases = all_testcases_dict class_testsuite.testcases = all_testcases_dict
@ -72,7 +74,7 @@ def test_create_overlay(class_testsuite, all_testcases_dict, platforms_list, ena
testinstance = TestInstance(testcase, platform, class_testsuite.outdir) testinstance = TestInstance(testcase, platform, class_testsuite.outdir)
platform.type = platform_type platform.type = platform_type
assert testinstance.create_overlay(platform, enable_asan, enable_coverage, coverage_platform) == expected_content assert testinstance.create_overlay(platform, enable_asan, enable_ubsan, enable_coverage, coverage_platform) == expected_content
def test_calculate_sizes(class_testsuite, all_testcases_dict, platforms_list): def test_calculate_sizes(class_testsuite, all_testcases_dict, platforms_list):
""" Test Calculate sizes method for zephyr elf""" """ Test Calculate sizes method for zephyr elf"""