Skip to content

Commit

Permalink
more updates go rust
Browse files Browse the repository at this point in the history
  • Loading branch information
ebehner committed Nov 8, 2024
1 parent 97e79af commit b272693
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 139 deletions.
5 changes: 2 additions & 3 deletions decompiler/frontend/binaryninja/handlers/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@
from decompiler.frontend.lifter import Handler
from decompiler.structures.pseudo import (
Constant,
CustomType,
FunctionSymbol,
GlobalVariable,
Integer,
NotUseableConstant,
OperationType,
Pointer,
Symbol,
UnaryOperation, FunctionSymbol,
UnaryOperation,
)

BYTE_SIZE = 8
Expand Down
2 changes: 1 addition & 1 deletion decompiler/frontend/binaryninja/rust_string_detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def __init__(self, binary_view: BinaryView, options: Options):
self._string_slicer_path = options.getstring("rust-string-detection.string_slicer_path", fallback="")
self._debug_submodules = options.getboolean("logging.debug-submodules", fallback=False)

def is_rust_binary(self):
def is_rust_binary(self) -> bool:
"""
Simple heurstic to determine, whether the binary is a Rust binary.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,13 @@ def _get_folded_case(self, block: BasicBlock) -> Optional[FoldedCase]:
if not isinstance(branch_instruction := block[-1], Branch):
return None
match branch_instruction.condition:
case Condition(OperationType.equal, subexpr, Constant(value=0x0)):
case Condition(operation=OperationType.equal, left=subexpr, right=Constant(value=0x0)):
edge_type_to_case_node = FalseCase
case Condition(OperationType.not_equal, subexpr, Constant(value=0x0)):
case Condition(operation=OperationType.not_equal, left=subexpr, right=Constant(value=0x0)):
edge_type_to_case_node = TrueCase
case Condition(OperationType.equal, Constant(value=0x0), subexpr):
case Condition(operation=OperationType.equal, left=Constant(value=0x0), right=subexpr):
edge_type_to_case_node = FalseCase
case Condition(OperationType.not_equal, Constant(value=0x0), subexpr):
case Condition(operation=OperationType.not_equal, left=Constant(value=0x0), right=subexpr):
edge_type_to_case_node = TrueCase
case _:
return None
Expand Down
66 changes: 16 additions & 50 deletions decompiler/pipeline/preprocessing/remove_go_prologue.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
"""Module for removing go idioms"""

import logging
from typing import Optional, Tuple
from typing import Optional, Set, Tuple

from decompiler.pipeline.preprocessing.util import _unused_addresses, match_expression
from decompiler.pipeline.preprocessing.util import get_constant_condition, is_noreturn_node, match_expression
from decompiler.pipeline.stage import PipelineStage
from decompiler.structures.graphs.basicblock import BasicBlock
from decompiler.structures.graphs.branches import ConditionalEdge, FalseCase, TrueCase, UnconditionalEdge
from decompiler.structures.pseudo.expressions import Constant, Variable
from decompiler.structures.pseudo.expressions import Variable
from decompiler.structures.pseudo.instructions import Assignment, Branch, Comment, Phi
from decompiler.structures.pseudo.operations import Call, Condition, OperationType, UnaryOperation
from decompiler.structures.pseudo.typing import Integer
from decompiler.structures.pseudo.operations import Call, OperationType, UnaryOperation
from decompiler.task import DecompilerTask


Expand All @@ -32,7 +31,7 @@ def run(self, task: DecompilerTask):
else:
logging.info("No Go function prologue found")

def _get_r14_name(self, task: DecompilerTask):
def _get_r14_name(self, task: DecompilerTask) -> str | None:
"""
Returns the variable name of the parameter stored in r14, e.g. 'arg1'.
If no such parameter exists, None is returned.
Expand All @@ -46,7 +45,7 @@ def _get_r14_name(self, task: DecompilerTask):
return None
return task.function_parameters[r14_parameter_index].name

