binaryninja-api icon indicating copy to clipboard operation
binaryninja-api copied to clipboard

arm64 needs to zero-extend floating point outputs

Open comex opened this issue 1 year ago • 0 comments

Version and Platform (required):

  • Binary Ninja Version: 4.1.5272-dev (3a057a87)
  • OS: macOS
  • OS Version: 14.5
  • CPU Architecture: M1

Bug Description: On arm64, most floating-point operations zero out the parts of the destination register that weren't affected.

For example, fmov s0, s1 (scalar single-precision move) copies the lower 32 bits from vector register 1 to vector register 0, and then zeroes out bits 32 to 127 (and higher, if using the SVE extension where registers can be larger than 128 bits).

However, Binary Ninja treats these operations as preserving the upper bits.

For example, in the attached object, which just consists of fmov s0, s1; ret, Binary Ninja outputs:

int128_t _main(int128_t arg1 @ v0, int32_t arg2 @ v1) __pure
    arg1.d = arg2
    return arg1

And the SSA LLIL confirms that this is intended as a partial overwrite:

>>> list(current_llil_ssa.instructions)
[<LowLevelILSetRegSsaPartial: v0#1.s0 = v1#0.s1>, <LowLevelILRet: <return> jump(x30#0)>]

I was actually able to "fix" this easily by modifying arch/arm64/il.cpp to set zeroExtend to true for these operands:

                        case REG_S29:
                        case REG_S30:
                        case REG_S31:
-                               return RegisterInfo(REG_V0+(reg-REG_S0), 0, 4);
+                               return RegisterInfo(REG_V0+(reg-REG_S0), 0, 4, true);

And ditto for the REG_D<n> versions of the registers (64-bit).

However, I'm not sure whether this change is correct for all instructions affected.

Zero extension applies to all instructions that, in the architecture reference manual, use the V[n] = value syntax. From the manual:

V[integer n] = bits(width) value
	assert n >= 0 && n <= 31;
	assert width IN {8,16,32,64,128};
	integer vlen = if IsSVEEnabled(PSTATE.EL) then VL else 128;
	if ConstrainUnpredictableBool() then
		_Z[n] = ZeroExtend(value);
	else
		_Z[n]<vlen-1:0> = ZeroExtend(value);

Note that the unpredictable part only applies to SVE with larger-than-128-bit registers, and even then, semantically speaking it's a full register zero (because you don't care about the bits above vlen).

But I'm not sure whether all instructions that Binary Ninja disassembles as having a REG_S[n] or REG_D[n] output operand are ones that actually use V[n] = value syntax with a 32- or 64-bit value.

comex avatar May 14 '24 20:05 comex