fuzzgen: Generate stack load/store instructions (#4438)
* fuzzgen: Generate static stack slots * fuzzgen: Generate stack manipulation instructions
This commit is contained in:
@@ -18,6 +18,12 @@ pub struct Config {
|
||||
pub block_signature_params: RangeInclusive<usize>,
|
||||
pub jump_tables_per_function: RangeInclusive<usize>,
|
||||
pub jump_table_entries: RangeInclusive<usize>,
|
||||
|
||||
/// Stack slots.
|
||||
/// The combination of these two determines stack usage per function
|
||||
pub static_stack_slots_per_function: RangeInclusive<usize>,
|
||||
/// Size in bytes
|
||||
pub static_stack_slot_size: RangeInclusive<usize>,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@@ -32,6 +38,8 @@ impl Default for Config {
|
||||
block_signature_params: 0..=16,
|
||||
jump_tables_per_function: 0..=4,
|
||||
jump_table_entries: 0..=16,
|
||||
static_stack_slots_per_function: 0..=8,
|
||||
static_stack_slot_size: 0..=128,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ use anyhow::Result;
|
||||
use arbitrary::{Arbitrary, Unstructured};
|
||||
use cranelift::codegen::ir::types::*;
|
||||
use cranelift::codegen::ir::{
|
||||
AbiParam, Block, ExternalName, Function, JumpTable, Opcode, Signature, Type, Value,
|
||||
AbiParam, Block, ExternalName, Function, JumpTable, Opcode, Signature, StackSlot, Type, Value,
|
||||
};
|
||||
use cranelift::codegen::isa::CallConv;
|
||||
use cranelift::frontend::{FunctionBuilder, FunctionBuilderContext, Variable};
|
||||
use cranelift::prelude::{EntityRef, InstBuilder, IntCC, JumpTableData};
|
||||
use cranelift::prelude::{
|
||||
EntityRef, InstBuilder, IntCC, JumpTableData, StackSlotData, StackSlotKind,
|
||||
};
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
type BlockSignature = Vec<Type>;
|
||||
@@ -47,6 +49,46 @@ fn insert_opcode_arity_2(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn insert_stack_load(
|
||||
fgen: &mut FunctionGenerator,
|
||||
builder: &mut FunctionBuilder,
|
||||
_opcode: Opcode,
|
||||
_args: &'static [Type],
|
||||
rets: &'static [Type],
|
||||
) -> Result<()> {
|
||||
let typevar = rets[0];
|
||||
let slot = fgen.stack_slot_with_size(builder, typevar.bytes())?;
|
||||
let slot_size = builder.func.sized_stack_slots[slot].size;
|
||||
let type_size = typevar.bytes();
|
||||
let offset = fgen.u.int_in_range(0..=(slot_size - type_size))? as i32;
|
||||
|
||||
let val = builder.ins().stack_load(typevar, slot, offset);
|
||||
let var = fgen.get_variable_of_type(typevar)?;
|
||||
builder.def_var(var, val);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn insert_stack_store(
|
||||
fgen: &mut FunctionGenerator,
|
||||
builder: &mut FunctionBuilder,
|
||||
_opcode: Opcode,
|
||||
args: &'static [Type],
|
||||
_rets: &'static [Type],
|
||||
) -> Result<()> {
|
||||
let typevar = args[0];
|
||||
let slot = fgen.stack_slot_with_size(builder, typevar.bytes())?;
|
||||
let slot_size = builder.func.sized_stack_slots[slot].size;
|
||||
let type_size = typevar.bytes();
|
||||
let offset = fgen.u.int_in_range(0..=(slot_size - type_size))? as i32;
|
||||
|
||||
let arg0 = fgen.get_variable_of_type(typevar)?;
|
||||
let arg0 = builder.use_var(arg0);
|
||||
|
||||
builder.ins().stack_store(arg0, slot, offset);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
type OpcodeInserter = fn(
|
||||
fgen: &mut FunctionGenerator,
|
||||
builder: &mut FunctionBuilder,
|
||||
@@ -88,6 +130,15 @@ const OPCODE_SIGNATURES: &'static [(
|
||||
(Opcode::Sdiv, &[I16, I16], &[I16], insert_opcode_arity_2),
|
||||
(Opcode::Sdiv, &[I32, I32], &[I32], insert_opcode_arity_2),
|
||||
(Opcode::Sdiv, &[I64, I64], &[I64], insert_opcode_arity_2),
|
||||
// Stack Access
|
||||
(Opcode::StackStore, &[I8], &[], insert_stack_store),
|
||||
(Opcode::StackStore, &[I16], &[], insert_stack_store),
|
||||
(Opcode::StackStore, &[I32], &[], insert_stack_store),
|
||||
(Opcode::StackStore, &[I64], &[], insert_stack_store),
|
||||
(Opcode::StackLoad, &[], &[I8], insert_stack_load),
|
||||
(Opcode::StackLoad, &[], &[I16], insert_stack_load),
|
||||
(Opcode::StackLoad, &[], &[I32], insert_stack_load),
|
||||
(Opcode::StackLoad, &[], &[I64], insert_stack_load),
|
||||
];
|
||||
|
||||
pub struct FunctionGenerator<'r, 'data>
|
||||
@@ -99,6 +150,7 @@ where
|
||||
vars: Vec<(Type, Variable)>,
|
||||
blocks: Vec<(Block, BlockSignature)>,
|
||||
jump_tables: Vec<JumpTable>,
|
||||
static_stack_slots: Vec<StackSlot>,
|
||||
}
|
||||
|
||||
impl<'r, 'data> FunctionGenerator<'r, 'data>
|
||||
@@ -112,6 +164,7 @@ where
|
||||
vars: vec![],
|
||||
blocks: vec![],
|
||||
jump_tables: vec![],
|
||||
static_stack_slots: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,6 +234,18 @@ where
|
||||
Ok(sig)
|
||||
}
|
||||
|
||||
/// Finds a stack slot with size of at least n bytes
|
||||
fn stack_slot_with_size(&mut self, builder: &mut FunctionBuilder, n: u32) -> Result<StackSlot> {
|
||||
let opts: Vec<_> = self
|
||||
.static_stack_slots
|
||||
.iter()
|
||||
.filter(|ss| builder.func.sized_stack_slots[**ss].size >= n)
|
||||
.map(|ss| *ss)
|
||||
.collect();
|
||||
|
||||
Ok(*self.u.choose(&opts[..])?)
|
||||
}
|
||||
|
||||
/// Creates a new var
|
||||
fn create_var(&mut self, builder: &mut FunctionBuilder, ty: Type) -> Result<Variable> {
|
||||
let id = self.vars.len();
|
||||
@@ -218,7 +283,7 @@ where
|
||||
};
|
||||
builder.ins().iconst(ty, imm64)
|
||||
}
|
||||
ty if ty.is_bool() => builder.ins().bconst(B1, bool::arbitrary(self.u)?),
|
||||
ty if ty.is_bool() => builder.ins().bconst(ty, bool::arbitrary(self.u)?),
|
||||
_ => unimplemented!(),
|
||||
})
|
||||
}
|
||||
@@ -381,6 +446,44 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_stack_slots(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
|
||||
for _ in 0..self.param(&self.config.static_stack_slots_per_function)? {
|
||||
let bytes = self.param(&self.config.static_stack_slot_size)? as u32;
|
||||
let ss_data = StackSlotData::new(StackSlotKind::ExplicitSlot, bytes);
|
||||
let slot = builder.create_sized_stack_slot(ss_data);
|
||||
|
||||
self.static_stack_slots.push(slot);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Zero initializes the stack slot by inserting `stack_store`'s.
|
||||
fn initialize_stack_slots(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
|
||||
let i64_zero = builder.ins().iconst(I64, 0);
|
||||
let i32_zero = builder.ins().iconst(I32, 0);
|
||||
let i16_zero = builder.ins().iconst(I16, 0);
|
||||
let i8_zero = builder.ins().iconst(I8, 0);
|
||||
|
||||
for &slot in self.static_stack_slots.iter() {
|
||||
let init_size = builder.func.sized_stack_slots[slot].size;
|
||||
let mut size = init_size;
|
||||
|
||||
// Insert the largest available store for the remaining size.
|
||||
while size != 0 {
|
||||
let offset = (init_size - size) as i32;
|
||||
let (val, filled) = match size {
|
||||
sz if sz / 8 > 0 => (i64_zero, 8),
|
||||
sz if sz / 4 > 0 => (i32_zero, 4),
|
||||
sz if sz / 2 > 0 => (i16_zero, 2),
|
||||
_ => (i8_zero, 1),
|
||||
};
|
||||
builder.ins().stack_store(val, slot, offset);
|
||||
size -= filled;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates a random amount of blocks in this function
|
||||
fn generate_blocks(
|
||||
&mut self,
|
||||
@@ -467,6 +570,7 @@ where
|
||||
|
||||
// Function preamble
|
||||
self.generate_jumptables(&mut builder)?;
|
||||
self.generate_stack_slots(&mut builder)?;
|
||||
|
||||
// Main instruction generation loop
|
||||
for (i, (block, block_sig)) in self.blocks.clone().iter().enumerate() {
|
||||
@@ -478,6 +582,10 @@ where
|
||||
// block signature and for the variable pool. Additionally, we must also define
|
||||
// initial values for all variables that are not the function signature.
|
||||
self.build_variable_pool(&mut builder)?;
|
||||
|
||||
// Stack slots have random bytes at the beginning of the function
|
||||
// initialize them to a constant value so that execution stays predictable.
|
||||
self.initialize_stack_slots(&mut builder)?;
|
||||
} else {
|
||||
// Define variables for the block params
|
||||
for (i, ty) in block_sig.iter().enumerate() {
|
||||
|
||||
Reference in New Issue
Block a user