summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--basicblock.cpp85
-rw-r--r--binaryninjaapi.h97
-rw-r--r--binaryninjacore.h57
-rw-r--r--formatter/generic/CMakeLists.txt45
-rw-r--r--formatter/generic/genericformatter.cpp842
-rw-r--r--formatter/generic/genericformatter.h14
-rw-r--r--lang/c/pseudoc.cpp8
-rw-r--r--lang/c/pseudoc.h3
-rw-r--r--lang/rust/pseudorust.cpp8
-rw-r--r--lang/rust/pseudorust.h3
-rw-r--r--languagerepresentation.cpp33
-rw-r--r--lineformatter.cpp225
-rw-r--r--python/__init__.py1
-rw-r--r--python/examples/pseudo_python.py13
-rw-r--r--python/function.py58
-rw-r--r--python/highlevelil.py53
-rw-r--r--python/languagerepresentation.py34
-rw-r--r--python/lineformatter.py291
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)