Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 177 additions & 1 deletion chb/app/BasicBlock.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# ------------------------------------------------------------------------------
# The MIT License (MIT)
#
# Copyright (c) 2021-2024 Aarno Labs LLC
# Copyright (c) 2021-2025 Aarno Labs LLC
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -52,12 +52,135 @@
import chb.app.Function


class BasicBlockFragment:
"""Represents a basic block fragment without ast control flow.

In ARM instructions may be predicated, e.g.:

MOVEQ R0, R1 : if condition then R0 := R1

In the ocaml analyzer this additional control flow can be accomodated
directly in CHIF without the need to create a separate basic block
in the CFG (i.e. lightweight control flow). In decompilation, some of
these instructions may be accomodated without explicit control flow (e.g.,
by the C ternary operation), but this is not always possible.

However, even when predicated instructions cannot be converted into
expressions, it is not necessary to create top-level basic blocks
that are subject to the CFG-to-AST conversion. A more light-weight
solution is to embed the necessary control flow (notably limited
to branches and instruction sequences) within the block created for
the original basic block.

The basic block is partitioned into a linear sequence of BasicBlock
Fragments, where each fragment can be one of the following:
- a (linear) instruction sequence statement
- a branch statement containing a condition and a single (if) branch
- a branch statement containing a condition and a then and an else
branch.
In case of the branch statement either branch can have one or more
instructions that have the same condition setter and the same
condition.
"""

def __init__(self, instr: Instruction) -> None:
self._linear: List[Instruction] = []
self._thenbranch: List[Instruction] = []
self._elsebranch: List[Instruction] = []
self._setter: Optional[str] = None
self._condition: Optional[str] = None
self.add_instr(instr)

@property
def condition(self) -> Optional[str]:
return self._condition

@property
def setter(self) -> Optional[str]:
return self._setter

@property
def is_predicated(self) -> bool:
return self.condition is not None

@property
def is_then_only(self) -> bool:
return self.is_predicated and len(self.elsebranch) == 0

@property
def linear(self) -> List[Instruction]:
return self._linear

@property
def thenbranch(self) -> List[Instruction]:
return self._thenbranch

@property
def elsebranch(self) -> List[Instruction]:
return self._elsebranch

@property
def is_empty(self) -> bool:
return (
len(self.linear) + len(self.thenbranch) + len(self.elsebranch) == 0)

def add_predicated_instr(self, instr: Instruction) -> None:
if self.is_empty:
self._condition = instr.get_instruction_cc()
self._setter = instr.get_instruction_condition_setter()
self.thenbranch.append(instr)
elif self.is_predicated:
if self.condition == instr.get_instruction_cc():
self.thenbranch.append(instr)
else:
self.elsebranch.append(instr)
else:
raise UF.CHBError("Cannot add predicated instruction to linear frag")

def add_linear_instr(self, instr: Instruction) -> None:
if self.is_empty or (not self.is_predicated):
self.linear.append(instr)
else:
raise UF.CHBError(
"Cannot add unpredicated instruction to predicate fragment")

def add_instr(self, instr: Instruction) -> None:
if instr.has_control_flow():
self.add_predicated_instr(instr)
else:
self.add_linear_instr(instr)

def __str__(self) -> str:
lines: List[str] = []
if self.condition:
setter = " (" + self.setter + ")" if self.setter else ""
lines.append("condition: " + self.condition + setter)
if self.linear:
lines.append("linear")
for i in self.linear:
lines.append(" " + str(i))
if self.thenbranch:
lines.append("then:")
for i in self.thenbranch:
lines.append(" " + str(i))
if self.elsebranch:
lines.append("else:")
for i in self.elsebranch:
lines.append(" " + str(i))
return "\n".join(lines)


class BasicBlock(ABC):

def __init__(
self,
xnode: ET.Element) -> None:
self.xnode = xnode
self._partition: Dict[str, BasicBlockFragment] = {}

@property
def partition(self) -> Dict[str, BasicBlockFragment]:
return self._partition

@property
def baddr(self) -> str:
Expand Down Expand Up @@ -95,6 +218,59 @@ def last_instruction(self) -> Instruction:
def has_return(self) -> bool:
return self.last_instruction.is_return_instruction

