diff options
| author | KyleMiles <krm504@nyu.edu> | 2021-05-19 20:40:24 -0400 |
|---|---|---|
| committer | KyleMiles <krm504@nyu.edu> | 2021-07-07 23:27:29 -0400 |
| commit | d19f5e85ee26c3d6604798d30ab821d15731cada (patch) | |
| tree | 9851b7f6bf5589df69508d4489c11148515cb550 | |
| parent | 0f58b0742e3010eee6bbd2e15f4dd4930ec0d822 (diff) | |
DebugInfo - plugable debug information importers - C++, Rust, and Python APIs
| -rw-r--r-- | binaryninjaapi.h | 90 | ||||
| -rw-r--r-- | binaryninjacore.h | 56 | ||||
| -rw-r--r-- | debuginfo.cpp | 249 | ||||
| -rw-r--r-- | python/__init__.py | 1 | ||||
| -rw-r--r-- | python/binaryview.py | 29 | ||||
| -rw-r--r-- | python/debuginfo.py | 426 | ||||
| -rwxr-xr-x | python/examples/debug_info.py | 269 | ||||
| -rwxr-xr-x | python/examples/test_debug_info | bin | 0 -> 14336 bytes | |||
| -rw-r--r-- | rust/src/binaryview.rs | 13 | ||||
| -rw-r--r-- | rust/src/debuginfo.rs | 657 | ||||
| -rw-r--r-- | rust/src/lib.rs | 1 | ||||
| -rw-r--r-- | rust/src/string.rs | 8 | ||||
| -rw-r--r-- | rust/src/types.rs | 157 |
13 files changed, 1939 insertions, 17 deletions
diff --git a/binaryninjaapi.h b/binaryninjaapi.h index 5c3e0cde..6d4119ca 100644 --- a/binaryninjaapi.h +++ b/binaryninjaapi.h @@ -1301,6 +1301,17 @@ __attribute__ ((format (printf, 1, 2))) bool autoDiscovered; }; + struct DataVariableAndName + { + DataVariableAndName() { } + DataVariableAndName(uint64_t a, Type* t, bool d, const std::string& n) : address(a), type(t), autoDiscovered(d), name(n) { } + + uint64_t address; + Confidence<Ref<Type>> type; + bool autoDiscovered; + std::string name; + }; + class TagType: public CoreRefCountObject<BNTagType, BNNewTagTypeReference, BNFreeTagType> { public: @@ -5623,8 +5634,85 @@ __attribute__ ((format (printf, 1, 2))) uint64_t findConstant; DataBuffer findBuffer; - + std::vector<BNAddressRange> ranges; uint64_t totalLength; }; + + struct DebugFunctionInfo + { + std::string shortName; + std::string fullName; + std::string rawName; + uint64_t address; + Ref<Type> returnType; + std::vector<std::tuple<std::string, Ref<Type>>> parameters; + bool variableParameters; + Ref<CallingConvention> callingConvention; + Ref<Platform> platform; + + DebugFunctionInfo(std::string shortName, + std::string fullName, + std::string rawName, + uint64_t address, + Ref<Type> returnType, + std::vector<std::tuple<std::string, Ref<Type>>> parameters, + bool variableParameters, + Ref<CallingConvention> callingConvention, + Ref<Platform> platform) : + shortName(shortName), + fullName(fullName), + rawName(rawName), + address(address), + returnType(returnType), + parameters(parameters), + variableParameters(variableParameters), + callingConvention(callingConvention), + platform(platform) + {} + }; + + class DebugInfo : public CoreRefCountObject<BNDebugInfo, BNNewDebugInfoReference, BNFreeDebugInfoReference> + { + public: + DebugInfo(BNDebugInfo* debugInfo); + + std::vector<NameAndType> GetTypes(const std::string& parserName = ""); + std::vector<DebugFunctionInfo> GetFunctions(const std::string& parserName = ""); + std::vector<DataVariableAndName> GetDataVariables(const std::string& parserName = ""); + + bool AddType(const std::string& name, Ref<Type> type); + bool AddFunction(const DebugFunctionInfo& function); + bool AddDataVariable(uint64_t address, Ref<Type> type, const std::string& name = ""); + }; + + class DebugInfoParser : public CoreRefCountObject<BNDebugInfoParser, BNNewDebugInfoParserReference, BNFreeDebugInfoParserReference> + { + public: + DebugInfoParser(BNDebugInfoParser* parser); + + static Ref<DebugInfoParser> GetByName(const std::string& name); + static std::vector<Ref<DebugInfoParser>> GetList(); + static std::vector<Ref<DebugInfoParser>> GetListForView(const Ref<BinaryView> data); + + std::string GetName() const; + Ref<DebugInfo> Parse(Ref<BinaryView> view, Ref<DebugInfo> existingDebugInfo = nullptr) const; + + bool IsValidForView(const Ref<BinaryView> view) const; + }; + + class CustomDebugInfoParser : public DebugInfoParser + { + static bool IsValidCallback(void* ctxt, BNBinaryView* view); + static void ParseCallback(void* ctxt, BNDebugInfo* debugInfo, BNBinaryView* view); + BNDebugInfoParser* Register(const std::string& name); + + public: + CustomDebugInfoParser(const std::string& name); + virtual ~CustomDebugInfoParser() {} + + virtual bool IsValid(Ref<BinaryView>) = 0; + virtual void ParseInfo(Ref<DebugInfo>, Ref<BinaryView>) = 0; + }; } + diff --git a/binaryninjacore.h b/binaryninjacore.h index def57659..0f6ac60c 100644 --- a/binaryninjacore.h +++ b/binaryninjacore.h @@ -106,7 +106,9 @@ #define BN_FULL_CONFIDENCE 255 #define BN_MINIMUM_CONFIDENCE 1 +#define BN_DEFAULT_CONFIDENCE 96 #define BN_HEURISTIC_CONFIDENCE 192 +#define BN_DEBUGINFO_CONFIDENCE 200 #define DEFAULT_INTERNAL_NAMESPACE "BNINTERNALNAMESPACE" #define DEFAULT_EXTERNAL_NAMESPACE "BNEXTERNALNAMESPACE" @@ -208,6 +210,8 @@ extern "C" struct BNDisassemblyTextRenderer; struct BNLinearViewObject; struct BNLinearViewCursor; + struct BNDebugInfo; + struct BNDebugInfoParser; //! Console log levels @@ -940,6 +944,15 @@ extern "C" uint8_t typeConfidence; }; + struct BNDataVariableAndName + { + uint64_t address; + BNType* type; + char* name; + bool autoDiscovered; + uint8_t typeConfidence; + }; + enum BNMediumLevelILOperation { MLIL_NOP, @@ -2570,6 +2583,21 @@ extern "C" AllowDeadStoreElimination }; + struct BNDebugFunctionInfo + { + char* shortName; + char* fullName; + char* rawName; + uint64_t address; + BNType* returnType; + char** parameterNames; + BNType** parameterTypes; + size_t parameterCount; + bool variableParameters; + BNCallingConvention* callingConvention; + BNPlatform* platform; + }; + BINARYNINJACOREAPI char* BNAllocString(const char* contents); BINARYNINJACOREAPI void BNFreeString(char* str); BINARYNINJACOREAPI char** BNAllocStringList(const char** contents, size_t size); @@ -3673,6 +3701,7 @@ __attribute__ ((format (printf, 1, 2))) BINARYNINJACOREAPI void BNUndefineUserDataVariable(BNBinaryView* view, uint64_t addr); BINARYNINJACOREAPI BNDataVariable* BNGetDataVariables(BNBinaryView* view, size_t* count); BINARYNINJACOREAPI void BNFreeDataVariables(BNDataVariable* vars, size_t count); + BINARYNINJACOREAPI void BNFreeDataVariablesAndName(BNDataVariableAndName* vars, size_t count); BINARYNINJACOREAPI bool BNGetDataVariableAtAddress(BNBinaryView* view, uint64_t addr, BNDataVariable* var); BINARYNINJACOREAPI bool BNParseTypeString(BNBinaryView* view, const char* text, @@ -3956,6 +3985,10 @@ __attribute__ ((format (printf, 1, 2))) BINARYNINJACOREAPI void BNDefineImportedFunction(BNBinaryView* view, BNSymbol* importAddressSym, BNFunction* func, BNType* type); BINARYNINJACOREAPI BNSymbol* BNDefineAutoSymbolAndVariableOrFunction(BNBinaryView* view, BNPlatform* platform, BNSymbol* sym, BNType* type); + BINARYNINJACOREAPI BNDebugInfo* BNGetDebugInfo(BNBinaryView* view); + BINARYNINJACOREAPI void BNApplyDebugInfo(BNBinaryView* view, BNDebugInfo* newDebugInfo); + BINARYNINJACOREAPI void BNSetDebugInfo(BNBinaryView* view, BNDebugInfo* newDebugInfo); + BINARYNINJACOREAPI BNSymbol* BNImportedFunctionFromImportAddressSymbol(BNSymbol* sym, uint64_t addr); // Low-level IL @@ -5189,6 +5222,29 @@ __attribute__ ((format (printf, 1, 2))) BINARYNINJACOREAPI char** BNRustSimplifyStrToFQN(const char* const, bool); BINARYNINJACOREAPI char* BNRustSimplifyStrToStr(const char* const); + BINARYNINJACOREAPI BNDebugInfoParser* BNRegisterDebugInfoParser(const char* name, bool (*isValid)(void*, BNBinaryView*), void (*parseInfo)(void*, BNDebugInfo*, BNBinaryView*), void* context); + BINARYNINJACOREAPI void BNUnregisterDebugInfoParser(const char* rawName); + BINARYNINJACOREAPI BNDebugInfoParser* BNGetDebugInfoParserByName(const char* name); + BINARYNINJACOREAPI BNDebugInfoParser** BNGetDebugInfoParsers(size_t* count); + BINARYNINJACOREAPI BNDebugInfoParser** BNGetDebugInfoParsersForView(BNBinaryView* view, size_t* count); + BINARYNINJACOREAPI char* BNGetDebugInfoParserName(BNDebugInfoParser* parser); + BINARYNINJACOREAPI bool BNIsDebugInfoParserValidForView(BNDebugInfoParser* parser, BNBinaryView* view); + BINARYNINJACOREAPI BNDebugInfo* BNParseDebugInfo(BNDebugInfoParser* parser, BNBinaryView* view, BNDebugInfo* existingDebugInfo); + BINARYNINJACOREAPI BNDebugInfoParser* BNNewDebugInfoParserReference(BNDebugInfoParser* parser); + BINARYNINJACOREAPI void BNFreeDebugInfoParserReference(BNDebugInfoParser* parser); + BINARYNINJACOREAPI void BNFreeDebugInfoParserList(BNDebugInfoParser** parsers, size_t count); + + BINARYNINJACOREAPI BNDebugInfo* BNNewDebugInfoReference(BNDebugInfo* debugInfo); + BINARYNINJACOREAPI void BNFreeDebugInfoReference(BNDebugInfo* debugInfo); + BINARYNINJACOREAPI bool BNAddDebugType(BNDebugInfo* const debugInfo, const char* const name, const BNType* const type); + BINARYNINJACOREAPI BNNameAndType* BNGetDebugTypes(BNDebugInfo* const debugInfo, const char* const name, size_t* count); + BINARYNINJACOREAPI void BNFreeDebugTypes(BNNameAndType* types, size_t count); + BINARYNINJACOREAPI bool BNAddDebugFunction(BNDebugInfo* const debugInfo, BNDebugFunctionInfo* func); + BINARYNINJACOREAPI BNDebugFunctionInfo* BNGetDebugFunctions(BNDebugInfo* const debugInfo, const char* const name, size_t* count); + BINARYNINJACOREAPI void BNFreeDebugFunctions(BNDebugFunctionInfo* functions, size_t count); + BINARYNINJACOREAPI bool BNAddDebugDataVariable(BNDebugInfo* const debugInfo, uint64_t address, const BNType* const type, const char* name); + BINARYNINJACOREAPI BNDataVariableAndName* BNGetDebugDataVariables(BNDebugInfo* const debugInfo, const char* const name, size_t* count); + #ifdef __cplusplus } #endif diff --git a/debuginfo.cpp b/debuginfo.cpp new file mode 100644 index 00000000..2bdc2e47 --- /dev/null +++ b/debuginfo.cpp @@ -0,0 +1,249 @@ +// Copyright (c) 2015-2021 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. + + +// TODO : Documentation + + +#include "binaryninjaapi.h" +using namespace BinaryNinja; +using namespace std; + + +/////////////// +// DebugInfo // +/////////////// + + +DebugInfo::DebugInfo(BNDebugInfo* debugInfo) +{ + m_object = debugInfo; +} + + +vector<NameAndType> DebugInfo::GetTypes(const string& parserName) +{ + size_t count; + BNNameAndType* nameAndTypes = BNGetDebugTypes(m_object, parserName.size() == 0 ? nullptr : parserName.c_str(), &count); + + vector<NameAndType> result; + for (size_t i = 0; i < count; ++i) + { + result.emplace_back(nameAndTypes[i].name, + Confidence<Ref<Type>>( + new Type(BNNewTypeReference(nameAndTypes[i].type)), + nameAndTypes[i].typeConfidence)); + } + + BNFreeDebugTypes(nameAndTypes, count); + return result; +} + + +vector<DebugFunctionInfo> DebugInfo::GetFunctions(const string& parserName) +{ + size_t count; + BNDebugFunctionInfo* functions = BNGetDebugFunctions(m_object, parserName.size() == 0 ? nullptr : parserName.c_str(), &count); + + vector<DebugFunctionInfo> result; + for (size_t i = 0; i < count; ++i) + { + vector<tuple<string, Ref<Type>>> parameters; + for (size_t j = 0; j < functions[i].parameterCount; ++j) + parameters.emplace_back(functions[i].parameterNames[j], new Type(BNNewTypeReference(functions[i].parameterTypes[j]))); + + 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].returnType ? new Type(BNNewTypeReference(functions[i].returnType)) : nullptr, + parameters, + functions[i].variableParameters, + functions[i].callingConvention ? new CoreCallingConvention(BNNewCallingConventionReference(functions[i].callingConvention)) : nullptr, + functions[i].platform ? new Platform(BNNewPlatformReference(functions[i].platform)) : nullptr); + } + + BNFreeDebugFunctions(functions, count); + return result; +} + + +vector<DataVariableAndName> DebugInfo::GetDataVariables(const string& parserName) +{ + size_t count; + BNDataVariableAndName* variableAndNames = BNGetDebugDataVariables(m_object, parserName.size() == 0 ? nullptr : parserName.c_str(), &count); + + vector<DataVariableAndName> result; + for (size_t i = 0; i < count; ++i) + { + result.emplace_back(variableAndNames[i].address, + Confidence(new Type(BNNewTypeReference(variableAndNames[i].type)), variableAndNames[i].typeConfidence), + variableAndNames[i].autoDiscovered, + variableAndNames[i].name); + } + + BNFreeDataVariablesAndName(variableAndNames, count); + return result; +} + + +bool DebugInfo::AddType(const string& name, Ref<Type> type) +{ + return BNAddDebugType(m_object, name.c_str(), type->GetObject()); +} + + +bool DebugInfo::AddFunction(const DebugFunctionInfo& function) +{ + BNDebugFunctionInfo* input = new BNDebugFunctionInfo(); + + input->shortName = function.shortName.size() ? BNAllocString(function.shortName.c_str()) : nullptr; + input->fullName = function.fullName.size() ? BNAllocString(function.fullName.c_str()) : nullptr; + input->rawName = function.rawName.size() ? BNAllocString(function.rawName.c_str()) : nullptr; + input->address = function.address; + input->returnType = function.returnType ? function.returnType->GetObject() : nullptr; + input->variableParameters = function.variableParameters; + input->callingConvention = function.callingConvention ? function.callingConvention->GetObject() : nullptr; + input->platform = function.platform ? function.platform->GetObject() : nullptr; + + size_t parameterCount = function.parameters.size(); + input->parameterCount = parameterCount; + input->parameterNames = new char*[parameterCount]; + input->parameterTypes = new BNType*[parameterCount]; + for (size_t i = 0; i < parameterCount; ++i) + { + input->parameterNames[i] = BNAllocString(std::get<0>(function.parameters[i]).c_str()); + input->parameterTypes[i] = std::get<1>(function.parameters[i])->GetObject(); + } + + bool result = BNAddDebugFunction(m_object, input); + + BNFreeString(input->shortName); + BNFreeString(input->fullName); + BNFreeString(input->rawName); + + for (size_t i = 0; i < parameterCount; ++i) + BNFreeString(input->parameterNames[i]); + + return result; +} + + +bool DebugInfo::AddDataVariable(uint64_t address, Ref<Type> type, const string& name) +{ + if (name.size() == 0) + return BNAddDebugDataVariable(m_object, address, type->GetObject(), nullptr); + return BNAddDebugDataVariable(m_object, address, type->GetObject(), name.c_str()); +} + + +///////////////////// +// DebugInfoParser // +///////////////////// + + +DebugInfoParser::DebugInfoParser(BNDebugInfoParser* parser) +{ + m_object = parser; +} + + +Ref<DebugInfoParser> DebugInfoParser::GetByName(const string& name) +{ + BNDebugInfoParser* parser = BNGetDebugInfoParserByName(name.c_str()); + if (parser) + return new DebugInfoParser(BNNewDebugInfoParserReference(parser)); + return nullptr; +} + + +vector<Ref<DebugInfoParser>> DebugInfoParser::GetList() +{ + size_t count = 0; + BNDebugInfoParser** parsers = BNGetDebugInfoParsers(&count); + + vector<Ref<DebugInfoParser>> result; + for (size_t i = 0; i < count; ++i) + { + result.emplace_back(new DebugInfoParser(BNNewDebugInfoParserReference(parsers[i]))); + } + + BNFreeDebugInfoParserList(parsers, count); + return result; +} + + +vector<Ref<DebugInfoParser>> DebugInfoParser::GetListForView(const Ref<BinaryView> data) +{ + size_t count = 0; + BNDebugInfoParser** parsers = BNGetDebugInfoParsersForView(data->GetObject(), &count); + + vector<Ref<DebugInfoParser>> result; + for (size_t i = 0; i < count; ++i) + { + result.emplace_back(new DebugInfoParser(BNNewDebugInfoParserReference(parsers[i]))); + } + + BNFreeDebugInfoParserList(parsers, count); + return result; +} + + +string DebugInfoParser::GetName() const +{ + return BNGetDebugInfoParserName(m_object); +} + + +Ref<DebugInfo> DebugInfoParser::Parse(Ref<BinaryView> view, Ref<DebugInfo> existingDebugInfo) const +{ + if (existingDebugInfo) + return new DebugInfo(BNNewDebugInfoReference(BNParseDebugInfo(m_object, view->GetObject(), existingDebugInfo->GetObject()))); + return new DebugInfo(BNParseDebugInfo(m_object, view->GetObject(), nullptr)); +} + + +bool DebugInfoParser::IsValidForView(const Ref<BinaryView> view) const +{ + return BNIsDebugInfoParserValidForView(m_object, view->GetObject()); +} + + +////////////////////////////// +// Plugin registration APIs // +////////////////////////////// + + +bool CustomDebugInfoParser::IsValidCallback(void* ctxt, BNBinaryView* view) +{ + CustomDebugInfoParser* parser = (CustomDebugInfoParser*)ctxt; + return parser->IsValid(new BinaryView(view)); +} + + +void CustomDebugInfoParser::ParseCallback(void* ctxt, BNDebugInfo* debugInfo, BNBinaryView* view) +{ + CustomDebugInfoParser* parser = (CustomDebugInfoParser*)ctxt; + parser->ParseInfo(new DebugInfo(debugInfo), new BinaryView(view)); +} + + +CustomDebugInfoParser::CustomDebugInfoParser(const string& name) : + DebugInfoParser(BNNewDebugInfoParserReference(BNRegisterDebugInfoParser(name.c_str(), IsValidCallback, ParseCallback, this))) {} diff --git a/python/__init__.py b/python/__init__.py index 1b199c9a..4f8a373f 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -36,6 +36,7 @@ from binaryninja.databuffer import * from binaryninja.filemetadata import * from binaryninja.fileaccessor import * from binaryninja.binaryview import * +from binaryninja.debuginfo import * from binaryninja.transform import * from binaryninja.architecture import * from binaryninja.basicblock import * diff --git a/python/binaryview.py b/python/binaryview.py index 30b763a0..ae88b4c0 100644 --- a/python/binaryview.py +++ b/python/binaryview.py @@ -28,6 +28,7 @@ import abc import numbers import json import inspect +from typing import Union from collections import defaultdict, OrderedDict @@ -500,6 +501,15 @@ class DataVariable(object): self._view = value +class DataVariableAndName(DataVariable): + def __init__(self, addr: int, var_type: types.Type, var_name: str, auto_discovered: bool, view: "BinaryView" = None) -> None: + super(DataVariableAndName, self).__init__(addr, var_type, auto_discovered, view) + self.name = var_name + + def __repr__(self) -> str: + return "<var 0x%x: %s %s>" % (self.address, str(self.type), self.name) + + class BinaryDataNotificationCallbacks(object): def __init__(self, view, notify): self._view = view @@ -1483,7 +1493,6 @@ class BinaryView(object): finally: core.BNFreeFunctionList(funcs, count.value) - def __getitem__(self, i): if isinstance(i, tuple): result = bytes() @@ -6251,6 +6260,24 @@ class BinaryView(object): """ core.BNSetGlobalCommentForAddress(self.handle, addr, comment) + @property + def debug_info(self) -> "binaryninja.debuginfo.DebugInfo": + """The current debug info object for this binary view""" + return binaryninja.debuginfo.DebugInfo(core.BNNewDebugInfoReference(core.BNGetDebugInfo(self.handle))) + + @debug_info.setter + def debug_info(self, value: "binaryninja.debuginfo.DebugInfo") -> Union[None, 'NotImplemented']: + """Sets the debug info for the current binary view""" + if not isinstance(value, binaryninja.debuginfo.DebugInfo): + return NotImplemented + core.BNSetDebugInfo(self.handle, value.handle) + + def apply_debug_info(self, value: "binaryninja.debuginfo.DebugInfo") -> Union[None, 'NotImplemented']: + """Sets the debug info and applies its contents to the current binary view""" + if not isinstance(value, binaryninja.debuginfo.DebugInfo): + return NotImplemented + core.BNApplyDebugInfo(self.handle, value.handle) + def query_metadata(self, key): """ `query_metadata` retrieves a metadata associated with the given key stored in the current BinaryView. diff --git a/python/debuginfo.py b/python/debuginfo.py new file mode 100644 index 00000000..15ac4efb --- /dev/null +++ b/python/debuginfo.py @@ -0,0 +1,426 @@ +# Copyright (c) 2015-2021 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 ctypes +from typing import Optional, List, Iterator, Callable, Tuple +import traceback + +# Binary Ninja components +import binaryninja +from binaryninja import _binaryninjacore as core +from binaryninja import callingconvention +from binaryninja import platform +from binaryninja import types +from binaryninja import log + + +_debug_info_parsers = {} + + +class _DebugInfoParserMetaClass(type): + @property + def list(self) -> List["DebugInfoParser"]: + """List all debug-info parsers (read-only)""" + binaryninja._init_plugins() + count = ctypes.c_ulonglong() + parsers = core.BNGetDebugInfoParsers(count) + result = [] + for i in range(0, count.value): + result.append(DebugInfoParser(core.BNNewDebugInfoParserReference(parsers[i]))) + core.BNFreeDebugInfoParserList(parsers, count.value) + return result + + def __iter__(self) -> Iterator["DebugInfoParser"]: + """Generator of all debug-info parsers""" + binaryninja._init_plugins() + count = ctypes.c_ulonglong() + parsers = core.BNGetDebugInfoParsers(count) + try: + for i in range(0, count.value): + yield DebugInfoParser(core.BNNewDebugInfoParserReference(parsers[i])) + finally: + core.BNFreeDebugInfoParserList(parsers, count.value) + + def __getitem__(cls, value: str) -> "DebugInfoParser": + """Returns debug info parser of the given name, if it exists""" + binaryninja._init_plugins() + parser = core.BNGetDebugInfoParserByName(str(value)) + if parser is None: + raise KeyError(f"'{str(value)}' is not a valid debug-info parser") + return DebugInfoParser(core.BNNewDebugInfoParserReference(parser)) + + @staticmethod + def get_parsers_for_view(view: binaryninja.binaryview.BinaryView) -> List["DebugInfoParser"]: + """Returns a list of debug-info parsers that are valid for the provided binary view""" + binaryninja._init_plugins() + + count = ctypes.c_ulonglong() + parsers = core.BNGetDebugInfoParsersForView(view.handle, count) + result = [] + for i in range(0, count.value): + result.append(DebugInfoParser(core.BNNewDebugInfoParserReference(parsers[i]))) + core.BNFreeDebugInfoParserList(parsers, count.value) + return result + + @staticmethod + def _is_valid(view: core.BNBinaryView, callback: Callable[[binaryninja.binaryview.BinaryView], bool]) -> bool: + try: + file_metadata = binaryninja.filemetadata.FileMetadata(handle = core.BNGetFileForView(view)) + view_obj = binaryninja.binaryview.BinaryView(file_metadata = file_metadata, handle = core.BNNewViewReference(view)) + return callback(view_obj) + except: + log.log_error(traceback.format_exc()) + + @staticmethod + def _parse_info(debug_info: core.BNDebugInfo, view: core.BNBinaryView, callback: Callable[["DebugInfo", binaryninja.binaryview.BinaryView], None]) -> None: + try: + file_metadata = binaryninja.filemetadata.FileMetadata(handle = core.BNGetFileForView(view)) + view_obj = binaryninja.binaryview.BinaryView(file_metadata = file_metadata, handle = core.BNNewViewReference(view)) + callback(DebugInfo(core.BNNewDebugInfoReference(debug_info)), view_obj) + except: + log.log_error(traceback.format_exc()) + + @classmethod + def register(cls, name: str, is_valid: Callable[[binaryninja.binaryview.BinaryView], bool], parse_info: Callable[["DebugInfo", binaryninja.binaryview.BinaryView], None]) -> "DebugInfoParser": + """Registers a DebugInfoParser. See ``binaryninja.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)) + parse_info_cb = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.POINTER(core.BNDebugInfo), ctypes.POINTER(core.BNBinaryView))(lambda ctxt, debug_info, view: cls._parse_info(debug_info, view, parse_info)) + + # Don't let our callbacks get garbage collected + global _debug_info_parsers + _debug_info_parsers[len(_debug_info_parsers)] = (is_valid_cb, parse_info_cb) + + return DebugInfoParser(core.BNNewDebugInfoParserReference(core.BNRegisterDebugInfoParser(name, is_valid_cb, parse_info_cb, None))) + + +class DebugInfoParser(object, metaclass=_DebugInfoParserMetaClass): + """ + ``DebugInfoParser``s represent the registered parsers and providers of debug information to 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 + debug info, Binary Ninja's output can be generated quicker, more accurately, and more completely. + + A DebugInfoParser consists of: + 1. A name + 2. An ``is_valid`` function which takes a BV and returns a bool + 3. A ``parse`` function which takes a ``DebugInfo`` object and uses the member functions ``add_type``, ``add_function``, and ``add_data_variable`` to populate all the info it can. + And finally calling ``bn.debuginfo.DebugInfoParser.register`` to register it with the core. + + Here's a minimal, complete example boilerplate-plugin: + ``` + import binaryninja as bn + + 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: + 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), []) + debug_info.add_function(function_info) + + bn.debuginfo.DebugInfoParser.register("debug info parser", is_valid, parse_info) + ``` + + ``DebugInfo`` can then be automatically applied to valid binary views (via the "Parse and Apply Debug Info" setting), or manually fetched/applied as bellow: + ``` + valid_parsers = bn.debuginfo.DebugInfoParser.get_parsers_for_view(bv) + parser = valid_parsers[0] + debug_info = parser.parse_debug_info(bv) + 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 + ``DebugInfo`` object just returned. This is automatic when opening a binary view with multiple valid debug info parsers. If you + wish to set the debug info for a binary view without applying it as well, you can call ``binaryninja.binaryview.BinaryView.set_debug_info``. + """ + def __init__(self, handle: core.BNDebugInfoParser) -> None: + self.handle = core.handle_of_type(handle, core.BNDebugInfoParser) + + def __del__(self) -> None: + core.BNFreeDebugInfoParserReference(self.handle) + + def __repr__(self) -> str: + return f"<debug-info parser: '{self.name}'>" + + def __eq__(self, other: "DebugInfoParser") -> bool: + if not isinstance(other, self.__class__): + return NotImplemented + return ctypes.addressof(self.handle.contents) == ctypes.addressof(other.handle.contents) + + def __ne__(self, other: "DebugInfoParser") -> bool: + if not isinstance(other, self.__class__): + return NotImplemented + return not (self == other) + + def __hash__(self) -> int: + return hash(ctypes.addressof(self.handle.contents)) + + @property + def name(self) -> str: + """Debug-info parser's name (read-only)""" + return core.BNGetDebugInfoParserName(self.handle) + + def is_valid_for_view(self, view: binaryninja.binaryview.BinaryView) -> bool: + """Returns whether this debug-info parser is valid for the provided binary view""" + return core.BNIsDebugInfoParserValidForView(self.handle, view.handle) + + def parse_debug_info(self, view: binaryninja.binaryview.BinaryView, debug_info: Optional["DebugInfo"] = None) -> "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""" + if isinstance(debug_info, DebugInfo): + return DebugInfo(core.BNNewDebugInfoReference(core.BNParseDebugInfo(self.handle, view.handle, debug_info.handle))) + else: + return DebugInfo(core.BNParseDebugInfo(self.handle, view.handle, None)) + + +class DebugFunctionInfo(object): + """ + ``DebugFunctionInfo`` collates ground-truth function-external attributes for use in BinaryNinja's internal analysis. + + 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. + """ + def __init__(self, address: Optional[int] = 0, short_name: Optional[str] = None, full_name: Optional[str] = None, raw_name: Optional[str] = None, return_type: Optional[types.Type] = None, parameters: Optional[List[Tuple[str, types.Type]]] = None, variable_parameters: Optional[bool] = False, calling_convention: Optional[callingconvention.CallingConvention] = None, platform: Optional[platform.Platform] = None) -> None: + self._short_name = short_name + self._full_name = full_name + self._raw_name = raw_name + self._address = address + self._return_type = return_type + self._parameters = parameters + self._variable_parameters = variable_parameters + self._calling_convention = calling_convention + self._platform = platform + + def __repr__(self) -> str: + suffix = f"@{hex(self._address)}>" if self._address != 0 else ">" + if self._short_name is not None: + return f"<debug-info function: {self._short_name}{suffix}" + elif self._full_name is not None: + return f"<debug-info function: {self._full_name}{suffix}" + elif self._raw_name is not None: + return f"<debug-info function: {self._raw_name}{suffix}" + else: + return f"<debug-info function{suffix}" + + @property + def short_name(self) -> Optional[str]: + return self._short_name + + @property + def full_name(self) -> Optional[str]: + return self._full_name + + @property + def raw_name(self) -> Optional[str]: + return self._raw_name + + @property + def address(self) -> Optional[int]: + return self._address + + @property + def return_type(self) -> Optional[int]: + return self._return_type + + @property + def parameters(self) -> Optional[List[Tuple[str, types.Type]]]: + return self._parameters + + @property + def variable_parameters(self) -> Optional[bool]: + return self._variable_parameters + + @property + def calling_convention(self) -> Optional[callingconvention.CallingConvention]: + return self._calling_convention + + @property + def platform(self) -> Optional[platform.Platform]: + return self._platform + + +class DebugInfo(object): + """ + ``class DebugInfo`` provides an interface to both provide and query debug info. The DebugInfo object is used + internally by the binary view to which it is applied to determine the attributes of functions, types, and variables + that would otherwise be costly to deduce. + + DebugInfo objects themselves are independent of binary views; their data can be sourced from any arbitrary binary + views and be applied to any other arbitrary binary view. A DebugInfo object can also contain debug info from multiple + 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 ``binaryninja.binaryview.BinaryView.debug_info``) + or a debug-info parser (see ``binaryninja.debuginfo.DebugInfoParser.parse_debug_info``). + + .. note:: Please note that calling one of ``add_*`` functions will not work outside of a debuginfo plugin. + """ + def __init__(self, handle: core.BNDebugInfo) -> None: + self.handle = core.handle_of_type(handle, core.BNDebugInfo) + + def __del__(self) -> None: + core.BNFreeDebugInfoReference(self.handle) + + def types_from_parser(self, name: Optional[str] = None) -> Iterator[Tuple[str, types.Type]]: + """Returns a generator of all types provided by a named DebugInfoParser""" + count = ctypes.c_ulonglong(0) + name_and_types = core.BNGetDebugTypes(self.handle, name, count) + try: + for i in range(0, count.value): + yield (name_and_types[i].name, types.Type(core.BNNewTypeReference(name_and_types[i].type))) + finally: + core.BNFreeDebugTypes(name_and_types, count.value) + + @property + def types(self) -> Iterator[Tuple[str, types.Type]]: + """A generator of all types provided by DebugInfoParsers""" + return self.types_from_parser() + + def functions_from_parser(self, name: Optional[str] = None) -> Iterator[DebugFunctionInfo]: + """Returns a generator of all functions provided by a named DebugInfoParser""" + count = ctypes.c_ulonglong(0) + functions = core.BNGetDebugFunctions(self.handle, name, count) + try: + for i in range(0, count.value): + + parameters: List[Tuple[str, binaryninja.types.Type]] = [] + for j in range(functions[i].parameterCount): + parameters.append((functions[i].parameterNames[j], binaryninja.types.Type(core.BNNewTypeReference(functions[i].parameterTypes[j])))) + + if functions[i].returnType: + return_type = binaryninja.types.Type(core.BNNewTypeReference(functions[i].returnType)) + else: + return_type = None + + if functions[i].callingConvention: + calling_convention = callingconvention.CallingConvention(handle=core.BNNewCallingConventionReference(functions[i].callingConvention)) + else: + calling_convention = None + + if functions[i].platform: + func_platform = platform.Platform(handle=core.BNNewPlatformReference(functions[i].platform)) + else: + func_platform = None + + yield DebugFunctionInfo( + functions[i].address, + functions[i].shortName, + functions[i].fullName, + functions[i].rawName, + return_type, + parameters, + functions[i].variableParameters, + calling_convention, + func_platform + ) + finally: + core.BNFreeDebugFunctions(functions, count.value) + + @property + def functions(self) -> Iterator[DebugFunctionInfo]: + """A generator of all functions provided by DebugInfoParsers""" + return self.functions_from_parser() + + def data_variables_from_parser(self, name: Optional[str] = None) -> Iterator[binaryninja.binaryview.DataVariableAndName]: + """Returns a generator of all data variables provided by a named DebugInfoParser""" + count = ctypes.c_ulonglong(0) + data_variables = core.BNGetDebugDataVariables(self.handle, name, count) + try: + for i in range(0, count.value): + yield binaryninja.binaryview.DataVariableAndName( + data_variables[i].address, + binaryninja.types.Type(core.BNNewTypeReference(data_variables[i].type)), + data_variables[i].name, + data_variables[i].autoDiscovered, + data_variables[i].typeConfidence + ) + finally: + core.BNFreeDataVariablesAndName(data_variables, count.value) + + @property + def data_variables(self) -> Iterator[binaryninja.binaryview.DataVariableAndName]: + """A generator of all data variables provided by DebugInfoParsers""" + return self.data_variables_from_parser() + + def add_type(self, name: str, new_type: binaryninja.types.Type) -> bool: + """Adds a type scoped under the current parser's name to the debug info""" + if isinstance(new_type, binaryninja.types.Type): + return core.BNAddDebugType(self.handle, name, new_type.handle) + return NotImplemented + + def add_function(self, new_func: DebugFunctionInfo) -> bool: + """Adds a function scoped under the current parser's name to the debug info""" + if not isinstance(new_func, DebugFunctionInfo): + return NotImplemented + + parameter_count = 0 + if new_func.parameters is not None: + parameter_count = len(new_func.parameters) + + func_info = core.BNDebugFunctionInfo() + + if new_func.return_type is None: + func_info.returnType = None + elif isinstance(new_func.return_type, binaryninja.types.Type): + func_info.returnType = new_func.return_type.handle + else: + return NotImplemented + + if new_func.calling_convention is None: + func_info.callingConvention = None + elif isinstance(new_func.calling_convention, callingconvention.CallingConvention): + func_info.callingConvention = new_func.calling_convention.handle + else: + return NotImplemented + + if new_func.platform is None: + func_info.platform = None + elif isinstance(new_func.platform, platform.Platform): + func_info.platform = new_func.platform.handle + else: + return NotImplemented + + func_info.shortName = new_func.short_name + func_info.fullName = new_func.full_name + func_info.rawName = new_func.raw_name + func_info.address = new_func.address + func_info.variableParameters = new_func.variable_parameters + + if parameter_count == 0: + func_info.parameterNames = None + func_info.parameterTypes = None + func_info.parameterCount = parameter_count + else: + func_info.parameterNames = (ctypes.c_char_p * parameter_count)(*map(lambda pair: binaryninja.cstr(pair[0]), new_func.parameters)) + func_info.parameterTypes = (ctypes.POINTER(core.BNType) * parameter_count)(*map(lambda pair: pair[1].handle, new_func.parameters)) + func_info.parameterCount = parameter_count + + return core.BNAddDebugFunction(self.handle, func_info) + + def add_data_variable(self, address: int, new_type: binaryninja.types.Type, name: Optional[str] = None) -> bool: + """Adds a data variable scoped under the current parser's name to the debug info""" + if isinstance(address, int) and isinstance(new_type, binaryninja.types.Type): + return core.BNAddDebugDataVariable(self.handle, address, new_type.handle, name) + return NotImplemented diff --git a/python/examples/debug_info.py b/python/examples/debug_info.py new file mode 100755 index 00000000..8f379544 --- /dev/null +++ b/python/examples/debug_info.py @@ -0,0 +1,269 @@ +#!/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.open_view(filename, options={"analysis.experimental.parseDebugInfo": 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 differnew file mode 100755 index 00000000..8a650411 --- /dev/null +++ b/python/examples/test_debug_info diff --git a/rust/src/binaryview.rs b/rust/src/binaryview.rs index 21239fc2..98e7a7a2 100644 --- a/rust/src/binaryview.rs +++ b/rust/src/binaryview.rs @@ -24,6 +24,7 @@ use crate::architecture::Architecture; use crate::architecture::CoreArchitecture; use crate::basicblock::BasicBlock; use crate::databuffer::DataBuffer; +use crate::debuginfo::DebugInfo; use crate::fileaccessor::FileAccessor; use crate::filemetadata::FileMetadata; use crate::flowgraph::FlowGraph; @@ -607,6 +608,18 @@ pub trait BinaryViewExt: BinaryViewBase { } } + fn debug_info(&self) -> Ref<DebugInfo> { + unsafe { DebugInfo::from_raw(BNGetDebugInfo(self.as_ref().handle)) } + } + + fn set_debug_info(&self, debug_info: &DebugInfo) { + unsafe { BNSetDebugInfo(self.as_ref().handle, debug_info.handle) } + } + + fn apply_debug_info(&self, debug_info: &DebugInfo) { + unsafe { BNApplyDebugInfo(self.as_ref().handle, debug_info.handle) } + } + fn show_graph_report<S: BnStrCompatible>(&self, raw_name: S, graph: FlowGraph) { let raw_name = raw_name.as_bytes_with_nul(); unsafe { diff --git a/rust/src/debuginfo.rs b/rust/src/debuginfo.rs new file mode 100644 index 00000000..a22450d5 --- /dev/null +++ b/rust/src/debuginfo.rs @@ -0,0 +1,657 @@ +// Copyright 2021 Vector 35 Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// TODO : These docs are here, but could afford to be cleaned up + +//! Parsers and providers of debug information to 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 +//! debug info, Binary Ninja's output can be generated quicker, more accurately, and more completely. +//! +//! A DebugInfoParser consists of: +//! 1. A name +//! 2. An `is_valid` function which takes a BV and returns a bool +//! 3. A `parse` function which takes a `DebugInfo` object and uses the member functions `add_type`, `add_function`, and `add_data_variable` to populate all the info it can. +//! And finally calling `binaryninja::debuginfo::DebugInfoParser::register` to register it with the core. +//! +//! Here's a minimal, complete example boilerplate-plugin: +//! ``` +//! use binaryninja::{ +//! binaryview::BinaryView, +//! debuginfo::{CustomDebugInfoParser, DebugInfo, DebugInfoParser}, +//! }; +//! +//! struct ExampleDebugInfoParser; +//! +//! impl CustomDebugInfoParser for ExampleDebugInfoParser { +//! fn is_valid(&self, _view: &BinaryView) -> bool { +//! true +//! } +//! +//! fn parse_info(&self, _debug_info: &mut DebugInfo, _view: &BinaryView) { +//! println!("Parsing info"); +//! } +//! } +//! +//! #[no_mangle] +//! pub extern "C" fn CorePluginInit() -> bool { +//! DebugInfoParser::register("example debug info parser", ExampleDebugInfoParser {}); +//! true +//! } +//! ``` +//! +//! `DebugInfo` can then be automatically applied to valid binary views (via the "Parse and Apply Debug Info" setting), or manually fetched/applied as bellow: +//! ``` +//! let valid_parsers = DebugInfoParser::parsers_for_view(bv); +//! let parser = valid_parsers[0]; +//! let debug_info = parser.parse_debug_info(bv); +//! 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 +//! `DebugInfo` object just returned. This is automatic when opening a binary view with multiple valid debug info parsers. If you +//! wish to set the debug info for a binary view without applying it as well, you can call `binaryninja::binaryview::BinaryView::set_debug_info`. + +use binaryninjacore_sys::*; + +use crate::{ + architecture::{Architecture, CoreArchitecture}, + binaryview::BinaryView, + callingconvention::CallingConvention, + platform::Platform, + rc::*, + string::{raw_to_string, BnStrCompatible, BnString}, + types::{DataVariableAndName, NameAndType, Type}, +}; + +use std::{ + hash::Hash, + mem, + os::raw::{c_char, c_void}, + ptr, slice, +}; + +////////////////////// +// DebugInfoParser + +/// Represents the registered parsers and providers of debug information to Binary Ninja. +/// See `binaryninja::debuginfo` for more information +#[derive(PartialEq, Eq, Hash)] +pub struct DebugInfoParser { + pub(crate) handle: *mut BNDebugInfoParser, +} + +impl DebugInfoParser { + pub(crate) unsafe fn from_raw(handle: *mut BNDebugInfoParser) -> Ref<Self> { + debug_assert!(!handle.is_null()); + + Ref::new(Self { handle }) + } + + /// Returns debug info parser of the given name, if it exists + pub fn from_name<S: BnStrCompatible>(name: S) -> Result<Ref<Self>, ()> { + let name = name.as_bytes_with_nul(); + let parser = unsafe { BNGetDebugInfoParserByName(name.as_ref().as_ptr() as *mut _) }; + + if parser.is_null() { + Err(()) + } else { + unsafe { Ok(Self::from_raw(parser)) } + } + } + + /// List all debug-info parsers + pub fn list() -> Array<DebugInfoParser> { + let mut count: usize = unsafe { mem::zeroed() }; + let raw_parsers = unsafe { BNGetDebugInfoParsers(&mut count as *mut _) }; + unsafe { Array::new(raw_parsers, count, ()) } + } + + /// Returns a list of debug-info parsers that are valid for the provided binary view + pub fn parsers_for_view(bv: &BinaryView) -> Array<DebugInfoParser> { + let mut count: usize = unsafe { mem::zeroed() }; + let raw_parsers = unsafe { BNGetDebugInfoParsersForView(bv.handle, &mut count as *mut _) }; + unsafe { Array::new(raw_parsers, count, ()) } + } + + /// Returns the name of the current parser + pub fn name(&self) -> BnString { + unsafe { BnString::from_raw(BNGetDebugInfoParserName(self.handle)) } + } + + /// Returns whether this debug-info parser is valid for the provided binary view + pub fn is_valid_for_view(&self, view: &BinaryView) -> bool { + unsafe { BNIsDebugInfoParserValidForView(self.handle, view.handle) } + } + + /// 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 + pub fn parse_debug_info( + &self, + view: &BinaryView, + existing_debug_info: Option<&DebugInfo>, + ) -> Ref<DebugInfo> { + match existing_debug_info { + Some(debug_info) => unsafe { + DebugInfo::from_raw(BNParseDebugInfo( + self.handle, + view.handle, + debug_info.handle, + )) + }, + None => unsafe { + DebugInfo::from_raw(BNParseDebugInfo(self.handle, view.handle, ptr::null_mut())) + }, + } + } + + // Registers a DebugInfoParser. See `binaryninja::debuginfo::DebugInfoParser` for more details. + pub fn register<S, C>(name: S, parser_callbacks: C) -> Ref<Self> + where + S: BnStrCompatible, + C: CustomDebugInfoParser, + { + extern "C" fn cb_is_valid<C>(ctxt: *mut c_void, view: *mut BNBinaryView) -> bool + where + C: CustomDebugInfoParser, + { + ffi_wrap!("CustomDebugInfoParser::is_valid", unsafe { + let cmd = &*(ctxt as *const C); + let view = BinaryView::from_raw(view); + + cmd.is_valid(&view) + }) + } + + extern "C" fn cb_parse_info<C>( + ctxt: *mut c_void, + debug_info: *mut BNDebugInfo, + view: *mut BNBinaryView, + ) where + C: CustomDebugInfoParser, + { + ffi_wrap!("CustomDebugInfoParser::parse_info", unsafe { + let cmd = &*(ctxt as *const C); + let view = BinaryView::from_raw(view); + let mut debug_info = DebugInfo::from_raw(debug_info); + + cmd.parse_info(&mut debug_info, &view); + }) + } + + let name = name.as_bytes_with_nul(); + let name_ptr = name.as_ref().as_ptr() as *mut _; + let ctxt = Box::into_raw(Box::new(parser_callbacks)); + + unsafe { + DebugInfoParser::from_raw(BNRegisterDebugInfoParser( + name_ptr, + Some(cb_is_valid::<C>), + Some(cb_parse_info::<C>), + ctxt as *mut _, + )) + } + } +} + +unsafe impl RefCountable for DebugInfoParser { + unsafe fn inc_ref(handle: &Self) -> Ref<Self> { + Ref::new(Self { + handle: BNNewDebugInfoParserReference(handle.handle), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeDebugInfoParserReference(handle.handle); + } +} + +impl AsRef<DebugInfoParser> for DebugInfoParser { + fn as_ref(&self) -> &Self { + self + } +} + +impl ToOwned for DebugInfoParser { + type Owned = Ref<Self>; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl CoreOwnedArrayProvider for DebugInfoParser { + type Raw = *mut BNDebugInfoParser; + type Context = (); + + unsafe fn free(raw: *mut Self::Raw, count: usize, _: &Self::Context) { + BNFreeDebugInfoParserList(raw, count); + } +} + +/////////////////////// +// DebugFunctionInfo + +/// Collates ground-truth function-external attributes for use in BinaryNinja's internal analysis. +/// +/// 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<A: Architecture, S1: BnStrCompatible, S2: BnStrCompatible> { + short_name: Option<S1>, + full_name: Option<S1>, + raw_name: Option<S1>, + return_type: Option<Ref<Type>>, + address: u64, + parameters: Vec<(S2, Ref<Type>)>, + variable_parameters: bool, + calling_convention: Option<Ref<CallingConvention<A>>>, + platform: Option<Ref<Platform>>, +} + +impl From<&BNDebugFunctionInfo> for DebugFunctionInfo<CoreArchitecture, String, String> { + fn from(raw: &BNDebugFunctionInfo) -> Self { + let raw_parameter_names: &[*mut ::std::os::raw::c_char] = + unsafe { slice::from_raw_parts(raw.parameterNames as *mut _, raw.parameterCount) }; + let raw_parameter_types: &[*mut BNType] = + unsafe { slice::from_raw_parts(raw.parameterTypes as *mut _, raw.parameterCount) }; + + let parameters: Vec<(String, Ref<Type>)> = (0..raw.parameterCount) + .map(|i| { + (raw_to_string(raw_parameter_names[i]).unwrap(), unsafe { + Type::ref_from_raw(raw_parameter_types[i]) + }) + }) + .collect(); + + Self { + short_name: raw_to_string(raw.shortName), + full_name: raw_to_string(raw.fullName), + raw_name: raw_to_string(raw.rawName), + return_type: if raw.returnType.is_null() { + None + } else { + Some(unsafe { Type::ref_from_raw(raw.returnType) }) + }, + address: raw.address, + parameters, + variable_parameters: raw.variableParameters, + calling_convention: if raw.callingConvention.is_null() { + None + } else { + Some(unsafe { + CallingConvention::ref_from_raw( + raw.callingConvention, + CoreArchitecture::from_raw(BNGetCallingConventionArchitecture( + raw.callingConvention, + )), + ) + }) + }, + platform: if raw.returnType.is_null() { + None + } else { + Some(unsafe { Platform::ref_from_raw(raw.platform) }) + }, + } + } +} + +impl<A: Architecture, S1: BnStrCompatible, S2: BnStrCompatible> Into<BNDebugFunctionInfo> + for DebugFunctionInfo<A, S1, S2> +{ + fn into(self) -> BNDebugFunctionInfo { + let parameter_count: usize = self.parameters.len(); + + let (short_name, _short_name_ref) = match self.short_name { + Some(name) => { + let temp = Box::new(name.as_bytes_with_nul()); + ((*temp).as_ref().as_ptr() as *mut _, Some(temp)) + } + _ => (ptr::null_mut() as *mut _, None), + }; + let (full_name, _full_name_ref) = match self.full_name { + Some(name) => { + let temp = Box::new(name.as_bytes_with_nul()); + ((*temp).as_ref().as_ptr() as *mut _, Some(temp)) + } + _ => (ptr::null_mut() as *mut _, None), + }; + let (raw_name, _raw_name_ref) = match self.raw_name { + Some(name) => { + let temp = Box::new(name.as_bytes_with_nul()); + ((*temp).as_ref().as_ptr() as *mut _, Some(temp)) + } + _ => (ptr::null_mut() as *mut _, None), + }; + + let (_parameter_name_bytes, mut parameter_names, mut parameter_types): ( + Vec<S2::Result>, + Vec<*mut c_char>, + Vec<*mut BNType>, + ) = self.parameters.into_iter().fold( + ( + Vec::with_capacity(parameter_count), + Vec::with_capacity(parameter_count), + Vec::with_capacity(parameter_count), + ), + |(mut parameter_name_bytes, mut parameter_names, mut parameter_types), (n, t)| { + parameter_name_bytes.push(n.as_bytes_with_nul()); + parameter_names + .push(parameter_name_bytes.last().unwrap().as_ref().as_ptr() as *mut c_char); + parameter_types.push(t.handle); + (parameter_name_bytes, parameter_names, parameter_types) + }, + ); + + BNDebugFunctionInfo { + shortName: short_name, + fullName: full_name, + rawName: raw_name, + address: self.address, + returnType: match self.return_type { + Some(return_type) => return_type.handle, + _ => ptr::null_mut(), + }, + parameterNames: match parameter_count { + 0 => ptr::null_mut(), + _ => parameter_names.as_mut_ptr(), + }, + parameterTypes: match parameter_count { + 0 => ptr::null_mut(), + _ => parameter_types.as_mut_ptr(), + }, + parameterCount: parameter_count, + variableParameters: self.variable_parameters, + callingConvention: match self.calling_convention { + Some(calling_convention) => calling_convention.handle, + _ => ptr::null_mut(), + }, + platform: match self.platform { + Some(platform) => platform.handle, + _ => ptr::null_mut(), + }, + } + } +} + +impl<A: Architecture, S1: BnStrCompatible, S2: BnStrCompatible> DebugFunctionInfo<A, S1, S2> { + pub fn new( + short_name: Option<S1>, + full_name: Option<S1>, + raw_name: Option<S1>, + return_type: Option<Ref<Type>>, + address: Option<u64>, + parameters: Option<Vec<(S2, Ref<Type>)>>, + variable_parameters: Option<bool>, + calling_convention: Option<Ref<CallingConvention<A>>>, + platform: Option<Ref<Platform>>, + ) -> Self { + Self { + short_name, + full_name, + raw_name, + return_type, + address: match address { + Some(address) => address, + _ => 0, + }, + parameters: match parameters { + Some(parameters) => parameters, + _ => vec![], + }, + variable_parameters: match variable_parameters { + Some(variable_parameters) => variable_parameters, + _ => false, + }, + calling_convention, + platform, + } + } +} + +/////////////// +// DebugInfo + +/// Provides an interface to both provide and query debug info. The DebugInfo object is used +/// internally by the binary view to which it is applied to determine the attributes of functions, types, and variables +/// that would otherwise be costly to deduce. +/// +/// DebugInfo objects themselves are independent of binary views; their data can be sourced from any arbitrary binary +/// views and be applied to any other arbitrary binary view. A DebugInfo object can also contain debug info from multiple +/// 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 `binaryninja::binaryview::BinaryView::debug_info`) +/// or a debug-info parser (see `binaryninja::debuginfo::DebugInfoParser::parse_debug_info`). +/// +/// Please note that calling one of `add_*` functions will not work outside of a debuginfo plugin. +#[derive(PartialEq, Eq, Hash)] +pub struct DebugInfo { + pub(crate) handle: *mut BNDebugInfo, +} + +impl DebugInfo { + pub(crate) unsafe fn from_raw(handle: *mut BNDebugInfo) -> Ref<Self> { + debug_assert!(!handle.is_null()); + + Ref::new(Self { handle }) + } + + /// Returns a generator of all types provided by a named DebugInfoParser + pub fn types_by_name<S: BnStrCompatible>(&self, parser_name: S) -> Vec<NameAndType<String>> { + let parser_name = parser_name.as_bytes_with_nul(); + + let mut count: usize = 0; + let debug_types_ptr = unsafe { + BNGetDebugTypes( + self.handle, + parser_name.as_ref().as_ptr() as *mut _, + &mut count, + ) + }; + let result: Vec<NameAndType<String>> = unsafe { + slice::from_raw_parts_mut(debug_types_ptr, count) + .iter() + .map(NameAndType::<String>::from_raw) + .collect() + }; + + unsafe { BNFreeDebugTypes(debug_types_ptr, count) }; + result + } + + /// A generator of all types provided by DebugInfoParsers + pub fn types(&self) -> Vec<NameAndType<String>> { + let mut count: usize = 0; + let debug_types_ptr = unsafe { BNGetDebugTypes(self.handle, ptr::null_mut(), &mut count) }; + let result: Vec<NameAndType<String>> = unsafe { + slice::from_raw_parts_mut(debug_types_ptr, count) + .iter() + .map(NameAndType::<String>::from_raw) + .collect() + }; + + unsafe { BNFreeDebugTypes(debug_types_ptr, count) }; + result + } + + /// Returns a generator of all functions provided by a named DebugInfoParser + pub fn functions_by_name<S: BnStrCompatible>( + &self, + parser_name: S, + ) -> Vec<DebugFunctionInfo<CoreArchitecture, String, String>> { + let parser_name = parser_name.as_bytes_with_nul(); + + let mut count: usize = 0; + let functions_ptr = unsafe { + BNGetDebugFunctions( + self.handle, + parser_name.as_ref().as_ptr() as *mut _, + &mut count, + ) + }; + + let result: Vec<DebugFunctionInfo<CoreArchitecture, String, String>> = unsafe { + slice::from_raw_parts_mut(functions_ptr, count) + .iter() + .map(DebugFunctionInfo::<CoreArchitecture, String, String>::from) + .collect() + }; + + unsafe { BNFreeDebugFunctions(functions_ptr, count) }; + result + } + + /// A generator of all functions provided by DebugInfoParsers + pub fn functions(&self) -> Vec<DebugFunctionInfo<CoreArchitecture, String, String>> { + let mut count: usize = 0; + let functions_ptr = + unsafe { BNGetDebugFunctions(self.handle, ptr::null_mut(), &mut count) }; + + let result: Vec<DebugFunctionInfo<CoreArchitecture, String, String>> = unsafe { + slice::from_raw_parts_mut(functions_ptr, count) + .iter() + .map(DebugFunctionInfo::<CoreArchitecture, String, String>::from) + .collect() + }; + + unsafe { BNFreeDebugFunctions(functions_ptr, count) }; + result + } + + /// Returns a generator of all data variables provided by a named DebugInfoParser + pub fn data_variables_by_name<S: BnStrCompatible>( + &self, + parser_name: S, + ) -> Vec<DataVariableAndName<String>> { + let parser_name = parser_name.as_bytes_with_nul(); + + let mut count: usize = 0; + let data_variables_ptr = unsafe { + BNGetDebugDataVariables( + self.handle, + parser_name.as_ref().as_ptr() as *mut _, + &mut count, + ) + }; + + let result: Vec<DataVariableAndName<String>> = unsafe { + slice::from_raw_parts_mut(data_variables_ptr, count) + .iter() + .map(DataVariableAndName::<String>::from_raw) + .collect() + }; + + unsafe { BNFreeDataVariablesAndName(data_variables_ptr, count) }; + result + } + + /// A generator of all data variables provided by DebugInfoParsers + pub fn data_variables(&self) -> Vec<DataVariableAndName<String>> { + let mut count: usize = 0; + let data_variables_ptr = + unsafe { BNGetDebugDataVariables(self.handle, ptr::null_mut(), &mut count) }; + + let result: Vec<DataVariableAndName<String>> = unsafe { + slice::from_raw_parts_mut(data_variables_ptr, count) + .iter() + .map(DataVariableAndName::<String>::from_raw) + .collect() + }; + + unsafe { BNFreeDataVariablesAndName(data_variables_ptr, count) }; + result + } + + /// Adds a type scoped under the current parser's name to the debug info + pub fn add_type<S: BnStrCompatible>(&mut self, name: S, new_type: &Type) -> bool { + let name = name.as_bytes_with_nul(); + unsafe { + BNAddDebugType( + self.handle, + name.as_ref().as_ptr() as *mut _, + new_type.handle, + ) + } + } + + /// Adds a function scoped under the current parser's name to the debug info + pub fn add_function<A: Architecture, S1: BnStrCompatible, S2: BnStrCompatible>( + &mut self, + new_func: DebugFunctionInfo<A, S1, S2>, + ) -> bool { + unsafe { BNAddDebugFunction(self.handle, &mut new_func.into() as *mut _) } + } + + /// Adds a data variable scoped under the current parser's name to the debug info + pub fn add_data_variable<S: BnStrCompatible>( + &self, + address: u64, + t: &Type, + name: Option<S>, + ) -> bool { + match name { + Some(name) => { + let name = name.as_bytes_with_nul(); + unsafe { + BNAddDebugDataVariable( + self.handle, + address, + t.handle, + name.as_ref().as_ptr() as *mut _, + ) + } + } + None => unsafe { + BNAddDebugDataVariable(self.handle, address, t.handle, ptr::null_mut()) + }, + } + } +} + +unsafe impl RefCountable for DebugInfo { + unsafe fn inc_ref(handle: &Self) -> Ref<Self> { + Ref::new(Self { + handle: BNNewDebugInfoReference(handle.handle), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeDebugInfoReference(handle.handle); + } +} + +impl AsRef<DebugInfo> for DebugInfo { + fn as_ref(&self) -> &Self { + self + } +} + +impl ToOwned for DebugInfo { + type Owned = Ref<Self>; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +//////////////////////////// +// CustomDebugInfoParser + +/// Implement this trait to implement a debug info parser. See `DebugInfoParser` for more details. +pub trait CustomDebugInfoParser: 'static + Sync { + fn is_valid(&self, view: &BinaryView) -> bool; + fn parse_info(&self, debug_info: &mut DebugInfo, view: &BinaryView); +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 56ca2989..6826e9f5 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -44,6 +44,7 @@ pub mod callingconvention; pub mod command; pub mod custombinaryview; pub mod databuffer; +pub mod debuginfo; pub mod disassembly; pub mod fileaccessor; pub mod filemetadata; diff --git a/rust/src/string.rs b/rust/src/string.rs index e042dbdd..198c764b 100644 --- a/rust/src/string.rs +++ b/rust/src/string.rs @@ -21,6 +21,14 @@ use std::os::raw; use crate::rc::*; +pub(crate) fn raw_to_string(ptr: *const raw::c_char) -> Option<String> { + if ptr.is_null() { + None + } else { + Some(unsafe { CStr::from_ptr(ptr).to_string_lossy().into_owned() }) + } +} + #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[repr(C)] pub struct BnStr { diff --git a/rust/src/types.rs b/rust/src/types.rs index 91bf6972..0884d9ac 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -21,7 +21,7 @@ use std::{fmt, mem, ptr, result, slice}; use crate::architecture::{Architecture, CoreArchitecture}; use crate::callingconvention::CallingConvention; -use crate::string::{BnStr, BnStrCompatible, BnString}; +use crate::string::{raw_to_string, BnStr, BnStrCompatible, BnString}; use crate::rc::*; @@ -774,7 +774,6 @@ impl Type { let mut stack_adjust = Conf::<i64>::new(0, min_confidence()).into(); let mut raw_parameters = Vec::<BNFunctionParameter>::with_capacity(parameters.len()); for parameter in parameters { - // TODO : The core side is fine, but test this function with named parameters to ensure rust doesn't delete the reference let raw_name = parameter.name.as_bytes_with_nul(); let location = match ¶meter.location { @@ -823,7 +822,6 @@ impl Type { let mut raw_parameters = Vec::<BNFunctionParameter>::with_capacity(parameters.len()); for parameter in parameters { - // TODO : The core side is fine, but test this function with named parameters to ensure rust doesn't delete the reference let raw_name = parameter.name.as_bytes_with_nul(); let location = match ¶meter.location { @@ -1105,10 +1103,9 @@ impl EnumerationBuilder { let members: &[*mut BNEnumerationMember] = slice::from_raw_parts(members_raw as *mut _, count); - let mut result = vec![]; - for i in 0..count { - result.push(EnumerationMember::from_raw(members[i])); - } + let result = (0..count) + .map(|i| EnumerationMember::from_raw(members[i])) + .collect(); BNFreeEnumerationMemberList(members_raw, count); @@ -1166,10 +1163,9 @@ impl Enumeration { let members: &[*mut BNEnumerationMember] = slice::from_raw_parts(members_raw as *mut _, count); - let mut result = vec![]; - for i in 0..count { - result.push(EnumerationMember::from_raw(members[i])); - } + let result = (0..count) + .map(|i| EnumerationMember::from_raw(members[i])) + .collect(); BNFreeEnumerationMemberList(members_raw, count); @@ -1217,10 +1213,7 @@ impl StructureBuilder { //! use binaryninja::types::{Structure, Type}; //! // Define struct, set size (in bytes) - //! let mut my_custom_struct = Structure::new(); - //! my_custom_struct.set_width(17); - - //! // Create some fields for the struct + //! let mut my_custom_struct = Structure::new();BnStr::from_raw(raw.name) //! let field_1 = Self::named_int(5, false, "my_weird_int_type"); //! let field_2 = Self::int(4, false); //! let field_3 = Self::int(8, false); @@ -1537,3 +1530,137 @@ unsafe impl<'a> CoreOwnedArrayWrapper<'a> for QualifiedNameAndType { mem::transmute(raw) } } + +////////////////////////// +// QualifiedNameAndType + +pub struct NameAndType<S: BnStrCompatible> { + pub name: S, + t: Conf<Ref<Type>>, +} + +impl NameAndType<String> { + pub(crate) fn from_raw(raw: &BNNameAndType) -> Self { + Self::new( + raw_to_string(raw.name).unwrap(), + unsafe { &Type::ref_from_raw(raw.type_) }, + raw.typeConfidence, + ) + } +} + +impl<S: BnStrCompatible> NameAndType<S> { + pub fn new(name: S, t: &Ref<Type>, confidence: u8) -> Self { + Self { + name: name, + t: Conf::new(t.clone(), confidence), + } + } + + pub fn type_with_confidence(&self) -> Conf<Ref<Type>> { + self.t.clone() + } +} + +unsafe impl<S: BnStrCompatible> CoreOwnedArrayProvider for NameAndType<S> { + type Raw = BNNameAndType; + type Context = (); + + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeNameAndTypeList(raw, count); + } +} + +unsafe impl<'a, S: 'a + BnStrCompatible> CoreOwnedArrayWrapper<'a> for NameAndType<S> { + type Wrapped = &'a NameAndType<S>; + + unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { + mem::transmute(raw) + } +} + +////////////////// +// DataVariable + +pub struct DataVariable { + pub address: u64, + pub t: Conf<Ref<Type>>, + pub auto_discovered: bool, +} + +// impl DataVariable { +// pub(crate) fn from_raw(var: &BNDataVariable) -> Self { +// Self { +// address: var.address, +// t: Conf::new(unsafe { Type::ref_from_raw(var.type_) }, var.typeConfidence), +// auto_discovered: var.autoDiscovered, +// } +// } +// } + +impl DataVariable { + pub fn type_with_confidence(&self) -> Conf<Ref<Type>> { + Conf::new(self.t.contents.clone(), self.t.confidence) + } +} + +// unsafe impl CoreOwnedArrayProvider for DataVariable { +// type Raw = BNDataVariable; +// type Context = (); + +// unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { +// BNFreeDataVariables(raw, count); +// } +// } + +// unsafe impl<'a> CoreOwnedArrayWrapper<'a> for DataVariable { +// type Wrapped = &'a DataVariable; + +// unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { +// mem::transmute(raw) +// } +// } + +///////////////////////// +// DataVariableAndName + +pub struct DataVariableAndName<S: BnStrCompatible> { + pub address: u64, + pub t: Conf<Ref<Type>>, + pub auto_discovered: bool, + pub name: S, +} + +impl DataVariableAndName<String> { + pub(crate) fn from_raw(var: &BNDataVariableAndName) -> Self { + Self { + address: var.address, + t: Conf::new(unsafe { Type::ref_from_raw(var.type_) }, var.typeConfidence), + auto_discovered: var.autoDiscovered, + name: raw_to_string(var.name).unwrap(), + } + } +} + +impl<S: BnStrCompatible> DataVariableAndName<S> { + pub fn type_with_confidence(&self) -> Conf<Ref<Type>> { + Conf::new(self.t.contents.clone(), self.t.confidence) + } +} + +// unsafe impl<S: BnStrCompatible> CoreOwnedArrayProvider for DataVariableAndName<S> { +// type Raw = BNDataVariableAndName; +// type Context = (); + +// unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { +// BNFreeDataVariablesAndName(raw, count); +// } +// } + +// unsafe impl<'a, S: 'a + BnStrCompatible> CoreOwnedArrayWrapper<'a> for DataVariableAndName<S> { +// type Wrapped = &'a DataVariableAndName<S>; + +// unsafe fn wrap_raw(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped { +// mem::transmute(raw) +// } +// } |
