From 09c85a868bad7e6e1b1b117eefc2c2aa9aaef4be Mon Sep 17 00:00:00 2001 From: Glenn Smith Date: Wed, 16 Feb 2022 17:54:15 -0500 Subject: Clang+TypeParser APIs --- python/__init__.py | 2 + python/generator.cpp | 12 + python/typeparser.py | 589 ++++++++++++++++++++++++++++++++++++++++++++++++++ python/typeprinter.py | 390 +++++++++++++++++++++++++++++++++ python/types.py | 157 ++++++++------ 5 files changed, 1088 insertions(+), 62 deletions(-) create mode 100644 python/typeparser.py create mode 100644 python/typeprinter.py (limited to 'python') diff --git a/python/__init__.py b/python/__init__.py index a230dec4..2d0f7065 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -65,6 +65,8 @@ from .workflow import * from .commonil import * from .database import * from .secretsprovider import * +from .typeparser import * +from .typeprinter import * # We import each of these by name to prevent conflicts between # log.py and the function 'log' which we don't import below from .log import ( diff --git a/python/generator.cpp b/python/generator.cpp index 99d2d455..cc4b5102 100644 --- a/python/generator.cpp +++ b/python/generator.cpp @@ -239,7 +239,19 @@ int main(int argc, char* argv[]) map> types, vars, funcs; string errors; auto arch = new CoreArchitecture(BNGetNativeTypeParserArchitecture()); + + string oldParser; + if (Settings::Instance()->Contains("analysis.types.parserNamer")) + oldParser = Settings::Instance()->Get("analysis.types.parserNamer"); + Settings::Instance()->Set("analysis.types.parserName", "CoreTypeParser"); + bool ok = arch->GetStandalonePlatform()->ParseTypesFromSourceFile(argv[1], types, vars, funcs, errors); + + if (!oldParser.empty()) + Settings::Instance()->Set("analysis.types.parserName", oldParser); + else + Settings::Instance()->Reset("analysis.types.parserName"); + fprintf(stderr, "Errors: %s\n", errors.c_str()); if (!ok) return 1; diff --git a/python/typeparser.py b/python/typeparser.py new file mode 100644 index 00000000..ac5134f0 --- /dev/null +++ b/python/typeparser.py @@ -0,0 +1,589 @@ +# Copyright (c) 2015-2022 Vector 35 Inc +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +import abc +import ctypes +import dataclasses +from json import dumps +from typing import List, Tuple, Optional + +import sys +import traceback + +# Binary Ninja Components +import binaryninja +import binaryninja._binaryninjacore as core + +from .settings import Settings +from . import platform +from . import types +from .log import log_error +from .enums import TypeParserErrorSeverity + + +@dataclasses.dataclass(frozen=True) +class QualifiedNameTypeAndId: + name: 'types.QualifiedNameType' + id: str + type: 'types.Type' + + @classmethod + def _from_core_struct(cls, struct: core.BNQualifiedNameTypeAndId) -> 'QualifiedNameTypeAndId': + name = types.QualifiedName._from_core_struct(struct.name) + type = types.Type.create(handle=core.BNNewTypeReference(struct.type)) + return QualifiedNameTypeAndId(name, struct.id, type) + + def _to_core_struct(self) -> core.BNQualifiedNameTypeAndId: + result = core.BNQualifiedNameTypeAndId() + result.name = types.QualifiedName(self.name)._to_core_struct() + result.type = core.BNNewTypeReference(self.type.handle) + result.id = self.id + return result + + +@dataclasses.dataclass(frozen=True) +class TypeParserError: + severity: TypeParserErrorSeverity + message: str + file_name: str + line: int + column: int + + def __str__(self): + text = "" + if self.severity == TypeParserErrorSeverity.ErrorSeverity \ + or self.severity == TypeParserErrorSeverity.FatalSeverity: + text += "error: " + if self.severity == TypeParserErrorSeverity.WarningSeverity: + text += "warning: " + if self.severity == TypeParserErrorSeverity.NoteSeverity: + text += "note: " + if self.severity == TypeParserErrorSeverity.RemarkSeverity: + text += "remark: " + if self.severity == TypeParserErrorSeverity.IgnoredSeverity: + text += "ignored: " + + if self.file_name == "": + text += f": {self.message}\n" + else: + text += f"{self.file_name}: {self.line}:{self.column} {self.message}\n" + return text + + @classmethod + def _from_core_struct(cls, struct: core.BNTypeParserError) -> 'TypeParserError': + return TypeParserError(struct.severity, struct.message, struct.fileName, struct.line, struct.column) + + def _to_core_struct(self) -> core.BNTypeParserError: + result = core.BNTypeParserError() + result.severity = self.severity + result.message = self.message + result.fileName = self.file_name + result.line = self.line + result.column = self.column + return result + + +@dataclasses.dataclass(frozen=True) +class ParsedType: + name: 'types.QualifiedNameType' + type: 'types.Type' + is_user: bool + + @classmethod + def _from_core_struct(cls, struct: core.BNParsedType) -> 'ParsedType': + name = types.QualifiedName._from_core_struct(struct.name) + type = types.Type.create(handle=core.BNNewTypeReference(struct.type)) + return ParsedType(name, type, struct.isUser) + + def _to_core_struct(self) -> core.BNParsedType: + result = core.BNParsedType() + result.name = types.QualifiedName(self.name)._to_core_struct() + result.type = core.BNNewTypeReference(self.type.handle) + result.isUser = self.is_user + return result + + +@dataclasses.dataclass(frozen=True) +class TypeParserResult: + types: List[ParsedType] + variables: List[ParsedType] + functions: List[ParsedType] + + def __repr__(self): + return f"" + + @classmethod + def _from_core_struct(cls, struct: core.BNTypeParserResult) -> 'TypeParserResult': + types = [] + variables = [] + functions = [] + for i in range(struct.typeCount): + types.append(ParsedType._from_core_struct(struct.types[i])) + for i in range(struct.variableCount): + variables.append(ParsedType._from_core_struct(struct.variables[i])) + for i in range(struct.functionCount): + functions.append(ParsedType._from_core_struct(struct.functions[i])) + return TypeParserResult(types, variables, functions) + + def _to_core_struct(self) -> core.BNTypeParserResult: + result = core.BNTypeParserResult() + result.typeCount = len(self.types) + result.variableCount = len(self.variables) + result.functionCount = len(self.functions) + result.types = (core.BNParsedType * len(self.types))() + result.variables = (core.BNParsedType * len(self.variables))() + result.functions = (core.BNParsedType * len(self.functions))() + + for (i, type) in enumerate(self.types): + result.types[i] = type._to_core_struct() + for (i, variable) in enumerate(self.variables): + result.variables[i] = variable._to_core_struct() + for (i, function) in enumerate(self.functions): + result.functions[i] = function._to_core_struct() + + return result + + +def to_bytes(field): + if type(field) == bytes: + return field + if type(field) == str: + return field.encode() + return str(field).encode() + + +class _TypeParserMetaclass(type): + def __iter__(self): + binaryninja._init_plugins() + count = ctypes.c_ulonglong() + types = core.BNGetTypeParserList(count) + try: + for i in range(0, count.value): + yield CoreTypeParser(types[i]) + finally: + core.BNFreeTypeParserList(types) + + def __getitem__(self, value): + binaryninja._init_plugins() + handle = core.BNGetTypeParserByName(str(value)) + if handle is None: + raise KeyError(f"'{value}' is not a valid TypeParser") + return CoreTypeParser(handle) + + @property + def default(self): + name = binaryninja.Settings().get_string("analysis.types.parserName") + return CoreTypeParser[name] + + +class TypeParser(metaclass=_TypeParserMetaclass): + name = None + _registered_parsers = [] + _cached_string = None + _cached_result = None + _cached_error = None + + def __init__(self, handle=None): + if handle is not None: + self.handle = core.handle_of_type(handle, core.BNTypeParser) + self.__dict__["name"] = core.BNGetTypeParserName(handle) + + def register(self): + assert self.__class__.name is not None + + self._cb = core.BNTypeParserCallbacks() + self._cb.context = 0 + self._cb.preprocessSource = self._cb.preprocessSource.__class__(self._preprocess_source) + self._cb.parseTypesFromSource = self._cb.parseTypesFromSource.__class__(self._parse_types_from_source) + self._cb.parseTypeString = self._cb.parseTypeString.__class__(self._parse_type_string) + self._cb.freeString = self._cb.freeString.__class__(self._free_string) + self._cb.freeResult = self._cb.freeResult.__class__(self._free_result) + self._cb.freeErrorList = self._cb.freeErrorList.__class__(self._free_error_list) + self.handle = core.BNRegisterTypeParser(self.__class__.name, self._cb) + self.__class__._registered_parsers.append(self) + + def __str__(self): + return f'' + + def __repr__(self): + return f'' + + def _preprocess_source( + self, ctxt, source, fileName, platform_, existingTypes, existingTypeCount, + options, optionCount, includeDirs, includeDirCount, + output, errors, errorCount + ) -> bool: + try: + source_py = core.pyNativeStr(source) + file_name_py = core.pyNativeStr(fileName) + platform_py = platform.Platform(handle=core.BNNewPlatformReference(platform_)) + + existing_types_py = [] + for i in range(existingTypeCount): + existing_types_py.append(QualifiedNameTypeAndId._from_core_struct(existingTypes[i])) + + options_py = [] + for i in range(optionCount): + options_py.append(core.pyNativeStr(options[i])) + + include_dirs_py = [] + for i in range(includeDirCount): + include_dirs_py.append(core.pyNativeStr(includeDirs[i])) + + (output_py, errors_py) = self.preprocess_source( + source_py, file_name_py, platform_py, existing_types_py, options_py, + include_dirs_py) + + if output_py is not None and output is not None: + TypeParser._cached_string = core.cstr(output_py) + output[0] = TypeParser._cached_string + if errorCount is not None: + errorCount[0] = len(errors_py) + if errors is not None: + errors_out = (core.BNTypeParserError * len(errors_py))() + for i in range(len(errors_py)): + errors_out[i] = errors_py[i]._to_core_struct() + TypeParser._cached_error = errors_out + errors[0] = errors_out + + return output_py is not None + except: + errorCount[0] = 0 + log_error(traceback.format_exc()) + return False + + def _parse_types_from_source( + self, ctxt, source, fileName, platform_, existingTypes, existingTypeCount, + options, optionCount, includeDirs, includeDirCount, autoTypeSource, + result, errors, errorCount + ) -> bool: + try: + source_py = core.pyNativeStr(source) + file_name_py = core.pyNativeStr(fileName) + platform_py = platform.Platform(handle=core.BNNewPlatformReference(platform_)) + + existing_types_py = [] + for i in range(existingTypeCount): + existing_types_py.append(QualifiedNameTypeAndId._from_core_struct(existingTypes[i])) + + options_py = [] + for i in range(optionCount): + options_py.append(core.pyNativeStr(options[i])) + + include_dirs_py = [] + for i in range(includeDirCount): + include_dirs_py.append(core.pyNativeStr(includeDirs[i])) + + auto_type_source = core.pyNativeStr(autoTypeSource) + + (result_py, errors_py) = self.parse_types_from_source( + source_py, file_name_py, platform_py, existing_types_py, options_py, + include_dirs_py, auto_type_source) + + if result_py is not None and result is not None: + result_struct = result_py._to_core_struct() + TypeParser._cached_result = result_struct + result[0] = result_struct + + if errorCount is not None: + errorCount[0] = len(errors_py) + if errors is not None: + errors_out = (core.BNTypeParserError * len(errors_py))() + for i in range(len(errors_py)): + errors_out[i] = errors_py[i]._to_core_struct() + TypeParser._cached_error = errors_out + errors[0] = errors_out + + return result_py is not None + except: + result[0].typeCount = 0 + result[0].variableCount = 0 + result[0].functionCount = 0 + errorCount[0] = 0 + log_error(traceback.format_exc()) + return False + + def _parse_type_string( + self, ctxt, source, platform_, existingTypes, existingTypeCount, + result, errors, errorCount + ) -> bool: + try: + source_py = core.pyNativeStr(source) + platform_py = platform.Platform(handle=core.BNNewPlatformReference(platform_)) + + existing_types_py = [] + for i in range(existingTypeCount): + existing_types_py.append(QualifiedNameTypeAndId._from_core_struct(existingTypes[i])) + + (result_py, errors_py) = self.parse_type_string( + source_py, platform_py, existing_types_py) + + if result_py is not None and result is not None: + result[0].name = types.QualifiedName(result_py[0])._to_core_struct() + result[0].type = core.BNNewTypeReference(result_py[1].handle) + + if errorCount is not None: + errorCount[0] = len(errors_py) + if errors is not None: + errors_out = (core.BNTypeParserError * len(errors_py))() + for i in range(len(errors_py)): + errors_out[i] = errors_py[i]._to_core_struct() + TypeParser._cached_error = errors_out + errors[0] = errors_out + + return result_py is not None + except: + errorCount[0] = 0 + log_error(traceback.format_exc()) + return False + + def _free_string( + self, ctxt, string + ) -> bool: + try: + TypeParser._cached_string = None + return True + except: + log_error(traceback.format_exc()) + return False + + def _free_result( + self, ctxt, result + ) -> bool: + try: + if TypeParser._cached_result is not None: + for i in range(TypeParser._cached_result.typeCount): + core.BNFreeType(TypeParser._cached_result.types[i].type) + for i in range(TypeParser._cached_result.variableCount): + core.BNFreeType(TypeParser._cached_result.variables[i].type) + for i in range(TypeParser._cached_result.functionCount): + core.BNFreeType(TypeParser._cached_result.functions[i].type) + TypeParser._cached_result = None + return True + except: + log_error(traceback.format_exc()) + return False + + def _free_error_list( + self, ctxt, errors, errorCount + ) -> bool: + try: + TypeParser._cached_error = None + return True + except: + log_error(traceback.format_exc()) + return False + + def preprocess_source( + self, source: str, file_name: str, platform: platform.Platform, + existing_types: Optional[List[QualifiedNameTypeAndId]] = None, + options: Optional[List[str]] = None, include_dirs: Optional[List[str]] = None + ) -> Tuple[Optional[str], List[TypeParserError]]: + raise NotImplementedError("Not implemented") + + def parse_types_from_source( + self, source: str, file_name: str, platform: platform.Platform, + existing_types: Optional[List[QualifiedNameTypeAndId]] = None, + options: Optional[List[str]] = None, include_dirs: Optional[List[str]] = None, + auto_type_source: str = "" + ) -> Tuple[Optional[TypeParserResult], List[TypeParserError]]: + raise NotImplementedError("Not implemented") + + def parse_type_string( + self, source: str, platform: platform.Platform, + existing_types: Optional[List[QualifiedNameTypeAndId]] = None + ) -> Tuple[Optional[Tuple['types.QualifiedNameType', 'types.Type']], List[TypeParserError]]: + raise NotImplementedError("Not implemented") + + +class CoreTypeParser(TypeParser): + + def preprocess_source( + self, source: str, file_name: str, platform: platform.Platform, + existing_types: Optional[List[QualifiedNameTypeAndId]] = None, + options: Optional[List[str]] = None, include_dirs: Optional[List[str]] = None + ) -> Tuple[Optional[str], List[TypeParserError]]: + if existing_types is None: + existing_types = [] + if options is None: + options = [] + if include_dirs is None: + include_dirs = [] + + existing_types_cpp = (core.BNQualifiedNameTypeAndId * len(existing_types))() + for (i, qnatid) in enumerate(existing_types): + existing_types_cpp[i] = qnatid._to_core_struct() + + options_cpp = (ctypes.c_char_p * len(options))() + for (i, s) in enumerate(options): + options_cpp[i] = core.cstr(s) + + include_dirs_cpp = (ctypes.c_char_p * len(include_dirs))() + for (i, s) in enumerate(include_dirs): + include_dirs_cpp[i] = core.cstr(s) + + output_cpp = ctypes.c_char_p() + errors_cpp = ctypes.POINTER(core.BNTypeParserError)() + error_count = ctypes.c_size_t() + + success = core.BNTypeParserPreprocessSource( + self.handle, source, file_name, platform.handle, + existing_types_cpp, len(existing_types), options_cpp, len(options), + include_dirs_cpp, len(include_dirs), + output_cpp, errors_cpp, error_count + ) + + if success: + output = core.pyNativeStr(output_cpp.value) + core.free_string(output_cpp) + else: + output = None + + errors = [] + for i in range(error_count.value): + errors.append(TypeParserError._from_core_struct(errors_cpp[i])) + core.BNFreeTypeParserErrors(errors_cpp, error_count.value) + + return output, errors + + def parse_types_from_source( + self, source: str, file_name: str, platform: platform.Platform, + existing_types: Optional[List[QualifiedNameTypeAndId]] = None, + options: Optional[List[str]] = None, include_dirs: Optional[List[str]] = None, + auto_type_source: str = "" + ) -> Tuple[Optional[TypeParserResult], List[TypeParserError]]: + if existing_types is None: + existing_types = [] + if options is None: + options = [] + if include_dirs is None: + include_dirs = [] + + existing_types_cpp = (core.BNQualifiedNameTypeAndId * len(existing_types))() + for (i, qnatid) in enumerate(existing_types): + existing_types_cpp[i] = qnatid._to_core_struct() + + options_cpp = (ctypes.c_char_p * len(options))() + for (i, s) in enumerate(options): + options_cpp[i] = core.cstr(s) + + include_dirs_cpp = (ctypes.c_char_p * len(include_dirs))() + for (i, s) in enumerate(include_dirs): + include_dirs_cpp[i] = core.cstr(s) + + result_cpp = core.BNTypeParserResult() + errors_cpp = ctypes.POINTER(core.BNTypeParserError)() + error_count = ctypes.c_size_t() + + success = core.BNTypeParserParseTypesFromSource( + self.handle, source, file_name, platform.handle, + existing_types_cpp, len(existing_types), options_cpp, len(options), + include_dirs_cpp, len(include_dirs), auto_type_source, + result_cpp, errors_cpp, error_count + ) + + if success: + result = TypeParserResult._from_core_struct(result_cpp) + else: + result = None + core.BNFreeTypeParserResult(result_cpp) + + errors = [] + for i in range(error_count.value): + errors.append(TypeParserError._from_core_struct(errors_cpp[i])) + core.BNFreeTypeParserErrors(errors_cpp, error_count.value) + + return result, errors + + + def parse_type_string( + self, source: str, platform: platform.Platform, + existing_types: Optional[List[QualifiedNameTypeAndId]] = None + ) -> Tuple[Optional[Tuple['types.QualifiedNameType', 'types.Type']], List[TypeParserError]]: + if existing_types is None: + existing_types = [] + existing_types_cpp = (core.BNQualifiedNameTypeAndId * len(existing_types))() + for (i, qnatid) in enumerate(existing_types): + existing_types_cpp[i] = qnatid._to_core_struct() + + result_cpp = core.BNQualifiedNameAndType() + errors_cpp = ctypes.POINTER(core.BNTypeParserError)() + error_count = ctypes.c_size_t() + + success = core.BNTypeParserParseTypeString( + self.handle, source, platform.handle, + existing_types_cpp, len(existing_types), + result_cpp, errors_cpp, error_count + ) + + if success: + result = ( + types.QualifiedName._from_core_struct(result_cpp.name), + types.Type.create(handle=core.BNNewTypeReference(result_cpp.type)) + ) + core.BNFreeQualifiedNameAndType(result_cpp) + else: + result = None + + errors = [] + for i in range(error_count.value): + errors.append(TypeParserError._from_core_struct(errors_cpp[i])) + core.BNFreeTypeParserErrors(errors_cpp, error_count.value) + + return result, errors + + +def preprocess_source(source: str, filename: str = None, + include_dirs: Optional[List[str]] = None) -> Tuple[Optional[str], str]: + """ + ``preprocess_source`` run the C preprocessor on the given source or source filename. + + :param str source: source to pre-process + :param str filename: optional filename to pre-process + :param include_dirs: list of string directories to use as include directories. + :type include_dirs: list(str) + :return: returns a tuple of (preprocessed_source, error_string) + :rtype: tuple(str,str) + :Example: + + >>> source = "#define TEN 10\\nint x[TEN];\\n" + >>> preprocess_source(source) + ('#line 1 "input"\\n\\n#line 2 "input"\\n int x [ 10 ] ;\\n', '') + >>> + """ + if filename is None: + filename = "input" + if include_dirs is None: + include_dirs = [] + dir_buf = (ctypes.c_char_p * len(include_dirs))() + for i in range(0, len(include_dirs)): + dir_buf[i] = include_dirs[i].encode('charmap') + output = ctypes.c_char_p() + errors = ctypes.c_char_p() + result = core.BNPreprocessSource(source, filename, output, errors, dir_buf, len(include_dirs)) + assert output.value is not None + assert errors.value is not None + output_str = output.value.decode('utf-8') + error_str = errors.value.decode('utf-8') + core.free_string(output) + core.free_string(errors) + if result: + return output_str, error_str + return None, error_str diff --git a/python/typeprinter.py b/python/typeprinter.py new file mode 100644 index 00000000..354084dd --- /dev/null +++ b/python/typeprinter.py @@ -0,0 +1,390 @@ +# Copyright (c) 2015-2022 Vector 35 Inc +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +import abc +import ctypes +import dataclasses +from json import dumps +from typing import List, Tuple, Optional + +import sys +import traceback + +# Binary Ninja Components +import binaryninja +import binaryninja._binaryninjacore as core + +from .settings import Settings +from . import platform as _platform +from . import types +from . import function as _function +from . import binaryview +from .log import log_error +from .enums import TokenEscapingType + + +def to_bytes(field): + if type(field) == bytes: + return field + if type(field) == str: + return field.encode() + return str(field).encode() + + +class _TypePrinterMetaclass(type): + def __iter__(self): + binaryninja._init_plugins() + count = ctypes.c_ulonglong() + types = core.BNGetTypePrinterList(count) + try: + for i in range(0, count.value): + yield CoreTypePrinter(types[i]) + finally: + core.BNFreeTypePrinterList(types) + + def __getitem__(self, value): + binaryninja._init_plugins() + handle = core.BNGetTypePrinterByName(str(value)) + if handle is None: + raise KeyError(f"'{value}' is not a valid TypePrinter") + return CoreTypePrinter(handle) + + @property + def default(self): + name = binaryninja.Settings().get_string("analysis.types.printerName") + return CoreTypePrinter[name] + + +class TypePrinter(metaclass=_TypePrinterMetaclass): + name = None + _registered_printers = [] + _cached_tokens = None + _cached_string = None + _cached_error = None + + def __init__(self, handle=None): + if handle is not None: + self.handle = core.handle_of_type(handle, core.BNTypePrinter) + self.__dict__["name"] = core.BNGetTypePrinterName(handle) + + def register(self): + assert self.__class__.name is not None + + self._cb = core.BNTypePrinterCallbacks() + self._cb.context = 0 + self._cb.getTypeTokens = self._cb.getTypeTokens.__class__(self._get_type_tokens) + self._cb.getTypeTokensBeforeName = self._cb.getTypeTokensBeforeName.__class__(self._get_type_tokens_before_name) + self._cb.getTypeTokensAfterName = self._cb.getTypeTokensAfterName.__class__(self._get_type_tokens_after_name) + self._cb.getTypeString = self._cb.getTypeString.__class__(self._get_type_string) + self._cb.getTypeStringBeforeName = self._cb.getTypeStringBeforeName.__class__(self._get_type_string_before_name) + self._cb.getTypeStringAfterName = self._cb.getTypeStringAfterName.__class__(self._get_type_string_after_name) + self._cb.getTypeLines = self._cb.getTypeLines.__class__(self._get_type_lines) + self._cb.freeTokens = self._cb.freeTokens.__class__(self._free_tokens) + self._cb.freeString = self._cb.freeString.__class__(self._free_string) + self._cb.freeLines = self._cb.freeLines.__class__(self._free_lines) + self.handle = core.BNRegisterTypePrinter(self.__class__.name, self._cb) + self.__class__._registered_printers.append(self) + + def __str__(self): + return f'' + + def __repr__(self): + return f'' + + def _get_type_tokens(self, ctxt, type, platform, name, base_confidence, escaping, result, result_count): + try: + platform_py = None + if platform: + platform_py = _platform.Platform(handle=core.BNNewPlatformReference(platform)) + result_py = self.get_type_tokens( + types.Type(handle=core.BNNewTypeReference(type)), platform_py, + types.QualifiedName._from_core_struct(name.contents), base_confidence, escaping) + + TypePrinter._cached_tokens = _function.InstructionTextToken._get_core_struct(result_py) + result[0] = TypePrinter._cached_tokens + result_count[0] = len(result_py) + + return True + except: + log_error(traceback.format_exc()) + return False + + def _get_type_tokens_before_name(self, ctxt, type, platform, base_confidence, parent_type, escaping, result, result_count): + try: + platform_py = None + if platform: + platform_py = _platform.Platform(handle=core.BNNewPlatformReference(platform)) + parent_type_py = None + if parent_type: + parent_type_py = types.Type(handle=core.BNNewTypeReference(parent_type)) + result_py = self.get_type_tokens_before_name( + types.Type(handle=core.BNNewTypeReference(type)), platform_py, + base_confidence, parent_type_py, escaping) + + TypePrinter._cached_tokens = _function.InstructionTextToken._get_core_struct(result_py) + result[0] = TypePrinter._cached_tokens + result_count[0] = len(result_py) + + return True + except: + log_error(traceback.format_exc()) + return False + + def _get_type_tokens_after_name(self, ctxt, type, platform, base_confidence, parent_type, escaping, result, result_count): + try: + platform_py = None + if platform: + platform_py = _platform.Platform(handle=core.BNNewPlatformReference(platform)) + parent_type_py = None + if parent_type: + parent_type_py = types.Type(handle=core.BNNewTypeReference(parent_type)) + result_py = self.get_type_tokens_after_name( + types.Type(handle=core.BNNewTypeReference(type)), platform_py, + base_confidence, parent_type_py, escaping) + + TypePrinter._cached_tokens = _function.InstructionTextToken._get_core_struct(result_py) + result[0] = TypePrinter._cached_tokens + result_count[0] = len(result_py) + + return True + except: + log_error(traceback.format_exc()) + return False + + def _get_type_string(self, ctxt, type, platform, name, escaping, result): + try: + platform_py = None + if platform: + platform_py = _platform.Platform(handle=core.BNNewPlatformReference(platform)) + result_py = self.get_type_string( + types.Type(handle=core.BNNewTypeReference(type)), platform_py, + types.QualifiedName._from_core_struct(name.contents), escaping) + + TypePrinter._cached_string = core.cstr(result_py) + result[0] = TypePrinter._cached_string + return True + except: + log_error(traceback.format_exc()) + return False + + def _get_type_string_before_name(self, ctxt, type, platform, escaping, result): + try: + platform_py = None + if platform: + platform_py = _platform.Platform(handle=core.BNNewPlatformReference(platform)) + result_py = self.get_type_string_before_name( + types.Type(handle=core.BNNewTypeReference(type)), platform_py, + escaping) + + TypePrinter._cached_string = core.cstr(result_py) + result[0] = TypePrinter._cached_string + return True + except: + log_error(traceback.format_exc()) + return False + + def _get_type_string_after_name(self, ctxt, type, platform, escaping, result): + try: + platform_py = None + if platform: + platform_py = _platform.Platform(handle=core.BNNewPlatformReference(platform)) + result_py = self.get_type_string_after_name( + types.Type(handle=core.BNNewTypeReference(type)), platform_py, + escaping) + + TypePrinter._cached_string = core.cstr(result_py) + result[0] = TypePrinter._cached_string + return True + except: + log_error(traceback.format_exc()) + return False + + def _get_type_lines(self, ctxt, type, data, name, line_width, collapsed, escaping, result, result_count): + try: + result_py = self.get_type_lines( + types.Type(handle=core.BNNewTypeReference(type)), + binaryview.BinaryView(handle=core.BNNewViewReference(data)), + types.QualifiedName._from_core_struct(name.contents), + line_width, collapsed, escaping) + + TypePrinter._cached_lines = (core.BNTypeDefinitionLine * len(result_py))() + for (i, line) in enumerate(result_py): + TypePrinter._cached_lines[i] = line._to_core_struct() + result[0] = TypePrinter._cached_lines + result_count[0] = len(result_py) + + return True + except: + log_error(traceback.format_exc()) + return False + + def _free_tokens(self, ctxt, tokens, count): + try: + TypePrinter._cached_tokens = None + return True + except: + log_error(traceback.format_exc()) + return False + + def _free_string(self, ctxt, string): + try: + TypePrinter._cached_string = None + return True + except: + log_error(traceback.format_exc()) + return False + + def _free_lines(self, ctxt, lines, count): + try: + for line in TypePrinter._cached_lines: + core.BNFreeType(line.type) + core.BNFreeType(line.rootType) + TypePrinter._cached_lines = None + return True + except: + log_error(traceback.format_exc()) + return False + + def get_type_tokens(self, type: types.Type, platform: Optional[_platform.Platform] = None, name: types.QualifiedNameType = "", base_confidence: int = core.max_confidence, escaping: TokenEscapingType = TokenEscapingType.BackticksTokenEscapingType) -> List[_function.InstructionTextToken]: + raise NotImplementedError() + + def get_type_tokens_before_name(self, type: types.Type, platform: Optional[_platform.Platform] = None, base_confidence: int = core.max_confidence, parent_type: Optional[types.Type] = None, escaping: TokenEscapingType = TokenEscapingType.BackticksTokenEscapingType) -> List[_function.InstructionTextToken]: + raise NotImplementedError() + + def get_type_tokens_after_name(self, type: types.Type, platform: Optional[_platform.Platform] = None, base_confidence: int = core.max_confidence, parent_type: Optional[types.Type] = None, escaping: TokenEscapingType = TokenEscapingType.BackticksTokenEscapingType) -> List[_function.InstructionTextToken]: + raise NotImplementedError() + + def get_type_string(self, type: types.Type, platform: Optional[_platform.Platform] = None, name: types.QualifiedNameType = "", escaping: TokenEscapingType = TokenEscapingType.BackticksTokenEscapingType) -> str: + raise NotImplementedError() + + def get_type_string_before_name(self, type: types.Type, platform: Optional[_platform.Platform] = None, escaping: TokenEscapingType = TokenEscapingType.BackticksTokenEscapingType) -> str: + raise NotImplementedError() + + def get_type_string_after_name(self, type: types.Type, platform: Optional[_platform.Platform] = None, escaping: TokenEscapingType = TokenEscapingType.BackticksTokenEscapingType) -> str: + raise NotImplementedError() + + def get_type_lines(self, type: types.Type, data: binaryview.BinaryView, name: types.QualifiedNameType, line_width = 80, collapsed = False, escaping: TokenEscapingType = TokenEscapingType.BackticksTokenEscapingType) -> List[types.TypeDefinitionLine]: + raise NotImplementedError() + + +class CoreTypePrinter(TypePrinter): + + def get_type_tokens(self, type: types.Type, platform: Optional[_platform.Platform] = None, + name: types.QualifiedNameType = "", base_confidence: int = core.max_confidence, + escaping: TokenEscapingType = TokenEscapingType.BackticksTokenEscapingType) -> List[ + _function.InstructionTextToken]: + if not isinstance(name, types.QualifiedName): + name = types.QualifiedName(name) + count = ctypes.c_ulonglong() + name_cpp = name._to_core_struct() + result_cpp = ctypes.POINTER(core.BNInstructionTextToken)() + if not core.BNGetTypePrinterTypeTokens(self.handle, type.handle, None if platform is None else platform.handle, name_cpp, base_confidence, ctypes.c_int(escaping), result_cpp, count): + raise RuntimeError("BNGetTypePrinterTypeTokens returned False") + + result = _function.InstructionTextToken._from_core_struct(result_cpp, count.value) + core.BNFreeInstructionText(result_cpp.contents, count.value) + return result + + def get_type_tokens_before_name(self, type: types.Type, platform: Optional[_platform.Platform] = None, + base_confidence: int = core.max_confidence, parent_type: Optional[types.Type] = None, + escaping: TokenEscapingType = TokenEscapingType.BackticksTokenEscapingType) -> List[ + _function.InstructionTextToken]: + count = ctypes.c_ulonglong() + result_cpp = ctypes.POINTER(core.BNInstructionTextToken)() + parent_type_cpp = None + if parent_type is not None: + parent_type_cpp = parent_type.handle + if not core.BNGetTypePrinterTypeTokensBeforeName(self.handle, type.handle, None if platform is None else platform.handle, base_confidence, parent_type_cpp, ctypes.c_int(escaping), result_cpp, count): + raise RuntimeError("BNGetTypePrinterTypeTokensBeforeName returned False") + + result = _function.InstructionTextToken._from_core_struct(result_cpp, count.value) + core.BNFreeInstructionText(result_cpp.contents, count.value) + return result + + def get_type_tokens_after_name(self, type: types.Type, platform: Optional[_platform.Platform] = None, + base_confidence: int = core.max_confidence, parent_type: Optional[types.Type] = None, + escaping: TokenEscapingType = TokenEscapingType.BackticksTokenEscapingType) -> List[ + _function.InstructionTextToken]: + count = ctypes.c_ulonglong() + result_cpp = ctypes.POINTER(core.BNInstructionTextToken)() + parent_type_cpp = None + if parent_type is not None: + parent_type_cpp = parent_type.handle + if not core.BNGetTypePrinterTypeTokensAfterName(self.handle, type.handle, None if platform is None else platform.handle, base_confidence, parent_type_cpp, ctypes.c_int(escaping), result_cpp, count): + raise RuntimeError("BNGetTypePrinterTypeTokensAfterName returned False") + + result = _function.InstructionTextToken._from_core_struct(result_cpp, count.value) + core.BNFreeInstructionText(result_cpp.contents, count.value) + return result + + def get_type_string(self, type: types.Type, platform: Optional[_platform.Platform] = None, + name: types.QualifiedNameType = "", + escaping: TokenEscapingType = TokenEscapingType.BackticksTokenEscapingType) -> str: + if not isinstance(name, types.QualifiedName): + name = types.QualifiedName(name) + result_cpp = ctypes.c_char_p() + if not core.BNGetTypePrinterTypeString(self.handle, type.handle, None if platform is None else platform.handle, name._to_core_struct(), ctypes.c_int(escaping), result_cpp): + raise RuntimeError("BNGetTypePrinterTypeString returned False") + + result = core.pyNativeStr(result_cpp.value) + core.free_string(result_cpp) + return result + + def get_type_string_before_name(self, type: types.Type, platform: Optional[_platform.Platform] = None, + escaping: TokenEscapingType = TokenEscapingType.BackticksTokenEscapingType) -> str: + result_cpp = ctypes.c_char_p() + if not core.BNGetTypePrinterTypeStringBeforeName(self.handle, type.handle, None if platform is None else platform.handle, ctypes.c_int(escaping), result_cpp): + raise RuntimeError("BNGetTypePrinterTypeStringBeforeName returned False") + + result = core.pyNativeStr(result_cpp.value) + core.free_string(result_cpp) + return result + + def get_type_string_after_name(self, type: types.Type, platform: Optional[_platform.Platform] = None, + escaping: TokenEscapingType = TokenEscapingType.BackticksTokenEscapingType) -> str: + result_cpp = ctypes.c_char_p() + if not core.BNGetTypePrinterTypeStringAfterName(self.handle, type.handle, None if platform is None else platform.handle, ctypes.c_int(escaping), result_cpp): + raise RuntimeError("BNGetTypePrinterTypeStringAfterName returned False") + + result = core.pyNativeStr(result_cpp.value) + core.free_string(result_cpp) + return result + + def get_type_lines(self, type: types.Type, data: binaryview.BinaryView, + name: types.QualifiedNameType, line_width = 80, collapsed = False, + escaping: TokenEscapingType = TokenEscapingType.BackticksTokenEscapingType + ) -> List[types.TypeDefinitionLine]: + if not isinstance(name, types.QualifiedName): + name = types.QualifiedName(name) + count = ctypes.c_ulonglong() + core_lines = ctypes.POINTER(core.BNTypeDefinitionLine)() + if not core.BNGetTypePrinterTypeLines(self.handle, type.handle, data.handle, name._to_core_struct(), line_width, collapsed, ctypes.c_int(escaping), core_lines, count): + raise RuntimeError("BNGetTypePrinterTypeLines returned False") + lines = [] + for i in range(count.value): + tokens = _function.InstructionTextToken._from_core_struct(core_lines[i].tokens, core_lines[i].count) + type_ = types.Type.create(handle=core.BNNewTypeReference(core_lines[i].type), platform=data.platform) + root_type = types.Type.create(handle=core.BNNewTypeReference(core_lines[i].rootType), platform=data.platform) + root_type_name = core.pyNativeStr(core_lines[i].rootTypeName) + line = types.TypeDefinitionLine(core_lines[i].lineType, tokens, type_, root_type, root_type_name, + core_lines[i].offset, core_lines[i].fieldIndex) + lines.append(line) + core.BNFreeTypeDefinitionLineList(core_lines, count.value) + return lines diff --git a/python/types.py b/python/types.py index 65f58192..e00b3408 100644 --- a/python/types.py +++ b/python/types.py @@ -19,7 +19,7 @@ # IN THE SOFTWARE. import ctypes -from typing import Generator, List, Union, Mapping, Tuple, Optional, Iterable +from typing import Generator, List, Union, Mapping, Tuple, Optional, Iterable, Dict from dataclasses import dataclass import uuid from abc import abstractmethod @@ -28,7 +28,8 @@ from abc import abstractmethod from . import _binaryninjacore as core from .enums import ( StructureVariant, SymbolType, SymbolBinding, TypeClass, NamedTypeReferenceClass, ReferenceType, VariableSourceType, - TypeReferenceType, MemberAccess, MemberScope, TypeDefinitionLineType, TokenEscapingType + TypeReferenceType, MemberAccess, MemberScope, TypeDefinitionLineType, TokenEscapingType, + NameType ) from . import callingconvention from . import function as _function @@ -37,6 +38,7 @@ from . import architecture from . import binaryview from . import platform as _platform from . import typelibrary +from . import typeparser QualifiedNameType = Union[Iterable[Union[str, bytes]], str, 'QualifiedName'] BoolWithConfidenceType = Union[bool, 'BoolWithConfidence'] @@ -47,6 +49,7 @@ EnumMembersType = Union[List[Tuple[str, int]], List[str], List['EnumerationMembe SomeType = Union['TypeBuilder', 'Type'] TypeContainer = Union['binaryview.BinaryView', 'typelibrary.TypeLibrary'] NameSpaceType = Optional[Union[str, List[str], 'NameSpace']] +TypeParserResult = typeparser.TypeParserResult # The following are needed to prevent the type checker from getting # confused as we have member functions in `Type` named the same thing _int = int @@ -223,6 +226,27 @@ class TypeDefinitionLine: def __repr__(self): return f"" + @staticmethod + def _from_core_struct(struct: core.BNTypeDefinitionLine, platform: Optional[_platform.Platform] = None): + tokens = _function.InstructionTextToken._from_core_struct(struct.tokens, struct.count) + type_ = Type.create(handle=core.BNNewTypeReference(struct.type), platform=platform) + root_type = Type.create(handle=core.BNNewTypeReference(struct.rootType), platform=platform) + root_type_name = core.pyNativeStr(struct.rootTypeName) + return TypeDefinitionLine(struct.lineType, tokens, type_, root_type, root_type_name, + struct.offset, struct.fieldIndex) + + def _to_core_struct(self): + struct = core.BNTypeDefinitionLine() + struct.lineType = self.line_type + struct.tokens = _function.InstructionTextToken._get_core_struct(self.tokens) + struct.count = len(self.tokens) + struct.type = core.BNNewTypeReference(self.type.handle) + struct.rootType = core.BNNewTypeReference(self.root_type.handle) + struct.rootTypeName = self.root_type_name + struct.offset = self.offset + struct.fieldIndex = self.field_index + return struct + class CoreSymbol: def __init__(self, handle: core.BNSymbolHandle): @@ -888,7 +912,10 @@ class FunctionBuilder(TypeBuilder): cls, return_type: Optional[SomeType] = None, calling_convention: Optional['callingconvention.CallingConvention'] = None, params: Optional[ParamsType] = None, var_args: Optional[BoolWithConfidenceType] = None, stack_adjust: Optional[OffsetWithConfidenceType] = None, - platform: Optional['_platform.Platform'] = None, confidence: int = core.max_confidence + platform: Optional['_platform.Platform'] = None, confidence: int = core.max_confidence, + can_return: Optional[BoolWithConfidence] = None, reg_stack_adjust: Optional[Dict['architecture.RegisterName', OffsetWithConfidenceType]] = None, + return_regs: Optional[Union['RegisterSet', List['architecture.RegisterType']]] = None, + name_type: 'NameType' = NameType.NoNameType ) -> 'FunctionBuilder': param_buf = FunctionBuilder._to_core_struct(params) if return_type is None: @@ -904,11 +931,38 @@ class FunctionBuilder(TypeBuilder): conv_conf.convention = calling_convention.handle conv_conf.confidence = calling_convention.confidence + if reg_stack_adjust is None: + reg_stack_adjust = {} + reg_stack_adjust_regs = (ctypes.c_uint32 * len(reg_stack_adjust))() + reg_stack_adjust_values = (core.BNOffsetWithConfidence * len(reg_stack_adjust))() + + for i, (reg, adjust) in enumerate(reg_stack_adjust.items()): + reg_stack_adjust_regs[i] = reg + reg_stack_adjust_values[i].value = adjust.value + reg_stack_adjust_values[i].confidence = adjust.confidence + + return_regs_set = core.BNRegisterSetWithConfidence() + if return_regs is None or platform is None: + return_regs_set.count = 0 + return_regs_set.confidence = 0 + else: + return_regs_set.count = len(return_regs) + return_regs_set.confidence = 255 + return_regs_set.regs = (ctypes.c_uint32 * len(return_regs))() + + for i, reg in enumerate(return_regs): + return_regs_set[i] = platform.arch.get_reg_index(reg) + if var_args is None: vararg_conf = BoolWithConfidence.get_core_struct(False, 0) else: vararg_conf = BoolWithConfidence.get_core_struct(var_args, core.max_confidence) + if can_return is None: + can_return_conf = BoolWithConfidence.get_core_struct(True, 0) + else: + can_return_conf = BoolWithConfidence.get_core_struct(can_return, core.max_confidence) + if stack_adjust is None: stack_adjust_conf = OffsetWithConfidence.get_core_struct(0, 0) else: @@ -916,7 +970,9 @@ class FunctionBuilder(TypeBuilder): if params is None: params = [] handle = core.BNCreateFunctionTypeBuilder( - ret_conf, conv_conf, param_buf, len(params), vararg_conf, stack_adjust_conf + ret_conf, conv_conf, param_buf, len(params), vararg_conf, can_return_conf, stack_adjust_conf, + reg_stack_adjust_regs, reg_stack_adjust_values, len(reg_stack_adjust), + return_regs_set, name_type ) assert handle is not None, "BNCreateFunctionTypeBuilder returned None" return cls(handle, platform, confidence) @@ -1783,13 +1839,7 @@ class Type: assert core_lines is not None, "core.BNGetTypeLines returned None" lines = [] for i in range(count.value): - tokens = _function.InstructionTextToken._from_core_struct(core_lines[i].tokens, core_lines[i].count) - type_ = Type.create(handle=core.BNNewTypeReference(core_lines[i].type), platform=self._platform) - root_type = Type.create(handle=core.BNNewTypeReference(core_lines[i].rootType), platform=self._platform) - root_type_name = core.pyNativeStr(core_lines[i].rootTypeName) - line = TypeDefinitionLine(core_lines[i].lineType, tokens, type_, root_type, root_type_name, - core_lines[i].offset, core_lines[i].fieldIndex) - lines.append(line) + lines.append(TypeDefinitionLine._from_core_struct(core_lines[i])) core.BNFreeTypeDefinitionLineList(core_lines, count.value) return lines @@ -2448,7 +2498,10 @@ class FunctionType(Type): calling_convention: Optional['callingconvention.CallingConvention'] = None, variable_arguments: BoolWithConfidenceType = BoolWithConfidence(False), stack_adjust: OffsetWithConfidence = OffsetWithConfidence(0), platform: Optional['_platform.Platform'] = None, - confidence: int = core.max_confidence + confidence: int = core.max_confidence, + can_return: BoolWithConfidence = True, reg_stack_adjust: Optional[Dict['architecture.RegisterName', OffsetWithConfidenceType]] = None, + return_regs: Optional[Union['RegisterSet', List['architecture.RegisterType']]] = None, + name_type: 'NameType' = NameType.NoNameType ) -> 'FunctionType': if ret is None: ret = VoidType.create() @@ -2474,9 +2527,38 @@ class FunctionType(Type): else: _stack_adjust = OffsetWithConfidence.get_core_struct(stack_adjust, core.max_confidence) + + if reg_stack_adjust is None: + reg_stack_adjust = {} + reg_stack_adjust_regs = (ctypes.c_uint32 * len(reg_stack_adjust))() + reg_stack_adjust_values = (core.BNOffsetWithConfidence * len(reg_stack_adjust))() + + for i, (reg, adjust) in enumerate(reg_stack_adjust.items()): + reg_stack_adjust_regs[i] = reg + reg_stack_adjust_values[i].value = adjust.value + reg_stack_adjust_values[i].confidence = adjust.confidence + + return_regs_set = core.BNRegisterSetWithConfidence() + if return_regs is None or platform is None: + return_regs_set.count = 0 + return_regs_set.confidence = 0 + else: + return_regs_set.count = len(return_regs) + return_regs_set.confidence = 255 + return_regs_set.regs = (ctypes.c_uint32 * len(return_regs))() + + for i, reg in enumerate(return_regs): + return_regs_set[i] = platform.arch.get_reg_index(reg) + + _can_return = BoolWithConfidence.get_core_struct(can_return) + if params is None: + params = [] func_type = core.BNCreateFunctionType( - ret_conf, conv_conf, param_buf, len(params), _variable_arguments, _stack_adjust + ret_conf, conv_conf, param_buf, len(params), _variable_arguments, _can_return, _stack_adjust, + reg_stack_adjust_regs, reg_stack_adjust_values, len(reg_stack_adjust), + return_regs_set, name_type ) + assert func_type is not None, f"core.BNCreateFunctionType returned None {ret_conf} {conv_conf} {param_buf} {_variable_arguments} {_stack_adjust}" return cls(core.BNNewTypeReference(func_type), platform, confidence) @@ -2734,55 +2816,6 @@ class RegisterSet: return RegisterSet(list(self.regs), confidence=confidence) -@dataclass(frozen=True) -class TypeParserResult: - types: Mapping[QualifiedName, Type] - variables: Mapping[QualifiedName, Type] - functions: Mapping[QualifiedName, Type] - - def __repr__(self): - return f"" - - -def preprocess_source(source: str, filename: Optional[str] = None, - include_dirs: Optional[List[str]] = None) -> Tuple[Optional[str], str]: - """ - ``preprocess_source`` run the C preprocessor on the given source or source filename. - - :param str source: source to pre-process - :param str filename: optional filename to pre-process - :param include_dirs: list of string directories to use as include directories. - :type include_dirs: list(str) - :return: returns a tuple of (preprocessed_source, error_string) - :rtype: tuple(str,str) - :Example: - - >>> source = "#define TEN 10\\nint x[TEN];\\n" - >>> preprocess_source(source) - ('#line 1 "input"\\n\\n#line 2 "input"\\n int x [ 10 ] ;\\n', '') - >>> - """ - if filename is None: - filename = "input" - if include_dirs is None: - include_dirs = [] - dir_buf = (ctypes.c_char_p * len(include_dirs))() - for i in range(0, len(include_dirs)): - dir_buf[i] = include_dirs[i].encode('charmap') - output = ctypes.c_char_p() - errors = ctypes.c_char_p() - result = core.BNPreprocessSource(source, filename, output, errors, dir_buf, len(include_dirs)) - assert output.value is not None - assert errors.value is not None - output_str = output.value.decode('utf-8') - error_str = errors.value.decode('utf-8') - core.free_string(output) - core.free_string(errors) - if result: - return output_str, error_str - return None, error_str - - @dataclass(frozen=True) class TypeFieldReference: func: Optional['_function.Function'] -- cgit v1.3.1