1 //! A fuzz testing oracle for roundtrip assembly-disassembly. 2 //! 3 //! This contains manual implementations of the `Arbitrary` trait for types 4 //! throughout this crate to avoid depending on the `arbitrary` crate 5 //! unconditionally (use the `fuzz` feature instead). 6 7 use crate::{ 8 AmodeOffset, AmodeOffsetPlusKnownOffset, AsReg, CodeSink, Constant, Fixed, Gpr, Inst, Label, 9 NonRspGpr, Registers, TrapCode, Xmm, 10 }; 11 use arbitrary::{Arbitrary, Result, Unstructured}; 12 use capstone::{Capstone, arch::BuildsCapstone, arch::BuildsCapstoneSyntax, arch::x86}; 13 14 /// Take a random assembly instruction and check its encoding and 15 /// pretty-printing against a known-good disassembler. 16 /// 17 /// # Panics 18 /// 19 /// This function panics to express failure as expected by the `arbitrary` 20 /// fuzzer infrastructure. It may fail during assembly, disassembly, or when 21 /// comparing the disassembled strings. 22 pub fn roundtrip(inst: &Inst<FuzzRegs>) { 23 // Check that we can actually assemble this instruction. 24 let assembled = assemble(inst); 25 let expected = disassemble(&assembled, inst); 26 27 // Check that our pretty-printed output matches the known-good output. Trim 28 // off the instruction offset first. 29 let expected = expected.split_once(' ').unwrap().1; 30 let actual = inst.to_string(); 31 if expected != actual && expected.trim() != fix_up(&actual) { 32 println!("> {inst}"); 33 println!(" debug: {inst:x?}"); 34 println!(" assembled: {}", pretty_print_hexadecimal(&assembled)); 35 println!(" expected (capstone): {expected}"); 36 println!(" actual (to_string): {actual}"); 37 assert_eq!(expected, &actual); 38 } 39 } 40 41 /// Use this assembler to emit machine code into a byte buffer. 42 /// 43 /// This will skip any traps or label registrations, but this is fine for the 44 /// single-instruction disassembly we're doing here. 45 fn assemble(inst: &Inst<FuzzRegs>) -> Vec<u8> { 46 let mut sink = TestCodeSink::default(); 47 let offsets: Vec<i32> = Vec::new(); 48 inst.encode(&mut sink, &offsets); 49 sink.patch_labels_as_if_they_referred_to_end(); 50 sink.buf 51 } 52 53 #[derive(Default)] 54 struct TestCodeSink { 55 buf: Vec<u8>, 56 offsets_using_label: Vec<u32>, 57 } 58 59 impl TestCodeSink { 60 /// References to labels, e.g. RIP-relative addressing, is stored with an 61 /// adjustment that takes into account the distance from the relative offset 62 /// to the end of the instruction, where the offset is relative to. That 63 /// means that to indeed make the offset relative to the end of the 64 /// instruction, which is what we pretend all labels are bound to, it's 65 /// required that this adjustment is taken into account. 66 /// 67 /// This function will iterate over all labels bound to this code sink and 68 /// pretend the label is found at the end of the `buf`. That means that the 69 /// distance from the label to the end of `buf` minus 4, which is the width 70 /// of the offset, is added to what's already present in the encoding buffer. 71 /// 72 /// This is effectively undoing the `bytes_at_end` adjustment that's part of 73 /// `Amode::RipRelative` addressing. 74 fn patch_labels_as_if_they_referred_to_end(&mut self) { 75 let len = i32::try_from(self.buf.len()).unwrap(); 76 for offset in self.offsets_using_label.iter() { 77 let range = self.buf[*offset as usize..].first_chunk_mut::<4>().unwrap(); 78 let offset = i32::try_from(*offset).unwrap() + 4; 79 let rel_distance = len - offset; 80 *range = (i32::from_le_bytes(*range) + rel_distance).to_le_bytes(); 81 } 82 } 83 } 84 85 impl CodeSink for TestCodeSink { 86 fn put1(&mut self, v: u8) { 87 self.buf.extend_from_slice(&[v]); 88 } 89 90 fn put2(&mut self, v: u16) { 91 self.buf.extend_from_slice(&v.to_le_bytes()); 92 } 93 94 fn put4(&mut self, v: u32) { 95 self.buf.extend_from_slice(&v.to_le_bytes()); 96 } 97 98 fn put8(&mut self, v: u64) { 99 self.buf.extend_from_slice(&v.to_le_bytes()); 100 } 101 102 fn add_trap(&mut self, _: TrapCode) {} 103 104 fn current_offset(&self) -> u32 { 105 self.buf.len().try_into().unwrap() 106 } 107 108 fn use_label_at_offset(&mut self, offset: u32, _: Label) { 109 self.offsets_using_label.push(offset); 110 } 111 112 fn get_label_for_constant(&mut self, c: Constant) -> Label { 113 Label(c.0) 114 } 115 } 116 117 /// Building a new `Capstone` each time is suboptimal (TODO). 118 fn disassemble(assembled: &[u8], original: &Inst<FuzzRegs>) -> String { 119 let cs = Capstone::new() 120 .x86() 121 .mode(x86::ArchMode::Mode64) 122 .syntax(x86::ArchSyntax::Att) 123 .detail(true) 124 .build() 125 .expect("failed to create Capstone object"); 126 let insts = cs 127 .disasm_all(assembled, 0x0) 128 .expect("failed to disassemble"); 129 130 if insts.len() != 1 { 131 println!("> {original}"); 132 println!(" debug: {original:x?}"); 133 println!(" assembled: {}", pretty_print_hexadecimal(&assembled)); 134 assert_eq!(insts.len(), 1, "not a single instruction"); 135 } 136 137 let inst = insts.first().expect("at least one instruction"); 138 if assembled.len() != inst.len() { 139 println!("> {original}"); 140 println!(" debug: {original:x?}"); 141 println!(" assembled: {}", pretty_print_hexadecimal(&assembled)); 142 println!( 143 " capstone-assembled: {}", 144 pretty_print_hexadecimal(inst.bytes()) 145 ); 146 assert_eq!(assembled.len(), inst.len(), "extra bytes not disassembled"); 147 } 148 149 inst.to_string() 150 } 151 152 fn pretty_print_hexadecimal(hex: &[u8]) -> String { 153 use std::fmt::Write; 154 let mut s = String::with_capacity(hex.len() * 2); 155 for b in hex { 156 write!(&mut s, "{b:02X}").unwrap(); 157 } 158 s 159 } 160 161 /// See `replace_signed_immediates`. 162 macro_rules! hex_print_signed_imm { 163 ($hex:expr, $from:ty => $to:ty) => {{ 164 let imm = <$from>::from_str_radix($hex, 16).unwrap() as $to; 165 let mut simm = String::new(); 166 if imm < 0 { 167 simm.push_str("-"); 168 } 169 let abs = match imm.checked_abs() { 170 Some(i) => i, 171 None => <$to>::MIN, 172 }; 173 if imm > -10 && imm < 10 { 174 simm.push_str(&format!("{:x}", abs)); 175 } else { 176 simm.push_str(&format!("0x{:x}", abs)); 177 } 178 simm 179 }}; 180 } 181 182 /// Replace signed immediates in the disassembly with their unsigned hexadecimal 183 /// equivalent. This is only necessary to match `capstone`'s complex 184 /// pretty-printing rules; e.g. `capstone` will: 185 /// - omit the `0x` prefix when printing `0x0` as `0`. 186 /// - omit the `0x` prefix when print small values (less than 10) 187 /// - print negative values as `-0x...` (signed hex) instead of `0xff...` 188 /// (normal hex) 189 /// - print `mov` immediates as base-10 instead of base-16 (?!). 190 fn replace_signed_immediates(dis: &str) -> std::borrow::Cow<'_, str> { 191 match dis.find('$') { 192 None => dis.into(), 193 Some(idx) => { 194 let (prefix, rest) = dis.split_at(idx + 1); // Skip the '$'. 195 let (_, rest) = chomp("-", rest); // Skip the '-' if it's there. 196 let (_, rest) = chomp("0x", rest); // Skip the '0x' if it's there. 197 let n = rest.chars().take_while(char::is_ascii_hexdigit).count(); 198 let (hex, rest) = rest.split_at(n); // Split at next non-hex character. 199 let simm = if dis.starts_with("mov") { 200 u64::from_str_radix(hex, 16).unwrap().to_string() 201 } else { 202 match hex.len() { 203 1 | 2 => hex_print_signed_imm!(hex, u8 => i8), 204 4 => hex_print_signed_imm!(hex, u16 => i16), 205 8 => hex_print_signed_imm!(hex, u32 => i32), 206 16 => hex_print_signed_imm!(hex, u64 => i64), 207 _ => panic!("unexpected length for hex: {hex}"), 208 } 209 }; 210 format!("{prefix}{simm}{rest}").into() 211 } 212 } 213 } 214 215 // See `replace_signed_immediates`. 216 fn chomp<'a>(pat: &str, s: &'a str) -> (&'a str, &'a str) { 217 if s.starts_with(pat) { 218 s.split_at(pat.len()) 219 } else { 220 ("", s) 221 } 222 } 223 224 #[test] 225 fn replace() { 226 assert_eq!( 227 replace_signed_immediates("andl $0xffffff9a, %r11d"), 228 "andl $-0x66, %r11d" 229 ); 230 assert_eq!( 231 replace_signed_immediates("xorq $0xffffffffffffffbc, 0x7f139ecc(%r9)"), 232 "xorq $-0x44, 0x7f139ecc(%r9)" 233 ); 234 assert_eq!( 235 replace_signed_immediates("subl $0x3ca77a19, -0x1a030f40(%r14)"), 236 "subl $0x3ca77a19, -0x1a030f40(%r14)" 237 ); 238 assert_eq!( 239 replace_signed_immediates("movq $0xffffffff864ae103, %rsi"), 240 "movq $18446744071667638531, %rsi" 241 ); 242 } 243 244 /// Remove everything after the first semicolon in the disassembly and trim any 245 /// trailing spaces. This is necessary to remove the implicit operands we end up 246 /// printing for Cranelift's sake. 247 fn remove_after_semicolon(dis: &str) -> &str { 248 match dis.find(';') { 249 None => dis, 250 Some(idx) => { 251 let (prefix, _) = dis.split_at(idx); 252 prefix.trim() 253 } 254 } 255 } 256 257 #[test] 258 fn remove_after_parenthesis_test() { 259 assert_eq!( 260 remove_after_semicolon("imulb 0x7658eddd(%rcx) ;; implicit: %ax"), 261 "imulb 0x7658eddd(%rcx)" 262 ); 263 } 264 265 /// Run some post-processing on the disassembly to make it match Capstone. 266 fn fix_up(dis: &str) -> std::borrow::Cow<'_, str> { 267 let dis = remove_after_semicolon(dis); 268 replace_signed_immediates(&dis) 269 } 270 271 /// Fuzz-specific registers. 272 /// 273 /// For the fuzzer, we do not need any fancy register types; see [`FuzzReg`]. 274 #[derive(Arbitrary, Debug)] 275 pub struct FuzzRegs; 276 277 impl Registers for FuzzRegs { 278 type ReadGpr = FuzzReg; 279 type ReadWriteGpr = FuzzReg; 280 type WriteGpr = FuzzReg; 281 type ReadXmm = FuzzReg; 282 type ReadWriteXmm = FuzzReg; 283 type WriteXmm = FuzzReg; 284 } 285 286 /// A simple `u8` register type for fuzzing only. 287 #[derive(Clone, Copy, Debug, PartialEq)] 288 pub struct FuzzReg(u8); 289 290 impl<'a> Arbitrary<'a> for FuzzReg { 291 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> { 292 Ok(Self(u.int_in_range(0..=15)?)) 293 } 294 } 295 296 impl AsReg for FuzzReg { 297 fn new(enc: u8) -> Self { 298 Self(enc) 299 } 300 fn enc(&self) -> u8 { 301 self.0 302 } 303 } 304 305 impl Arbitrary<'_> for AmodeOffsetPlusKnownOffset { 306 fn arbitrary(u: &mut Unstructured<'_>) -> Result<Self> { 307 // For now, we don't generate offsets (TODO). 308 Ok(Self { 309 simm32: AmodeOffset::arbitrary(u)?, 310 offset: None, 311 }) 312 } 313 } 314 315 impl<R: AsReg, const E: u8> Arbitrary<'_> for Fixed<R, E> { 316 fn arbitrary(_: &mut Unstructured<'_>) -> Result<Self> { 317 Ok(Self::new(E)) 318 } 319 } 320 321 impl<R: AsReg> Arbitrary<'_> for NonRspGpr<R> { 322 fn arbitrary(u: &mut Unstructured<'_>) -> Result<Self> { 323 use crate::gpr::enc::*; 324 let gpr = u.choose(&[ 325 RAX, RCX, RDX, RBX, RBP, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15, 326 ])?; 327 Ok(Self::new(R::new(*gpr))) 328 } 329 } 330 impl<'a, R: AsReg> Arbitrary<'a> for Gpr<R> { 331 fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> { 332 Ok(Self(R::new(u.int_in_range(0..=15)?))) 333 } 334 } 335 impl<'a, R: AsReg> Arbitrary<'a> for Xmm<R> { 336 fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> { 337 Ok(Self(R::new(u.int_in_range(0..=15)?))) 338 } 339 } 340 341 /// Helper trait that's used to be the same as `Registers` except with an extra 342 /// `for<'a> Arbitrary<'a>` bound on all of the associated types. 343 pub trait RegistersArbitrary: 344 Registers< 345 ReadGpr: for<'a> Arbitrary<'a>, 346 ReadWriteGpr: for<'a> Arbitrary<'a>, 347 WriteGpr: for<'a> Arbitrary<'a>, 348 ReadXmm: for<'a> Arbitrary<'a>, 349 ReadWriteXmm: for<'a> Arbitrary<'a>, 350 WriteXmm: for<'a> Arbitrary<'a>, 351 > 352 { 353 } 354 355 impl<R> RegistersArbitrary for R 356 where 357 R: Registers, 358 R::ReadGpr: for<'a> Arbitrary<'a>, 359 R::ReadWriteGpr: for<'a> Arbitrary<'a>, 360 R::WriteGpr: for<'a> Arbitrary<'a>, 361 R::ReadXmm: for<'a> Arbitrary<'a>, 362 R::ReadWriteXmm: for<'a> Arbitrary<'a>, 363 R::WriteXmm: for<'a> Arbitrary<'a>, 364 { 365 } 366 367 #[cfg(test)] 368 mod test { 369 use super::*; 370 use arbtest::arbtest; 371 use std::sync::atomic::{AtomicUsize, Ordering}; 372 373 #[test] 374 fn smoke() { 375 let count = AtomicUsize::new(0); 376 arbtest(|u| { 377 let inst: Inst<FuzzRegs> = u.arbitrary()?; 378 roundtrip(&inst); 379 println!("#{}: {inst}", count.fetch_add(1, Ordering::SeqCst)); 380 Ok(()) 381 }) 382 .budget_ms(1_000); 383 384 // This will run the `roundtrip` fuzzer for one second. To repeatably 385 // test a single input, append `.seed(0x<failing seed>)`. 386 } 387 } 388