def test_to_yaml_sysctls(): """ Dump a snapshot with multiple sysctl groups into YAML. YAML string should contain the version of Diffkemp, source kernel directory, a simple list of function groups, each one containing a function list with the "name", "llvm", "glob_var" and "tag" fields set, and the kind of this list, which should be a group list. The LLVM paths in the YAML should be relative to the snapshot directory. """ kernel_dir = "kernel/linux-3.10.0-957.el7" output_dir = "snapshots-sysctl/linux-3.10.0-957.el7" snap = Snapshot.create_from_source(kernel_dir, output_dir, "sysctl", False) snap.add_fun_group("kernel.sched_latency_ns") snap.add_fun_group("kernel.timer_migration") snap.add_fun("sched_proc_update_handler", LlvmKernelModule( "snapshots-sysctl/linux-3.10.0-957.el7/" "kernel/sched/fair.ll"), None, "proc handler", "kernel.sched_latency_ns") snap.add_fun("proc_dointvec_minmax", LlvmKernelModule( "snapshots-sysctl/linux-3.10.0-957.el7/kernel/sysctl.ll"), None, "proc handler", "kernel.timer_migration") yaml_str = snap.to_yaml() yaml_snap = yaml.safe_load(yaml_str) assert len(yaml_snap) == 1 yaml_dict = yaml_snap[0] assert len(yaml_dict) == 5 assert isinstance(yaml_dict["created_time"], datetime.datetime) assert len(yaml_dict["list"]) == 2 assert set([g["sysctl"] for g in yaml_dict["list"]]) == { "kernel.sched_latency_ns", "kernel.timer_migration"} for g in yaml_dict["list"]: assert len(g["functions"]) == 1 if g["sysctl"] == "kernel.sched_latency_ns": assert g["functions"][0] == { "name": "sched_proc_update_handler", "llvm": "kernel/sched/fair.ll", "glob_var": None, "tag": "proc handler" } elif g["sysctl"] == "kernel.timer_migration": assert g["functions"][0] == { "name": "proc_dointvec_minmax", "llvm": "kernel/sysctl.ll", "glob_var": None, "tag": "proc handler" }
def test_filter(): """Filter snapshot functions.""" kernel_dir = "kernel/linux-3.10.0-957.el7" output_dir = "snapshots/linux-3.10.0-957.el7" snap = Snapshot.create_from_source(kernel_dir, output_dir, None, False) snap.add_fun("___pskb_trim", LlvmKernelModule("net/core/skbuff.ll")) snap.add_fun("__alloc_pages_nodemask", LlvmKernelModule("mm/page_alloc.ll")) snap.filter(["__alloc_pages_nodemask"]) assert len(snap.fun_groups[None].functions) == 1 assert "___pskb_trim" not in snap.fun_groups[None].functions assert "__alloc_pages_nodemask" in snap.fun_groups[None].functions
def test_get_by_name_functions(): """Get the module of inserted function by its name.""" kernel_dir = "kernel/linux-3.10.0-957.el7" output_dir = "snapshots/linux-3.10.0-957.el7" snap = Snapshot.create_from_source(kernel_dir, output_dir, None, False) mod_buff = LlvmKernelModule("net/core/skbuff.ll") mod_alloc = LlvmKernelModule("mm/page_alloc.ll") snap.add_fun("___pskb_trim", mod_buff) snap.add_fun("__alloc_pages_nodemask", mod_alloc) fun = snap.get_by_name("___pskb_trim") assert fun.mod is mod_buff fun = snap.get_by_name("__alloc_pages_nodemask") assert fun.mod is mod_alloc
def get_module_from_source(self, source_path): """ Create an LLVM module from a source file. Builds the source into LLVM IR if needed. :param source_path: Relative path to the file :returns Instance of LlvmKernelModule """ name = source_path[:-2] if source_path.endswith(".c") else source_path # If the module has already been created, return it if name in self.modules: return self.modules[name] llvm_file = os.path.join(self.kernel_dir, "{}.ll".format(name)) source_file = os.path.join(self.kernel_dir, source_path) if self.builder: try: self.builder.build_source_to_llvm(source_file, llvm_file) except BuildException: pass if not os.path.isfile(llvm_file): return None mod = LlvmKernelModule(llvm_file, source_file) self.modules[name] = mod return mod
def _from_yaml(self, yaml_file): """ Load the snaphot from its YAML representation. Paths are assumed to be relative to the root directory. :param yaml_file: Contents of the YAML file. """ yaml_file = yaml.safe_load(yaml_file) yaml_dict = yaml_file[0] self.created_time = yaml_dict["created_time"] if os.path.isdir(yaml_dict["source_kernel_dir"]): self.kernel_source = KernelSource(yaml_dict["source_kernel_dir"], True) for g in yaml_dict["list"]: if "sysctl" in g: self.kind = "sysctl" group = g["sysctl"] functions = g["functions"] else: group = None functions = yaml_dict["list"] self.fun_groups[group] = self.FunctionGroup() for f in functions: self.add_fun( f["name"], LlvmKernelModule( os.path.join( os.path.relpath(self.snapshot_source.kernel_dir), f["llvm"])) if f["llvm"] else None, f["glob_var"], f["tag"], group)
def get_module_for_kernel_mod(self, mod_dir, mod_name): """ Get LLVM module for a kernel module. :param mod_dir: Kernel module directory. :param mod_name: Kernel module name. :return: LlvmKernelModule containing the built LLVM file. """ llvm_file = self.builder.build_kernel_mod_to_llvm(mod_dir, mod_name) return LlvmKernelModule(os.path.join(self.kernel_dir, llvm_file))
def from_yaml(self, yaml_file): """ Load the list from YAML file. Paths are assumed to be relative to the root directory. :param yaml_file: Contents of the YAML file. """ funs = yaml.safe_load(yaml_file) for f in funs: self.add(f["name"], LlvmKernelModule(os.path.join(self.root_dir, f["llvm"])))
def test_get_by_name_sysctls(): """Get the module of inserted function by its name and sysctl group.""" kernel_dir = "kernel/linux-3.10.0-957.el7" output_dir = "snapshots/linux-3.10.0-957.el7" snap = Snapshot.create_from_source(kernel_dir, output_dir, "sysctl", False) snap.add_fun_group("kernel.sched_latency_ns") snap.add_fun_group("kernel.timer_migration") mod_fair = LlvmKernelModule( "snapshots-sysctl/linux-3.10.0-957.el7/kernel/sched/fair.ll") mod_sysctl = LlvmKernelModule( "snapshots-sysctl/linux-3.10.0-957.el7/kernel/sysctl.ll") snap.add_fun("sched_proc_update_handler", mod_fair, None, "proc handler", "kernel.sched_latency_ns") snap.add_fun("proc_dointvec_minmax", mod_sysctl, None, "proc handler", "kernel.timer_migration") # Test that the function fun = snap.get_by_name("proc_dointvec_minmax", "kernel.sched_latency_ns") assert fun is None fun = snap.get_by_name("proc_dointvec_minmax", "kernel.timer_migration") assert fun.mod is mod_sysctl
def test_get_modules(): """ Test getting all modules in the snapshot function lists. Check if the snapshot returns a list of all modules of all groups in case that multiple groups are present. """ kernel_dir = "kernel/linux-3.10.0-957.el7" output_dir = "snapshots/linux-3.10.0-957.el7" snap = Snapshot.create_from_source(kernel_dir, output_dir, "sysctl", False) snap.add_fun_group("kernel.sched_latency_ns") snap.add_fun_group("kernel.timer_migration") snap.add_fun("sched_proc_update_handler", LlvmKernelModule("kernel/sched/fair.ll"), None, "proc_handler", "kernel.sched_latency_ns") snap.add_fun("proc_dointvec_minmax", LlvmKernelModule("kernel/sysctl.ll"), None, "proc_handler", "kernel.timer_migration") modules = snap.modules() assert len(modules) == 2 assert set([m.llvm for m in modules ]) == {"kernel/sched/fair.ll", "kernel/sysctl.ll"}
def test_add_fun_none_group(): """Create a snapshot and try to add functions into a None group.""" kernel_dir = "kernel/linux-3.10.0-957.el7" output_dir = "snapshots/linux-3.10.0-957.el7" snap = Snapshot.create_from_source(kernel_dir, output_dir, None, False) mod = LlvmKernelModule("net/core/skbuff.ll") snap.add_fun("___pskb_trim", mod) assert "___pskb_trim" in snap.fun_groups[None].functions fun_desc = snap.fun_groups[None].functions["___pskb_trim"] assert fun_desc.mod is mod assert fun_desc.glob_var is None assert fun_desc.tag is None
def test_to_yaml_functions(): """ Dump a snapshot with a single "None" group into YAML. YAML string should contain the version of Diffkemp, source kernel directory, a simple list of functions, each one having the "name", "llvm", "glob_var" and "tag" fields set, and the kind of this list, which should be a function list. The LLVM paths in the YAML should be relative to the snapshot directory. """ kernel_dir = "kernel/linux-3.10.0-957.el7" output_dir = "snapshots/linux-3.10.0-957.el7" snap = Snapshot.create_from_source(kernel_dir, output_dir, None, False) snap.add_fun( "___pskb_trim", LlvmKernelModule("snapshots/linux-3.10.0-957.el7/net/core/skbuff.ll")) snap.add_fun( "__alloc_pages_nodemask", LlvmKernelModule("snapshots/linux-3.10.0-957.el7/mm/page_alloc.ll")) yaml_str = snap.to_yaml() yaml_snap = yaml.safe_load(yaml_str) assert len(yaml_snap) == 1 yaml_dict = yaml_snap[0] assert len(yaml_dict) == 5 assert isinstance(yaml_dict["created_time"], datetime.datetime) assert len(yaml_dict["list"]) == 2 assert set([f["name"] for f in yaml_dict["list"]]) ==\ {"___pskb_trim", "__alloc_pages_nodemask"} for f in yaml_dict["list"]: if f["name"] == "___pskb_trim": assert f["llvm"] == "net/core/skbuff.ll" elif f["name"] == "__alloc_pages_nodemask": assert f["llvm"] == "mm/page_alloc.ll"
def test_add_fun_sysctl_group(): """Create a snapshot and try to add functions into sysctl groups.""" kernel_dir = "kernel/linux-3.10.0-957.el7" output_dir = "snapshots/linux-3.10.0-957.el7" snap = Snapshot.create_from_source(kernel_dir, output_dir, "sysctl", False) snap.add_fun_group("kernel.sched_latency_ns") mod = LlvmKernelModule("kernel/sched/debug.ll") snap.add_fun("sched_debug_header", mod, "sysctl_sched_latency", "using_data_variable \"sysctl_sched_latency\"", "kernel.sched_latency_ns") assert "sched_debug_header" in snap.fun_groups[ "kernel.sched_latency_ns"].functions fun_desc = snap.fun_groups["kernel.sched_latency_ns"].functions[ "sched_debug_header"] assert fun_desc.mod is mod assert fun_desc.glob_var == "sysctl_sched_latency" assert fun_desc.tag == "using_data_variable \"sysctl_sched_latency\""
def get_module_from_source(self, source_path, created_before=None): """ Create an LLVM module from a source file. Builds the source into LLVM IR if needed. No module is returned if the module is already present but its LLVM IR was generated or its source file modified after the given time constraint. :param source_path: Relative path to the file :param created_before: File creation time constraint. :returns Instance of LlvmKernelModule """ name = source_path[:-2] if source_path.endswith(".c") else source_path llvm_file = os.path.join(self.kernel_dir, "{}.ll".format(name)) source_file = os.path.join(self.kernel_dir, source_path) # If the LLVM IR file exits but was modified after the given timestamp, # do not return the module. if created_before: try: if (os.path.getmtime(source_file) > created_before or os.path.getmtime(llvm_file) > created_before): return None except OSError: pass # If the module has already been created, return it if name in self.modules: return self.modules[name] if self.builder: try: self.builder.build_source_to_llvm(source_file, llvm_file) except BuildException: pass if not os.path.isfile(llvm_file): return None mod = LlvmKernelModule(llvm_file, source_file) self.modules[name] = mod return mod
def compare(args): old_functions = FunctionList(args.snapshot_dir_old) with open(os.path.join(args.snapshot_dir_old, "functions.yaml"), "r") as fun_list_yaml: old_functions.from_yaml(fun_list_yaml.read()) old_source = KernelSource(args.snapshot_dir_old) new_functions = FunctionList(args.snapshot_dir_new) with open(os.path.join(args.snapshot_dir_new, "functions.yaml"), "r") as fun_list_yaml: new_functions.from_yaml(fun_list_yaml.read()) new_source = KernelSource(args.snapshot_dir_new) if args.function: old_functions.filter([args.function]) new_functions.filter([args.function]) config = Config(old_source, new_source, args.show_diff, args.control_flow_only, args.verbose, args.semdiff_tool) result = Result(Result.Kind.NONE, args.snapshot_dir_old, args.snapshot_dir_old) for fun, old_mod in sorted(old_functions.functions.items()): new_mod = new_functions.get_by_name(fun) if not (old_mod.has_function(fun) and new_mod.has_function(fun)): continue fun_result = functions_diff( mod_first=old_mod, mod_second=new_mod, fun_first=fun, fun_second=fun, glob_var=None, config=config) 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) if fun_result.kind in [Result.Kind.ERROR, Result.Kind.UNKNOWN]: print("{}: {}".format(fun, str(fun_result.kind))) elif fun_result.kind == Result.Kind.NOT_EQUAL: print_syntax_diff(args.snapshot_dir_old, args.snapshot_dir_new, fun, fun_result, False, args.show_diff) # Clean LLVM modules (allow GC to collect the occupied memory) old_mod.clean_module() new_mod.clean_module() LlvmKernelModule.clean_all() if args.report_stat: print("") print("Statistics") print("----------") result.report_stat(args.show_errors) return 0
def run_simpll(first, second, fun_first, fun_second, var, suffix=None, cache_dir=None, control_flow_only=False, output_llvm_ir=False, print_asm_diffs=False, verbose=False, use_ffi=False): """ Simplify modules to ease their semantic difference. Uses the SimpLL tool. :return A tuple containing the two LLVM IR files generated by SimpLL followed by the result of the comparison in the form of a graph and a list of missing function definitions. """ stderr = None if not verbose: stderr = open(os.devnull, "w") first_out_name = add_suffix(first, suffix) if suffix else first second_out_name = add_suffix(second, suffix) if suffix else second if use_ffi: output = ffi.new("char [1000000]") conf_struct = ffi.new("struct config *") cache_dir = ffi.new("char []", cache_dir.encode("ascii") if cache_dir else b"") variable = ffi.new("char []", var.encode("ascii") if var else b"") conf_struct = ffi.new("struct config *") conf_struct.CacheDir = cache_dir conf_struct.ControlFlowOnly = control_flow_only conf_struct.OutputLlvmIR = output_llvm_ir conf_struct.PrintAsmDiffs = print_asm_diffs conf_struct.PrintCallStacks = True conf_struct.Variable = variable conf_struct.Verbose = verbose conf_struct.VerboseMacros = False module_left = ffi.new("char []", first.encode("ascii")) module_right = ffi.new("char []", second.encode("ascii")) module_left_out = ffi.new("char []", first_out_name.encode("ascii")) module_right_out = ffi.new("char []", second_out_name.encode("ascii")) fun_left = ffi.new("char []", fun_first.encode("ascii")) fun_right = ffi.new("char []", fun_second.encode("ascii")) try: lib.runSimpLL(module_left, module_right, module_left_out, module_right_out, fun_left, fun_right, conf_struct[0], output) simpll_out = ffi.string(output) except ffi.error: raise SimpLLException("Simplifying files failed") else: try: # Determine the SimpLL binary to use. # The manually built one has priority over the installed one. if os.path.isfile("build/diffkemp/simpll/diffkemp-simpll"): simpll_bin = "build/diffkemp/simpll/diffkemp-simpll" else: simpll_bin = "diffkemp-simpll" # SimpLL command simpll_command = list([simpll_bin, first, second, "--print-callstacks"]) # Main (analysed) functions simpll_command.append("--fun") if fun_first != fun_second: simpll_command.append("{},{}".format(fun_first, fun_second)) else: simpll_command.append(fun_first) # Analysed variable if var: simpll_command.extend(["--var", var]) # Suffix for output files if suffix and output_llvm_ir: simpll_command.extend(["--suffix", suffix]) # Cache directory with equal function pairs if cache_dir: simpll_command.extend(["--cache-dir", cache_dir]) if control_flow_only: simpll_command.append("--control-flow") if output_llvm_ir: simpll_command.append("--output-llvm-ir") if print_asm_diffs: simpll_command.append("--print-asm-diffs") if verbose: simpll_command.append("--verbose") print(" ".join(simpll_command)) simpll_out = check_output(simpll_command) except CalledProcessError: raise SimpLLException("Simplifying files failed") if output_llvm_ir: check_call(["opt", "-S", "-deadargelim", "-o", first_out_name, first_out_name], stderr=stderr) check_call(["opt", "-S", "-deadargelim", "-o", second_out_name, second_out_name], stderr=stderr) first_out = LlvmKernelModule(first_out_name) second_out = LlvmKernelModule(second_out_name) missing_defs = None try: result_graph = ComparisonGraph() simpll_result = yaml.safe_load(simpll_out) if simpll_result is not None: if "function-results" in simpll_result: for fun_result in simpll_result["function-results"]: # Create the vertex from the result and insert it into # the graph. vertex = ComparisonGraph.Vertex.from_yaml( fun_result, result_graph) # Prefer pointed name to ensure that a difference # contaning the variant function as either the left or # the right side has its name in the key. # This is useful because one can tell this is a weak # vertex from its name. if "." in vertex.names[ComparisonGraph.Side.LEFT]: result_graph[vertex.names[ ComparisonGraph.Side.LEFT]] = vertex else: result_graph[vertex.names[ ComparisonGraph.Side.RIGHT]] = vertex result_graph.normalize() result_graph.populate_predecessor_lists() result_graph.mark_uncachable_from_assumed_equal() missing_defs = simpll_result["missing-defs"] \ if "missing-defs" in simpll_result else None except yaml.YAMLError: pass return first_out, second_out, result_graph, missing_defs
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
def simplify_modules_diff(first, second, fun_first, fun_second, var, suffix=None, control_flow_only=False, verbose=False): """ Simplify modules to ease their semantic difference. Uses the SimpLL tool. """ stderr = None if not verbose: stderr = open(os.devnull, "w") first_out_name = add_suffix(first, suffix) if suffix else first second_out_name = add_suffix(second, suffix) if suffix else second try: simpll_command = [ "build/diffkemp/simpll/simpll", first, second, "--print-callstacks" ] # Main (analysed) functions simpll_command.append("--fun") if fun_first != fun_second: simpll_command.append("{},{}".format(fun_first, fun_second)) else: simpll_command.append(fun_first) # Analysed variable if var: simpll_command.extend(["--var", var]) # Suffix for output files if suffix: simpll_command.extend(["--suffix", suffix]) if control_flow_only: simpll_command.append("--control-flow") if verbose: simpll_command.append("--verbose") print(" ".join(simpll_command)) simpll_out = check_output(simpll_command) check_call([ "opt", "-S", "-deadargelim", "-o", first_out_name, first_out_name ], stderr=stderr) check_call([ "opt", "-S", "-deadargelim", "-o", second_out_name, second_out_name ], stderr=stderr) first_out = LlvmKernelModule(first_out_name) second_out = LlvmKernelModule(second_out_name) objects_to_compare = [] missing_defs = None syndiff_defs = None try: simpll_result = yaml.safe_load(simpll_out) if simpll_result is not None: if "diff-functions" in simpll_result: for fun_pair_yaml in simpll_result["diff-functions"]: fun_pair = [ Result.Entity( fun["function"], fun["file"] if "file" in fun else "", fun["line"] if "line" in fun else None, "\n".join([ "{} at {}:{}".format( call["function"], call["file"], call["line"]) for call in fun["callstack"] ]) if "callstack" in fun else "", fun["is-syn-diff"], fun["covered-by-syn-diff"]) for fun in [fun_pair_yaml["first"], fun_pair_yaml["second"]] ] objects_to_compare.append(tuple(fun_pair)) missing_defs = simpll_result["missing-defs"] \ if "missing-defs" in simpll_result else None syndiff_defs = simpll_result["syndiff-defs"] \ if "syndiff-defs" in simpll_result else None except yaml.YAMLError: pass return first_out, second_out, objects_to_compare, missing_defs, \ syndiff_defs except CalledProcessError: raise SimpLLException("Simplifying files failed")