|
5 | 5 | //! unconditionally (use the `fuzz` feature instead). |
6 | 6 |
|
7 | 7 | use crate::{ |
8 | | - AmodeOffset, AmodeOffsetPlusKnownOffset, AsReg, Fixed, Gpr, Inst, NonRspGpr, Registers, Xmm, |
| 8 | + AmodeOffset, AmodeOffsetPlusKnownOffset, AsReg, CodeSink, Constant, Fixed, Gpr, Inst, Label, |
| 9 | + NonRspGpr, Registers, TrapCode, Xmm, |
9 | 10 | }; |
10 | 11 | use arbitrary::{Arbitrary, Result, Unstructured}; |
11 | 12 | use capstone::{Capstone, arch::BuildsCapstone, arch::BuildsCapstoneSyntax, arch::x86}; |
@@ -42,10 +43,75 @@ pub fn roundtrip(inst: &Inst<FuzzRegs>) { |
42 | 43 | /// This will skip any traps or label registrations, but this is fine for the |
43 | 44 | /// single-instruction disassembly we're doing here. |
44 | 45 | fn assemble(inst: &Inst<FuzzRegs>) -> Vec<u8> { |
45 | | - let mut buffer = Vec::new(); |
| 46 | + let mut sink = TestCodeSink::default(); |
46 | 47 | let offsets: Vec<i32> = Vec::new(); |
47 | | - inst.encode(&mut buffer, &offsets); |
48 | | - buffer |
| 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 | + } |
49 | 115 | } |
50 | 116 |
|
51 | 117 | /// Building a new `Capstone` each time is suboptimal (TODO). |
|
0 commit comments