1use std::string::{String, ToString};
8use std::vec::Vec;
9use std::{format, println};
10
11use crate::{
12 AmodeOffset, AmodeOffsetPlusKnownOffset, AsReg, CodeSink, DeferredTarget, Feature, Features,
13 Fixed, Gpr, Inst, KnownOffset, NonRspGpr, Registers, TrapCode, Xmm,
14};
15use arbitrary::{Arbitrary, Result, Unstructured};
16use capstone::{Capstone, arch::BuildsCapstone, arch::BuildsCapstoneSyntax, arch::x86};
17
18pub fn roundtrip(inst: &Inst<FuzzRegs>) {
27 if features_mention(inst.features(), Feature::avx_vnni) {
31 return;
32 }
33
34 let assembled = assemble(inst);
36 let expected = disassemble(&assembled, inst);
37
38 let expected = expected.split_once(' ').unwrap().1;
41 let actual = inst.to_string();
42 if expected != actual && expected.trim() != fix_up(&actual) {
43 println!("> {inst}");
44 println!(" debug: {inst:x?}");
45 println!(" assembled: {}", pretty_print_hexadecimal(&assembled));
46 println!(" expected (capstone): {expected}");
47 println!(" actual (to_string): {actual}");
48 assert_eq!(expected, &actual);
49 }
50}
51
52fn features_mention(features: &Features, target: Feature) -> bool {
55 match features {
56 Features::And(a, b) | Features::Or(a, b) => {
57 features_mention(a, target) || features_mention(b, target)
58 }
59 Features::Feature(f) => *f == target,
60 }
61}
62
63fn assemble(inst: &Inst<FuzzRegs>) -> Vec<u8> {
68 let mut sink = TestCodeSink::default();
69 inst.encode(&mut sink);
70 sink.patch_labels_as_if_they_referred_to_end();
71 sink.buf
72}
73
74#[derive(Default)]
75struct TestCodeSink {
76 buf: Vec<u8>,
77 offsets_using_label: Vec<usize>,
78}
79
80impl TestCodeSink {
81 fn patch_labels_as_if_they_referred_to_end(&mut self) {
96 let len = i32::try_from(self.buf.len()).unwrap();
97 for offset in self.offsets_using_label.iter() {
98 let range = self.buf[*offset..].first_chunk_mut::<4>().unwrap();
99 let offset = i32::try_from(*offset).unwrap() + 4;
100 let rel_distance = len - offset;
101 *range = (i32::from_le_bytes(*range) + rel_distance).to_le_bytes();
102 }
103 }
104}
105
106impl CodeSink for TestCodeSink {
107 fn put1(&mut self, v: u8) {
108 self.buf.extend_from_slice(&[v]);
109 }
110
111 fn put2(&mut self, v: u16) {
112 self.buf.extend_from_slice(&v.to_le_bytes());
113 }
114
115 fn put4(&mut self, v: u32) {
116 self.buf.extend_from_slice(&v.to_le_bytes());
117 }
118
119 fn put8(&mut self, v: u64) {
120 self.buf.extend_from_slice(&v.to_le_bytes());
121 }
122
123 fn add_trap(&mut self, _: TrapCode) {}
124
125 fn use_target(&mut self, _: DeferredTarget) {
126 let offset = self.buf.len();
127 self.offsets_using_label.push(offset);
128 }
129
130 fn known_offset(&self, target: KnownOffset) -> i32 {
131 panic!("unsupported known target {target:?}")
132 }
133}
134
135fn disassemble(assembled: &[u8], original: &Inst<FuzzRegs>) -> String {
137 let cs = Capstone::new()
138 .x86()
139 .mode(x86::ArchMode::Mode64)
140 .syntax(x86::ArchSyntax::Att)
141 .detail(true)
142 .build()
143 .expect("failed to create Capstone object");
144 let insts = cs
145 .disasm_all(assembled, 0x0)
146 .expect("failed to disassemble");
147
148 if insts.len() != 1 {
149 println!("> {original}");
150 println!(" debug: {original:x?}");
151 println!(" assembled: {}", pretty_print_hexadecimal(&assembled));
152 assert_eq!(insts.len(), 1, "not a single instruction");
153 }
154
155 let inst = insts.first().expect("at least one instruction");
156 if assembled.len() != inst.len() {
157 println!("> {original}");
158 println!(" debug: {original:x?}");
159 println!(" assembled: {}", pretty_print_hexadecimal(&assembled));
160 println!(
161 " capstone-assembled: {}",
162 pretty_print_hexadecimal(inst.bytes())
163 );
164 assert_eq!(assembled.len(), inst.len(), "extra bytes not disassembled");
165 }
166
167 inst.to_string()
168}
169
170fn pretty_print_hexadecimal(hex: &[u8]) -> String {
171 use core::fmt::Write;
172 let mut s = String::with_capacity(hex.len() * 2);
173 for b in hex {
174 write!(&mut s, "{b:02X}").unwrap();
175 }
176 s
177}
178
179macro_rules! hex_print_signed_imm {
181 ($hex:expr, $from:ty => $to:ty) => {{
182 let imm = <$from>::from_str_radix($hex, 16).unwrap() as $to;
183 let mut simm = String::new();
184 if imm < 0 {
185 simm.push_str("-");
186 }
187 let abs = match imm.checked_abs() {
188 Some(i) => i,
189 None => <$to>::MIN,
190 };
191 if imm > -10 && imm < 10 {
192 simm.push_str(&format!("{:x}", abs));
193 } else {
194 simm.push_str(&format!("0x{:x}", abs));
195 }
196 simm
197 }};
198}
199
200fn replace_signed_immediates(dis: &str) -> alloc::borrow::Cow<'_, str> {
209 match dis.find('$') {
210 None => dis.into(),
211 Some(idx) => {
212 let (prefix, rest) = dis.split_at(idx + 1); let (_, rest) = chomp("-", rest); let (_, rest) = chomp("0x", rest); let n = rest.chars().take_while(char::is_ascii_hexdigit).count();
216 let (hex, rest) = rest.split_at(n); let simm = if dis.starts_with("mov") {
218 u64::from_str_radix(hex, 16).unwrap().to_string()
219 } else {
220 match hex.len() {
221 1 | 2 => hex_print_signed_imm!(hex, u8 => i8),
222 4 => hex_print_signed_imm!(hex, u16 => i16),
223 8 => hex_print_signed_imm!(hex, u32 => i32),
224 16 => hex_print_signed_imm!(hex, u64 => i64),
225 _ => panic!("unexpected length for hex: {hex}"),
226 }
227 };
228 format!("{prefix}{simm}{rest}").into()
229 }
230 }
231}
232
233fn chomp<'a>(pat: &str, s: &'a str) -> (&'a str, &'a str) {
235 if s.starts_with(pat) {
236 s.split_at(pat.len())
237 } else {
238 ("", s)
239 }
240}
241
242#[test]
243fn replace() {
244 assert_eq!(
245 replace_signed_immediates("andl $0xffffff9a, %r11d"),
246 "andl $-0x66, %r11d"
247 );
248 assert_eq!(
249 replace_signed_immediates("xorq $0xffffffffffffffbc, 0x7f139ecc(%r9)"),
250 "xorq $-0x44, 0x7f139ecc(%r9)"
251 );
252 assert_eq!(
253 replace_signed_immediates("subl $0x3ca77a19, -0x1a030f40(%r14)"),
254 "subl $0x3ca77a19, -0x1a030f40(%r14)"
255 );
256 assert_eq!(
257 replace_signed_immediates("movq $0xffffffff864ae103, %rsi"),
258 "movq $18446744071667638531, %rsi"
259 );
260}
261
262fn remove_after_semicolon(dis: &str) -> &str {
266 match dis.find(';') {
267 None => dis,
268 Some(idx) => {
269 let (prefix, _) = dis.split_at(idx);
270 prefix.trim()
271 }
272 }
273}
274
275#[test]
276fn remove_after_parenthesis_test() {
277 assert_eq!(
278 remove_after_semicolon("imulb 0x7658eddd(%rcx) ;; implicit: %ax"),
279 "imulb 0x7658eddd(%rcx)"
280 );
281}
282
283fn fix_up(dis: &str) -> alloc::borrow::Cow<'_, str> {
285 let dis = remove_after_semicolon(dis);
286 replace_signed_immediates(&dis)
287}
288
289#[derive(Clone, Arbitrary, Debug)]
293pub struct FuzzRegs;
294
295impl Registers for FuzzRegs {
296 type ReadGpr = FuzzReg;
297 type ReadWriteGpr = FuzzReg;
298 type WriteGpr = FuzzReg;
299 type ReadXmm = FuzzReg;
300 type ReadWriteXmm = FuzzReg;
301 type WriteXmm = FuzzReg;
302}
303
304#[derive(Clone, Copy, Debug, PartialEq)]
306pub struct FuzzReg(u8);
307
308impl<'a> Arbitrary<'a> for FuzzReg {
309 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
310 Ok(Self(u.int_in_range(0..=15)?))
311 }
312}
313
314impl AsReg for FuzzReg {
315 fn new(enc: u8) -> Self {
316 Self(enc)
317 }
318 fn enc(&self) -> u8 {
319 self.0
320 }
321}
322
323impl Arbitrary<'_> for AmodeOffset {
324 fn arbitrary(u: &mut Unstructured<'_>) -> Result<Self> {
325 let base = if u.arbitrary()? {
331 i32::from(u.arbitrary::<i8>()?)
332 } else {
333 u.arbitrary::<i32>()?
334 };
335 Ok(match u.int_in_range(0..=5)? {
336 0 => AmodeOffset::ZERO,
337 n => AmodeOffset::new(base << (n - 1)),
338 })
339 }
340}
341
342impl Arbitrary<'_> for AmodeOffsetPlusKnownOffset {
343 fn arbitrary(u: &mut Unstructured<'_>) -> Result<Self> {
344 Ok(Self {
346 simm32: AmodeOffset::arbitrary(u)?,
347 offset: None,
348 })
349 }
350}
351
352impl<R: AsReg, const E: u8> Arbitrary<'_> for Fixed<R, E> {
353 fn arbitrary(_: &mut Unstructured<'_>) -> Result<Self> {
354 Ok(Self::new(E))
355 }
356}
357
358impl<R: AsReg> Arbitrary<'_> for NonRspGpr<R> {
359 fn arbitrary(u: &mut Unstructured<'_>) -> Result<Self> {
360 use crate::gpr::enc::*;
361 let gpr = u.choose(&[
362 RAX, RCX, RDX, RBX, RBP, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15,
363 ])?;
364 Ok(Self::new(R::new(*gpr)))
365 }
366}
367impl<'a, R: AsReg> Arbitrary<'a> for Gpr<R> {
368 fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
369 Ok(Self(R::new(u.int_in_range(0..=15)?)))
370 }
371}
372impl<'a, R: AsReg> Arbitrary<'a> for Xmm<R> {
373 fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
374 Ok(Self(R::new(u.int_in_range(0..=15)?)))
375 }
376}
377
378pub trait RegistersArbitrary:
381 Registers<
382 ReadGpr: for<'a> Arbitrary<'a>,
383 ReadWriteGpr: for<'a> Arbitrary<'a>,
384 WriteGpr: for<'a> Arbitrary<'a>,
385 ReadXmm: for<'a> Arbitrary<'a>,
386 ReadWriteXmm: for<'a> Arbitrary<'a>,
387 WriteXmm: for<'a> Arbitrary<'a>,
388 >
389{
390}
391
392impl<R> RegistersArbitrary for R
393where
394 R: Registers,
395 R::ReadGpr: for<'a> Arbitrary<'a>,
396 R::ReadWriteGpr: for<'a> Arbitrary<'a>,
397 R::WriteGpr: for<'a> Arbitrary<'a>,
398 R::ReadXmm: for<'a> Arbitrary<'a>,
399 R::ReadWriteXmm: for<'a> Arbitrary<'a>,
400 R::WriteXmm: for<'a> Arbitrary<'a>,
401{
402}
403
404#[cfg(test)]
405mod test {
406 use super::*;
407 use arbtest::arbtest;
408 use std::sync::atomic::{AtomicUsize, Ordering};
409
410 #[test]
411 fn smoke() {
412 let count = AtomicUsize::new(0);
413 arbtest(|u| {
414 let inst: Inst<FuzzRegs> = u.arbitrary()?;
415 roundtrip(&inst);
416 println!("#{}: {inst}", count.fetch_add(1, Ordering::SeqCst));
417 Ok(())
418 })
419 .budget_ms(1_000);
420
421 }
424
425 #[test]
426 fn callq() {
427 for i in -500..500 {
428 println!("immediate: {i}");
429 let inst = crate::inst::callq_d::new(i);
430 roundtrip(&inst.into());
431 }
432 }
433}