Skip to main content

imping_runtime/
global.rs

1//! Typed global variables.
2
3use crate::module::Module;
4use crate::plain::Plain;
5use core::fmt;
6use core::marker::PhantomData;
7
8/// A global variable of type `T` at an address in a [`Module`].
9///
10/// Generated bindings declare one per IR global; it holds only the address,
11/// one per build when they were generated from several. When `T` is
12/// [`Plain`], [`read`](Self::read) and [`write`](Self::write) access it
13/// safely; otherwise use [`ptr`](Self::ptr).
14///
15/// # Data races
16///
17/// Each read or write is a single volatile access that doesn't synchronise
18/// with the target. If the target uses the global on another thread at the
19/// same time, a value larger than a machine word can be seen half-updated.
20///
21/// # Examples
22///
23/// ```no_run
24/// use imping_runtime::{Build, Global, Module};
25///
26/// static TARGET: Module = Module::new(
27///     "target.exe",
28///     &[Build { timestamp: 0x64b8f2a1, image_size: 0xa2b000 }],
29/// );
30/// static GRAVITY: Global<f32> = Global::new(&TARGET, 0x3a1200);
31///
32/// # fn main() -> imping_runtime::Result<()> {
33/// // SAFETY: this build of `target.exe` keeps an `f32` at RVA 0x3a1200.
34/// unsafe { TARGET.init() }?;
35/// let gravity = GRAVITY.read();
36/// GRAVITY.write(gravity / 2.0);
37/// # Ok(())
38/// # }
39/// ```
40pub struct Global<T> {
41    module: &'static Module,
42    address: Address,
43    _type: PhantomData<fn() -> T>,
44}
45
46/// Where a global is.
47#[derive(Clone, Copy)]
48enum Address {
49    /// At one RVA in every build.
50    One(u32),
51    /// Per labelled build, in the module's label order; `None` where that
52    /// build's IR lacks it.
53    PerBuild {
54        name: &'static str,
55        rvas: &'static [Option<u32>],
56    },
57}
58
59impl<T> Global<T> {
60    /// Describes the global at `rva`, an address relative to `module`'s base.
61    pub const fn new(module: &'static Module, rva: u32) -> Self {
62        Self {
63            module,
64            address: Address::One(rva),
65            _type: PhantomData,
66        }
67    }
68
69    /// Describes the global `name` with an address per labelled build of
70    /// `module`.
71    pub(crate) const fn per_build(
72        module: &'static Module,
73        name: &'static str,
74        rvas: &'static [Option<u32>],
75    ) -> Self {
76        Self {
77            module,
78            address: Address::PerBuild { name, rvas },
79            _type: PhantomData,
80        }
81    }
82
83    /// Returns a pointer to the global in this process.
84    ///
85    /// # Panics
86    ///
87    /// If the module's [`init`](Module::init) has not succeeded, or the loaded
88    /// build's IR lacks the global; [`try_ptr`](Self::try_ptr) checks first.
89    pub fn ptr(&self) -> *mut T {
90        if let Some(ptr) = self.try_ptr() {
91            return ptr;
92        }
93        match (self.module.base(), self.address) {
94            (Some(_), Address::PerBuild { name, .. }) => {
95                let build = self
96                    .module
97                    .build_index()
98                    .map_or("loaded", |index| self.module.label(index));
99                panic!(
100                    "imping: `{name}` isn't in the {build} IR of `{}`",
101                    self.module.name()
102                )
103            }
104            _ => panic!(
105                "imping: `{}` is not initialized; call its `init()` first",
106                self.module.name()
107            ),
108        }
109    }
110
111    /// Returns a pointer to the global in this process, or `None` if the
112    /// module's [`init`](Module::init) has not succeeded or the loaded build's
113    /// IR lacks the global.
114    ///
115    /// On a build none of the IRs is, after
116    /// [`init_any_build`](Module::init_any_build), it's the first build's
117    /// address that has the global, unchecked, as nothing can search for a
118    /// global.
119    pub fn try_ptr(&self) -> Option<*mut T> {
120        let base = self.module.base()?;
121        Some((base + self.used_rva()? as usize) as *mut T)
122    }
123
124    /// Returns the global's address relative to its module's base in the
125    /// loaded build, as its IR has it: `None` before `init`, where that build's
126    /// IR lacks the global, or on a build none of the IRs is.
127    ///
128    /// A global made with [`new`](Self::new) has one address, which this always
129    /// returns.
130    pub fn rva(&self) -> Option<u32> {
131        match self.address {
132            Address::One(rva) => Some(rva),
133            Address::PerBuild { rvas, .. } => {
134                let index = self.module.build_index()?;
135                rvas.get(index).copied().flatten()
136            }
137        }
138    }
139
140    /// The RVA [`try_ptr`](Self::try_ptr) uses: [`rva`](Self::rva), or on a
141    /// build none of the IRs is, the first build's that has the global.
142    fn used_rva(&self) -> Option<u32> {
143        match self.address {
144            Address::PerBuild { rvas, .. }
145                if self.module.base().is_some() && self.module.build_index().is_none() =>
146            {
147                rvas.iter().flatten().next().copied()
148            }
149            _ => self.rva(),
150        }
151    }
152
153    /// Returns the module the global is in.
154    pub const fn module(&self) -> &'static Module {
155        self.module
156    }
157}
158
159impl<T: Plain> Global<T> {
160    /// Reads the global.
161    ///
162    /// Doesn't synchronise with the target; see [`Global`] for data races.
163    ///
164    /// # Panics
165    ///
166    /// As [`ptr`](Self::ptr) does.
167    pub fn read(&self) -> T {
168        let ptr = self.ptr();
169        // SAFETY: `ptr` returned, so `init`'s caller promised a `T` lives here
170        // in the mapped image, and every bit pattern is a valid `T`.
171        unsafe {
172            if ptr.is_aligned() {
173                ptr.read_volatile()
174            } else {
175                ptr.read_unaligned()
176            }
177        }
178    }
179
180    /// Writes the global.
181    ///
182    /// Doesn't synchronise with the target; see [`Global`] for data races.
183    ///
184    /// # Panics
185    ///
186    /// As [`ptr`](Self::ptr) does.
187    pub fn write(&self, value: T) {
188        let ptr = self.ptr();
189        // SAFETY: as for `read`; any `T` may be stored there.
190        unsafe {
191            if ptr.is_aligned() {
192                ptr.write_volatile(value);
193            } else {
194                ptr.write_unaligned(value);
195            }
196        }
197    }
198}
199
200impl<T> fmt::Debug for Global<T> {
201    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202        let mut debug = f.debug_struct("Global");
203        debug.field("module", &self.module.name());
204        match self.address {
205            Address::One(rva) => debug.field("rva", &format_args!("{rva:#x}")),
206            Address::PerBuild { name, rvas } => debug.field("name", &name).field("rvas", &rvas),
207        };
208        debug.finish()
209    }
210}