예제 #1
0
def test_load_snapshot_from_dir_functions():
    """
    Create a temporary snapshot directory and try to parse it. Use a YAML
    configuration file that contains only a list of functions. Expect that
    the function list inside the parsed snapshot contains a single "None"
    group which contains the list of loaded functions. All parsed LLVM paths
    should contain the list root dir.
    """
    with TemporaryDirectory(prefix="test_snapshots_") as snap_dir, \
            NamedTemporaryFile(mode="w+t", prefix="snapshot_", suffix=".yaml",
                               dir=snap_dir) as config_file:
        # Populate the temporary snapshot configuration file.
        config_file.writelines("""
        - created_time: 2020-01-01 00:00:00.000001+00:00
          diffkemp_version: '0.1'
          kind: function_list
          list:
          - glob_var: null
            llvm: net/core/skbuff.ll
            name: ___pskb_trim
            tag: null
          - glob_var: null
            llvm: mm/page_alloc.ll
            name: __alloc_pages_nodemask
            tag: null
          llvm_source_finder:
            kind: kernel_with_builder
            path: null
          source_dir: /diffkemp/kernel/linux-3.10.0-957.el7
          llvm_version: 13
        """)

        # Load the temporary snapshot configuration file.
        config_file.seek(0)
        config_filename = os.path.basename(config_file.name)
        snap = Snapshot.load_from_dir(snap_dir, config_filename)

        assert str(snap.created_time) == "2020-01-01 00:00:00.000001+00:00"
        assert isinstance(snap.snapshot_tree, SourceTree)
        assert isinstance(snap.snapshot_tree.source_finder,
                          KernelLlvmSourceBuilder)
        assert snap.snapshot_tree.source_dir == snap_dir
        assert len(snap.fun_groups) == 1
        assert None in snap.fun_groups
        assert len(snap.fun_groups[None].functions) == 2
        assert set(snap.fun_groups[None].functions.keys()) == \
            {"___pskb_trim",
             "__alloc_pages_nodemask"}

        for name, f in snap.fun_groups[None].functions.items():
            assert f.glob_var is None
            assert f.tag is None
            if name == "___pskb_trim":
                assert os.path.abspath(f.mod.llvm) == snap_dir + \
                       "/net/core/skbuff.ll"
            elif name == "__alloc_pages_nodemask":
                assert os.path.abspath(f.mod.llvm) == snap_dir + \
                       "/mm/page_alloc.ll"
예제 #2
0
def test_load_snapshot_from_dir_sysctls():
    """
    Create a temporary snapshot directory and try to parse it. Use a YAML
    configuration file that contains a list of sysctl groups, each group
    containing a single function. All parsed LLVM paths should contain the list
    root dir.
    """
    with TemporaryDirectory(prefix="test_snapshots_sysctl_") as snap_dir, \
            NamedTemporaryFile(mode="w+t", prefix="snapshot_", suffix=".yaml",
                               dir=snap_dir) as config_file:
        # Populate the temporary sysctl snapshot configuration file.
        config_file.writelines("""
        - created_time: 2020-01-01 00:00:00.000001
          diffkemp_version: '0.1'
          kind: function_list
          list:
          - functions:
            - glob_var: null
              llvm: kernel/sched/fair.ll
              name: sched_proc_update_handler
              tag: proc handler
            sysctl: kernel.sched_latency_ns
          - functions:
            - glob_var: null
              llvm: kernel/sysctl.ll
              name: proc_dointvec_minmax
              tag: proc handler
            sysctl: kernel.timer_migration
          source_kernel_dir: /diffkemp/kernel/linux-3.10.0-957.el7
        """)

        # Load the temporary sysctl snapshot configuration file.
        config_file.seek(0)
        config_filename = os.path.basename(config_file.name)
        snap = Snapshot.load_from_dir(snap_dir, config_filename)

        assert str(snap.created_time) == "2020-01-01 00:00:00.000001"
        assert len(snap.fun_groups) == 2
        assert set(snap.fun_groups.keys()) == {
            "kernel.sched_latency_ns", "kernel.timer_migration"
        }

        for name, g in snap.fun_groups.items():
            f = None
            assert len(g.functions) == 1
            if name == "kernel.sched_latency_ns":
                assert g.functions.keys() == {"sched_proc_update_handler"}
                f = g.functions["sched_proc_update_handler"]
                assert os.path.abspath(f.mod.llvm) == snap_dir + \
                    "/kernel/sched/fair.ll"
            elif name == "kernel.timer_migration":
                assert g.functions.keys() == {"proc_dointvec_minmax"}
                f = g.functions["proc_dointvec_minmax"]
                assert os.path.abspath(f.mod.llvm) == snap_dir + \
                    "/kernel/sysctl.ll"
            assert f.tag == "proc handler"
            assert f.glob_var is None
