Skip to content
Open
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
106 changes: 105 additions & 1 deletion arch/armv7/arch_armv7.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3016,6 +3016,110 @@ string ArmCommonArchitecture::GetFlagWriteTypeName(uint32_t flags)
}
}

vector<uint32_t> ArmCommonArchitecture::GetAllSemanticFlagClasses()
{
return {IL_FLAG_CLASS_INT, IL_FLAG_CLASS_FLOAT};
}


string ArmCommonArchitecture::GetSemanticFlagClassName(uint32_t semClass)
{
switch (semClass)
{
case IL_FLAG_CLASS_INT: return "int";
case IL_FLAG_CLASS_FLOAT: return "float";
default: return "";
}
}


uint32_t ArmCommonArchitecture::GetSemanticClassForFlagWriteType(uint32_t writeType)
{
return writeType == IL_FLAGWRITE_FLOAT_COMPARE ? IL_FLAG_CLASS_FLOAT : IL_FLAG_CLASS_INT;
}


vector<uint32_t> ArmCommonArchitecture::GetAllSemanticFlagGroups()
{
return {IL_FLAG_GROUP_EQ, IL_FLAG_GROUP_NE, IL_FLAG_GROUP_CS, IL_FLAG_GROUP_CC,
IL_FLAG_GROUP_MI, IL_FLAG_GROUP_PL, IL_FLAG_GROUP_VS, IL_FLAG_GROUP_VC,
IL_FLAG_GROUP_HI, IL_FLAG_GROUP_LS, IL_FLAG_GROUP_GE, IL_FLAG_GROUP_LT,
IL_FLAG_GROUP_GT, IL_FLAG_GROUP_LE};
}


string ArmCommonArchitecture::GetSemanticFlagGroupName(uint32_t semGroup)
{
switch (semGroup)
{
case IL_FLAG_GROUP_EQ: return "eq";
case IL_FLAG_GROUP_NE: return "ne";
case IL_FLAG_GROUP_CS: return "cs";
case IL_FLAG_GROUP_CC: return "cc";
case IL_FLAG_GROUP_MI: return "mi";
case IL_FLAG_GROUP_PL: return "pl";
case IL_FLAG_GROUP_VS: return "vs";
case IL_FLAG_GROUP_VC: return "vc";
case IL_FLAG_GROUP_HI: return "hi";
case IL_FLAG_GROUP_LS: return "ls";
case IL_FLAG_GROUP_GE: return "ge";
case IL_FLAG_GROUP_LT: return "lt";
case IL_FLAG_GROUP_GT: return "gt";
case IL_FLAG_GROUP_LE: return "le";
default: return "";
}
}


map<uint32_t, BNLowLevelILFlagCondition> ArmCommonArchitecture::GetFlagConditionsForSemanticFlagGroup(uint32_t semGroup)
{
// VFP sets NZCV to 1000, 0110, 0010, or 0011 for less, equal,
// greater, or unordered. Only map conditions exactly represented by
// an IL floating comparison; conditions including unordered outcomes
// such as LT (N != V) must keep their architectural flag expression.
switch (semGroup)
{
case IL_FLAG_GROUP_EQ: return {{IL_FLAG_CLASS_INT, LLFC_E}, {IL_FLAG_CLASS_FLOAT, LLFC_FE}};
case IL_FLAG_GROUP_NE: return {{IL_FLAG_CLASS_INT, LLFC_NE}, {IL_FLAG_CLASS_FLOAT, LLFC_FNE}};
case IL_FLAG_GROUP_CS: return {{IL_FLAG_CLASS_INT, LLFC_UGE}};
case IL_FLAG_GROUP_CC: return {{IL_FLAG_CLASS_INT, LLFC_ULT}, {IL_FLAG_CLASS_FLOAT, LLFC_FLT}};
case IL_FLAG_GROUP_MI: return {{IL_FLAG_CLASS_INT, LLFC_NEG}, {IL_FLAG_CLASS_FLOAT, LLFC_FLT}};
case IL_FLAG_GROUP_PL: return {{IL_FLAG_CLASS_INT, LLFC_POS}};
case IL_FLAG_GROUP_VS: return {{IL_FLAG_CLASS_INT, LLFC_O}, {IL_FLAG_CLASS_FLOAT, LLFC_FUO}};
case IL_FLAG_GROUP_VC: return {{IL_FLAG_CLASS_INT, LLFC_NO}, {IL_FLAG_CLASS_FLOAT, LLFC_FO}};
case IL_FLAG_GROUP_HI: return {{IL_FLAG_CLASS_INT, LLFC_UGT}};
case IL_FLAG_GROUP_LS: return {{IL_FLAG_CLASS_INT, LLFC_ULE}, {IL_FLAG_CLASS_FLOAT, LLFC_FLE}};
case IL_FLAG_GROUP_GE: return {{IL_FLAG_CLASS_INT, LLFC_SGE}, {IL_FLAG_CLASS_FLOAT, LLFC_FGE}};
case IL_FLAG_GROUP_LT: return {{IL_FLAG_CLASS_INT, LLFC_SLT}};
case IL_FLAG_GROUP_GT: return {{IL_FLAG_CLASS_INT, LLFC_SGT}, {IL_FLAG_CLASS_FLOAT, LLFC_FGT}};
case IL_FLAG_GROUP_LE: return {{IL_FLAG_CLASS_INT, LLFC_SLE}};
default: return {};
}
}


