1use smallvec::SmallVec;
26
27use crate::{
28 AsmError,
29 core::{
30 arch_traits::Arch,
31 buffer::{CodeBufferFinalized, CodeOffset, LabelUse},
32 },
33};
34
35#[cfg(feature = "jit")]
36use crate::core::jit_allocator::{JitAllocator, Span};
37
38#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
40pub struct PatchBlockId(u32);
41
42impl PatchBlockId {
43 pub(crate) const fn from_index(index: usize) -> Self {
44 Self(index as u32)
45 }
46
47 pub const fn index(self) -> usize {
48 self.0 as usize
49 }
50}
51
52#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
54pub struct PatchSiteId(u32);
55
56impl PatchSiteId {
57 pub(crate) const fn from_index(index: usize) -> Self {
58 Self(index as u32)
59 }
60
61 pub const fn index(self) -> usize {
62 self.0 as usize
63 }
64}
65
66#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68pub struct PatchBlock {
69 pub offset: CodeOffset,
70 pub size: CodeOffset,
71 pub align: CodeOffset,
72}
73
74impl PatchBlock {
75 pub const fn to_patchable(self, arch: Arch) -> PatchableBlock {
77 unsafe { PatchableBlock::new(self.offset, self.size, arch) }
79 }
80}
81
82#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84pub struct PatchSite {
85 pub offset: CodeOffset,
86 pub kind: LabelUse,
87 pub current_target: CodeOffset,
88 pub addend: i64,
89}
90
91impl PatchSite {
92 pub const fn to_patchable(self) -> PatchableSite {
94 unsafe { PatchableSite::new(self.offset, self.kind, self.addend) }
96 }
97}
98
99#[derive(Clone, Debug, PartialEq, Eq)]
101pub struct PatchCatalog {
102 arch: Arch,
103 blocks: SmallVec<[PatchBlock; 4]>,
104 sites: SmallVec<[PatchSite; 8]>,
105}
106
107impl PatchCatalog {
108 pub(crate) fn with_parts(
109 arch: Arch,
110 blocks: SmallVec<[PatchBlock; 4]>,
111 sites: SmallVec<[PatchSite; 8]>,
112 ) -> Self {
113 Self {
114 arch,
115 blocks,
116 sites,
117 }
118 }
119
120 pub fn arch(&self) -> Arch {
121 self.arch
122 }
123
124 pub fn is_empty(&self) -> bool {
125 self.blocks.is_empty() && self.sites.is_empty()
126 }
127
128 pub fn blocks(&self) -> &[PatchBlock] {
129 &self.blocks
130 }
131
132 pub fn sites(&self) -> &[PatchSite] {
133 &self.sites
134 }
135
136 pub fn block(&self, id: PatchBlockId) -> Option<&PatchBlock> {
137 self.blocks.get(id.index())
138 }
139
140 pub fn site(&self, id: PatchSiteId) -> Option<&PatchSite> {
141 self.sites.get(id.index())
142 }
143
144 pub fn site_mut(&mut self, id: PatchSiteId) -> Option<&mut PatchSite> {
145 self.sites.get_mut(id.index())
146 }
147}
148
149#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
151pub struct PatchableSite {
152 offset: CodeOffset,
153 kind: LabelUse,
154 addend: i64,
155}
156
157impl PatchableSite {
158 pub const unsafe fn new(offset: CodeOffset, kind: LabelUse, addend: i64) -> Self {
165 Self {
166 offset,
167 kind,
168 addend,
169 }
170 }
171
172 pub const fn offset(self) -> CodeOffset {
173 self.offset
174 }
175
176 pub const fn kind(self) -> LabelUse {
177 self.kind
178 }
179
180 pub const fn addend(self) -> i64 {
181 self.addend
182 }
183
184 pub unsafe fn retarget(
191 self,
192 bytes: &mut [u8],
193 target_offset: CodeOffset,
194 ) -> Result<(), AsmError> {
195 if !self.kind.can_reach(self.offset, target_offset) {
196 return Err(AsmError::TooLarge);
197 }
198 let patch_size = self.kind.patch_size();
199 let patch_end = (self.offset as usize)
200 .checked_add(patch_size)
201 .ok_or(AsmError::InvalidState)?;
202 if patch_end > bytes.len() {
203 return Err(AsmError::InvalidState);
204 }
205 let patch_slice = &mut bytes[self.offset as usize..patch_end];
206 self.kind
207 .patch_with_addend(patch_slice, self.offset, target_offset, self.addend);
208 Ok(())
209 }
210
211 #[cfg(feature = "jit")]
218 pub unsafe fn retarget_span(
219 self,
220 jit_allocator: &mut JitAllocator,
221 span: &mut Span,
222 target_offset: CodeOffset,
223 ) -> Result<(), AsmError> {
224 if !self.kind.can_reach(self.offset, target_offset) {
225 return Err(AsmError::TooLarge);
226 }
227 let patch_size = self.kind.patch_size();
228 let patch_end = (self.offset as usize)
229 .checked_add(patch_size)
230 .ok_or(AsmError::InvalidState)?;
231 if patch_end > span.size() {
232 return Err(AsmError::InvalidState);
233 }
234
235 unsafe {
241 jit_allocator.write(span, |span| {
242 let patch_ptr = span.rw().add(self.offset as usize);
243 let patch_slice = core::slice::from_raw_parts_mut(patch_ptr, patch_size);
244 self.kind
245 .patch_with_addend(patch_slice, self.offset, target_offset, self.addend);
246 })?;
247 }
248 Ok(())
249 }
250}
251
252#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
254pub struct PatchableBlock {
255 offset: CodeOffset,
256 size: CodeOffset,
257 arch: Arch,
258}
259
260impl PatchableBlock {
261 pub const unsafe fn new(offset: CodeOffset, size: CodeOffset, arch: Arch) -> Self {
268 Self { offset, size, arch }
269 }
270
271 pub const fn offset(self) -> CodeOffset {
272 self.offset
273 }
274
275 pub const fn size(self) -> CodeOffset {
276 self.size
277 }
278
279 pub const fn arch(self) -> Arch {
280 self.arch
281 }
282
283 pub unsafe fn rewrite(self, bytes: &mut [u8], new_bytes: &[u8]) -> Result<(), AsmError> {
290 if new_bytes.len() > self.size as usize {
291 return Err(AsmError::TooLarge);
292 }
293 let instruction_alignment = minimum_patch_alignment(self.arch) as usize;
294 if new_bytes.len() % instruction_alignment != 0 {
295 return Err(AsmError::InvalidArgument);
296 }
297 let block_end = (self.offset as usize)
298 .checked_add(self.size as usize)
299 .ok_or(AsmError::InvalidState)?;
300 if block_end > bytes.len() {
301 return Err(AsmError::InvalidState);
302 }
303
304 let block = &mut bytes[self.offset as usize..block_end];
305 block[..new_bytes.len()].copy_from_slice(new_bytes);
306 fill_with_nops(self.arch, &mut block[new_bytes.len()..])?;
307 Ok(())
308 }
309
310 pub unsafe fn repatch_u32(self, bytes: &mut [u8], value: u32) -> Result<(), AsmError> {
316 if self.size != 4 {
317 return Err(AsmError::InvalidArgument);
318 }
319 unsafe { self.rewrite(bytes, &value.to_le_bytes()) }
322 }
323
324 pub unsafe fn repatch_u64(self, bytes: &mut [u8], value: u64) -> Result<(), AsmError> {
330 if self.size != 8 {
331 return Err(AsmError::InvalidArgument);
332 }
333 unsafe { self.rewrite(bytes, &value.to_le_bytes()) }
336 }
337
338 #[cfg(feature = "jit")]
344 pub unsafe fn rewrite_span(
345 self,
346 jit_allocator: &mut JitAllocator,
347 span: &mut Span,
348 new_bytes: &[u8],
349 ) -> Result<(), AsmError> {
350 if new_bytes.len() > self.size as usize {
351 return Err(AsmError::TooLarge);
352 }
353 let instruction_alignment = minimum_patch_alignment(self.arch) as usize;
354 if new_bytes.len() % instruction_alignment != 0 {
355 return Err(AsmError::InvalidArgument);
356 }
357 let block_end = (self.offset as usize)
358 .checked_add(self.size as usize)
359 .ok_or(AsmError::InvalidState)?;
360 if block_end > span.size() {
361 return Err(AsmError::InvalidState);
362 }
363
364 let mut fill_result = Ok(());
365 unsafe {
372 jit_allocator.write(span, |span| {
373 let block_ptr = span.rw().add(self.offset as usize);
374 block_ptr.copy_from_nonoverlapping(new_bytes.as_ptr(), new_bytes.len());
375 let tail = core::slice::from_raw_parts_mut(
376 block_ptr.add(new_bytes.len()),
377 self.size as usize - new_bytes.len(),
378 );
379 fill_result = fill_with_nops(self.arch, tail);
380 })?;
381 }
382 fill_result
383 }
384
385 #[cfg(feature = "jit")]
391 pub unsafe fn repatch_u32_span(
392 self,
393 jit_allocator: &mut JitAllocator,
394 span: &mut Span,
395 value: u32,
396 ) -> Result<(), AsmError> {
397 if self.size != 4 {
398 return Err(AsmError::InvalidArgument);
399 }
400 unsafe { self.rewrite_span(jit_allocator, span, &value.to_le_bytes()) }
403 }
404
405 #[cfg(feature = "jit")]
411 pub unsafe fn repatch_u64_span(
412 self,
413 jit_allocator: &mut JitAllocator,
414 span: &mut Span,
415 value: u64,
416 ) -> Result<(), AsmError> {
417 if self.size != 8 {
418 return Err(AsmError::InvalidArgument);
419 }
420 unsafe { self.rewrite_span(jit_allocator, span, &value.to_le_bytes()) }
423 }
424}
425
426pub fn minimum_patch_alignment(arch: Arch) -> CodeOffset {
427 match arch {
428 Arch::AArch64 | Arch::RISCV32 | Arch::RISCV64 => 4,
429 _ => 1,
430 }
431}
432
433pub fn fill_with_nops(arch: Arch, buffer: &mut [u8]) -> Result<(), AsmError> {
434 let pattern: &[u8] = match arch {
435 Arch::X86 | Arch::X64 => &[0x90],
436 Arch::AArch64 => &[0x1f, 0x20, 0x03, 0xd5],
437 Arch::RISCV32 | Arch::RISCV64 => &[0x13, 0x00, 0x00, 0x00],
438 _ => return Err(AsmError::InvalidArgument),
439 };
440
441 if pattern.len() > 1 && buffer.len() % pattern.len() != 0 {
442 return Err(AsmError::InvalidArgument);
443 }
444
445 for chunk in buffer.chunks_mut(pattern.len()) {
446 chunk.copy_from_slice(pattern);
447 }
448
449 Ok(())
450}
451
452impl CodeBufferFinalized {
453 pub fn patch_catalog(&self) -> &PatchCatalog {
454 &self.patch_catalog
455 }
456}
457
458#[cfg(test)]
459mod tests {
460 use super::*;
461
462 #[test]
463 fn retarget_rejects_out_of_range_slice() {
464 let site = unsafe { PatchableSite::new(62, LabelUse::X86JmpRel32, 0) };
465 let mut bytes = [0u8; 64];
466 assert_eq!(
467 unsafe { site.retarget(&mut bytes, 0) }.unwrap_err(),
468 AsmError::InvalidState
469 );
470 }
471
472 #[test]
473 fn rewrite_rejects_misaligned_payload_for_a64() {
474 let block = unsafe { PatchableBlock::new(0, 4, Arch::AArch64) };
475 let mut bytes = [0u8; 4];
476 assert_eq!(
477 unsafe { block.rewrite(&mut bytes, &[0]) }.unwrap_err(),
478 AsmError::InvalidArgument
479 );
480 }
481
482 #[test]
483 fn repatch_u32_round_trips() {
484 let block = unsafe { PatchableBlock::new(1, 4, Arch::X64) };
485 let mut bytes = [0xB8, 0, 0, 0, 0];
486 unsafe { block.repatch_u32(&mut bytes, 0x11223344).unwrap() };
487 assert_eq!(&bytes[1..], &[0x44, 0x33, 0x22, 0x11]);
488 }
489
490 #[cfg(feature = "jit")]
491 #[test]
492 fn span_patch_rejects_ranges_outside_span() {
493 use crate::core::jit_allocator::JitAllocatorOptions;
494
495 let mut allocator = JitAllocator::new(JitAllocatorOptions::default());
496 let mut span = allocator.alloc(64).unwrap();
497 let span_size = span.size() as CodeOffset;
498
499 let block = unsafe { PatchableBlock::new(span_size, 1, Arch::X64) };
500 assert_eq!(
501 unsafe { block.rewrite_span(&mut allocator, &mut span, &[0x90]) }.unwrap_err(),
502 AsmError::InvalidState
503 );
504
505 let site = unsafe { PatchableSite::new(span_size - 3, LabelUse::X86JmpRel32, 0) };
506 assert_eq!(
507 unsafe { site.retarget_span(&mut allocator, &mut span, 0) }.unwrap_err(),
508 AsmError::InvalidState
509 );
510 }
511}