@property
def has_conditional_return(self) -> bool:
return self.last_instruction.is_conditional_return_instruction

def has_control_flow(self) -> bool:
"""Returns true if this block contains predicated instructions that
are not otherwise covered in aggregates or by other conditions.

The case of a block with a conditional return is already handled in
the Cfg, so it is excluded here.

The case of a block with a conditional return and other conditional
instructions is not yet handled.
"""

count = len([i for i in self.instructions.values() if i.has_control_flow()])
if count == 1 and self.has_conditional_return:
return False

return any(i.has_control_flow() for i in self.instructions.values())

def partition_control_flow(self) -> None:
curblock: Optional[BasicBlockFragment] = None
curaddr: Optional[str] = None

for (a, i) in sorted(self.instructions.items()):
if curaddr is None or curblock is None:
curaddr = a
curblock = BasicBlockFragment(i)
else:
if i.has_control_flow():
if curblock.is_predicated:
if i.get_instruction_condition_setter() == curblock.setter:
curblock.add_instr(i)
else:
self._partition[curaddr] = curblock
curblock = BasicBlockFragment(i)
curaddr = a
else:
self._partition[curaddr] = curblock
curblock = BasicBlockFragment(i)
curaddr = a
else:
if curblock.is_predicated:
self._partition[curaddr] = curblock
curblock = BasicBlockFragment(i)
curaddr = a
else:
curblock.add_instr(i)

if curaddr is not None and curblock is not None:
self._partition[curaddr] = curblock

