diff --git a/.gitignore b/.gitignore index e9c7c1a..da36d9b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /target/ +/.tmp/ /fuzz/target/ /.pnpm-store/ /web/dist/ diff --git a/crates/aarch64/src/lib.rs b/crates/aarch64/src/lib.rs index 061b1ad..f71f813 100644 --- a/crates/aarch64/src/lib.rs +++ b/crates/aarch64/src/lib.rs @@ -27,6 +27,12 @@ const EXCLUSIVE_MONITORS_STATUS_USER_OP: &str = "ExclusiveMonitorsStatus"; const DATA_MEMORY_BARRIER_USER_OP: &str = "DataMemoryBarrier"; const NEON_COUNT_USER_OP: &str = "NEON_cnt"; const NEON_ADD_ACROSS_USER_OP: &str = "NEON_addv"; +const NEON_COMPARE_EQUAL_USER_OP: &str = "NEON_cmeq"; +const NEON_UNSIGNED_MAXIMUM_ACROSS_USER_OP: &str = "NEON_umaxv"; +const NEON_BITWISE_SELECT_USER_OP: &str = "NEON_bsl"; +const NEON_UNSIGNED_MINIMUM_ACROSS_USER_OP: &str = "NEON_uminv"; +const NEON_COMPARE_LESS_THAN_USER_OP: &str = "NEON_cmlt"; +const NEON_BITWISE_INSERT_USER_OP: &str = "NEON_bit"; const SLEIGH_SOURCES: [(&str, &str); 7] = [ ( @@ -397,10 +403,12 @@ impl Interpreter { #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] enum Storage { - X(u8), + X { index: u8, offset: u8 }, Vector { index: u8, offset: u8 }, Sp, TpidrEl0, + Fpcr, + Fpsr, Flag(Flag), Scratch(VarNode), } @@ -472,6 +480,27 @@ enum Operation { left: Value, right: Value, }, + IntegerToFloat { + destination: Place, + source: Value, + signed: bool, + }, + FloatToInteger { + destination: Place, + source: Value, + }, + FloatArithmetic { + destination: Place, + left: Value, + right: Value, + operation: FloatArithmetic, + }, + FloatCompare { + destination: Place, + left: Value, + right: Value, + comparison: FloatComparison, + }, ShiftLeft { destination: Place, left: Value, @@ -588,11 +617,63 @@ enum Operation { source: Value, element_size: Value, }, + NeonCompareEqual { + destination: Place, + left: Value, + right: Value, + element_size: Value, + }, + NeonUnsignedMaximumAcross { + destination: Place, + source: Value, + element_size: Value, + }, + NeonBitwiseSelect { + destination: Place, + selector: Value, + when_set: Value, + when_clear: Value, + element_size: Value, + }, + NeonUnsignedMinimumAcross { + destination: Place, + source: Value, + element_size: Value, + }, + NeonCompareLessThan { + destination: Place, + left: Value, + right: Value, + element_size: Value, + }, + NeonBitwiseInsert { + destination: Place, + original: Value, + source: Value, + mask: Value, + element_size: Value, + }, SupervisorCall { immediate: Value, }, } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum FloatArithmetic { + Add, + Subtract, + Multiply, + Divide, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum FloatComparison { + Equal, + NotEqual, + Less, + LessEqual, +} + #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum SemanticOutcome { Advanced, @@ -619,16 +700,29 @@ fn compile_language() -> Result { fn lower_block(language: &SleighData, instructions: &[PcodeInstruction]) -> Option> { let mut operations = Vec::with_capacity(instructions.len()); + let mut arguments = [None; 2]; for instruction in instructions { if instruction.op == PcodeOp::InstructionMarker { continue; } - operations.push(lower_pcode(language, *instruction)?); + if let PcodeOp::Arg(index) = instruction.op { + *arguments.get_mut(usize::from(index))? = + Some(lower_value(language, instruction.inputs.first())?); + continue; + } + operations.push(lower_pcode(language, *instruction, arguments)?); + if matches!(instruction.op, PcodeOp::PcodeOp(_)) { + arguments = [None; 2]; + } } Some(operations) } -fn lower_pcode(language: &SleighData, instruction: PcodeInstruction) -> Option { +fn lower_pcode( + language: &SleighData, + instruction: PcodeInstruction, + arguments: [Option; 2], +) -> Option { let [left, right] = instruction.inputs.get(); match instruction.op { operation @ (PcodeOp::Copy @@ -658,6 +752,47 @@ fn lower_pcode(language: &SleighData, instruction: PcodeInstruction) -> Option { lower_binary_pcode(language, operation, instruction.output, left, right) } + operation @ (PcodeOp::IntToFloat | PcodeOp::UintToFloat) => { + Some(Operation::IntegerToFloat { + destination: lower_place(language, instruction.output)?, + source: lower_value(language, left)?, + signed: operation == PcodeOp::IntToFloat, + }) + } + PcodeOp::FloatToInt => Some(Operation::FloatToInteger { + destination: lower_place(language, instruction.output)?, + source: lower_value(language, left)?, + }), + operation @ (PcodeOp::FloatAdd + | PcodeOp::FloatSub + | PcodeOp::FloatMul + | PcodeOp::FloatDiv) => Some(Operation::FloatArithmetic { + destination: lower_place(language, instruction.output)?, + left: lower_value(language, left)?, + right: lower_value(language, right)?, + operation: match operation { + PcodeOp::FloatAdd => FloatArithmetic::Add, + PcodeOp::FloatSub => FloatArithmetic::Subtract, + PcodeOp::FloatMul => FloatArithmetic::Multiply, + PcodeOp::FloatDiv => FloatArithmetic::Divide, + _ => unreachable!("caller filters floating arithmetic"), + }, + }), + operation @ (PcodeOp::FloatEqual + | PcodeOp::FloatNotEqual + | PcodeOp::FloatLess + | PcodeOp::FloatLessEqual) => Some(Operation::FloatCompare { + destination: lower_place(language, instruction.output)?, + left: lower_value(language, left)?, + right: lower_value(language, right)?, + comparison: match operation { + PcodeOp::FloatEqual => FloatComparison::Equal, + PcodeOp::FloatNotEqual => FloatComparison::NotEqual, + PcodeOp::FloatLess => FloatComparison::Less, + PcodeOp::FloatLessEqual => FloatComparison::LessEqual, + _ => unreachable!("caller filters floating comparison"), + }, + }), PcodeOp::Load(memory) if memory == pcode::RAM_SPACE => Some(Operation::Load { destination: lower_place(language, instruction.output)?, address: lower_value(language, left)?, @@ -666,7 +801,14 @@ fn lower_pcode(language: &SleighData, instruction: PcodeInstruction) -> Option lower_control_pcode(language, operation, instruction.output, left, right), + operation => lower_control_pcode( + language, + operation, + instruction.output, + left, + right, + arguments, + ), } } @@ -834,6 +976,7 @@ fn lower_control_pcode( output: VarNode, left: PcodeValue, right: PcodeValue, + arguments: [Option; 2], ) -> Option { match operation { PcodeOp::Branch(_) => Some(Operation::Branch { @@ -868,6 +1011,42 @@ fn lower_control_pcode( source: lower_value(language, left)?, element_size: lower_value(language, right)?, }), + NEON_COMPARE_EQUAL_USER_OP => Some(Operation::NeonCompareEqual { + destination: lower_place(language, output)?, + left: lower_value(language, left)?, + right: lower_value(language, right)?, + element_size: arguments[0]?, + }), + NEON_UNSIGNED_MAXIMUM_ACROSS_USER_OP => Some(Operation::NeonUnsignedMaximumAcross { + destination: lower_place(language, output)?, + source: lower_value(language, left)?, + element_size: lower_value(language, right)?, + }), + NEON_BITWISE_SELECT_USER_OP => Some(Operation::NeonBitwiseSelect { + destination: lower_place(language, output)?, + selector: lower_value(language, left)?, + when_set: lower_value(language, right)?, + when_clear: arguments[0]?, + element_size: arguments[1]?, + }), + NEON_UNSIGNED_MINIMUM_ACROSS_USER_OP => Some(Operation::NeonUnsignedMinimumAcross { + destination: lower_place(language, output)?, + source: lower_value(language, left)?, + element_size: lower_value(language, right)?, + }), + NEON_COMPARE_LESS_THAN_USER_OP => Some(Operation::NeonCompareLessThan { + destination: lower_place(language, output)?, + left: lower_value(language, left)?, + right: lower_value(language, right)?, + element_size: arguments[0]?, + }), + NEON_BITWISE_INSERT_USER_OP => Some(Operation::NeonBitwiseInsert { + destination: lower_place(language, output)?, + original: lower_value(language, left)?, + source: lower_value(language, right)?, + mask: arguments[0]?, + element_size: arguments[1]?, + }), _ => None, }, _ => None, @@ -878,12 +1057,14 @@ fn lower_place(language: &SleighData, variable: VarNode) -> Option { if variable.size == 0 || variable.size > 16 { return None; } - let storage = vector_storage(language, variable).or_else(|| { - language.name_of_varnode(variable).map_or_else( - || Some(Storage::Scratch(variable)), - |name| architectural_storage(name).or(Some(Storage::Scratch(variable))), - ) - })?; + let storage = vector_storage(language, variable) + .or_else(|| general_register_storage(language, variable)) + .or_else(|| { + language.name_of_varnode(variable).map_or_else( + || Some(Storage::Scratch(variable)), + |name| architectural_storage(name).or(Some(Storage::Scratch(variable))), + ) + })?; Some(Place { storage, size: variable.size, @@ -902,10 +1083,24 @@ fn vector_storage(language: &SleighData, variable: VarNode) -> Option { }) } +fn general_register_storage(language: &SleighData, variable: VarNode) -> Option { + (0..GENERAL_REGISTER_COUNT).find_map(|index| { + let register = language.get_varnode(&format!("x{index}"))?; + (register.id == variable.id + && usize::from(variable.offset) + usize::from(variable.size) <= 8) + .then(|| Storage::X { + index: u8::try_from(index).expect("AArch64 general register index fits u8"), + offset: variable.offset, + }) + }) +} + fn architectural_storage(name: &str) -> Option { match name { "sp" => return Some(Storage::Sp), "tpidr_el0" => return Some(Storage::TpidrEl0), + "fpcr" => return Some(Storage::Fpcr), + "fpsr" => return Some(Storage::Fpsr), "NG" => return Some(Storage::Flag(Flag::Negative)), "ZR" => return Some(Storage::Flag(Flag::Zero)), "CY" => return Some(Storage::Flag(Flag::Carry)), @@ -917,7 +1112,7 @@ fn architectural_storage(name: &str) -> Option { .or_else(|| name.strip_prefix('w'))? .parse::() .ok()?; - (index < 31).then_some(Storage::X(index)) + (index < 31).then_some(Storage::X { index, offset: 0 }) } fn lower_value(language: &SleighData, value: PcodeValue) -> Option { @@ -967,6 +1162,10 @@ fn execute_operations( | Operation::BitwiseAnd { .. } | Operation::BitwiseOr { .. } | Operation::BitwiseXor { .. } + | Operation::IntegerToFloat { .. } + | Operation::FloatToInteger { .. } + | Operation::FloatArithmetic { .. } + | Operation::FloatCompare { .. } | Operation::ShiftLeft { .. } | Operation::ShiftRight { .. } | Operation::SignedShiftRight { .. } @@ -987,7 +1186,13 @@ fn execute_operations( | Operation::ExclusiveMonitorPass { .. } | Operation::ExclusiveMonitorsStatus { .. } | Operation::NeonCount { .. } - | Operation::NeonAddAcross { .. } => { + | Operation::NeonAddAcross { .. } + | Operation::NeonCompareEqual { .. } + | Operation::NeonUnsignedMaximumAcross { .. } + | Operation::NeonBitwiseSelect { .. } + | Operation::NeonUnsignedMinimumAcross { .. } + | Operation::NeonCompareLessThan { .. } + | Operation::NeonBitwiseInsert { .. } => { execute_value_operation(state, &mut scratch, operation, pc, encoding)?; } Operation::Load { .. } | Operation::Store { .. } => { @@ -1055,44 +1260,21 @@ fn execute_value_operation( destination, source, } => (destination, !read(source)?), - Operation::Add { - destination, - left, - right, - } => (destination, read(left)?.wrapping_add(read(right)?)), - Operation::Subtract { - destination, - left, - right, - } => (destination, read(left)?.wrapping_sub(read(right)?)), - Operation::Multiply { - destination, - left, - right, - } => (destination, read(left)?.wrapping_mul(read(right)?)), - Operation::Divide { - destination, - left, - right, - } => ( - destination, - read(left)?.checked_div(read(right)?).unwrap_or(0), - ), - Operation::BitwiseAnd { - destination, - left, - right, - } => (destination, read(left)? & read(right)?), - Operation::BitwiseOr { - destination, - left, - right, - } => (destination, read(left)? | read(right)?), - Operation::BitwiseXor { - destination, - left, - right, - } => (destination, read(left)? ^ read(right)?), + operation @ (Operation::Add { .. } + | Operation::Subtract { .. } + | Operation::Multiply { .. } + | Operation::Divide { .. } + | Operation::BitwiseAnd { .. } + | Operation::BitwiseOr { .. } + | Operation::BitwiseXor { .. }) => { + execute_integer_value_operation(state, scratch, &operation, pc, encoding)? + } + operation @ (Operation::IntegerToFloat { .. } + | Operation::FloatToInteger { .. } + | Operation::FloatArithmetic { .. } + | Operation::FloatCompare { .. }) => { + execute_float_value_operation(state, scratch, &operation, pc, encoding)? + } operation @ (Operation::ShiftLeft { .. } | Operation::ShiftRight { .. } | Operation::SignedShiftRight { .. }) => { @@ -1116,7 +1298,14 @@ fn execute_value_operation( | Operation::ExclusiveMonitorsStatus { .. }) => { execute_exclusive_value_operation(state, scratch, &operation, pc, encoding)? } - operation @ (Operation::NeonCount { .. } | Operation::NeonAddAcross { .. }) => { + operation @ (Operation::NeonCount { .. } + | Operation::NeonAddAcross { .. } + | Operation::NeonCompareEqual { .. } + | Operation::NeonUnsignedMaximumAcross { .. } + | Operation::NeonBitwiseSelect { .. } + | Operation::NeonUnsignedMinimumAcross { .. } + | Operation::NeonCompareLessThan { .. } + | Operation::NeonBitwiseInsert { .. }) => { execute_neon_value_operation(state, scratch, &operation, pc, encoding)? } Operation::Load { .. } @@ -1130,6 +1319,221 @@ fn execute_value_operation( write_place(state, scratch, destination, value).ok_or_else(unsupported) } +fn execute_integer_value_operation( + state: &Aarch64State, + scratch: &ScratchValues, + operation: &Operation, + pc: GuestAddress, + encoding: u32, +) -> Result<(Place, u128), Trap> { + let unsupported = || Trap::UnsupportedInstruction { pc, encoding }; + let read = |value| read_value(state, scratch, value).ok_or_else(unsupported); + let (Operation::Add { + destination, + left, + right, + } + | Operation::Subtract { + destination, + left, + right, + } + | Operation::Multiply { + destination, + left, + right, + } + | Operation::Divide { + destination, + left, + right, + } + | Operation::BitwiseAnd { + destination, + left, + right, + } + | Operation::BitwiseOr { + destination, + left, + right, + } + | Operation::BitwiseXor { + destination, + left, + right, + }) = *operation + else { + return Err(unsupported()); + }; + let left = read(left)?; + let right = read(right)?; + let value = match operation { + Operation::Add { .. } => left.wrapping_add(right), + Operation::Subtract { .. } => left.wrapping_sub(right), + Operation::Multiply { .. } => left.wrapping_mul(right), + Operation::Divide { .. } => left.checked_div(right).unwrap_or(0), + Operation::BitwiseAnd { .. } => left & right, + Operation::BitwiseOr { .. } => left | right, + Operation::BitwiseXor { .. } => left ^ right, + _ => return Err(unsupported()), + }; + Ok((destination, value)) +} + +fn execute_float_value_operation( + state: &Aarch64State, + scratch: &ScratchValues, + operation: &Operation, + pc: GuestAddress, + encoding: u32, +) -> Result<(Place, u128), Trap> { + let unsupported = || Trap::UnsupportedInstruction { pc, encoding }; + let read = |value| read_value(state, scratch, value).ok_or_else(unsupported); + match *operation { + Operation::IntegerToFloat { + destination, + source, + signed, + } => Ok(( + destination, + integer_to_float_bits(read(source)?, source.size, destination.size, signed) + .ok_or_else(unsupported)?, + )), + Operation::FloatToInteger { + destination, + source, + } => Ok(( + destination, + float_to_unsigned_integer(read(source)?, source.size, destination.size) + .ok_or_else(unsupported)?, + )), + Operation::FloatArithmetic { + destination, + left, + right, + operation, + } => Ok(( + destination, + float_arithmetic_bits(read(left)?, read(right)?, destination.size, operation) + .ok_or_else(unsupported)?, + )), + Operation::FloatCompare { + destination, + left, + right, + comparison, + } => Ok(( + destination, + u128::from( + compare_float_bits(read(left)?, read(right)?, left.size, comparison) + .ok_or_else(unsupported)?, + ), + )), + _ => Err(unsupported()), + } +} + +#[allow(clippy::cast_precision_loss)] +fn integer_to_float_bits( + value: u128, + source_size: u8, + destination_size: u8, + signed: bool, +) -> Option { + match (destination_size, signed) { + (4, false) => Some(u128::from((u64::try_from(value).ok()? as f32).to_bits())), + (8, false) => Some(u128::from((u64::try_from(value).ok()? as f64).to_bits())), + (4, true) => Some(u128::from( + (i64::try_from(signed_value(value, source_size)?).ok()? as f32).to_bits(), + )), + (8, true) => Some(u128::from( + (i64::try_from(signed_value(value, source_size)?).ok()? as f64).to_bits(), + )), + _ => None, + } +} + +#[allow( + clippy::cast_possible_truncation, + clippy::cast_sign_loss, + reason = "Rust float-to-integer casts implement FCVTZU's truncating, saturating result" +)] +fn float_to_unsigned_integer(value: u128, source_size: u8, destination_size: u8) -> Option { + let value = match source_size { + 4 => f64::from(f32::from_bits(u32::try_from(value).ok()?)), + 8 => f64::from_bits(u64::try_from(value).ok()?), + _ => return None, + }; + match destination_size { + 4 => Some(u128::from(value as u32)), + 8 => Some(u128::from(value as u64)), + _ => None, + } +} + +fn float_arithmetic_bits( + left: u128, + right: u128, + size: u8, + operation: FloatArithmetic, +) -> Option { + match size { + 4 => { + let left = f32::from_bits(u32::try_from(left).ok()?); + let right = f32::from_bits(u32::try_from(right).ok()?); + let result = match operation { + FloatArithmetic::Add => left + right, + FloatArithmetic::Subtract => left - right, + FloatArithmetic::Multiply => left * right, + FloatArithmetic::Divide => left / right, + }; + Some(u128::from(result.to_bits())) + } + 8 => { + let left = f64::from_bits(u64::try_from(left).ok()?); + let right = f64::from_bits(u64::try_from(right).ok()?); + let result = match operation { + FloatArithmetic::Add => left + right, + FloatArithmetic::Subtract => left - right, + FloatArithmetic::Multiply => left * right, + FloatArithmetic::Divide => left / right, + }; + Some(u128::from(result.to_bits())) + } + _ => None, + } +} + +fn compare_float_bits( + left: u128, + right: u128, + size: u8, + comparison: FloatComparison, +) -> Option { + let (left, right) = match size { + 4 => ( + f64::from(f32::from_bits(u32::try_from(left).ok()?)), + f64::from(f32::from_bits(u32::try_from(right).ok()?)), + ), + 8 => ( + f64::from_bits(u64::try_from(left).ok()?), + f64::from_bits(u64::try_from(right).ok()?), + ), + _ => return None, + }; + let ordering = left.partial_cmp(&right); + Some(match comparison { + FloatComparison::Equal => ordering == Some(core::cmp::Ordering::Equal), + FloatComparison::NotEqual => ordering != Some(core::cmp::Ordering::Equal), + FloatComparison::Less => ordering == Some(core::cmp::Ordering::Less), + FloatComparison::LessEqual => matches!( + ordering, + Some(core::cmp::Ordering::Less | core::cmp::Ordering::Equal) + ), + }) +} + fn execute_exclusive_value_operation( state: &Aarch64State, scratch: &ScratchValues, @@ -1215,6 +1619,71 @@ fn execute_neon_value_operation( source, element_size, } => (destination, source, element_size, false), + Operation::NeonCompareEqual { + destination, + left, + right, + element_size, + } + | Operation::NeonCompareLessThan { + destination, + left, + right, + element_size, + } => { + let signed_less_than = matches!(operation, Operation::NeonCompareLessThan { .. }); + return execute_neon_comparison( + state, + scratch, + (destination, left, right, element_size), + signed_less_than, + (pc, encoding), + ); + } + Operation::NeonUnsignedMaximumAcross { + destination, + source, + element_size, + } + | Operation::NeonUnsignedMinimumAcross { + destination, + source, + element_size, + } => { + let element_size = u8::try_from(read(element_size)?).map_err(|_| unsupported())?; + let maximum = matches!(operation, Operation::NeonUnsignedMaximumAcross { .. }); + let result = unsigned_lane_extreme(read(source)?, source.size, element_size, maximum) + .ok_or_else(unsupported)?; + return Ok((destination, result)); + } + Operation::NeonBitwiseSelect { + destination, + selector, + when_set, + when_clear, + element_size, + } => { + return execute_neon_bitwise_insert( + state, + scratch, + (destination, when_clear, when_set, selector, element_size), + (pc, encoding), + ); + } + Operation::NeonBitwiseInsert { + destination, + original, + source, + mask, + element_size, + } => { + return execute_neon_bitwise_insert( + state, + scratch, + (destination, original, source, mask, element_size), + (pc, encoding), + ); + } _ => return Err(unsupported()), }; if read(element_size)? != 1 { @@ -1234,6 +1703,102 @@ fn execute_neon_value_operation( Ok((destination, result)) } +fn execute_neon_bitwise_insert( + state: &Aarch64State, + scratch: &ScratchValues, + operands: (Place, Value, Value, Value, Value), + location: (GuestAddress, u32), +) -> Result<(Place, u128), Trap> { + let (destination, original, source, mask, element_size) = operands; + let (pc, encoding) = location; + let unsupported = || Trap::UnsupportedInstruction { pc, encoding }; + let read = |value| read_value(state, scratch, value).ok_or_else(unsupported); + if read(element_size)? != 1 + || original.size != source.size + || original.size != mask.size + || original.size != destination.size + { + return Err(unsupported()); + } + let mask = read(mask)?; + Ok(( + destination, + (read(original)? & !mask) | (read(source)? & mask), + )) +} + +fn unsigned_lane_extreme( + source: u128, + source_size: u8, + element_size: u8, + maximum: bool, +) -> Option { + if element_size == 0 || !source_size.is_multiple_of(element_size) { + return None; + } + let lane_mask = value_mask(element_size)?; + let mut result = if maximum { 0 } else { lane_mask }; + for lane in 0..(source_size / element_size) { + let shift = u32::from(lane) * u32::from(element_size) * 8; + let value = (source >> shift) & lane_mask; + result = if maximum { + result.max(value) + } else { + result.min(value) + }; + } + Some(result) +} + +fn execute_neon_comparison( + state: &Aarch64State, + scratch: &ScratchValues, + operands: (Place, Value, Value, Value), + signed_less_than: bool, + location: (GuestAddress, u32), +) -> Result<(Place, u128), Trap> { + let (destination, left, right, element_size) = operands; + let (pc, encoding) = location; + let unsupported = || Trap::UnsupportedInstruction { pc, encoding }; + let read = |value| read_value(state, scratch, value).ok_or_else(unsupported); + let broadcast_right = right.size < left.size; + let element_size = if broadcast_right { + right.size + } else { + u8::try_from(read(element_size)?).map_err(|_| unsupported())? + }; + if element_size == 0 + || destination.size != left.size + || (!broadcast_right && left.size != right.size) + || !left.size.is_multiple_of(element_size) + { + return Err(unsupported()); + } + let lane_mask = value_mask(element_size).ok_or_else(unsupported)?; + let left = read(left)?; + let right = read(right)?; + let mut result = 0_u128; + for lane in 0..(destination.size / element_size) { + let shift = u32::from(lane) * u32::from(element_size) * 8; + let left_lane = (left >> shift) & lane_mask; + let right_lane = if broadcast_right { + right & lane_mask + } else { + (right >> shift) & lane_mask + }; + let matches = if signed_less_than { + signed_value(left_lane, element_size).ok_or_else(unsupported)? + < signed_value(right_lane, element_size).ok_or_else(unsupported)? + } else { + left_lane == right_lane + }; + if matches { + result |= lane_mask << shift; + } + } + Ok((destination, result)) +} + fn execute_boolean_value_operation( state: &Aarch64State, scratch: &ScratchValues, @@ -1384,6 +1949,10 @@ fn execute_memory_operation( | Operation::BitwiseAnd { .. } | Operation::BitwiseOr { .. } | Operation::BitwiseXor { .. } + | Operation::IntegerToFloat { .. } + | Operation::FloatToInteger { .. } + | Operation::FloatArithmetic { .. } + | Operation::FloatCompare { .. } | Operation::ShiftLeft { .. } | Operation::ShiftRight { .. } | Operation::SignedShiftRight { .. } @@ -1409,6 +1978,12 @@ fn execute_memory_operation( | Operation::MemoryBarrier | Operation::NeonCount { .. } | Operation::NeonAddAcross { .. } + | Operation::NeonCompareEqual { .. } + | Operation::NeonUnsignedMaximumAcross { .. } + | Operation::NeonBitwiseSelect { .. } + | Operation::NeonUnsignedMinimumAcross { .. } + | Operation::NeonCompareLessThan { .. } + | Operation::NeonBitwiseInsert { .. } | Operation::SupervisorCall { .. } => Err(unsupported()), } } @@ -1416,12 +1991,16 @@ fn execute_memory_operation( fn read_value(state: &Aarch64State, scratch: &ScratchValues, value: Value) -> Option { let raw = match value.source { ValueSource::Constant(value) => value, - ValueSource::Storage(Storage::X(index)) => u128::from(state.x(index)?), + ValueSource::Storage(Storage::X { index, offset }) => { + u128::from(state.x(index)?.checked_shr(u32::from(offset) * 8)?) + } ValueSource::Storage(Storage::Vector { index, offset }) => { state.vector(index)?.checked_shr(u32::from(offset) * 8)? } ValueSource::Storage(Storage::Sp) => u128::from(state.sp().get()), ValueSource::Storage(Storage::TpidrEl0) => u128::from(state.tpidr_el0()), + ValueSource::Storage(Storage::Fpcr) => u128::from(state.fpcr()), + ValueSource::Storage(Storage::Fpsr) => u128::from(state.fpsr()), ValueSource::Storage(Storage::Flag(flag)) => u128::from(read_flag(state, flag)), ValueSource::Storage(Storage::Scratch(variable)) => read_scratch(scratch, variable)?, }; @@ -1436,7 +2015,19 @@ fn write_place( ) -> Option<()> { let value = truncate(value, destination.size)?; match destination.storage { - Storage::X(index) => state.set_x(index, u64::try_from(value).ok()?).ok()?, + Storage::X { index, offset } => { + let shift = u32::from(offset) * 8; + let value = u64::try_from(value).ok()?; + if offset == 0 && destination.size == 4 { + state.set_x(index, value).ok()?; + } else { + let mask = u64::try_from(value_mask(destination.size)?).ok()? << shift; + let current = state.x(index)?; + state + .set_x(index, (current & !mask) | ((value << shift) & mask)) + .ok()?; + } + } Storage::Vector { index, offset } => { let shift = u32::from(offset) * 8; let shifted_mask = value_mask(destination.size)?.checked_shl(shift)?; @@ -1447,6 +2038,8 @@ fn write_place( } Storage::Sp => state.set_sp(GuestAddress::new(u64::try_from(value).ok()?)), Storage::TpidrEl0 => state.set_tpidr_el0(u64::try_from(value).ok()?), + Storage::Fpcr => state.set_fpcr(u32::try_from(value).ok()?), + Storage::Fpsr => state.set_fpsr(u32::try_from(value).ok()?), Storage::Flag(flag) => write_flag(state, flag, value != 0), Storage::Scratch(variable) => write_scratch(scratch, variable, value)?, } @@ -1806,6 +2399,147 @@ mod tests { assert_eq!(state.vector(22), Some(36)); } + #[test] + fn executes_cpython_vector_comparison_and_reduction_semantics() { + const CODE: &[u8] = &[ + 0x21, 0x8c, 0xa0, 0x6e, // cmeq v1.4s, v1.4s, v0.4s + 0x22, 0xa8, 0xb0, 0x6e, // umaxv s2, v1.4s + ]; + const SELECT_AND_MINIMUM: &[u8] = &[ + 0x40, 0x1c, 0x61, 0x2e, // bsl v0.8b, v2.8b, v1.8b + 0x00, 0xa8, 0x71, 0x2e, // uminv h0, v0.4h + ]; + const ZERO_COMPARE: &[u8] = &[ + 0x00, 0x98, 0xa0, 0x4e, // cmeq v0.4s, v0.4s, #0 + ]; + const SIGNED_COMPARE: &[u8] = &[ + 0x00, 0xa8, 0x60, 0x0e, // cmlt v0.4h, v0.4h, #0 + ]; + const DUPLICATE: &[u8] = &[ + 0x60, 0x0c, 0x01, 0x4e, // dup v0.16b, w3 + ]; + const BITWISE_INSERT: &[u8] = &[ + 0x40, 0x1c, 0xa1, 0x6e, // bit v0.16b, v2.16b, v1.16b + ]; + let mut memory = executable_memory(CODE); + let mut state = Aarch64State::new(CODE_ADDRESS, GuestAddress::new(0x8000)); + state + .set_vector(0, 4_u128 << 96 | 3_u128 << 64 | 2_u128 << 32 | 1) + .unwrap(); + state + .set_vector(1, 4_u128 << 96 | 5_u128 << 64 | 2_u128 << 32) + .unwrap(); + let vector_two_upper = 0x0123_4567_89ab_cdef_7654_3210_u128 << 32; + state.set_vector(2, vector_two_upper).unwrap(); + let mut interpreter = Interpreter::new().unwrap(); + + for _ in 0..2 { + interpreter.step(&mut state, &mut memory).unwrap(); + } + + assert_eq!( + state.vector(1), + Some(u128::from(u32::MAX) << 96 | u128::from(u32::MAX) << 32) + ); + assert_eq!( + state.vector(2), + Some(vector_two_upper | u128::from(u32::MAX)) + ); + + let mut memory = executable_memory(SELECT_AND_MINIMUM); + let mut state = Aarch64State::new(CODE_ADDRESS, GuestAddress::new(0x8000)); + let upper = 0xdead_beef_cafe_babe_u128 << 64; + state.set_vector(0, upper | 0xff00_ff00_ff00_ff00).unwrap(); + state.set_vector(1, 0x1111_1111_1111_1111).unwrap(); + state.set_vector(2, 0xaaaa_aaaa_aaaa_aaaa).unwrap(); + for _ in 0..2 { + interpreter.step(&mut state, &mut memory).unwrap(); + } + assert_eq!(state.vector(0), Some(upper | 0xaa11_aa11_aa11_aa11)); + + let mut memory = executable_memory(ZERO_COMPARE); + let mut state = Aarch64State::new(CODE_ADDRESS, GuestAddress::new(0x8000)); + state.set_vector(0, 2_u128 << 96 | 1_u128 << 32).unwrap(); + interpreter.step(&mut state, &mut memory).unwrap(); + assert_eq!( + state.vector(0), + Some(u128::from(u32::MAX) << 64 | u128::from(u32::MAX)) + ); + + let mut memory = executable_memory(SIGNED_COMPARE); + let mut state = Aarch64State::new(CODE_ADDRESS, GuestAddress::new(0x8000)); + state + .set_vector(0, 0xffff_u128 << 48 | 1_u128 << 32 | 0x8000) + .unwrap(); + interpreter.step(&mut state, &mut memory).unwrap(); + assert_eq!(state.vector(0), Some(0xffff_u128 << 48 | 0xffff)); + + let mut memory = executable_memory(DUPLICATE); + let mut state = Aarch64State::new(CODE_ADDRESS, GuestAddress::new(0x8000)); + state.set_x(3, 0xab).unwrap(); + interpreter.step(&mut state, &mut memory).unwrap(); + assert_eq!( + state.vector(0), + Some(0xabab_abab_abab_abab_abab_abab_abab_abab) + ); + + let mut memory = executable_memory(BITWISE_INSERT); + let mut state = Aarch64State::new(CODE_ADDRESS, GuestAddress::new(0x8000)); + state.set_vector(0, 0x0f0f_0f0f_0f0f_0f0f).unwrap(); + state.set_vector(1, 0xff00_ff00_ff00_ff00).unwrap(); + state.set_vector(2, 0xaaaa_aaaa_aaaa_aaaa).unwrap(); + interpreter.step(&mut state, &mut memory).unwrap(); + assert_eq!(state.vector(0), Some(0xaa0f_aa0f_aa0f_aa0f)); + } + + #[test] + fn executes_cpython_scalar_float_conversion_arithmetic_and_comparison() { + const CODE: &[u8] = &[ + 0x21, 0x01, 0x23, 0x9e, // ucvtf s1, x9 + 0x00, 0x01, 0x23, 0x9e, // ucvtf s0, x8 + 0x00, 0x18, 0x21, 0x1e, // fdiv s0, s0, s1 + 0x01, 0x10, 0x2c, 0x1e, // fmov s1, #0.5 + 0x00, 0x20, 0x21, 0x1e, // fcmp s0, s1 + ]; + const CONVERT_TO_INTEGER: &[u8] = &[ + 0x08, 0x00, 0x79, 0x9e, // fcvtzu x8, d0 + ]; + const READ_FPCR: &[u8] = &[ + 0x08, 0x44, 0x3b, 0xd5, // mrs x8, fpcr + ]; + let mut memory = executable_memory(CODE); + let mut state = Aarch64State::new(CODE_ADDRESS, GuestAddress::new(0x8000)); + state.set_x(8, 3).unwrap(); + state.set_x(9, 4).unwrap(); + let mut interpreter = Interpreter::new().unwrap(); + + for _ in 0..CODE.len() / 4 { + interpreter.step(&mut state, &mut memory).unwrap(); + } + + assert_eq!( + u32::try_from(state.vector(0).unwrap()).unwrap(), + 0.75_f32.to_bits() + ); + assert_eq!( + u32::try_from(state.vector(1).unwrap()).unwrap(), + 0.5_f32.to_bits() + ); + assert_eq!(state.nzcv(), 0x2000_0000); + + let mut memory = executable_memory(CONVERT_TO_INTEGER); + let mut state = Aarch64State::new(CODE_ADDRESS, GuestAddress::new(0x8000)); + state.set_vector(0, u128::from(7.75_f64.to_bits())).unwrap(); + interpreter.step(&mut state, &mut memory).unwrap(); + assert_eq!(state.x(8), Some(7)); + + let mut memory = executable_memory(READ_FPCR); + let mut state = Aarch64State::new(CODE_ADDRESS, GuestAddress::new(0x8000)); + state.set_fpcr(0x40_0000); + interpreter.step(&mut state, &mut memory).unwrap(); + assert_eq!(state.x(8), Some(0x40_0000)); + } + #[test] fn enforces_the_instruction_budget() { let mut memory = executable_memory(SYSCALL_CODE); diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 4b762f3..c373679 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -12,8 +12,7 @@ use binarrow_linux_runtime::Process; use binarrow_loader::{Credentials, ProcessConfig, ProcessParameters, load_process}; use binarrow_memory_fs::MemoryFileSystem; -const USAGE: &str = - "usage: binarrow inspect \n binarrow run [guest arguments...]"; +const USAGE: &str = "usage: binarrow inspect \n binarrow run [guest arguments...]\n binarrow trace [guest arguments...]"; fn main() -> ExitCode { match run(env::args_os().skip(1)) { @@ -36,7 +35,8 @@ fn run(mut arguments: impl Iterator) -> Result { inspect(&path)?; Ok(0) } - Some("run") => run_guest(&path, arguments), + Some("run") => run_guest(&path, arguments, false), + Some("trace") => run_guest(&path, arguments, true), _ => Err(format!( "unknown command {}; {USAGE}", command.to_string_lossy() @@ -56,7 +56,11 @@ fn inspect(path: &PathBuf) -> Result<(), String> { .map_err(|error| format!("could not write inspection: {error}")) } -fn run_guest(path: &PathBuf, arguments: impl Iterator) -> Result { +fn run_guest( + path: &PathBuf, + arguments: impl Iterator, + print_trace: bool, +) -> Result { let bytes = fs::read(path).map_err(|error| format!("could not read {}: {error}", path.display()))?; let mut random_bytes = [0; 16]; @@ -82,6 +86,13 @@ fn run_guest(path: &PathBuf, arguments: impl Iterator) -> Resul let result = process .run_with_filesystem(&mut terminal, &mut filesystem) .map_err(|error| error.to_string())?; + if print_trace { + let mut stderr = io::stderr().lock(); + for event in process.trace() { + writeln!(stderr, "{event}") + .map_err(|error| format!("could not write trace: {error}"))?; + } + } Ok(result.exit_code) } diff --git a/crates/cli/tests/run.rs b/crates/cli/tests/run.rs index 7a212a9..bad5766 100644 --- a/crates/cli/tests/run.rs +++ b/crates/cli/tests/run.rs @@ -46,6 +46,22 @@ fn run_command_supplies_the_ephemeral_filesystem() { assert!(output.stderr.is_empty()); } +#[test] +fn trace_command_prints_syscalls_after_guest_output() { + let fixture = TempFixture::new("trace", &hello_aarch64_elf(0)); + let output = Command::new(env!("CARGO_BIN_EXE_binarrow")) + .arg("trace") + .arg(&fixture.path) + .output() + .expect("binarrow should start"); + + assert_eq!(output.status.code(), Some(0)); + assert_eq!(output.stdout, MESSAGE); + let trace = String::from_utf8(output.stderr).expect("trace should be UTF-8"); + assert!(trace.contains("write(fd=1")); + assert!(trace.contains("exit(status=0)")); +} + struct TempFixture { path: std::path::PathBuf, } diff --git a/crates/linux-runtime/src/lib.rs b/crates/linux-runtime/src/lib.rs index 2f9b328..65ae844 100644 --- a/crates/linux-runtime/src/lib.rs +++ b/crates/linux-runtime/src/lib.rs @@ -1223,8 +1223,8 @@ impl Process { let offset = self.register(5); let required_flags = MAP_PRIVATE | MAP_ANONYMOUS; let allowed_flags = required_flags | MAP_STACK; - if requested_address != 0 - || length == 0 + if length == 0 + || (requested_address != 0 && !requested_address.is_multiple_of(PAGE_SIZE)) || protection & !(PROT_READ | PROT_WRITE | PROT_EXECUTE) != 0 || flags & required_flags != required_flags || flags & !allowed_flags != 0 @@ -1241,7 +1241,15 @@ impl Process { self.set_return(Errno::OutOfMemory.return_value()); return; }; - let Some(address) = self.find_mmap_address(mapped_length) else { + let address = if requested_address == 0 { + self.find_mmap_address(mapped_length) + } else { + let hint = GuestAddress::new(requested_address); + self.range_is_unmapped(hint, mapped_length) + .then_some(hint) + .or_else(|| self.find_mmap_address(mapped_length)) + }; + let Some(address) = address else { self.set_return(Errno::OutOfMemory.return_value()); return; }; @@ -1258,12 +1266,23 @@ impl Process { self.set_return(Errno::OutOfMemory.return_value()); return; } - self.next_mmap_address = address + let mapped_end = address .checked_add(mapped_length) .expect("a successfully mapped range has a valid end"); + self.next_mmap_address = self.next_mmap_address.max(mapped_end); self.set_return(address.get()); } + fn range_is_unmapped(&self, start: GuestAddress, length: u64) -> bool { + let Some(end) = start.checked_add(length) else { + return false; + }; + self.memory + .regions() + .iter() + .all(|region| region.end() <= start || region.start() >= end) + } + fn find_mmap_address(&self, length: u64) -> Option { let mut candidate = self.next_mmap_address; for region in self.memory.regions() { @@ -1832,6 +1851,26 @@ mod tests { } } + #[test] + fn anonymous_mmap_honors_an_available_address_hint() { + let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); + let mut process = Process::new(image).unwrap(); + process.state.set_x(0, 0x3ffe_1000).unwrap(); + process.state.set_x(1, 0x1f_000).unwrap(); + process.state.set_x(2, 3).unwrap(); + process.state.set_x(3, 0x22).unwrap(); + process.state.set_x(4, u64::MAX).unwrap(); + process.state.set_x(5, 0).unwrap(); + + process.dispatch_mmap(); + + assert_eq!(process.register(0), 0x3ffe_1000); + assert!(process.memory().regions().iter().any(|region| { + region.start() == GuestAddress::new(0x3ffe_1000) + && region.end() == GuestAddress::new(0x4000_0000) + })); + } + fn load_hello( config: ProcessConfig, file_descriptor: u16,