1use smallvec::SmallVec;
43
44use crate::{
45 AsmError,
46 core::{
47 arch_traits::Arch,
48 buffer::{CodeBufferFinalized, CodeOffset, LabelUse},
49 },
50};
51
52#[cfg(feature = "jit")]
53use crate::core::jit_allocator::{JitAllocator, Span};
54
55#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
57pub struct PatchBlockId(u32);
58
59impl PatchBlockId {
60 pub(crate) const fn from_index(index: usize) -> Self {
61 Self(index as u32)
62 }
63
64 pub const fn index(self) -> usize {
65 self.0 as usize
66 }
67}
68
69#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
71pub struct PatchSiteId(u32);
72
73impl PatchSiteId {
74 pub(crate) const fn from_index(index: usize) -> Self {
75 Self(index as u32)
76 }
77
78 pub const fn index(self) -> usize {
79 self.0 as usize
80 }
81}
82
83#[derive(Clone, Copy, Debug, PartialEq, Eq)]
85pub struct PatchBlock {
86 pub offset: CodeOffset,
87 pub size: CodeOffset,
88 pub align: CodeOffset,
89}
90
91impl PatchBlock {
92 pub const fn to_patchable(self, arch: Arch) -> PatchableBlock {
94 unsafe { PatchableBlock::new(self.offset, self.size, arch) }
96 }
97}
98
99#[derive(Clone, Copy, Debug, PartialEq, Eq)]
101pub struct PatchSite {
102 pub offset: CodeOffset,
103 pub kind: LabelUse,
104 pub current_target: CodeOffset,
105 pub addend: i64,
106}
107
108impl PatchSite {
109 pub const fn to_patchable(self) -> PatchableSite {
111 unsafe { PatchableSite::new(self.offset, self.kind, self.addend) }
113 }
114}
115
116#[derive(Clone, Debug, PartialEq, Eq)]
118pub struct PatchCatalog {
119 arch: Arch,
120 blocks: SmallVec<[PatchBlock; 4]>,
121 sites: SmallVec<[PatchSite; 8]>,
122}
123
124impl PatchCatalog {
125 pub(crate) fn with_parts(
126 arch: Arch,
127 blocks: SmallVec<[PatchBlock; 4]>,
128 sites: SmallVec<[PatchSite; 8]>,
129 ) -> Self {
130 Self {
131 arch,
132 blocks,
133 sites,
134 }
135 }
136
137 pub fn arch(&self) -> Arch {
138 self.arch
139 }
140
141 pub fn is_empty(&self) -> bool {
142 self.blocks.is_empty() && self.sites.is_empty()
143 }
144
145 pub fn blocks(&self) -> &[PatchBlock] {
146 &self.blocks
147 }
148
149 pub fn sites(&self) -> &[PatchSite] {
150 &self.sites
151 }
152
153 pub fn block(&self, id: PatchBlockId) -> Option<&PatchBlock> {
154 self.blocks.get(id.index())
155 }
156
157 pub fn site(&self, id: PatchSiteId) -> Option<&PatchSite> {
158 self.sites.get(id.index())
159 }
160
161 pub fn site_mut(&mut self, id: PatchSiteId) -> Option<&mut PatchSite> {
162 self.sites.get_mut(id.index())
163 }
164}
165
166#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
168pub struct PatchableSite {
169 offset: CodeOffset,
170 kind: LabelUse,
171 addend: i64,
172}
173
174impl PatchableSite {
175 pub const unsafe fn new(offset: CodeOffset, kind: LabelUse, addend: i64) -> Self {
182 Self {
183 offset,
184 kind,
185 addend,
186 }
187 }
188
189 pub const fn offset(self) -> CodeOffset {
190 self.offset
191 }
192
193 pub const fn kind(self) -> LabelUse {
194 self.kind
195 }
196
197 pub const fn addend(self) -> i64 {
198 self.addend
199 }
200
201 pub unsafe fn retarget(
208 self,
209 bytes: &mut [u8],
210 target_offset: CodeOffset,
211 ) -> Result<(), AsmError> {
212 if !self.kind.can_reach(self.offset, target_offset) {
213 return Err(AsmError::TooLarge);
214 }
215 let patch_size = self.kind.patch_size();
216 let patch_end = (self.offset as usize)
217 .checked_add(patch_size)
218 .ok_or(AsmError::InvalidState)?;
219 if patch_end > bytes.len() {
220 return Err(AsmError::InvalidState);
221 }
222 let patch_slice = &mut bytes[self.offset as usize..patch_end];
223 self.kind
224 .patch_with_addend(patch_slice, self.offset, target_offset, self.addend);
225 Ok(())
226 }
227
228 #[cfg(feature = "jit")]
235 pub unsafe fn retarget_span(
236 self,
237 jit_allocator: &mut JitAllocator,
238 span: &mut Span,
239 target_offset: CodeOffset,
240 ) -> Result<(), AsmError> {
241 if !self.kind.can_reach(self.offset, target_offset) {
242 return Err(AsmError::TooLarge);
243 }
244 let patch_size = self.kind.patch_size();
245 let patch_end = (self.offset as usize)
246 .checked_add(patch_size)
247 .ok_or(AsmError::InvalidState)?;
248 if patch_end > span.size() {
249 return Err(AsmError::InvalidState);
250 }
251
252 unsafe {
253 jit_allocator.write(span, |span| {
254 let patch_ptr = span.rw().add(self.offset as usize);
255 let patch_slice = core::slice::from_raw_parts_mut(patch_ptr, patch_size);
256 self.kind.patch_with_addend(
257 patch_slice,
258 self.offset,
259 target_offset,
260 self.addend,
261 );
262 })?;
263 }
264 Ok(())
265 }
266}
267
268#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
270pub struct PatchableBlock {
271 offset: CodeOffset,
272 size: CodeOffset,
273 arch: Arch,
274}
275
276impl PatchableBlock {
277 pub const unsafe fn new(offset: CodeOffset, size: CodeOffset, arch: Arch) -> Self {
284 Self {
285 offset,
286 size,
287 arch,
288 }
289 }
290
291 pub const fn offset(self) -> CodeOffset {
292 self.offset
293 }
294
295 pub const fn size(self) -> CodeOffset {
296 self.size
297 }
298
299 pub const fn arch(self) -> Arch {
300 self.arch
301 }
302
303 pub unsafe fn rewrite(self, bytes: &mut [u8], new_bytes: &[u8]) -> Result<(), AsmError> {
310 if new_bytes.len() > self.size as usize {
311 return Err(AsmError::TooLarge);
312 }
313 let instruction_alignment = minimum_patch_alignment(self.arch) as usize;
314 if new_bytes.len() % instruction_alignment != 0 {
315 return Err(AsmError::InvalidArgument);
316 }
317 let block_end = (self.offset as usize)
318 .checked_add(self.size as usize)
319 .ok_or(AsmError::InvalidState)?;
320 if block_end > bytes.len() {
321 return Err(AsmError::InvalidState);
322 }
323
324 let block = &mut bytes[self.offset as usize..block_end];
325 block[..new_bytes.len()].copy_from_slice(new_bytes);
326 fill_with_nops(self.arch, &mut block[new_bytes.len()..])?;
327 Ok(())
328 }
329
330 pub unsafe fn repatch_u32(self, bytes: &mut [u8], value: u32) -> Result<(), AsmError> {
336 if self.size != 4 {
337 return Err(AsmError::InvalidArgument);
338 }
339 unsafe { self.rewrite(bytes, &value.to_le_bytes()) }
340 }
341
342 pub unsafe fn repatch_u64(self, bytes: &mut [u8], value: u64) -> Result<(), AsmError> {
348 if self.size != 8 {
349 return Err(AsmError::InvalidArgument);
350 }
351 unsafe { self.rewrite(bytes, &value.to_le_bytes()) }
352 }
353
354 #[cfg(feature = "jit")]
360 pub unsafe fn rewrite_span(
361 self,
362 jit_allocator: &mut JitAllocator,
363 span: &mut Span,
364 new_bytes: &[u8],
365 ) -> Result<(), AsmError> {
366 if new_bytes.len() > self.size as usize {
367 return Err(AsmError::TooLarge);
368 }
369 let instruction_alignment = minimum_patch_alignment(self.arch) as usize;
370 if new_bytes.len() % instruction_alignment != 0 {
371 return Err(AsmError::InvalidArgument);
372 }
373 let block_end = (self.offset as usize)
374 .checked_add(self.size as usize)
375 .ok_or(AsmError::InvalidState)?;
376 if block_end > span.size() {
377 return Err(AsmError::InvalidState);
378 }
379
380 let mut fill_result = Ok(());
381 unsafe {
382 jit_allocator.write(span, |span| {
383 let block_ptr = span.rw().add(self.offset as usize);
384 block_ptr.copy_from_nonoverlapping(new_bytes.as_ptr(), new_bytes.len());
385 let tail = core::slice::from_raw_parts_mut(
386 block_ptr.add(new_bytes.len()),
387 self.size as usize - new_bytes.len(),
388 );
389 fill_result = fill_with_nops(self.arch, tail);
390 })?;
391 }
392 fill_result
393 }
394
395 #[cfg(feature = "jit")]
401 pub unsafe fn repatch_u32_span(
402 self,
403 jit_allocator: &mut JitAllocator,
404 span: &mut Span,
405 value: u32,
406 ) -> Result<(), AsmError> {
407 if self.size != 4 {
408 return Err(AsmError::InvalidArgument);
409 }
410 unsafe { self.rewrite_span(jit_allocator, span, &value.to_le_bytes()) }
411 }
412
413 #[cfg(feature = "jit")]
419 pub unsafe fn repatch_u64_span(
420 self,
421 jit_allocator: &mut JitAllocator,
422 span: &mut Span,
423 value: u64,
424 ) -> Result<(), AsmError> {
425 if self.size != 8 {
426 return Err(AsmError::InvalidArgument);
427 }
428 unsafe { self.rewrite_span(jit_allocator, span, &value.to_le_bytes()) }
429 }
430}
431
432pub fn minimum_patch_alignment(arch: Arch) -> CodeOffset {
433 match arch {
434 Arch::AArch64 | Arch::RISCV32 | Arch::RISCV64 => 4,
435 _ => 1,
436 }
437}
438
439pub fn fill_with_nops(arch: Arch, buffer: &mut [u8]) -> Result<(), AsmError> {
440 let pattern: &[u8] = match arch {
441 Arch::X86 | Arch::X64 => &[0x90],
442 Arch::AArch64 => &[0x1f, 0x20, 0x03, 0xd5],
443 Arch::RISCV32 | Arch::RISCV64 => &[0x13, 0x00, 0x00, 0x00],
444 _ => return Err(AsmError::InvalidArgument),
445 };
446
447 if pattern.len() > 1 && buffer.len() % pattern.len() != 0 {
448 return Err(AsmError::InvalidArgument);
449 }
450
451 for chunk in buffer.chunks_mut(pattern.len()) {
452 chunk.copy_from_slice(pattern);
453 }
454
455 Ok(())
456}
457
458impl CodeBufferFinalized {
459 pub fn patch_catalog(&self) -> &PatchCatalog {
460 &self.patch_catalog
461 }
462}
463
464#[cfg(test)]
465mod tests {
466 use super::*;
467
468 #[test]
469 fn retarget_rejects_out_of_range_slice() {
470 let site = unsafe { PatchableSite::new(62, LabelUse::X86JmpRel32, 0) };
471 let mut bytes = [0u8; 64];
472 assert_eq!(
473 unsafe { site.retarget(&mut bytes, 0) }.unwrap_err(),
474 AsmError::InvalidState
475 );
476 }
477
478 #[test]
479 fn rewrite_rejects_misaligned_payload_for_a64() {
480 let block = unsafe { PatchableBlock::new(0, 4, Arch::AArch64) };
481 let mut bytes = [0u8; 4];
482 assert_eq!(
483 unsafe { block.rewrite(&mut bytes, &[0]) }.unwrap_err(),
484 AsmError::InvalidArgument
485 );
486 }
487
488 #[test]
489 fn repatch_u32_round_trips() {
490 let block = unsafe { PatchableBlock::new(1, 4, Arch::X64) };
491 let mut bytes = [0xB8, 0, 0, 0, 0];
492 unsafe { block.repatch_u32(&mut bytes, 0x11223344).unwrap() };
493 assert_eq!(&bytes[1..], &[0x44, 0x33, 0x22, 0x11]);
494 }
495
496 #[cfg(feature = "jit")]
497 #[test]
498 fn span_patch_rejects_ranges_outside_span() {
499 use crate::core::jit_allocator::JitAllocatorOptions;
500
501 let mut allocator = JitAllocator::new(JitAllocatorOptions::default());
502 let mut span = allocator.alloc(64).unwrap();
503 let span_size = span.size() as CodeOffset;
504
505 let block = unsafe { PatchableBlock::new(span_size, 1, Arch::X64) };
506 assert_eq!(
507 unsafe { block.rewrite_span(&mut allocator, &mut span, &[0x90]) }.unwrap_err(),
508 AsmError::InvalidState
509 );
510
511 let site = unsafe { PatchableSite::new(span_size - 3, LabelUse::X86JmpRel32, 0) };
512 assert_eq!(
513 unsafe { site.retarget_span(&mut allocator, &mut span, 0) }.unwrap_err(),
514 AsmError::InvalidState
515 );
516 }
517}