@property
@abstractmethod
def instructions(self) -> Mapping[str, Instruction]:
Expand Down
2 changes: 1 addition & 1 deletion chb/app/CHVersion.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
chbversion: str = "0.3.0-20250608"
chbversion: str = "0.3.0-20250624"
33 changes: 32 additions & 1 deletion chb/app/Cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -682,10 +682,41 @@ def ast(self,
for n in self.rpo_sorted_nodes:
astblock = astfn.astblock(n)
blocknode = astblock.ast(astree)
if astblock.has_return:

if astblock.has_conditional_return:
succ = self.successors(n)[0]
instr = astblock.last_instruction
rv = instr.return_value()
rvcondition = instr.ast_condition(astree)
astexpr: Optional[AST.ASTExpr] = None
if rv is not None and not astree.returns_void():
astexpr = XU.xxpr_to_ast_def_expr(
rv, instr.xdata, instr.iaddr, astree)
rtnstmt = astree.mk_return_stmt(
astexpr, instr.iaddr, instr.bytestring)

if rvcondition is not None:
elsebr = astree.mk_instr_sequence([])
brstmt = cast(AST.ASTBranch, astree.mk_branch(
rvcondition, rtnstmt, elsebr, succ))
blockstmts[n] = [blocknode, brstmt]
else:
blockstmts[n] = [blocknode, rtnstmt]
else:
rtnstmt = astree.mk_return_stmt(
None, instr.iaddr, instr.bytestring)
if rvcondition is not None:
elsebr = astree.mk_instr_sequence([])
brstmt = cast(AST.ASTBranch, astree.mk_branch(
rvcondition, rtnstmt, elsebr, succ))
blockstmts[n] = [blocknode, brstmt]
else:
blockstmts[n] = [blocknode, rtnstmt]

elif astblock.has_return:
instr = astblock.last_instruction
rv = instr.return_value()
astexpr = None
if rv is not None and not astree.returns_void():
astexpr = XU.xxpr_to_ast_def_expr(
rv, instr.xdata, instr.iaddr, astree)
Expand Down
58 changes: 58 additions & 0 deletions chb/app/InstrXData.py
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,26 @@ def get_instruction_condition(self) -> XXpr:
else:
raise UF.CHBError("No instruction condition index found")

def get_instruction_c_condition(self) -> XXpr:
for t in self.tags:
if t.startswith("icc:"):
index = int(t[4:])
argval = self.args[index]
if argval == -2:
raise UF.CHBError(
"Error value in instruction c condition")
return self.xprdictionary.xpr(argval)
else:
raise UF.CHBError("No instruction c_condition index found")

def has_valid_instruction_c_condition(self) -> bool:
for t in self.tags:
if t.startswith("icc:"):
index = int(t[4:])
argval = self.args[index]
return argval >= 0
return False

def has_condition_block_condition(self) -> bool:
return "TF" in self.tags

Expand Down Expand Up @@ -576,6 +596,17 @@ def has_return_cxpr(self) -> bool:
else:
return False

@property
def is_return_xpr_ok(self) -> bool:
if not self.has_return_xpr():
return False
rvtag = next(t for t in self.tags if t.startswith("return:"))
rvix = int(rvtag[7:])
rval = self.args[rvix]
if rval == -2:
return False
return True

def get_return_xpr(self) -> XXpr:
rvtag = next(t for t in self.tags if t.startswith("return:"))
rvix = int(rvtag[7:])
Expand All @@ -584,6 +615,17 @@ def get_return_xpr(self) -> XXpr:
raise UF.CHBError("Unexpected error in return value")
return self.xprdictionary.xpr(rval)

@property
def is_return_xxpr_ok(self) -> bool:
if not self.has_return_xpr():
return False
rvtag = next(t for t in self.tags if t.startswith("return:"))
rvix = int(rvtag[7:])
rval = self.args[rvix + 1]
if rval == -2:
return False
return True

def get_return_xxpr(self) -> XXpr:
rvtag = next(t for t in self.tags if t.startswith("return:"))
rvix = int(rvtag[7:])
Expand All @@ -600,6 +642,22 @@ def get_return_cxpr(self) -> XXpr:
raise UF.CHBError("Unexpected error in C return value")
return self.xprdictionary.xpr(rval)

@property
def is_predicate_assignment(self) -> bool:
return "agg:predassign" in self.tags

@property
def is_nondet_predicate_assignment(self) -> bool:
return "agg:predassign:nd" in self.tags

@property
def is_ternary_assignment(self) -> bool:
return "agg:ternassign" in self.tags

@property
def is_nondet_ternary_assignment(self) -> bool:
return "agg:ternassign:nd" in self.tags

@property
def is_aggregate_jumptable(self) -> bool:
return "agg-jt" in self.tags
Expand Down
28 changes: 25 additions & 3 deletions chb/app/Instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# ------------------------------------------------------------------------------
# The MIT License (MIT)
#
# Copyright (c) 2021-2024 Aarno Labs LLC
# Copyright (c) 2021-2025 Aarno Labs LLC
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -145,6 +145,18 @@ def rev_bytestring(self) -> str:
revb = "".join(i+j for i, j in zip(b[:-1][::-2], b[::-2]))
return revb

def has_control_flow(self) -> bool:
"""Returns true if this instruction is predicated and not covered
by an enclosing aggregate or other condition."""

return self.xnode.get("brcc") is not None

def get_instruction_cc(self) -> Optional[str]:
return self.xnode.get("brcc")

def get_instruction_condition_setter(self) -> Optional[str]:
return self.xnode.get("brsetter")

def md5(self) -> str:
m = hashlib.md5()
m.update(self.bytestring.encode("utf-8"))
Expand Down Expand Up @@ -206,6 +218,10 @@ def is_store_instruction(self) -> bool:
def is_return_instruction(self) -> bool:
...

@property
def is_conditional_return_instruction(self) -> bool:
return False

@property
def is_nop_instruction(self) -> bool:
return False
Expand Down Expand Up @@ -288,10 +304,16 @@ def ast_prov(self, astree: ASTInterface) -> Tuple[
def is_condition_true(self) -> bool:
return False

def ast_condition_prov(self, astree: ASTInterface, reverse: bool = False) -> Tuple[
Optional[AST.ASTExpr], Optional[AST.ASTExpr]]:
def ast_condition_prov(
self, astree: ASTInterface, reverse: bool = False
) -> Tuple[Optional[AST.ASTExpr], Optional[AST.ASTExpr]]:
raise UF.CHBError("ast-condition-prov not defined")

def ast_cc_condition_prov(
self, astree: ASTInterface
) -> Tuple[Optional[AST.ASTExpr], Optional[AST.ASTExpr]]:
raise UF.CHBError("ast-cc-codntiion-prov not defined")

def assembly_ast_condition(
self,
astree: ASTInterface,
Expand Down
Loading