diff --git a/harness/tsffs-gcc-ppe42.h b/harness/tsffs-gcc-ppe42.h new file mode 100644 index 00000000..d394db0b --- /dev/null +++ b/harness/tsffs-gcc-ppe42.h @@ -0,0 +1,299 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +/// Definitions and macros for compiled-in harnessing of C and C++ target +/// software for the PPE42 (PowerPC Processor Embedded 42) architecture +/// using the rlwimi magic instruction format + +#ifndef TSFFS_GCC_PPE42_H +#define TSFFS_GCC_PPE42_H + +/// Define common with LibFuzzer and other fuzzers to allow code that is +/// fuzzing-specific to be left in the codebase. See +/// https://llvm.org/docs/LibFuzzer.html#id35 for more information +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +#define FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION (1) +#endif // FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + +// ============================================================================ +// Low-level magic instruction primitives using rlwimi +// ============================================================================ + +/// __ppe42_magic +/// +/// Invoke the magic instruction for PPE42 using the rlwimi instruction. +/// This is the standard magic instruction format used by SBE firmware. +/// +/// The rlwimi instruction is a no-op on hardware but triggers Core_Magic_Instruction +/// HAP in SIMICS when magic breakpoints are enabled with: +/// simics> enable-magic-breakpoint +/// +/// SBE uses magic numbers in the range 8000-8190. We use 8010-8019 for TSFFS. +/// +/// # Arguments +/// +/// * `n` - The magic number to pass (must be in range 8010-8019 for TSFFS) +#define __ppe42_magic(n) \ + __asm__ __volatile__("rlwimi %0,%0,0,%1,%2" \ + : \ + : "i" (((n) >> 8) & 0x1f), \ + "i" (((n) >> 4) & 0xf), \ + "i" ((((n) >> 0) & 0xf) | 16) \ + : ) + +/// __ppe42_magic_extended1 +/// +/// Invoke the magic instruction with one pseudo-argument in register r10. +/// +/// # Arguments +/// +/// * `n` - The magic number +/// * `arg0` - The value to place in register r10 +#define __ppe42_magic_extended1(n, arg0) \ + __asm__ __volatile__("mr 10, %0; rlwimi %1,%1,0,%2,%3" \ + : \ + : "r"(arg0), \ + "i" (((n) >> 8) & 0x1f), \ + "i" (((n) >> 4) & 0xf), \ + "i" ((((n) >> 0) & 0xf) | 16) \ + : "r10") + +/// __ppe42_magic_extended2 +/// +/// Invoke the magic instruction with two pseudo-arguments in registers r10 and r3. +/// +/// # Arguments +/// +/// * `n` - The magic number +/// * `arg0` - The value to place in register r10 +/// * `arg1` - The value to place in register r3 +#define __ppe42_magic_extended2(n, arg0, arg1) \ + __asm__ __volatile__("mr 10, %0; mr 3, %1; rlwimi %2,%2,0,%3,%4" \ + : \ + : "r"(arg0), "r"(arg1), \ + "i" (((n) >> 8) & 0x1f), \ + "i" (((n) >> 4) & 0xf), \ + "i" ((((n) >> 0) & 0xf) | 16) \ + : "r10", "r3") + +/// __ppe42_magic_extended3 +/// +/// Invoke the magic instruction with three pseudo-arguments in registers r10, r3, and r4. +/// +/// # Arguments +/// +/// * `n` - The magic number +/// * `arg0` - The value to place in register r10 +/// * `arg1` - The value to place in register r3 +/// * `arg2` - The value to place in register r4 +#define __ppe42_magic_extended3(n, arg0, arg1, arg2) \ + __asm__ __volatile__("mr 10, %0; mr 3, %1; mr 4, %2; rlwimi %3,%3,0,%4,%5" \ + : \ + : "r"(arg0), "r"(arg1), "r"(arg2), \ + "i" (((n) >> 8) & 0x1f), \ + "i" (((n) >> 4) & 0xf), \ + "i" ((((n) >> 0) & 0xf) | 16) \ + : "r10", "r3", "r4") + +/// __ppe42_magic_extended4 +/// +/// Invoke the magic instruction with four pseudo-arguments in registers r10, r3, r4, and r5. +/// +/// # Arguments +/// +/// * `n` - The magic number +/// * `arg0` - The value to place in register r10 +/// * `arg1` - The value to place in register r3 +/// * `arg2` - The value to place in register r4 +/// * `arg3` - The value to place in register r5 +#define __ppe42_magic_extended4(n, arg0, arg1, arg2, arg3) \ + __asm__ __volatile__("mr 10, %0; mr 3, %1; mr 4, %2; mr 5, %3; rlwimi %4,%4,0,%5,%6" \ + : \ + : "r"(arg0), "r"(arg1), "r"(arg2), "r"(arg3), \ + "i" (((n) >> 8) & 0x1f), \ + "i" (((n) >> 4) & 0xf), \ + "i" ((((n) >> 0) & 0xf) | 16) \ + : "r10", "r3", "r4", "r5") + +// ============================================================================ +// Magic number definitions +// ============================================================================ + +/// Magic number base for TSFFS on SBE (in SBE range 8000-8190) +#define TSFFS_MAGIC_BASE (8010) + +/// The default index number used for magic instructions +#define DEFAULT_INDEX (0x0000U) + +/// Magic numbers for TSFFS operations (offset from base) +#define N_START_BUFFER_PTR_SIZE_PTR (TSFFS_MAGIC_BASE + 1) // 8011 +#define N_START_BUFFER_PTR_SIZE_VAL (TSFFS_MAGIC_BASE + 2) // 8012 +#define N_START_BUFFER_PTR_SIZE_PTR_VAL (TSFFS_MAGIC_BASE + 3) // 8013 +#define N_STOP_NORMAL (TSFFS_MAGIC_BASE + 4) // 8014 +#define N_STOP_ASSERT (TSFFS_MAGIC_BASE + 5) // 8015 +#define N_COVERAGE (TSFFS_MAGIC_BASE + 6) // 8016 + +// ============================================================================ +// Fuzzing harness macros +// ============================================================================ + +/// HARNESS_START +/// +/// Signal the fuzzer to start the fuzzing loop at the point this macro is called. +/// +/// # Arguments +/// +/// - `buffer`: The pointer to the testcase buffer +/// - `size_ptr`: The pointer to the size of the testcase buffer +#define HARNESS_START(buffer, size_ptr) \ + do { \ + __ppe42_magic_extended3(N_START_BUFFER_PTR_SIZE_PTR, DEFAULT_INDEX, (unsigned long)(buffer), (unsigned long)(size_ptr)); \ + } while (0) + +/// HARNESS_START_INDEX +/// +/// Signal the fuzzer to start with a specific index. +/// +/// # Arguments +/// +/// - `start_index`: The index to use for this start harness +/// - `buffer`: The pointer to the testcase buffer +/// - `size_ptr`: The pointer to the size of the testcase buffer +#define HARNESS_START_INDEX(start_index, buffer, size_ptr) \ + do { \ + __ppe42_magic_extended3(N_START_BUFFER_PTR_SIZE_PTR, start_index, buffer, size_ptr); \ + } while (0) + +/// HARNESS_START_WITH_MAXIMUM_SIZE +/// +/// Signal the fuzzer to start with a maximum size value. +/// +/// # Arguments +/// +/// - `buffer`: The pointer to the testcase buffer +/// - `max_size`: The maximum size of the testcase buffer +#define HARNESS_START_WITH_MAXIMUM_SIZE(buffer, max_size) \ + do { \ + __ppe42_magic_extended3(N_START_BUFFER_PTR_SIZE_VAL, DEFAULT_INDEX, buffer, max_size); \ + } while (0) + +/// HARNESS_START_WITH_MAXIMUM_SIZE_INDEX +/// +/// Signal the fuzzer to start with a specific index and maximum size. +/// +/// # Arguments +/// +/// - `start_index`: The index to use for this start harness +/// - `buffer`: The pointer to the testcase buffer +/// - `max_size`: The maximum size of the testcase buffer +#define HARNESS_START_WITH_MAXIMUM_SIZE_INDEX(start_index, buffer, max_size) \ + do { \ + __ppe42_magic_extended3(N_START_BUFFER_PTR_SIZE_VAL, start_index, buffer, max_size); \ + } while (0) + +/// HARNESS_START_WITH_MAXIMUM_SIZE_AND_PTR +/// +/// Signal the fuzzer to start with both size pointer and maximum size. +/// +/// # Arguments +/// +/// - `buffer`: The pointer to the testcase buffer +/// - `size_ptr`: The pointer to the size of the testcase buffer +/// - `max_size`: The maximum size of the testcase buffer +#define HARNESS_START_WITH_MAXIMUM_SIZE_AND_PTR(buffer, size_ptr, max_size) \ + do { \ + __ppe42_magic_extended4(N_START_BUFFER_PTR_SIZE_PTR_VAL, DEFAULT_INDEX, buffer, size_ptr, max_size); \ + } while (0) + +/// HARNESS_START_WITH_MAXIMUM_SIZE_AND_PTR_INDEX +/// +/// Signal the fuzzer to start with index, size pointer, and maximum size. +/// +/// # Arguments +/// +/// - `start_index`: The index to use for this start harness +/// - `buffer`: The pointer to the testcase buffer +/// - `size_ptr`: The pointer to the size of the testcase buffer +/// - `max_size`: The maximum size of the testcase buffer +#define HARNESS_START_WITH_MAXIMUM_SIZE_AND_PTR_INDEX(start_index, buffer, size_ptr, max_size) \ + do { \ + __ppe42_magic_extended4(N_START_BUFFER_PTR_SIZE_PTR_VAL, start_index, buffer, size_ptr, max_size); \ + } while (0) + +/// HARNESS_STOP +/// +/// Signal the fuzzer to stop and reset to the beginning of the fuzzing loop. +#define HARNESS_STOP() \ + do { \ + __ppe42_magic_extended1(N_STOP_NORMAL, DEFAULT_INDEX); \ + } while (0) + +/// HARNESS_STOP_INDEX +/// +/// Signal the fuzzer to stop with a specific index. +/// +/// # Arguments +/// +/// - `stop_index`: The index to use for this stop harness +#define HARNESS_STOP_INDEX(stop_index) \ + do { \ + __ppe42_magic_extended1(N_STOP_NORMAL, stop_index); \ + } while (0) + +/// HARNESS_ASSERT +/// +/// Signal the fuzzer that a custom assertion has occurred. +#define HARNESS_ASSERT() \ + do { \ + __ppe42_magic_extended1(N_STOP_ASSERT, DEFAULT_INDEX); \ + } while (0) + +/// HARNESS_ASSERT_INDEX +/// +/// Signal the fuzzer that a custom assertion has occurred with a specific index. +/// +/// # Arguments +/// +/// - `assert_index`: The index to use for this assertion harness +#define HARNESS_ASSERT_INDEX(assert_index) \ + do { \ + __ppe42_magic_extended1(N_STOP_ASSERT, assert_index); \ + } while (0) + +// ============================================================================ +// Coverage tracking macros (for manual instrumentation) +// ============================================================================ + +/// HARNESS_COVERAGE +/// +/// Report a coverage point with a unique ID. This is used for manual coverage +/// instrumentation on architectures that don't support automatic instruction-level +/// coverage tracking (like PPE42). +/// +/// Each branch or basic block should have a unique coverage_id. The fuzzer will +/// track which coverage points are hit and use this information to guide mutations. +/// +/// # Arguments +/// +/// - `coverage_id`: A unique identifier for this coverage point (0-65535) +/// +/// # Example +/// +/// ```c +/// if (condition) { +/// HARNESS_COVERAGE(1); // Coverage point for true branch +/// // ... code ... +/// } else { +/// HARNESS_COVERAGE(2); // Coverage point for false branch +/// // ... code ... +/// } +/// ``` +#define HARNESS_COVERAGE(coverage_id) \ + __ppe42_magic_extended1(N_COVERAGE, coverage_id) + +/// COVERAGE_BRANCH +/// +/// Alias for HARNESS_COVERAGE for convenience. +#define COVERAGE_BRANCH(id) HARNESS_COVERAGE(id) + +#endif // TSFFS_GCC_PPE42_H diff --git a/harness/tsffs.h b/harness/tsffs.h index 9d4d88d5..9c33c8e6 100644 --- a/harness/tsffs.h +++ b/harness/tsffs.h @@ -2587,6 +2587,8 @@ } while (0); #endif // TSFFS_H +#elif defined(__PPC__) || defined(__ppc__) || defined(__powerpc__) || defined(_ARCH_PPC) +#include "tsffs-gcc-ppe42.h" #else #error "Unsupported platform!" #endif diff --git a/src/arch/mod.rs b/src/arch/mod.rs index 561568b8..c1f9b00c 100644 --- a/src/arch/mod.rs +++ b/src/arch/mod.rs @@ -5,8 +5,8 @@ use self::{ aarch64::AArch64ArchitectureOperations, arm::ARMArchitectureOperations, - risc_v::RISCVArchitectureOperations, x86::X86ArchitectureOperations, - x86_64::X86_64ArchitectureOperations, + ppe42::PPE42ArchitectureOperations, risc_v::RISCVArchitectureOperations, + x86::X86ArchitectureOperations, x86_64::X86_64ArchitectureOperations, }; use crate::{ tracer::TraceEntry, traits::TracerDisassembler, ManualStartAddress, ManualStartInfo, StartInfo, @@ -27,6 +27,7 @@ use std::{fmt::Debug, str::FromStr}; pub mod aarch64; pub mod arm; +pub mod ppe42; pub mod risc_v; pub mod x86; pub mod x86_64; @@ -44,6 +45,8 @@ pub(crate) enum ArchitectureHint { Arm, /// The architecture is aarch64 Aarch64, + /// The architecture is PPE42 + Ppe42, } impl FromStr for ArchitectureHint { @@ -56,6 +59,7 @@ impl FromStr for ArchitectureHint { "riscv" | "risc-v" | "riscv32" | "riscv64" => Self::Riscv, "armv4" | "armv5" | "armv6" | "armv7" | "arm" | "arm32" => Self::Arm, "aarch64" | "armv8" | "arm64" => Self::Aarch64, + "ppe42" | "ppc" | "powerpc" | "ppc32" => Self::Ppe42, _ => bail!("Unknown hint: {}", s), }) } @@ -69,6 +73,7 @@ impl From for AttrValueType { ArchitectureHint::Riscv => "risc-v", ArchitectureHint::Arm => "arm", ArchitectureHint::Aarch64 => "aarch64", + ArchitectureHint::Ppe42 => "ppc", } .into() } @@ -93,6 +98,9 @@ impl ArchitectureHint { ArchitectureHint::Aarch64 => { Architecture::Aarch64(AArch64ArchitectureOperations::new_unchecked(cpu)?) } + ArchitectureHint::Ppe42 => { + Architecture::Ppe42(PPE42ArchitectureOperations::new_unchecked(cpu)?) + } }) } } @@ -108,6 +116,8 @@ pub(crate) enum Architecture { Arm(ARMArchitectureOperations), /// The AARCH64 architecture (v8 and above) Aarch64(AArch64ArchitectureOperations), + /// The PPE42 architecture + Ppe42(PPE42ArchitectureOperations), } impl Debug for Architecture { @@ -121,6 +131,7 @@ impl Debug for Architecture { Architecture::Riscv(_) => "risc-v", Architecture::Arm(_) => "arm", Architecture::Aarch64(_) => "aarch64", + Architecture::Ppe42(_) => "ppe42", } ) } @@ -132,6 +143,9 @@ pub trait ArchitectureOperations { const ARGUMENT_REGISTER_1: &'static str; const ARGUMENT_REGISTER_2: &'static str; const POINTER_WIDTH_OVERRIDE: Option = None; + /// If true, addresses are treated as physical (no logical-to-physical translation) + /// This is useful for embedded processors without MMU or with identity mapping + const USE_PHYSICAL_ADDRESSES: bool = false; /// Create a new instance of the architecture operations fn new(cpu: *mut ConfObject) -> Result @@ -177,6 +191,11 @@ pub trait ArchitectureOperations { .and_then(|n| self.int_register().read(n))?) } + /// Get the current program counter value + fn get_program_counter(&mut self) -> Result { + Ok(self.processor_info_v2().get_program_counter()?) + } + /// Get the magic start information from the harness which takes the arguments: /// /// - buffer: The address of the buffer containing the testcase @@ -190,21 +209,36 @@ pub trait ArchitectureOperations { .get_number(Self::ARGUMENT_REGISTER_1.as_raw_cstr()?)?; let buffer_logical_address = self.int_register().read(buffer_register_number)?; let size_ptr_logical_address = self.int_register().read(size_ptr_register_number)?; - let buffer_physical_address_block = self - .processor_info_v2() - .logical_to_physical(buffer_logical_address, Access::Sim_Access_Read)?; - let size_ptr_physical_address_block = self - .processor_info_v2() - .logical_to_physical(size_ptr_logical_address, Access::Sim_Access_Read)?; - - ensure!( - buffer_physical_address_block.valid != 0, - "Invalid linear address found in magic start buffer register {buffer_register_number}: {buffer_logical_address:#x}" - ); - ensure!( - size_ptr_physical_address_block.valid != 0, - "Invalid linear address found in magic start size register {size_ptr_register_number}: {size_ptr_logical_address:#x}" - ); + + + // For architectures that use physical addresses directly (like embedded processors), + // skip the logical-to-physical translation + let (buffer_physical_address, buffer_is_virtual) = if Self::USE_PHYSICAL_ADDRESSES { + (buffer_logical_address, false) + } else { + let buffer_physical_address_block = self + .processor_info_v2() + .logical_to_physical(buffer_logical_address, Access::Sim_Access_Read)?; + ensure!( + buffer_physical_address_block.valid != 0, + "Invalid linear address found in magic start buffer register {buffer_register_number}: {buffer_logical_address:#x}" + ); + (buffer_physical_address_block.address, buffer_physical_address_block.address != buffer_logical_address) + }; + + let (size_ptr_physical_address, _size_ptr_is_virtual) = if Self::USE_PHYSICAL_ADDRESSES { + (size_ptr_logical_address, false) + } else { + let size_ptr_physical_address_block = self + .processor_info_v2() + .logical_to_physical(size_ptr_logical_address, Access::Sim_Access_Read)?; + ensure!( + size_ptr_physical_address_block.valid != 0, + "Invalid linear address found in magic start size register {size_ptr_register_number}: {size_ptr_logical_address:#x}" + ); + (size_ptr_physical_address_block.address, size_ptr_physical_address_block.address != size_ptr_logical_address) + }; + let size_size = if let Some(width) = Self::POINTER_WIDTH_OVERRIDE { width @@ -214,7 +248,7 @@ pub trait ArchitectureOperations { let size = read_phys_memory( self.cpu(), - size_ptr_physical_address_block.address, + size_ptr_physical_address, size_size, )?; @@ -222,12 +256,12 @@ pub trait ArchitectureOperations { .map(|i| { read_byte( self.processor_info_v2().get_physical_memory()?, - buffer_physical_address_block.address + i, + buffer_physical_address + i, ) .map_err(|e| { anyhow!( "Failed to read byte at {:#x}: {}", - buffer_physical_address_block.address + i, + buffer_physical_address + i, e ) }) @@ -236,18 +270,20 @@ pub trait ArchitectureOperations { Ok(StartInfo::builder() .address( - if buffer_physical_address_block.address != buffer_logical_address { - StartPhysicalAddress::WasVirtual(buffer_physical_address_block.address) + if buffer_is_virtual { + StartPhysicalAddress::WasVirtual(buffer_physical_address) } else { - StartPhysicalAddress::WasPhysical(buffer_physical_address_block.address) + StartPhysicalAddress::WasPhysical(buffer_physical_address) }, ) .contents(contents) .size(StartSize::SizePtr { - address: if size_ptr_physical_address_block.address != size_ptr_logical_address { - StartPhysicalAddress::WasVirtual(size_ptr_physical_address_block.address) + address: if Self::USE_PHYSICAL_ADDRESSES { + StartPhysicalAddress::WasPhysical(size_ptr_physical_address) + } else if size_ptr_physical_address != size_ptr_logical_address { + StartPhysicalAddress::WasVirtual(size_ptr_physical_address) } else { - StartPhysicalAddress::WasPhysical(size_ptr_physical_address_block.address) + StartPhysicalAddress::WasPhysical(size_ptr_physical_address) }, maximum_size: size as usize, }) @@ -381,7 +417,10 @@ pub trait ArchitectureOperations { /// Returns the address and whether the address is virtual for the testcase buffer used by /// the manual start functionality fn get_manual_start_info(&mut self, info: &ManualStartInfo) -> Result { - let buffer_physical_address = if matches!(info.address, ManualStartAddress::Virtual(_)) { + let buffer_physical_address = if Self::USE_PHYSICAL_ADDRESSES { + // For embedded processors, treat all addresses as physical + info.address.address() + } else if matches!(info.address, ManualStartAddress::Virtual(_)) { let physical_address_block = self .processor_info_v2() // NOTE: Do we need to support segmented memory via logical_to_physical? @@ -409,19 +448,26 @@ pub trait ArchitectureOperations { let size = match &info.size { crate::ManualStartSize::SizePtr { address } => { - let address = match address { - ManualStartAddress::Virtual(v) => { - let physical_address = self - .processor_info_v2() - .logical_to_physical(*v, Access::Sim_Access_Read)?; + let address = if Self::USE_PHYSICAL_ADDRESSES { + // For embedded processors, treat size pointer as physical + StartPhysicalAddress::WasPhysical(address.address()) + } else { + match address { + ManualStartAddress::Virtual(v) => { + let physical_address = self + .processor_info_v2() + .logical_to_physical(*v, Access::Sim_Access_Read)?; - if physical_address.valid == 0 { - bail!("Invalid linear address given for start buffer : {v:#x}"); - } + if physical_address.valid == 0 { + bail!("Invalid linear address given for start buffer : {v:#x}"); + } - StartPhysicalAddress::WasVirtual(physical_address.address) + StartPhysicalAddress::WasVirtual(physical_address.address) + } + ManualStartAddress::Physical(p) => { + StartPhysicalAddress::WasPhysical(*p) + } } - ManualStartAddress::Physical(p) => StartPhysicalAddress::WasPhysical(*p), }; let size_size = if let Some(width) = Self::POINTER_WIDTH_OVERRIDE { @@ -429,11 +475,12 @@ pub trait ArchitectureOperations { } else { self.processor_info_v2().get_logical_address_width()? / u8::BITS as i32 }; + let maximum_size = - read_phys_memory(self.cpu(), address.physical_address(), size_size)?; + read_phys_memory(self.cpu(), address.physical_address(), size_size)? as usize; StartSize::SizePtr { address, - maximum_size: maximum_size as usize, + maximum_size, } } crate::ManualStartSize::MaxSize(maximum_size) => StartSize::MaxSize(*maximum_size), @@ -532,18 +579,24 @@ impl ArchitectureOperations for Architecture { Self: Sized, { if let Ok(x86_64) = X86_64ArchitectureOperations::new(cpu) { - Ok(Self::X86_64(x86_64)) - } else if let Ok(x86) = X86ArchitectureOperations::new(cpu) { - Ok(Self::I386(x86)) - } else if let Ok(riscv) = RISCVArchitectureOperations::new(cpu) { - Ok(Self::Riscv(riscv)) - } else if let Ok(arm) = ARMArchitectureOperations::new(cpu) { - Ok(Self::Arm(arm)) - } else if let Ok(aarch64) = AArch64ArchitectureOperations::new(cpu) { - Ok(Self::Aarch64(aarch64)) - } else { - bail!("Unsupported architecture"); + return Ok(Self::X86_64(x86_64)); + } + if let Ok(x86) = X86ArchitectureOperations::new(cpu) { + return Ok(Self::I386(x86)); + } + if let Ok(riscv) = RISCVArchitectureOperations::new(cpu) { + return Ok(Self::Riscv(riscv)); + } + if let Ok(arm) = ARMArchitectureOperations::new(cpu) { + return Ok(Self::Arm(arm)); + } + if let Ok(aarch64) = AArch64ArchitectureOperations::new(cpu) { + return Ok(Self::Aarch64(aarch64)); + } + if let Ok(ppe42) = PPE42ArchitectureOperations::new(cpu) { + return Ok(Self::Ppe42(ppe42)); } + bail!("Unsupported architecture"); } fn cpu(&self) -> *mut ConfObject { @@ -553,6 +606,7 @@ impl ArchitectureOperations for Architecture { Architecture::Riscv(riscv) => riscv.cpu(), Architecture::Arm(arm) => arm.cpu(), Architecture::Aarch64(aarch64) => aarch64.cpu(), + Architecture::Ppe42(ppe42) => ppe42.cpu(), } } @@ -563,6 +617,7 @@ impl ArchitectureOperations for Architecture { Architecture::Riscv(riscv) => riscv.disassembler(), Architecture::Arm(arm) => arm.disassembler(), Architecture::Aarch64(aarch64) => aarch64.disassembler(), + Architecture::Ppe42(ppe42) => ppe42.disassembler(), } } @@ -573,6 +628,7 @@ impl ArchitectureOperations for Architecture { Architecture::Riscv(riscv) => riscv.int_register(), Architecture::Arm(arm) => arm.int_register(), Architecture::Aarch64(aarch64) => aarch64.int_register(), + Architecture::Ppe42(ppe42) => ppe42.int_register(), } } @@ -583,6 +639,7 @@ impl ArchitectureOperations for Architecture { Architecture::Riscv(riscv) => riscv.processor_info_v2(), Architecture::Arm(arm) => arm.processor_info_v2(), Architecture::Aarch64(aarch64) => aarch64.processor_info_v2(), + Architecture::Ppe42(ppe42) => ppe42.processor_info_v2(), } } @@ -593,6 +650,7 @@ impl ArchitectureOperations for Architecture { Architecture::Riscv(riscv) => riscv.cpu_instruction_query(), Architecture::Arm(arm) => arm.cpu_instruction_query(), Architecture::Aarch64(aarch64) => aarch64.cpu_instruction_query(), + Architecture::Ppe42(ppe42) => ppe42.cpu_instruction_query(), } } @@ -603,6 +661,7 @@ impl ArchitectureOperations for Architecture { Architecture::Riscv(riscv) => riscv.cpu_instrumentation_subscribe(), Architecture::Arm(arm) => arm.cpu_instrumentation_subscribe(), Architecture::Aarch64(aarch64) => aarch64.cpu_instrumentation_subscribe(), + Architecture::Ppe42(ppe42) => ppe42.cpu_instrumentation_subscribe(), } } @@ -613,6 +672,7 @@ impl ArchitectureOperations for Architecture { Architecture::Riscv(riscv) => riscv.cycle(), Architecture::Arm(arm) => arm.cycle(), Architecture::Aarch64(aarch64) => aarch64.cycle(), + Architecture::Ppe42(ppe42) => ppe42.cycle(), } } @@ -623,6 +683,7 @@ impl ArchitectureOperations for Architecture { Architecture::Riscv(riscv) => riscv.get_magic_index_selector(), Architecture::Arm(arm) => arm.get_magic_index_selector(), Architecture::Aarch64(aarch64) => aarch64.get_magic_index_selector(), + Architecture::Ppe42(ppe42) => ppe42.get_magic_index_selector(), } } @@ -633,6 +694,7 @@ impl ArchitectureOperations for Architecture { Architecture::Riscv(riscv) => riscv.get_magic_start_buffer_ptr_size_ptr(), Architecture::Arm(arm) => arm.get_magic_start_buffer_ptr_size_ptr(), Architecture::Aarch64(aarch64) => aarch64.get_magic_start_buffer_ptr_size_ptr(), + Architecture::Ppe42(ppe42) => ppe42.get_magic_start_buffer_ptr_size_ptr(), } } @@ -643,6 +705,7 @@ impl ArchitectureOperations for Architecture { Architecture::Riscv(riscv) => riscv.get_magic_start_buffer_ptr_size_val(), Architecture::Arm(arm) => arm.get_magic_start_buffer_ptr_size_val(), Architecture::Aarch64(aarch64) => aarch64.get_magic_start_buffer_ptr_size_val(), + Architecture::Ppe42(ppe42) => ppe42.get_magic_start_buffer_ptr_size_val(), } } @@ -653,6 +716,7 @@ impl ArchitectureOperations for Architecture { Architecture::Riscv(riscv) => riscv.get_magic_start_buffer_ptr_size_ptr(), Architecture::Arm(arm) => arm.get_magic_start_buffer_ptr_size_ptr_val(), Architecture::Aarch64(aarch64) => aarch64.get_magic_start_buffer_ptr_size_ptr_val(), + Architecture::Ppe42(ppe42) => ppe42.get_magic_start_buffer_ptr_size_ptr_val(), } } @@ -663,6 +727,7 @@ impl ArchitectureOperations for Architecture { Architecture::Riscv(riscv) => riscv.get_manual_start_info(info), Architecture::Arm(arm) => arm.get_manual_start_info(info), Architecture::Aarch64(aarch64) => aarch64.get_manual_start_info(info), + Architecture::Ppe42(ppe42) => ppe42.get_manual_start_info(info), } } @@ -673,6 +738,7 @@ impl ArchitectureOperations for Architecture { Architecture::Riscv(riscv) => riscv.write_start(testcase, info), Architecture::Arm(arm) => arm.write_start(testcase, info), Architecture::Aarch64(aarch64) => aarch64.write_start(testcase, info), + Architecture::Ppe42(ppe42) => ppe42.write_start(testcase, info), } } @@ -683,6 +749,7 @@ impl ArchitectureOperations for Architecture { Architecture::Riscv(riscv) => riscv.trace_pc(instruction_query), Architecture::Arm(arm) => arm.trace_pc(instruction_query), Architecture::Aarch64(aarch64) => aarch64.trace_pc(instruction_query), + Architecture::Ppe42(ppe42) => ppe42.trace_pc(instruction_query), } } @@ -693,6 +760,7 @@ impl ArchitectureOperations for Architecture { Architecture::Riscv(riscv) => riscv.trace_cmp(instruction_query), Architecture::Arm(arm) => arm.trace_cmp(instruction_query), Architecture::Aarch64(aarch64) => aarch64.trace_cmp(instruction_query), + Architecture::Ppe42(ppe42) => ppe42.trace_cmp(instruction_query), } } } diff --git a/src/arch/ppe42.rs b/src/arch/ppe42.rs new file mode 100644 index 00000000..f95336f7 --- /dev/null +++ b/src/arch/ppe42.rs @@ -0,0 +1,409 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +//! Architecture-specific implementation for PPE42 (PowerPC Processor Embedded 42) architecture + +use anyhow::{anyhow, bail, Result}; +use libafl::prelude::CmpValues; +use raw_cstr::AsRawCstr; +use simics::api::{ + get_interface, read_phys_memory, sys::instruction_handle_t, Access, ConfObject, + CpuInstructionQueryInterface, CpuInstrumentationSubscribeInterface, CycleInterface, + IntRegisterInterface, ProcessorInfoV2Interface, +}; +use std::{ffi::CStr, mem::size_of, slice::from_raw_parts}; + +use crate::{ + tracer::{CmpExpr, CmpType, CmpValue, TraceEntry}, + traits::TracerDisassembler, +}; + +use super::ArchitectureOperations; + +pub(crate) struct PPE42ArchitectureOperations { + cpu: *mut ConfObject, + disassembler: Disassembler, + int_register: IntRegisterInterface, + processor_info_v2: ProcessorInfoV2Interface, + cpu_instruction_query: Option, + #[allow(dead_code)] + cpu_instrumentation_subscribe: Option, + cycle: CycleInterface, +} + +impl ArchitectureOperations for PPE42ArchitectureOperations { + // PPE42 uses PowerPC register conventions + // r3-r10 are typically used for arguments + // We'll use r10 for index selector and r3-r5 for arguments + const INDEX_SELECTOR_REGISTER: &'static str = "r10"; + const ARGUMENT_REGISTER_0: &'static str = "r3"; + const ARGUMENT_REGISTER_1: &'static str = "r4"; + const ARGUMENT_REGISTER_2: &'static str = "r5"; + // PPE42 is an embedded processor - treat addresses as physical (no MMU translation) + const USE_PHYSICAL_ADDRESSES: bool = true; + // PPE42 is 32-bit, so pointers/size values are 4 bytes + const POINTER_WIDTH_OVERRIDE: Option = Some(4); + + fn new(cpu: *mut ConfObject) -> Result { + let mut processor_info_v2: ProcessorInfoV2Interface = get_interface(cpu) + .map_err(|e| anyhow!("Failed to get ProcessorInfoV2Interface for PPE42: {}", e))?; + + let arch = unsafe { CStr::from_ptr(processor_info_v2.architecture()?) } + .to_str()? + .to_string(); + + if arch == "ppc" || arch == "powerpc" || arch == "ppe42" || arch == "ppc32" { + let int_register: IntRegisterInterface = get_interface(cpu) + .map_err(|e| anyhow!("Failed to get IntRegisterInterface for PPE42: {}", e))?; + // CpuInstructionQueryInterface is optional - not all PPE42 CPUs support it + let cpu_instruction_query: Option = get_interface(cpu).ok(); + // CpuInstrumentationSubscribeInterface is optional - not all PPE42 CPUs support it + let cpu_instrumentation_subscribe: Option = get_interface(cpu).ok(); + let cycle: CycleInterface = get_interface(cpu) + .map_err(|e| anyhow!("Failed to get CycleInterface for PPE42: {}", e))?; + + Ok(Self { + cpu, + disassembler: Disassembler::new(), + int_register, + processor_info_v2, + cpu_instruction_query, + cpu_instrumentation_subscribe, + cycle, + }) + } else { + bail!("Architecture {} is not PPE42/PowerPC", arch); + } + } + + fn new_unchecked(cpu: *mut ConfObject) -> Result + where + Self: Sized, + { + Ok(Self { + cpu, + disassembler: Disassembler::new(), + int_register: get_interface(cpu)?, + processor_info_v2: get_interface(cpu)?, + cpu_instruction_query: get_interface(cpu).ok(), + cpu_instrumentation_subscribe: get_interface(cpu).ok(), + cycle: get_interface(cpu)?, + }) + } + + fn cpu(&self) -> *mut ConfObject { + self.cpu + } + + fn disassembler(&mut self) -> &mut dyn TracerDisassembler { + &mut self.disassembler + } + + fn int_register(&mut self) -> &mut IntRegisterInterface { + &mut self.int_register + } + + fn processor_info_v2(&mut self) -> &mut ProcessorInfoV2Interface { + &mut self.processor_info_v2 + } + + fn cpu_instruction_query(&mut self) -> &mut CpuInstructionQueryInterface { + self.cpu_instruction_query.as_mut().expect("CpuInstructionQueryInterface not available on this PPE42 CPU") + } + + fn cpu_instrumentation_subscribe(&mut self) -> &mut CpuInstrumentationSubscribeInterface { + self.cpu_instrumentation_subscribe.as_mut().expect("CpuInstrumentationSubscribeInterface not available on this PPE42 CPU") + } + + fn cycle(&mut self) -> &mut CycleInterface { + &mut self.cycle + } + + fn trace_pc(&mut self, instruction_query: *mut instruction_handle_t) -> Result { + // If CpuInstructionQueryInterface is not available, we can't trace PC + let cpu_instruction_query = self.cpu_instruction_query.as_mut() + .ok_or_else(|| anyhow!("CpuInstructionQueryInterface not available for trace_pc"))?; + + let instruction_bytes = cpu_instruction_query.get_instruction_bytes(instruction_query)?; + + self.disassembler.disassemble(unsafe { + from_raw_parts(instruction_bytes.data, instruction_bytes.size) + })?; + + if self.disassembler.last_was_call() + || self.disassembler.last_was_control_flow() + || self.disassembler.last_was_ret() + { + Ok(TraceEntry::builder() + .edge(self.processor_info_v2.get_program_counter()?) + .build()) + } else { + Ok(TraceEntry::default()) + } + } + + fn trace_cmp(&mut self, instruction_query: *mut instruction_handle_t) -> Result { + // If CpuInstructionQueryInterface is not available, we can't trace CMP + let cpu_instruction_query = self.cpu_instruction_query.as_mut() + .ok_or_else(|| anyhow!("CpuInstructionQueryInterface not available for trace_cmp"))?; + + let instruction_bytes = cpu_instruction_query.get_instruction_bytes(instruction_query)?; + self.disassembler.disassemble(unsafe { + from_raw_parts(instruction_bytes.data, instruction_bytes.size) + })?; + + let pc = self.processor_info_v2.get_program_counter()?; + + let mut cmp_values = Vec::new(); + + for expr in self.disassembler.cmp() { + if let Ok(value) = self.simplify(&expr) { + cmp_values.push(value); + } + } + + let cmp_value = if let (Some(l), Some(r)) = (cmp_values.first(), cmp_values.get(1)) { + match (l, r) { + (CmpValue::U8(l), CmpValue::U8(r)) => Some(CmpValues::U8((*l, *r))), + (CmpValue::I8(l), CmpValue::I8(r)) => Some(CmpValues::U8(( + u8::from_le_bytes(l.to_le_bytes()), + u8::from_le_bytes(r.to_le_bytes()), + ))), + (CmpValue::U16(l), CmpValue::U16(r)) => Some(CmpValues::U16((*l, *r))), + (CmpValue::I16(l), CmpValue::I16(r)) => Some(CmpValues::U16(( + u16::from_le_bytes(l.to_le_bytes()), + u16::from_le_bytes(r.to_le_bytes()), + ))), + (CmpValue::U32(l), CmpValue::U32(r)) => Some(CmpValues::U32((*l, *r))), + (CmpValue::I32(l), CmpValue::I32(r)) => Some(CmpValues::U32(( + u32::from_le_bytes(l.to_le_bytes()), + u32::from_le_bytes(r.to_le_bytes()), + ))), + (CmpValue::U64(l), CmpValue::U64(r)) => Some(CmpValues::U64((*l, *r))), + (CmpValue::I64(l), CmpValue::I64(r)) => Some(CmpValues::U64(( + u64::from_le_bytes(l.to_le_bytes()), + u64::from_le_bytes(r.to_le_bytes()), + ))), + (CmpValue::Expr(_), CmpValue::Expr(_)) => None, + _ => None, + } + } else { + None + }; + + Ok(TraceEntry::builder() + .cmp(( + pc, + self.disassembler.cmp_type(), + cmp_value.ok_or_else(|| anyhow!("No cmp value available"))?, + )) + .build()) + } +} + +impl PPE42ArchitectureOperations { + fn simplify(&mut self, expr: &CmpExpr) -> Result { + match expr { + CmpExpr::Deref((b, _)) => { + let v = self.simplify(b)?; + match v { + CmpValue::U64(a) => { + let address = self + .processor_info_v2 + .logical_to_physical(a, Access::Sim_Access_Read)?; + Ok(CmpValue::U64(read_phys_memory( + self.cpu, + address.address, + size_of::() as i32, + )?)) + } + CmpValue::U32(a) => { + let address = self + .processor_info_v2 + .logical_to_physical(a as u64, Access::Sim_Access_Read)?; + Ok(CmpValue::U32(read_phys_memory( + self.cpu, + address.address, + size_of::() as i32, + )? as u32)) + } + _ => bail!("Invalid dereference size {:?}", v), + } + } + CmpExpr::Reg((n, _)) => { + let regno = self.int_register.get_number(n.as_raw_cstr()?)?; + let value = self.int_register.read(regno)?; + // PPE42 is 32-bit + Ok(CmpValue::U32(value as u32)) + } + CmpExpr::Add((l, r)) => { + let lv = self.simplify(l)?; + let rv = self.simplify(r)?; + + match (lv, rv) { + (CmpValue::U32(lu), CmpValue::U32(ru)) => { + Ok(CmpValue::U32(lu.wrapping_add(ru))) + } + (CmpValue::I32(lu), CmpValue::I32(ru)) => { + Ok(CmpValue::I32(lu.wrapping_add(ru))) + } + _ => bail!("Cannot add non-matching types"), + } + } + CmpExpr::Sub((l, r)) => { + let lv = self.simplify(l)?; + let rv = self.simplify(r)?; + + match (lv, rv) { + (CmpValue::U32(lu), CmpValue::U32(ru)) => { + Ok(CmpValue::U32(lu.wrapping_sub(ru))) + } + (CmpValue::I32(lu), CmpValue::I32(ru)) => { + Ok(CmpValue::I32(lu.wrapping_sub(ru))) + } + _ => bail!("Cannot subtract non-matching types"), + } + } + CmpExpr::Mul((l, r)) => { + let lv = self.simplify(l)?; + let rv = self.simplify(r)?; + + match (lv, rv) { + (CmpValue::U32(lu), CmpValue::U32(ru)) => { + Ok(CmpValue::U32(lu.wrapping_mul(ru))) + } + (CmpValue::I32(lu), CmpValue::I32(ru)) => { + Ok(CmpValue::I32(lu.wrapping_mul(ru))) + } + _ => bail!("Cannot multiply non-matching types"), + } + } + CmpExpr::U8(u) => Ok(CmpValue::U8(*u)), + CmpExpr::I8(i) => Ok(CmpValue::I8(*i)), + CmpExpr::U16(u) => Ok(CmpValue::U16(*u)), + CmpExpr::I16(i) => Ok(CmpValue::I16(*i)), + CmpExpr::U32(u) => Ok(CmpValue::U32(*u)), + CmpExpr::I32(i) => Ok(CmpValue::I32(*i)), + CmpExpr::U64(u) => Ok(CmpValue::U64(*u)), + CmpExpr::I64(i) => Ok(CmpValue::I64(*i)), + CmpExpr::Addr(a) => Ok(CmpValue::U32(*a as u32)), + _ => bail!("Unsupported expression {:?}", expr), + } + } +} + +/// Minimal disassembler for PPE42 +/// Since there's no PowerPC disassembler crate available, we implement basic functionality +pub(crate) struct Disassembler { + last_bytes: Option>, +} + +impl Disassembler { + pub fn new() -> Self { + Self { last_bytes: None } + } + + /// Check if instruction is a branch instruction + /// PowerPC branch instructions have opcode in bits 0-5 + fn is_branch_instruction(bytes: &[u8]) -> bool { + if bytes.len() < 4 { + return false; + } + let opcode = bytes[0] >> 2; // Top 6 bits + // Opcodes 16-19: bc, sc, b, bclr/bcctr + matches!(opcode, 16..=19) + } + + /// Check if instruction is a call (branch and link) + fn is_call_instruction(bytes: &[u8]) -> bool { + if bytes.len() < 4 { + return false; + } + let opcode = bytes[0] >> 2; + let lk_bit = bytes[3] & 0x01; // Link bit is the last bit + + // Branch with link bit set + (opcode == 18 || opcode == 16) && lk_bit == 1 + } + + /// Check if instruction is a return (bclr with specific conditions) + fn is_return_instruction(bytes: &[u8]) -> bool { + if bytes.len() < 4 { + return false; + } + let opcode = bytes[0] >> 2; + let extended = ((bytes[1] as u16) << 8) | (bytes[2] as u16); + let xo = (extended >> 1) & 0x3FF; // Extended opcode + + // bclr (opcode 19, XO 16) - Branch Conditional to Link Register + opcode == 19 && xo == 16 + } +} + +impl Default for Disassembler { + fn default() -> Self { + Self::new() + } +} + +impl TracerDisassembler for Disassembler { + fn disassemble(&mut self, bytes: &[u8]) -> Result<()> { + if bytes.len() < 4 { + bail!("PowerPC instructions must be at least 4 bytes"); + } + self.last_bytes = Some(bytes.to_vec()); + Ok(()) + } + + fn disassemble_to_string(&mut self, bytes: &[u8]) -> Result { + if bytes.len() < 4 { + bail!("PowerPC instructions must be at least 4 bytes"); + } + // Return hex representation since we don't have a full disassembler + Ok(format!( + "{:02x}{:02x}{:02x}{:02x}", + bytes[0], bytes[1], bytes[2], bytes[3] + )) + } + + fn last_was_control_flow(&self) -> bool { + if let Some(ref bytes) = self.last_bytes { + Self::is_branch_instruction(bytes) + } else { + false + } + } + + fn last_was_call(&self) -> bool { + if let Some(ref bytes) = self.last_bytes { + Self::is_call_instruction(bytes) + } else { + false + } + } + + fn last_was_ret(&self) -> bool { + if let Some(ref bytes) = self.last_bytes { + Self::is_return_instruction(bytes) + } else { + false + } + } + + fn last_was_cmp(&self) -> bool { + // Check if the last instruction was a comparison + // This is a simplified implementation + false + } + + fn cmp(&self) -> Vec { + // Basic comparison detection for PowerPC + // This is a simplified implementation + Vec::new() + } + + fn cmp_type(&self) -> Vec { + // Return a vector of comparison types + vec![CmpType::Equal] + } +}