use bitfields::bitfield;
#[bitfield(u64)]
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct Relocation {
offset: u16,
#[bits(7)]
encoding: RelocationEncoding,
relative: bool,
size: u8,
#[bits(12)]
addend: i16,
#[bits(4)]
patch_kind: PatchKind,
patch_id: u16,
}
#[derive(Copy, Clone)]
#[repr(u8)]
pub enum RelocationEncoding {
Invalid = 0,
Generic,
X86Signed,
Unknown,
}
impl RelocationEncoding {
const fn from_bits(bits: u8) -> Self {
if bits >= Self::Unknown as u8 {
Self::Invalid
} else {
unsafe { core::mem::transmute::<u8, Self>(bits) }
}
}
const fn into_bits(self) -> u8 {
self as u8
}
}
#[derive(Copy, Clone)]
#[repr(u8)]
pub enum PatchKind {
Hole = 0,
Stack,
Target,
Unknown,
}
impl PatchKind {
const fn from_bits(bits: u8) -> Self {
if bits >= Self::Unknown as u8 {
Self::Unknown
} else {
unsafe { core::mem::transmute::<u8, Self>(bits) }
}
}
const fn into_bits(self) -> u8 {
self as u8
}
}
pub enum PatchInfo {
Hole(u16),
Stack(u16),
Target(u16),
}
impl Relocation {
pub fn is_invalid(&self) -> bool {
matches!(self.encoding(), RelocationEncoding::Invalid)
}
pub fn patch_info(&self) -> Option<PatchInfo> {
Some(match self.patch_kind() {
PatchKind::Hole => PatchInfo::Hole(self.patch_id()),
PatchKind::Stack => PatchInfo::Stack(self.patch_id()),
PatchKind::Target => PatchInfo::Target(self.patch_id()),
_ => return None,
})
}
pub fn supports_value(&self, value: usize) -> bool {
if self.relative() {
return false;
}
match self.encoding() {
RelocationEncoding::Generic => fits_unsigned(value, self.size()),
RelocationEncoding::X86Signed => fits_signed(value as isize, self.size()),
_ => false,
}
}
pub fn apply_raw(&self, dest: &mut [u8], value: usize) {
let value = value.wrapping_add_signed(self.addend() as isize);
let offset = self.offset();
let size = self.size();
match self.encoding() {
RelocationEncoding::Generic => {
let size = (size / 8) as usize;
dest[offset as usize..][..size].copy_from_slice(&value.to_le_bytes()[..size]);
}
RelocationEncoding::X86Signed => {
let size = (size / 8) as usize;
dest[offset as usize..][..size].copy_from_slice(&value.to_le_bytes()[..size]);
}
_ => unreachable!(),
}
}
}
fn fits_unsigned(value: usize, bits: u8) -> bool {
match bits {
0 => value == 0,
bits if bits as u32 >= usize::BITS => true,
bits => value < (1usize << bits),
}
}
fn fits_signed(value: isize, bits: u8) -> bool {
match bits {
0 => value == 0,
bits if bits as u32 >= isize::BITS => true,
bits => {
let shift = isize::BITS - bits as u32;
(value << shift >> shift) == value
}
}
}
#[derive(Copy, Clone)]
pub enum JumpTarget {
Next,
Target(u16),
}
#[derive(Copy, Clone)]
pub enum DelayedTarget {
Block(u16),
Constant(u16),
Next,
}
#[derive(Clone)]
pub struct DelayedRelocation {
offset: usize,
relocation: Relocation,
target: DelayedTarget,
}
impl DelayedRelocation {
pub fn try_apply(
dest: &mut [u8],
offset: usize,
relocation: Relocation,
stack_vars: &[usize],
holes: &[usize],
jumps: &[JumpTarget],
) -> Option<Self> {
let mut value = match relocation.patch_info().unwrap() {
PatchInfo::Hole(i) => holes[i as usize],
PatchInfo::Stack(i) => stack_vars[i as usize],
PatchInfo::Target(i) => {
return Some(DelayedRelocation {
offset,
relocation,
target: match jumps[i as usize] {
JumpTarget::Next => DelayedTarget::Next,
JumpTarget::Target(target) => DelayedTarget::Block(target),
},
});
}
};
if relocation.relative() {
value -= offset + relocation.offset() as usize
}
relocation.apply_raw(&mut dest[offset..], value);
None
}
pub fn constant(offset: usize, relocation: Relocation, constant: u16) -> Self {
Self {
offset,
relocation,
target: DelayedTarget::Constant(constant),
}
}
pub fn next(offset: usize, relocation: Relocation) -> Self {
Self {
offset,
relocation,
target: DelayedTarget::Next,
}
}
pub fn target(&self) -> DelayedTarget {
self.target
}
pub fn offset(&self) -> usize {
self.offset
}
pub fn location(&self) -> usize {
self.offset + self.relocation.offset() as usize
}
pub fn resolve(&self, base: usize, value: usize) -> usize {
if self.relocation.relative() {
value.wrapping_sub(base + self.location())
} else {
value
}
}
pub fn apply(&self, dest: &mut [u8], value: usize) {
self.relocation.apply_raw(&mut dest[self.offset..], value);
}
}