diff options
| -rw-r--r-- | basicblock.cpp | 85 | ||||
| -rw-r--r-- | binaryninjaapi.h | 97 | ||||
| -rw-r--r-- | binaryninjacore.h | 57 | ||||
| -rw-r--r-- | formatter/generic/CMakeLists.txt | 45 | ||||
| -rw-r--r-- | formatter/generic/genericformatter.cpp | 842 | ||||
| -rw-r--r-- | formatter/generic/genericformatter.h | 14 | ||||
| -rw-r--r-- | lang/c/pseudoc.cpp | 8 | ||||
| -rw-r--r-- | lang/c/pseudoc.h | 3 | ||||
| -rw-r--r-- | lang/rust/pseudorust.cpp | 8 | ||||
| -rw-r--r-- | lang/rust/pseudorust.h | 3 | ||||
| -rw-r--r-- | languagerepresentation.cpp | 33 | ||||
| -rw-r--r-- | lineformatter.cpp | 225 | ||||
| -rw-r--r-- | python/__init__.py | 1 | ||||
| -rw-r--r-- | python/examples/pseudo_python.py | 13 | ||||
| -rw-r--r-- | python/function.py | 58 | ||||
| -rw-r--r-- | python/highlevelil.py | 53 | ||||
| -rw-r--r-- | python/languagerepresentation.py | 34 | ||||
| -rw-r--r-- | python/lineformatter.py | 291 |
18 files changed, 1821 insertions, 49 deletions
diff --git a/basicblock.cpp b/basicblock.cpp index 6bdd93ae..67856d88 100644 --- a/basicblock.cpp +++ b/basicblock.cpp @@ -36,6 +36,24 @@ DisassemblySettings::DisassemblySettings(BNDisassemblySettings* settings) } +Ref<DisassemblySettings> DisassemblySettings::GetDefaultSettings() +{ + return new DisassemblySettings(BNDefaultDisassemblySettings()); +} + + +Ref<DisassemblySettings> DisassemblySettings::GetDefaultGraphSettings() +{ + return new DisassemblySettings(BNDefaultGraphDisassemblySettings()); +} + + +Ref<DisassemblySettings> DisassemblySettings::GetDefaultLinearSettings() +{ + return new DisassemblySettings(BNDefaultLinearDisassemblySettings()); +} + + DisassemblySettings* DisassemblySettings::Duplicate() { return new DisassemblySettings(BNDuplicateDisassemblySettings(m_object)); @@ -144,6 +162,73 @@ DisassemblyTextLine::DisassemblyTextLine() } +size_t DisassemblyTextLine::GetTotalWidth() const +{ + size_t result = 0; + for (auto& i : tokens) + result += i.width; + return result; +} + + +static void FindAddressAndIndentationTokens( + const vector<InstructionTextToken>& tokens, const std::function<void(const InstructionTextToken&)>& callback) +{ + size_t startToken = 0; + for (size_t i = 0; i < tokens.size(); i++) + { + if (tokens[i].type == AddressSeparatorToken) + { + startToken = i + 1; + break; + } + } + + for (size_t i = 0; i < startToken; i++) + callback(tokens[i]); + for (size_t i = startToken; i < tokens.size(); i++) + { + if (tokens[i].type == AddressDisplayToken || tokens[i].type == AddressSeparatorToken + || tokens[i].type == CollapseStateIndicatorToken) + { + callback(tokens[i]); + continue; + } + + bool whitespace = true; + for (auto ch : tokens[i].text) + { + if (!isspace(ch)) + { + whitespace = false; + break; + } + } + + if (!whitespace) + break; + + callback(tokens[i]); + } +} + + +size_t DisassemblyTextLine::GetAddressAndIndentationWidth() const +{ + size_t result = 0; + FindAddressAndIndentationTokens(tokens, [&](const InstructionTextToken& token) { result += token.width; }); + return result; +} + + +vector<InstructionTextToken> DisassemblyTextLine::GetAddressAndIndentationTokens() const +{ + vector<InstructionTextToken> result; + FindAddressAndIndentationTokens(tokens, [&](const InstructionTextToken& token) { result.push_back(token); }); + return result; +} + + BasicBlock::BasicBlock(BNBasicBlock* block) { m_object = block; diff --git a/binaryninjaapi.h b/binaryninjaapi.h index f8d7504a..92e6b397 100644 --- a/binaryninjaapi.h +++ b/binaryninjaapi.h @@ -4057,6 +4057,10 @@ namespace BinaryNinja { DisassemblyTextLineTypeInfo typeInfo; DisassemblyTextLine(); + + size_t GetTotalWidth() const; + size_t GetAddressAndIndentationWidth() const; + std::vector<InstructionTextToken> GetAddressAndIndentationTokens() const; }; /*! @@ -10175,6 +10179,10 @@ namespace BinaryNinja { DisassemblySettings(BNDisassemblySettings* settings); DisassemblySettings* Duplicate(); + static Ref<DisassemblySettings> GetDefaultSettings(); + static Ref<DisassemblySettings> GetDefaultGraphSettings(); + static Ref<DisassemblySettings> GetDefaultLinearSettings(); + bool IsOptionSet(BNDisassemblyOption option) const; void SetOption(BNDisassemblyOption option, bool state = true); @@ -13698,6 +13706,82 @@ namespace BinaryNinja { std::set<SSAVariable> GetSSAVariables(); }; + struct LineFormatterSettings + { + Ref<HighLevelILFunction> highLevelIL; + size_t desiredLineLength; + size_t minimumContentLength; + size_t tabWidth; + std::string languageName; + std::string commentStartString; + std::string commentEndString; + std::string annotationStartString; + std::string annotationEndString; + + /*! Gets the default line formatter settings for High Level IL code. + + \param settings The settings for reformatting. + \param func High Level IL function to be reformatted. + \return Settings for reformatting. + */ + static LineFormatterSettings GetDefault(DisassemblySettings* settings, HighLevelILFunction* func); + + /*! Gets the default line formatter settings for a language representation function. + + \param settings The settings for reformatting. + \param func Language representation function to be reformatted. + \return Settings for reformatting. + */ + static LineFormatterSettings GetLanguageRepresentationSettings( + DisassemblySettings* settings, LanguageRepresentationFunction* func); + + static LineFormatterSettings FromAPIObject(const BNLineFormatterSettings* settings); + BNLineFormatterSettings ToAPIObject() const; + }; + + class LineFormatter : public StaticCoreRefCountObject<BNLineFormatter> + { + std::string m_nameForRegister; + + static BNDisassemblyTextLine* FormatLinesCallback(void* ctxt, BNDisassemblyTextLine* inLines, size_t inCount, + const BNLineFormatterSettings* settings, size_t* outCount); + static void FreeLinesCallback(void* ctxt, BNDisassemblyTextLine* lines, size_t count); + + public: + LineFormatter(const std::string& name); + LineFormatter(BNLineFormatter* formatter); + + /*! Registers the line formatter. + + \param formatter The line formatter to register. + */ + static void Register(LineFormatter* formatter); + + static std::vector<Ref<LineFormatter>> GetList(); + static Ref<LineFormatter> GetByName(const std::string& name); + static Ref<LineFormatter> GetDefault(); + + /*! Reformats the given list of lines. Returns a new list of lines containing the reformatted code. + + \param lines The lines to reformat. + \param settings The settings for reformatting. + \return A new list of reformatted lines. + */ + virtual std::vector<DisassemblyTextLine> FormatLines( + const std::vector<DisassemblyTextLine>& lines, const LineFormatterSettings& settings) = 0; + }; + + class CoreLineFormatter : public LineFormatter + { + public: + CoreLineFormatter(BNLineFormatter* formatter); + + std::vector<DisassemblyTextLine> FormatLines( + const std::vector<DisassemblyTextLine>& lines, const LineFormatterSettings& settings) override; + }; + + class LanguageRepresentationFunctionType; + /*! LanguageRepresentationFunction represents a single function in a registered high level language. \ingroup highlevelil @@ -13707,7 +13791,8 @@ namespace BinaryNinja { BNFreeLanguageRepresentationFunction> { public: - LanguageRepresentationFunction(Architecture* arch, Function* func, HighLevelILFunction* highLevelIL); + LanguageRepresentationFunction(LanguageRepresentationFunctionType* type, Architecture* arch, Function* func, + HighLevelILFunction* highLevelIL); LanguageRepresentationFunction(BNLanguageRepresentationFunction* func); /*! Gets the lines of tokens for a given High Level IL instruction. @@ -13746,6 +13831,7 @@ namespace BinaryNinja { */ BNHighlightColor GetHighlight(BasicBlock* block); + Ref<LanguageRepresentationFunctionType> GetLanguage() const; Ref<Architecture> GetArchitecture() const; Ref<Function> GetFunction() const; Ref<HighLevelILFunction> GetHighLevelILFunction() const; @@ -13894,6 +13980,13 @@ namespace BinaryNinja { */ virtual Ref<TypeParser> GetTypeParser() { return nullptr; } + /*! Returns the line formatter for formatting code in this language. If NULL is returned, the default + formatter will be used. + + \return The optional formatter for formatting code in this language. + */ + virtual Ref<LineFormatter> GetLineFormatter() { return nullptr; } + /*! Returns a list of lines representing a function prototype in this language. If no lines are returned, the default C-style prototype will be used. @@ -13920,6 +14013,7 @@ namespace BinaryNinja { static bool IsValidCallback(void* ctxt, BNBinaryView* view); static BNTypePrinter* GetTypePrinterCallback(void* ctxt); static BNTypeParser* GetTypeParserCallback(void* ctxt); + static BNLineFormatter* GetLineFormatterCallback(void* ctxt); static BNDisassemblyTextLine* GetFunctionTypeTokensCallback( void* ctxt, BNFunction* func, BNDisassemblySettings* settings, size_t* count); static void FreeLinesCallback(void* ctxt, BNDisassemblyTextLine* lines, size_t count); @@ -13934,6 +14028,7 @@ namespace BinaryNinja { bool IsValid(BinaryView* view) override; Ref<TypePrinter> GetTypePrinter() override; Ref<TypeParser> GetTypeParser() override; + Ref<LineFormatter> GetLineFormatter() override; std::vector<DisassemblyTextLine> GetFunctionTypeTokens( Function* func, DisassemblySettings* settings = nullptr) override; }; diff --git a/binaryninjacore.h b/binaryninjacore.h index bc785484..bba1c786 100644 --- a/binaryninjacore.h +++ b/binaryninjacore.h @@ -44,7 +44,7 @@ // will require rebuilding. The minimum version is increased when there are // incompatible changes that break binary compatibility, such as changes to // existing types or functions. -#define BN_MINIMUM_CORE_ABI_VERSION 86 +#define BN_MINIMUM_CORE_ABI_VERSION 89 #ifdef __GNUC__ #ifdef BINARYNINJACORE_LIBRARY @@ -302,6 +302,7 @@ extern "C" typedef struct BNDemangler BNDemangler; typedef struct BNFirmwareNinja BNFirmwareNinja; typedef struct BNFirmwareNinjaReferenceNode BNFirmwareNinjaReferenceNode; + typedef struct BNLineFormatter BNLineFormatter; //! Console log levels typedef enum BNLogLevel @@ -727,6 +728,7 @@ extern "C" HighLevelILLinearDisassembly = 65, WaitForIL = 66, IndentHLILBody = 67, + DisableLineFormatting = 68, // Debugging options ShowFlagUsage = 128, @@ -3439,6 +3441,7 @@ extern "C" bool (*isValid)(void* ctxt, BNBinaryView* view); BNTypePrinter* (*getTypePrinter)(void* ctxt); BNTypeParser* (*getTypeParser)(void* ctxt); + BNLineFormatter* (*getLineFormatter)(void* ctxt); BNDisassemblyTextLine* (*getFunctionTypeTokens)( void* ctxt, BNFunction* func, BNDisassemblySettings* settings, size_t* count); void (*freeLines)(void* ctxt, BNDisassemblyTextLine* lines, size_t count); @@ -3538,6 +3541,28 @@ extern "C" size_t unique; } BNFirmwareNinjaDeviceAccesses; + typedef struct BNLineFormatterSettings + { + BNHighLevelILFunction* highLevelIL; + size_t desiredLineLength; + size_t minimumContentLength; + size_t tabWidth; + char* languageName; + char* commentStartString; + char* commentEndString; + char* annotationStartString; + char* annotationEndString; + } BNLineFormatterSettings; + + typedef struct BNCustomLineFormatter + { + void* context; + BNDisassemblyTextLine* (*formatLines)(void* ctxt, BNDisassemblyTextLine* inLines, size_t inCount, + const BNLineFormatterSettings* settings, size_t* outCount); + void (*freeLines)(void* ctxt, BNDisassemblyTextLine* lines, size_t count); + } BNCustomLineFormatter; + + BINARYNINJACOREAPI char* BNAllocString(const char* contents); BINARYNINJACOREAPI char* BNAllocStringWithLength(const char* contents, size_t len); BINARYNINJACOREAPI void BNFreeString(char* str); @@ -5454,6 +5479,9 @@ extern "C" // Disassembly settings BINARYNINJACOREAPI BNDisassemblySettings* BNCreateDisassemblySettings(void); + BINARYNINJACOREAPI BNDisassemblySettings* BNDefaultDisassemblySettings(void); + BINARYNINJACOREAPI BNDisassemblySettings* BNDefaultGraphDisassemblySettings(void); + BINARYNINJACOREAPI BNDisassemblySettings* BNDefaultLinearDisassemblySettings(void); BINARYNINJACOREAPI BNDisassemblySettings* BNNewDisassemblySettingsReference(BNDisassemblySettings* settings); BINARYNINJACOREAPI BNDisassemblySettings* BNDuplicateDisassemblySettings(BNDisassemblySettings* settings); BINARYNINJACOREAPI void BNFreeDisassemblySettings(BNDisassemblySettings* settings); @@ -6144,15 +6172,19 @@ extern "C" BNLanguageRepresentationFunctionType* type, BNBinaryView* view); BINARYNINJACOREAPI BNTypePrinter* BNGetLanguageRepresentationFunctionTypePrinter(BNLanguageRepresentationFunctionType* type); BINARYNINJACOREAPI BNTypeParser* BNGetLanguageRepresentationFunctionTypeParser(BNLanguageRepresentationFunctionType* type); + BINARYNINJACOREAPI BNLineFormatter* BNGetLanguageRepresentationFunctionTypeLineFormatter( + BNLanguageRepresentationFunctionType* type); BINARYNINJACOREAPI BNDisassemblyTextLine* BNGetLanguageRepresentationFunctionTypeFunctionTypeTokens( BNLanguageRepresentationFunctionType* type, BNFunction* func, BNDisassemblySettings* settings, size_t* count); BINARYNINJACOREAPI BNLanguageRepresentationFunction* BNCreateCustomLanguageRepresentationFunction( - BNArchitecture* arch, BNFunction* func, BNHighLevelILFunction* highLevelIL, - BNCustomLanguageRepresentationFunction* callbacks); + BNLanguageRepresentationFunctionType* type, BNArchitecture* arch, BNFunction* func, + BNHighLevelILFunction* highLevelIL, BNCustomLanguageRepresentationFunction* callbacks); BINARYNINJACOREAPI BNLanguageRepresentationFunction* BNNewLanguageRepresentationFunctionReference( BNLanguageRepresentationFunction* func); BINARYNINJACOREAPI void BNFreeLanguageRepresentationFunction(BNLanguageRepresentationFunction* func); + BINARYNINJACOREAPI BNLanguageRepresentationFunctionType* BNGetLanguageRepresentationType( + BNLanguageRepresentationFunction* func); BINARYNINJACOREAPI BNArchitecture* BNGetLanguageRepresentationArchitecture(BNLanguageRepresentationFunction* func); BINARYNINJACOREAPI BNFunction* BNGetLanguageRepresentationOwnerFunction(BNLanguageRepresentationFunction* func); BINARYNINJACOREAPI BNHighLevelILFunction* BNGetLanguageRepresentationILFunction(BNLanguageRepresentationFunction* func); @@ -8057,6 +8089,25 @@ extern "C" BINARYNINJACOREAPI void BNFreeFirmwareNinjaReferenceNode(BNFirmwareNinjaReferenceNode* node); BINARYNINJACOREAPI BNFirmwareNinjaReferenceNode* BNNewFirmwareNinjaReferenceNodeReference(BNFirmwareNinjaReferenceNode* node); BINARYNINJACOREAPI void BNFreeFirmwareNinjaReferenceNodes(BNFirmwareNinjaReferenceNode** nodes, size_t count); + + // Line formatters + BINARYNINJACOREAPI BNLineFormatter* BNRegisterLineFormatter(const char* name, BNCustomLineFormatter* callbacks); + BINARYNINJACOREAPI BNLineFormatter** BNGetLineFormatterList(size_t* count); + BINARYNINJACOREAPI void BNFreeLineFormatterList(BNLineFormatter** formatters); + BINARYNINJACOREAPI BNLineFormatter* BNGetLineFormatterByName(const char* name); + BINARYNINJACOREAPI BNLineFormatter* BNGetDefaultLineFormatter(); + + BINARYNINJACOREAPI char* BNGetLineFormatterName(BNLineFormatter* formatter); + + BINARYNINJACOREAPI BNDisassemblyTextLine* BNFormatLines(BNLineFormatter* formatter, BNDisassemblyTextLine* inLines, + size_t inCount, const BNLineFormatterSettings* settings, size_t* outCount); + + BINARYNINJACOREAPI BNLineFormatterSettings* BNGetDefaultLineFormatterSettings( + BNDisassemblySettings* settings, BNHighLevelILFunction* func); + BINARYNINJACOREAPI BNLineFormatterSettings* BNGetLanguageRepresentationLineFormatterSettings( + BNDisassemblySettings* settings, BNLanguageRepresentationFunction* func); + BINARYNINJACOREAPI void BNFreeLineFormatterSettings(BNLineFormatterSettings* settings); + #ifdef __cplusplus } #endif diff --git a/formatter/generic/CMakeLists.txt b/formatter/generic/CMakeLists.txt new file mode 100644 index 00000000..85a5a3e2 --- /dev/null +++ b/formatter/generic/CMakeLists.txt @@ -0,0 +1,45 @@ +cmake_minimum_required(VERSION 3.9 FATAL_ERROR) + +project(formatter_generic) + +if(NOT BN_INTERNAL_BUILD) + add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ${PROJECT_BINARY_DIR}/api) +endif() + +file(GLOB SOURCES + *.cpp + *.h) + +if(DEMO) + add_library(formatter_generic STATIC ${SOURCES}) +else() + add_library(formatter_generic SHARED ${SOURCES}) +endif() + +target_include_directories(formatter_generic + PRIVATE ${PROJECT_SOURCE_DIR}) + +if(WIN32) + target_link_directories(formatter_generic + PRIVATE ${BN_INSTALL_DIR}) + target_link_libraries(formatter_generic binaryninjaapi binaryninjacore) +else() + target_link_libraries(formatter_generic binaryninjaapi) +endif() + +set_target_properties(formatter_generic PROPERTIES + CXX_STANDARD 17 + CXX_VISIBILITY_PRESET hidden + CXX_STANDARD_REQUIRED ON + C_STANDARD 99 + C_STANDARD_REQUIRED ON + C_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON + POSITION_INDEPENDENT_CODE ON) + +if(BN_INTERNAL_BUILD) + plugin_rpath(formatter_generic) + set_target_properties(formatter_generic PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${BN_CORE_PLUGIN_DIR} + RUNTIME_OUTPUT_DIRECTORY ${BN_CORE_PLUGIN_DIR}) +endif() diff --git a/formatter/generic/genericformatter.cpp b/formatter/generic/genericformatter.cpp new file mode 100644 index 00000000..8e73d740 --- /dev/null +++ b/formatter/generic/genericformatter.cpp @@ -0,0 +1,842 @@ +#include <ctype.h> +#include <stack> +#include "genericformatter.h" + +using namespace BinaryNinja; +using namespace std; + + +enum ItemType +{ + Atom, + Comment, + Operator, + FieldAccessor, + Argument, + ArgumentSeparator, + Statement, + StatementSeparator, + Group, + Container, + StartOfContainer, + ContainerContents, + EndOfContainer, +}; + + +static string TrimString(const string& str) +{ + size_t startPos = 0; + size_t endPos = 0; + + bool start = true; + for (size_t i = 0; i < str.size(); i++) + { + if (isspace(str[i])) + { + if (start) + { + startPos = i + 1; + endPos = i + 1; + } + } + else + { + start = false; + endPos = i + 1; + } + } + + return str.substr(startPos, endPos - startPos); +} + + +static string TrimLeadingWhitespace(const string& str) +{ + size_t startPos = 0; + for (size_t i = 0; i < str.size(); i++) + { + if (!isspace(str[i])) + { + startPos = i; + break; + } + } + return str.substr(startPos); +} + + +static string TrimTrailingWhitespace(const string& str) +{ + if (str.empty()) + return str; + + size_t endPos = str.size(); + for (size_t i = str.size() - 1; i > 0; i--) + { + if (!isspace(str[i])) + { + endPos = i + 1; + break; + } + } + return str.substr(0, endPos); +} + + +static const map<string, BNOperatorPrecedence> g_operatorPrecedenceMap = {{"=", AssignmentOperatorPrecedence}, + {":=", AssignmentOperatorPrecedence}, {"+=", AssignmentOperatorPrecedence}, {"-=", AssignmentOperatorPrecedence}, + {"*=", AssignmentOperatorPrecedence}, {"/=", AssignmentOperatorPrecedence}, {"s/=", AssignmentOperatorPrecedence}, + {"u/=", AssignmentOperatorPrecedence}, {"%=", AssignmentOperatorPrecedence}, {"s%=", AssignmentOperatorPrecedence}, + {"u%=", AssignmentOperatorPrecedence}, {"&=", AssignmentOperatorPrecedence}, {"|=", AssignmentOperatorPrecedence}, + {"^=", AssignmentOperatorPrecedence}, {">>=", AssignmentOperatorPrecedence}, {"s>>=", AssignmentOperatorPrecedence}, + {"u>>=", AssignmentOperatorPrecedence}, {"<<=", AssignmentOperatorPrecedence}, + {"s<<=", AssignmentOperatorPrecedence}, {"u<<=", AssignmentOperatorPrecedence}, {"?", TernaryOperatorPrecedence}, + {":", TernaryOperatorPrecedence}, {"||", LogicalOrOperatorPrecedence}, {"or", LogicalOrOperatorPrecedence}, + {"&&", LogicalAndOperatorPrecedence}, {"and", LogicalAndOperatorPrecedence}, {"&", BitwiseAndOperatorPrecedence}, + {"|", BitwiseOrOperatorPrecedence}, {"^", BitwiseXorOperatorPrecedence}, {"==", EqualityOperatorPrecedence}, + {"===", EqualityOperatorPrecedence}, {"!=", EqualityOperatorPrecedence}, {"!==", EqualityOperatorPrecedence}, + {"<>", EqualityOperatorPrecedence}, {"<", CompareOperatorPrecedence}, {"s<", CompareOperatorPrecedence}, + {"u<", CompareOperatorPrecedence}, {"<=", CompareOperatorPrecedence}, {"s<=", CompareOperatorPrecedence}, + {"u<=", CompareOperatorPrecedence}, {">", CompareOperatorPrecedence}, {"s>", CompareOperatorPrecedence}, + {"u>", CompareOperatorPrecedence}, {">=", CompareOperatorPrecedence}, {"s>=", CompareOperatorPrecedence}, + {"u>=", CompareOperatorPrecedence}, {"<<", ShiftOperatorPrecedence}, {"s<<", ShiftOperatorPrecedence}, + {"u<<", ShiftOperatorPrecedence}, {">>", ShiftOperatorPrecedence}, {"s>>", ShiftOperatorPrecedence}, + {"u>>", ShiftOperatorPrecedence}, {"+", AddOperatorPrecedence}, {"-", AddOperatorPrecedence}, + {"*", MultiplyOperatorPrecedence}, {"/", MultiplyOperatorPrecedence}, {"s/", MultiplyOperatorPrecedence}, + {"u/", MultiplyOperatorPrecedence}, {"%", MultiplyOperatorPrecedence}, {"s%", MultiplyOperatorPrecedence}, + {"u%", MultiplyOperatorPrecedence}, {"!", UnaryOperatorPrecedence}, {"not", UnaryOperatorPrecedence}, + {"~", UnaryOperatorPrecedence}}; + + +static BNOperatorPrecedence GetOperatorPrecedence(const InstructionTextToken& token, size_t* ternary = nullptr) +{ + string trimmedText = TrimString(token.text); + auto i = g_operatorPrecedenceMap.find(trimmedText); + if (i != g_operatorPrecedenceMap.end()) + { + if (i->second == TernaryOperatorPrecedence && ternary) + { + // HLIL uses ':' in additional contexts, so look for active ternary operators before + // treating it as part of a ternary + if (trimmedText == "?") + { + (*ternary)++; + } + else if (trimmedText == ":") + { + if (*ternary) + (*ternary)--; + else + return MemberAndFunctionOperatorPrecedence; + } + } + return i->second; + } + return MemberAndFunctionOperatorPrecedence; +} + + +struct Item +{ + ItemType type; + vector<Item> items; + vector<InstructionTextToken> tokens; + size_t width; + + void AppendAllTokens(vector<InstructionTextToken>& output, bool& firstTokenOfLine) + { + if (firstTokenOfLine) + { + if (!tokens.empty()) + { + InstructionTextToken token = tokens.front(); + string trimmedText = TrimLeadingWhitespace(token.text); + token.width -= token.text.size() - trimmedText.size(); + token.text = trimmedText; + output.push_back(token); + output.insert(output.end(), tokens.begin() + 1, tokens.end()); + firstTokenOfLine = false; + } + } + else + { + output.insert(output.end(), tokens.begin(), tokens.end()); + } + + for (auto& item : items) + item.AppendAllTokens(output, firstTokenOfLine); + } + + void AddTokenToLastAtom(const InstructionTextToken& token) + { + if (!tokens.empty()) + tokens.push_back(token); + else if (items.empty()) + items.push_back(Item {Atom, {}, {token}, 0}); + else + items.back().AddTokenToLastAtom(token); + } + + void CalculateWidth() + { + width = 0; + for (auto& token : tokens) + width += token.width; + for (auto& item : items) + { + item.CalculateWidth(); + width += item.width; + } + } +}; + + +struct ItemLayoutStackEntry +{ + vector<Item> items; + size_t additionalContinuationIndentation; + size_t desiredWidth; + size_t desiredContinuationWidth; + bool newLineOnReenteringScope; +}; + + +static vector<Item> CreateStatementItems(const vector<Item>& items) +{ + vector<Item> result, pending; + bool hasArgs = false; + for (auto& i : items) + { + if (i.type == StatementSeparator) + { + if (pending.empty()) + { + result.push_back(Item {Atom, {}, {i.tokens}, 0}); + } + else + { + for (auto& j : i.tokens) + pending.back().AddTokenToLastAtom(j); + result.push_back(Item {Statement, pending, {}, 0}); + } + pending.clear(); + hasArgs = true; + } + else if (i.type == StartOfContainer && pending.empty()) + { + result.push_back(i); + } + else if (i.type == EndOfContainer && hasArgs && !pending.empty()) + { + result.push_back(Item {Statement, pending, {}, 0}); + result.push_back(i); + pending.clear(); + } + else + { + pending.push_back(Item {i.type, CreateStatementItems(i.items), i.tokens, 0}); + } + } + + if (!pending.empty()) + { + if (hasArgs) + result.push_back(Item {Statement, pending, {}, 0}); + else + result.insert(result.end(), pending.begin(), pending.end()); + } + + return result; +} + + +static vector<Item> CreateAssignmentOperatorGroups(const vector<Item>& items) +{ + vector<Item> result, pending; + bool hasOperators = false; + for (auto& i : items) + { + if (i.type == Operator && !i.tokens.empty()) + { + BNOperatorPrecedence precedence = GetOperatorPrecedence(i.tokens[0]); + if (precedence == AssignmentOperatorPrecedence) + { + if (pending.empty()) + { + result.push_back(Item {Atom, {}, {i.tokens}, 0}); + } + else + { + for (auto& j : i.tokens) + pending.back().AddTokenToLastAtom(j); + result.push_back(Item {Statement, pending, {}, 0}); + } + pending.clear(); + hasOperators = true; + continue; + } + } + + if (i.type == StartOfContainer && pending.empty()) + { + result.push_back(i); + } + else if (i.type == EndOfContainer && hasOperators && !pending.empty()) + { + result.push_back(Item {Group, pending, {}, 0}); + result.push_back(i); + pending.clear(); + } + else + { + pending.push_back(Item {i.type, CreateAssignmentOperatorGroups(i.items), i.tokens, 0}); + } + } + + if (!pending.empty()) + { + if (hasOperators) + result.push_back(Item {Group, pending, {}, 0}); + else + result.insert(result.end(), pending.begin(), pending.end()); + } + + return result; +} + + +static vector<Item> CreateArgumentItems(const vector<Item>& items, bool inContainer) +{ + vector<Item> result, pending; + bool hasArgs = false; + for (auto& i : items) + { + if (i.type == ArgumentSeparator) + { + if (pending.empty()) + { + result.push_back(Item {Atom, {}, {i.tokens}, 0}); + } + else + { + for (auto& j : i.tokens) + pending.back().AddTokenToLastAtom(j); + result.push_back(Item {inContainer ? Argument : Group, pending, {}, 0}); + } + pending.clear(); + hasArgs = true; + } + else if (i.type == StartOfContainer && pending.empty()) + { + result.push_back(i); + } + else if (i.type == EndOfContainer && hasArgs && !pending.empty()) + { + result.push_back(Item {inContainer ? Argument : Group, pending, {}, 0}); + result.push_back(i); + pending.clear(); + } + else + { + pending.push_back(Item {i.type, CreateArgumentItems(i.items, i.type == Container), i.tokens, 0}); + } + } + + if (!pending.empty()) + { + if (hasArgs) + result.push_back(Item {inContainer ? Argument : Group, pending, {}, 0}); + else + result.insert(result.end(), pending.begin(), pending.end()); + } + + return result; +} + + +static vector<Item> CreateOperatorGroups(const vector<Item>& items) +{ + vector<Item> result, pending; + bool hasOperators = false; + for (auto& i : items) + { + if (i.type == Operator) + { + if (pending.size() == 1) + result.push_back(pending[0]); + else if (!pending.empty()) + result.push_back(Item {Group, pending, {}, 0}); + result.push_back(i); + pending.clear(); + hasOperators = true; + continue; + } + + if (i.type == StartOfContainer && pending.empty()) + { + result.push_back(i); + } + else if (i.type == EndOfContainer && hasOperators && pending.size() > 1) + { + result.push_back(Item {Group, pending, {}, 0}); + result.push_back(i); + pending.clear(); + } + else + { + pending.push_back(Item {i.type, CreateOperatorGroups(i.items), i.tokens, 0}); + } + } + + if (!pending.empty()) + { + if (hasOperators && pending.size() > 1) + result.push_back(Item {Group, pending, {}, 0}); + else + result.insert(result.end(), pending.begin(), pending.end()); + } + + return result; +} + + +static vector<Item> CreateOperatorPrecedenceGroups(const vector<Item>& items) +{ + // Look for the operator with the lowest precedence. These will be grouped first. + optional<BNOperatorPrecedence> lowestPrecedence; + size_t ternary = 0; + for (auto i = items.begin(); i != items.end(); ++i) + { + if (i != items.begin() && i->type == Operator && !i->tokens.empty()) + { + BNOperatorPrecedence precedence = GetOperatorPrecedence(i->tokens[0], &ternary); + if (!lowestPrecedence.has_value() || precedence < lowestPrecedence.value()) + lowestPrecedence = precedence; + } + } + + // If there were no operators, no need to group at this level. Just traverse down into child items. + if (!lowestPrecedence.has_value()) + { + vector<Item> result; + result.reserve(items.size()); + for (auto& i : items) + result.push_back({i.type, CreateOperatorPrecedenceGroups(i.items), i.tokens, 0}); + return result; + } + + // Go through the items and split the items into groups around the lowest precedence operator + vector<Item> result, pending; + ternary = 0; + for (auto i = items.begin(); i != items.end(); ++i) + { + if (i != items.begin() && i->type == Operator && !i->tokens.empty()) + { + BNOperatorPrecedence precedence = GetOperatorPrecedence(i->tokens[0], &ternary); + if (precedence == lowestPrecedence.value()) + { + if (pending.size() == 1) + result.push_back(pending[0]); + else if (!pending.empty()) + result.push_back(Item {Group, pending, {}, 0}); + else + result.insert(result.end(), pending.begin(), pending.end()); + pending.clear(); + } + } + + if (i->type == StartOfContainer && pending.empty()) + { + result.push_back(*i); + } + else if (i->type == EndOfContainer && pending.size() > 1 && !result.empty()) + { + result.push_back(Item {Group, pending, {}, 0}); + result.push_back(*i); + pending.clear(); + } + else + { + pending.push_back(*i); + } + } + + if (!pending.empty()) + { + if (pending.size() > 1 && !result.empty()) + result.push_back(Item {Group, pending, {}, 0}); + else + result.insert(result.end(), pending.begin(), pending.end()); + } + + // Recurse into these groups and process the next lowest precedence in each + vector<Item> processed; + processed.reserve(result.size()); + for (auto& i : result) + processed.push_back({i.type, CreateOperatorPrecedenceGroups(i.items), i.tokens, 0}); + return processed; +} + + +static vector<Item> RelocateStartAndEndOfContainerItems(const vector<Item>& items) +{ + vector<Item> result; + for (auto& i : items) + { + if (!result.empty() && i.type == Container && !i.items.empty() && i.items.front().type == StartOfContainer) + { + Item startOfContainer = i.items.front(); + for (auto& j : startOfContainer.tokens) + result.back().AddTokenToLastAtom(j); + + vector<Item> containerItems(i.items.begin() + 1, i.items.end()); + containerItems = RelocateStartAndEndOfContainerItems(containerItems); + result.push_back({Container, containerItems, {}, 0}); + } + else if (i.type == EndOfContainer && !result.empty()) + { + for (auto& j : i.tokens) + result.back().AddTokenToLastAtom(j); + } + else + { + result.push_back(Item {i.type, RelocateStartAndEndOfContainerItems(i.items), i.tokens, 0}); + } + } + return result; +} + + +GenericLineFormatter::GenericLineFormatter(): LineFormatter("GenericLineFormatter") +{ +} + + +vector<DisassemblyTextLine> GenericLineFormatter::FormatLines( + const vector<DisassemblyTextLine>& lines, const LineFormatterSettings& settings) +{ + vector<DisassemblyTextLine> result; + for (size_t i = 0; i < lines.size(); i++) + { + const DisassemblyTextLine& currentLine = lines[i]; + + size_t totalLength = currentLine.GetTotalWidth(); + size_t indentation = currentLine.GetAddressAndIndentationWidth(); + + // Check width against settings + size_t contentLength = totalLength - indentation; + if (totalLength <= settings.desiredLineLength || contentLength <= settings.minimumContentLength) + { + // Line fits, emit as-is + result.push_back(currentLine); + continue; + } + + // Calculate indentation for continuation lines. If the next line in the input is more indented, make + // the continuation lines more indented than that to separate the continuation from the new scope. + size_t continuationIndentation = indentation + settings.tabWidth; + if ((i + 1) < lines.size()) + { + size_t nextLineIndentation = lines[i + 1].GetAddressAndIndentationWidth(); + if (nextLineIndentation > indentation) + continuationIndentation = nextLineIndentation + settings.tabWidth; + } + size_t additionalContinuationIndentation = continuationIndentation - indentation; + + // Compute the target length for this line + size_t desiredWidth = settings.minimumContentLength; + if (indentation < settings.desiredLineLength) + { + size_t remainingWidth = settings.desiredLineLength - indentation; + if (remainingWidth > desiredWidth) + desiredWidth = remainingWidth; + } + + // Compute the target length for the continuation lines + size_t desiredContinuationWidth = settings.minimumContentLength; + if (continuationIndentation < settings.desiredLineLength) + { + size_t remainingWidth = settings.desiredLineLength - continuationIndentation; + if (remainingWidth > desiredContinuationWidth) + desiredContinuationWidth = remainingWidth; + } + + // Gather the indentation tokens at the beginning of the line + vector<InstructionTextToken> indentationTokens = currentLine.GetAddressAndIndentationTokens(); + size_t tokenIndex = indentationTokens.size(); + + // First break the line down into nested container items. A container is anything between a pair of + // BraceTokens (except for strings, where the entire string, including the quotes, are treated as + // a single atom). + vector<Item> items; + stack<vector<Item>> itemStack; + for (; tokenIndex < currentLine.tokens.size(); tokenIndex++) + { + const InstructionTextToken& token = currentLine.tokens[tokenIndex]; + string trimmedText = TrimString(token.text); + + switch (token.type) + { + case BraceToken: + if (tokenIndex + 1 < currentLine.tokens.size() + && currentLine.tokens[tokenIndex + 1].type == StringToken) + { + // Treat string tokens surrounded by brace tokens as a unit (this is usually the quotes + // surrounding the string) + Item atom; + atom.type = Atom; + atom.tokens.push_back(token); + atom.tokens.push_back(currentLine.tokens[tokenIndex + 1]); + atom.width = 0; + tokenIndex++; + if (tokenIndex + 1 < currentLine.tokens.size() + && currentLine.tokens[tokenIndex + 1].type == BraceToken) + { + atom.tokens.push_back(currentLine.tokens[tokenIndex + 1]); + tokenIndex++; + } + + items.push_back(atom); + break; + } + + if (trimmedText == "(" || trimmedText == "[" || trimmedText == "{") + { + // Create a ContainerContents item and place it onto the item stack. This will hold anything + // inside the container once the end of the container is found. + items.push_back(Item {Container, {}, {}, 0}); + itemStack.push(items); + + // Starting a new context + items.clear(); + items.push_back(Item {StartOfContainer, {}, {token}, 0}); + } + else if (trimmedText == ")" || trimmedText == "]" || trimmedText == "}") + { + items.push_back(Item {EndOfContainer, {}, {token}, 0}); + + if (itemStack.empty()) + break; + + // Go back up the item stack and add the items to the container + vector<Item> parent = itemStack.top(); + itemStack.pop(); + parent.back().items.insert(parent.back().items.end(), items.begin(), items.end()); + items = parent; + } + break; + case CommentToken: + { + // The rest of the line is a comment. There may be tokens that are not of CommentToken type, but + // these are used to create clickable items when things are referenced by the comment. + Item comment {Comment, {}, {}, 0}; + for (; tokenIndex < currentLine.tokens.size(); tokenIndex++) + comment.tokens.push_back(currentLine.tokens[tokenIndex]); + items.push_back(comment); + break; + } + case TextToken: + if (trimmedText == ",") + items.push_back(Item {ArgumentSeparator, {}, {token}, 0}); + else if ((!trimmedText.empty() && trimmedText[0] == '.') || trimmedText == "->") + items.push_back(Item {FieldAccessor, {}, {token}, 0}); + else if (trimmedText == ";") + items.push_back(Item {StatementSeparator, {}, {token}, 0}); + else if (trimmedText == ":" && !items.empty()) + items.back().AddTokenToLastAtom(token); + else + items.push_back(Item {Atom, {}, {token}, 0}); + break; + case OperationToken: + if ((!trimmedText.empty() && trimmedText[0] == '.') || trimmedText == "->") + items.push_back(Item {FieldAccessor, {}, {token}, 0}); + else + items.push_back(Item {Operator, {}, {token}, 0}); + break; + default: + items.push_back(Item {Atom, {}, {token}, 0}); + break; + } + } + + while (!itemStack.empty()) + { + vector<Item> parent = itemStack.top(); + itemStack.pop(); + parent.back().items.insert(parent.back().items.end(), items.begin(), items.end()); + items = parent; + } + + // Process the items to find semicolons, and create statement items containing the group of items making + // up each statement. + items = CreateStatementItems(items); + + // Process the items to find assignment operators, and group up the source and destination items. This needs + // to be done before creating arguments to better handle multiple return value constructs. + items = CreateAssignmentOperatorGroups(items); + + // Process the items to find commas, and create argument items containing the group of items making + // up each argument. + items = CreateArgumentItems(items, false); + + // Process the items to find operators, and create group items containing the operands + items = CreateOperatorGroups(items); + + // Process the items to group operations by operator precedence + items = CreateOperatorPrecedenceGroups(items); + + // Move start of container items to the last token of the previous item, and end of container items to + // the previous atom. + items = RelocateStartAndEndOfContainerItems(items); + + // Now that items are done, compute widths for layout + for (auto& j : items) + j.CalculateWidth(); + + DisassemblyTextLine outputLine = currentLine; + outputLine.tokens = indentationTokens; + size_t currentWidth = 0; + bool firstTokenOfLine = true; + + stack<ItemLayoutStackEntry> layoutStack; + layoutStack.push({items, additionalContinuationIndentation, desiredWidth, desiredContinuationWidth, false}); + + auto newLine = [&]() { + if (!firstTokenOfLine) + { + string lastTokenText = outputLine.tokens.back().text; + string trimmedText = TrimTrailingWhitespace(lastTokenText); + outputLine.tokens.back().width -= lastTokenText.size() - trimmedText.size(); + outputLine.tokens.back().text = trimmedText; + } + + result.push_back(outputLine); + outputLine.tokens = indentationTokens; + + // Make sure any collapsible state indicators are set to padding so that the indicators don't + // show up more than once for a single scope. + for (auto& outToken : outputLine.tokens) + { + if (outToken.type == CollapseStateIndicatorToken) + outToken.context = ContentCollapsiblePadding; + } + + outputLine.tokens.emplace_back(TextToken, string(additionalContinuationIndentation, ' ')); + currentWidth = 0; + desiredWidth = desiredContinuationWidth; + firstTokenOfLine = true; + }; + + while (!layoutStack.empty()) + { + ItemLayoutStackEntry layoutStackEntry = layoutStack.top(); + layoutStack.pop(); + + items = layoutStackEntry.items; + additionalContinuationIndentation = layoutStackEntry.additionalContinuationIndentation; + desiredWidth = layoutStackEntry.desiredWidth; + desiredContinuationWidth = layoutStackEntry.desiredContinuationWidth; + + // Check to see if the scope we are returning to needs a new line. This is used when an argument + // spans multiple lines. The rest of the arguments are placed on separate lines from the long argument. + if (layoutStackEntry.newLineOnReenteringScope && currentWidth > 0) + newLine(); + + for (auto item = items.begin(); item != items.end();) + { + if (currentWidth + item->width > desiredWidth) + { + // Current item is too wide to fit on the current line, will need to start a new line. + auto next = item; + ++next; + + // If we are already on a fresh line, or the item is too wide to fit on a new line of its + // own, we have to start emitting tokens and wrap in the middle of the item. If the item + // is a container, always use the splitting behavior. + if (currentWidth == 0 || item->width > desiredContinuationWidth || item->type == Container) + { + if (item->type == Argument && currentWidth != 0) + { + // If an argument is too wide to show on a single line all by itself, start the argument + // on a new line, and add additional indentation for the continuation of the argument. + if (next != items.end()) + { + layoutStack.push({vector(next, items.end()), additionalContinuationIndentation, + desiredWidth, desiredContinuationWidth, true}); + } + + newLine(); + + additionalContinuationIndentation += settings.tabWidth; + if (desiredContinuationWidth < settings.minimumContentLength + settings.tabWidth) + desiredContinuationWidth = settings.minimumContentLength; + else + desiredContinuationWidth -= settings.tabWidth; + + layoutStack.push({item->items, additionalContinuationIndentation, desiredWidth, + desiredContinuationWidth, false}); + break; + } + + if (item->tokens.empty()) + { + // Item contains other items. Place the context onto the layout stack and resume processing. + if (next != items.end()) + { + layoutStack.push({vector(next, items.end()), additionalContinuationIndentation, + desiredWidth, desiredContinuationWidth, false}); + } + layoutStack.push({item->items, additionalContinuationIndentation, desiredWidth, + desiredContinuationWidth, false}); + break; + } + + // Item is an atom. We just have to emit the tokens even though it is too wide. + item->AppendAllTokens(outputLine.tokens, firstTokenOfLine); + ++item; + continue; + } + + // Start a new line and add the item on the fresh line. + newLine(); + continue; + } + + // Item fits, emit all tokens for it + item->AppendAllTokens(outputLine.tokens, firstTokenOfLine); + currentWidth += item->width; + ++item; + } + } + + // Emit the last line if it had tokens + if (currentWidth > 0) + newLine(); + } + + return result; +} + + +extern "C" +{ + BN_DECLARE_CORE_ABI_VERSION + +#ifndef DEMO_EDITION + BINARYNINJAPLUGIN void CorePluginDependencies() + { + } +#endif + +#ifdef DEMO_EDITION + bool GenericFormatterPluginInit() +#else + BINARYNINJAPLUGIN bool CorePluginInit() +#endif + { + GenericLineFormatter* formatter = new GenericLineFormatter(); + LineFormatter::Register(formatter); + return true; + } +} diff --git a/formatter/generic/genericformatter.h b/formatter/generic/genericformatter.h new file mode 100644 index 00000000..d48758ee --- /dev/null +++ b/formatter/generic/genericformatter.h @@ -0,0 +1,14 @@ +#pragma once + +#include "binaryninjaapi.h" + + +class GenericLineFormatter: public BinaryNinja::LineFormatter +{ +public: + GenericLineFormatter(); + + std::vector<BinaryNinja::DisassemblyTextLine> FormatLines( + const std::vector<BinaryNinja::DisassemblyTextLine>& lines, + const BinaryNinja::LineFormatterSettings& settings) override; +}; diff --git a/lang/c/pseudoc.cpp b/lang/c/pseudoc.cpp index dff591ff..52622f57 100644 --- a/lang/c/pseudoc.cpp +++ b/lang/c/pseudoc.cpp @@ -6,9 +6,9 @@ using namespace std; using namespace BinaryNinja; -PseudoCFunction::PseudoCFunction( - Architecture* arch, Function* owner, HighLevelILFunction* highLevelILFunction) : - LanguageRepresentationFunction(arch, owner, highLevelILFunction), m_highLevelIL(highLevelILFunction) +PseudoCFunction::PseudoCFunction(LanguageRepresentationFunctionType* type, Architecture* arch, Function* owner, + HighLevelILFunction* highLevelILFunction) : + LanguageRepresentationFunction(type, arch, owner, highLevelILFunction), m_highLevelIL(highLevelILFunction) { } @@ -2766,7 +2766,7 @@ PseudoCFunctionType::PseudoCFunctionType(): LanguageRepresentationFunctionType(" Ref<LanguageRepresentationFunction> PseudoCFunctionType::Create(Architecture* arch, Function* owner, HighLevelILFunction* highLevelILFunction) { - return new PseudoCFunction(arch, owner, highLevelILFunction); + return new PseudoCFunction(this, arch, owner, highLevelILFunction); } diff --git a/lang/c/pseudoc.h b/lang/c/pseudoc.h index 024be2f1..feb64552 100644 --- a/lang/c/pseudoc.h +++ b/lang/c/pseudoc.h @@ -51,7 +51,8 @@ protected: const BinaryNinja::HighLevelILInstruction& instr, BinaryNinja::HighLevelILTokenEmitter& tokens) override; public: - PseudoCFunction(BinaryNinja::Architecture* arch, BinaryNinja::Function* owner, BinaryNinja::HighLevelILFunction* highLevelILFunction); + PseudoCFunction(BinaryNinja::LanguageRepresentationFunctionType* type, BinaryNinja::Architecture* arch, + BinaryNinja::Function* owner, BinaryNinja::HighLevelILFunction* highLevelILFunction); std::string GetAnnotationStartString() const override; std::string GetAnnotationEndString() const override; diff --git a/lang/rust/pseudorust.cpp b/lang/rust/pseudorust.cpp index 837a7932..a1f7defe 100644 --- a/lang/rust/pseudorust.cpp +++ b/lang/rust/pseudorust.cpp @@ -7,9 +7,9 @@ using namespace std; using namespace BinaryNinja; -PseudoRustFunction::PseudoRustFunction( - Architecture* arch, Function* owner, HighLevelILFunction* highLevelILFunction) : - LanguageRepresentationFunction(arch, owner, highLevelILFunction), m_highLevelIL(highLevelILFunction) +PseudoRustFunction::PseudoRustFunction(LanguageRepresentationFunctionType* type, Architecture* arch, Function* owner, + HighLevelILFunction* highLevelILFunction) : + LanguageRepresentationFunction(type, arch, owner, highLevelILFunction), m_highLevelIL(highLevelILFunction) { } @@ -2814,7 +2814,7 @@ PseudoRustFunctionType::PseudoRustFunctionType(): LanguageRepresentationFunction Ref<LanguageRepresentationFunction> PseudoRustFunctionType::Create(Architecture* arch, Function* owner, HighLevelILFunction* highLevelILFunction) { - return new PseudoRustFunction(arch, owner, highLevelILFunction); + return new PseudoRustFunction(this, arch, owner, highLevelILFunction); } diff --git a/lang/rust/pseudorust.h b/lang/rust/pseudorust.h index a2ab9584..96e656c2 100644 --- a/lang/rust/pseudorust.h +++ b/lang/rust/pseudorust.h @@ -59,7 +59,8 @@ protected: virtual void EndLines(const BinaryNinja::HighLevelILInstruction& instr, BinaryNinja::HighLevelILTokenEmitter& tokens) override; public: - PseudoRustFunction(BinaryNinja::Architecture* arch, BinaryNinja::Function* owner, BinaryNinja::HighLevelILFunction* highLevelILFunction); + PseudoRustFunction(BinaryNinja::LanguageRepresentationFunctionType* type, BinaryNinja::Architecture* arch, + BinaryNinja::Function* owner, BinaryNinja::HighLevelILFunction* highLevelILFunction); std::string GetAnnotationStartString() const override; std::string GetAnnotationEndString() const override; diff --git a/languagerepresentation.cpp b/languagerepresentation.cpp index 0930c0b8..1af38b4b 100644 --- a/languagerepresentation.cpp +++ b/languagerepresentation.cpp @@ -4,7 +4,8 @@ using namespace BinaryNinja; using namespace std; -LanguageRepresentationFunction::LanguageRepresentationFunction(Architecture* arch, Function* func, HighLevelILFunction* highLevelIL) +LanguageRepresentationFunction::LanguageRepresentationFunction( + LanguageRepresentationFunctionType* type, Architecture* arch, Function* func, HighLevelILFunction* highLevelIL) { BNCustomLanguageRepresentationFunction callbacks; callbacks.context = this; @@ -20,8 +21,8 @@ LanguageRepresentationFunction::LanguageRepresentationFunction(Architecture* arc callbacks.getAnnotationStartString = GetAnnotationStartStringCallback; callbacks.getAnnotationEndString = GetAnnotationEndStringCallback; AddRefForRegistration(); - m_object = BNCreateCustomLanguageRepresentationFunction(arch->GetObject(), func->GetObject(), - highLevelIL->GetObject(), &callbacks); + m_object = BNCreateCustomLanguageRepresentationFunction( + type->GetObject(), arch->GetObject(), func->GetObject(), highLevelIL->GetObject(), &callbacks); } @@ -112,6 +113,12 @@ BNHighlightColor LanguageRepresentationFunction::GetHighlight(BasicBlock* block) } +Ref<LanguageRepresentationFunctionType> LanguageRepresentationFunction::GetLanguage() const +{ + return new CoreLanguageRepresentationFunctionType(BNGetLanguageRepresentationType(m_object)); +} + + Ref<Architecture> LanguageRepresentationFunction::GetArchitecture() const { return new CoreArchitecture(BNGetLanguageRepresentationArchitecture(m_object)); @@ -312,6 +319,7 @@ void LanguageRepresentationFunctionType::Register(LanguageRepresentationFunction callbacks.isValid = IsValidCallback; callbacks.getTypePrinter = GetTypePrinterCallback; callbacks.getTypeParser = GetTypeParserCallback; + callbacks.getLineFormatter = GetLineFormatterCallback; callbacks.getFunctionTypeTokens = GetFunctionTypeTokensCallback; callbacks.freeLines = FreeLinesCallback; @@ -363,6 +371,16 @@ BNTypeParser* LanguageRepresentationFunctionType::GetTypeParserCallback(void* ct } +BNLineFormatter* LanguageRepresentationFunctionType::GetLineFormatterCallback(void* ctxt) +{ + LanguageRepresentationFunctionType* type = (LanguageRepresentationFunctionType*)ctxt; + Ref<LineFormatter> result = type->GetLineFormatter(); + if (!result) + return nullptr; + return result->GetObject(); +} + + BNDisassemblyTextLine* LanguageRepresentationFunctionType::GetFunctionTypeTokensCallback( void* ctxt, BNFunction* func, BNDisassemblySettings* settings, size_t* count) { @@ -472,6 +490,15 @@ Ref<TypeParser> CoreLanguageRepresentationFunctionType::GetTypeParser() } +Ref<LineFormatter> CoreLanguageRepresentationFunctionType::GetLineFormatter() +{ + BNLineFormatter* formatter = BNGetLanguageRepresentationFunctionTypeLineFormatter(m_object); + if (!formatter) + return nullptr; + return new CoreLineFormatter(formatter); +} + + vector<DisassemblyTextLine> CoreLanguageRepresentationFunctionType::GetFunctionTypeTokens( Function* func, DisassemblySettings* settings) { diff --git a/lineformatter.cpp b/lineformatter.cpp new file mode 100644 index 00000000..71da6b6e --- /dev/null +++ b/lineformatter.cpp @@ -0,0 +1,225 @@ +// Copyright (c) 2024 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. + +#include "binaryninjaapi.h" + +using namespace BinaryNinja; +using namespace std; + + +LineFormatterSettings LineFormatterSettings::GetDefault(DisassemblySettings* settings, HighLevelILFunction* func) +{ + BNLineFormatterSettings* apiObj = + BNGetDefaultLineFormatterSettings(settings ? settings->GetObject() : nullptr, func->GetObject()); + LineFormatterSettings result = FromAPIObject(apiObj); + BNFreeLineFormatterSettings(apiObj); + return result; +} + + +LineFormatterSettings LineFormatterSettings::GetLanguageRepresentationSettings( + DisassemblySettings* settings, LanguageRepresentationFunction* func) +{ + BNLineFormatterSettings* apiObj = + BNGetLanguageRepresentationLineFormatterSettings(settings ? settings->GetObject() : nullptr, func->GetObject()); + LineFormatterSettings result = FromAPIObject(apiObj); + BNFreeLineFormatterSettings(apiObj); + return result; +} + + +LineFormatterSettings LineFormatterSettings::FromAPIObject(const BNLineFormatterSettings* settings) +{ + LineFormatterSettings result; + result.highLevelIL = new HighLevelILFunction(BNNewHighLevelILFunctionReference(settings->highLevelIL)); + result.desiredLineLength = settings->desiredLineLength; + result.minimumContentLength = settings->minimumContentLength; + result.tabWidth = settings->tabWidth; + result.languageName = settings->languageName; + result.commentStartString = settings->commentStartString; + result.commentEndString = settings->commentEndString; + result.annotationStartString = settings->annotationStartString; + result.annotationEndString = settings->annotationEndString; + return result; +} + + +BNLineFormatterSettings LineFormatterSettings::ToAPIObject() const +{ + BNLineFormatterSettings result; + result.highLevelIL = highLevelIL->GetObject(); + result.desiredLineLength = desiredLineLength; + result.minimumContentLength = minimumContentLength; + result.tabWidth = tabWidth; + result.languageName = (char*)languageName.c_str(); + result.commentStartString = (char*)commentStartString.c_str(); + result.commentEndString = (char*)commentEndString.c_str(); + result.annotationStartString = (char*)annotationStartString.c_str(); + result.annotationEndString = (char*)annotationEndString.c_str(); + return result; +} + + +LineFormatter::LineFormatter(const string& name) : m_nameForRegister(name) {} + + +LineFormatter::LineFormatter(BNLineFormatter* formatter) +{ + m_object = formatter; +} + + +void LineFormatter::Register(LineFormatter* formatter) +{ + BNCustomLineFormatter cb; + cb.context = formatter; + cb.formatLines = FormatLinesCallback; + cb.freeLines = FreeLinesCallback; + + formatter->AddRefForRegistration(); + formatter->m_object = BNRegisterLineFormatter(formatter->m_nameForRegister.c_str(), &cb); +} + + +BNDisassemblyTextLine* LineFormatter::FormatLinesCallback(void* ctxt, BNDisassemblyTextLine* inLines, size_t inCount, + const BNLineFormatterSettings* settings, size_t* outCount) +{ + LineFormatter* formatter = (LineFormatter*)ctxt; + + vector<DisassemblyTextLine> input; + input.reserve(inCount); + for (size_t i = 0; i < inCount; i++) + { + DisassemblyTextLine line; + line.addr = inLines[i].addr; + line.instrIndex = inLines[i].instrIndex; + line.highlight = inLines[i].highlight; + line.tokens = InstructionTextToken::ConvertInstructionTextTokenList(inLines[i].tokens, inLines[i].count); + line.tags = Tag::ConvertTagList(inLines[i].tags, inLines[i].tagCount); + input.push_back(line); + } + + vector<DisassemblyTextLine> outLines = + formatter->FormatLines(input, LineFormatterSettings::FromAPIObject(settings)); + + *outCount = outLines.size(); + BNDisassemblyTextLine* buf = new BNDisassemblyTextLine[outLines.size()]; + for (size_t i = 0; i < outLines.size(); i++) + { + const DisassemblyTextLine& line = outLines[i]; + buf[i].addr = line.addr; + buf[i].instrIndex = line.instrIndex; + buf[i].highlight = line.highlight; + buf[i].tokens = InstructionTextToken::CreateInstructionTextTokenList(line.tokens); + buf[i].count = line.tokens.size(); + buf[i].tags = Tag::CreateTagList(line.tags, &(buf[i].tagCount)); + } + + return buf; +} + + +void LineFormatter::FreeLinesCallback(void*, BNDisassemblyTextLine* lines, size_t count) +{ + for (size_t i = 0; i < count; i++) + { + InstructionTextToken::FreeInstructionTextTokenList(lines[i].tokens, lines[i].count); + Tag::FreeTagList(lines[i].tags, lines[i].tagCount); + } + delete[] lines; +} + + +vector<Ref<LineFormatter>> LineFormatter::GetList() +{ + size_t count; + BNLineFormatter** list = BNGetLineFormatterList(&count); + vector<Ref<LineFormatter>> result; + for (size_t i = 0; i < count; i++) + result.push_back(new CoreLineFormatter(list[i])); + BNFreeLineFormatterList(list); + return result; +} + + +Ref<LineFormatter> LineFormatter::GetByName(const string& name) +{ + BNLineFormatter* result = BNGetLineFormatterByName(name.c_str()); + if (!result) + return nullptr; + return new CoreLineFormatter(result); +} + + +Ref<LineFormatter> LineFormatter::GetDefault() +{ + BNLineFormatter* result = BNGetDefaultLineFormatter(); + if (!result) + return nullptr; + return new CoreLineFormatter(result); +} + + +CoreLineFormatter::CoreLineFormatter(BNLineFormatter* formatter) : LineFormatter(formatter) {} + + +vector<DisassemblyTextLine> CoreLineFormatter::FormatLines( + const vector<DisassemblyTextLine>& lines, const LineFormatterSettings& settings) +{ + size_t inCount = lines.size(); + BNDisassemblyTextLine* inLines = new BNDisassemblyTextLine[lines.size()]; + for (size_t i = 0; i < lines.size(); i++) + { + const DisassemblyTextLine& line = lines[i]; + inLines[i].addr = line.addr; + inLines[i].instrIndex = line.instrIndex; + inLines[i].highlight = line.highlight; + inLines[i].tokens = InstructionTextToken::CreateInstructionTextTokenList(line.tokens); + inLines[i].count = line.tokens.size(); + inLines[i].tags = Tag::CreateTagList(line.tags, &(inLines[i].tagCount)); + } + + size_t outCount = 0; + BNLineFormatterSettings apiSettings = settings.ToAPIObject(); + BNDisassemblyTextLine* outLines = BNFormatLines(m_object, inLines, inCount, &apiSettings, &outCount); + + for (size_t i = 0; i < inCount; i++) + { + InstructionTextToken::FreeInstructionTextTokenList(inLines[i].tokens, inLines[i].count); + Tag::FreeTagList(inLines[i].tags, inLines[i].tagCount); + } + delete[] inLines; + + vector<DisassemblyTextLine> result; + result.reserve(outCount); + for (size_t i = 0; i < outCount; i++) + { + DisassemblyTextLine line; + line.addr = outLines[i].addr; + line.instrIndex = outLines[i].instrIndex; + line.highlight = outLines[i].highlight; + line.tokens = InstructionTextToken::ConvertInstructionTextTokenList(outLines[i].tokens, outLines[i].count); + line.tags = Tag::ConvertTagList(outLines[i].tags, outLines[i].tagCount); + result.push_back(line); + } + + BNFreeDisassemblyTextLines(outLines, outCount); + return result; +} diff --git a/python/__init__.py b/python/__init__.py index 9b5a394e..1cdc541e 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -80,6 +80,7 @@ from .externallibrary import * from .undo import * from .fileaccessor import * from .languagerepresentation import * +from .lineformatter import * # We import each of these by name to prevent conflicts between # log.py and the function 'log' which we don't import below from .log import ( diff --git a/python/examples/pseudo_python.py b/python/examples/pseudo_python.py index 6a9e401b..655039fe 100644 --- a/python/examples/pseudo_python.py +++ b/python/examples/pseudo_python.py @@ -316,16 +316,7 @@ class PseudoPythonFunction(LanguageRepresentationFunction): tokens.append(InstructionTextToken(InstructionTextTokenType.TextToken, ": ")) tokens.append(instr.var.type.get_tokens()) elif instr.operation == HighLevelILOperation.HLIL_FLOAT_CONST: - # The constant value in the instruction contains the raw bits of the floating point value. Convert - # this to a floating point value and display it. - if instr.size == 4: - value = struct.unpack("<f", struct.pack("<I", instr.constant))[0] - tokens.append(InstructionTextToken(InstructionTextTokenType.FloatToken, f"{value:g}")) - elif instr.size == 8: - value = struct.unpack("<d", struct.pack("<Q", instr.constant))[0] - tokens.append(InstructionTextToken(InstructionTextTokenType.FloatToken, f"{value:g}")) - else: - tokens.append_integer_text_token(instr, instr.constant, instr.size) + tokens.append(InstructionTextToken(InstructionTextTokenType.FloatingPointToken, f"{instr.constant:g}")) elif instr.operation == HighLevelILOperation.HLIL_CONST: # Check for bool type. Display these as True or False. The default handling will use C style # booleans instead of Python style. @@ -1075,7 +1066,7 @@ class PseudoPythonFunctionType(LanguageRepresentationFunctionType): language_name = "Pseudo Python" def create(self, arch: Architecture, owner: Function, hlil: HighLevelILFunction): - return PseudoPythonFunction(arch, owner, hlil) + return PseudoPythonFunction(self, arch, owner, hlil) def function_type_tokens(self, func: Function, settings: DisassemblySettings) -> DisassemblyTextLine: tokens = [] diff --git a/python/function.py b/python/function.py index e962d91f..80f0a37c 100644 --- a/python/function.py +++ b/python/function.py @@ -135,6 +135,18 @@ class DisassemblySettings: option = DisassemblyOption[option] core.BNSetDisassemblySettingsOption(self.handle, option, state) + @staticmethod + def default_settings() -> 'DisassemblySettings': + return DisassemblySettings(core.BNDefaultDisassemblySettings()) + + @staticmethod + def default_graph_settings() -> 'DisassemblySettings': + return DisassemblySettings(core.BNDefaultGraphDisassemblySettings()) + + @staticmethod + def default_linear_settings() -> 'DisassemblySettings': + return DisassemblySettings(core.BNDefaultLinearDisassemblySettings()) + @dataclass class ILReferenceSource: @@ -3355,6 +3367,52 @@ class DisassemblyTextLine: return f"<disassemblyTextLine {self}>" return f"<disassemblyTextLine {self.address:#x}: {self}>" + @property + def total_width(self): + return sum(token.width for token in self.tokens) + + def _find_address_and_indentation_tokens(self, callback): + start_token = 0 + for i in range(len(self.tokens)): + if self.tokens[i].type == InstructionTextTokenType.AddressSeparatorToken: + start_token = i + 1 + break + + for token in self.tokens[:start_token]: + callback(token) + + for token in self.tokens[start_token:]: + if token.type in [InstructionTextTokenType.AddressDisplayToken, + InstructionTextTokenType.AddressSeparatorToken, + InstructionTextTokenType.CollapseStateIndicatorToken]: + callback(token) + continue + if len(token.text) != 0 and not token.text.isspace(): + break + callback(token) + + @property + def address_and_indentation_width(self): + result = 0 + + def sum_width(token): + nonlocal result + result += token.width + + self._find_address_and_indentation_tokens(sum_width) + return result + + @property + def address_and_indentation_tokens(self): + result = [] + + def collect_tokens(token): + nonlocal result + result.append(token) + + self._find_address_and_indentation_tokens(collect_tokens) + return result + class DisassemblyTextRenderer: def __init__( diff --git a/python/highlevelil.py b/python/highlevelil.py index 1d7feb71..85be4794 100644 --- a/python/highlevelil.py +++ b/python/highlevelil.py @@ -26,7 +26,10 @@ from enum import Enum # Binary Ninja components from . import _binaryninjacore as core -from .enums import HighLevelILOperation, DataFlowQueryOption, FunctionGraphType, ILInstructionAttribute, StringType +from .enums import ( + HighLevelILOperation, DataFlowQueryOption, FunctionGraphType, ILInstructionAttribute, StringType, + DisassemblyOption +) from . import function from . import binaryview from . import architecture @@ -331,7 +334,9 @@ class HighLevelILInstruction(BaseILInstruction): return ILInstruction[instr.operation](func, expr_index, core_instr, as_ast, instr_index) def __str__(self): - lines = self.lines + settings = function.DisassemblySettings.default_settings() + settings.set_option(DisassemblyOption.DisableLineFormatting) + lines = self.get_lines(settings) if lines is None: return "invalid" result = [] @@ -343,7 +348,9 @@ class HighLevelILInstruction(BaseILInstruction): return '\n'.join(result) def __repr__(self): - lines = self.lines + settings = function.DisassemblySettings.default_settings() + settings.set_option(DisassemblyOption.DisableLineFormatting) + lines = self.get_lines(settings) continuation = "" if lines is None: first_line = "<invalid>" @@ -386,26 +393,14 @@ class HighLevelILInstruction(BaseILInstruction): @property def tokens(self) -> TokenList: """HLIL tokens taken from the HLIL text lines (read-only) -- does not include newlines or indentation, use lines for that information""" - return [token for line in self.lines for token in line.tokens] + settings = function.DisassemblySettings.default_settings() + settings.set_option(DisassemblyOption.DisableLineFormatting) + return [token for line in self.get_lines(settings) for token in line.tokens] @property def lines(self) -> LinesType: """HLIL text lines (read-only)""" - count = ctypes.c_ulonglong() - lines = core.BNGetHighLevelILExprText(self.function.handle, self.expr_index, self.as_ast, count, None) - assert lines is not None, "core.BNGetHighLevelILExprText returned None" - try: - for i in range(0, count.value): - addr = lines[i].addr - if lines[i].instrIndex != 0xffffffffffffffff: - il_instr = self.function[lines[i].instrIndex] - else: - il_instr = None - color = highlight.HighlightColor._from_core_struct(lines[i].highlight) - tokens = function.InstructionTextToken._from_core_struct(lines[i].tokens, lines[i].count) - yield function.DisassemblyTextLine(tokens, addr, il_instr, color) - finally: - core.BNFreeDisassemblyTextLines(lines, count.value) + return self.get_lines() @property def prefix_operands(self) -> List[Union[HighLevelILOperandType, HighLevelILOperationAndSize]]: @@ -918,6 +913,26 @@ class HighLevelILInstruction(BaseILInstruction): def has_side_effects(self) -> bool: return core.BNHighLevelILHasSideEffects(self.function.handle, self.expr_index) + def get_lines(self, settings: Optional['function.DisassemblySettings'] = None) -> LinesType: + """Gets HLIL text lines with optional settings""" + if settings is not None: + settings = settings.handle + count = ctypes.c_ulonglong() + lines = core.BNGetHighLevelILExprText(self.function.handle, self.expr_index, self.as_ast, count, settings) + assert lines is not None, "core.BNGetHighLevelILExprText returned None" + try: + for i in range(0, count.value): + addr = lines[i].addr + if lines[i].instrIndex != 0xffffffffffffffff: + il_instr = self.function[lines[i].instrIndex] + else: + il_instr = None + color = highlight.HighlightColor._from_core_struct(lines[i].highlight) + tokens = function.InstructionTextToken._from_core_struct(lines[i].tokens, lines[i].count) + yield function.DisassemblyTextLine(tokens, addr, il_instr, color) + finally: + core.BNFreeDisassemblyTextLines(lines, count.value) + @dataclass(frozen=True, repr=False, eq=False) class HighLevelILUnaryBase(HighLevelILInstruction, UnaryOperation): diff --git a/python/languagerepresentation.py b/python/languagerepresentation.py index ac34c860..a0a5a234 100644 --- a/python/languagerepresentation.py +++ b/python/languagerepresentation.py @@ -30,6 +30,7 @@ from . import binaryview from . import function from . import highlevelil from . import highlight +from . import lineformatter from . import variable from . import types from .log import log_error @@ -327,7 +328,8 @@ class LanguageRepresentationFunction: annotation_end_string = "}" def __init__( - self, arch: Optional['architecture.Architecture'] = None, owner: Optional['function.Function'] = None, + self, func_type: Optional['LanguageRepresentationFunctionType'] = None, + arch: Optional['architecture.Architecture'] = None, owner: Optional['function.Function'] = None, hlil: Optional['highlevelil.HighLevelILFunction'] = None, handle=None ): if handle is None: @@ -354,7 +356,9 @@ class LanguageRepresentationFunction: self.comment_end_string = self.__class__.comment_end_string self.annotation_start_string = self.__class__.annotation_start_string self.annotation_end_string = self.__class__.annotation_end_string - _handle = core.BNCreateCustomLanguageRepresentationFunction(arch.handle, owner.handle, hlil.handle, self._cb) + _handle = core.BNCreateCustomLanguageRepresentationFunction( + func_type.handle, arch.handle, owner.handle, hlil.handle, self._cb + ) assert _handle is not None else: self.comment_start_string = core.BNGetLanguageRepresentationFunctionCommentStartString(handle) @@ -621,6 +625,7 @@ class LanguageRepresentationFunctionType(metaclass=_LanguageRepresentationFuncti self._cb.isValid = self._cb.isValid.__class__(self._is_valid) self._cb.getTypePrinter = self._cb.getTypePrinter.__class__(self._type_printer) self._cb.getTypeParser = self._cb.getTypeParser.__class__(self._type_parser) + self._cb.getLineFormatter = self._cb.getLineFormatter.__class__(self._line_formatter) self._cb.getFunctionTypeTokens = self._cb.getFunctionTypeTokens.__class__(self._function_type_tokens) self._cb.freeLines = self._cb.freeLines.__class__(self._free_lines) self.handle = core.BNRegisterLanguageRepresentationFunctionType(self.__class__.language_name, self._cb) @@ -672,6 +677,16 @@ class LanguageRepresentationFunctionType(metaclass=_LanguageRepresentationFuncti log_error(traceback.format_exc()) return None + def _line_formatter(self, ctxt): + try: + result = self.line_formatter + if result is None: + return None + return ctypes.cast(result.handle, ctypes.c_void_p).value + except: + log_error(traceback.format_exc()) + return None + def _function_type_tokens( self, ctxt, func: core.BNFunctionHandle, settings: Optional[core.BNDisassemblySettingsHandle], count: ctypes.POINTER(ctypes.c_ulonglong) @@ -751,6 +766,14 @@ class LanguageRepresentationFunctionType(metaclass=_LanguageRepresentationFuncti """ return None + @property + def line_formatter(self) -> Optional['lineformatter.LineFormatter']: + """ + Returns the line formatter for formatting lines in this language. If ``None`` is returned, the default + line formatter will be used. + """ + return None + def function_type_tokens( self, func: 'function.Function', settings: Optional['function.DisassemblySettings'] ) -> List['function.DisassemblyTextLine']: @@ -806,6 +829,13 @@ class CoreLanguageRepresentationFunctionType(LanguageRepresentationFunctionType) return None return binaryninja.typeparser.TypeParser(handle=result) + @property + def line_formatter(self) -> Optional['lineformatter.LineFormatter']: + result = core.BNGetLanguageRepresentationFunctionTypeLineFormatter(self.handle) + if result is None: + return None + return binaryninja.lineformatter.LineFormatter(handle=result) + def function_type_tokens( self, func: 'function.Function', settings: Optional['function.DisassemblySettings'] ) -> List['function.DisassemblyTextLine']: diff --git a/python/lineformatter.py b/python/lineformatter.py new file mode 100644 index 00000000..7f74572b --- /dev/null +++ b/python/lineformatter.py @@ -0,0 +1,291 @@ +# Copyright (c) 2025 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 +import traceback +from dataclasses import dataclass +from typing import List, Optional, Union + +# Binary Ninja components +import binaryninja +from . import _binaryninjacore as core +from . import function +from . import highlevelil +from . import highlight +from . import languagerepresentation +from .log import log_error +from .enums import HighlightStandardColor + + +@dataclass(frozen=True) +class LineFormatterSettings: + hlil: highlevelil.HighLevelILFunction + desired_line_length: int + minimum_content_length: int + tab_width: int + language_name: Optional[str] + comment_start_string: str + comment_end_string: str + annotation_start_string: str + annotation_end_string: str + + @staticmethod + def default(settings: Optional['function.DisassemblySettings'], hlil: 'highlevelil.HighLevelILFunction') -> 'LineFormatterSettings': + """ + Gets the default line formatter settings for High Level IL code. + """ + if settings is not None: + settings = settings.handle + api_obj = core.BNGetDefaultLineFormatterSettings(settings, hlil.handle) + result = LineFormatterSettings._from_core_struct(api_obj[0]) + core.BNFreeLineFormatterSettings(api_obj) + return result + + @staticmethod + def language_representation_settings( + settings: Optional['function.DisassemblySettings'], func: 'languagerepresentation.LanguageRepresentationFunction' + ) -> 'LineFormatterSettings': + """ + Gets the default line formatter settings for a language representation function. + """ + if settings is not None: + settings = settings.handle + api_obj = core.BNGetLanguageRepresentationLineFormatterSettings(settings, func.handle) + result = LineFormatterSettings._from_core_struct(api_obj[0]) + core.BNFreeLineFormatterSettings(api_obj) + return result + + @staticmethod + def _from_core_struct(settings: core.BNLineFormatterSettings) -> 'LineFormatterSettings': + if len(settings.languageName) == 0: + language_name = None + else: + language_name = settings.languageName + hlil = highlevelil.HighLevelILFunction(handle=core.BNNewHighLevelILFunctionReference(settings.highLevelIL)) + return LineFormatterSettings( + hlil, settings.desiredLineLength, settings.minimumContentLength, settings.tabWidth, language_name, + settings.commentStartString, settings.commentEndString, + settings.annotationStartString, settings.annotationEndString + ) + + def _to_core_struct(self) -> core.BNLineFormatterSettings: + result = core.BNLineFormatterSettings() + result.highLevelIL = self.hlil.handle + result.desiredLineLength = self.desired_line_length + result.minimumContentLength = self.minimum_content_length + result.tabWidth = self.tab_width + result.languageName = self.language_name if self.language_name is not None else "" + result.commentStartString = self.comment_start_string + result.commentEndString = self.comment_end_string + result.annotationStartString = self.annotation_start_string + result.annotationEndString = self.annotation_end_string + return result + + +class _LineFormatterMetaClass(type): + def __iter__(self): + binaryninja._init_plugins() + count = ctypes.c_ulonglong() + types = core.BNGetLineFormatterList(count) + assert types is not None, "core.BNGetLineFormatterList returned None" + try: + for i in range(0, count.value): + yield CoreLineFormatter(handle=types[i]) + finally: + core.BNFreeLineFormatterList(types) + + def __getitem__(cls, value): + binaryninja._init_plugins() + lang = core.BNGetLineFormatterByName(str(value)) + if lang is None: + raise KeyError("'%s' is not a valid formatter" % str(value)) + return CoreLineFormatter(handle=lang) + + +class LineFormatter(metaclass=_LineFormatterMetaClass): + """ + ``class LineFormatter`` represents a custom line formatter, which can reformat code in High Level IL + and high level language representations. + """ + _registered_formatters = [] + formatter_name = None + + def __init__(self, handle=None): + if handle is not None: + self.handle = core.handle_of_type(handle, core.BNLineFormatter) + + def register(self): + """Registers the line formatter.""" + if self.__class__.formatter_name is None: + raise ValueError("formatter_name is missing") + self._cb = core.BNCustomLineFormatter() + self._cb.context = 0 + self._cb.formatLines = self._cb.formatLines.__class__(self._format_lines) + self._cb.freeLines = self._cb.freeLines.__class__(self._free_lines) + self.handle = core.BNRegisterLineFormatter(self.__class__.formatter_name, self._cb) + self.__class__._registered_formatters.append(self) + + def _format_lines( + self, ctxt, in_lines, in_count: int, settings: core.BNLineFormatterSettingsHandle, + out_count: ctypes.POINTER(ctypes.c_ulonglong) + ): + try: + settings = settings[0] + if len(settings.languageName) == 0: + language_name = None + else: + language_name = settings.languageName + hlil = highlevelil.HighLevelILFunction(handle=core.BNNewHighLevelILFunctionReference(settings.highLevelIL)) + settings = LineFormatterSettings( + hlil, settings.desiredLineLength, settings.minimumContentLength, settings.tabWidth, language_name, + settings.commentStartString, settings.commentEndString, + settings.annotationStartString, settings.annotationEndString + ) + + lines = [] + if in_lines is not None: + for i in range(0, in_count): + addr = in_lines[i].addr + if in_lines[i].instrIndex != 0xffffffffffffffff: + il_instr = hlil[in_lines[i].instrIndex] # type: ignore + else: + il_instr = None + color = highlight.HighlightColor._from_core_struct(in_lines[i].highlight) + tokens = function.InstructionTextToken._from_core_struct(in_lines[i].tokens, in_lines[i].count) + lines.append(function.DisassemblyTextLine(tokens, addr, il_instr, color)) + + lines = self.format_lines(lines, settings) + + out_count[0] = len(lines) + self.line_buf = (core.BNDisassemblyTextLine * len(lines))() + for i in range(len(lines)): + line = lines[i] + color = line.highlight + if not isinstance(color, HighlightStandardColor) and not isinstance(color, highlight.HighlightColor): + raise ValueError("Specified color is not one of HighlightStandardColor, highlight.HighlightColor") + if isinstance(color, HighlightStandardColor): + color = highlight.HighlightColor(color) + self.line_buf[i].highlight = color._to_core_struct() + if line.address is None: + if len(line.tokens) > 0: + self.line_buf[i].addr = line.tokens[0].address + else: + self.line_buf[i].addr = 0 + else: + self.line_buf[i].addr = line.address + if line.il_instruction is not None: + self.line_buf[i].instrIndex = line.il_instruction.instr_index + else: + self.line_buf[i].instrIndex = 0xffffffffffffffff + + self.line_buf[i].count = len(line.tokens) + self.line_buf[i].tokens = function.InstructionTextToken._get_core_struct(line.tokens) + + return ctypes.cast(self.line_buf, ctypes.c_void_p).value + except: + log_error(traceback.format_exc()) + out_count[0] = 0 + return None + + def _free_lines(self, ctxt, lines, count): + self.line_buf = None + + def format_lines( + self, in_lines: List['function.DisassemblyTextLine'], settings: 'LineFormatterSettings' + ) -> List['function.DisassemblyTextLine']: + """ + Reformats the given list of lines. Returns a new list of lines containing the reformatted code. + """ + raise NotImplementedError + + @property + def name(self) -> str: + if hasattr(self, 'handle'): + return core.BNGetLineFormatterName(self.handle) + return self.__class__.formatter_name + + def __repr__(self): + return f"<LineFormatter: {self.name}>" + + +_formatter_cache = {} + + +class CoreLineFormatter(LineFormatter): + def __init__(self, handle: core.BNLineFormatter): + super(CoreLineFormatter, self).__init__(handle=handle) + if type(self) is CoreLineFormatter: + global _formatter_cache + _formatter_cache[ctypes.addressof(handle.contents)] = self + + def format_lines( + self, in_lines: List['function.DisassemblyTextLine'], settings: 'LineFormatterSettings' + ) -> List['function.DisassemblyTextLine']: + line_buf = (core.BNDisassemblyTextLine * len(in_lines))() + for i in range(len(in_lines)): + line = in_lines[i] + color = line.highlight + if not isinstance(color, HighlightStandardColor) and not isinstance(color, highlight.HighlightColor): + raise ValueError("Specified color is not one of HighlightStandardColor, highlight.HighlightColor") + if isinstance(color, HighlightStandardColor): + color = highlight.HighlightColor(color) + line_buf[i].highlight = color._to_core_struct() + if line.address is None: + if len(line.tokens) > 0: + line_buf[i].addr = line.tokens[0].address + else: + line_buf[i].addr = 0 + else: + line_buf[i].addr = line.address + if line.il_instruction is not None: + line_buf[i].instrIndex = line.il_instruction.instr_index + else: + line_buf[i].instrIndex = 0xffffffffffffffff + + line_buf[i].count = len(line.tokens) + line_buf[i].tokens = function.InstructionTextToken._get_core_struct(line.tokens) + + count = ctypes.c_ulonglong() + lines = core.BNFormatLines(self.handle, line_buf, len(in_lines), settings._to_core_struct(), count) + + result = [] + if lines is not None: + result = [] + for i in range(0, count.value): + addr = lines[i].addr + if lines[i].instrIndex != 0xffffffffffffffff: + il_instr = settings.hlil[lines[i].instrIndex] # type: ignore + else: + il_instr = None + color = highlight.HighlightColor._from_core_struct(lines[i].highlight) + tokens = function.InstructionTextToken._from_core_struct(lines[i].tokens, lines[i].count) + result.append(function.DisassemblyTextLine(tokens, addr, il_instr, color)) + core.BNFreeDisassemblyTextLines(lines, count.value) + return result + + @classmethod + def _from_cache(cls, handle) -> 'LineFormatter': + """ + Look up a representation type from a given BNLineFormatter handle + :param handle: BNLineFormatter pointer + :return: Formatter instance responsible for this handle + """ + global _formatter_cache + return _formatter_cache.get(ctypes.addressof(handle.contents)) or cls(handle) |