def _is_root_single_indirect_successor(self, node: BasicBlock):
def _is_root_single_indirect_successor(self, node: BasicBlock) -> bool:
"""
Helper function used to verify the graph structure.
Expand All @@ -67,7 +66,7 @@ def _is_root_single_indirect_successor(self, node: BasicBlock):

return False

def _find_morestack_node_in_loop(self, node: BasicBlock):
def _find_morestack_node_in_loop(self, node: BasicBlock) -> BasicBlock:
"""
Helper function used to verify the graph structure.
Expand Down Expand Up @@ -139,7 +138,7 @@ def _verify_graph_structure_loopless(self) -> Optional[Tuple[BasicBlock, BasicBl

return start_node, morestack_node

def _find_morestack_node_loopless(self, node, visited):
def _find_morestack_node_loopless(self, node: BasicBlock, visited: Set[BasicBlock]) -> BasicBlock | None:
"""
Helper function used to verify the graph structure.
Expand All @@ -162,28 +161,11 @@ def _find_morestack_node_loopless(self, node, visited):
return None

# zero successors, check for no return
if self._is_noreturn_node(node):
if is_noreturn_node(node):
return node

return None

def _get_called_functions(self, instructions):
"""
Helper method to iterate over all called functions in a list of instructions.
"""
for instruction in instructions:
if isinstance(instruction, Assignment) and isinstance(instruction.value, Call):
yield instruction.value.function

def _is_noreturn_node(self, node: BasicBlock) -> bool:
"""
Helper method to check if `node` contains just one call to a non-returning function.
"""
called_functions = list(self._get_called_functions(node.instructions))
if len(called_functions) != 1:
return False
return called_functions[0].can_return == False

def _verify_graph_structure_loop(self) -> Optional[Tuple[BasicBlock, BasicBlock]]:
"""
Verify the graph structure. This method returns morestack_node and start_node if graph structure matches go prologue, otherwise None.
Expand Down Expand Up @@ -214,7 +196,6 @@ def _verify_graph_structure_loop(self) -> Optional[Tuple[BasicBlock, BasicBlock]
morestack_node = None
start_node = None
for successor in successors:
# if root in self._cfg.get_successors(successor):
if self._is_root_single_indirect_successor(successor):
morestack_node = self._find_morestack_node_in_loop(successor)
else:
Expand All @@ -231,7 +212,7 @@ def _verify_graph_structure_loop(self) -> Optional[Tuple[BasicBlock, BasicBlock]

return start_node, morestack_node

def _match_r14(self, variable: Variable):
def _match_r14(self, variable: Variable) -> bool:
"""
This method is used to check if `variable` corresponds to r14 which has a special meaning in Go prologues.
Expand Down Expand Up @@ -266,7 +247,7 @@ def _check_root_node(self) -> bool:
# check if rhs of condition compares an address (e.g. of __return_addr)
left_expression = root_node_if.condition.left
match left_expression:
case UnaryOperation(OperationType.address):
case UnaryOperation(operation=OperationType.address):
pass
case _:
return False
Expand Down Expand Up @@ -352,24 +333,22 @@ def _remove_go_prologue(self, start_node: BasicBlock, morestack_node: BasicBlock
# This should never happen
raise ValueError("If condition with broken out edges")

root_node_if.substitute(root_node_if.condition, self._get_constant_condition(new_condition))
root_node_if.substitute(root_node_if.condition, get_constant_condition(new_condition))

# Handle incoming edges to morestack_node from non-returning functions
# We can't simply delete edges without causing problems to Phi functions.
# Therefore we replace the unconditional edge with a conditional one.
# Therefore, we replace the unconditional edge with a conditional one.
# The added condition at the end of the block makes sure the edge is never taken.
# A conditional edge to a newly created "return_node" is added as well.
# The return_node does nothing.
# After dead code elmination, this will just have the effect of deleting the edge.
return_node = BasicBlock(_unused_addresses(cfg=self._cfg, amount=1)[0], [], self._cfg)
self._cfg.add_node(return_node)
return_node = self._cfg.create_block()
unconditional_in_edges = [edge for edge in self._cfg.get_in_edges(morestack_node) if isinstance(edge, UnconditionalEdge)]
for edge in unconditional_in_edges:
# edge.source.add_instruction(Return([]))
self._cfg.remove_edge(edge)
self._cfg.add_edge(FalseCase(edge.source, edge.sink))
self._cfg.add_edge(TrueCase(edge.source, return_node))
condition = self._get_constant_condition(True)
condition = get_constant_condition(True)
edge.source.add_instruction(Branch(condition))

if unconditional_in_edges:
Expand All @@ -383,20 +362,7 @@ def _remove_go_prologue(self, start_node: BasicBlock, morestack_node: BasicBlock

logging.info(comment_string)

def _get_constant_condition(self, value: bool):
"""
Helper method creating a Pseudo condition that always evaluates to `True` or `False`, depending on `value`.
"""
int_value = 1 if value else 0
return Condition(
OperationType.equal,
[
Constant(1, Integer.int32_t()),
Constant(int_value, Integer.int32_t()),
],
)

def _check_and_remove_go_prologue(self):
def _check_and_remove_go_prologue(self) -> bool:
"""
Detect and remove the typical go function prologue
Expand Down
49 changes: 8 additions & 41 deletions decompiler/pipeline/preprocessing/remove_noreturn_boilerplate.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@

from typing import Iterator, List

from decompiler.pipeline.preprocessing.util import _unused_addresses
from decompiler.pipeline.preprocessing.util import get_constant_condition, is_noreturn_node
from decompiler.pipeline.stage import PipelineStage
from decompiler.structures.graphs.basicblock import BasicBlock
from decompiler.structures.graphs.branches import ConditionalEdge, FalseCase, TrueCase
from decompiler.structures.graphs.rootedgraph import RootedGraph
from decompiler.structures.pseudo.expressions import Constant
from decompiler.structures.pseudo.instructions import Assignment, Branch, Comment
from decompiler.structures.pseudo.operations import Call, Condition, OperationType
from decompiler.structures.pseudo.typing import Integer
from decompiler.structures.pseudo.instructions import Branch, Comment
from decompiler.task import DecompilerTask
from networkx import MultiDiGraph, dominance_frontiers

Expand All @@ -28,32 +25,15 @@ def run(self, task: DecompilerTask):
self._cfg = task.graph
self._aggressive_removal_postdominators_merged_sinks()

def _get_called_functions(self, instructions):
"""
Helper method to iterate over all called functions in a list of instructions.
"""
for instruction in instructions:
if isinstance(instruction, Assignment) and isinstance(instruction.value, Call):
yield instruction.value.function

def _get_noreturn_nodes(self) -> Iterator[BasicBlock]:
"""
Iterate leaf nodes of cfg, yield nodes containing a call to a non-returning funtion.
"""
leaf_nodes = [x for x in self._cfg.nodes if self._cfg.out_degree(x) == 0]
for node in leaf_nodes:
if self._is_noreturn_node(node):
if is_noreturn_node(node):
yield node

def _is_noreturn_node(self, node: BasicBlock) -> bool:
"""
Check if node contains call to a non-returning function.
"""
called_functions = list(self._get_called_functions(node.instructions))
if len(called_functions) != 1:
return False
return called_functions[0].can_return == False

def _patch_condition_edges(self, edges: List[ConditionalEdge]) -> None:
"""
This method removes whatever was detected to be boilerplate.
Expand All @@ -63,9 +43,9 @@ def _patch_condition_edges(self, edges: List[ConditionalEdge]) -> None:
for edge in edges:
match edge:
case TrueCase():
condition = self._get_constant_condition(False)
condition = get_constant_condition(False)
case FalseCase():
condition = self._get_constant_condition(True)
condition = get_constant_condition(True)
case _:
continue
instructions = edge.source.instructions
Expand All @@ -74,19 +54,6 @@ def _patch_condition_edges(self, edges: List[ConditionalEdge]) -> None:
instructions.append(Comment("Removed potential boilerplate code"))
instructions.append(Branch(condition))

def _get_constant_condition(self, value: bool):
"""
Helper method creating a Pseudo condition that always evaluates to `True` or `False`, depending on `value`.
"""
int_value = 1 if value else 0
return Condition(
OperationType.equal,
[
Constant(1, Integer.int32_t()),
Constant(int_value, Integer.int32_t()),
],
)

def _aggressive_removal_postdominators_merged_sinks(self):
"""
Finds and removes boilerplate code, using the heuristic described below
Expand All @@ -107,9 +74,9 @@ def _aggressive_removal_postdominators_merged_sinks(self):
returning_leaf_nodes = [node for node in leaf_nodes if node not in noreturn_nodes]
# create a virtual merged end node (for post dominance calculation)
# additionally we create another virtual node so that different non-returning nodes are 'merged'
unused_addresses = _unused_addresses(cfg=self._cfg, amount=2)
virtual_end_node = BasicBlock(address=unused_addresses[0])
virtual_merged_noreturn_node = BasicBlock(address=unused_addresses[1])
min_used_address = min(block.address for block in self._cfg)
virtual_end_node = BasicBlock(min_used_address - 1)
virtual_merged_noreturn_node = BasicBlock(min_used_address - 2)
# reverse CFG and add virtual nodes to it
reversed_cfg_view: MultiDiGraph = self._cfg._graph.reverse(copy=False)
reversed_cfg_shallow_copy = MultiDiGraph(reversed_cfg_view)
Expand Down
6 changes: 3 additions & 3 deletions decompiler/pipeline/preprocessing/remove_stack_canary.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
from decompiler.pipeline.preprocessing.util import match_expression
from decompiler.pipeline.stage import PipelineStage
from decompiler.structures.graphs.branches import BasicBlockEdgeCondition
from decompiler.structures.graphs.cfg import BasicBlock, ControlFlowGraph, UnconditionalEdge
from decompiler.structures.pseudo.instructions import Assignment, Branch
from decompiler.structures.pseudo.operations import Call, OperationType
from decompiler.structures.graphs.cfg import BasicBlock, UnconditionalEdge
from decompiler.structures.pseudo.instructions import Branch
from decompiler.structures.pseudo.operations import OperationType
from decompiler.task import DecompilerTask


Expand Down
Loading

0 comments on commit b272693

Please sign in to comment.