Source code for binaryninja.flowgraph

# 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 threading
import traceback
from typing import List, Optional, Tuple

# Binary Ninja components
import binaryninja
from . import _binaryninjacore as core
from .enums import (
    BranchType, InstructionTextTokenType, HighlightStandardColor, FlowGraphOption, EdgePenStyle, ThemeColor
)
from . import function
from . import binaryview
from . import lowlevelil
from . import mediumlevelil
from . import highlevelil
from . import basicblock
from .log import log_error
from . import highlight
from . import interaction


[docs] class FlowGraphEdge: def __init__(self, branch_type, source, target, points, back_edge, style): self.type = BranchType(branch_type) self.source = source self.target = target self.points = points self.back_edge = back_edge self.style = style def __repr__(self): return "<%s: %s>" % (self.type.name, repr(self.target)) def __eq__(self, other): return (self.type, self.source, self.target, self.points, self.back_edge, self.style) == (other.type, other.source, other.target, other.points, other.back_edge, other.style) def __hash__(self): return hash((self.type, self.source, self.target, self.points, self.back_edge, self.style))
[docs] class EdgeStyle: def __init__(self, style: Optional[EdgePenStyle] = None, width: Optional[int] = None, theme_color: Optional[ThemeColor] = None): self.style = style if style is not None else EdgePenStyle.SolidLine self.width = width if width is not None else 0 self.color = theme_color if theme_color is not None else ThemeColor.AddressColor def _to_core_struct(self) -> core.BNEdgeStyle: result = core.BNEdgeStyle() result.style = int(self.style) result.width = self.width result.color = self.color return result
[docs] @staticmethod def from_core_struct(edge_style): return EdgeStyle(edge_style.style, edge_style.width, edge_style.color)
def __eq__(self, other): return (self.style, self.width, self.color) == (other.style, other.width, other.color) def __hash__(self): return hash((self.style, self.width, self.color))
[docs] class FlowGraphNode: def __init__(self, graph=None, handle=None): _handle = handle if _handle is None: if graph is None: raise ValueError("flow graph node must be associated with a graph") _handle = core.BNCreateFlowGraphNode(graph.handle) assert _handle is not None self.handle = _handle self._graph = graph if self._graph is None: self._graph = FlowGraph(handle=core.BNGetFlowGraphNodeOwner(self.handle)) def __del__(self): if core is not None: core.BNFreeFlowGraphNode(self.handle) def __repr__(self): block = self.basic_block if block: arch = block.arch if arch: return "<graph node: %s@%#x-%#x>" % (arch.name, block.start, block.end) else: return "<graph node: %#x-%#x>" % (block.start, block.end) return "<graph node>" def __eq__(self, other): if not isinstance(other, self.__class__): return NotImplemented return ctypes.addressof(self.handle.contents) == ctypes.addressof(other.handle.contents) def __ne__(self, other): if not isinstance(other, self.__class__): return NotImplemented return not (self == other) def __hash__(self): return hash(ctypes.addressof(self.handle.contents)) def __iter__(self): count = ctypes.c_ulonglong() lines = core.BNGetFlowGraphNodeLines(self.handle, count) assert lines is not None, "core.BNGetFlowGraphNodeLines returned None" block = self.basic_block try: for i in range(0, count.value): addr = lines[i].addr if (lines[i].instrIndex != 0xffffffffffffffff) and (block is not None) and hasattr(block, 'il_function'): il_instr = block.il_function[lines[i].instrIndex] else: il_instr = None tokens = function.InstructionTextToken._from_core_struct(lines[i].tokens, lines[i].count) yield function.DisassemblyTextLine(tokens, addr, il_instr) finally: core.BNFreeDisassemblyTextLines(lines, count.value) @property def graph(self): return self._graph @graph.setter def graph(self, value): self._graph = value @property def basic_block(self): """Basic block associated with this part of the flow graph (well not automatically cause the node to render as a native basic block)""" 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 @basic_block.setter def basic_block(self, block): if block is None: core.BNSetFlowGraphBasicBlock(self.handle, None) else: core.BNSetFlowGraphBasicBlock(self.handle, block.handle) @property def x(self): """Flow graph block X""" return core.BNGetFlowGraphNodeX(self.handle) @x.setter def x(self, x: int): return core.BNFlowGraphNodeSetX(self.handle, x) @property def y(self): """Flow graph block Y""" return core.BNGetFlowGraphNodeY(self.handle) @y.setter def y(self, y: int): return core.BNFlowGraphNodeSetY(self.handle, y) @property def width(self): """Flow graph block width (read-only)""" return core.BNGetFlowGraphNodeWidth(self.handle) @property def height(self): """Flow graph block height (read-only)""" return core.BNGetFlowGraphNodeHeight(self.handle) @property def lines(self): """Flow graph block list of text lines""" count = ctypes.c_ulonglong() lines = core.BNGetFlowGraphNodeLines(self.handle, count) assert lines is not None, "core.BNGetFlowGraphNodeLines returned None" block = self.basic_block result = [] for i in range(0, count.value): addr = lines[i].addr if (lines[i].instrIndex != 0xffffffffffffffff) and (block is not None) and hasattr(block, 'il_function'): il_instr = block.il_function[lines[i].instrIndex] else: il_instr = None color = highlight.HighlightColor._from_core_struct(lines[i].highlight) tokens = function.InstructionTextToken._from_core_struct(lines[i].tokens, lines[i].count) result.append(function.DisassemblyTextLine(tokens, addr, il_instr, color)) core.BNFreeDisassemblyTextLines(lines, count.value) return result @lines.setter def lines(self, lines): if isinstance(lines, str): lines = lines.split('\n') line_buf = (core.BNDisassemblyTextLine * len(lines))() for i in range(0, len(lines)): line = lines[i] if isinstance(line, str): line = function.DisassemblyTextLine([ function.InstructionTextToken(InstructionTextTokenType.TextToken, line) ]) if not isinstance(line, function.DisassemblyTextLine): line = function.DisassemblyTextLine(line) if line.address is None: if len(line.tokens) > 0: line_buf[i].addr = line.tokens[0].address else: line_buf[i].addr = 0 else: line_buf[i].addr = line.address if line.il_instruction is not None: line_buf[i].instrIndex = line.il_instruction.instr_index else: line_buf[i].instrIndex = 0xffffffffffffffff color = line.highlight if not isinstance(color, HighlightStandardColor) and not isinstance(color, highlight.HighlightColor): raise ValueError("Specified color is not one of HighlightStandardColor, highlight.HighlightColor") if isinstance(color, HighlightStandardColor): color = highlight.HighlightColor(color) line_buf[i].highlight = color._to_core_struct() line_buf[i].count = len(line.tokens) line_buf[i].tokens = function.InstructionTextToken._get_core_struct(line.tokens) core.BNSetFlowGraphNodeLines(self.handle, line_buf, len(lines)) @property def outgoing_edges(self) -> List[FlowGraphEdge]: """Flow graph block list of outgoing edges (read-only)""" count = ctypes.c_ulonglong() edges = core.BNGetFlowGraphNodeOutgoingEdges(self.handle, count) assert edges is not None, "core.BNGetFlowGraphNodeOutgoingEdges returned None" result = [] for i in range(0, count.value): branch_type = BranchType(edges[i].type) target = edges[i].target if target: target = FlowGraphNode(self._graph, core.BNNewFlowGraphNodeReference(target)) points = [] for j in range(0, edges[i].pointCount): points.append((edges[i].points[j].x, edges[i].points[j].y)) result.append( FlowGraphEdge(branch_type, self, target, points, edges[i].backEdge, EdgeStyle(edges[i].style)) ) core.BNFreeFlowGraphNodeEdgeList(edges, count.value) return result @property def incoming_edges(self): """Flow graph block list of incoming edges (read-only)""" count = ctypes.c_ulonglong() edges = core.BNGetFlowGraphNodeIncomingEdges(self.handle, count) assert edges is not None, "core.BNGetFlowGraphNodeIncomingEdges returned None" result = [] for i in range(0, count.value): branch_type = BranchType(edges[i].type) target = edges[i].target if target: target = FlowGraphNode(self._graph, core.BNNewFlowGraphNodeReference(target)) points = [] for j in range(0, edges[i].pointCount): points.append((edges[i].points[j].x, edges[i].points[j].y)) result.append( FlowGraphEdge(branch_type, self, target, points, edges[i].backEdge, EdgeStyle(edges[i].style)) ) core.BNFreeFlowGraphNodeEdgeList(edges, count.value) return result @property def highlight(self): """Gets or sets the highlight color for the node :Example: >>> g = FlowGraph() >>> node = FlowGraphNode(g) >>> node.highlight = HighlightStandardColor.BlueHighlightColor >>> node.highlight <color: blue> """ return highlight.HighlightColor._from_core_struct(core.BNGetFlowGraphNodeHighlight(self.handle)) @highlight.setter def highlight(self, color): if not isinstance(color, HighlightStandardColor) and not isinstance(color, highlight.HighlightColor): raise ValueError("Specified color is not one of HighlightStandardColor, highlight.HighlightColor") if isinstance(color, HighlightStandardColor): color = highlight.HighlightColor(color) core.BNSetFlowGraphNodeHighlight(self.handle, color._to_core_struct())
[docs] def add_outgoing_edge(self, edge_type, target, style=None): """ ``add_outgoing_edge`` connects two flow graph nodes with an edge. :param BranchType edge_type: Type of edge to add :param FlowGraphNode target: Target node object :param EdgeStyle style: (optional) Styling for graph edge Branch Type must be set to UserDefinedBranch """ if not target.is_valid_for_graph(self._graph): raise ValueError("Target of edge has not been added to the owning graph") if style is None: style = EdgeStyle() elif not isinstance(style, EdgeStyle): raise AttributeError("style must be of type EdgeStyle") core.BNAddFlowGraphNodeOutgoingEdge(self.handle, edge_type, target.handle, style._to_core_struct())
[docs] def is_valid_for_graph(self, graph): return core.BNIsNodeValidForFlowGraph(graph.handle, self.handle)
[docs] def set_visibility_region(self, x: int, y: int, w: int, h: int): core.BNFlowGraphNodeSetVisibilityRegion(self.handle, x, y, w, h)
[docs] def set_outgoing_edge_points(self, edge_num: int, points: List[Tuple[float, float]]): point_buf = (core.BNPoint * len(points))() for i in range(0, len(points)): point_buf[i].x = points[i][0] point_buf[i].y = points[i][1] core.BNFlowGraphNodeSetOutgoingEdgePoints(self.handle, edge_num, point_buf, len(points))
[docs] class FlowGraphLayoutRequest: def __init__(self, graph, callback=None): self.on_complete = callback self._cb = ctypes.CFUNCTYPE(None, ctypes.c_void_p)(self._complete) self.handle = core.BNStartFlowGraphLayout(graph.handle, None, self._cb) def __del__(self): if core is None: return self.abort() core.BNFreeFlowGraphLayoutRequest(self.handle) def _complete(self, ctxt): try: if self.on_complete is not None: self.on_complete() except: log_error(traceback.format_exc()) @property def complete(self): """Whether flow graph layout is complete (read-only)""" return core.BNIsFlowGraphLayoutRequestComplete(self.handle) @property def graph(self): """Flow graph that is being processed (read-only)""" return CoreFlowGraph(core.BNGetGraphForFlowGraphLayoutRequest(self.handle))
[docs] def abort(self): core.BNAbortFlowGraphLayoutRequest(self.handle) self.on_complete = None
[docs] class FlowGraph: """ ``class FlowGraph`` implements a directed flow graph to be shown in the UI. This class allows plugins to create custom flow graphs and render them in the UI using the flow graph report API. An example of creating a flow graph and presenting it in the UI: >>> graph = FlowGraph() >>> node_a = FlowGraphNode(graph) >>> node_a.lines = ["Node A"] >>> node_b = FlowGraphNode(graph) >>> node_b.lines = ["Node B"] >>> node_c = FlowGraphNode(graph) >>> node_c.lines = ["Node C"] >>> graph.append(node_a) 0 >>> graph.append(node_b) 1 >>> graph.append(node_c) 2 >>> edge = EdgeStyle(EdgePenStyle.DashDotDotLine, 2, ThemeColor.AddressColor) >>> node_a.add_outgoing_edge(BranchType.UserDefinedBranch, node_b, edge) >>> node_a.add_outgoing_edge(BranchType.UnconditionalBranch, node_c) >>> show_graph_report("Custom Graph", graph) .. note:: In the current implementation, only graphs that have a single start node where all other nodes are \ reachable from outgoing edges can be rendered correctly. This describes the natural limitations of a control \ flow graph, which is what the rendering logic was designed for. Graphs that have nodes that are only reachable \ from incoming edges, or graphs that have disjoint subgraphs will not render correctly. This will be fixed \ in a future version. """ _registered_instances = [] def __init__(self, handle: Optional[core.BNCustomFlowGraphHandle] = None): _handle = handle if _handle is None: self._ext_cb = core.BNCustomFlowGraph() self._ext_cb.context = 0 self._ext_cb.prepareForLayout = self._ext_cb.prepareForLayout.__class__(self._prepare_for_layout) self._ext_cb.populateNodes = self._ext_cb.populateNodes.__class__(self._populate_nodes) self._ext_cb.completeLayout = self._ext_cb.completeLayout.__class__(self._complete_layout) self._ext_cb.update = self._ext_cb.update.__class__(self._update) self._ext_cb.externalRefTaken = self._ext_cb.externalRefTaken.__class__(self._external_ref_taken) self._ext_cb.externalRefReleased = self._ext_cb.externalRefReleased.__class__(self._external_ref_released) _handle = core.BNCreateCustomFlowGraph(self._ext_cb) assert _handle is not None self.handle = _handle def __del__(self): if core is not None: core.BNFreeFlowGraph(self.handle) def __repr__(self): function = self.function if function is None: return "<flow graph>" return "<graph of %s>" % repr(function) def __eq__(self, other): if not isinstance(other, self.__class__): return NotImplemented return ctypes.addressof(self.handle.contents) == ctypes.addressof(other.handle.contents) def __ne__(self, other): if not isinstance(other, self.__class__): return NotImplemented return not (self == other) def __hash__(self): return hash(ctypes.addressof(self.handle.contents)) def __setattr__(self, name, value): try: object.__setattr__(self, name, value) except AttributeError: raise AttributeError("attribute '%s' is read only" % name) def __iter__(self): count = ctypes.c_ulonglong() nodes = core.BNGetFlowGraphNodes(self.handle, count) assert nodes is not None, "core.BNGetFlowGraphNodes returned None" try: for i in range(0, count.value): fg_node = core.BNNewFlowGraphNodeReference(nodes[i]) assert fg_node is not None, "core.BNNewFlowGraphNodeReference returned None" yield FlowGraphNode(self, fg_node) finally: core.BNFreeFlowGraphNodeList(nodes, count.value) def __getitem__(self, i): node = core.BNGetFlowGraphNode(self.handle, i) if node is None: return None return FlowGraphNode(self, node) def _prepare_for_layout(self, ctxt): try: self.prepare_for_layout() except: log_error(traceback.format_exc()) def _populate_nodes(self, ctxt): try: self.populate_nodes() except: log_error(traceback.format_exc()) def _complete_layout(self, ctxt): try: self.complete_layout() except: log_error(traceback.format_exc()) def _update(self, ctxt): try: graph = self.update() if graph is NotImplemented: return None flow_graph = core.BNNewFlowGraphReference(graph.handle) assert flow_graph is not None, "core.BNNewFlowGraphReference returned None" return ctypes.cast(flow_graph, ctypes.c_void_p).value except: log_error(traceback.format_exc()) return None def _external_ref_taken(self, ctxt): try: self.__class__._registered_instances.append(self) except: log_error(traceback.format_exc()) def _external_ref_released(self, ctxt): try: self.__class__._registered_instances.remove(self) except: log_error(traceback.format_exc())
[docs] def finish_prepare_for_layout(self): """ ``finish_prepare_for_layout`` signals that preparations for rendering a graph are complete. This method should only be called by a :func:`prepare_for_layout` reimplementation. """ core.BNFinishPrepareForLayout(self.handle)
[docs] def prepare_for_layout(self): """ ``prepare_for_layout`` can be overridden by subclasses to handling preparations that must take place before a flow graph is rendered, such as waiting for a function to finish analysis. If this function is overridden, the :func:`finish_prepare_for_layout` method must be called once preparations are completed. """ self.finish_prepare_for_layout()
[docs] def populate_nodes(self): """ ``populate_nodes`` can be overridden by subclasses to create nodes in a graph when a flow graph needs to be rendered. This will happen on a worker thread and will not block the UI. """ pass
[docs] def complete_layout(self): """ ``complete_layout`` can be overridden by subclasses and is called when a graph layout is completed. """ pass
@property def function(self): """Function for a flow graph""" func = core.BNGetFunctionForFlowGraph(self.handle) if func is None: return None return function.Function(handle=func) @function.setter def function(self, func): if func is not None: func = func.handle core.BNSetFunctionForFlowGraph(self.handle, func) @property def view(self): """Binary view for a flow graph""" view = core.BNGetViewForFlowGraph(self.handle) if view is None: return None return binaryview.BinaryView(handle=view) @view.setter def view(self, view): if view is not None: view = view.handle core.BNSetViewForFlowGraph(self.handle, view) @property def complete(self): """Whether flow graph layout is complete (read-only)""" return core.BNIsFlowGraphLayoutComplete(self.handle) @property def nodes(self): """List of nodes in graph (read-only)""" count = ctypes.c_ulonglong() blocks = core.BNGetFlowGraphNodes(self.handle, count) assert blocks is not None, "core.BNGetFlowGraphNodes returned None" result = [] for i in range(0, count.value): fg_node = core.BNNewFlowGraphNodeReference(blocks[i]) assert fg_node is not None, "core.BNNewFlowGraphNodeReference returned None" result.append(FlowGraphNode(self, fg_node)) core.BNFreeFlowGraphNodeList(blocks, count.value) return result @property def has_nodes(self): """Whether the flow graph has at least one node (read-only)""" return core.BNFlowGraphHasNodes(self.handle) @property def width(self): """Flow graph width""" return core.BNGetFlowGraphWidth(self.handle) @width.setter def width(self, width: int): return core.BNFlowGraphSetWidth(self.handle, width) @property def height(self): """Flow graph height""" return core.BNGetFlowGraphHeight(self.handle) @height.setter def height(self, height: int): return core.BNFlowGraphSetHeight(self.handle, height) @property def horizontal_block_margin(self): return core.BNGetHorizontalFlowGraphNodeMargin(self.handle) @horizontal_block_margin.setter def horizontal_block_margin(self, value): core.BNSetFlowGraphNodeMargins(self.handle, value, self.vertical_block_margin) @property def vertical_block_margin(self): return core.BNGetVerticalFlowGraphNodeMargin(self.handle) @vertical_block_margin.setter def vertical_block_margin(self, value): core.BNSetFlowGraphNodeMargins(self.handle, self.horizontal_block_margin, value) @property def is_il(self): return core.BNIsILFlowGraph(self.handle) @property def is_low_level_il(self): return core.BNIsLowLevelILFlowGraph(self.handle) @property def is_medium_level_il(self): return core.BNIsMediumLevelILFlowGraph(self.handle) @property def is_high_level_il(self): return core.BNIsHighLevelILFlowGraph(self.handle) @property def il_function(self): if self.is_low_level_il: il_func = core.BNGetFlowGraphLowLevelILFunction(self.handle) if not il_func: return None function = self.function if function is None: return None return lowlevelil.LowLevelILFunction(function.arch, il_func, function) if self.is_medium_level_il: il_func = core.BNGetFlowGraphMediumLevelILFunction(self.handle) if not il_func: return None function = self.function if function is None: return None return mediumlevelil.MediumLevelILFunction(function.arch, il_func, function) if self.is_high_level_il: il_func = core.BNGetFlowGraphHighLevelILFunction(self.handle) if not il_func: return None function = self.function if function is None: return None return highlevelil.HighLevelILFunction(function.arch, il_func, function) return None @il_function.setter def il_function(self, func): if isinstance(func, lowlevelil.LowLevelILFunction): core.BNSetFlowGraphLowLevelILFunction(self.handle, func.handle) core.BNSetFlowGraphMediumLevelILFunction(self.handle, None) core.BNSetFlowGraphHighLevelILFunction(self.handle, None) elif isinstance(func, mediumlevelil.MediumLevelILFunction): core.BNSetFlowGraphLowLevelILFunction(self.handle, None) core.BNSetFlowGraphMediumLevelILFunction(self.handle, func.handle) core.BNSetFlowGraphHighLevelILFunction(self.handle, None) elif isinstance(func, highlevelil.HighLevelILFunction): core.BNSetFlowGraphLowLevelILFunction(self.handle, None) core.BNSetFlowGraphMediumLevelILFunction(self.handle, None) core.BNSetFlowGraphHighLevelILFunction(self.handle, func.handle) elif func is None: core.BNSetFlowGraphLowLevelILFunction(self.handle, None) core.BNSetFlowGraphMediumLevelILFunction(self.handle, None) core.BNSetFlowGraphHighLevelILFunction(self.handle, None) else: raise TypeError("expected IL function for setting il_function property") @property def uses_block_highlights(self): """Set if flow graph uses the standard basic block highlighting settings""" return self.is_option_set(FlowGraphOption.FlowGraphUsesBlockHighlights) @uses_block_highlights.setter def uses_block_highlights(self, value): self.set_option(FlowGraphOption.FlowGraphUsesBlockHighlights, value) @property def uses_instruction_highlights(self): """Set if flow graph uses the standard instruction highlighting settings""" return self.is_option_set(FlowGraphOption.FlowGraphUsesInstructionHighlights) @uses_instruction_highlights.setter def uses_instruction_highlights(self, value): self.set_option(FlowGraphOption.FlowGraphUsesInstructionHighlights, value) @property def includes_user_comments(self): """Set if flow graph includes comments made by the user""" return self.is_option_set(FlowGraphOption.FlowGraphIncludesUserComments) @includes_user_comments.setter def includes_user_comments(self, value): self.set_option(FlowGraphOption.FlowGraphIncludesUserComments, value) @property def allows_patching(self): """Set if flow graph should allow modification of code from within the graph view""" return self.is_option_set(FlowGraphOption.FlowGraphAllowsPatching) @allows_patching.setter def allows_patching(self, value): self.set_option(FlowGraphOption.FlowGraphAllowsPatching, value) @property def allows_inline_instruction_editing(self): """Set if flow graph should allow inline instruction editing (assembly only)""" return self.is_option_set(FlowGraphOption.FlowGraphAllowsInlineInstructionEditing) @allows_inline_instruction_editing.setter def allows_inline_instruction_editing(self, value): self.set_option(FlowGraphOption.FlowGraphAllowsInlineInstructionEditing, value) @property def shows_secondary_reg_highlighting(self): """Set if flow graph should highlight associated registers in the UI""" return self.is_option_set(FlowGraphOption.FlowGraphShowsSecondaryRegisterHighlighting) @shows_secondary_reg_highlighting.setter def shows_secondary_reg_highlighting(self, value): self.set_option(FlowGraphOption.FlowGraphShowsSecondaryRegisterHighlighting, value) @property def is_addressable(self): """Set if flow graph should make use of address information""" return self.is_option_set(FlowGraphOption.FlowGraphIsAddressable) @is_addressable.setter def is_addressable(self, value): self.set_option(FlowGraphOption.FlowGraphIsAddressable, value) @property def is_workflow_graph(self): """Set if flow graph should be treated as a workflow graph""" return self.is_option_set(FlowGraphOption.FlowGraphIsWorkflowGraph) @is_workflow_graph.setter def is_workflow_graph(self, value): self.set_option(FlowGraphOption.FlowGraphIsWorkflowGraph, value)
[docs] def layout(self, callback=None): """ ``layout`` starts rendering a graph for display. Once a layout is complete, each node will contain coordinates and extents that can be used to render a graph with minimum additional computation. This function does not wait for the graph to be ready to display, but a callback can be provided to signal when the graph is ready. :param callback callback: Function to be called when the graph is ready to display :return: Pending flow graph layout request object :rtype: FlowGraphLayoutRequest """ return FlowGraphLayoutRequest(self, callback)
def _wait_complete(self): self._wait_cond.release()
[docs] def layout_and_wait(self): """ ``layout_and_wait`` starts rendering a graph for display, and waits for the graph to be ready to display. After this function returns, each node will contain coordinates and extents that can be used to render a graph with minimum additional computation. Do not use this API on the UI thread (use :func:`layout` with a callback instead). """ self._wait_cond = threading.Lock() self._wait_cond.acquire() _ = self.layout(self._wait_complete) self._wait_cond.acquire() self._wait_cond.release()
[docs] def get_nodes_in_region(self, left, top, right, bottom): count = ctypes.c_ulonglong() nodes = core.BNGetFlowGraphNodesInRegion(self.handle, left, top, right, bottom, count) assert nodes is not None, "core.BNGetFlowGraphNodesInRegion returned None" result = [] for i in range(0, count.value): fg_node = core.BNNewFlowGraphNodeReference(nodes[i]) assert fg_node is not None, "core.BNNewFlowGraphNodeReference returned None" result.append(FlowGraphNode(self, fg_node)) core.BNFreeFlowGraphNodeList(nodes, count.value) return result
[docs] def append(self, node): """ ``append`` adds a node to a flow graph. :param FlowGraphNode node: Node to add :return: Index of node :rtype: int """ return core.BNAddFlowGraphNode(self.handle, node.handle)
[docs] def show(self, title): """ ``show`` displays the graph in a new tab in the UI. :param str title: Title to show in the new tab """ interaction.show_graph_report(title, self)
[docs] def update(self): """ ``update`` can be overridden by subclasses to allow a graph to be updated after it has been presented in the UI. This will automatically occur if the function referenced by the :py:attr:`function` property has been updated. Return a new :class:`FlowGraph` object with the new information if updates are desired. If the graph does not need updating, ``None`` can be returned to leave the graph in its current state. :return: Updated graph, or ``None`` :rtype: FlowGraph """ return NotImplemented
[docs] def set_option(self, option, value=True): core.BNSetFlowGraphOption(self.handle, option, value)
[docs] def is_option_set(self, option): return core.BNIsFlowGraphOptionSet(self.handle, option)
[docs] class CoreFlowGraph(FlowGraph): def __init__(self, handle): super(CoreFlowGraph, self).__init__(handle)
[docs] def update(self): graph = core.BNUpdateFlowGraph(self.handle) if not graph: return None return CoreFlowGraph(graph)
[docs] class FlowGraphLayout: def __init__(self, handle: Optional[core.BNCustomFlowGraphLayout] = None): if handle is not None: self.handle = core.handle_of_type(handle, core.BNFlowGraphLayout)
[docs] def register(self, name: str): """ Register a custom layout with the API """ self._cb = core.BNCustomFlowGraphLayout() self._cb.context = 0 self._cb.layout = self._cb.layout.__class__(self._layout) self.handle = core.BNRegisterFlowGraphLayout(name, self._cb)
def _layout(self, ctxt, graph_handle: core.BNFlowGraphHandle, node_handles: 'ctypes.pointer[core.BNFlowGraphNodeHandle]', node_handle_count: int) -> bool: try: graph = FlowGraph(handle=graph_handle) nodes = [] for i in range(node_handle_count): nodes.append(FlowGraphNode(graph=graph, handle=node_handles[i])) return self.layout(graph, nodes) except: log_error(traceback.format_exc()) return False
[docs] def layout(self, graph: FlowGraph, nodes: List[FlowGraphNode]) -> bool: return False