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"
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
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