summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--basicblock.cpp6
-rw-r--r--binaryninjaapi.h315
-rw-r--r--binaryninjacore.h57
-rw-r--r--ffi.h80
-rw-r--r--flowgraph.cpp44
-rw-r--r--linearviewcursor.cpp26
-rw-r--r--linearviewobject.cpp35
-rw-r--r--plugins/stack_render_layer/CMakeLists.txt46
-rw-r--r--plugins/stack_render_layer/plugin.cpp124
-rw-r--r--python/__init__.py1
-rw-r--r--python/basicblock.py105
-rw-r--r--python/binaryview.py17
-rw-r--r--python/examples/args_render_layer.py213
-rw-r--r--python/examples/follow_reg_render_layer.py209
-rw-r--r--python/flowgraph.py97
-rw-r--r--python/function.py82
-rw-r--r--python/lineardisassembly.py105
-rw-r--r--python/renderlayer.py450
-rw-r--r--renderlayer.cpp329
-rw-r--r--rust/src/basic_block.rs43
-rw-r--r--rust/src/binary_view.rs4
-rw-r--r--rust/src/collaboration/sync.rs6
-rw-r--r--rust/src/disassembly.rs7
-rw-r--r--rust/src/flowgraph.rs134
-rw-r--r--rust/src/function.rs6
-rw-r--r--rust/src/lib.rs1
-rw-r--r--rust/src/linear_view.rs164
-rw-r--r--rust/src/low_level_il.rs2
-rw-r--r--rust/src/medium_level_il/function.rs4
-rw-r--r--rust/src/render_layer.rs410
-rw-r--r--rust/src/workflow.rs4
-rw-r--r--rust/tests/render_layer.rs96
-rw-r--r--ui/disassemblyview.h5
-rw-r--r--ui/flowgraphwidget.h2
-rw-r--r--ui/linearview.h5
35 files changed, 3112 insertions, 122 deletions
diff --git a/basicblock.cpp b/basicblock.cpp
index 67856d88..a56512fe 100644
--- a/basicblock.cpp
+++ b/basicblock.cpp
@@ -592,6 +592,12 @@ bool BasicBlock::IsMediumLevelILBlock() const
}
+bool BasicBlock::IsHighLevelILBlock() const
+{
+ return BNIsHighLevelILBasicBlock(m_object);
+}
+
+
Ref<LowLevelILFunction> BasicBlock::GetLowLevelILFunction() const
{
BNLowLevelILFunction* func = BNGetBasicBlockLowLevelILFunction(m_object);
diff --git a/binaryninjaapi.h b/binaryninjaapi.h
index 92e6b397..709675f7 100644
--- a/binaryninjaapi.h
+++ b/binaryninjaapi.h
@@ -4073,7 +4073,9 @@ namespace BinaryNinja {
Ref<BasicBlock> block;
DisassemblyTextLine contents;
- static LinearDisassemblyLine FromAPIObject(BNLinearDisassemblyLine* line);
+ BNLinearDisassemblyLine GetAPIObject() const;
+ static LinearDisassemblyLine FromAPIObject(const BNLinearDisassemblyLine* line);
+ static void FreeAPIObject(BNLinearDisassemblyLine* line);
};
class NamedTypeReference;
@@ -10414,17 +10416,23 @@ namespace BinaryNinja {
*/
bool IsILBlock() const;
+ /*! Whether the basic block contains Low Level IL
+
+ \return Whether the basic block contains Low Level IL
+ */
+ bool IsLowLevelILBlock() const;
+
/*! Whether the basic block contains Medium Level IL
\return Whether the basic block contains Medium Level IL
*/
- bool IsLowLevelILBlock() const;
+ bool IsMediumLevelILBlock() const;
/*! Whether the basic block contains High Level IL
\return Whether the basic block contains High Level IL
*/
- bool IsMediumLevelILBlock() const;
+ bool IsHighLevelILBlock() const;
/*! Get the Low Level IL Function for this basic block
@@ -11443,6 +11451,12 @@ namespace BinaryNinja {
*/
Ref<FlowGraphNode> GetNode(size_t i);
+ /*! Get the total number of nodes in the graph
+
+ \return Node count
+ */
+ size_t GetNodeCount() const;
+
/*! Whether the FlowGraph has any nodes added
\return Whether the FlowGraph has any nodes added
@@ -11451,12 +11465,29 @@ namespace BinaryNinja {
/*! Add a node to this flowgraph
+ \note After the graph has completed layout, this function has no effect.
+
\param node Node to be added.
\return Index of the node
*/
size_t AddNode(FlowGraphNode* node);
+ /*! Replace an existing node in the graph with a new node.
+ Any existing edges referencing the old node will be updated to point to
+ the new node.
+ \note After the graph has completed layout, this function has no effect.
+
+ \param i Index of the node to replace
+ \param newNode New node with which to replace the old node
+ */
+ void ReplaceNode(size_t i, FlowGraphNode* newNode);
+
+ /*! Clear all the nodes in the graph
+
+ \note After the graph has completed layout, this function has no effect.
+ */
+ void ClearNodes();
/*! Flow graph width
@@ -11548,6 +11579,26 @@ namespace BinaryNinja {
void SetOption(BNFlowGraphOption option, bool value = true);
bool IsOptionSet(BNFlowGraphOption option);
+
+ /*! Get the list of Render Layers which will be applied to this Flow Graph,
+ after it calls PopulateNodes.
+
+ \return List of Render Layers
+ */
+ std::vector<class RenderLayer*> GetRenderLayers() const;
+
+ /*! Add a Render Layer to be applied to this Flow Graph. Note that layers will
+ be applied in the order in which they are added.
+
+ \param layer Render Layer to add
+ */
+ void AddRenderLayer(class RenderLayer* layer);
+
+ /*! Remove a Render Layer from being applied to this Flow Graph
+
+ \param layer Render Layer to remove
+ */
+ void RemoveRenderLayer(class RenderLayer* layer);
};
/*!
@@ -17101,6 +17152,26 @@ namespace BinaryNinja {
Ref<LinearViewCursor> Duplicate();
+ /*! Get the list of Render Layers which will be applied to this cursor, at the
+ end of calls to GetLines.
+
+ \return List of Render Layers
+ */
+ std::vector<class RenderLayer*> GetRenderLayers() const;
+
+ /*! Add a Render Layer to be applied to this cursor. Note that layers will
+ be applied in the order in which they are added.
+
+ \param layer Render Layer to add
+ */
+ void AddRenderLayer(class RenderLayer* layer);
+
+ /*! Remove a Render Layer from being applied to this cursor
+
+ \param layer Render Layer to remove
+ */
+ void RemoveRenderLayer(class RenderLayer* layer);
+
static int Compare(LinearViewCursor* a, LinearViewCursor* b);
};
@@ -19007,6 +19078,244 @@ namespace BinaryNinja {
static void AddNamesForOuterStructureMembers(
BinaryView* data, Type* type, const HighLevelILInstruction& var, std::vector<std::string>& nameList);
};
+
+ /*! RenderLayer is a plugin class that allows you to customize the presentation of
+ Linear and Graph view output, adding, changing, or removing lines before they are
+ presented in the UI.
+ */
+ class RenderLayer: public StaticCoreRefCountObject<BNRenderLayer>
+ {
+ std::string m_nameForRegister;
+ static std::unordered_map<BNRenderLayer*, RenderLayer*> g_registeredInstances;
+
+ protected:
+ explicit RenderLayer(const std::string& name);
+ RenderLayer(BNRenderLayer* layer);
+ virtual ~RenderLayer() = default;
+ static void ApplyToFlowGraphCallback(void* ctxt, BNFlowGraph* graph);
+ static void ApplyToLinearViewObjectCallback(
+ void* ctxt,
+ BNLinearViewObject* obj,
+ BNLinearViewObject* prev,
+ BNLinearViewObject* next,
+ BNLinearDisassemblyLine* inLines,
+ size_t inLineCount,
+ BNLinearDisassemblyLine** outLines,
+ size_t* outLineCount
+ );
+ static void FreeLinesCallback(void* ctxt, BNLinearDisassemblyLine* lines, size_t count);
+
+ public:
+ /*! Register a custom Render Layer.
+
+ \param layer Render Layer to register
+ \param enableState Whether the layer should be enabled by default
+ */
+ static void Register(RenderLayer* layer, BNRenderLayerDefaultEnableState enableState = DisabledByDefaultRenderLayerDefaultEnableState);
+
+ /*! Get the list of all currently registered Render Layers.
+
+ \return List of Render Layers
+ */
+ static std::vector<Ref<RenderLayer>> GetList();
+
+ /*! Look up a Render Layer by its name
+
+ \param name Name of Render Layer
+ \return Render Layer, if it exists. Otherwise, nullptr.
+ */
+ static Ref<RenderLayer> GetByName(const std::string& name);
+
+ /*! Get the name of a Render Layer
+
+ \return Render Layer's name
+ */
+ std::string GetName() const;
+
+ /*! Get whether the Render Layer is enabled by default
+
+ \return Default enable state
+ */
+ BNRenderLayerDefaultEnableState GetDefaultEnableState() const;
+
+ /*! Apply this Render Layer to a single Basic Block of Disassembly lines.
+ Subclasses should modify the input `lines` list to make modifications to
+ the presentation of the block.
+
+ \note This function will only handle Disassembly lines, and not any ILs.
+
+ \param block Basic Block containing those lines
+ \param lines Lines of text for the block, to be modified by this function
+ */
+ virtual void ApplyToDisassemblyBlock(
+ Ref<BasicBlock> block,
+ std::vector<DisassemblyTextLine>& lines
+ )
+ {
+ (void)block;
+ (void)lines;
+ }
+
+ /*! Apply this Render Layer to a single Basic Block of Low Level IL lines.
+ Subclasses should modify the input `lines` list to make modifications to
+ the presentation of the block.
+
+ \note This function will only handle Lifted IL/LLIL/LLIL(SSA) lines.
+ You can use the block's `function_graph_type` property to determine which is being handled.
+
+ \param block Basic Block containing those lines
+ \param lines Lines of text for the block, to be modified by this function
+ */
+ virtual void ApplyToLowLevelILBlock(
+ Ref<BasicBlock> block,
+ std::vector<DisassemblyTextLine>& lines
+ )
+ {
+ (void)block;
+ (void)lines;
+ }
+
+ /*! Apply this Render Layer to a single Basic Block of Medium Level IL lines.
+ Subclasses should modify the input `lines` list to make modifications to
+ the presentation of the block.
+
+ \note This function will only handle MLIL/MLIL(SSA)/Mapped MLIL/Mapped MLIL(SSA) lines.
+ You can use the block's `function_graph_type` property to determine which is being handled.
+
+ \param block Basic Block containing those lines
+ \param lines Lines of text for the block, to be modified by this function
+ */
+ virtual void ApplyToMediumLevelILBlock(
+ Ref<BasicBlock> block,
+ std::vector<DisassemblyTextLine>& lines
+ )
+ {
+ (void)block;
+ (void)lines;
+ }
+
+ /*! Apply this Render Layer to a single Basic Block of High Level IL lines.
+ Subclasses should modify the input `lines` list to make modifications to
+ the presentation of the block.
+
+ \note This function will only handle HLIL/HLIL(SSA)/Language Representation lines.
+ You can use the block's `function_graph_type` property to determine which is being handled.
+
+ \warning This function will NOT apply to High Level IL bodies as displayed
+ in Linear View! Those are handled by `ApplyToHighLevelILBody` instead as they
+ do not have a Basic Block associated with them.
+
+ \param block Basic Block containing those lines
+ \param lines Lines of text for the block, to be modified by this function
+ */
+ virtual void ApplyToHighLevelILBlock(
+ Ref<BasicBlock> block,
+ std::vector<DisassemblyTextLine>& lines
+ )
+ {
+ (void)block;
+ (void)lines;
+ }
+
+ /*! Apply this Render Layer to the entire body of a High Level IL function.
+ Subclasses should modify the input `lines` list to make modifications to
+ the presentation of the function.
+
+ \warning This function only applies to Linear View, and not to Graph View!
+ If you want to handle Graph View too, you will need to use `ApplyToHighLevelILBlock`
+ and handle the lines one block at a time.
+
+ \param function Function containing those lines
+ \param lines Lines of text for the function, to be modified by this function
+ */
+ virtual void ApplyToHighLevelILBody(
+ Ref<Function> function,
+ std::vector<LinearDisassemblyLine>& lines
+ )
+ {
+ (void)function;
+ (void)lines;
+ }
+
+ /*! Apply to lines generated by Linear View that are not part of a function.
+ It is up to your implementation to figure out which type of Linear View Object
+ lines these are, and what to do with them.
+
+ \param obj Linear View Object being rendered
+ \param prev Linear View Object located directly above this one
+ \param next Linear View Object located directly below this one
+ \param lines Lines rendered by `obj`, to be modified by this function
+ */
+ virtual void ApplyToMiscLinearLines(
+ Ref<LinearViewObject> obj,
+ Ref<LinearViewObject> prev,
+ Ref<LinearViewObject> next,
+ std::vector<LinearDisassemblyLine>& lines
+ )
+ {
+ (void)obj;
+ (void)prev;
+ (void)next;
+ (void)lines;
+ }
+
+ /*! Apply to lines generated by a Basic Block, of any type. If not overridden, this
+ function will call the appropriate ApplyToXLevelILBlock function.
+
+ \param block Basic Block containing those lines
+ \param lines Lines of text for the block, to be modified by this function
+ */
+ virtual void ApplyToBlock(
+ Ref<BasicBlock> block,
+ std::vector<DisassemblyTextLine>& lines
+ );
+
+ /*! Apply this Render Layer to a Flow Graph, potentially modifying its nodes,
+ their edges, their lines, and their lines' content.
+
+ \note If you override this function, you will need to call the parent
+ implementation if you want to use the higher level ApplyToXLevelILBlock
+ functionality.
+
+ \param graph Graph to modify
+ */
+ virtual void ApplyToFlowGraph(Ref<FlowGraph> graph);
+
+ /*! Apply this Render Layer to the lines produced by a LinearViewObject for rendering
+ in Linear View, potentially modifying the lines and their contents.
+
+ \note If you override this function, you will need to call the parent
+ implementation if you want to use the higher level ApplyToXLevelILBlock
+ functionality.
+
+ \param obj Linear View Object being rendered
+ \param prev Linear View Object located directly above this one
+ \param next Linear View Object located directly below this one
+ \param lines Lines originally rendered by the Linear View Object
+ \return Updated set of lines to display in Linear View
+ */
+ virtual void ApplyToLinearViewObject(
+ Ref<LinearViewObject> obj,
+ Ref<LinearViewObject> prev,
+ Ref<LinearViewObject> next,
+ std::vector<LinearDisassemblyLine>& lines
+ );
+ };
+
+ class CoreRenderLayer: public RenderLayer
+ {
+ public:
+ CoreRenderLayer(BNRenderLayer* layer);
+ virtual ~CoreRenderLayer() = default;
+
+ virtual void ApplyToFlowGraph(Ref<FlowGraph> graph) override;
+ virtual void ApplyToLinearViewObject(
+ Ref<LinearViewObject> obj,
+ Ref<LinearViewObject> prev,
+ Ref<LinearViewObject> next,
+ std::vector<LinearDisassemblyLine>& lines
+ ) override;
+ };
} // namespace BinaryNinja
diff --git a/binaryninjacore.h b/binaryninjacore.h
index bba1c786..73bff230 100644
--- a/binaryninjacore.h
+++ b/binaryninjacore.h
@@ -37,7 +37,7 @@
// Current ABI version for linking to the core. This is incremented any time
// there are changes to the API that affect linking, including new functions,
// new types, or modifications to existing functions or types.
-#define BN_CURRENT_CORE_ABI_VERSION 89
+#define BN_CURRENT_CORE_ABI_VERSION 90
// Minimum ABI version that is supported for loading of plugins. Plugins that
// are linked to an ABI version less than this will not be able to load and
@@ -303,6 +303,7 @@ extern "C"
typedef struct BNFirmwareNinja BNFirmwareNinja;
typedef struct BNFirmwareNinjaReferenceNode BNFirmwareNinjaReferenceNode;
typedef struct BNLineFormatter BNLineFormatter;
+ typedef struct BNRenderLayer BNRenderLayer;
//! Console log levels
typedef enum BNLogLevel
@@ -3562,6 +3563,29 @@ extern "C"
void (*freeLines)(void* ctxt, BNDisassemblyTextLine* lines, size_t count);
} BNCustomLineFormatter;
+ typedef struct BNRenderLayerCallbacks
+ {
+ void* context;
+ void (*applyToFlowGraph)(void* ctxt, BNFlowGraph* graph);
+ void (*applyToLinearViewObject)(
+ void* ctxt,
+ BNLinearViewObject* obj,
+ BNLinearViewObject* prev,
+ BNLinearViewObject* next,
+ BNLinearDisassemblyLine* inLines,
+ size_t inLineCount,
+ BNLinearDisassemblyLine** outLines,
+ size_t* outLineCount
+ );
+ void (*freeLines)(void* ctxt, BNLinearDisassemblyLine* lines, size_t count);
+ } BNRenderLayerCallbacks;
+
+ typedef enum BNRenderLayerDefaultEnableState
+ {
+ DisabledByDefaultRenderLayerDefaultEnableState,
+ EnabledByDefaultRenderLayerDefaultEnableState,
+ AlwaysEnabledRenderLayerDefaultEnableState,
+ } BNRenderLayerDefaultEnableState;
BINARYNINJACOREAPI char* BNAllocString(const char* contents);
BINARYNINJACOREAPI char* BNAllocStringWithLength(const char* contents, size_t len);
@@ -5127,6 +5151,10 @@ extern "C"
BINARYNINJACOREAPI BNLinearDisassemblyLine* BNGetLinearViewCursorLines(BNLinearViewCursor* cursor, size_t* count);
BINARYNINJACOREAPI int BNCompareLinearViewCursors(BNLinearViewCursor* a, BNLinearViewCursor* b);
+ BINARYNINJACOREAPI BNRenderLayer** BNGetLinearViewCursorRenderLayers(BNLinearViewCursor* cursor, size_t* count);
+ BINARYNINJACOREAPI void BNAddLinearViewCursorRenderLayer(BNLinearViewCursor* cursor, BNRenderLayer* layer);
+ BINARYNINJACOREAPI void BNRemoveLinearViewCursorRenderLayer(BNLinearViewCursor* cursor, BNRenderLayer* layer);
+
BINARYNINJACOREAPI void BNDefineDataVariable(BNBinaryView* view, uint64_t addr, BNTypeWithConfidence* type);
BINARYNINJACOREAPI void BNDefineUserDataVariable(BNBinaryView* view, uint64_t addr, BNTypeWithConfidence* type);
BINARYNINJACOREAPI void BNUndefineDataVariable(BNBinaryView* view, uint64_t addr, bool blacklist);
@@ -5550,14 +5578,21 @@ extern "C"
BINARYNINJACOREAPI BNFlowGraphNode** BNGetFlowGraphNodesInRegion(
BNFlowGraph* graph, int left, int top, int right, int bottom, size_t* count);
BINARYNINJACOREAPI void BNFreeFlowGraphNodeList(BNFlowGraphNode** nodes, size_t count);
+ BINARYNINJACOREAPI size_t BNGetFlowGraphNodeCount(BNFlowGraph* graph);
BINARYNINJACOREAPI bool BNFlowGraphHasNodes(BNFlowGraph* graph);
BINARYNINJACOREAPI size_t BNAddFlowGraphNode(BNFlowGraph* graph, BNFlowGraphNode* node);
+ BINARYNINJACOREAPI void BNReplaceFlowGraphNode(BNFlowGraph* graph, size_t i, BNFlowGraphNode* newNode);
+ BINARYNINJACOREAPI void BNClearFlowGraphNodes(BNFlowGraph* graph);
BINARYNINJACOREAPI int BNGetFlowGraphWidth(BNFlowGraph* graph);
BINARYNINJACOREAPI int BNGetFlowGraphHeight(BNFlowGraph* graph);
BINARYNINJACOREAPI void BNFlowGraphSetWidth(BNFlowGraph* graph, int width);
BINARYNINJACOREAPI void BNFlowGraphSetHeight(BNFlowGraph* graph, int height);
+ BINARYNINJACOREAPI BNRenderLayer** BNGetFlowGraphRenderLayers(BNFlowGraph* graph, size_t* count);
+ BINARYNINJACOREAPI void BNAddFlowGraphRenderLayer(BNFlowGraph* graph, BNRenderLayer* layer);
+ BINARYNINJACOREAPI void BNRemoveFlowGraphRenderLayer(BNFlowGraph* graph, BNRenderLayer* layer);
+
BINARYNINJACOREAPI BNFlowGraphNode* BNCreateFlowGraphNode(BNFlowGraph* graph);
BINARYNINJACOREAPI BNFlowGraphNode* BNNewFlowGraphNodeReference(BNFlowGraphNode* node);
BINARYNINJACOREAPI void BNFreeFlowGraphNode(BNFlowGraphNode* node);
@@ -8108,6 +8143,26 @@ extern "C"
BNDisassemblySettings* settings, BNLanguageRepresentationFunction* func);
BINARYNINJACOREAPI void BNFreeLineFormatterSettings(BNLineFormatterSettings* settings);
+ // Render Layers
+ BINARYNINJACOREAPI BNRenderLayer* BNRegisterRenderLayer(const char* name, BNRenderLayerCallbacks* callbacks, BNRenderLayerDefaultEnableState enableState);
+ BINARYNINJACOREAPI BNRenderLayer** BNGetRenderLayerList(size_t* count);
+ BINARYNINJACOREAPI void BNFreeRenderLayerList(BNRenderLayer** renderLayers);
+ BINARYNINJACOREAPI BNRenderLayer* BNGetRenderLayerByName(const char* name);
+ BINARYNINJACOREAPI char* BNGetRenderLayerName(BNRenderLayer* renderLayer);
+ BINARYNINJACOREAPI BNRenderLayerDefaultEnableState BNGetRenderLayerDefaultEnableState(BNRenderLayer* renderLayer);
+
+ BINARYNINJACOREAPI void BNApplyRenderLayerToFlowGraph(BNRenderLayer* renderLayer, BNFlowGraph* graph);
+ BINARYNINJACOREAPI void BNApplyRenderLayerToLinearViewObject(
+ BNRenderLayer* renderLayer,
+ BNLinearViewObject* obj,
+ BNLinearViewObject* prev,
+ BNLinearViewObject* next,
+ BNLinearDisassemblyLine* inLines,
+ size_t inLineCount,
+ BNLinearDisassemblyLine** outLines,
+ size_t* outLineCount
+ );
+
#ifdef __cplusplus
}
#endif
diff --git a/ffi.h b/ffi.h
index f9686cb5..15654c5f 100644
--- a/ffi.h
+++ b/ffi.h
@@ -65,6 +65,86 @@ namespace BinaryNinja
//endregion
+ //region Generic API Objects
+
+ /*! Helper class to determine if a type is "API-able" aka has the following interface:
+
+ struct Foo
+ {
+ BNFoo GetAPIObject() const;
+ static Foo FromAPIObject(const BNFoo* obj);
+ static void FreeAPIObject(BNFoo* obj);
+ };
+
+ If you get weird compiler errors around here, make sure you've implemented
+ the above interface correctly (with the `const`s too!).
+ */
+ template<
+ typename T,
+ // Grab the type for TAPI from the return type of GetAPIObject()
+ // Store into template argument for easier lookup
+ typename TAPI_ = decltype(std::declval<T>().GetAPIObject())
+ >
+ // Subtype of bool_constant to allow std::enable_if usage
+ struct APIAble : std::bool_constant<
+ // Make sure T::FromAPIObject(TAPI*) actually works
+ std::is_invocable_v<decltype(T::FromAPIObject), const TAPI_*>
+ // Make sure T::FromAPIObject(TAPI*) returns T
+ && std::is_same_v<T, decltype(T::FromAPIObject(std::declval<const TAPI_*>()))>
+ // Make sure T::FreeAPIObject(TAPI*) actually works
+ && std::is_invocable_v<decltype(T::FreeAPIObject), TAPI_*>
+ >
+ {
+ // For reference by users of APIAble
+ typedef TAPI_ TAPI;
+ };
+
+ template<typename T, typename _ = std::enable_if_t<APIAble<T>::value, void>>
+ void AllocAPIObjectList(const std::vector<T>& objects, typename APIAble<T>::TAPI BN_API_PTR** output, size_t* count)
+ {
+ *count = objects.size();
+ *output = new typename APIAble<T>::TAPI[objects.size()];
+
+ size_t i = 0;
+ for (const auto& o: objects)
+ {
+ (*output)[i] = o.GetAPIObject();
+ i ++;
+ }
+ }
+
+ template<typename T, typename _ = std::enable_if_t<APIAble<T>::value, void>>
+ typename APIAble<T>::TAPI BN_API_PTR* AllocAPIObjectList(const std::vector<T>& objects, size_t* count)
+ {
+ typename APIAble<T>::TAPI* result;
+ AllocAPIObjectList(objects, &result, count);
+ return result;
+ }
+
+ template<typename T, typename _ = std::enable_if_t<APIAble<T>::value, void>>
+ std::vector<T> ParseAPIObjectList(const typename APIAble<T>::TAPI* objects, size_t count)
+ {
+ std::vector<T> result;
+ result.reserve(count);
+ for (size_t i = 0; i < count; i ++)
+ {
+ result.push_back(T::FromAPIObject(&objects[i]));
+ }
+ return result;
+ }
+
+ template<typename T, typename _ = std::enable_if_t<APIAble<T>::value, void>>
+ void FreeAPIObjectList(typename APIAble<T>::TAPI BN_API_PTR* objects, size_t count)
+ {
+ for (size_t i = 0; i < count; i ++)
+ {
+ T::FreeAPIObject(&objects[i]);
+ }
+ delete[] objects;
+ }
+
+ //endregion
+
//------------------------------------------------------------------------------------
//region Try/Catch Helpers
diff --git a/flowgraph.cpp b/flowgraph.cpp
index 257764c9..b28b7ed9 100644
--- a/flowgraph.cpp
+++ b/flowgraph.cpp
@@ -256,6 +256,12 @@ Ref<FlowGraphNode> FlowGraph::GetNode(size_t i)
}
+size_t FlowGraph::GetNodeCount() const
+{
+ return BNGetFlowGraphNodeCount(m_object);
+}
+
+
bool FlowGraph::HasNodes() const
{
return BNFlowGraphHasNodes(m_object);
@@ -269,6 +275,18 @@ size_t FlowGraph::AddNode(FlowGraphNode* node)
}
+void FlowGraph::ReplaceNode(size_t i, FlowGraphNode* newNode)
+{
+ BNReplaceFlowGraphNode(m_object, i, newNode->GetObject());
+}
+
+
+void FlowGraph::ClearNodes()
+{
+ BNClearFlowGraphNodes(m_object);
+}
+
+
int FlowGraph::GetWidth() const
{
return BNGetFlowGraphWidth(m_object);
@@ -419,6 +437,32 @@ bool FlowGraph::IsOptionSet(BNFlowGraphOption option)
}
+std::vector<RenderLayer*> FlowGraph::GetRenderLayers() const
+{
+ size_t count = 0;
+ BNRenderLayer** layers = BNGetFlowGraphRenderLayers(m_object, &count);
+ std::vector<RenderLayer*> result;
+ for (size_t i = 0; i < count; i ++)
+ {
+ result.push_back(new CoreRenderLayer(layers[i]));
+ }
+ BNFreeRenderLayerList(layers);
+ return result;
+}
+
+
+void FlowGraph::AddRenderLayer(RenderLayer* layer)
+{
+ BNAddFlowGraphRenderLayer(m_object, layer->GetObject());
+}
+
+
+void FlowGraph::RemoveRenderLayer(RenderLayer* layer)
+{
+ BNRemoveFlowGraphRenderLayer(m_object, layer->GetObject());
+}
+
+
CoreFlowGraph::CoreFlowGraph(BNFlowGraph* graph) : FlowGraph(graph)
{
m_queryMode = BNFlowGraphUpdateQueryMode(GetObject());
diff --git a/linearviewcursor.cpp b/linearviewcursor.cpp
index 1ba252f7..50c6228a 100644
--- a/linearviewcursor.cpp
+++ b/linearviewcursor.cpp
@@ -211,6 +211,32 @@ Ref<LinearViewCursor> LinearViewCursor::Duplicate()
}
+std::vector<RenderLayer*> LinearViewCursor::GetRenderLayers() const
+{
+ size_t count = 0;
+ BNRenderLayer** layers = BNGetLinearViewCursorRenderLayers(m_object, &count);
+ std::vector<RenderLayer*> result;
+ for (size_t i = 0; i < count; i ++)
+ {
+ result.push_back(new CoreRenderLayer(layers[i]));
+ }
+ BNFreeRenderLayerList(layers);
+ return result;
+}
+
+
+void LinearViewCursor::AddRenderLayer(RenderLayer* layer)
+{
+ BNAddLinearViewCursorRenderLayer(m_object, layer->GetObject());
+}
+
+
+void LinearViewCursor::RemoveRenderLayer(RenderLayer* layer)
+{
+ BNRemoveLinearViewCursorRenderLayer(m_object, layer->GetObject());
+}
+
+
int LinearViewCursor::Compare(LinearViewCursor* a, LinearViewCursor* b)
{
return BNCompareLinearViewCursors(a->GetObject(), b->GetObject());
diff --git a/linearviewobject.cpp b/linearviewobject.cpp
index 4f00adb0..7cd08fcc 100644
--- a/linearviewobject.cpp
+++ b/linearviewobject.cpp
@@ -24,7 +24,27 @@ using namespace std;
using namespace BinaryNinja;
-LinearDisassemblyLine LinearDisassemblyLine::FromAPIObject(BNLinearDisassemblyLine* line)
+BNLinearDisassemblyLine LinearDisassemblyLine::GetAPIObject() const
+{
+ BNLinearDisassemblyLine result;
+ result.type = this->type;
+ result.function = this->function ? BNNewFunctionReference(this->function->GetObject()) : nullptr;
+ result.block = this->block ? BNNewBasicBlockReference(this->block->GetObject()) : nullptr;
+ result.contents.addr = this->contents.addr;
+ result.contents.instrIndex = this->contents.instrIndex;
+ result.contents.highlight = this->contents.highlight;
+ result.contents.count = this->contents.tokens.size();
+ result.contents.tokens = InstructionTextToken::CreateInstructionTextTokenList(this->contents.tokens);
+ result.contents.tags = Tag::CreateTagList(this->contents.tags, &result.contents.tagCount);
+ result.contents.typeInfo.hasTypeInfo = this->contents.typeInfo.hasTypeInfo;
+ result.contents.typeInfo.parentType = this->contents.typeInfo.parentType ? BNNewTypeReference(this->contents.typeInfo.parentType->GetObject()) : nullptr;
+ result.contents.typeInfo.fieldIndex = this->contents.typeInfo.fieldIndex;
+ result.contents.typeInfo.offset = this->contents.typeInfo.offset;
+ return result;
+}
+
+
+LinearDisassemblyLine LinearDisassemblyLine::FromAPIObject(const BNLinearDisassemblyLine* line)
{
LinearDisassemblyLine result;
result.type = line->type;
@@ -45,6 +65,19 @@ LinearDisassemblyLine LinearDisassemblyLine::FromAPIObject(BNLinearDisassemblyLi
}
+void LinearDisassemblyLine::FreeAPIObject(BNLinearDisassemblyLine* line)
+{
+ if (line->function)
+ BNFreeFunction(line->function);
+ if (line->block)
+ BNFreeBasicBlock(line->block);
+ InstructionTextToken::FreeInstructionTextTokenList(line->contents.tokens, line->contents.count);
+ Tag::FreeTagList(line->contents.tags, line->contents.tagCount);
+ if (line->contents.typeInfo.parentType)
+ BNFreeType(line->contents.typeInfo.parentType);
+}
+
+
LinearViewObjectIdentifier::LinearViewObjectIdentifier() : type(SingleLinearViewObject), start(0), end(0) {}
diff --git a/plugins/stack_render_layer/CMakeLists.txt b/plugins/stack_render_layer/CMakeLists.txt
new file mode 100644
index 00000000..3133e63d
--- /dev/null
+++ b/plugins/stack_render_layer/CMakeLists.txt
@@ -0,0 +1,46 @@
+cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
+
+project(stack_render_layer)
+
+file(GLOB SOURCES
+ *.cpp
+ *.c
+ *.h)
+
+if(DEMO)
+ add_library(${PROJECT_NAME} STATIC ${SOURCES})
+else()
+ add_library(${PROJECT_NAME} SHARED ${SOURCES})
+endif()
+
+if(NOT BN_INTERNAL_BUILD)
+ # Out-of-tree build
+ find_path(
+ BN_API_PATH
+ NAMES binaryninjaapi.h
+ HINTS ../../.. binaryninjaapi $ENV{BN_API_PATH}
+ REQUIRED
+ )
+ add_subdirectory(${BN_API_PATH} api)
+endif()
+
+target_link_libraries(${PROJECT_NAME} binaryninjaapi)
+
+set_target_properties(${PROJECT_NAME} 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(${PROJECT_NAME})
+ set_target_properties(${PROJECT_NAME} PROPERTIES
+ LIBRARY_OUTPUT_DIRECTORY ${BN_CORE_PLUGIN_DIR}
+ RUNTIME_OUTPUT_DIRECTORY ${BN_CORE_PLUGIN_DIR})
+else()
+ bn_install_plugin(${PROJECT_NAME})
+endif()
diff --git a/plugins/stack_render_layer/plugin.cpp b/plugins/stack_render_layer/plugin.cpp
new file mode 100644
index 00000000..b32ee470
--- /dev/null
+++ b/plugins/stack_render_layer/plugin.cpp
@@ -0,0 +1,124 @@
+
+#include <binaryninjaapi.h>
+#include <thread>
+
+using namespace BinaryNinja;
+
+
+class StackRenderLayer: public RenderLayer
+{
+public:
+ StackRenderLayer(): RenderLayer("Annotate Stack Offset") {}
+
+ void ApplyToLines(
+ Ref<BasicBlock> block,
+ std::vector<DisassemblyTextLine>& lines
+ )
+ {
+ for (auto& line: lines)
+ {
+ // Skip blank lines (block separators)
+ if (line.tokens.empty())
+ {
+ continue;
+ }
+
+ // Insert tokens after the address separator
+ int64_t sep = -1;
+ for (int64_t i = 0; i < line.tokens.size(); i ++)
+ {
+ if (line.tokens[i].type == AddressSeparatorToken)
+ {
+ sep = i;
+ break;
+ }
+ }
+ // Don't annotate lines which don't have an address separator
+ // (these are usually annotations like { Does not return }
+ if (sep == -1)
+ {
+ continue;
+ }
+
+ // Grab stack offset value from function
+ auto stackOffset = block->GetFunction()->GetRegisterValueAtInstruction(
+ block->GetArchitecture(),
+ line.addr,
+ block->GetArchitecture()->GetStackPointerRegister()
+ );
+ auto stackOffsetAfter = block->GetFunction()->GetRegisterValueAfterInstruction(
+ block->GetArchitecture(),
+ line.addr,
+ block->GetArchitecture()->GetStackPointerRegister()
+ );
+ if (stackOffset.state == StackFrameOffset)
+ {
+ // Stack pointer is resolved to an offset: show the offset
+ // (but negative because that is how other tools do it)
+ line.tokens.emplace(
+ line.tokens.begin() + sep + 1,
+ IntegerToken,
+ fmt::format("{:4x}", -stackOffset.value),
+ -stackOffset.value
+ );
+ }
+ else
+ {
+ // Stack pointer is not resolved, show ??
+ line.tokens.emplace(
+ line.tokens.begin() + sep + 1,
+ IntegerToken,
+ " ??",
+ 0
+ );
+ }
+ // And put a spacer after the offset token
+ if (stackOffset != stackOffsetAfter)
+ {
+ line.tokens.emplace(
+ line.tokens.begin() + sep + 2,
+ TextToken,
+ "* "
+ );
+ }
+ else
+ {
+ line.tokens.emplace(
+ line.tokens.begin() + sep + 2,
+ TextToken,
+ " "
+ );
+ }
+ }
+ }
+
+ virtual void ApplyToDisassemblyBlock(
+ Ref<BasicBlock> block,
+ std::vector<DisassemblyTextLine>& lines
+ ) override
+ {
+ // Break this out into a helper so we don't have to write it twice
+ ApplyToLines(block, lines);
+ }
+
+ virtual void ApplyToLowLevelILBlock(
+ Ref<BasicBlock> block,
+ std::vector<DisassemblyTextLine>& lines
+ ) override
+ {
+ // Break this out into a helper so we don't have to write it twice
+ ApplyToLines(block, lines);
+ }
+};
+
+
+extern "C" {
+ BN_DECLARE_CORE_ABI_VERSION
+
+ BINARYNINJAPLUGIN bool CorePluginInit()
+ {
+ static StackRenderLayer* layer = new StackRenderLayer();
+ RenderLayer::Register(layer, DisabledByDefaultRenderLayerDefaultEnableState);
+ return true;
+ }
+} \ No newline at end of file
diff --git a/python/__init__.py b/python/__init__.py
index 1cdc541e..db4e8e69 100644
--- a/python/__init__.py
+++ b/python/__init__.py
@@ -81,6 +81,7 @@ from .undo import *
from .fileaccessor import *
from .languagerepresentation import *
from .lineformatter import *
+from .renderlayer 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/basicblock.py b/python/basicblock.py
index 6c03fe32..a8dc85a8 100644
--- a/python/basicblock.py
+++ b/python/basicblock.py
@@ -23,6 +23,7 @@ from dataclasses import dataclass
from typing import Generator, Optional, List, Tuple
# Binary Ninja components
+import binaryninja
from . import _binaryninjacore as core
from .enums import BranchType, HighlightStandardColor
from . import binaryview
@@ -88,6 +89,33 @@ class BasicBlock:
if core is not None:
core.BNFreeBasicBlock(self.handle)
+ @classmethod
+ def _from_core_block(cls, block: core.BNBasicBlockHandle) -> Optional['BasicBlock']:
+ """From a BNBasicBlockHandle, get a BasicBlock or one of the IL subclasses (takes ref)"""
+ func_handle = core.BNGetBasicBlockFunction(block)
+ if not func_handle:
+ core.BNFreeBasicBlock(block)
+ return None
+
+ view = binaryview.BinaryView(handle=core.BNGetFunctionData(func_handle))
+ func = _function.Function(view, func_handle)
+
+ if core.BNIsLowLevelILBasicBlock(block):
+ return binaryninja.lowlevelil.LowLevelILBasicBlock(
+ block, binaryninja.lowlevelil.LowLevelILFunction(func.arch, core.BNGetBasicBlockLowLevelILFunction(block), func),
+ view
+ )
+ elif core.BNIsMediumLevelILBasicBlock(block):
+ mlil_func = binaryninja.mediumlevelil.MediumLevelILFunction(
+ func.arch, core.BNGetBasicBlockMediumLevelILFunction(block), func
+ )
+ return binaryninja.mediumlevelil.MediumLevelILBasicBlock(block, mlil_func, view)
+ elif core.BNIsHighLevelILBasicBlock(block):
+ hlil_func = binaryninja.highlevelil.HighLevelILFunction(func.arch, core.BNGetBasicBlockHighLevelILFunction(block), func)
+ return binaryninja.highlevelil.HighLevelILBasicBlock(block, hlil_func, view)
+ else:
+ return BasicBlock(block, view)
+
def __repr__(self):
arch = self.arch
if arch:
@@ -211,6 +239,78 @@ class BasicBlock:
return self._func
@property
+ def il_function(self) -> Optional['_function.ILFunctionType']:
+ """IL Function of which this block is a part, if the block is part of an IL Function."""
+ func = self.function
+ if func is None:
+ return None
+ il_type = self.function_graph_type
+ if il_type == _function.FunctionGraphType.NormalFunctionGraph:
+ return None
+ elif il_type == _function.FunctionGraphType.LowLevelILFunctionGraph:
+ return func.low_level_il
+ elif il_type == _function.FunctionGraphType.LiftedILFunctionGraph:
+ return func.lifted_il
+ elif il_type == _function.FunctionGraphType.LowLevelILSSAFormFunctionGraph:
+ return func.low_level_il.ssa_form
+ elif il_type == _function.FunctionGraphType.MediumLevelILFunctionGraph:
+ return func.medium_level_il
+ elif il_type == _function.FunctionGraphType.MediumLevelILSSAFormFunctionGraph:
+ return func.medium_level_il.ssa_form
+ elif il_type == _function.FunctionGraphType.MappedMediumLevelILFunctionGraph:
+ return func.mapped_medium_level_il
+ elif il_type == _function.FunctionGraphType.MappedMediumLevelILSSAFormFunctionGraph:
+ return func.mapped_medium_level_il.ssa_form
+ elif il_type == _function.FunctionGraphType.HighLevelILFunctionGraph:
+ return func.high_level_il
+ elif il_type == _function.FunctionGraphType.HighLevelILSSAFormFunctionGraph:
+ return func.high_level_il.ssa_form
+ elif il_type == _function.FunctionGraphType.HighLevelLanguageRepresentationFunctionGraph:
+ return func.high_level_il
+ else:
+ return None
+
+ @property
+ def il_function_if_available(self) -> Optional['_function.ILFunctionType']:
+ """IL Function of which this block is a part, if the block is part of an IL Function, and if the function has generated IL already."""
+ func = self.function
+ if func is None:
+ return None
+ il_type = self.function_graph_type
+ if il_type == _function.FunctionGraphType.NormalFunctionGraph:
+ return None
+ elif il_type == _function.FunctionGraphType.LowLevelILFunctionGraph:
+ return func.llil_if_available
+ elif il_type == _function.FunctionGraphType.LiftedILFunctionGraph:
+ return func.lifted_il_if_available
+ elif il_type == _function.FunctionGraphType.LowLevelILSSAFormFunctionGraph:
+ if func.llil_if_available is None:
+ return None
+ return func.llil_if_available.ssa_form
+ elif il_type == _function.FunctionGraphType.MediumLevelILFunctionGraph:
+ return func.mlil_if_available
+ elif il_type == _function.FunctionGraphType.MediumLevelILSSAFormFunctionGraph:
+ if func.mlil_if_available is None:
+ return None
+ return func.mlil_if_available.ssa_form
+ elif il_type == _function.FunctionGraphType.MappedMediumLevelILFunctionGraph:
+ return func.mmlil_if_available
+ elif il_type == _function.FunctionGraphType.MappedMediumLevelILSSAFormFunctionGraph:
+ if func.mmlil_if_available is None:
+ return None
+ return func.mmlil_if_available.ssa_form
+ elif il_type == _function.FunctionGraphType.HighLevelILFunctionGraph:
+ return func.hlil_if_available
+ elif il_type == _function.FunctionGraphType.HighLevelILSSAFormFunctionGraph:
+ if func.hlil_if_available is None:
+ return None
+ return func.hlil_if_available.ssa_form
+ elif il_type == _function.FunctionGraphType.HighLevelLanguageRepresentationFunctionGraph:
+ return func.hlil_if_available
+ else:
+ return None
+
+ @property
def view(self) -> Optional['binaryview.BinaryView']:
"""BinaryView that contains the basic block (read-only)"""
if self._view is not None:
@@ -426,6 +526,11 @@ class BasicBlock:
self.set_user_highlight(value)
@property
+ def function_graph_type(self) -> '_function.FunctionGraphType':
+ """Type of function graph from which this block represents instructions"""
+ return _function.FunctionGraphType(core.BNGetBasicBlockFunctionGraphType(self.handle))
+
+ @property
def is_il(self) -> bool:
"""Whether the basic block contains IL"""
return core.BNIsILBasicBlock(self.handle)
diff --git a/python/binaryview.py b/python/binaryview.py
index 73ddfd39..c1a57765 100644
--- a/python/binaryview.py
+++ b/python/binaryview.py
@@ -9011,20 +9011,9 @@ to a the type "tagRECT" found in the typelibrary "winX64common"
def _LinearDisassemblyLine_convertor(
self, lines: core.BNLinearDisassemblyLineHandle
) -> 'lineardisassembly.LinearDisassemblyLine':
- func = None
- block = None
- line = lines[0]
- if line.function:
- func = _function.Function(self, core.BNNewFunctionReference(line.function))
- if line.block:
- block_handle = core.BNNewBasicBlockReference(line.block)
- assert block_handle is not None, "core.BNNewBasicBlockReference returned None"
- block = basicblock.BasicBlock(block_handle, self)
- color = highlight.HighlightColor._from_core_struct(line.contents.highlight)
- addr = line.contents.addr
- tokens = _function.InstructionTextToken._from_core_struct(line.contents.tokens, line.contents.count)
- contents = _function.DisassemblyTextLine(tokens, addr, color=color)
- return lineardisassembly.LinearDisassemblyLine(line.type, func, block, contents)
+ line = lineardisassembly.LinearDisassemblyLine._from_core_struct(lines[0])
+ core.BNFreeLinearDisassemblyLines(lines, 1)
+ return line
def find_all_text(
self, start: int, end: int, text: str, settings: Optional[_function.DisassemblySettings] = None,
diff --git a/python/examples/args_render_layer.py b/python/examples/args_render_layer.py
new file mode 100644
index 00000000..bac0889e
--- /dev/null
+++ b/python/examples/args_render_layer.py
@@ -0,0 +1,213 @@
+import functools
+from typing import List, Mapping, Tuple, Iterator
+
+from binaryninja import DisassemblyTextLine, LowLevelILInstruction, LowLevelILOperation, \
+ TypeClass, DisassemblyTextRenderer, MediumLevelILFunction, \
+ MediumLevelILCallSsa, MediumLevelILVarSsa, MediumLevelILConstBase, \
+ MediumLevelILInstruction, MediumLevelILTailcallSsa, MediumLevelILOperation, \
+ MediumLevelILVarPhi, log_debug, RenderLayer, BasicBlock, InstructionTextTokenType, \
+ RenderLayerDefaultEnableState
+
+"""
+Render Layer that shows you where the arguments to calls are set, for Disasm/LLIL.
+- Adds "Argument '<arg>' for call at <callsite>" comments to lines that set up call args
+- Adds "Call at <callsite>" comments to the call sites
+
+=========================================================================================
+
+But how do you determine the argument to a call?
+What seems like it has worked:
+- You can't determine that an instruction is a parameter, you have to go from the call to its parameters
+- Since trying to look up the call for an instruction is impossible, instead go through every call at once for a function (and memoize it)
+- LLIL is useless for looking this up, since it has no types and the call parameters often include a list of every register
+- Using MLIL, we can find all of the parameters as MLIL instructions, but we need to map them to LLIL so we can use them in the LLIL/Disasm display
+- How do we map them? Turns out that's rather inconvenient:
+ - Register arguments are generally pretty easy because they are just MLIL vars
+ - Stack arguments somehow also work out generally, the .llil on the MLIL points to the push()
+ - Constants are a mess and I just use the MLIL's address (this is often incorrect)
+ - Flags are completely unhandled for now
+ - Phis are handled by just looking up every var they use... probably not proper but sort of works
+- This fails in a couple of scenarios though, notably __builtin_xxxxxx() functions
+ - Which instruction specifies the length of a group of `mov qword [rbx+8], rax {0}` calls? I think it just picks one?
+ - The `rep` instructions could actually have these params resolved (they use real registers) but in practice this doesn't work
+ - Thunks are unhandled
+"""
+
+
+@functools.lru_cache(maxsize=64)
+def get_param_sites(mlil: MediumLevelILFunction) -> Mapping[LowLevelILInstruction, List[Tuple[MediumLevelILInstruction, int]]]:
+ """
+ For a given function, find all LLIL instructions that are parameters to a call,
+ and return a mapping for each instruction with all the calls that it maps to,
+ their corresponding MLIL call instruction, and which numbered parameter they are
+ in the call.
+
+ :param mlil: MLIL function to search
+ :return: Map of param sites as described above
+ """
+ call_sites = {}
+ mlil = mlil.ssa_form
+
+ # As a function to handle call and tailcall identically
+ def collect_call_params(call_site, dest, params):
+ def_sites = []
+ for i, param in enumerate(params):
+ llil = param.llil
+ if llil is not None:
+ def_sites.append((param, llil))
+ continue
+
+ match param:
+ case MediumLevelILVarSsa(src=var_src):
+ def_site = mlil.get_ssa_var_definition(var_src)
+ if def_site is not None and def_site.llil is not None:
+ def_sites.append((i, def_site.llil))
+ continue
+ # Handle phis by just looking up the def sites of all their sources
+ match def_site:
+ case MediumLevelILVarPhi(src=phis):
+ for phi in phis:
+ phi_def = mlil.get_ssa_var_definition(phi)
+ if phi_def is not None and phi_def.llil is not None:
+ def_sites.append((i, phi_def.llil))
+ case MediumLevelILConstBase():
+ # This is wrong, but it works (sometimes)
+ # Oh god, have I just quoted php.net
+ def_site_idx = mlil.llil.get_instruction_start(param.address)
+ if def_site_idx is not None:
+ def_sites.append((i, mlil.llil[def_site_idx].ssa_form))
+ continue
+
+ if len(def_sites) == 0:
+ log_debug(f"Could not find def site for param {i} in call at {call_site.address:#x}")
+
+ call_sites[call_site] = def_sites
+
+ for instr in mlil.instructions:
+ match instr:
+ case MediumLevelILCallSsa(dest=dest, params=params) as call_site:
+ collect_call_params(call_site, dest, params)
+ case MediumLevelILTailcallSsa(dest=dest, params=params) as call_site:
+ collect_call_params(call_site, dest, params)
+
+ # Inverse args
+ all_def_sites = {}
+ for call_site, params in call_sites.items():
+ for (param_idx, llil) in params:
+ if llil not in all_def_sites:
+ all_def_sites[llil] = []
+ else:
+ print(f"got two at {llil.instr_index} @ {llil.address:#x} -> {call_site.address:#x}")
+ all_def_sites[llil].append((call_site, param_idx))
+
+ return all_def_sites
+
+
+def get_llil_arg(llil: LowLevelILInstruction) -> Iterator[Tuple[str, MediumLevelILInstruction]]:
+ args = get_param_sites(llil.function.mlil)
+
+ if llil.ssa_form in args:
+ for call_site, param_idx in args[llil.ssa_form]:
+ target_type = call_site.function.get_expr_type(call_site.dest.expr_index)
+
+ # Try getting the param name from the call's type
+ if target_type is not None:
+ if target_type.type_class == TypeClass.PointerTypeClass:
+ target_type = target_type.target
+ if target_type.type_class == TypeClass.FunctionTypeClass:
+ target_params = target_type.parameters
+ if param_idx < len(target_params):
+ param_name = target_params[param_idx].name
+ if param_name == '':
+ param_name = f"arg{param_idx+1}"
+ yield param_name, call_site
+ continue
+
+ # Some calls have extra params that aren't reflected in their type
+ yield f"arg{param_idx+1}", call_site
+ return
+
+
+def apply_to_lines(lines, get_instr, renderer):
+ # So we don't process lines twice since we're iterating over a list as we modify it
+ skip_lines = []
+
+ # Tailcalls that don't return incorrectly mark the { Does not return } line as a call
+ ignore_calls = set()
+
+ for i, line in enumerate(lines):
+ if len(line.tokens) == 0:
+ continue
+ if i in skip_lines:
+ continue
+
+ llil_instr = get_instr(line)
+ if llil_instr is not None:
+ new_lines = []
+ for (arg, call) in get_llil_arg(llil_instr):
+ if call.operation == MediumLevelILOperation.MLIL_TAILCALL_SSA:
+ comment = f"Argument '{arg}' for tailcall at {call.address:#x}"
+ else:
+ comment = f"Argument '{arg}' for call at {call.address:#x}"
+ renderer.wrap_comment(new_lines, line, comment, False, " ", "")
+ for j, token in enumerate(line.tokens):
+ if token.type == InstructionTextTokenType.AddressSeparatorToken:
+ line.tokens = line.tokens[:j]
+ break
+
+ # Annotate calls too so we can see them easily next to their args
+ if llil_instr.address == line.address and llil_instr.address not in ignore_calls:
+ if llil_instr.operation in [
+ LowLevelILOperation.LLIL_CALL,
+ LowLevelILOperation.LLIL_CALL_SSA,
+ LowLevelILOperation.LLIL_TAILCALL,
+ LowLevelILOperation.LLIL_TAILCALL_SSA
+ ]:
+ ignore_calls.add(llil_instr.address)
+ if llil_instr.operation in [
+ LowLevelILOperation.LLIL_TAILCALL,
+ LowLevelILOperation.LLIL_TAILCALL_SSA
+ ]:
+ comment = f"Tailcall at {llil_instr.address:#x}"
+ else:
+ comment = f"Call at {llil_instr.address:#x}"
+ # Creating comments is a bit unwieldy at the moment
+ renderer.wrap_comment(new_lines, line, comment, False, " ", "")
+ for j, token in enumerate(line.tokens):
+ if token.type == InstructionTextTokenType.AddressSeparatorToken:
+ line.tokens = line.tokens[:j]
+ break
+
+ # If any of our lines changed, swap out the existing lines with the new ones
+ if len(new_lines) > 0:
+ lines.pop(i)
+ for j, new_line in enumerate(new_lines):
+ lines.insert(i + j, new_line)
+ skip_lines.append(i + j)
+ return lines
+
+
+class ArgumentsRenderLayer(RenderLayer):
+ name = "Annotate Call Parameters"
+ default_enable_state = RenderLayerDefaultEnableState.EnabledByDefaultRenderLayerDefaultEnableState
+
+ def apply_to_disassembly_block(
+ self,
+ block: BasicBlock,
+ lines: List['DisassemblyTextLine']
+ ):
+ # Break this out into a helper so we don't have to write it twice
+ renderer = DisassemblyTextRenderer(block.function)
+ return apply_to_lines(lines, lambda line: block.function.get_llil_at(line.address), renderer)
+
+ def apply_to_low_level_il_block(
+ self,
+ block: BasicBlock,
+ lines: List['DisassemblyTextLine']
+ ):
+ # Break this out into a helper so we don't have to write it twice
+ renderer = DisassemblyTextRenderer(block.function)
+ return apply_to_lines(lines, lambda line: line.il_instruction, renderer)
+
+
+ArgumentsRenderLayer.register()
diff --git a/python/examples/follow_reg_render_layer.py b/python/examples/follow_reg_render_layer.py
new file mode 100644
index 00000000..36e66576
--- /dev/null
+++ b/python/examples/follow_reg_render_layer.py
@@ -0,0 +1,209 @@
+from typing import List
+
+from PySide6.QtCore import QSettings
+from binaryninja import DisassemblyTextLine, LowLevelILOperation, DisassemblyTextRenderer, \
+ MediumLevelILOperation, \
+ RenderLayer, BasicBlock, InstructionTextTokenType, RenderLayerDefaultEnableState, \
+ PluginCommand, BinaryView, interaction, InstructionTextToken, RegisterValueType
+
+
+"""
+Render layer that adds annotations to follow the value of a register, for Disasm/LLIL.
+- Adds "<reg> after: <value>" comments to every line that modifies your register
+- Adds "<reg> at block entry: <value>" comments to every block
+- Adds "<reg> at block exit: <value>" comments to every block
+You can switch registers with the plugin command "Set Followed Register"
+"""
+
+
+class FollowRegRenderLayer(RenderLayer):
+ name = "Follow Register"
+ default_enable_state = RenderLayerDefaultEnableState.EnabledByDefaultRenderLayerDefaultEnableState
+
+ def __init__(self, handle=None):
+ super().__init__(handle)
+ self.followed_reg = QSettings().value("layer/followed_reg", None)
+
+ def apply_to_lines(
+ self,
+ block: BasicBlock,
+ lines: List['DisassemblyTextLine'],
+ ):
+ if self.followed_reg is None:
+ return lines
+ if self.followed_reg not in block.arch.regs:
+ return lines
+
+ func = block.function
+ arch = block.arch
+
+ # Give our tokens a unique value so they don't highlight all the other comments
+ token_value = arch.get_reg_index(self.followed_reg) ^ 0x12345678
+
+ # ===============================================================================
+ # Render the "<reg> after" comments
+
+ for i, line in enumerate(lines):
+ # Ignore non-disassembly lines
+ if not any(token.type == InstructionTextTokenType.AddressSeparatorToken for token in line.tokens):
+ continue
+
+ # If the register has changed, we should render the update
+ written = False
+ for w in func.get_regs_written_by(line.address, arch):
+ # In case this instruction only modifies a sub register of our target
+ # Or in the case that we wanted to follow a sub register
+ if arch.regs[w].full_width_reg == arch.regs[self.followed_reg].full_width_reg:
+ written = True
+ break
+
+ if written:
+ # Add comment tokens to existing line
+ line.tokens.extend([
+ InstructionTextToken(InstructionTextTokenType.CommentToken, ' // ', token_value),
+ InstructionTextToken(InstructionTextTokenType.CommentToken, self.followed_reg, token_value),
+ InstructionTextToken(InstructionTextTokenType.CommentToken, ' after: ', token_value),
+ ])
+
+ after = func.get_reg_value_after(line.address, self.followed_reg, arch)
+ if after.type == RegisterValueType.UndeterminedValue:
+ if line.il_instruction is not None:
+ instr = line.il_instruction
+ else:
+ instr_start = func.low_level_il.get_instruction_start(line.address, arch)
+ instr = func.low_level_il[instr_start]
+ if instr is not None:
+ after_possible = instr.get_possible_reg_values_after(self.followed_reg)
+ line.tokens.append(
+ InstructionTextToken(InstructionTextTokenType.CommentToken, str(after_possible), token_value)
+ )
+ else:
+ line.tokens.append(
+ InstructionTextToken(InstructionTextTokenType.CommentToken, str(after), token_value)
+ )
+ else:
+ line.tokens.append(
+ InstructionTextToken(InstructionTextTokenType.CommentToken, str(after), token_value)
+ )
+
+ block_start = block.start
+ block_end = block.end
+ if block.is_low_level_il:
+ block_start = func.llil[block.start].address
+ block_end = func.llil[block.end - 1].address
+
+ # ===============================================================================
+ # Render the before-block "<reg> at block entry" comment
+
+ start_before = func.get_reg_value_at(block_start, self.followed_reg, arch)
+ line = [
+ InstructionTextToken(InstructionTextTokenType.CommentToken, '// ', token_value),
+ InstructionTextToken(InstructionTextTokenType.CommentToken, self.followed_reg, token_value),
+ InstructionTextToken(InstructionTextTokenType.CommentToken, ' at block entry: ', token_value),
+ ]
+
+ # Sometimes the first line is blank and we want to insert after it
+ first_line = 0
+ if len(lines[0].tokens) == 0:
+ first_line = 1
+ if start_before.type == RegisterValueType.UndeterminedValue:
+ if lines[first_line].il_instruction is not None:
+ instr = lines[first_line].il_instruction
+ else:
+ instr_start = func.low_level_il.get_instruction_start(block_start, arch)
+ instr = func.low_level_il[instr_start]
+ if instr is not None:
+ start_before_possible = instr.get_possible_reg_values(self.followed_reg)
+ line.append(
+ InstructionTextToken(InstructionTextTokenType.CommentToken, str(start_before_possible), token_value)
+ )
+ else:
+ line.append(
+ InstructionTextToken(InstructionTextTokenType.CommentToken, str(start_before), token_value)
+ )
+ else:
+ line.append(
+ InstructionTextToken(InstructionTextTokenType.CommentToken, str(start_before), token_value)
+ )
+
+ lines.insert(first_line, DisassemblyTextLine(line, block_start))
+
+ # ===============================================================================
+ # Render the after-block "<reg> at block exit" comment
+
+ end_after = func.get_reg_value_after(block_end, self.followed_reg, arch)
+ line = [
+ InstructionTextToken(InstructionTextTokenType.CommentToken, '// ', token_value),
+ InstructionTextToken(InstructionTextTokenType.CommentToken, self.followed_reg, token_value),
+ InstructionTextToken(InstructionTextTokenType.CommentToken, ' at block exit: ', token_value),
+ ]
+
+ # Sometimes the last line is blank and we want to insert before it
+ if len(lines) > 1 and len(lines[-1].tokens) == 0:
+ last_line = len(lines) - 1
+ else:
+ last_line = len(lines)
+ if end_after.type == RegisterValueType.UndeterminedValue:
+ if lines[last_line - 1].il_instruction is not None:
+ instr = lines[last_line - 1].il_instruction
+ else:
+ instr_start = func.low_level_il.get_instruction_start(block_end, arch)
+ instr = func.low_level_il[instr_start]
+ if instr is not None:
+ end_after_possible = instr.get_possible_reg_values_after(self.followed_reg)
+ line.append(
+ InstructionTextToken(InstructionTextTokenType.CommentToken, str(end_after_possible), token_value)
+ )
+ else:
+ line.append(
+ InstructionTextToken(InstructionTextTokenType.CommentToken, str(end_after), token_value)
+ )
+ else:
+ line.append(
+ InstructionTextToken(InstructionTextTokenType.CommentToken, str(end_after), token_value)
+ )
+
+ lines.insert(last_line, DisassemblyTextLine(line, block_end))
+
+ return lines
+
+ def apply_to_disassembly_block(
+ self,
+ block: BasicBlock,
+ lines: List['DisassemblyTextLine']
+ ):
+ # Break this out into a helper so we don't have to write it twice
+ return self.apply_to_lines(block, lines)
+
+ def apply_to_low_level_il_block(
+ self,
+ block: BasicBlock,
+ lines: List['DisassemblyTextLine']
+ ):
+ # Break this out into a helper so we don't have to write it twice
+ return self.apply_to_lines(block, lines)
+
+
+def set_follow_reg(bv: BinaryView):
+ if bv.platform is not None:
+ regs = list(bv.platform.arch.regs.keys())
+ idx = interaction.get_large_choice_input("Choose", "Choose Followed Register", regs)
+
+ # Save choice both in QSettings and on the RenderLayer object instance
+ layer = RenderLayer[FollowRegRenderLayer.name]
+ if idx is None:
+ layer.followed_reg = None
+ QSettings().remove("layer/followed_reg")
+ else:
+ layer.followed_reg = regs[idx]
+ QSettings().setValue("layer/followed_reg", regs[idx])
+
+ # Trigger view refresh (gross)
+ for func in bv.get_functions_containing(bv.offset):
+ func.reanalyze()
+
+
+FollowRegRenderLayer.register()
+
+# Using a command for this is kinda janky but currently the only option
+PluginCommand.register("Set Followed Register", "Choose which register to follow for the Follow Register Render Layer", set_follow_reg)
diff --git a/python/flowgraph.py b/python/flowgraph.py
index e661fff8..1f5040c2 100644
--- a/python/flowgraph.py
+++ b/python/flowgraph.py
@@ -156,30 +156,7 @@ class FlowGraphNode:
block = core.BNGetFlowGraphBasicBlock(self.handle)
if not block:
return None
- func_handle = core.BNGetBasicBlockFunction(block)
- if not func_handle:
- core.BNFreeBasicBlock(block)
- return None
-
- view = binaryview.BinaryView(handle=core.BNGetFunctionData(func_handle))
- func = function.Function(view, func_handle)
-
- if core.BNIsLowLevelILBasicBlock(block):
- block = lowlevelil.LowLevelILBasicBlock(
- block, lowlevelil.LowLevelILFunction(func.arch, core.BNGetBasicBlockLowLevelILFunction(block), func),
- view
- )
- elif core.BNIsMediumLevelILBasicBlock(block):
- mlil_func = mediumlevelil.MediumLevelILFunction(
- func.arch, core.BNGetBasicBlockMediumLevelILFunction(block), func
- )
- block = mediumlevelil.MediumLevelILBasicBlock(block, mlil_func, view)
- elif core.BNIsHighLevelILBasicBlock(block):
- hlil_func = highlevelil.HighLevelILFunction(func.arch, core.BNGetBasicBlockHighLevelILFunction(block), func)
- block = highlevelil.HighLevelILBasicBlock(block, hlil_func, view)
- else:
- block = basicblock.BasicBlock(block, view)
- return block
+ return basicblock.BasicBlock._from_core_block(block)
@basic_block.setter
def basic_block(self, block):
@@ -613,6 +590,11 @@ class FlowGraph:
return result
@property
+ def node_count(self) -> int:
+ """Number of nodes in graph (read-only)"""
+ return core.BNGetFlowGraphNodeCount(self.handle)
+
+ @property
def has_nodes(self):
"""Whether the flow graph has at least one node (read-only)"""
return core.BNFlowGraphHasNodes(self.handle)
@@ -835,12 +817,35 @@ class FlowGraph:
"""
``append`` adds a node to a flow graph.
+ .. note:: After the graph has completed layout, this function has no effect.
+
:param FlowGraphNode node: Node to add
:return: Index of node
:rtype: int
"""
return core.BNAddFlowGraphNode(self.handle, node.handle)
+ def replace(self, index, node):
+ """
+ ``replace`` replaces an existing node in the graph with a new node.
+ Any existing edges referencing the old node will be updated to point to
+ the new node.
+
+ .. note:: After the graph has completed layout, this function has no effect.
+
+ :param index: Index of the node to replace
+ :param node: New node with which to replace the old node
+ """
+ core.BNReplaceFlowGraphNode(self.handle, index, node.handle)
+
+ def clear(self):
+ """
+ ``clear`` clears all the nodes in the graph
+
+ .. note:: After the graph has completed layout, this function has no effect.
+ """
+ core.BNClearFlowGraphNodes(self.handle)
+
def show(self, title):
"""
``show`` displays the graph in a new tab in the UI.
@@ -869,6 +874,50 @@ class FlowGraph:
def is_option_set(self, option):
return core.BNIsFlowGraphOptionSet(self.handle, option)
+ @property
+ def render_layers(self) -> List['binaryninja.RenderLayer']:
+ """
+ Get the list of Render Layers which will be applied to this Flow Graph,
+ after it calls populate_nodes.
+ :return: List of Render Layers
+ """
+ count = ctypes.c_size_t(0)
+ layers = core.BNGetFlowGraphRenderLayers(self.handle, count)
+ assert layers is not None, "core.BNGetFlowGraphRenderLayers returned None"
+
+ try:
+ result = []
+ for i in range(0, count.value):
+ result.append(binaryninja.RenderLayer(handle=layers[i]))
+
+ return result
+ finally:
+ core.BNFreeRenderLayerList(layers)
+
+ @render_layers.setter
+ def render_layers(self, render_layers: List['binaryninja.RenderLayer']):
+ for layer in self.render_layers:
+ self.remove_render_layer(layer)
+ for layer in render_layers:
+ self.add_render_layer(layer)
+
+ def add_render_layer(self, layer: 'binaryninja.RenderLayer'):
+ """
+ Add a Render Layer to be applied to this Flow Graph. Note that layers will
+ be applied in the order in which they are added.
+
+ :param layer: Render Layer to add
+ """
+ core.BNAddFlowGraphRenderLayer(self.handle, layer.handle)
+
+ def remove_render_layer(self, layer: 'binaryninja.RenderLayer'):
+ """
+ Remove a Render Layer from being applied to this Flow Graph
+
+ :param layer: Render Layer to remove
+ """
+ core.BNRemoveFlowGraphRenderLayer(self.handle, layer.handle)
+
class CoreFlowGraph(FlowGraph):
def __init__(self, handle):
diff --git a/python/function.py b/python/function.py
index 80f0a37c..14cc5e43 100644
--- a/python/function.py
+++ b/python/function.py
@@ -3335,15 +3335,29 @@ class AdvancedFunctionAnalysisDataRequestor:
@dataclass
+class DisassemblyTextLineTypeInfo:
+ parent_type: Optional['types.Type']
+ field_index: int
+ offset: int
+
+
+@dataclass
class DisassemblyTextLine:
tokens: List['InstructionTextToken']
highlight: '_highlight.HighlightColor'
address: Optional[int]
il_instruction: Optional[ILInstructionType]
+ tags: List['binaryview.Tag']
+ type_info: Optional[DisassemblyTextLineTypeInfo]
def __init__(
- self, tokens: List['InstructionTextToken'], address: Optional[int] = None, il_instr: Optional[ILInstructionType] = None,
- color: Optional[Union['_highlight.HighlightColor', HighlightStandardColor]] = None
+ self,
+ tokens: List['InstructionTextToken'],
+ address: Optional[int] = None,
+ il_instr: Optional[ILInstructionType] = None,
+ color: Optional[Union['_highlight.HighlightColor', HighlightStandardColor]] = None,
+ tags: Optional[List['binaryview.Tag']] = None,
+ type_info: Optional[DisassemblyTextLineTypeInfo] = None,
):
self.address = address
self.tokens = tokens
@@ -3358,6 +3372,10 @@ class DisassemblyTextLine:
self.highlight = _highlight.HighlightColor(color)
else:
self.highlight = color
+ if tags is None:
+ tags = []
+ self.tags = tags
+ self.type_info = type_info
def __str__(self):
return "".join(map(str, self.tokens))
@@ -3413,6 +3431,64 @@ class DisassemblyTextLine:
self._find_address_and_indentation_tokens(collect_tokens)
return result
+ @classmethod
+ def _from_core_struct(cls, struct: core.BNDisassemblyTextLine, il_func: Optional['ILFunctionType'] = None):
+ il_instr = None
+ if il_func is not None and struct.instrIndex < len(il_func):
+ try:
+ il_instr = il_func[struct.instrIndex]
+ except:
+ il_instr = None
+ tokens = InstructionTextToken._from_core_struct(struct.tokens, struct.count)
+
+ tags = []
+ for i in range(struct.tagCount):
+ tags.append(binaryview.Tag(handle=core.BNNewTagReference(struct.tags[i])))
+
+ type_info = None
+ if struct.typeInfo.hasTypeInfo:
+ parent_type = None
+ if struct.typeInfo.parentType:
+ parent_type = types.Type.create(core.BNNewTypeReference(struct.typeInfo.parentType))
+ type_info = DisassemblyTextLineTypeInfo(
+ parent_type=parent_type,
+ field_index=struct.typeInfo.fieldIndex,
+ offset=struct.typeInfo.offset
+ )
+
+ return DisassemblyTextLine(
+ tokens,
+ struct.addr,
+ il_instr,
+ _highlight.HighlightColor._from_core_struct(struct.highlight),
+ tags,
+ type_info
+ )
+
+ def _to_core_struct(self) -> core.BNDisassemblyTextLine:
+ result = core.BNDisassemblyTextLine()
+ result.addr = self.address
+ if self.il_instruction is not None:
+ result.instrIndex = self.il_instruction.instr_index
+ else:
+ result.instrIndex = 0xffffffffffffffff
+ result.tokens = InstructionTextToken._get_core_struct(self.tokens)
+ result.count = len(self.tokens)
+ result.highlight = self.highlight._to_core_struct()
+ result.tagCount = len(self.tags)
+ result.tags = (ctypes.POINTER(core.BNTag) * len(self.tags))()
+ for i, tag in enumerate(self.tags):
+ result.tags[i] = tag.handle
+ if self.type_info is None:
+ result.typeInfo.hasTypeInfo = False
+ else:
+ result.typeInfo.hasTypeInfo = True
+ if self.type_info.parent_type is not None:
+ result.typeInfo.parentType = self.type_info.parent_type.handle
+ result.typeInfo.fieldIndex = self.type_info.field_index
+ result.typeInfo.offset = self.type_info.offset
+ return result
+
class DisassemblyTextRenderer:
def __init__(
@@ -3463,7 +3539,7 @@ class DisassemblyTextRenderer:
def basic_block(self) -> Optional['basicblock.BasicBlock']:
result = core.BNGetDisassemblyTextRendererBasicBlock(self.handle)
if result:
- return basicblock.BasicBlock(handle=result)
+ return basicblock.BasicBlock._from_core_block(handle=result)
return None
@basic_block.setter
diff --git a/python/lineardisassembly.py b/python/lineardisassembly.py
index 40a0d6c5..8c4b0eb5 100644
--- a/python/lineardisassembly.py
+++ b/python/lineardisassembly.py
@@ -27,7 +27,7 @@ from . import highlight
from . import function as _function
from . import basicblock
from . import binaryview
-from .enums import LinearViewObjectIdentifierType
+from .enums import (LinearDisassemblyLineType, LinearViewObjectIdentifierType)
class LinearDisassemblyLine:
@@ -43,6 +43,41 @@ class LinearDisassemblyLine:
def __str__(self):
return str(self.contents)
+ @classmethod
+ def _from_core_struct(cls, struct: core.BNLinearDisassemblyLine, obj: Optional['LinearViewObject'] = None) -> 'LinearDisassemblyLine':
+ function = None
+ if struct.function:
+ function = _function.Function(handle=core.BNNewFunctionReference(struct.function))
+ block = None
+ if struct.block:
+ block = basicblock.BasicBlock._from_core_block(core.BNNewBasicBlockReference(struct.block))
+ il_func = None
+ if block is not None:
+ il_func = block.il_function
+ if obj is not None and function is not None:
+ # Hack: HLIL bodies don't have basic blocks
+ if obj.identifier.name == "HLIL Function Body":
+ il_func = function.hlil
+ elif obj.identifier.name == "HLIL SSA Function Body":
+ il_func = function.hlil.ssa_form
+ elif obj.identifier.name == "Language Representation Function Body":
+ il_func = function.hlil
+
+ contents = _function.DisassemblyTextLine._from_core_struct(struct.contents, il_func)
+ return LinearDisassemblyLine(LinearDisassemblyLineType(struct.type), function, block, contents)
+
+ def _to_core_struct(self) -> core.BNLinearDisassemblyLine:
+ result = core.BNLinearDisassemblyLine()
+ result.type = self.type
+ result.function = None
+ if self.function is not None:
+ result.function = self.function.handle
+ result.block = None
+ if self.block is not None:
+ result.block = self.block.handle
+ result.contents = _function.DisassemblyTextLine._to_core_struct(self.contents)
+ return result
+
class LinearViewObjectIdentifier:
def __init__(self, name, start=None, end=None):
@@ -247,7 +282,7 @@ class LinearViewObject:
count = ctypes.c_ulonglong(0)
return LinearViewCursor._make_lines(
- core.BNGetLinearViewObjectLines(self.handle, prev_obj, next_obj, count), count.value
+ core.BNGetLinearViewObjectLines(self.handle, prev_obj, next_obj, count), count.value, self
)
def ordering_index_for_child(self, child):
@@ -602,34 +637,21 @@ class LinearViewCursor:
return core.BNLinearViewCursorNext(self.handle)
@staticmethod
- def _make_lines(lines, count: int) -> List['LinearDisassemblyLine']:
+ def _make_lines(lines, count: int, object) -> List['LinearDisassemblyLine']:
assert lines is not None, "core returned None for LinearDisassembly lines"
try:
result = []
for i in range(0, count):
- func = None
- block = None
- if lines[i].function:
- func = _function.Function(None, core.BNNewFunctionReference(lines[i].function))
- if lines[i].block:
- core_block = core.BNNewBasicBlockReference(lines[i].block)
- assert core_block is not None, "core.BNNewBasicBlockReference returned None"
- block = basicblock.BasicBlock(core_block, None)
- color = highlight.HighlightColor._from_core_struct(lines[i].contents.highlight)
- addr = lines[i].contents.addr
- tokens = _function.InstructionTextToken._from_core_struct(
- lines[i].contents.tokens, lines[i].contents.count
- )
- contents = _function.DisassemblyTextLine(tokens, addr, color=color)
- result.append(LinearDisassemblyLine(lines[i].type, func, block, contents))
+ result.append(LinearDisassemblyLine._from_core_struct(lines[i], obj=object))
return result
finally:
core.BNFreeLinearDisassemblyLines(lines, count)
@property
def lines(self):
+ current = self.current_object
count = ctypes.c_ulonglong(0)
- return LinearViewCursor._make_lines(core.BNGetLinearViewCursorLines(self.handle, count), count.value)
+ return LinearViewCursor._make_lines(core.BNGetLinearViewCursorLines(self.handle, count), count.value, current)
def duplicate(self):
return LinearViewCursor(None, handle=core.BNDuplicateLinearViewCursor(self.handle))
@@ -637,3 +659,48 @@ class LinearViewCursor:
@staticmethod
def compare(a, b):
return core.BNCompareLinearViewCursors(a.handle, b.handle)
+
+ @property
+ def render_layers(self) -> List['binaryninja.RenderLayer']:
+ """
+ Get the list of Render Layers which will be applied to this cursor, at the
+ end of calls to lines().
+
+ :return: List of Render Layers
+ """
+ count = ctypes.c_size_t(0)
+ layers = core.BNGetLinearViewCursorRenderLayers(self.handle, count)
+ assert layers is not None, "core.BNGetLinearViewCursorRenderLayers returned None"
+
+ try:
+ result = []
+ for i in range(0, count.value):
+ result.append(binaryninja.RenderLayer(handle=layers[i]))
+
+ return result
+ finally:
+ core.BNFreeRenderLayerList(layers)
+
+ @render_layers.setter
+ def render_layers(self, render_layers: List['binaryninja.RenderLayer']):
+ for layer in self.render_layers:
+ self.remove_render_layer(layer)
+ for layer in render_layers:
+ self.add_render_layer(layer)
+
+ def add_render_layer(self, layer: 'binaryninja.RenderLayer'):
+ """
+ Add a Render Layer to be applied to this cursor. Note that layers will
+ be applied in the order in which they are added.
+
+ :param layer: Render Layer to add
+ """
+ core.BNAddLinearViewCursorRenderLayer(self.handle, layer.handle)
+
+ def remove_render_layer(self, layer: 'binaryninja.RenderLayer'):
+ """
+ Remove a Render Layer from being applied to this cursor
+
+ :param layer: Render Layer to remove
+ """
+ core.BNRemoveLinearViewCursorRenderLayer(self.handle, layer.handle)
diff --git a/python/renderlayer.py b/python/renderlayer.py
new file mode 100644
index 00000000..6f5984e0
--- /dev/null
+++ b/python/renderlayer.py
@@ -0,0 +1,450 @@
+# Copyright (c) 2015-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.
+
+import ctypes
+import traceback
+
+# Binary Ninja components
+import binaryninja
+from . import _binaryninjacore as core, LinearDisassemblyLine
+from .enums import LinearDisassemblyLineType, RenderLayerDefaultEnableState
+from . import binaryview
+from . import types
+from .log import log_error
+from typing import Iterable, List, Optional, Union, Tuple
+
+
+class _RenderLayerMetaclass(type):
+ def __iter__(self):
+ binaryninja._init_plugins()
+ count = ctypes.c_ulonglong()
+ instances = core.BNGetRenderLayerList(count)
+ try:
+ for i in range(0, count.value):
+ yield self._handle_to_instance(instances[i])
+ finally:
+ core.BNFreeRenderLayerList(instances)
+
+ def __getitem__(self, value):
+ binaryninja._init_plugins()
+ handle = core.BNGetRenderLayerByName(str(value))
+ if handle is None:
+ raise KeyError(f"'{value}' is not a valid RenderLayer")
+ return self._handle_to_instance(handle)
+
+ def _handle_to_instance(self, handle):
+ handle_ptr = ctypes.cast(handle, ctypes.c_void_p)
+ if handle_ptr.value in RenderLayer._registered_instances:
+ return RenderLayer._registered_instances[handle_ptr.value]
+ return CoreRenderLayer(handle)
+
+
+class RenderLayer(metaclass=_RenderLayerMetaclass):
+ """
+ RenderLayer is a plugin class that allows you to customize the presentation of
+ Linear and Graph view output, adding, changing, or removing lines before they are
+ presented in the UI.
+ """
+
+ name = None
+ default_enable_state = RenderLayerDefaultEnableState.DisabledByDefaultRenderLayerDefaultEnableState
+ _registered_instances = {}
+ _pending_lines = {}
+
+ def __init__(self, handle=None):
+ if handle is not None:
+ self.handle = core.handle_of_type(handle, core.BNRenderLayer)
+ self.__dict__["name"] = core.BNGetRenderLayerName(handle)
+ self.__dict__["default_enable_state"] = core.BNGetRenderLayerDefaultEnableState(handle)
+ else:
+ self.handle = None
+
+ @classmethod
+ def register(cls):
+ """
+ Register a custom Render Layer.
+ """
+ layer = cls()
+
+ assert layer.__class__.name is not None
+ assert layer.handle is None
+
+ layer._cb = core.BNRenderLayerCallbacks()
+ layer._cb.context = 0
+ layer._cb.applyToFlowGraph = layer._cb.applyToFlowGraph.__class__(layer._apply_to_flow_graph)
+ layer._cb.applyToLinearViewObject = layer._cb.applyToLinearViewObject.__class__(layer._apply_to_linear_view_object)
+ layer._cb.freeLines = layer._cb.freeLines.__class__(layer._free_lines)
+ layer.handle = core.BNRegisterRenderLayer(layer.__class__.name, layer._cb, layer.default_enable_state)
+ handle_ptr = ctypes.cast(layer.handle, ctypes.c_void_p)
+ cls._registered_instances[handle_ptr.value] = layer
+
+ def __eq__(self, other):
+ if not isinstance(other, RenderLayer):
+ return False
+ return self.name == other.name
+
+ def __str__(self):
+ return f'<RenderLayer: {self.name}>'
+
+ def __repr__(self):
+ return f'<RenderLayer: {self.name}>'
+
+ def _apply_to_flow_graph(self, ctxt, graph):
+ try:
+ self.apply_to_flow_graph(binaryninja.FlowGraph(handle=core.BNNewFlowGraphReference(graph)))
+ except:
+ log_error(traceback.format_exc())
+
+ def _apply_to_linear_view_object(self, ctxt, obj, prev, next, in_lines, in_line_count, out_lines, out_line_count):
+ try:
+ obj_obj = binaryninja.LinearViewObject(core.BNNewLinearViewObjectReference(obj))
+ prev_obj = binaryninja.LinearViewObject(core.BNNewLinearViewObjectReference(prev)) if prev else None
+ next_obj = binaryninja.LinearViewObject(core.BNNewLinearViewObjectReference(next)) if next else None
+
+ lines = []
+ for i in range(in_line_count):
+ lines.append(LinearDisassemblyLine._from_core_struct(in_lines[i], obj=obj_obj))
+
+ lines = self.apply_to_linear_view_object(obj_obj, prev_obj, next_obj, lines)
+
+ out_line_count[0] = len(lines)
+ out_lines_buf = (core.BNLinearDisassemblyLine * len(lines))()
+ for i, r in enumerate(lines):
+ out_lines_buf[i] = r._to_core_struct()
+ out_lines_ptr = ctypes.cast(out_lines_buf, ctypes.c_void_p)
+ out_lines[0] = out_lines_buf
+ self._pending_lines[out_lines_ptr.value] = (out_lines_ptr.value, out_lines_buf)
+ except:
+ log_error(traceback.format_exc())
+ out_lines[0] = None
+ out_line_count[0] = 0
+
+ def _free_lines(self, ctxt, lines, count):
+ try:
+ buf = ctypes.cast(lines, ctypes.c_void_p)
+ if buf.value is not None:
+ if buf.value not in self._pending_lines:
+ raise ValueError("freeing lines list that wasn't allocated")
+ del self._pending_lines[buf.value]
+ except:
+ log_error(traceback.format_exc())
+
+ def apply_to_disassembly_block(
+ self,
+ block: 'binaryninja.BasicBlock',
+ lines: List['binaryninja.DisassemblyTextLine']
+ ):
+ """
+ Apply this Render Layer to a single Basic Block of Disassembly lines.
+ Subclasses should modify the input `lines` list to make modifications to
+ the presentation of the block.
+
+ .. note:: This function will only handle Disassembly lines, and not any ILs.
+
+ :param block: Basic Block containing those lines
+ :param lines: Original lines of text for the block
+ :return: Modified list of lines
+ """
+ return lines
+
+ def apply_to_low_level_il_block(
+ self,
+ block: 'binaryninja.LowLevelILBasicBlock',
+ lines: List['binaryninja.DisassemblyTextLine']
+ ):
+ """
+ Apply this Render Layer to a single Basic Block of Low Level IL lines.
+ Subclasses should modify the input `lines` list to make modifications to
+ the presentation of the block.
+
+ .. note:: This function will only handle Lifted IL/LLIL/LLIL(SSA) lines. \
+ You can use the block's `function_graph_type` property to determine which is being handled.
+
+ :param block: Basic Block containing those lines
+ :param lines: Original lines of text for the block
+ :return: Modified list of lines
+ """
+ return lines
+
+ def apply_to_medium_level_il_block(
+ self,
+ block: 'binaryninja.MediumLevelILBasicBlock',
+ lines: List['binaryninja.DisassemblyTextLine']
+ ):
+ """
+ Apply this Render Layer to a single Basic Block of Medium Level IL lines.
+ Subclasses should modify the input `lines` list to make modifications to
+ the presentation of the block.
+
+ .. note:: This function will only handle MLIL/MLIL(SSA)/Mapped MLIL/Mapped MLIL(SSA) lines. \
+ You can use the block's `function_graph_type` property to determine which is being handled.
+
+ :param block: Basic Block containing those lines
+ :param lines: Original lines of text for the block
+ :return: Modified list of lines
+ """
+ return lines
+
+ def apply_to_high_level_il_block(
+ self,
+ block: 'binaryninja.HighLevelILBasicBlock',
+ lines: List['binaryninja.DisassemblyTextLine']
+ ):
+ """
+ Apply this Render Layer to a single Basic Block of High Level IL lines.
+ Subclasses should modify the input `lines` list to make modifications to
+ the presentation of the block.
+
+ .. note:: This function will only handle HLIL/HLIL(SSA)/Language Representation lines. \
+ You can use the block's `function_graph_type` property to determine which is being handled.
+
+ .. warning:: This function will NOT apply to High Level IL bodies as displayed \
+ in Linear View! Those are handled by `apply_to_high_level_il_body` instead as they \
+ do not have a Basic Block associated with them.
+
+ :param block: Basic Block containing those lines
+ :param lines: Original lines of text for the block
+ :return: Modified list of lines
+ """
+ return lines
+
+ def apply_to_high_level_il_body(
+ self,
+ function: 'binaryninja.Function',
+ lines: List['binaryninja.LinearDisassemblyLine']
+ ):
+ """
+ Apply this Render Layer to the entire body of a High Level IL function.
+ Subclasses should modify the input `lines` list to make modifications to
+ the presentation of the function.
+
+ .. warning:: This function only applies to Linear View, and not to Graph View! \
+ If you want to handle Graph View too, you will need to use `apply_to_high_level_il_block` \
+ and handle the lines one block at a time.
+
+ :param function: Function containing those lines
+ :param lines: Original lines of text for the function
+ :return: Modified list of lines
+ """
+ return lines
+
+ def apply_to_misc_linear_lines(
+ self,
+ obj: 'binaryninja.LinearViewObject',
+ prev: Optional['binaryninja.LinearViewObject'],
+ next: Optional['binaryninja.LinearViewObject'],
+ lines: List['binaryninja.LinearDisassemblyLine']
+ ):
+ """
+ Apply to lines generated by Linear View that are not part of a function.
+ It is up to your implementation to figure out which type of Linear View Object
+ lines these are, and what to do with them.
+
+ :param obj: Linear View Object being rendered
+ :param prev: Linear View Object located directly above this one
+ :param next: Linear View Object located directly below this one
+ :param lines: Original lines rendered by `obj`
+ :return: Modified list of lines
+ """
+ return lines
+
+ def apply_to_block(
+ self,
+ block: 'binaryninja.BasicBlock',
+ lines: List['binaryninja.DisassemblyTextLine'],
+ ):
+ """
+ Apply to lines generated by a Basic Block, of any type. If not overridden, this
+ function will call the appropriate apply_to_X_level_il_block function.
+
+ :param block: Basic Block containing those lines
+ :param lines: Original lines of text for the block
+ :return: Modified list of lines
+ """
+ if not block.is_il:
+ return self.apply_to_disassembly_block(block, lines)
+ elif block.is_low_level_il:
+ return self.apply_to_low_level_il_block(block, lines)
+ elif block.is_medium_level_il:
+ return self.apply_to_medium_level_il_block(block, lines)
+ elif block.is_high_level_il:
+ return self.apply_to_high_level_il_block(block, lines)
+ else:
+ # ???
+ return lines
+
+ def apply_to_flow_graph(self, graph: 'binaryninja.FlowGraph') -> None:
+ """
+ Apply this Render Layer to a Flow Graph, potentially modifying its nodes,
+ their edges, their lines, and their lines' content.
+
+ .. note:: If you override this function, you will need to call the ``super()`` \
+ implementation if you want to use the higher level ``apply_to_X_level_il_block`` \
+ functionality.
+
+ :param graph: Graph to modify
+ """
+ pass
+ for i, node in enumerate(graph.nodes):
+ lines = node.lines
+ if node.basic_block is not None and isinstance(node.basic_block, binaryninja.BasicBlock):
+ lines = self.apply_to_block(node.basic_block, lines)
+ node.lines = lines
+
+ def apply_to_linear_view_object(
+ self,
+ obj: 'binaryninja.LinearViewObject',
+ prev: Optional['binaryninja.LinearViewObject'],
+ next: Optional['binaryninja.LinearViewObject'],
+ lines: List['binaryninja.LinearDisassemblyLine']
+ ) -> List['binaryninja.LinearDisassemblyLine']:
+ """
+ Apply this Render Layer to the lines produced by a LinearViewObject for rendering
+ in Linear View, potentially modifying the lines and their contents.
+
+ .. note:: If you override this function, you will need to call the ``super()`` \
+ implementation if you want to use the higher level ``apply_to_X_level_il_block`` \
+ functionality.
+
+ :param obj: Linear View Object being rendered
+ :param prev: Linear View Object located directly above this one
+ :param next: Linear View Object located directly below this one
+ :param lines: Original lines rendered by the Linear View Object
+ :return: Modified list of lines to display in Linear View
+ """
+ # Hack: HLIL bodies don't have basic blocks
+ if len(lines) > 0 and obj.identifier.name in [
+ "HLIL Function Body",
+ "HLIL SSA Function Body",
+ "Language Representation Function Body"
+ ]:
+ return self.apply_to_high_level_il_body(lines[0].function, lines)
+
+ block_lines = []
+ final_lines = []
+ last_block = None
+
+ def finish_block():
+ nonlocal block_lines
+ nonlocal final_lines
+ if len(block_lines) > 0:
+ if last_block is not None:
+ # Convert linear lines to disassembly lines for the apply()
+ # and then convert back for linear view
+ new_block_lines = []
+ disasm_lines = []
+ misc_lines = []
+
+ def process_disasm():
+ nonlocal disasm_lines
+
+ if len(disasm_lines) > 0:
+ disasm_lines = self.apply_to_block(last_block, disasm_lines)
+ func = block_lines[0].function
+ block = block_lines[0].block
+ for block_line in disasm_lines:
+ new_block_lines.append(
+ LinearDisassemblyLine(
+ LinearDisassemblyLineType.CodeDisassemblyLineType,
+ func,
+ block,
+ block_line
+ )
+ )
+ disasm_lines = []
+
+ def process_misc():
+ nonlocal misc_lines
+ nonlocal new_block_lines
+
+ if len(misc_lines) > 0:
+ misc_lines = self.apply_to_misc_linear_lines(obj, prev, next, misc_lines)
+ new_block_lines += misc_lines
+ misc_lines = []
+
+ for block_line in block_lines:
+ # Lines in the block get sent to process_disasm, anything else goes
+ # to process_misc so we preserve line information
+ if block_line.type == LinearDisassemblyLineType.CodeDisassemblyLineType:
+ process_misc()
+ disasm_lines.append(block_line.contents)
+ else:
+ process_disasm()
+ misc_lines.append(block_line)
+
+ # At the end, zero or one of these has lines in it
+ process_misc()
+ process_disasm()
+ block_lines = new_block_lines
+ else:
+ block_lines = self.apply_to_misc_linear_lines(obj, prev, next, block_lines)
+ final_lines += block_lines
+ block_lines = []
+
+ for line in lines:
+ # Assume we've finished a block when the line's block changes
+ if line.block != last_block:
+ finish_block()
+ block_lines.append(line)
+ last_block = line.block
+
+ # And we've finished a block when we're done with every line
+ finish_block()
+ return final_lines
+
+
+class CoreRenderLayer(RenderLayer):
+
+ def apply_to_flow_graph(self, graph: 'binaryninja.FlowGraph') -> None:
+ core.BNApplyRenderLayerToFlowGraph(self.handle, graph.handle)
+
+ def apply_to_linear_view_object(
+ self,
+ obj: 'binaryninja.LinearViewObject',
+ prev: Optional['binaryninja.LinearViewObject'],
+ next: Optional['binaryninja.LinearViewObject'],
+ lines: List['binaryninja.LinearDisassemblyLine']
+ ) -> List['binaryninja.LinearDisassemblyLine']:
+
+ in_lines_buf = (core.BNLinearDisassemblyLine * len(lines))()
+ for i, r in enumerate(lines):
+ in_lines_buf[i] = r._to_core_struct()
+
+ out_lines = ctypes.POINTER(core.BNLinearDisassemblyLine)()
+ out_line_count = ctypes.c_size_t(0)
+
+ core.BNApplyRenderLayerToLinearViewObject(
+ self.handle,
+ obj.handle,
+ prev.handle if prev is not None else None,
+ next.handle if next is not None else None,
+ in_lines_buf,
+ len(lines),
+ out_lines,
+ out_line_count
+ )
+
+ result = []
+ for i in range(out_line_count.value):
+ result.append(binaryninja.LinearDisassemblyLine._from_core_struct(out_lines[i], obj=obj))
+
+ core.BNFreeLinearDisassemblyLines(out_lines, out_line_count.value)
+
+ return result
diff --git a/renderlayer.cpp b/renderlayer.cpp
new file mode 100644
index 00000000..fb89be53
--- /dev/null
+++ b/renderlayer.cpp
@@ -0,0 +1,329 @@
+// Copyright (c) 2015-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"
+#include "ffi.h"
+
+using namespace BinaryNinja;
+using namespace std;
+
+std::unordered_map<BNRenderLayer*, RenderLayer*> RenderLayer::g_registeredInstances;
+
+
+RenderLayer::RenderLayer(const std::string& name): m_nameForRegister(name)
+{
+
+}
+
+
+RenderLayer::RenderLayer(BNRenderLayer* layer)
+{
+ m_object = layer;
+}
+
+
+void RenderLayer::ApplyToFlowGraphCallback(void* ctxt, BNFlowGraph* graph)
+{
+ RenderLayer* layer = (RenderLayer*)ctxt;
+ layer->ApplyToFlowGraph(new CoreFlowGraph(BNNewFlowGraphReference(graph)));
+}
+
+
+void RenderLayer::ApplyToLinearViewObjectCallback(
+ void* ctxt,
+ BNLinearViewObject* obj,
+ BNLinearViewObject* prev,
+ BNLinearViewObject* next,
+ BNLinearDisassemblyLine* inLines,
+ size_t inLineCount,
+ BNLinearDisassemblyLine** outLines,
+ size_t* outLineCount
+)
+{
+ RenderLayer* layer = (RenderLayer*)ctxt;
+ vector<LinearDisassemblyLine> lines = ParseAPIObjectList<LinearDisassemblyLine>(inLines, inLineCount);
+
+ layer->ApplyToLinearViewObject(
+ new LinearViewObject(BNNewLinearViewObjectReference(obj)),
+ prev ? new LinearViewObject(BNNewLinearViewObjectReference(prev)) : nullptr,
+ next ? new LinearViewObject(BNNewLinearViewObjectReference(next)) : nullptr,
+ lines
+ );
+
+ AllocAPIObjectList<LinearDisassemblyLine>(lines, outLines, outLineCount);
+}
+
+
+void RenderLayer::FreeLinesCallback(void* ctxt, BNLinearDisassemblyLine* lines, size_t count)
+{
+ FreeAPIObjectList<LinearDisassemblyLine>(lines, count);
+}
+
+
+void RenderLayer::Register(RenderLayer* layer, BNRenderLayerDefaultEnableState enableState)
+{
+ BNRenderLayerCallbacks cb;
+ cb.context = (void*)layer;
+ cb.applyToFlowGraph = ApplyToFlowGraphCallback;
+ cb.applyToLinearViewObject = ApplyToLinearViewObjectCallback;
+ cb.freeLines = FreeLinesCallback;
+ layer->m_object = BNRegisterRenderLayer(layer->m_nameForRegister.c_str(), &cb, enableState);
+ g_registeredInstances[layer->m_object] = layer;
+}
+
+
+std::vector<Ref<RenderLayer>> RenderLayer::GetList()
+{
+ size_t count;
+ BNRenderLayer** list = BNGetRenderLayerList(&count);
+ vector<Ref<RenderLayer>> result;
+ for (size_t i = 0; i < count; i ++)
+ {
+ if (auto reg = g_registeredInstances.find(list[i]); reg != g_registeredInstances.end())
+ {
+ result.push_back(reg->second);
+ }
+ else
+ {
+ result.push_back(new CoreRenderLayer(list[i]));
+ }
+ }
+ BNFreeRenderLayerList(list);
+ return result;
+}
+
+
+Ref<RenderLayer> RenderLayer::GetByName(const std::string& name)
+{
+ BNRenderLayer* result = BNGetRenderLayerByName(name.c_str());
+ if (!result)
+ return nullptr;
+ if (auto reg = g_registeredInstances.find(result); reg != g_registeredInstances.end())
+ {
+ return reg->second;
+ }
+ return new CoreRenderLayer(result);
+}
+
+
+BNRenderLayerDefaultEnableState RenderLayer::GetDefaultEnableState() const
+{
+ return BNGetRenderLayerDefaultEnableState(m_object);
+}
+
+
+std::string RenderLayer::GetName() const
+{
+ char* name = BNGetRenderLayerName(m_object);
+ std::string value = name;
+ BNFreeString(name);
+ return value;
+}
+
+
+void RenderLayer::ApplyToBlock(
+ Ref<BasicBlock> block,
+ std::vector<DisassemblyTextLine>& lines
+)
+{
+ if (!block->IsILBlock())
+ {
+ ApplyToDisassemblyBlock(block, lines);
+ }
+ else if (block->IsLowLevelILBlock())
+ {
+ ApplyToLowLevelILBlock(block, lines);
+ }
+ else if (block->IsMediumLevelILBlock())
+ {
+ ApplyToMediumLevelILBlock(block, lines);
+ }
+ else if (block->IsHighLevelILBlock())
+ {
+ ApplyToHighLevelILBlock(block, lines);
+ }
+}
+
+
+void RenderLayer::ApplyToFlowGraph(Ref<FlowGraph> graph)
+{
+ for (auto node: graph->GetNodes())
+ {
+ auto lines = node->GetLines();
+ if (node->GetBasicBlock())
+ {
+ ApplyToBlock(node->GetBasicBlock(), lines);
+ }
+ node->SetLines(lines);
+ }
+}
+
+
+void RenderLayer::ApplyToLinearViewObject(
+ Ref<LinearViewObject> obj,
+ Ref<LinearViewObject> prev,
+ Ref<LinearViewObject> next,
+ std::vector<LinearDisassemblyLine>& lines
+)
+{
+ // Hack: HLIL bodies don't have basic blocks
+ if (!lines.empty() &&
+ (obj->GetIdentifier().name == "HLIL Function Body"
+ || obj->GetIdentifier().name == "HLIL SSA Function Body"
+ || obj->GetIdentifier().name == "Language Representation Function Body"))
+ {
+ ApplyToHighLevelILBody(lines[0].function, lines);
+ return;
+ }
+
+ std::vector<LinearDisassemblyLine> blockLines;
+ std::vector<LinearDisassemblyLine> finalLines;
+ Ref<BasicBlock> lastBlock;
+
+ auto finishBlock = [&]()
+ {
+ if (!blockLines.empty())
+ {
+ if (lastBlock)
+ {
+ // Convert linear lines to disassembly lines for the apply()
+ // and then convert back for linear view
+ std::vector<LinearDisassemblyLine> newBlockLines;
+ std::vector<DisassemblyTextLine> disasmLines;
+ std::vector<LinearDisassemblyLine> miscLines;
+
+ auto processDisasm = [&]()
+ {
+ if (!disasmLines.empty())
+ {
+ ApplyToBlock(lastBlock, disasmLines);
+ Ref<Function> func = blockLines[0].function;
+ Ref<BasicBlock> block = blockLines[0].block;
+ for (auto& blockLine: disasmLines)
+ {
+ LinearDisassemblyLine newLine;
+ newLine.type = CodeDisassemblyLineType;
+ newLine.function = func;
+ newLine.block = block;
+ newLine.contents = blockLine;
+ newBlockLines.push_back(newLine);
+ }
+ disasmLines.clear();
+ }
+ };
+
+ auto processMisc = [&]()
+ {
+ if (!miscLines.empty())
+ {
+ ApplyToMiscLinearLines(obj, prev, next, miscLines);
+ std::move(
+ miscLines.begin(),
+ miscLines.end(),
+ std::back_inserter(newBlockLines)
+ );
+ miscLines.clear();
+ }
+ };
+
+ for (auto& blockLine: blockLines)
+ {
+ // Lines in the block get sent to processDisasm, anything else goes
+ // to processMisc so we preserve line information
+ if (blockLine.type == CodeDisassemblyLineType)
+ {
+ processMisc();
+ disasmLines.push_back(blockLine.contents);
+ }
+ else
+ {
+ processDisasm();
+ miscLines.push_back(blockLine);
+ }
+ }
+ // At the end, zero or one of these has lines in it
+ processMisc();
+ processDisasm();
+ blockLines = newBlockLines;
+ }
+ else
+ {
+ ApplyToMiscLinearLines(obj, prev, next, blockLines);
+ }
+ }
+ std::move(blockLines.begin(), blockLines.end(), std::back_inserter(finalLines));
+ };
+
+ for (auto& line: lines)
+ {
+ // Assume we've finished a block when the line's block changes
+ if (line.block != lastBlock)
+ {
+ finishBlock();
+ }
+ blockLines.push_back(line);
+ lastBlock = line.block;
+ }
+ // And we've finished a block when we're done with every line
+ finishBlock();
+
+ lines = finalLines;
+}
+
+
+CoreRenderLayer::CoreRenderLayer(BNRenderLayer* layer): RenderLayer(layer)
+{
+}
+
+
+void CoreRenderLayer::ApplyToFlowGraph(Ref<FlowGraph> graph)
+{
+ BNApplyRenderLayerToFlowGraph(m_object, graph->GetObject());
+}
+
+
+void CoreRenderLayer::ApplyToLinearViewObject(
+ Ref<LinearViewObject> obj,
+ Ref<LinearViewObject> prev,
+ Ref<LinearViewObject> next,
+ std::vector<LinearDisassemblyLine>& lines
+)
+{
+ BNLinearDisassemblyLine* inLines;
+ size_t inLineCount;
+ AllocAPIObjectList<LinearDisassemblyLine>(lines, &inLines, &inLineCount);
+
+ BNLinearDisassemblyLine* outLines;
+ size_t outLineCount;
+
+ BNApplyRenderLayerToLinearViewObject(
+ m_object,
+ obj->GetObject(),
+ prev ? prev->GetObject() : nullptr,
+ next ? next->GetObject() : nullptr,
+ inLines,
+ inLineCount,
+ &outLines,
+ &outLineCount
+ );
+
+ lines = ParseAPIObjectList<LinearDisassemblyLine>(outLines, outLineCount);
+ FreeAPIObjectList<LinearDisassemblyLine>(inLines, inLineCount);
+}
diff --git a/rust/src/basic_block.rs b/rust/src/basic_block.rs
index b880d28f..8846a90b 100644
--- a/rust/src/basic_block.rs
+++ b/rust/src/basic_block.rs
@@ -19,6 +19,7 @@ use crate::BranchType;
use binaryninjacore_sys::*;
use std::fmt;
use std::fmt::Debug;
+use std::hash::{Hash, Hasher};
enum EdgeDirection {
Incoming,
@@ -97,7 +98,14 @@ pub trait BlockContext: Clone + Sync + Send + Sized {
fn iter(&self, block: &BasicBlock<Self>) -> Self::Iter;
}
-#[derive(PartialEq, Eq, Hash)]
+#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, PartialOrd, Ord)]
+pub enum BasicBlockType {
+ Native,
+ LowLevelIL,
+ MediumLevelIL,
+ HighLevelIL,
+}
+
pub struct BasicBlock<C: BlockContext> {
pub(crate) handle: *mut BNBasicBlock,
context: C,
@@ -127,6 +135,19 @@ impl<C: BlockContext> BasicBlock<C> {
}
}
+ pub fn block_type(&self) -> BasicBlockType {
+ if unsafe { !BNIsILBasicBlock(self.handle) } {
+ BasicBlockType::Native
+ } else if unsafe { BNIsLowLevelILBasicBlock(self.handle) } {
+ BasicBlockType::LowLevelIL
+ } else if unsafe { BNIsMediumLevelILBasicBlock(self.handle) } {
+ BasicBlockType::MediumLevelIL
+ } else {
+ // We checked all other IL levels, so this is safe.
+ BasicBlockType::HighLevelIL
+ }
+ }
+
pub fn iter(&self) -> C::Iter {
self.context.iter(self)
}
@@ -194,7 +215,7 @@ impl<C: BlockContext> BasicBlock<C> {
if block.is_null() {
return None;
}
- Some(Ref::new(BasicBlock::from_raw(block, self.context.clone())))
+ Some(BasicBlock::ref_from_raw(block, self.context.clone()))
}
}
@@ -237,6 +258,24 @@ impl<C: BlockContext> BasicBlock<C> {
// TODO iterated dominance frontier
}
+impl<C: BlockContext> Hash for BasicBlock<C> {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.function().hash(state);
+ self.block_type().hash(state);
+ state.write_usize(self.index());
+ }
+}
+
+impl<C: BlockContext> PartialEq for BasicBlock<C> {
+ fn eq(&self, other: &Self) -> bool {
+ self.function() == other.function()
+ && self.index() == other.index()
+ && self.block_type() == other.block_type()
+ }
+}
+
+impl<C: BlockContext> Eq for BasicBlock<C> {}
+
impl<C: BlockContext> IntoIterator for &BasicBlock<C> {
type Item = C::Instruction;
type IntoIter = C::Iter;
diff --git a/rust/src/binary_view.rs b/rust/src/binary_view.rs
index 43e913d5..af4709e7 100644
--- a/rust/src/binary_view.rs
+++ b/rust/src/binary_view.rs
@@ -1209,8 +1209,8 @@ pub trait BinaryViewExt: BinaryViewBase {
/// Retrieves a list of the next disassembly lines.
///
- /// `get_next_linear_disassembly_lines` retrieves an [Array] over [LinearDisassemblyLine] objects for the
- /// next disassembly lines, and updates the [LinearViewCursor] passed in. This function can be called
+ /// Retrieves an [`Array`] over [`LinearDisassemblyLine`] objects for the
+ /// next disassembly lines, and updates the [`LinearViewCursor`] passed in. This function can be called
/// repeatedly to get more lines of linear disassembly.
///
/// # Arguments
diff --git a/rust/src/collaboration/sync.rs b/rust/src/collaboration/sync.rs
index 14e18856..1c11b8f0 100644
--- a/rust/src/collaboration/sync.rs
+++ b/rust/src/collaboration/sync.rs
@@ -701,7 +701,7 @@ pub fn is_type_archive_snapshot_ignored<S: BnStrCompatible>(
pub fn download_type_archive<S: BnStrCompatible>(
file: &RemoteFile,
location: S,
-) -> Result<Option<TypeArchive>, ()> {
+) -> Result<Option<Ref<TypeArchive>>, ()> {
download_type_archive_with_progress(file, location, NoProgressCallback)
}
@@ -711,7 +711,7 @@ pub fn download_type_archive_with_progress<S: BnStrCompatible, F: ProgressCallba
file: &RemoteFile,
location: S,
mut progress: F,
-) -> Result<Option<TypeArchive>, ()> {
+) -> Result<Option<Ref<TypeArchive>>, ()> {
let mut value = std::ptr::null_mut();
let db_path = location.into_bytes_with_nul();
let success = unsafe {
@@ -724,7 +724,7 @@ pub fn download_type_archive_with_progress<S: BnStrCompatible, F: ProgressCallba
)
};
success
- .then(|| NonNull::new(value).map(|handle| unsafe { TypeArchive::from_raw(handle) }))
+ .then(|| NonNull::new(value).map(|handle| unsafe { TypeArchive::ref_from_raw(handle) }))
.ok_or(())
}
diff --git a/rust/src/disassembly.rs b/rust/src/disassembly.rs
index 99938e5f..5f9ed559 100644
--- a/rust/src/disassembly.rs
+++ b/rust/src/disassembly.rs
@@ -30,9 +30,10 @@ pub type DisassemblyOption = BNDisassemblyOption;
pub type InstructionTextTokenType = BNInstructionTextTokenType;
pub type StringType = BNStringType;
-#[derive(Clone, PartialEq, Debug, Default)]
+#[derive(Clone, PartialEq, Debug, Default, Eq)]
pub struct DisassemblyTextLine {
pub address: u64,
+ // TODO: This is not always available.
pub instruction_index: usize,
pub tokens: Vec<InstructionTextToken>,
pub highlight: HighlightColor,
@@ -234,7 +235,7 @@ impl DisassemblyTextLineTypeInfo {
}
}
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InstructionTextToken {
pub address: u64,
pub text: String,
@@ -882,6 +883,8 @@ impl From<InstructionTextTokenKind> for BNInstructionTextTokenType {
}
}
+impl Eq for InstructionTextTokenKind {}
+
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum InstructionTextTokenContext {
Normal,
diff --git a/rust/src/flowgraph.rs b/rust/src/flowgraph.rs
index ead984f9..f5d4ed80 100644
--- a/rust/src/flowgraph.rs
+++ b/rust/src/flowgraph.rs
@@ -14,12 +14,15 @@
//! Interfaces for creating and displaying pretty CFGs in Binary Ninja.
-use binaryninjacore_sys::*;
-
use crate::disassembly::DisassemblyTextLine;
+use binaryninjacore_sys::*;
+use std::slice;
use crate::rc::*;
+use crate::basic_block::{BasicBlock, BlockContext};
+use crate::function::HighlightColor;
+use crate::render_layer::CoreRenderLayer;
use std::marker::PhantomData;
pub type BranchType = BNBranchType;
@@ -37,14 +40,61 @@ impl FlowGraph {
Self { handle: raw }
}
+ pub(crate) unsafe fn ref_from_raw(raw: *mut BNFlowGraph) -> Ref<Self> {
+ Ref::new(Self { handle: raw })
+ }
+
pub fn new() -> Ref<Self> {
- unsafe { Ref::new(FlowGraph::from_raw(BNCreateFlowGraph())) }
+ unsafe { FlowGraph::ref_from_raw(BNCreateFlowGraph()) }
+ }
+
+ pub fn nodes<'a>(&self) -> Vec<Ref<FlowGraphNode<'a>>> {
+ let mut count: usize = 0;
+ let nodes_ptr = unsafe { BNGetFlowGraphNodes(self.handle, &mut count as *mut usize) };
+
+ let nodes = unsafe { slice::from_raw_parts_mut(nodes_ptr, count) };
+
+ let mut result = vec![];
+ result.reserve(count);
+
+ for i in 0..count {
+ result.push(unsafe { RefCountable::inc_ref(&FlowGraphNode::from_raw(nodes[i])) });
+ }
+
+ unsafe { BNFreeFlowGraphNodeList(nodes_ptr, count) };
+
+ result
+ }
+
+ pub fn get_node<'a>(&self, i: usize) -> Option<Ref<FlowGraphNode<'a>>> {
+ let node_ptr = unsafe { BNGetFlowGraphNode(self.handle, i) };
+ if node_ptr.is_null() {
+ None
+ } else {
+ Some(unsafe { Ref::new(FlowGraphNode::from_raw(node_ptr)) })
+ }
+ }
+
+ pub fn get_node_count(&self) -> usize {
+ unsafe { BNGetFlowGraphNodeCount(self.handle) }
+ }
+
+ pub fn has_nodes(&self) -> bool {
+ unsafe { BNFlowGraphHasNodes(self.handle) }
}
pub fn append(&self, node: &FlowGraphNode) -> usize {
unsafe { BNAddFlowGraphNode(self.handle, node.handle) }
}
+ pub fn replace(&self, index: usize, node: &FlowGraphNode) {
+ unsafe { BNReplaceFlowGraphNode(self.handle, index, node.handle) }
+ }
+
+ pub fn clear(&self) {
+ unsafe { BNClearFlowGraphNodes(self.handle) }
+ }
+
pub fn set_option(&self, option: FlowGraphOption, value: bool) {
unsafe { BNSetFlowGraphOption(self.handle, option, value) }
}
@@ -52,6 +102,27 @@ impl FlowGraph {
pub fn is_option_set(&self, option: FlowGraphOption) -> bool {
unsafe { BNIsFlowGraphOptionSet(self.handle, option) }
}
+
+ /// A list of the currently applied [`CoreRenderLayer`]'s
+ pub fn render_layers(&self) -> Array<CoreRenderLayer> {
+ let mut count: usize = 0;
+ unsafe {
+ let handles = BNGetFlowGraphRenderLayers(self.handle, &mut count);
+ Array::new(handles, count, ())
+ }
+ }
+
+ /// Add a Render Layer to be applied to this [`FlowGraph`].
+ ///
+ /// NOTE: Layers will be applied in the order in which they are added.
+ pub fn add_render_layer(&self, layer: &CoreRenderLayer) {
+ unsafe { BNAddFlowGraphRenderLayer(self.handle, layer.handle.as_ptr()) };
+ }
+
+ /// Remove a Render Layer from being applied to this [`FlowGraph`].
+ pub fn remove_render_layer(&self, layer: &CoreRenderLayer) {
+ unsafe { BNRemoveFlowGraphRenderLayer(self.handle, layer.handle.as_ptr()) };
+ }
}
unsafe impl RefCountable for FlowGraph {
@@ -88,8 +159,37 @@ impl<'a> FlowGraphNode<'a> {
}
}
- pub fn new(graph: &FlowGraph) -> Self {
- unsafe { FlowGraphNode::from_raw(BNCreateFlowGraphNode(graph.handle)) }
+ pub(crate) unsafe fn ref_from_raw(raw: *mut BNFlowGraphNode) -> Ref<Self> {
+ Ref::new(Self {
+ handle: raw,
+ _data: PhantomData,
+ })
+ }
+
+ pub fn new(graph: &FlowGraph) -> Ref<Self> {
+ unsafe { FlowGraphNode::ref_from_raw(BNCreateFlowGraphNode(graph.handle)) }
+ }
+
+ pub fn basic_block<C: BlockContext>(&self, context: C) -> Option<Ref<BasicBlock<C>>> {
+ let block_ptr = unsafe { BNGetFlowGraphBasicBlock(self.handle) };
+ if block_ptr.is_null() {
+ return None;
+ }
+ Some(unsafe { BasicBlock::ref_from_raw(block_ptr, context) })
+ }
+
+ pub fn set_basic_block<C: BlockContext>(&self, block: Option<&BasicBlock<C>>) {
+ match block {
+ Some(block) => unsafe { BNSetFlowGraphBasicBlock(self.handle, block.handle) },
+ None => unsafe { BNSetFlowGraphBasicBlock(self.handle, std::ptr::null_mut()) },
+ }
+ }
+
+ pub fn lines(&self) -> Array<DisassemblyTextLine> {
+ let mut count = 0;
+ let result = unsafe { BNGetFlowGraphNodeLines(self.handle, &mut count) };
+ assert!(!result.is_null());
+ unsafe { Array::new(result, count, ()) }
}
pub fn set_lines(&self, lines: impl IntoIterator<Item = DisassemblyTextLine>) {
@@ -106,6 +206,30 @@ impl<'a> FlowGraphNode<'a> {
}
}
+ /// Returns the graph position of the node in X, Y form.
+ pub fn position(&self) -> (i32, i32) {
+ let pos_x = unsafe { BNGetFlowGraphNodeX(self.handle) };
+ let pos_y = unsafe { BNGetFlowGraphNodeY(self.handle) };
+ (pos_x, pos_y)
+ }
+
+ /// Sets the graph position of the node.
+ pub fn set_position(&self, x: i32, y: i32) {
+ unsafe { BNFlowGraphNodeSetX(self.handle, x) };
+ unsafe { BNFlowGraphNodeSetX(self.handle, y) };
+ }
+
+ pub fn highlight_color(&self) -> HighlightColor {
+ let raw = unsafe { BNGetFlowGraphNodeHighlight(self.handle) };
+ HighlightColor::from(raw)
+ }
+
+ pub fn set_highlight_color(&self, highlight: HighlightColor) {
+ unsafe { BNSetFlowGraphNodeHighlight(self.handle, highlight.into()) };
+ }
+
+ // TODO: Add getters and setters for edges
+
pub fn add_outgoing_edge(
&self,
type_: BranchType,
diff --git a/rust/src/function.rs b/rust/src/function.rs
index d4eb1d53..63ebc82e 100644
--- a/rust/src/function.rs
+++ b/rust/src/function.rs
@@ -128,7 +128,7 @@ impl Iterator for NativeBlockIter {
}
}
-#[derive(Clone)]
+#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NativeBlock {
_priv: (),
}
@@ -2346,7 +2346,7 @@ impl Function {
/// Flow graph of unresolved stack adjustments
pub fn unresolved_stack_adjustment_graph(&self) -> Option<Ref<FlowGraph>> {
let graph = unsafe { BNGetUnresolvedStackAdjustmentGraph(self.handle) };
- (!graph.is_null()).then(|| unsafe { Ref::new(FlowGraph::from_raw(graph)) })
+ (!graph.is_null()).then(|| unsafe { FlowGraph::ref_from_raw(graph) })
}
pub fn create_graph(
@@ -2358,7 +2358,7 @@ impl Function {
let raw_view_type = FunctionViewType::into_raw(view_type);
let result = unsafe { BNCreateFunctionGraph(self.handle, raw_view_type, settings_raw) };
FunctionViewType::free_raw(raw_view_type);
- unsafe { Ref::new(FlowGraph::from_raw(result)) }
+ unsafe { FlowGraph::ref_from_raw(result) }
}
pub fn parent_components(&self) -> Array<Component> {
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index eb89ff0c..0cb0484e 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -68,6 +68,7 @@ pub mod project;
pub mod rc;
pub mod references;
pub mod relocation;
+pub mod render_layer;
pub mod section;
pub mod segment;
pub mod settings;
diff --git a/rust/src/linear_view.rs b/rust/src/linear_view.rs
index dc3ca597..6d2347b3 100644
--- a/rust/src/linear_view.rs
+++ b/rust/src/linear_view.rs
@@ -18,26 +18,37 @@ use binaryninjacore_sys::*;
use crate::binary_view::BinaryView;
use crate::disassembly::{DisassemblySettings, DisassemblyTextLine};
-use crate::function::Function;
+use crate::function::{Function, NativeBlock};
+use crate::basic_block::BasicBlock;
use crate::rc::*;
+use crate::render_layer::CoreRenderLayer;
+use crate::string::{raw_to_string, BnString};
use std::ops::Deref;
-use std::mem;
-
pub type LinearDisassemblyLineType = BNLinearDisassemblyLineType;
+pub type LinearViewObjectIdentifierType = BNLinearViewObjectIdentifierType;
-// TODO: Rename to LinearView?
pub struct LinearViewObject {
pub(crate) handle: *mut BNLinearViewObject,
}
impl LinearViewObject {
+ pub(crate) unsafe fn from_raw(handle: *mut BNLinearViewObject) -> Self {
+ debug_assert!(!handle.is_null());
+ Self { handle }
+ }
+
pub(crate) unsafe fn ref_from_raw(handle: *mut BNLinearViewObject) -> Ref<Self> {
debug_assert!(!handle.is_null());
Ref::new(Self { handle })
}
+ pub fn identifier(&self) -> LinearViewObjectIdentifier {
+ let raw = unsafe { BNGetLinearViewObjectIdentifier(self.handle) };
+ LinearViewObjectIdentifier::from_owned_raw(raw)
+ }
+
pub fn data_only(view: &BinaryView, settings: &DisassemblySettings) -> Ref<Self> {
unsafe {
let handle = BNCreateLinearViewDataOnly(view.handle, settings.handle);
@@ -214,6 +225,45 @@ impl ToOwned for LinearViewObject {
unsafe impl Send for LinearViewObject {}
unsafe impl Sync for LinearViewObject {}
+#[derive(Clone, PartialEq, Debug)]
+pub struct LinearViewObjectIdentifier {
+ pub name: String,
+ pub ty: LinearViewObjectIdentifierType,
+ pub start: u64,
+ pub end: u64,
+}
+
+impl LinearViewObjectIdentifier {
+ pub fn from_raw(value: &BNLinearViewObjectIdentifier) -> Self {
+ Self {
+ name: raw_to_string(value.name).unwrap(),
+ ty: value.type_,
+ start: value.start,
+ end: value.end,
+ }
+ }
+
+ pub fn from_owned_raw(value: BNLinearViewObjectIdentifier) -> Self {
+ let owned = Self::from_raw(&value);
+ Self::free_raw(value);
+ owned
+ }
+
+ pub fn into_raw(value: Self) -> BNLinearViewObjectIdentifier {
+ let bn_name = BnString::new(value.name);
+ BNLinearViewObjectIdentifier {
+ name: BnString::into_raw(bn_name),
+ type_: value.ty,
+ start: value.start,
+ end: value.end,
+ }
+ }
+
+ pub fn free_raw(value: BNLinearViewObjectIdentifier) {
+ let _ = unsafe { BnString::from_raw(value.name) };
+ }
+}
+
#[derive(Eq)]
pub struct LinearViewCursor {
pub(crate) handle: *mut BNLinearViewCursor,
@@ -252,15 +302,15 @@ impl LinearViewCursor {
!(self.before_begin() || self.after_end())
}
- pub fn seek_to_start(&self) {
+ pub fn seek_to_start(&mut self) {
unsafe { BNSeekLinearViewCursorToBegin(self.handle) }
}
- pub fn seek_to_end(&self) {
+ pub fn seek_to_end(&mut self) {
unsafe { BNSeekLinearViewCursorToEnd(self.handle) }
}
- pub fn seek_to_address(&self, address: u64) {
+ pub fn seek_to_address(&mut self, address: u64) {
unsafe { BNSeekLinearViewCursorToAddress(self.handle, address) }
}
@@ -275,15 +325,15 @@ impl LinearViewCursor {
unsafe { BNGetLinearViewCursorOrderingIndexTotal(self.handle) }
}
- pub fn seek_to_ordering_index(&self, idx: u64) {
+ pub fn seek_to_ordering_index(&mut self, idx: u64) {
unsafe { BNSeekLinearViewCursorToAddress(self.handle, idx) }
}
- pub fn previous(&self) -> bool {
+ pub fn previous(&mut self) -> bool {
unsafe { BNLinearViewCursorPrevious(self.handle) }
}
- pub fn next(&self) -> bool {
+ pub fn next(&mut self) -> bool {
unsafe { BNLinearViewCursorNext(self.handle) }
}
@@ -294,6 +344,27 @@ impl LinearViewCursor {
Array::new(handles, count, ())
}
}
+
+ /// A list of the currently applied [`CoreRenderLayer`]'s
+ pub fn render_layers(&self) -> Array<CoreRenderLayer> {
+ let mut count: usize = 0;
+ unsafe {
+ let handles = BNGetLinearViewCursorRenderLayers(self.handle, &mut count);
+ Array::new(handles, count, ())
+ }
+ }
+
+ /// Add a Render Layer to be applied to this [`LinearViewCursor`].
+ ///
+ /// NOTE: Layers will be applied in the order in which they are added.
+ pub fn add_render_layer(&self, layer: &CoreRenderLayer) {
+ unsafe { BNAddLinearViewCursorRenderLayer(self.handle, layer.handle.as_ptr()) };
+ }
+
+ /// Remove a Render Layer from being applied to this [`LinearViewCursor`].
+ pub fn remove_render_layer(&self, layer: &CoreRenderLayer) {
+ unsafe { BNRemoveLinearViewCursorRenderLayer(self.handle, layer.handle.as_ptr()) };
+ }
}
impl PartialEq for LinearViewCursor {
@@ -341,55 +412,81 @@ impl ToOwned for LinearViewCursor {
unsafe impl Send for LinearViewCursor {}
unsafe impl Sync for LinearViewCursor {}
+#[derive(Clone, PartialEq, Debug, Eq)]
pub struct LinearDisassemblyLine {
- t: LinearDisassemblyLineType,
-
- // These will be cleaned up by BNFreeLinearDisassemblyLines, so we
- // don't drop them in the relevant deconstructors.
- // TODO: This is insane!
- function: mem::ManuallyDrop<Ref<Function>>,
- contents: mem::ManuallyDrop<DisassemblyTextLine>,
+ pub ty: LinearDisassemblyLineType,
+ pub function: Option<Ref<Function>>,
+ pub basic_block: Option<Ref<BasicBlock<NativeBlock>>>,
+ pub contents: DisassemblyTextLine,
}
impl LinearDisassemblyLine {
- pub(crate) unsafe fn from_raw(raw: &BNLinearDisassemblyLine) -> Self {
- let linetype = raw.type_;
- // TODO: We must remove this behavior.
- let function = mem::ManuallyDrop::new(Function::ref_from_raw(raw.function));
- let contents = mem::ManuallyDrop::new(DisassemblyTextLine::from_raw(&raw.contents));
+ pub(crate) unsafe fn from_raw(value: &BNLinearDisassemblyLine) -> Self {
+ let function = if !value.function.is_null() {
+ Some(unsafe { Function::from_raw(value.function).to_owned() })
+ } else {
+ None
+ };
+ let basic_block = if !value.block.is_null() {
+ Some(unsafe { BasicBlock::from_raw(value.block, NativeBlock::new()).to_owned() })
+ } else {
+ None
+ };
Self {
- t: linetype,
+ ty: value.type_,
function,
- contents,
+ basic_block,
+ contents: DisassemblyTextLine::from_raw(&value.contents),
}
}
- pub fn function(&self) -> &Function {
- self.function.as_ref()
+ #[allow(unused)]
+ pub(crate) unsafe fn from_owned_raw(value: BNLinearDisassemblyLine) -> Self {
+ let owned = Self::from_raw(&value);
+ Self::free_raw(value);
+ owned
+ }
+
+ pub(crate) fn into_raw(value: Self) -> BNLinearDisassemblyLine {
+ let function_ptr = value
+ .function
+ .map(|f| unsafe { Ref::into_raw(f) }.handle)
+ .unwrap_or(std::ptr::null_mut());
+ let block_ptr = value
+ .basic_block
+ .map(|b| unsafe { Ref::into_raw(b) }.handle)
+ .unwrap_or(std::ptr::null_mut());
+ BNLinearDisassemblyLine {
+ type_: value.ty,
+ function: function_ptr,
+ block: block_ptr,
+ contents: DisassemblyTextLine::into_raw(value.contents),
+ }
}
- pub fn line_type(&self) -> LinearDisassemblyLineType {
- self.t
+ pub(crate) fn free_raw(value: BNLinearDisassemblyLine) {
+ let _ = unsafe { Function::ref_from_raw(value.function) };
+ DisassemblyTextLine::free_raw(value.contents);
}
}
impl Deref for LinearDisassemblyLine {
type Target = DisassemblyTextLine;
fn deref(&self) -> &Self::Target {
- self.contents.deref()
+ &self.contents
}
}
impl std::fmt::Display for LinearDisassemblyLine {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "{}", self.deref())
+ write!(f, "{}", self.contents)
}
}
impl CoreArrayProvider for LinearDisassemblyLine {
type Raw = BNLinearDisassemblyLine;
type Context = ();
- type Wrapped<'a> = Guard<'a, LinearDisassemblyLine>;
+ type Wrapped<'a> = LinearDisassemblyLine;
}
unsafe impl CoreArrayProviderInner for LinearDisassemblyLine {
@@ -397,8 +494,7 @@ unsafe impl CoreArrayProviderInner for LinearDisassemblyLine {
BNFreeLinearDisassemblyLines(raw, count);
}
- unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> {
- // TODO: Cant remove this guard until we remove those manual drops... INSANE!
- Guard::new(Self::from_raw(raw), context)
+ unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> {
+ Self::from_raw(raw)
}
}
diff --git a/rust/src/low_level_il.rs b/rust/src/low_level_il.rs
index 5df7252b..750bcfd1 100644
--- a/rust/src/low_level_il.rs
+++ b/rust/src/low_level_il.rs
@@ -24,7 +24,7 @@ use crate::architecture::Register as ArchReg;
use crate::architecture::{Architecture, RegisterId};
use crate::function::Location;
-mod block;
+pub mod block;
pub mod expression;
pub mod function;
pub mod instruction;
diff --git a/rust/src/medium_level_il/function.rs b/rust/src/medium_level_il/function.rs
index a9802005..8f91c731 100644
--- a/rust/src/medium_level_il/function.rs
+++ b/rust/src/medium_level_il/function.rs
@@ -548,10 +548,10 @@ impl MediumLevelILFunction {
unsafe { BNGetMediumLevelILSSAVarValue(self.handle, &raw_var, ssa_variable.version) }.into()
}
- pub fn create_graph(&self, settings: Option<DisassemblySettings>) -> FlowGraph {
+ pub fn create_graph(&self, settings: Option<DisassemblySettings>) -> Ref<FlowGraph> {
let settings = settings.map(|x| x.handle).unwrap_or(std::ptr::null_mut());
let graph = unsafe { BNCreateMediumLevelILFunctionGraph(self.handle, settings) };
- unsafe { FlowGraph::from_raw(graph) }
+ unsafe { FlowGraph::ref_from_raw(graph) }
}
/// This gets just the MLIL variables - you may be interested in the union
diff --git a/rust/src/render_layer.rs b/rust/src/render_layer.rs
new file mode 100644
index 00000000..fa617fe7
--- /dev/null
+++ b/rust/src/render_layer.rs
@@ -0,0 +1,410 @@
+//! Customize the presentation of Linear and Graph view output.
+
+use crate::basic_block::{BasicBlock, BasicBlockType};
+use crate::disassembly::DisassemblyTextLine;
+use crate::flowgraph::FlowGraph;
+use crate::function::{Function, NativeBlock};
+use crate::linear_view::{LinearDisassemblyLine, LinearDisassemblyLineType, LinearViewObject};
+use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner};
+use crate::string::BnStrCompatible;
+use binaryninjacore_sys::*;
+use std::ffi::{c_char, c_void};
+use std::ptr::NonNull;
+
+pub type RenderLayerDefaultEnableState = BNRenderLayerDefaultEnableState;
+
+/// Register a [`RenderLayer`] with the API.
+pub fn register_render_layer<S: BnStrCompatible, T: RenderLayer>(
+ name: S,
+ render_layer: T,
+ enable_state: RenderLayerDefaultEnableState,
+) -> (&'static mut T, CoreRenderLayer) {
+ let render_layer = Box::leak(Box::new(render_layer));
+ let mut callback = BNRenderLayerCallbacks {
+ context: render_layer as *mut _ as *mut c_void,
+ applyToFlowGraph: Some(cb_apply_to_flow_graph::<T>),
+ applyToLinearViewObject: Some(cb_apply_to_linear_view_object::<T>),
+ freeLines: Some(cb_free_lines),
+ };
+ let result = unsafe {
+ BNRegisterRenderLayer(
+ name.into_bytes_with_nul().as_ref().as_ptr() as *const _,
+ &mut callback,
+ enable_state,
+ )
+ };
+ let core = CoreRenderLayer::from_raw(NonNull::new(result).unwrap());
+ (render_layer, core)
+}
+
+pub trait RenderLayer: Sized {
+ /// Apply this Render Layer to a Flow Graph.
+ fn apply_to_flow_graph(&self, graph: &mut FlowGraph) {
+ for node in graph.nodes() {
+ if let Some(block) = node.basic_block(NativeBlock::new()) {
+ let new_lines = self.apply_to_block(&block, node.lines().to_vec());
+ node.set_lines(new_lines);
+ }
+ }
+ }
+
+ /// Apply this Render Layer to the lines produced by a LinearViewObject for rendering in Linear View.
+ fn apply_to_linear_object(
+ &self,
+ object: &mut LinearViewObject,
+ _prev_object: Option<&mut LinearViewObject>,
+ _next_object: Option<&mut LinearViewObject>,
+ lines: Vec<LinearDisassemblyLine>,
+ ) -> Vec<LinearDisassemblyLine> {
+ let text_to_lines =
+ |function: &Function, block: &BasicBlock<NativeBlock>, text: DisassemblyTextLine| {
+ LinearDisassemblyLine {
+ ty: LinearDisassemblyLineType::CodeDisassemblyLineType,
+ function: Some(function.to_owned()),
+ basic_block: Some(block.to_owned()),
+ contents: text,
+ }
+ };
+
+ // Hack: HLIL bodies don't have basic blocks.
+ let obj_ident = object.identifier();
+ if !lines.is_empty()
+ && (obj_ident.name.starts_with("HLIL") || obj_ident.name.starts_with("Language"))
+ {
+ // Apply to HLIL body.
+ let function = lines[0]
+ .function
+ .to_owned()
+ .expect("HLIL body has no function");
+ return self.apply_to_hlil_body(&function, lines);
+ }
+
+ // Collect the "line blocks".
+ // Line blocks are contiguous lines with the same backing basic block (or lack thereof).
+ // Line blocks also group by line type.
+ let mut line_blocks: Vec<Vec<LinearDisassemblyLine>> = Vec::new();
+ for line in lines {
+ let Some(last_block) = line_blocks.last_mut() else {
+ // No last block, create the first block.
+ line_blocks.push(vec![line]);
+ continue;
+ };
+
+ let Some(last_line) = last_block.last() else {
+ // No last line, create the first line.
+ last_block.push(line);
+ continue;
+ };
+
+ // TODO: If we want to allow a block with multiple line types we need to specifically check
+ // TODO: If the last line type was Code, if it is and the last line is not we make a new block.
+ if last_line.basic_block == line.basic_block && last_line.ty == line.ty {
+ // Same basic block and line type, this is a part of the same line block.
+ last_block.push(line);
+ } else {
+ // Not the same line block, create a new block.
+ line_blocks.push(vec![line]);
+ }
+ }
+
+ line_blocks
+ .into_iter()
+ .filter_map(|line_block| {
+ let probe_line = line_block.first()?;
+ Some((probe_line.ty, probe_line.basic_block.to_owned(), line_block))
+ })
+ .map(|(line_ty, basic_block, lines)| {
+ match line_ty {
+ LinearDisassemblyLineType::CodeDisassemblyLineType => {
+ // Dealing with code lines.
+ let block = basic_block.expect("Code line has no basic block");
+ let function = block.function();
+ let text_lines = lines.into_iter().map(|line| line.contents).collect();
+ let new_text_lines = self.apply_to_block(&block, text_lines);
+ let new_lines = new_text_lines
+ .into_iter()
+ .map(|line| text_to_lines(&function, &block, line))
+ .collect();
+ new_lines
+ }
+ _ => {
+ // Dealing with misc lines.
+ self.apply_to_misc_lines(
+ object,
+ _prev_object.as_deref(),
+ _next_object.as_deref(),
+ lines,
+ )
+ }
+ }
+ })
+ .flatten()
+ .collect()
+ }
+
+ /// Apply this Render Layer to a single Basic Block of Disassembly lines.
+ ///
+ /// Modify the lines to change the presentation of the block.
+ fn apply_to_disassembly_block(
+ &self,
+ _block: &BasicBlock<NativeBlock>,
+ lines: Vec<DisassemblyTextLine>,
+ ) -> Vec<DisassemblyTextLine> {
+ lines
+ }
+
+ /// Apply this Render Layer to a single Basic Block of Low Level IL lines.
+ ///
+ /// Modify the lines to change the presentation of the block.
+ fn apply_to_llil_block(
+ &self,
+ _block: &BasicBlock<NativeBlock>,
+ lines: Vec<DisassemblyTextLine>,
+ ) -> Vec<DisassemblyTextLine> {
+ lines
+ }
+
+ /// Apply this Render Layer to a single Basic Block of Medium Level IL lines.
+ ///
+ /// Modify the lines to change the presentation of the block.
+ fn apply_to_mlil_block(
+ &self,
+ _block: &BasicBlock<NativeBlock>,
+ lines: Vec<DisassemblyTextLine>,
+ ) -> Vec<DisassemblyTextLine> {
+ lines
+ }
+
+ /// Apply this Render Layer to a single Basic Block of High Level IL lines.
+ ///
+ /// Modify the lines to change the presentation of the block.
+ ///
+ /// This function will NOT apply to High Level IL bodies as displayed in Linear View!
+ /// Those are handled by [`RenderLayer::apply_to_hlil_body`] instead as they do not
+ /// have a [`BasicBlock`] associated with them.
+ fn apply_to_hlil_block(
+ &self,
+ _block: &BasicBlock<NativeBlock>,
+ lines: Vec<DisassemblyTextLine>,
+ ) -> Vec<DisassemblyTextLine> {
+ lines
+ }
+
+ /// Apply this Render Layer to the entire body of a High Level IL function.
+ ///
+ /// Modify the lines to change the presentation of the block.
+ ///
+ /// This function only applies to Linear View, and not to Graph View! If you want to
+ /// handle Graph View too, you will need to use [`RenderLayer::apply_to_hlil_block`] and handle
+ /// the lines one block at a time.
+ fn apply_to_hlil_body(
+ &self,
+ _function: &Function,
+ lines: Vec<LinearDisassemblyLine>,
+ ) -> Vec<LinearDisassemblyLine> {
+ lines
+ }
+
+ // TODO: We might want to just go ahead and pass the line type.
+ /// Apply to lines generated by Linear View that are not part of a function.
+ ///
+ /// Modify the lines to change the presentation of the block.
+ fn apply_to_misc_lines(
+ &self,
+ _object: &mut LinearViewObject,
+ _prev_object: Option<&LinearViewObject>,
+ _next_object: Option<&LinearViewObject>,
+ lines: Vec<LinearDisassemblyLine>,
+ ) -> Vec<LinearDisassemblyLine> {
+ lines
+ }
+
+ /// Apply this Render Layer to all IL blocks and disassembly blocks.
+ ///
+ /// If not implemented this will handle calling the view specific apply functions:
+ ///
+ /// - [`RenderLayer::apply_to_disassembly_block`]
+ /// - [`RenderLayer::apply_to_llil_block`]
+ /// - [`RenderLayer::apply_to_mlil_block`]
+ /// - [`RenderLayer::apply_to_hlil_block`]
+ ///
+ /// Modify the lines to change the presentation of the block.
+ fn apply_to_block(
+ &self,
+ block: &BasicBlock<NativeBlock>,
+ lines: Vec<DisassemblyTextLine>,
+ ) -> Vec<DisassemblyTextLine> {
+ match block.block_type() {
+ BasicBlockType::Native => self.apply_to_disassembly_block(block, lines),
+ BasicBlockType::LowLevelIL => self.apply_to_llil_block(block, lines),
+ BasicBlockType::MediumLevelIL => self.apply_to_mlil_block(block, lines),
+ BasicBlockType::HighLevelIL => self.apply_to_hlil_block(block, lines),
+ }
+ }
+}
+
+#[repr(transparent)]
+pub struct CoreRenderLayer {
+ pub(crate) handle: NonNull<BNRenderLayer>,
+}
+
+impl CoreRenderLayer {
+ pub fn from_raw(handle: NonNull<BNRenderLayer>) -> Self {
+ Self { handle }
+ }
+
+ pub fn render_layers() -> Array<CoreRenderLayer> {
+ let mut count = 0;
+ let result = unsafe { BNGetRenderLayerList(&mut count) };
+ unsafe { Array::new(result, count, ()) }
+ }
+
+ pub fn render_layer_by_name<S: BnStrCompatible>(name: S) -> Option<CoreRenderLayer> {
+ let name_raw = name.into_bytes_with_nul();
+ let result = unsafe { BNGetRenderLayerByName(name_raw.as_ref().as_ptr() as *const c_char) };
+ NonNull::new(result).map(|x| Self::from_raw(x))
+ }
+
+ pub fn default_enable_state(&self) -> RenderLayerDefaultEnableState {
+ unsafe { BNGetRenderLayerDefaultEnableState(self.handle.as_ptr()) }
+ }
+
+ pub fn apply_to_flow_graph(&self, graph: &FlowGraph) {
+ unsafe { BNApplyRenderLayerToFlowGraph(self.handle.as_ptr(), graph.handle) }
+ }
+
+ pub fn apply_to_linear_view_object(
+ &self,
+ object: &LinearViewObject,
+ prev_object: Option<&LinearViewObject>,
+ next_object: Option<&LinearViewObject>,
+ lines: Vec<LinearDisassemblyLine>,
+ ) -> Vec<LinearDisassemblyLine> {
+ let mut lines_raw: Vec<_> = lines
+ .into_iter()
+ // NOTE: Freed after the core call
+ .map(LinearDisassemblyLine::into_raw)
+ .collect();
+
+ let prev_object_ptr = prev_object
+ .map(|o| o.handle)
+ .unwrap_or(std::ptr::null_mut());
+ let next_object_ptr = next_object
+ .map(|o| o.handle)
+ .unwrap_or(std::ptr::null_mut());
+
+ let mut new_lines = std::ptr::null_mut();
+ let mut new_line_count = 0;
+
+ unsafe {
+ BNApplyRenderLayerToLinearViewObject(
+ self.handle.as_ptr(),
+ object.handle,
+ prev_object_ptr,
+ next_object_ptr,
+ lines_raw.as_mut_ptr(),
+ lines_raw.len(),
+ &mut new_lines,
+ &mut new_line_count,
+ )
+ };
+
+ for line in lines_raw {
+ LinearDisassemblyLine::free_raw(line);
+ }
+
+ let raw: Array<LinearDisassemblyLine> =
+ unsafe { Array::new(new_lines, new_line_count, ()) };
+ raw.to_vec()
+ }
+}
+
+impl CoreArrayProvider for CoreRenderLayer {
+ type Raw = *mut BNRenderLayer;
+ type Context = ();
+ type Wrapped<'a> = Self;
+}
+
+unsafe impl CoreArrayProviderInner for CoreRenderLayer {
+ unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) {
+ BNFreeRenderLayerList(raw)
+ }
+
+ unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> {
+ // TODO: Because handle is a NonNull we should prob make Self::Raw that as well...
+ let handle = NonNull::new(*raw).unwrap();
+ CoreRenderLayer::from_raw(handle)
+ }
+}
+
+unsafe extern "C" fn cb_apply_to_flow_graph<T: RenderLayer>(
+ ctxt: *mut c_void,
+ graph: *mut BNFlowGraph,
+) {
+ let ctxt: &mut T = &mut *(ctxt as *mut T);
+ // SAFETY: We do not own the flowgraph, do not take it as Ref.
+ let mut flow_graph = FlowGraph::from_raw(graph);
+ ctxt.apply_to_flow_graph(&mut flow_graph);
+}
+
+unsafe extern "C" fn cb_apply_to_linear_view_object<T: RenderLayer>(
+ ctxt: *mut c_void,
+ object: *mut BNLinearViewObject,
+ prev: *mut BNLinearViewObject,
+ next: *mut BNLinearViewObject,
+ in_lines: *mut BNLinearDisassemblyLine,
+ in_line_count: usize,
+ out_lines: *mut *mut BNLinearDisassemblyLine,
+ out_line_count: *mut usize,
+) {
+ let ctxt: &mut T = &mut *(ctxt as *mut T);
+ // SAFETY: We do not own the flowgraph, do not take it as Ref.
+ let mut object = LinearViewObject::from_raw(object);
+ let mut prev_object = if !prev.is_null() {
+ Some(LinearViewObject::from_raw(prev))
+ } else {
+ None
+ };
+ let mut next_object = if !next.is_null() {
+ Some(LinearViewObject::from_raw(next))
+ } else {
+ None
+ };
+
+ let raw_lines = std::slice::from_raw_parts(in_lines, in_line_count);
+ // NOTE: The caller is owned of the inLines.
+ let lines: Vec<_> = raw_lines
+ .iter()
+ .map(|line| LinearDisassemblyLine::from_raw(line))
+ .collect();
+
+ let new_lines = ctxt.apply_to_linear_object(
+ &mut object,
+ prev_object.as_mut(),
+ next_object.as_mut(),
+ lines,
+ );
+
+ unsafe {
+ *out_line_count = new_lines.len();
+ let boxed_new_lines: Box<[_]> = new_lines
+ .into_iter()
+ // NOTE: Freed by cb_free_lines
+ .map(LinearDisassemblyLine::into_raw)
+ .collect();
+ // NOTE: Dropped by cb_free_lines
+ *out_lines = Box::leak(boxed_new_lines).as_mut_ptr();
+ }
+}
+
+unsafe extern "C" fn cb_free_lines(
+ _ctxt: *mut c_void,
+ lines: *mut BNLinearDisassemblyLine,
+ line_count: usize,
+) {
+ let lines_ptr = std::ptr::slice_from_raw_parts_mut(lines, line_count);
+ let boxed_lines = Box::from_raw(lines_ptr);
+ for line in boxed_lines {
+ LinearDisassemblyLine::free_raw(line);
+ }
+}
diff --git a/rust/src/workflow.rs b/rust/src/workflow.rs
index 68cd3e18..e95beb48 100644
--- a/rust/src/workflow.rs
+++ b/rust/src/workflow.rs
@@ -565,7 +565,7 @@ impl Workflow {
&self,
activity: A,
sequential: Option<bool>,
- ) -> Option<FlowGraph> {
+ ) -> Option<Ref<FlowGraph>> {
let sequential = sequential.unwrap_or(false);
let activity_name = activity.into_bytes_with_nul();
let graph = unsafe {
@@ -578,7 +578,7 @@ impl Workflow {
if graph.is_null() {
return None;
}
- Some(unsafe { FlowGraph::from_raw(graph) })
+ Some(unsafe { FlowGraph::ref_from_raw(graph) })
}
/// Not yet implemented.
diff --git a/rust/tests/render_layer.rs b/rust/tests/render_layer.rs
new file mode 100644
index 00000000..6c914564
--- /dev/null
+++ b/rust/tests/render_layer.rs
@@ -0,0 +1,96 @@
+use binaryninja::basic_block::BasicBlock;
+use binaryninja::disassembly::{DisassemblyOption, DisassemblySettings, DisassemblyTextLine};
+use binaryninja::function::NativeBlock;
+use binaryninja::headless::Session;
+use binaryninja::linear_view::LinearViewObject;
+use binaryninja::render_layer::{register_render_layer, CoreRenderLayer, RenderLayer};
+use rstest::{fixture, rstest};
+use std::path::PathBuf;
+
+#[fixture]
+#[once]
+fn session() -> Session {
+ Session::new().expect("Failed to initialize session")
+}
+
+#[rstest]
+fn test_render_layer_register(_session: &Session) {
+ struct EmptyRenderLayer;
+ impl RenderLayer for EmptyRenderLayer {}
+ register_render_layer("Test Render Layer", EmptyRenderLayer);
+ CoreRenderLayer::render_layer_by_name("Test Render Layer").expect("Failed to get render layer");
+}
+
+#[rstest]
+fn test_render_layer_linear_view(_session: &Session) {
+ let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
+ let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view");
+
+ struct NopRenderLayer;
+ impl RenderLayer for NopRenderLayer {
+ fn apply_to_disassembly_block(
+ &self,
+ _block: &BasicBlock<NativeBlock>,
+ lines: Vec<DisassemblyTextLine>,
+ ) -> Vec<DisassemblyTextLine> {
+ println!("Nothing added to disassembly block");
+ lines
+ }
+ }
+ let (_, nop_render_layer) = register_render_layer("Nop Render Layer", NopRenderLayer);
+
+ // Create linear view object stuff
+ let settings = DisassemblySettings::new();
+ settings.set_option(DisassemblyOption::ShowAddress, false);
+ settings.set_option(DisassemblyOption::WaitForIL, true);
+ settings.set_option(DisassemblyOption::IndentHLILBody, false);
+ settings.set_option(DisassemblyOption::ShowCollapseIndicators, false);
+ settings.set_option(DisassemblyOption::ShowFunctionHeader, false);
+
+ let linear_view = LinearViewObject::disassembly(&view, &settings);
+ let mut cursor = linear_view.create_cursor();
+ // Seek to the start of the function `__crt_strtox::is_overflow_condition<uint64_t>`
+ cursor.seek_to_address(0x26240);
+ let current_object = cursor.current_object();
+ let current_lines = cursor.lines().to_vec();
+
+ let new_lines = nop_render_layer.apply_to_linear_view_object(
+ &current_object,
+ None,
+ None,
+ current_lines.clone(),
+ );
+
+ // These should 100% be in the same order. If not that is a bug.
+
+ for (i, (current_line, new_line)) in current_lines.iter().zip(new_lines.iter()).enumerate() {
+ if current_line != new_line {
+ assert_eq!(current_line, new_line, "Line mismatch at index {}", i);
+ }
+ }
+
+ struct AddRenderLayer;
+ impl RenderLayer for AddRenderLayer {
+ fn apply_to_disassembly_block(
+ &self,
+ _block: &BasicBlock<NativeBlock>,
+ mut lines: Vec<DisassemblyTextLine>,
+ ) -> Vec<DisassemblyTextLine> {
+ println!("Adding to disassembly block");
+ lines.push(DisassemblyTextLine::from("heyyyyy"));
+ lines
+ }
+ }
+ let (_, adding_render_layer) = register_render_layer("Add Render Layer", AddRenderLayer);
+
+ // Calling lines() again should now have the render layer applied.
+ cursor.add_render_layer(&adding_render_layer);
+ let new_current_lines = cursor.lines().to_vec();
+ // Assert that new_current_lines is one longer than current_lines (we added a line)
+ assert_eq!(new_current_lines.len(), current_lines.len() + 1);
+
+ // Remove the render layer and make sure that the line is no longer present.
+ cursor.remove_render_layer(&adding_render_layer);
+ let new_new_current_lines = cursor.lines().to_vec();
+ assert_eq!(new_new_current_lines.len(), current_lines.len());
+}
diff --git a/ui/disassemblyview.h b/ui/disassemblyview.h
index c7595f96..7c97576d 100644
--- a/ui/disassemblyview.h
+++ b/ui/disassemblyview.h
@@ -81,6 +81,9 @@ class BINARYNINJAUIAPI DisassemblyView : public FlowGraphWidget
void setDisplayedFileName();
void setAddressBaseOffset(bool toHere);
+ void toggleRenderLayer(const std::string& layer);
+ FlowGraphRef applyRenderLayers(FlowGraphRef graph);
+
virtual DisassemblySettingsRef getDisassemblySettings() override;
virtual void setDisassemblySettings(DisassemblySettingsRef settings) override;
@@ -89,6 +92,7 @@ class BINARYNINJAUIAPI DisassemblyView : public FlowGraphWidget
virtual void onHighlightChanged(const HighlightTokenState& highlight) override;
static void registerActions();
+ virtual void bindDynamicActions() override;
private:
class DisassemblyViewOptionsWidget : public MenuHelper
@@ -138,6 +142,7 @@ class BINARYNINJAUIAPI DisassemblyView : public FlowGraphWidget
BNDisassemblyCallParameterHints m_callParamHints;
DisassemblyContainer* m_container;
SettingsRef m_settings;
+ std::set<std::string> m_layers;
private Q_SLOTS:
void viewInHexEditor();
diff --git a/ui/flowgraphwidget.h b/ui/flowgraphwidget.h
index 858b8126..3ef1b111 100644
--- a/ui/flowgraphwidget.h
+++ b/ui/flowgraphwidget.h
@@ -182,7 +182,7 @@ class BINARYNINJAUIAPI FlowGraphWidget :
virtual void contextMenuEvent(QContextMenuEvent*) override;
void bindActions();
- void bindDynamicActions();
+ virtual void bindDynamicActions();
void navigateToAddress(uint64_t addr);
void navigateToGotoLabel(uint64_t label);
diff --git a/ui/linearview.h b/ui/linearview.h
index 888425e7..d328fd7c 100644
--- a/ui/linearview.h
+++ b/ui/linearview.h
@@ -242,6 +242,8 @@ class BINARYNINJAUIAPI LinearView : public QAbstractScrollArea, public View, pub
QWidget* m_dataButtonContainer = nullptr;
QHBoxLayout* m_dataButtonLayout = nullptr;
+ std::set<std::string> m_layers;
+
void setTopToAddress(uint64_t addr);
void setTopToOrderingIndex(uint64_t idx);
void refreshLines(size_t lineOffset = 0, bool refreshUIContext = true);
@@ -499,6 +501,9 @@ public:
void setDisplayedFileName();
void setAddressBaseOffset(bool toHere);
+ void toggleRenderLayer(const std::string& layer);
+ BinaryNinja::Ref<BinaryNinja::LinearViewCursor> applyRenderLayers(BinaryNinja::Ref<BinaryNinja::LinearViewCursor> cursor);
+
virtual bool goToReference(FunctionRef func, uint64_t source, uint64_t target) override;
QFont getFont() override { return m_render.getFont(); }