예제 #3
0
def compare(args):
    """
    Compare snapshots of linux kernels. Runs the semantic comparison and shows
    information about the compared functions that are semantically different.
    """
    # Parse both the new and the old snapshot.
    old_snapshot = Snapshot.load_from_dir(args.snapshot_dir_old)
    new_snapshot = Snapshot.load_from_dir(args.snapshot_dir_new)

    # Set the output directory
    if not args.stdout:
        if args.output_dir:
            output_dir = args.output_dir
            if os.path.isdir(output_dir):
                sys.stderr.write("Error: output directory exists\n")
                sys.exit(errno.EEXIST)
        else:
            output_dir = default_output_dir(args.snapshot_dir_old,
                                            args.snapshot_dir_new)
    else:
        output_dir = None

    if args.function:
        old_snapshot.filter([args.function])
        new_snapshot.filter([args.function])

    config = Config(old_snapshot, new_snapshot, args.show_diff,
                    args.output_llvm_ir, args.control_flow_only,
                    args.print_asm_diffs, args.verbose, args.enable_simpll_ffi,
                    args.semdiff_tool)
    result = Result(Result.Kind.NONE,
                    args.snapshot_dir_old,
                    args.snapshot_dir_old,
                    start_time=default_timer())

    for group_name, group in sorted(old_snapshot.fun_groups.items()):
        group_printed = False

        # Set the group directory
        if output_dir is not None and group_name is not None:
            group_dir = os.path.join(output_dir, group_name)
        else:
            group_dir = None

        result_graph = None
        cache = SimpLLCache(mkdtemp())

        if args.enable_module_cache:
            module_cache = _generate_module_cache(group.functions.items(),
                                                  group_name, new_snapshot, 3)
        else:
            module_cache = None

        for fun, old_fun_desc in sorted(group.functions.items()):
            # Check if the function exists in the other snapshot
            new_fun_desc = new_snapshot.get_by_name(fun, group_name)
            if not new_fun_desc:
                continue

            # Check if the module exists in both snapshots
            if old_fun_desc.mod is None or new_fun_desc.mod is None:
                result.add_inner(Result(Result.Kind.UNKNOWN, fun, fun))
                if group_name is not None and not group_printed:
                    print("{}:".format(group_name))
                    group_printed = True
                print("{}: unknown".format(fun))
                continue

            # If function has a global variable, set it
            glob_var = KernelParam(old_fun_desc.glob_var) \
                if old_fun_desc.glob_var else None

            # Run the semantic diff
            fun_result = functions_diff(mod_first=old_fun_desc.mod,
                                        mod_second=new_fun_desc.mod,
                                        fun_first=fun,
                                        fun_second=fun,
                                        glob_var=glob_var,
                                        config=config,
                                        prev_result_graph=result_graph,
                                        function_cache=cache,
                                        module_cache=module_cache)
            result_graph = fun_result.graph

            if fun_result is not None:
                if args.regex_filter is not None:
                    # Filter results by regex
                    pattern = re.compile(args.regex_filter)
                    for called_res in fun_result.inner.values():
                        if pattern.search(called_res.diff):
                            break
                    else:
                        fun_result.kind = Result.Kind.EQUAL_SYNTAX

                result.add_inner(fun_result)

                # Printing information about failures and non-equal functions.
                if fun_result.kind in [
                        Result.Kind.NOT_EQUAL, Result.Kind.ERROR,
                        Result.Kind.UNKNOWN
                ]:
                    if fun_result.kind == Result.Kind.NOT_EQUAL:
                        # Create the output directory if needed
                        if output_dir is not None:
                            if not os.path.isdir(output_dir):
                                os.mkdir(output_dir)
                        # Create the group directory or print the group name
                        # if needed
                        if group_dir is not None:
                            if not os.path.isdir(group_dir):
                                os.mkdir(group_dir)
                        elif group_name is not None and not group_printed:
                            print("{}:".format(group_name))
                            group_printed = True
                        print_syntax_diff(
                            snapshot_dir_old=args.snapshot_dir_old,
                            snapshot_dir_new=args.snapshot_dir_new,
                            fun=fun,
                            fun_result=fun_result,
                            fun_tag=old_fun_desc.tag,
                            output_dir=group_dir if group_dir else output_dir,
                            show_diff=args.show_diff,
                            initial_indent=2 if (group_name is not None
                                                 and group_dir is None) else 0)
                    else:
                        # Print the group name if needed
                        if group_name is not None and not group_printed:
                            print("{}:".format(group_name))
                            group_printed = True
                        print("{}: {}".format(fun, str(fun_result.kind)))

            # Clean LLVM modules (allow GC to collect the occupied memory)
            old_fun_desc.mod.clean_module()
            new_fun_desc.mod.clean_module()
            LlvmKernelModule.clean_all()

    old_snapshot.finalize()
    new_snapshot.finalize()

    if output_dir is not None and os.path.isdir(output_dir):
        print("Differences stored in {}/".format(output_dir))

    if args.report_stat:
        print("")
        print("Statistics")
        print("----------")
        result.stop_time = default_timer()
        result.report_stat(args.show_errors)
    return 0