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}