diff options
| author | KyleMiles <krm504@nyu.edu> | 2024-01-16 15:07:23 -0500 |
|---|---|---|
| committer | KyleMiles <krm504@nyu.edu> | 2024-01-19 15:45:24 -0500 |
| commit | fb0fcd199680032932784b07b074738999779d58 (patch) | |
| tree | 55a1d53541c0ec99bb486ff9863d14540602ad9c | |
| parent | 60d69dba06adb8ea442ee5e28c1bfda5290818e1 (diff) | |
Add support for components in debug info
| -rw-r--r-- | binaryninjaapi.h | 11 | ||||
| -rw-r--r-- | binaryninjacore.h | 6 | ||||
| -rw-r--r-- | debuginfo.cpp | 27 | ||||
| -rw-r--r-- | python/debuginfo.py | 108 | ||||
| -rwxr-xr-x | python/examples/debug_info.py | 292 | ||||
| -rwxr-xr-x | python/examples/test_debug_info | bin | 14336 -> 0 bytes | |||
| -rw-r--r-- | rust/examples/dwarf/dwarf_import/src/dwarfdebuginfo.rs | 7 | ||||
| -rw-r--r-- | rust/src/debuginfo.rs | 88 |
8 files changed, 174 insertions, 365 deletions
diff --git a/binaryninjaapi.h b/binaryninjaapi.h index 5b92a2e5..872b862b 100644 --- a/binaryninjaapi.h +++ b/binaryninjaapi.h @@ -15557,11 +15557,12 @@ namespace BinaryNinja { uint64_t address; Ref<Type> type; Ref<Platform> platform; + std::vector<std::string> components; DebugFunctionInfo(std::string shortName, std::string fullName, std::string rawName, uint64_t address, - Ref<Type> type, Ref<Platform> platform) : - shortName(shortName), - fullName(fullName), rawName(rawName), address(address), platform(platform) + Ref<Type> type, Ref<Platform> platform, const std::vector<std::string>& components) : + shortName(shortName), fullName(fullName), rawName(rawName), + address(address), platform(platform), components(components) {} }; @@ -15606,9 +15607,9 @@ namespace BinaryNinja { bool RemoveFunctionByIndex(const std::string& parserName, const size_t index); bool RemoveDataVariableByAddress(const std::string& parserName, const uint64_t address); - bool AddType(const std::string& name, Ref<Type> type); + bool AddType(const std::string& name, Ref<Type> type, const std::vector<std::string>& components = {}); bool AddFunction(const DebugFunctionInfo& function); - bool AddDataVariable(uint64_t address, Ref<Type> type, const std::string& name = ""); + bool AddDataVariable(uint64_t address, Ref<Type> type, const std::string& name = "", const std::vector<std::string>& components = {}); }; /*! diff --git a/binaryninjacore.h b/binaryninjacore.h index 0fdba060..3bea2623 100644 --- a/binaryninjacore.h +++ b/binaryninjacore.h @@ -2989,6 +2989,8 @@ extern "C" uint64_t address; BNType* type; BNPlatform* platform; + char** components; + size_t componentN; } BNDebugFunctionInfo; typedef struct BNSecretsProviderCallbacks @@ -6597,7 +6599,7 @@ extern "C" BINARYNINJACOREAPI bool BNRemoveDebugParserDataVariables( BNDebugInfo* const debugInfo, const char* const parserName); BINARYNINJACOREAPI bool BNAddDebugType( - BNDebugInfo* const debugInfo, const char* const name, const BNType* const type); + BNDebugInfo* const debugInfo, const char* const name, const BNType* const type, const char** const components, size_t components_count); BINARYNINJACOREAPI BNNameAndType* BNGetDebugTypes( BNDebugInfo* const debugInfo, const char* const name, size_t* count); BINARYNINJACOREAPI BNType* BNGetDebugTypeByName( @@ -6614,7 +6616,7 @@ extern "C" BNDebugInfo* const debugInfo, const char* const parserName, const size_t index); BINARYNINJACOREAPI void BNFreeDebugFunctions(BNDebugFunctionInfo* functions, size_t count); BINARYNINJACOREAPI bool BNAddDebugDataVariable( - BNDebugInfo* const debugInfo, uint64_t address, const BNType* const type, const char* name); + BNDebugInfo* const debugInfo, uint64_t address, const BNType* const type, const char* name, const char** const components, size_t components_count); BINARYNINJACOREAPI bool BNAddDebugDataVariableInfo( BNDebugInfo* const debugInfo, const BNDataVariableAndName* var); BINARYNINJACOREAPI BNDataVariableAndName* BNGetDebugDataVariables( diff --git a/debuginfo.cpp b/debuginfo.cpp index 0a726a58..2af16525 100644 --- a/debuginfo.cpp +++ b/debuginfo.cpp @@ -93,11 +93,14 @@ vector<DebugFunctionInfo> DebugInfo::GetFunctions(const string& parserName) cons vector<DebugFunctionInfo> result; for (size_t i = 0; i < count; ++i) { + vector<string> components; + for (size_t componentN = 0; componentN < functions[i].componentN; ++componentN) + components.emplace_back(functions[i].components[componentN]); result.emplace_back(functions[i].shortName ? functions[i].shortName : "", functions[i].fullName ? functions[i].fullName : "", functions[i].rawName ? functions[i].rawName : "", functions[i].address, functions[i].type ? new Type(BNNewTypeReference(functions[i].type)) : nullptr, - functions[i].platform ? new Platform(BNNewPlatformReference(functions[i].platform)) : nullptr); + functions[i].platform ? new Platform(BNNewPlatformReference(functions[i].platform)) : nullptr, components); } BNFreeDebugFunctions(functions, count); @@ -268,14 +271,21 @@ bool DebugInfo::RemoveDataVariableByAddress(const string& parserName, const uint } -bool DebugInfo::AddType(const string& name, Ref<Type> type) +bool DebugInfo::AddType(const string& name, Ref<Type> type, const vector<string>& components) { - return BNAddDebugType(m_object, name.c_str(), type->GetObject()); + const char** const componentArray = new const char*[components.size()]; + for (size_t i = 0; i < components.size(); ++i) + componentArray[i] = components[i].c_str(); + return BNAddDebugType(m_object, name.c_str(), type->GetObject(), componentArray, components.size()); } bool DebugInfo::AddFunction(const DebugFunctionInfo& function) { + char** components = new char*[function.components.size()]; + for (size_t i = 0; i < function.components.size(); ++i) + components[i] = (char*)function.components[i].c_str(); + BNDebugFunctionInfo input; input.shortName = function.shortName.size() ? BNAllocString(function.shortName.c_str()) : nullptr; @@ -284,6 +294,8 @@ bool DebugInfo::AddFunction(const DebugFunctionInfo& function) input.address = function.address; input.type = function.type ? function.type->GetObject() : nullptr; input.platform = function.platform ? function.platform->GetObject() : nullptr; + input.components = components; + input.componentN = function.components.size(); bool result = BNAddDebugFunction(m_object, &input); @@ -295,11 +307,14 @@ bool DebugInfo::AddFunction(const DebugFunctionInfo& function) } -bool DebugInfo::AddDataVariable(uint64_t address, Ref<Type> type, const string& name) +bool DebugInfo::AddDataVariable(uint64_t address, Ref<Type> type, const string& name, const vector<string>& components) { + const char** const componentArray = new const char*[components.size()]; + for (size_t i = 0; i < components.size(); ++i) + componentArray[i] = components[i].c_str(); if (name.size() == 0) - return BNAddDebugDataVariable(m_object, address, type->GetObject(), nullptr); - return BNAddDebugDataVariable(m_object, address, type->GetObject(), name.c_str()); + return BNAddDebugDataVariable(m_object, address, type->GetObject(), nullptr, componentArray, components.size()); + return BNAddDebugDataVariable(m_object, address, type->GetObject(), name.c_str(), componentArray, components.size()); } diff --git a/python/debuginfo.py b/python/debuginfo.py index 2898fb38..7e189afd 100644 --- a/python/debuginfo.py +++ b/python/debuginfo.py @@ -107,15 +107,17 @@ class _DebugInfoParserMetaClass(type): @staticmethod def _parse_info( - debug_info: core.BNDebugInfo, view: core.BNBinaryView, progress: ProgressFuncType, - callback: Callable[["DebugInfo", 'binaryview.BinaryView'], bool], + debug_info: core.BNDebugInfo, view: core.BNBinaryView, debug_view: core.BNBinaryView, progress: ProgressFuncType, + callback: Callable[["DebugInfo", 'binaryview.BinaryView', 'binaryview.BinaryView', ProgressFuncType], bool], ) -> bool: try: file_metadata = filemetadata.FileMetadata(handle=core.BNGetFileForView(view)) view_obj = binaryview.BinaryView(file_metadata=file_metadata, handle=core.BNNewViewReference(view)) + debug_file_metadata = filemetadata.FileMetadata(handle=core.BNGetFileForView(debug_view)) + debug_view_obj = binaryview.BinaryView(file_metadata=debug_file_metadata, handle=core.BNNewViewReference(debug_view)) parser_ref = core.BNNewDebugInfoReference(debug_info) assert parser_ref is not None, "core.BNNewDebugInfoReference returned None" - return callback(DebugInfo(parser_ref), view_obj, progress) + return callback(DebugInfo(parser_ref), view_obj, debug_view_obj, progress) except: log_error(traceback.format_exc()) return False @@ -123,18 +125,19 @@ class _DebugInfoParserMetaClass(type): @classmethod def register( cls, name: str, is_valid: Callable[['binaryview.BinaryView'], bool], - parse_info: Callable[["DebugInfo", 'binaryview.BinaryView', ProgressFuncType], bool] + parse_info: Callable[["DebugInfo", 'binaryview.BinaryView', 'binaryview.BinaryView', ProgressFuncType], bool] ) -> "DebugInfoParser": """Registers a DebugInfoParser. See ``debuginfo.DebugInfoParser`` for more details.""" binaryninja._init_plugins() - is_valid_cb = ctypes.CFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, - ctypes.POINTER(core.BNBinaryView - ))(lambda ctxt, view: cls._is_valid(view, is_valid)) + is_valid_cb = ctypes.CFUNCTYPE( + ctypes.c_bool, ctypes.c_void_p, ctypes.POINTER(core.BNBinaryView) + )(lambda ctxt, view: cls._is_valid(view, is_valid)) + parse_info_cb = ctypes.CFUNCTYPE( - ctypes.c_bool, ctypes.c_void_p, ctypes.POINTER(core.BNDebugInfo), ctypes.POINTER(core.BNDebugInfo), ctypes.POINTER(core.BNBinaryView), - ctypes.CFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, ctypes.c_size_t, ctypes.c_size_t), ctypes.c_void_p, - )(lambda ctxt, debug_info, view, progress, progress_ctxt: cls._parse_info(debug_info, view, lambda cur, max: progress(progress_ctxt, cur, max), parse_info)) + ctypes.c_bool, ctypes.c_void_p, ctypes.POINTER(core.BNDebugInfo), ctypes.POINTER(core.BNBinaryView), ctypes.POINTER(core.BNBinaryView), + ctypes.CFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, ctypes.c_size_t, ctypes.c_size_t), ctypes.c_void_p, + )(lambda ctxt, debug_info, view, debug_view, progress, progress_ctxt: cls._parse_info(debug_info, view, debug_view, lambda cur, max: progress(progress_ctxt, cur, max), parse_info)) # Don't let our callbacks get garbage collected global _debug_info_parsers @@ -148,7 +151,7 @@ class _DebugInfoParserMetaClass(type): class DebugInfoParser(object, metaclass=_DebugInfoParserMetaClass): """ - :py:class:`DebugInfoParser` represents the registered parsers and providers of debug information to Binary Ninja. + :py:class:`DebugInfoParser` represents the registered parsers and providers of debug information for Binary Ninja. The debug information is used by Binary Ninja as ground-truth information about the attributes of functions, types, and variables that Binary Ninja's analysis pipeline would otherwise work to deduce. By providing @@ -157,7 +160,7 @@ class DebugInfoParser(object, metaclass=_DebugInfoParserMetaClass): A DebugInfoParser consists of: 1. A name - 2. An ``is_valid`` function which takes a BV and returns a bool + 2. An ``is_valid`` function which takes a :py:class:`binaryview.BinaryView` and returns a bool. 3. A ``parse`` function which takes a :py:class:`DebugInfo` object and uses the member functions :py:meth:`DebugInfo.add_type`, :py:meth:`DebugInfo.add_function`, and :py:meth:`DebugInfo.add_data_variable` to populate all the info it can. And finally calling :py:meth:`DebugInfoParser.register` to register it with the core. @@ -169,10 +172,10 @@ class DebugInfoParser(object, metaclass=_DebugInfoParserMetaClass): def is_valid(bv: bn.binaryview.BinaryView) -> bool: return bv.view_type == "Raw" - def parse_info(debug_info: bn.debuginfo.DebugInfo, bv: bn.binaryview.BinaryView) -> None: + def parse_info(debug_info: bn.debuginfo.DebugInfo, bv: bn.binaryview.BinaryView, debug_file: bn.binaryview.BinaryView, progress: Callable[[int, int], bool]) -> None: debug_info.add_type("name", bn.types.Type.int(4, True)) debug_info.add_data_variable(0x1234, bn.types.Type.int(4, True), "name") - function_info = bn.debuginfo.DebugFunctionInfo(0xdead1337, "short_name", "full_name", "raw_name", bn.types.Type.int(4, False), []) + function_info = bn.debuginfo.DebugFunctionInfo(0xadd6355, "short_name", "full_name", "raw_name", bn.types.Type.function(bn.types.Type.int(4, False), None), components=["some", "namespaces"]) debug_info.add_function(function_info) bn.debuginfo.DebugInfoParser.register("debug info parser", is_valid, parse_info) @@ -181,7 +184,7 @@ class DebugInfoParser(object, metaclass=_DebugInfoParserMetaClass): valid_parsers = bn.debuginfo.DebugInfoParser.get_parsers_for_view(bv) parser = valid_parsers[0] - debug_info = parser.parse_debug_info(bv) + debug_info = parser.parse_debug_info(bv, bv) # See docs for why BV is here twice bv.apply_debug_info(debug_info) Multiple debug-info parsers can manually contribute debug info for a binary view by simply calling ``parse_debug_info`` with the @@ -220,7 +223,13 @@ class DebugInfoParser(object, metaclass=_DebugInfoParserMetaClass): return core.BNIsDebugInfoParserValidForView(self.handle, view.handle) def parse_debug_info(self, view: 'binaryview.BinaryView', debug_view: 'binaryview.BinaryView', debug_info: Optional["DebugInfo"] = None, progress: ProgressFuncType = None) -> Optional["DebugInfo"]: - """Returns a ``DebugInfo`` object populated with debug info by this debug-info parser. Only provide a ``DebugInfo`` object if you wish to append to the existing debug info.""" + """ + Returns a ``DebugInfo`` object populated with debug info by this debug-info parser. Only provide a ``DebugInfo`` object if you wish to append to the existing debug info. + + Some debug file formats need both the original :py:class:`binaryview.BinaryView` (the binary being analyzed/having debug information applied to it) in addition to the :py:class:`binaryview.BinaryView` of the file containing the debug information. + For formats where you can get all the information you need from a single file, calls to ``parse_debug_info`` are required to provide that file for both arguments. + For formats where you can get all the information you need from a single file, implementations of `parse` should only read from the second `debug_file` parameter and ignore the former. + """ if progress is None: progress = lambda cur, max: True progress_c = ctypes.CFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, ctypes.c_size_t, ctypes.c_size_t)(lambda ctxt, cur, max: progress(cur, max)) @@ -242,11 +251,11 @@ class DebugInfoParser(object, metaclass=_DebugInfoParserMetaClass): @dataclass(frozen=True) class DebugFunctionInfo(object): """ - ``DebugFunctionInfo`` collates ground-truth function-external attributes for use in BinaryNinja's internal analysis. + ``DebugFunctionInfo`` collates ground-truth function attributes for use in BinaryNinja's analysis. - When contributing function info, provide only what you know - BinaryNinja will figure out everything else that it can, as it usually does. + When contributing function info, provide only what you know - BinaryNinja will figure out everything else that it can. - Functions will not be created if an address is not provided, but will be able to be queried from debug info for later user analysis. + Functions will not be created if an address is not provided, but are able to be queried by the user from `bv.debug_info` for later analysis. """ address: Optional[int] = None short_name: Optional[str] = None @@ -254,6 +263,7 @@ class DebugFunctionInfo(object): raw_name: Optional[str] = None function_type: Optional[_types.Type] = None platform: Optional['_platform.Platform'] = None + components: Optional[List[str]] = None def __repr__(self) -> str: suffix = f"@{self.address:#x}>" if self.address != 0 else ">" @@ -278,7 +288,7 @@ class DebugInfo(object): DebugInfoParsers. This makes it possible to gather debug info that may be distributed across several different formats and files. - DebugInfo cannot be instantiated by the user, instead get it from either the binary view (see ``'binaryview.BinaryView'.debug_info``) + DebugInfo cannot be instantiated by the user, instead you must get it from either the binary view (see ``'binaryview.BinaryView'.debug_info``) or a debug-info parser (see ``debuginfo.DebugInfoParser.parse_debug_info``). .. note:: Please note that calling one of ``add_*`` functions will not work outside of a debuginfo plugin. @@ -289,10 +299,6 @@ class DebugInfo(object): def __del__(self) -> None: core.BNFreeDebugInfoReference(self.handle) - def get_parsers(self) -> List[str]: - """Kept for backward compatibility. Use ``parsers`` property instead.""" - return self.parsers - @property def parsers(self) -> List[str]: count = ctypes.c_ulonglong() @@ -303,7 +309,6 @@ class DebugInfo(object): finally: core.BNFreeStringList(parsers, count.value) - def get_type_container(self, parser_name: str) -> 'typecontainer.TypeContainer': """ Type Container for all types in the DebugInfo that resulted from the parse of @@ -336,20 +341,25 @@ class DebugInfo(object): try: assert functions is not None, "core.BNGetDebugFunctions returned None" for i in range(0, count.value): + function = functions[i] - if functions[i].type: - function_type = _types.Type.create(core.BNNewTypeReference(functions[i].type)) + if function.type: + function_type = _types.Type.create(core.BNNewTypeReference(function.type)) else: function_type = None - if functions[i].platform: - func_platform = _platform.Platform(handle=core.BNNewPlatformReference(functions[i].platform)) + if function.platform: + func_platform = _platform.Platform(handle=core.BNNewPlatformReference(function.platform)) else: func_platform = None + components = [] + for c in range(0, function.componentN): + components.append(function.components[c]) + yield DebugFunctionInfo( - functions[i].address, functions[i].shortName, functions[i].fullName, functions[i].rawName, - function_type, func_platform + function.address, function.shortName, function.fullName, function.rawName, + function_type, func_platform, components ) finally: core.BNFreeDebugFunctions(functions, count.value) @@ -467,10 +477,16 @@ class DebugInfo(object): def remove_data_variable_by_address(self, parser_name: str, address: int): return core.BNRemoveDebugDataVariableByAddress(self.handle, parser_name, address) - def add_type(self, name: str, new_type: '_types.Type') -> bool: - """Adds a type scoped under the current parser's name to the debug info""" + def add_type(self, name: str, new_type: '_types.Type', components: Optional[List[str]] = None) -> bool: + """Adds a type scoped under the current parser's name to the debug info. While you're able to provide a list of components a type should live in, this is currently unused.""" + if components is None: + components = [] + component_list = (ctypes.c_char_p * len(components))() + for i in range(0, len(components)): + component_list[i] = str(components[i]).encode('charmap') + if isinstance(new_type, _types.Type): - return core.BNAddDebugType(self.handle, name, new_type.handle) + return core.BNAddDebugType(self.handle, name, new_type.handle, component_list, len(components)) return NotImplemented def add_function(self, new_func: DebugFunctionInfo) -> bool: @@ -494,6 +510,18 @@ class DebugInfo(object): else: return NotImplemented + if new_func.components is None: + components = [] + elif isinstance(new_func.components, list): + components = new_func.components + else: + return NotImplemented + component_list = (ctypes.c_char_p * len(components))() + for c in range(0, len(components)): + component_list[c] = str(components[c]).encode('charmap') + func_info.components = component_list + func_info.componentN = len(components) + func_info.shortName = new_func.short_name func_info.fullName = new_func.full_name func_info.rawName = new_func.raw_name @@ -501,8 +529,14 @@ class DebugInfo(object): return core.BNAddDebugFunction(self.handle, func_info) - def add_data_variable(self, address: int, new_type: '_types.Type', name: Optional[str] = None) -> bool: - """Adds a data variable scoped under the current parser's name to the debug info""" + def add_data_variable(self, address: int, new_type: '_types.Type', name: Optional[str] = None, components: Optional[List[str]] = None) -> bool: + """Adds a data variable scoped under the current parser's name to the debug info. Optionally, you can provide a path of component names under which that data variable should appear in the symbols sidebar.""" + if components is None: + components = [] + component_list = (ctypes.c_char_p * len(components))() + for i in range(0, len(components)): + component_list[i] = str(components[i]).encode('charmap') + if isinstance(address, int) and isinstance(new_type, _types.Type): - return core.BNAddDebugDataVariable(self.handle, address, new_type.handle, name) + return core.BNAddDebugDataVariable(self.handle, address, new_type.handle, name, component_list, len(components)) return NotImplemented diff --git a/python/examples/debug_info.py b/python/examples/debug_info.py deleted file mode 100755 index 4f4328d3..00000000 --- a/python/examples/debug_info.py +++ /dev/null @@ -1,292 +0,0 @@ -#!/usr/bin/env python3 - -# If you're here, you're likely looking for boilerplate code. Here it is: -# ``` -# import binaryninja as bn -# -# def is_valid(bv: bn.binaryview.BinaryView): -# return bv.view_type == "Raw" -# -# def parse_info(debug_info: bn.debuginfo.DebugInfo, bv: bn.binaryview.BinaryView): -# debug_info.add_type("name", bn.types.Type.int(4, True)) -# -# debug_info.add_data_variable(0x1234, bn.types.Type.int(4, True), "name") -# debug_info.add_data_variable(0x4321, bn.types.Type.int(4, True)) # Names are optional -# -# # Just provide the information you can; we can't create the function without an address, but we'll -# # figure out what we can and you can query this info later when you have a better idea of things -# function_info = bn.debuginfo.DebugFunctionInfo(0xdead1337, "short_name", "full_name", "raw_name", bn.types.Type.int(4, False), []) -# debug_info.add_function(function_info) -# -# bn.debuginfo.DebugInfoParser.register("debug info parser", is_valid, parse_info) -# ``` - -# If you're interesting in applying debug info to existing BNDBs or otherwise manipulating debug info more directally, consider: -# ``` -# valid_parsers = bn.debuginfo.DebugInfoParser.get_parsers_for_view(bv) -# parser = bn.debuginfo.DebugInfoParser[name] -# debug_info = parser.parse_debug_info(bv) -# bv.apply_debug_info(debug_info) -# ``` - -# The rest of this file serves as a test and example of implementing debug info parsers, and the resultant debug info. -# -# All that is required is to provide functions similar to "is_valid" and "parse_info" below, and call -# `binaryninja.debuginfo.DebugInfoParser.register` with a name for your parser; your parser will be made -# available for all valid binary views, with the ability to parse and apply debug info to existing BNDBs. -# -# For the purposes of this example, the following test program was compiled and the symbol `__elf_interp` -# overwritten to provide some magic for us to key on. This example should prove sufficient to -# demonstraight the capabilities of a debug info parser; providing function prototypes, local variables, -# data variables, and new types. It also highlights some limitations of BN at time of writing which -# should be fixed (see github.com/Vector35/binaryninja-api/issues/2399). -# ``` -# #include <stdint.h> -# #include <stdbool.h> -# -# struct test_type_1 { -# int a; -# char b[4]; -# uint64_t c; -# bool d; -# } test_var_1; -# -# struct test_type_2 { -# struct test_type_1 a; -# struct test_type_1* b; -# struct test_type_2* c; -# }; -# -# int test_var_2 = 0x1232; -# const int test_var_3 = 0x1233; -# static int test_var_4 = 0x1234; -# -# void no_return_type_no_parameters() { } -# -# bool used_parameter(bool value) -# { -# return !value; -# } -# -# int unused_parameters(bool value_1, int value_2, char* value_3) -# { -# return 8*16-12/32+7|13; -# } -# -# int used_and_unused_parameters_1(int value_1, int value_2, char* value_3, bool value_4) -# { -# return value_1 + value_2; -# } -# -# uint8_t used_and_unused_parameters_2(bool value_1, uint8_t value_2, char* value_3, uint8_t value_4, char value_5) -# { -# return value_2 + value_4; -# } -# -# void local_parameters(bool value_1, uint8_t value_2, char* value_3, uint8_t value_4, char value_5) -# { -# char local_var_1 = value_1 ? value_3[15] : value_5; -# uint8_t local_var_2 = value_2 + 25; -# } -# -# int main() -# { -# int a = 0b01010101; -# int b = 0b10101010; -# return ~(a | b | test_var_2); -# } -# ``` - -import binaryninja as bn -import os - -filename = os.path.join(os.path.dirname(os.path.abspath(__file__)), "test_debug_info") - -# Some setup code not just for informative printing - -print = print -if __name__ != "__main__": - print = bn.log_error - - -def pretty_print_add_data_variable( - debug_info: bn.debuginfo.DebugInfo, address: int, t: bn.types.Type, name: str = None -) -> None: - print(f" Adding data variable of type `{t}` at {hex(address)} : {debug_info.add_data_variable(address, t, name)}") - - -def pretty_print_add_function( - debug_info: bn.debuginfo.DebugInfo, address: int, short_name: str = None, full_name: str = None, raw_name: str = None, - return_type=None, parameters=None -) -> None: - function_info = bn.debuginfo.DebugFunctionInfo(address, short_name, full_name, raw_name, return_type, parameters) - if parameters is not None: - print( - f" Adding function `{return_type} {short_name}({', '.join(f'{t} {name}' for name, t in parameters)})` at {hex(address)} : {debug_info.add_function(function_info)}" - ) - else: - print( - f" Adding function `{return_type} {short_name}()` at {hex(address)} : {debug_info.add_function(function_info)}" - ) - - -# The beginning of the actual debug info plugin - - -def is_valid(bv: bn.binaryview.BinaryView): - sym = bv.get_symbol_by_raw_name("__elf_interp") - if sym is None: - return False - else: - var = bv.get_data_var_at(sym.address) - return b"test_debug_info_parsing" == bv.read(sym.address, var.type.width - 1) - - -def parse_info(debug_info: bn.debuginfo.DebugInfo, bv: bn.binaryview.BinaryView): - print("Adding types") - types = [] - for name, t in bv.parse_types_from_string( - """ - struct test_type_1 { - int a; - char b[4]; - uint64_t c; - bool d; - }; - - struct test_type_2 { - struct test_type_1 a; - struct test_type_1* b; - struct test_type_2* c; - };""" - ).types.items(): - print(f" Adding type \"{name}\" `{t}` : {debug_info.add_type(str(name), t)}") - types.append(t) - - print("Adding data variables") - pretty_print_add_data_variable(debug_info, 0x4030, types[0], "test_var_1") - pretty_print_add_data_variable(debug_info, 0x4010, bn.types.Type.int(4, True), "test_var_2") - # Names are optional - pretty_print_add_data_variable(debug_info, 0x4014, bn.types.Type.int(4, True)) - - t = bn.types.Type.int(4, True) - t.const = True - pretty_print_add_data_variable(debug_info, 0x2004, t, "test_var_3") - - print("Adding functions") - char_star = bv.parse_type_string("char*")[0] - pretty_print_add_function(debug_info, 0x1129, "no_return_type_no_parameters", None, None, bn.types.Type.void(), None) - pretty_print_add_function( - debug_info, 0x1134, "used_parameter", None, None, bn.types.Type.bool(), [("value", bn.types.Type.bool())] - ) - pretty_print_add_function( - debug_info, 0x1155, "unused_parameters", None, None, bn.types.Type.int(4, True), - [("value_1", bn.types.Type.bool()), ("value_2", bn.types.Type.int(4, True)), ("value_3", char_star)] - ) - pretty_print_add_function( - debug_info, 0x1170, "used_and_unused_parameters_1", None, None, bn.types.Type.int(4, True), - [("value_1", bn.types.Type.int(4, True)), ("value_2", bn.types.Type.int(4, True)), ("value_3", char_star), - ("value_4", bn.types.Type.bool())] - ) - pretty_print_add_function( - debug_info, 0x1191, "used_and_unused_parameters_2", None, None, bn.types.Type.int(1, False), - [("value_1", bn.types.Type.bool()), ("value_2", bn.types.Type.int(1, False)), ("value_3", char_star), - ("value_4", bn.types.Type.int(1, False)), ("value_5", bn.types.Type.char())] - ) - pretty_print_add_function( - debug_info, 0x11c0, "local_parameters", None, None, bn.types.Type.void(), [("value_1", bn.types.Type.bool()), - ("value_2", bn.types.Type.int(1, False)), - ("value_3", char_star), - ("value_4", bn.types.Type.int(1, False)), - ("value_5", bn.types.Type.char())] - ) - - -parser = bn.debuginfo.DebugInfoParser.register("test debug info parser", is_valid, parse_info) -print(f"Registered parser: {parser.name}") - -# The above is all that is needed for a DebugInfo plugin -# The below serves to test the correctness of (the Python bindings' implementation of) debug info parsers' functionality. - -bn.debuginfo.DebugInfoParser.register("dummy extra debug parser 1", lambda bv: False, lambda di, bv: None) -bn.debuginfo.DebugInfoParser.register( - "dummy extra debug parser 2", lambda bv: bv.view_type != "Raw", lambda di, bv: None -) - -# Test fetching parser list and fetching by name -print(f"Availible parsers: {len(list(bn.debuginfo.DebugInfoParser))}") -for p in bn.debuginfo.DebugInfoParser: - if p == parser: - print(f" {bn.debuginfo.DebugInfoParser[p.name].name} (the one we just registered)") - else: - print(f" {bn.debuginfo.DebugInfoParser[p.name].name}") - -# Test calling our `is_valid` callback -bv = bn.load(filename, options={"analysis.debugInfo.internal": False}) -if parser.is_valid_for_view(bv): - print("Parser is valid") -else: - print("Parser is NOT valid!") - quit() - -# Test getting list of valid parsers, and DebugInfoParser's repr -print("") -for p in bn.debuginfo.DebugInfoParser.get_parsers_for_view(bv): - print(f"`{p.name}` is valid for `{bv}`") -print("") - -# Test calling our `parse_info` callback -debug_info = parser.parse_debug_info(bv) -# debug_info = bv.debug_info - -print("\nEach of the following pairs of prints should be the same\n") - -print("All types:") -for name, t in debug_info.types: - print(f" \"{name}\": `{t}`") - -print("Types from parser:") -for name, t in debug_info.types_from_parser(parser.name): - print(f" \"{name}\": `{t}`") - -print("") - -print("All functions:") -for func in debug_info.functions: - print(f" {func}") - -print("Functions from parser:") -for func in debug_info.functions_from_parser(parser.name): - print(f" {func}") - -print("") - -print("All data variables:") -for data_var in debug_info.data_variables: - print(f" {data_var}") - -print("Data variables from parser:") -for data_var in debug_info.data_variables_from_parser(parser.name): - print(f" {data_var}") - -print("Appling debug info!") -bv.apply_debug_info(debug_info) -bv.update_analysis_and_wait() - -# Checking applied debug info -print("") -print("Types:") -for name, t in debug_info.types: - print(f" {bv.get_type_by_name(name)}") - -print("") - -print("Functions:") -for func in debug_info.functions: - print(f" {bv.get_function_at(func.address)}") - -print("") - -print("Data variables:") -for data_var in debug_info.data_variables: - print(f" {bv.get_data_var_at(data_var.address)}") diff --git a/python/examples/test_debug_info b/python/examples/test_debug_info Binary files differdeleted file mode 100755 index 8a650411..00000000 --- a/python/examples/test_debug_info +++ /dev/null diff --git a/rust/examples/dwarf/dwarf_import/src/dwarfdebuginfo.rs b/rust/examples/dwarf/dwarf_import/src/dwarfdebuginfo.rs index 89ff2e97..cefc8672 100644 --- a/rust/examples/dwarf/dwarf_import/src/dwarfdebuginfo.rs +++ b/rust/examples/dwarf/dwarf_import/src/dwarfdebuginfo.rs @@ -293,7 +293,8 @@ impl DebugInfoBuilder { fn commit_types(&self, debug_info: &mut DebugInfo) { for debug_type in self.types() { if debug_type.commit { - debug_info.add_type(debug_type.name.clone(), debug_type.t.as_ref()); + debug_info.add_type(debug_type.name.clone(), debug_type.t.as_ref(), &[]); + // TODO : Components } } } @@ -304,7 +305,8 @@ impl DebugInfoBuilder { assert!(debug_info.add_data_variable( address, &self.get_type(*type_uid).unwrap().1, - name.clone() + name.clone(), + &[] // TODO : Components )); } } @@ -346,6 +348,7 @@ impl DebugInfoBuilder { Some(self.get_function_type(function)), function.address, function.platform.clone(), + vec![], // TODO : Components )); } } diff --git a/rust/src/debuginfo.rs b/rust/src/debuginfo.rs index 640819dd..ab4f8f6b 100644 --- a/rust/src/debuginfo.rs +++ b/rust/src/debuginfo.rs @@ -285,17 +285,23 @@ unsafe impl CoreOwnedArrayProvider for DebugInfoParser { /// When contributing function info, provide only what you know - BinaryNinja will figure out everything else that it can, as it usually does. /// /// Functions will not be created if an address is not provided, but will be able to be queried from debug info for later user analysis. -pub struct DebugFunctionInfo<S: BnStrCompatible> { - short_name: Option<S>, - full_name: Option<S>, - raw_name: Option<S>, +pub struct DebugFunctionInfo { + short_name: Option<String>, + full_name: Option<String>, + raw_name: Option<String>, type_: Option<Ref<Type>>, address: u64, platform: Option<Ref<Platform>>, + components: Vec<String>, } -impl From<&BNDebugFunctionInfo> for DebugFunctionInfo<String> { +impl From<&BNDebugFunctionInfo> for DebugFunctionInfo { fn from(raw: &BNDebugFunctionInfo) -> Self { + let components = unsafe { slice::from_raw_parts(raw.components, raw.componentN) } + .iter() + .map(|component| raw_to_string(*component as *const _).unwrap()) + .collect(); + Self { short_name: raw_to_string(raw.shortName), full_name: raw_to_string(raw.fullName), @@ -311,18 +317,20 @@ impl From<&BNDebugFunctionInfo> for DebugFunctionInfo<String> { } else { Some(unsafe { Platform::ref_from_raw(raw.platform) }) }, + components, } } } -impl<S: BnStrCompatible> DebugFunctionInfo<S> { +impl DebugFunctionInfo { pub fn new( - short_name: Option<S>, - full_name: Option<S>, - raw_name: Option<S>, + short_name: Option<String>, + full_name: Option<String>, + raw_name: Option<String>, type_: Option<Ref<Type>>, address: Option<u64>, platform: Option<Ref<Platform>>, + components: Vec<String>, ) -> Self { Self { short_name, @@ -334,6 +342,7 @@ impl<S: BnStrCompatible> DebugFunctionInfo<S> { _ => 0, }, platform, + components, } } } @@ -408,7 +417,7 @@ impl DebugInfo { pub fn functions_by_name<S: BnStrCompatible>( &self, parser_name: S, - ) -> Vec<DebugFunctionInfo<String>> { + ) -> Vec<DebugFunctionInfo> { let parser_name = parser_name.into_bytes_with_nul(); let mut count: usize = 0; @@ -420,10 +429,10 @@ impl DebugInfo { ) }; - let result: Vec<DebugFunctionInfo<String>> = unsafe { + let result: Vec<DebugFunctionInfo> = unsafe { slice::from_raw_parts_mut(functions_ptr, count) .iter() - .map(DebugFunctionInfo::<String>::from) + .map(DebugFunctionInfo::from) .collect() }; @@ -432,15 +441,15 @@ impl DebugInfo { } /// A generator of all functions provided by DebugInfoParsers - pub fn functions(&self) -> Vec<DebugFunctionInfo<String>> { + pub fn functions(&self) -> Vec<DebugFunctionInfo> { let mut count: usize = 0; let functions_ptr = unsafe { BNGetDebugFunctions(self.handle, ptr::null_mut(), &mut count) }; - let result: Vec<DebugFunctionInfo<String>> = unsafe { + let result: Vec<DebugFunctionInfo> = unsafe { slice::from_raw_parts_mut(functions_ptr, count) .iter() - .map(DebugFunctionInfo::<String>::from) + .map(DebugFunctionInfo::from) .collect() }; @@ -720,38 +729,57 @@ impl DebugInfo { } /// Adds a type scoped under the current parser's name to the debug info - pub fn add_type<S: BnStrCompatible>(&self, name: S, new_type: &Type) -> bool { + pub fn add_type<S: BnStrCompatible>( + &self, + name: S, + new_type: &Type, + components: &[&str], + ) -> bool { + let mut components_array: Vec<*const ::std::os::raw::c_char> = + Vec::with_capacity(components.len()); + for component in components { + components_array.push(component.as_ptr() as _); + } + let name = name.into_bytes_with_nul(); unsafe { BNAddDebugType( self.handle, name.as_ref().as_ptr() as *mut _, new_type.handle, + components_array.as_ptr() as _, + components.len(), ) } } /// Adds a function scoped under the current parser's name to the debug info - pub fn add_function<S: BnStrCompatible>(&self, new_func: DebugFunctionInfo<S>) -> bool { + pub fn add_function(&self, new_func: DebugFunctionInfo) -> bool { let short_name_bytes = new_func.short_name.map(|name| name.into_bytes_with_nul()); let short_name = short_name_bytes .as_ref() .map_or(ptr::null_mut() as *mut _, |name| { - name.as_ref().as_ptr() as *mut _ + name.as_ptr() as _ }); let full_name_bytes = new_func.full_name.map(|name| name.into_bytes_with_nul()); let full_name = full_name_bytes .as_ref() .map_or(ptr::null_mut() as *mut _, |name| { - name.as_ref().as_ptr() as *mut _ + name.as_ptr() as _ }); let raw_name_bytes = new_func.raw_name.map(|name| name.into_bytes_with_nul()); let raw_name = raw_name_bytes .as_ref() .map_or(ptr::null_mut() as *mut _, |name| { - name.as_ref().as_ptr() as *mut _ + name.as_ptr() as _ }); + let mut components_array: Vec<*const ::std::os::raw::c_char> = + Vec::with_capacity(new_func.components.len()); + for component in &new_func.components { + components_array.push(component.as_ptr() as _); + } + unsafe { BNAddDebugFunction( self.handle, @@ -768,6 +796,8 @@ impl DebugInfo { Some(platform) => platform.handle, _ => ptr::null_mut(), }, + components: components_array.as_ptr() as _, + componentN: new_func.components.len(), }, ) } @@ -779,7 +809,14 @@ impl DebugInfo { address: u64, t: &Type, name: Option<S>, + components: &[&str], ) -> bool { + let mut components_array: Vec<*const ::std::os::raw::c_char> = + Vec::with_capacity(components.len()); + for component in components { + components_array.push(component.as_ptr() as _); + } + match name { Some(name) => { let name = name.into_bytes_with_nul(); @@ -789,11 +826,20 @@ impl DebugInfo { address, t.handle, name.as_ref().as_ptr() as *mut _, + components.as_ptr() as _, + components.len(), ) } } None => unsafe { - BNAddDebugDataVariable(self.handle, address, t.handle, ptr::null_mut()) + BNAddDebugDataVariable( + self.handle, + address, + t.handle, + ptr::null_mut(), + components.as_ptr() as _, + components.len(), + ) }, } } |
