diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 7ce3ac0bd..3a35c65db 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -102,7 +102,9 @@ set(REVNG_CLI_COMMANDS_MODULE_FILES revng/cli/_commands/opt.py revng/cli/_commands/pipeline_tools.py revng/cli/_commands/llvm_pipeline.py revng/cli/_commands/override_by_name.py revng/cli/_commands/daemon.py - revng/cli/_commands/download_pdb.py revng/cli/_commands/hard_purge.py) + revng/cli/_commands/download_pdb.py revng/cli/_commands/hard_purge.py + revng/cli/_commands/import_idb.py revng/cli/_commands/idb_converter.py) + python_module(TARGET_NAME revng-python-cli-commands MODULE_FILES ${REVNG_CLI_COMMANDS_MODULE_FILES}) diff --git a/python/requirements.txt b/python/requirements.txt index a403639f7..665717dca 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -27,3 +27,6 @@ psutil # Requirements for pdb-download pefile + +# revng.import_idb dependencies +python-idb diff --git a/python/revng/cli/_commands/idb_converter.py b/python/revng/cli/_commands/idb_converter.py new file mode 100644 index 000000000..6543f0049 --- /dev/null +++ b/python/revng/cli/_commands/idb_converter.py @@ -0,0 +1,591 @@ +#!/usr/bin/env python3 +# +# This file is distributed under the MIT License. See LICENSE.md for details. +# + +import sys +from typing import Dict, List, Optional, Set, Tuple, Union + +import idb +import idb.analysis +import idb.fileformat +import idb.typeinf +import idb.typeinf_flags + +import revng.model.v1 as m +from revng.cli.support import log_error +from revng.model.metaaddress import MetaAddressType + +RevngTypes = Union[ + m.UnionType, + m.StructType, + m.PrimitiveType, + m.EnumType, + m.TypedefType, + m.RawFunctionType, + m.CABIFunctionType, +] + +idb_procname_to_revng_arch = { + "x86_64": m.Architecture.x86_64, + "x86": m.Architecture.x86, + "arm": m.Architecture.arm, + "aarch64": m.Architecture.aarch64, + "mips": m.Architecture.mips, + "mipsb": m.Architecture.mips, + "mipsl": m.Architecture.mipsel, + "s390x": m.Architecture.systemz, +} + +revng_arch_to_metaaddr_code_type = { + m.Architecture.x86: MetaAddressType.Code_x86, + m.Architecture.x86_64: MetaAddressType.Code_x86_64, + m.Architecture.arm: MetaAddressType.Code_arm, + m.Architecture.aarch64: MetaAddressType.Code_aarch64, + m.Architecture.mips: MetaAddressType.Code_mips, + m.Architecture.mipsel: MetaAddressType.Code_mipsel, + m.Architecture.systemz: MetaAddressType.Code_systemz, +} + +revng_arch_to_abi = { + m.Architecture.arm: m.ABI.AAPCS, + m.Architecture.aarch64: m.ABI.AAPCS64, + m.Architecture.x86: m.ABI.SystemV_x86, + m.Architecture.x86_64: m.ABI.SystemV_x86_64, + m.Architecture.mips: m.ABI.SystemV_MIPS_o32, + m.Architecture.mipsel: m.ABI.SystemV_MIPSEL_o32, + m.Architecture.systemz: m.ABI.SystemZ_s390x, +} + +revng_arch_to_abiname = { + m.Architecture.arm: "AAPCS", + m.Architecture.aarch64: "AAPCS64", + m.Architecture.x86: "SystemV_x86", + m.Architecture.x86_64: "SystemV_x86_64", + m.Architecture.mips: "SystemV_MIPS_o32", + m.Architecture.mipsel: "SystemV_MIPSEL_o32", + m.Architecture.systemz: "SystemZ_s390x", +} + +CONST_QUALIFIER = m.Qualifier(Kind=m.QualifierKind.Const) + + +class IDBConverter: + def __init__(self, input_idb: idb.fileformat.IDB, base_addr, verbose): + self.idb: idb.fileformat.IDB = input_idb + self.api = idb.IDAPython(self.idb) + + self.arch: m.Architecture = self._get_arch() + self.is64bit: bool = self._is_64_bit() + self.segments: List[m.Segment] = [] + self.revng_types_by_id: Dict[int, RevngTypes] = {} + self.idb_types_to_revng_types: Dict[int, m.QualifiedType] = {} + self.functions: Set[m.Function] = set() + self.dynamic_functions: List[m.DynamicFunction] = [] + self.imported_libraries: List[str] = [] + self.base_addr = base_addr + self.verbose = verbose + + self._structs_to_fixup: Set[Tuple[m.StructType, idb.typeinf.TInfo]] = set() + self._ordinal_types_to_fixup: Set[Tuple[m.QualifiedType, int]] = set() + self._unions_to_fixup: Set[Tuple[m.UnionType, idb.typeinf.TInfo]] = set() + + self._import_types() + self._fixup_structs() + self._fixup_unions() + self._fixup_ordinal_types() + self._import_functions() + self._collect_imports() + + def log(self, message): + if self.verbose: + sys.stderr.write(message + "\n") + + def _import_types(self): + """Imports initial types from the IDB. The types will be incomplete and need to be fixed""" + til = self.idb.til + for type_definition in til.types.defs: + assert isinstance(type_definition, idb.typeinf.TILTypeInfo) + the_type = type_definition.type + assert isinstance(the_type, idb.typeinf.TInfo) + self._convert_idb_type_to_revng_type(the_type, ordinal=type_definition.ordinal) + + def _import_functions(self): + metaaddr_type = revng_arch_to_metaaddr_code_type[self.arch] + if self.arch == m.Architecture.arm and self.api.idc.ItemSize(0x0) == 2: + # If the instrucitons are 16-bit long, it is a Thumb mode. + metaaddr_type = MetaAddressType.Code_arm_thumb + + for function_start_addr in self.api.idautils.Functions(): + function = idb.analysis.Function(self.idb, function_start_addr) + function_name = function.get_name() + + idb_function_type = function.get_signature() + + function_attributes: List[m.FunctionAttribute] = [] + + if self.api.ida_nalt.is_noret(function_start_addr): + function_attributes.insert(0, m.FunctionAttribute.NoReturn) + + function_start_addr += self.base_addr + + if idb_function_type is not None: + qualified_revng_function_type = self._convert_idb_type_to_revng_type( + idb_function_type + ) + revng_function_type = self.unwrap_qualified(qualified_revng_function_type) + revng_function = m.Function( + OriginalName=function_name, + Entry=m.MetaAddress(Address=function_start_addr, Type=metaaddr_type), + Attributes=function_attributes, + Prototype=m.Reference.create(m.Binary, revng_function_type), + ) + else: + self.log(f"warning: Function {function_name} without a signature.") + revng_function = m.Function( + OriginalName=function_name, + Entry=m.MetaAddress(Address=function_start_addr, Type=metaaddr_type), + Attributes=function_attributes, + ) + + self.functions.add(revng_function) + + def _get_arch(self): + inf_structure = self.api.idaapi.get_inf_structure() + procname = inf_structure.procname.lower() + + if procname == "arm" and inf_structure.is_64bit(): + procname = "aarch64" + elif procname == "metapc" and inf_structure.is_64bit(): + procname = "x86_64" + elif procname == "metapc" and inf_structure.is_32bit(): + procname = "x86" + + return idb_procname_to_revng_arch[procname] + + def _is_64_bit(self): + inf_structure = self.api.idaapi.get_inf_structure() + return inf_structure.is_64bit() + + def _import_names_helper(self, function_addr, function_name): + function = idb.analysis.Function(self.idb, function_addr) + if not function: + log_error(f"Unable to find function {function_name}.") + return True + function_name = function.get_name() + function_type = None + try: + function_type = function.get_signature() + except Exception as exception: + log_error(f"Unable to parse function type for {function_name}.") + log_error(str(exception)) + function_type = None + return True + revng_function_type = None + if function_type is not None: + qualified_revng_function_type = self._convert_idb_type_to_revng_type(function_type) + revng_function_type = self.unwrap_qualified(qualified_revng_function_type) + else: + abi = revng_arch_to_abiname[self.arch] + qualified_return_type = self._get_primitive_type(m.PrimitiveTypeKind.Void, 0) + revng_function_type = m.CABIFunctionType( + OriginalName=function_name, + ABI=abi, + ReturnType=qualified_return_type, + Arguments=[], + ) + self.revng_types_by_id[revng_function_type.ID] = revng_function_type + dynamic_function = m.DynamicFunction( + OriginalName=function_name, + Prototype=m.Reference.create(m.Binary, revng_function_type), + ) + self.dynamic_functions.append(dynamic_function) + return True + + def _collect_imports(self): + # NOTE: If @plt was used, the functions are in Functions() already, but the name starts + # with ".", and we do import types for them during parsing of regular/local functions. + + # NOTE: We cannot use api.ida_nalt.get_import_module_qty() here, since it relies + # on `library names`, but IDA prints `.dynsym` as library for each library in case of ELF. + + for mod_index in range(self.api.ida_nalt.get_import_module_qty() + 1): + + def import_names_callback(function_addr, function_name, ignored_always_none): + assert ignored_always_none is None + return self._import_names_helper(function_addr, function_name) + + try: + mod_name = self.api.ida_nalt.get_import_module_name(mod_index) + except KeyError as exception: + # TODO: Due to the bug mentioned bellow, we try sometimes to + # find imported modules that do not exist. + if mod_index != 0: + # It would be very strange if the mod_index is not 0. + log_error(f"Unable to find module with index: {mod_index}.") + log_error(str(exception)) + continue + # NOTE: IDA 7 has a bug when reporting imported library names - prints `.dynsym` for + # each import. + if mod_name and mod_name != ".dynsym": + self.imported_libraries.append(mod_name) + self.api.ida_nalt.enum_import_names(mod_index, import_names_callback) + + def _fixup_ordinal_types(self): + """Fixes some types that were referring an ordinal that was not observed yet.""" + while self._ordinal_types_to_fixup: + quialified_type, idb_ordinal_type = self._ordinal_types_to_fixup.pop() + assert idb_ordinal_type in self.idb_types_to_revng_types + real_quialified_type = self.idb_types_to_revng_types[idb_ordinal_type] + + the_revng_type = self.unwrap_qualified(real_quialified_type) + revng_type_to_fix = self.unwrap_qualified(quialified_type) + # TODO: For now, we found out that structs can be affected by this only. + assert isinstance(the_revng_type, m.StructType) and isinstance( + revng_type_to_fix, m.StructType + ) + + revng_type_to_fix.Fields = the_revng_type.Fields + revng_type_to_fix.OriginalName = the_revng_type.OriginalName + revng_type_to_fix.Size = the_revng_type.Size + + def _fixup_structs(self): + while self._structs_to_fixup: + revng_type, idb_type = self._structs_to_fixup.pop() + + assert isinstance(revng_type, m.StructType) + assert idb_type.is_decl_struct() + + fields = [] + committed_size = 0 + + # TODO: For now, we are ignoring structs that contain bitfields. + # We should also improve the python-idb package to pickup the struct size directly from + # the IDB files (e.g. to populate the `type.type_details.storage_size` for structs). + for member in idb_type.type_details.members: + if member.type.is_decl_bitfield(): + self.log( + f"warning: Ignoring {revng_type.OriginalName} struct that contains a " + "bitfield." + ) + return + + for member in idb_type.type_details.members: + underlying_type = self._convert_idb_type_to_revng_type(member.type) + revng_member = m.StructField( + OriginalName=member.name, + Type=underlying_type, + Offset=committed_size, + ) + member_size = member.type.get_size() + if member_size == 0: + self.log( + f"warning: Dropping zero-sized field {member.name} of struct " + f"{revng_type.OriginalName}." + ) + else: + fields.append(revng_member) + committed_size += member_size + + revng_type.Fields = fields + revng_type.Size = committed_size + + def _fixup_unions(self): + while self._unions_to_fixup: + revng_type, idb_type = self._unions_to_fixup.pop() + + assert isinstance(revng_type, m.UnionType) + assert idb_type.is_decl_union() + + fields = [] + for idx, member in enumerate(idb_type.type_details.members): + qualified_type = self._convert_idb_type_to_revng_type(member.type) + revng_member = m.UnionField( + OriginalName=member.name, + Type=qualified_type, + Index=idx, + ) + fields.append(revng_member) + + revng_type.Fields = fields + + def _convert_idb_type_to_revng_type( + self, + type: idb.typeinf.TInfo, # noqa: A002 + ordinal=None, + ) -> m.QualifiedType: + """Converts the given TInfo obtained from python-idb to the corresponding revng + QualifiedType. If available, the integer identifying the type in the IDB (ordinal) should + be supplied to allow handling circular references. If a type with the given ordinal was + already converted the same instance is returned. + """ + assert isinstance(type, idb.typeinf.TInfo) + + # Check if we already converted this type, and if so return the existing type. + # Fundamental to handle circular dependencies. + existing_revng_type = self.idb_types_to_revng_types.get(ordinal) + if existing_revng_type is not None: + return existing_revng_type + + type_name = type.get_name() + revng_type_qualifiers: List[m.Qualifier] = [] + + if type.is_decl_typedef(): + aliased_type = type.get_final_tinfo() + + # IDA's types could be generated/identified in two ways: by ordinal and by names, + # so we handle both ways here. + aliased_type_ordinal = None + if type.type_details.is_ordref: + aliased_type_ordinal = type.type_details.ordinal + else: + aliased_tiltypeinfo = type.til.types.find_by_name(aliased_type.name) + if aliased_tiltypeinfo: + aliased_type_ordinal = aliased_tiltypeinfo.ordinal + + qualified_type = self._convert_idb_type_to_revng_type( + aliased_type, ordinal=aliased_type_ordinal + ) + + revng_type = m.TypedefType(OriginalName=type_name, UnderlyingType=qualified_type) + + elif type.is_decl_enum(): + revng_underlying_type = self._get_primitive_type( + m.PrimitiveTypeKind.Unsigned, type.type_details.storage_size + ) + entries = [] + for member in type.type_details.members: + member_value = member.value + size_of_underlying_type_in_bits = type.type_details.storage_size * 8 + if member_value >= 2**size_of_underlying_type_in_bits: + self.log( + f"warning: Value {hex(member_value)} for enum member " + f"{type_name}.{member.name} out of range, ignoring it." + ) + continue + # TODO: We should keep the user comment which might exist in member.cmt. + enum_entry = m.EnumEntry( + OriginalName=member.name, + Value=member_value, + ) + entries.append(enum_entry) + + if len(entries) == 0: + self.log(f"warning: An empty enum type: {type_name}.") + revng_type = m.TypedefType( + OriginalName=type_name, UnderlyingType=revng_underlying_type + ) + else: + revng_type = m.EnumType( + OriginalName=type_name, + Entries=entries, + UnderlyingType=revng_underlying_type, + ) + + elif type.is_decl_struct(): + if type.type_details.ref is not None and type.type_details.ref.type_details.is_ordref: + if type.type_details.ref.type_details.ordinal not in self.idb_types_to_revng_types: + # Make a placeholder for this, since we did not observe the type yet. + revng_type = m.StructType(OriginalName="", Size=0, Fields=[]) + qualified_type = m.QualifiedType( + UnqualifiedType=m.Reference.create(m.Binary, revng_type), + Qualifiers=revng_type_qualifiers, + ) + self.revng_types_by_id[revng_type.ID] = revng_type + self._ordinal_types_to_fixup.add( + (qualified_type, type.type_details.ref.type_details.ordinal) + ) + return qualified_type + else: + return self.idb_types_to_revng_types[type.type_details.ref.type_details.ordinal] + else: + revng_type = m.StructType(OriginalName=type_name, Size=0, Fields=[]) + # Empty structs will be considered as invalid types. + if len(type.type_details.members) == 0: + self.log( + f"warning: Found invalid empty struct {type_name}, replacing with a " + "void* typedef." + ) + else: + # Struct members and size will be computed later. + self._structs_to_fixup.add((revng_type, type)) + + elif type.is_decl_union(): + # Union members will be computed later. + revng_type = m.UnionType(OriginalName=type_name, Fields=[]) + self._unions_to_fixup.add((revng_type, type)) + + elif type.is_decl_ptr(): + underlying_type = self._convert_idb_type_to_revng_type(type.type_details.obj_type) + revng_type = self.resolve_typeref(underlying_type.UnqualifiedType) + revng_type_qualifiers = list(underlying_type.Qualifiers) + + elif type.is_decl_array(): + underlying_type = self._convert_idb_type_to_revng_type(type.type_details.elem_type) + revng_type = self.resolve_typeref(underlying_type.UnqualifiedType) + revng_type_qualifiers = list(underlying_type.Qualifiers) + + elif type.is_decl_bool(): + size = type.get_size() + revng_type = self.unwrap_qualified( + self._get_primitive_type(m.PrimitiveTypeKind.Unsigned, size) + ) + + elif type.is_decl_int() or type.is_decl_floating(): + size = type.get_size() + primitive_kind = get_primitive_kind(type) + revng_type = self.unwrap_qualified(self._get_primitive_type(primitive_kind, size)) + + elif type.is_decl_void(): + primitive_kind = m.PrimitiveTypeKind.Void + size = 0 + if type.get_name() != "": + # Treat this case as `typedef void someothername`. + revng_void_type = self._get_primitive_type(primitive_kind, size) + revng_type = m.TypedefType(OriginalName=type_name, UnderlyingType=revng_void_type) + else: + revng_type = self.unwrap_qualified(self._get_primitive_type(primitive_kind, size)) + + elif type.is_decl_func(): + # TODO: handle non C-ABI functions. + # We cannot handle stack arguments at the moment. + assert type.type_details.stkargs is None + + idb_return_type = type.get_rettype() + revng_return_type = self._convert_idb_type_to_revng_type(idb_return_type) + + arguments = [] + for idx, argument in enumerate(type.type_details.args): + argument_qualified_type = self._convert_idb_type_to_revng_type(argument.type) + revng_argument = m.Argument( + Index=idx, + Type=argument_qualified_type, + OriginalName=argument.name, + ) + arguments.append(revng_argument) + + revng_type = m.CABIFunctionType( + ABI=revng_arch_to_abi[self.arch], + ReturnType=revng_return_type, + Arguments=arguments, + OriginalName=type_name, + ) + + elif type.is_decl_partial(): + # Represents an unknown or void type with a known size. + assert type.get_size() != 0 + # The type should be compatible with being a primitive type. + # NOTE: If we find a case where this is not satisifed, we can produce a char[]. + assert ( + type.get_size() == 1 + or type.get_size() == 2 + or type.get_size() == 4 + or type.get_size() == 8 + or type.get_size() == 10 + or type.get_size() == 16 + ) + revng_type = self.unwrap_qualified( + self._get_primitive_type( + m.PrimitiveTypeKind.Generic, + type.get_size(), + ) + ) + + else: + # IDA does not know anything about this type. + # TODO: In some cases we should emit a void type (when the type is always used as a + # pointer). + size = type.get_size() + if size == 0: + revng_type = self.unwrap_qualified( + self._get_primitive_type(m.PrimitiveTypeKind.Void, 0) + ) + else: + kind = m.PrimitiveTypeKind.PointerOrNumber + revng_type = self.unwrap_qualified(self._get_primitive_type(kind, size)) + + existing_revng_type = self.revng_types_by_id.get(revng_type.ID) + if existing_revng_type: + # A type with this ID was already emitted, ensure we are returning the same instance. + assert revng_type is existing_revng_type + + qualified_type = m.QualifiedType( + UnqualifiedType=m.Reference.create(m.Binary, revng_type), + Qualifiers=revng_type_qualifiers, + ) + + if type.is_decl_ptr(): + qualified_type.Qualifiers.insert( + 0, m.Qualifier(Kind=m.QualifierKind.Pointer, Size=type.get_size()) + ) + + if type.is_decl_array(): + n_elements = type.type_details.n_elems + if n_elements == 0: + self.log(f"warning: Array {type_name} has invalid zero size.") + qualified_type.Qualifiers.insert( + 0, m.Qualifier(Kind=m.QualifierKind.Array, Size=n_elements) + ) + + if type.is_decl_const(): + qualified_type.Qualifiers.insert(0, CONST_QUALIFIER) + + if ordinal is not None: + self.idb_types_to_revng_types[ordinal] = qualified_type + self.revng_types_by_id[revng_type.ID] = revng_type + + return qualified_type + + def _get_primitive_type(self, kind: m.PrimitiveTypeKind, size: int) -> m.QualifiedType: + """Gets a primitive type, taking care to register it""" + revng_type = m.PrimitiveType(PrimitiveKind=kind, Size=size) + if revng_type.ID not in self.revng_types_by_id: + self.revng_types_by_id[revng_type.ID] = revng_type + + return m.QualifiedType(UnqualifiedType=m.Reference.create(m.Binary, revng_type)) + + def get_model(self) -> m.Binary: + return m.Binary( + # NOTE: We assume that the EntryPoint can be obtained from binary itself, so we use an + # invalid address for it here. + EntryPoint=m.MetaAddress(Address=0x0, Type=MetaAddressType.Invalid), + Functions=list(self.functions), + ImportedDynamicFunctions=list(self.dynamic_functions), + Types=list(self.revng_types_by_id.values()), + Architecture=self.arch, + Segments=self.segments, + ImportedLibraries=self.imported_libraries, + ) + + def resolve_typeref(self, typeref: m.Reference) -> Optional[RevngTypes]: + return self.revng_types_by_id.get(typeref.id) + + def get_revng_type_by_name(self, name): + for revng_type in self.revng_types_by_id.values(): + if revng_type.OriginalName == name: + return revng_type + return None + + def unwrap_qualified(self, qt: m.QualifiedType): + if qt.Qualifiers: + raise ValueError("Trying to unwrap qualified type with non empty qualifiers list!") + + return self.resolve_typeref(qt.UnqualifiedType) + + +def get_primitive_kind(idb_type: idb.typeinf.TInfo) -> m.PrimitiveTypeKind: + if idb_type.is_decl_void(): + return m.PrimitiveTypeKind.Void + elif idb_type.is_decl_floating(): + return m.PrimitiveTypeKind.Float + elif idb.typeinf_flags.is_type_integral(idb_type.get_decltype()): + if idb_type.is_signed(): + return m.PrimitiveTypeKind.Signed + elif idb_type.is_unsigned(): + return m.PrimitiveTypeKind.Unsigned + else: + return m.PrimitiveTypeKind.Number + elif idb_type.is_decl_ptr(): + return m.PrimitiveTypeKind.PointerOrNumber + + return m.PrimitiveTypeKind.Generic diff --git a/python/revng/cli/_commands/import_idb.py b/python/revng/cli/_commands/import_idb.py new file mode 100644 index 000000000..a2c32ec75 --- /dev/null +++ b/python/revng/cli/_commands/import_idb.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +# +# This file is distributed under the MIT License. See LICENSE.md for details. +# + + +from tempfile import NamedTemporaryFile + +import idb +import yaml + +from revng.cli.commands_registry import Command, Options, commands_registry +from revng.cli.revng import run_revng_command +from revng.model import YamlDumper + +from .idb_converter import IDBConverter + + +class ImportIDBCommand(Command): + def __init__(self): + super().__init__( + ("model", "import", "idb"), "Extract a rev.ng model from an IDB/I64 database" + ) + + def register_arguments(self, parser): + parser.add_argument("idb", help="Path to the IDB/I64 file to import") + parser.add_argument( + "--output", "-o", default="/dev/stdout", help="Output filepath (default stdout)" + ) + parser.add_argument( + "--base", default=0x0, help="base address where dynamic objects should be loaded" + ) + + def run(self, options: Options): + idb_path = options.parsed_args.idb + output_path = options.parsed_args.output + + # NOTE: This should be used for PIC only. + base_address = options.parsed_args.base + + with idb.from_file(idb_path) as db: + idb_converter = IDBConverter(db, base_address, options.parsed_args.verbose) + revng_model = idb_converter.get_model() + + yaml_model = yaml.dump(revng_model, Dumper=YamlDumper) + + def temporary_file(suffix="", mode="w+"): + return NamedTemporaryFile(suffix=suffix, mode=mode, delete=not options.keep_temporaries) + + with temporary_file(suffix=".yml") as model_file: + model_file.write(yaml_model) + model_file.flush() + + # Fix the model and do the clean-up. + run_revng_command( + [ + "model", + "opt", + "-fix", + model_file.name, + "-o", + model_file.name, + ], + options, + ) + + run_revng_command( + [ + "model", + "opt", + "-deduplicate-equivalent-types", + model_file.name, + "-o", + model_file.name, + ], + options, + ) + + run_revng_command( + [ + "model", + "opt", + "-promote-original-name", + model_file.name, + "-o", + model_file.name, + ], + options, + ) + + run_revng_command( + [ + "model", + "opt", + "-purge-unnamed-and-unreachable-types", + model_file.name, + "-o", + output_path, + ], + options, + ) + + return 0 + + +commands_registry.register_command(ImportIDBCommand())