vector<uint32_t> ArmCommonArchitecture::GetFlagsRequiredForSemanticFlagGroup(uint32_t semGroup)
{
auto conditions = GetFlagConditionsForSemanticFlagGroup(semGroup);
auto condition = conditions.find(IL_FLAG_CLASS_INT);
if (condition == conditions.end())
return {};
return GetFlagsRequiredForFlagCondition(condition->second, IL_FLAG_CLASS_INT);
}


size_t ArmCommonArchitecture::GetSemanticFlagGroupLowLevelIL(uint32_t semGroup, LowLevelILFunction& il)
{
// When definitions are mixed or have no exact semantic mapping, expand
// the original hardware condition using its integer NZCV flag roles.
auto conditions = GetFlagConditionsForSemanticFlagGroup(semGroup);
auto condition = conditions.find(IL_FLAG_CLASS_INT);
if (condition == conditions.end())
return il.Unimplemented();
return GetFlagConditionLowLevelIL(condition->second, IL_FLAG_CLASS_INT, il);
}


BNFlagRole ArmCommonArchitecture::GetFlagRole(uint32_t flag, uint32_t)
{
switch (flag)
Expand Down Expand Up @@ -3094,7 +3198,7 @@ size_t ArmCommonArchitecture::GetFlagWriteLowLevelIL(BNLowLevelILOperation op, s
case IL_FLAG_Z:
return il.FloatCompareEqual(size, lhs, rhs);
case IL_FLAG_C:
return il.Not(1, il.FloatCompareLessThan(size, lhs, rhs));
return il.Not(0, il.FloatCompareLessThan(size, lhs, rhs));
case IL_FLAG_V:
return il.FloatCompareUnordered(size, lhs, rhs);
default:
Expand Down
8 changes: 8 additions & 0 deletions arch/armv7/arch_armv7.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ class ArmCommonArchitecture: public BinaryNinja::Architecture
virtual BinaryNinja::Ref<BinaryNinja::Architecture> GetAssociatedArchitectureByAddress(uint64_t& addr) override;
virtual std::string GetFlagName(uint32_t flag) override;
virtual std::string GetFlagWriteTypeName(uint32_t flags) override;
virtual std::vector<uint32_t> GetAllSemanticFlagClasses() override;
virtual std::string GetSemanticFlagClassName(uint32_t semClass) override;
virtual uint32_t GetSemanticClassForFlagWriteType(uint32_t writeType) override;
virtual std::vector<uint32_t> GetAllSemanticFlagGroups() override;
virtual std::string GetSemanticFlagGroupName(uint32_t semGroup) override;
virtual std::vector<uint32_t> GetFlagsRequiredForSemanticFlagGroup(uint32_t semGroup) override;
virtual std::map<uint32_t, BNLowLevelILFlagCondition> GetFlagConditionsForSemanticFlagGroup(uint32_t semGroup) override;
virtual size_t GetSemanticFlagGroupLowLevelIL(uint32_t semGroup, BinaryNinja::LowLevelILFunction& il) override;
virtual BNFlagRole GetFlagRole(uint32_t flag, uint32_t semClass = 0) override;
virtual std::vector<uint32_t> GetFlagsWrittenByFlagWriteType(uint32_t flags) override;
virtual std::vector<uint32_t> GetFlagsRequiredForFlagCondition(BNLowLevelILFlagCondition cond, uint32_t semClass) override;
Expand Down
38 changes: 19 additions & 19 deletions arch/armv7/il.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,25 +44,25 @@ ExprId GetCondition(LowLevelILFunction& il, Condition cond)
{
switch(cond)
{
case COND_EQ: return il.FlagCondition(LLFC_E);
case COND_NE: return il.FlagCondition(LLFC_NE);
case COND_CS: return il.FlagCondition(LLFC_UGE);
case COND_CC: return il.FlagCondition(LLFC_ULT);
case COND_MI: return il.FlagCondition(LLFC_NEG);
case COND_PL: return il.FlagCondition(LLFC_POS);
case COND_VS: return il.FlagCondition(LLFC_O);
case COND_VC: return il.FlagCondition(LLFC_NO);
case COND_HI: return il.FlagCondition(LLFC_UGT);
case COND_LS: return il.FlagCondition(LLFC_ULE);
case COND_GE: return il.FlagCondition(LLFC_SGE);
case COND_LT: return il.FlagCondition(LLFC_SLT);
case COND_GT: return il.FlagCondition(LLFC_SGT);
case COND_LE: return il.FlagCondition(LLFC_SLE);
case COND_NONE:
case COND_NONE2:
return il.Const(0, 1); //Always branch
default:
return il.Const(0, 0); //Never branch
case COND_EQ: return il.FlagGroup(IL_FLAG_GROUP_EQ);
case COND_NE: return il.FlagGroup(IL_FLAG_GROUP_NE);
case COND_CS: return il.FlagGroup(IL_FLAG_GROUP_CS);
case COND_CC: return il.FlagGroup(IL_FLAG_GROUP_CC);
case COND_MI: return il.FlagGroup(IL_FLAG_GROUP_MI);
case COND_PL: return il.FlagGroup(IL_FLAG_GROUP_PL);
case COND_VS: return il.FlagGroup(IL_FLAG_GROUP_VS);
case COND_VC: return il.FlagGroup(IL_FLAG_GROUP_VC);
case COND_HI: return il.FlagGroup(IL_FLAG_GROUP_HI);
case COND_LS: return il.FlagGroup(IL_FLAG_GROUP_LS);
case COND_GE: return il.FlagGroup(IL_FLAG_GROUP_GE);
case COND_LT: return il.FlagGroup(IL_FLAG_GROUP_LT);
case COND_GT: return il.FlagGroup(IL_FLAG_GROUP_GT);
case COND_LE: return il.FlagGroup(IL_FLAG_GROUP_LE);
case COND_NONE:
case COND_NONE2:
return il.Const(0, 1); //Always branch
default:
return il.Const(0, 0); //Never branch
}
}

Expand Down
18 changes: 18 additions & 0 deletions arch/armv7/il.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,24 @@
#define IL_FLAGWRITE_CNZ 3
#define IL_FLAGWRITE_FLOAT_COMPARE 4

#define IL_FLAG_CLASS_INT 1
#define IL_FLAG_CLASS_FLOAT 2

#define IL_FLAG_GROUP_EQ 1
#define IL_FLAG_GROUP_NE 2
#define IL_FLAG_GROUP_CS 3
#define IL_FLAG_GROUP_CC 4
#define IL_FLAG_GROUP_MI 5
#define IL_FLAG_GROUP_PL 6
#define IL_FLAG_GROUP_VS 7
#define IL_FLAG_GROUP_VC 8
#define IL_FLAG_GROUP_HI 9
#define IL_FLAG_GROUP_LS 10
#define IL_FLAG_GROUP_GE 11
#define IL_FLAG_GROUP_LT 12
#define IL_FLAG_GROUP_GT 13
#define IL_FLAG_GROUP_LE 14

struct decomp_result;

enum Armv7Intrinsic : uint32_t
Expand Down
161 changes: 161 additions & 0 deletions arch/armv7/test_float_flags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
"""ARM conditions following VFP compares must preserve unordered outcomes."""

@zznop zznop Oct 8, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommend moving this test to the root tests directory (maybe under special cases)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah good catch will do


import math

import pytest

import binaryninja as bn
from binaryninja import LowLevelILOperation as Op


# Bit positions represent the VFP outcomes less, equal, greater, unordered.
# These are the hardware NZCV conditions, independently of the IL mappings.
CONDITIONS = [
("eq", 0b0010), ("ne", 0b1101), ("cs", 0b1110), ("cc", 0b0001),
("mi", 0b0001), ("pl", 0b1110), ("vs", 0b1000), ("vc", 0b0111),
("hi", 0b1100), ("ls", 0b0011), ("ge", 0b0110), ("lt", 0b1001),
("gt", 0b0100), ("le", 0b1011),
]
FLOAT_COMPARISONS = {
"eq": Op.LLIL_FCMP_E, "ne": Op.LLIL_FCMP_NE, "cc": Op.LLIL_FCMP_LT,
"mi": Op.LLIL_FCMP_LT, "vs": Op.LLIL_FCMP_UO, "vc": Op.LLIL_FCMP_O,
"ls": Op.LLIL_FCMP_LE, "ge": Op.LLIL_FCMP_GE, "gt": Op.LLIL_FCMP_GT,
}


def _conditional_return(arch, condition):
# mov r0, #0; mov<condition> r0, #1; bx lr. Thumb uses an IT block
# and MOV.W so initializing the result does not overwrite the flags.
if arch == "armv7":
move = (0x03a00001 | condition << 28).to_bytes(4, "little")
return bytes.fromhex("0000a0e3") + move + bytes.fromhex("1eff2fe1")
it = (0xbf08 | condition << 4).to_bytes(2, "little")
return bytes.fromhex("4ff00000") + it + bytes.fromhex("4ff001007047")


def _vfp_compare(arch, size):
# vcmpe.f32 s0, s1 / vcmpe.f64 d0, d1; vmrs apsr_nzcv, fpscr
if arch == "armv7":
return bytes.fromhex("e00ab4ee10faf1ee" if size == 4 else "c10bb4ee10faf1ee")
return bytes.fromhex("b4eee00af1ee10fa" if size == 4 else "b4eec10bf1ee10fa")


def _analyze(arch, code):
bv = bn.BinaryView.new(code)
bv.create_user_function(0, bn.Platform["linux-" + arch])
bv.update_analysis_and_wait()
return bv, bv.get_function_at(0)


def _expressions(il):
return [expr for block in il for instr in block for expr in instr.traverse(lambda expr: expr)]


def _evaluate(expr, registers, flags):
op = expr.operation
if op == Op.LLIL_REG:
return registers[expr.src.name]
if op == Op.LLIL_FLAG:
return flags[expr.src.name]
if op in (Op.LLIL_CONST, Op.LLIL_FLOAT_CONST):
return expr.constant
if op == Op.LLIL_NOT:
assert expr.size == 0, "Flag inversions must be boolean, not bytewise complements"
return not _evaluate(expr.src, registers, flags)
left = _evaluate(expr.left, registers, flags)
right = _evaluate(expr.right, registers, flags)
if op in (Op.LLIL_CMP_E, Op.LLIL_FCMP_E):
return left == right
if op in (Op.LLIL_CMP_NE, Op.LLIL_FCMP_NE):
return left != right
if op in (Op.LLIL_CMP_SLT, Op.LLIL_FCMP_LT):
return left < right
if op in (Op.LLIL_CMP_SLE, Op.LLIL_FCMP_LE):
return left <= right
if op in (Op.LLIL_CMP_SGE, Op.LLIL_FCMP_GE):
return left >= right
if op in (Op.LLIL_CMP_SGT, Op.LLIL_FCMP_GT):
return left > right
if op == Op.LLIL_FCMP_UO:
return math.isnan(left) or math.isnan(right)
if op == Op.LLIL_FCMP_O:
return not (math.isnan(left) or math.isnan(right))
if op == Op.LLIL_AND:
return bool(left) and bool(right)
if op == Op.LLIL_OR:
return bool(left) or bool(right)
pytest.fail(f"Unexpected condition expression: {expr}")


def _branch_value(il, left, right):
registers = {"s0": left, "s1": right, "d0": left, "d1": right, "r0": left, "r1": right}
flags = {}
for block in il:
for instr in block:
if instr.operation == Op.LLIL_SET_FLAG:
flags[instr.dest.name] = _evaluate(instr.src, registers, flags)
elif instr.operation == Op.LLIL_SET_REG:
registers[instr.dest.name] = _evaluate(instr.src, registers, flags)
elif instr.operation == Op.LLIL_IF:
return bool(_evaluate(instr.condition, registers, flags))
pytest.fail("Expected a conditional instruction")


@pytest.mark.parametrize("arch", ["armv7", "thumb2"])
@pytest.mark.parametrize("size", [4, 8])
@pytest.mark.parametrize("condition", range(14), ids=[name for name, _ in CONDITIONS])
def test_vfp_condition_semantics(arch, size, condition):
name, mask = CONDITIONS[condition]
bv, function = _analyze(arch, _vfp_compare(arch, size) + _conditional_return(arch, condition))
with bv:
lifted_conditions = [expr.condition for block in function.lifted_il for expr in block
if expr.operation == Op.LLIL_IF]
assert len(lifted_conditions) == 1
assert lifted_conditions[0].operation == Op.LLIL_FLAG_GROUP
assert lifted_conditions[0].semantic_group.name == name
assert function.arch.semantic_class_for_flag_write_type["fcmp"] == "float"
expressions = _expressions(function.llil)
if name in FLOAT_COMPARISONS:
comparisons = [expr for expr in expressions if expr.operation == FLOAT_COMPARISONS[name]]
assert len(comparisons) == 1
assert comparisons[0].size == size
for left, right in (
(-1.0, 1.0), (1.0, 1.0), (1.0, -1.0),
(float("nan"), 1.0), (1.0, float("nan")), (float("nan"), float("nan")),
(-0.0, 0.0), (float("-inf"), float("inf")), (float("inf"), float("inf")),
):
if math.isnan(left) or math.isnan(right):
outcome = 8
elif left < right:
outcome = 1
elif left == right:
outcome = 2
else:
outcome = 4
assert _branch_value(function.llil, left, right) == bool(mask & outcome), (name, left, right)


@pytest.mark.parametrize("arch", ["armv7", "thumb2"])
@pytest.mark.parametrize("size", [4, 8])
def test_vfp_condition_with_overwritten_sign_flag(arch, size):
# MOVS overwrites N/Z while leaving the VFP unordered flag V intact.
# GE must use those mixed definitions, not the original float >= predicate.
movs = bytes.fromhex("0020b0e3" if arch == "armv7" else "0022")
bv, function = _analyze(arch, _vfp_compare(arch, size) + movs + _conditional_return(arch, 10))
with bv:
assert not any(expr.operation == Op.LLIL_FCMP_GE for expr in _expressions(function.llil))
assert _branch_value(function.llil, -1.0, 1.0)
assert not _branch_value(function.llil, float("nan"), 1.0)


@pytest.mark.parametrize("arch", ["armv7", "thumb2"])
def test_integer_ge_condition_keeps_signed_comparison(arch):
# cmp r0, r1; conditional return. Semantic groups must retain integer semantics.
cmp = bytes.fromhex("010050e1" if arch == "armv7" else "8842")
bv, function = _analyze(arch, cmp + _conditional_return(arch, 10))
with bv:
expressions = _expressions(function.llil)
assert any(expr.operation == Op.LLIL_CMP_SGE for expr in expressions)
assert not any(expr.operation == Op.LLIL_FCMP_GE for expr in expressions)
for left, right in ((-1, 1), (0, 0), (1, -1)):
assert _branch_value(function.llil, left, right) == (left >= right)
Loading
Loading