dynasmrt/
relocations.rs

1//! This module defines the `Relocation` trait and several utilities for implementing relocations.
2
3use byteorder::{ByteOrder, LittleEndian};
4use std::fmt::Debug;
5
6use std::convert::TryFrom;
7
8/// Error returned when encoding a relocation failed
9#[derive(Debug)]
10pub struct ImpossibleRelocation { }
11
12
13/// Used to inform assemblers on how to implement relocations for each architecture.
14/// When implementing a new architecture, one simply has to implement this trait for
15/// the architecture's relocation definition.
16pub trait Relocation {
17    /// construct this relocation from an encoded representation.
18    fn from_encoding(encoding: u8) -> Self;
19    /// construct this relocation from a simple size. This is used to implement relocations in directives and literal pools.
20    fn from_size(kind: RelocationKind, size: RelocationSize) -> Self;
21    /// The size of the slice of bytes affected by this relocation
22    fn size(&self) -> usize;
23    /// Write a value into a buffer of size `self.size()` in the format of this relocation.
24    /// Any bits not part of the relocation should be preserved.
25    fn write_value(&self, buf: &mut [u8], value: isize) -> Result<(), ImpossibleRelocation>;
26    /// Read a value from a buffer of size `self.size()` in the format of this relocation.
27    fn read_value(&self, buf: &[u8]) -> isize;
28    /// Specifies what kind of relocation this relocation instance is.
29    fn kind(&self) -> RelocationKind;
30    /// Specifies the default page size on this platform.
31    fn page_size() -> usize;
32}
33
34/// Trait that just handles the internal decoding of architecture-specific relocation encodings
35/// This is useful so the relocation type machinery can be generic over it.
36pub trait ArchitectureRelocationEncoding : Clone + Copy + Debug {
37    /// decodes this custom relocation encoding from the bitfield in the relocation code
38    fn decode(code: u8) -> Self;
39}
40
41
42/// Enum that specifies if/how relocations should be adapted if the assembling buffer is moved
43#[derive(Clone, Copy, Debug)]
44pub enum RelocationKind {
45    /// A simple, PC-relative relocation. These can be encoded once and do not need
46    /// to be adjusted when the executable buffer is moved.
47    Relative,
48    /// An absolute relocation to a relative address,
49    /// i.e. trying to put the address of a dynasm x86 function in a register
50    /// This means adjustment is necessary when the executable buffer is moved
51    AbsToRel,
52    /// A relative relocation to an absolute address,
53    /// i.e. trying to call a Rust function with a dynasm x86 call.
54    /// This means adjustment is necessary when the executable buffer is moved
55    RelToAbs,
56    /// An absolute relocation to an absolute address
57    /// This isn't particularly useful, but the user can specify it sometimes
58    Absolute
59}
60
61/// Enum that specifies how a certain relocation is encoded.
62#[derive(Clone, Copy, Debug)]
63pub enum RelocationEncoding<A: ArchitectureRelocationEncoding> {
64    /// This relocation is just some bytes of data
65    Simple(RelocationSize),
66    /// This relocation is complex and requires architecture-specific decoding logic
67    ArchSpecific(A)
68}
69
70/// `RelocationType` contains all information needed to describe how a relocation should be
71/// performed by the runtime
72#[derive(Clone, Copy, Debug)]
73pub struct RelocationType<A: ArchitectureRelocationEncoding> {
74    /// How this relocation should be adapted if the buffer gets moved
75    pub kind: RelocationKind,
76    /// The way this relocation is to be encoded
77    pub encoding: RelocationEncoding<A>
78}
79
80impl<A: ArchitectureRelocationEncoding> RelocationType<A> {
81    /// decode the packed representation emitted by the plugin
82    pub fn decode(code: u8) -> Self {
83        let kind = match code >> 6 {
84            0 => RelocationKind::Relative,
85            1 => RelocationKind::AbsToRel,
86            2 => RelocationKind::RelToAbs,
87            3 => RelocationKind::Absolute,
88            _ => unreachable!()
89        };
90
91        let encoding = match code & 0x3F {
92            0 => RelocationEncoding::Simple(RelocationSize::Byte),
93            1 => RelocationEncoding::Simple(RelocationSize::Word),
94            2 => RelocationEncoding::Simple(RelocationSize::DWord),
95            3 => RelocationEncoding::Simple(RelocationSize::QWord),
96            c => RelocationEncoding::ArchSpecific(A::decode(c - 4))
97        };
98        RelocationType {
99            kind,
100            encoding
101        }
102    }
103
104    /// manually create a `RelocationType` for a simple size-based relocation
105    pub fn from_size(kind: RelocationKind, size: RelocationSize) -> Self {
106        RelocationType {
107            kind,
108            encoding: RelocationEncoding::Simple(size)
109        }
110    }
111}
112
113/// A simple size-based relocation descriptor for relocations in data directives.
114/// Can be converted to a relocation for any kind of architecture using `Relocation::from_size`.
115#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
116pub enum RelocationSize {
117    /// A byte-sized relocation
118    Byte = 1,
119    /// A two-byte relocation
120    Word = 2,
121    /// A four-byte sized relocation
122    DWord = 4,
123    /// An 8-byte sized relocation
124    QWord = 8,
125}
126
127impl RelocationSize {
128    /// The size of this size-based relocation in bytes
129    pub fn size(&self) -> usize {
130        *self as usize
131    }
132
133    /// Pack `value` into this relocation size and format it into `buf`
134    pub fn write_value(&self, buf: &mut [u8], value: isize) -> Result<(), ImpossibleRelocation> {
135        match self {
136            RelocationSize::Byte => buf[0] =
137                i8::try_from(value).map_err(|_| ImpossibleRelocation { } )?
138            as u8,
139            RelocationSize::Word => LittleEndian::write_i16(buf,
140                i16::try_from(value).map_err(|_| ImpossibleRelocation { } )?
141            ),
142            RelocationSize::DWord => LittleEndian::write_i32(buf,
143                i32::try_from(value).map_err(|_| ImpossibleRelocation { } )?
144            ),
145            RelocationSize::QWord => LittleEndian::write_i64(buf,
146                i64::try_from(value).map_err(|_| ImpossibleRelocation { } )?
147            ),
148        }
149        Ok(())
150    }
151
152    /// Extract a value of this size from `buf`
153    pub fn read_value(&self, buf: &[u8]) -> isize {
154        match self {
155            RelocationSize::Byte => buf[0] as i8 as isize,
156            RelocationSize::Word => LittleEndian::read_i16(buf) as isize,
157            RelocationSize::DWord => LittleEndian::read_i32(buf) as isize,
158            RelocationSize::QWord => LittleEndian::read_i64(buf) as isize,
159        }
160    }
161}
162
163#[derive(Copy, Clone, Debug)]
164enum NoComplexRelocationEncodings {}
165
166impl ArchitectureRelocationEncoding for NoComplexRelocationEncodings {
167    fn decode(code: u8) -> Self {
168        panic!("Invalid complex relocation code {code} given for the current architecture");
169    }
170}
171
172/// A simple relocation type for relocations that do not need complex bitpacking.
173#[derive(Debug, Clone, Copy)]
174pub struct SimpleRelocation(RelocationType<NoComplexRelocationEncodings>);
175
176/// A relocation that has no architecture-specific encoding/decoding logic
177impl Relocation for SimpleRelocation {
178    fn from_encoding(encoding: u8) -> Self {
179        SimpleRelocation(RelocationType::decode(encoding))
180    }
181
182    fn from_size(kind: RelocationKind, size: RelocationSize) -> Self {
183        SimpleRelocation(RelocationType::from_size(kind, size))
184    }
185
186    fn size(&self) -> usize {
187        match self.0.encoding {
188            RelocationEncoding::Simple(s) => s.size(),
189            _ => unreachable!()
190        }
191    }
192
193    fn write_value(&self, buf: &mut [u8], value: isize) -> Result<(), ImpossibleRelocation> {
194        match self.0.encoding {
195            RelocationEncoding::Simple(s) => s.write_value(buf, value),
196            _ => unreachable!()
197        }
198    }
199
200    fn read_value(&self, buf: &[u8]) -> isize {
201        match self.0.encoding {
202            RelocationEncoding::Simple(s) => s.read_value(buf),
203            _ => unreachable!()
204        }
205    }
206
207    fn kind(&self) -> RelocationKind {
208        self.0.kind
209    }
210
211    fn page_size() -> usize {
212        4096
213    }
214}
215
216pub(crate) fn fits_signed_bitfield(value: i64, bits: u8) -> bool {
217    if bits >= 64 {
218        return true;
219    }
220
221    let half = 1i64 << (bits - 1);
222    value < half && value >= -half
223}