use smallvec::SmallVec;
use crate::{
AsmError,
core::{
arch_traits::Arch,
buffer::{CodeBufferFinalized, CodeOffset, LabelUse},
},
};
#[cfg(feature = "jit")]
use crate::core::jit_allocator::{JitAllocator, Span};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct PatchBlockId(u32);
impl PatchBlockId {
pub(crate) const fn from_index(index: usize) -> Self {
Self(index as u32)
}
pub const fn index(self) -> usize {
self.0 as usize
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct PatchSiteId(u32);
impl PatchSiteId {
pub(crate) const fn from_index(index: usize) -> Self {
Self(index as u32)
}
pub const fn index(self) -> usize {
self.0 as usize
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PatchBlock {
pub offset: CodeOffset,
pub size: CodeOffset,
pub align: CodeOffset,
}
impl PatchBlock {
pub const fn to_patchable(self, arch: Arch) -> PatchableBlock {
unsafe { PatchableBlock::new(self.offset, self.size, arch) }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PatchSite {
pub offset: CodeOffset,
pub kind: LabelUse,
pub current_target: CodeOffset,
pub addend: i64,
}
impl PatchSite {
pub const fn to_patchable(self) -> PatchableSite {
unsafe { PatchableSite::new(self.offset, self.kind, self.addend) }
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PatchCatalog {
arch: Arch,
blocks: SmallVec<[PatchBlock; 4]>,
sites: SmallVec<[PatchSite; 8]>,
}
impl PatchCatalog {
pub(crate) fn with_parts(
arch: Arch,
blocks: SmallVec<[PatchBlock; 4]>,
sites: SmallVec<[PatchSite; 8]>,
) -> Self {
Self {
arch,
blocks,
sites,
}
}
pub fn arch(&self) -> Arch {
self.arch
}
pub fn is_empty(&self) -> bool {
self.blocks.is_empty() && self.sites.is_empty()
}
pub fn blocks(&self) -> &[PatchBlock] {
&self.blocks
}
pub fn sites(&self) -> &[PatchSite] {
&self.sites
}
pub fn block(&self, id: PatchBlockId) -> Option<&PatchBlock> {
self.blocks.get(id.index())
}
pub fn site(&self, id: PatchSiteId) -> Option<&PatchSite> {
self.sites.get(id.index())
}
pub fn site_mut(&mut self, id: PatchSiteId) -> Option<&mut PatchSite> {
self.sites.get_mut(id.index())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct PatchableSite {
offset: CodeOffset,
kind: LabelUse,
addend: i64,
}
impl PatchableSite {
pub const unsafe fn new(offset: CodeOffset, kind: LabelUse, addend: i64) -> Self {
Self {
offset,
kind,
addend,
}
}
pub const fn offset(self) -> CodeOffset {
self.offset
}
pub const fn kind(self) -> LabelUse {
self.kind
}
pub const fn addend(self) -> i64 {
self.addend
}
pub unsafe fn retarget(
self,
bytes: &mut [u8],
target_offset: CodeOffset,
) -> Result<(), AsmError> {
if !self.kind.can_reach(self.offset, target_offset) {
return Err(AsmError::TooLarge);
}
let patch_size = self.kind.patch_size();
let patch_end = (self.offset as usize)
.checked_add(patch_size)
.ok_or(AsmError::InvalidState)?;
if patch_end > bytes.len() {
return Err(AsmError::InvalidState);
}
let patch_slice = &mut bytes[self.offset as usize..patch_end];
self.kind
.patch_with_addend(patch_slice, self.offset, target_offset, self.addend);
Ok(())
}
#[cfg(feature = "jit")]
pub unsafe fn retarget_span(
self,
jit_allocator: &mut JitAllocator,
span: &mut Span,
target_offset: CodeOffset,
) -> Result<(), AsmError> {
if !self.kind.can_reach(self.offset, target_offset) {
return Err(AsmError::TooLarge);
}
let patch_size = self.kind.patch_size();
let patch_end = (self.offset as usize)
.checked_add(patch_size)
.ok_or(AsmError::InvalidState)?;
if patch_end > span.size() {
return Err(AsmError::InvalidState);
}
unsafe {
jit_allocator.write(span, |span| {
let patch_ptr = span.rw().add(self.offset as usize);
let patch_slice = core::slice::from_raw_parts_mut(patch_ptr, patch_size);
self.kind.patch_with_addend(
patch_slice,
self.offset,
target_offset,
self.addend,
);
})?;
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct PatchableBlock {
offset: CodeOffset,
size: CodeOffset,
arch: Arch,
}
impl PatchableBlock {
pub const unsafe fn new(offset: CodeOffset, size: CodeOffset, arch: Arch) -> Self {
Self {
offset,
size,
arch,
}
}
pub const fn offset(self) -> CodeOffset {
self.offset
}
pub const fn size(self) -> CodeOffset {
self.size
}
pub const fn arch(self) -> Arch {
self.arch
}
pub unsafe fn rewrite(self, bytes: &mut [u8], new_bytes: &[u8]) -> Result<(), AsmError> {
if new_bytes.len() > self.size as usize {
return Err(AsmError::TooLarge);
}
let instruction_alignment = minimum_patch_alignment(self.arch) as usize;
if new_bytes.len() % instruction_alignment != 0 {
return Err(AsmError::InvalidArgument);
}
let block_end = (self.offset as usize)
.checked_add(self.size as usize)
.ok_or(AsmError::InvalidState)?;
if block_end > bytes.len() {
return Err(AsmError::InvalidState);
}
let block = &mut bytes[self.offset as usize..block_end];
block[..new_bytes.len()].copy_from_slice(new_bytes);
fill_with_nops(self.arch, &mut block[new_bytes.len()..])?;
Ok(())
}
pub unsafe fn repatch_u32(self, bytes: &mut [u8], value: u32) -> Result<(), AsmError> {
if self.size != 4 {
return Err(AsmError::InvalidArgument);
}
unsafe { self.rewrite(bytes, &value.to_le_bytes()) }
}
pub unsafe fn repatch_u64(self, bytes: &mut [u8], value: u64) -> Result<(), AsmError> {
if self.size != 8 {
return Err(AsmError::InvalidArgument);
}
unsafe { self.rewrite(bytes, &value.to_le_bytes()) }
}
#[cfg(feature = "jit")]
pub unsafe fn rewrite_span(
self,
jit_allocator: &mut JitAllocator,
span: &mut Span,
new_bytes: &[u8],
) -> Result<(), AsmError> {
if new_bytes.len() > self.size as usize {
return Err(AsmError::TooLarge);
}
let instruction_alignment = minimum_patch_alignment(self.arch) as usize;
if new_bytes.len() % instruction_alignment != 0 {
return Err(AsmError::InvalidArgument);
}
let block_end = (self.offset as usize)
.checked_add(self.size as usize)
.ok_or(AsmError::InvalidState)?;
if block_end > span.size() {
return Err(AsmError::InvalidState);
}
let mut fill_result = Ok(());
unsafe {
jit_allocator.write(span, |span| {
let block_ptr = span.rw().add(self.offset as usize);
block_ptr.copy_from_nonoverlapping(new_bytes.as_ptr(), new_bytes.len());
let tail = core::slice::from_raw_parts_mut(
block_ptr.add(new_bytes.len()),
self.size as usize - new_bytes.len(),
);
fill_result = fill_with_nops(self.arch, tail);
})?;
}
fill_result
}
#[cfg(feature = "jit")]
pub unsafe fn repatch_u32_span(
self,
jit_allocator: &mut JitAllocator,
span: &mut Span,
value: u32,
) -> Result<(), AsmError> {
if self.size != 4 {
return Err(AsmError::InvalidArgument);
}
unsafe { self.rewrite_span(jit_allocator, span, &value.to_le_bytes()) }
}
#[cfg(feature = "jit")]
pub unsafe fn repatch_u64_span(
self,
jit_allocator: &mut JitAllocator,
span: &mut Span,
value: u64,
) -> Result<(), AsmError> {
if self.size != 8 {
return Err(AsmError::InvalidArgument);
}
unsafe { self.rewrite_span(jit_allocator, span, &value.to_le_bytes()) }
}
}
pub fn minimum_patch_alignment(arch: Arch) -> CodeOffset {
match arch {
Arch::AArch64 | Arch::RISCV32 | Arch::RISCV64 => 4,
_ => 1,
}
}
pub fn fill_with_nops(arch: Arch, buffer: &mut [u8]) -> Result<(), AsmError> {
let pattern: &[u8] = match arch {
Arch::X86 | Arch::X64 => &[0x90],
Arch::AArch64 => &[0x1f, 0x20, 0x03, 0xd5],
Arch::RISCV32 | Arch::RISCV64 => &[0x13, 0x00, 0x00, 0x00],
_ => return Err(AsmError::InvalidArgument),
};
if pattern.len() > 1 && buffer.len() % pattern.len() != 0 {
return Err(AsmError::InvalidArgument);
}
for chunk in buffer.chunks_mut(pattern.len()) {
chunk.copy_from_slice(pattern);
}
Ok(())
}
impl CodeBufferFinalized {
pub fn patch_catalog(&self) -> &PatchCatalog {
&self.patch_catalog
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn retarget_rejects_out_of_range_slice() {
let site = unsafe { PatchableSite::new(62, LabelUse::X86JmpRel32, 0) };
let mut bytes = [0u8; 64];
assert_eq!(
unsafe { site.retarget(&mut bytes, 0) }.unwrap_err(),
AsmError::InvalidState
);
}
#[test]
fn rewrite_rejects_misaligned_payload_for_a64() {
let block = unsafe { PatchableBlock::new(0, 4, Arch::AArch64) };
let mut bytes = [0u8; 4];
assert_eq!(
unsafe { block.rewrite(&mut bytes, &[0]) }.unwrap_err(),
AsmError::InvalidArgument
);
}
#[test]
fn repatch_u32_round_trips() {
let block = unsafe { PatchableBlock::new(1, 4, Arch::X64) };
let mut bytes = [0xB8, 0, 0, 0, 0];
unsafe { block.repatch_u32(&mut bytes, 0x11223344).unwrap() };
assert_eq!(&bytes[1..], &[0x44, 0x33, 0x22, 0x11]);
}
#[cfg(feature = "jit")]
#[test]
fn span_patch_rejects_ranges_outside_span() {
use crate::core::jit_allocator::JitAllocatorOptions;
let mut allocator = JitAllocator::new(JitAllocatorOptions::default());
let mut span = allocator.alloc(64).unwrap();
let span_size = span.size() as CodeOffset;
let block = unsafe { PatchableBlock::new(span_size, 1, Arch::X64) };
assert_eq!(
unsafe { block.rewrite_span(&mut allocator, &mut span, &[0x90]) }.unwrap_err(),
AsmError::InvalidState
);
let site = unsafe { PatchableSite::new(span_size - 3, LabelUse::X86JmpRel32, 0) };
assert_eq!(
unsafe { site.retarget_span(&mut allocator, &mut span, 0) }.unwrap_err(),
AsmError::InvalidState
);
}
}