juicebox_asm/rt.rs
1// SPDX-License-Identifier: MIT
2//
3// Copyright (c) 2023, Johannes Stoelp <dev@memzero.de>
4
5//! Simple `mmap`ed runtime.
6//!
7//! This runtime supports adding code to executable pages and turn the added code into user
8//! specified function pointer.
9
10#[cfg(not(target_os = "linux"))]
11compile_error!("This runtime is only supported on linux");
12
13mod perf {
14 use std::fs;
15 use std::io::Write;
16
17 /// Provide support for the simple [perf jit interface][perf-jit].
18 ///
19 /// This allows a simple (static) jit runtime to generate meta data describing the generated
20 /// functions, which is used during post-processing by `perf report` to symbolize addresses
21 /// captured while executing jitted code.
22 ///
23 /// By the nature of this format, this can not be used for dynamic jit runtimes, which reuses
24 /// memory which previously contained jitted code.
25 ///
26 /// [perf-jit]: https://elixir.bootlin.com/linux/v6.6.6/source/tools/perf/Documentation/jit-interface.txt
27 pub(super) struct PerfMap {
28 file: std::fs::File,
29 }
30
31 impl PerfMap {
32 /// Create an empty perf map file.
33 pub(super) fn new() -> Self {
34 let name = format!("/tmp/perf-{}.map", unsafe { libc::getpid() });
35 let file = fs::OpenOptions::new()
36 .truncate(true)
37 .create(true)
38 .write(true)
39 .open(&name)
40 .unwrap_or_else(|_| panic!("Failed to open perf map file {}", name));
41
42 PerfMap { file }
43 }
44
45 /// Add an entry to the perf map file.
46 pub(super) fn add_entry(&mut self, start: usize, len: usize) {
47 // Each line has the following format, fields separated with spaces:
48 // START SIZE NAME
49 //
50 // START and SIZE are hex numbers without 0x.
51 // NAME is the rest of the line, so it could contain special characters.
52 writeln!(self.file, "{:x} {:x} jitfn_{:x}", start, len, start)
53 .expect("Failed to write PerfMap entry");
54 }
55 }
56}
57
58/// A simple `mmap`ed runtime with executable pages.
59pub struct Runtime {
60 buf: *mut u8,
61 len: usize,
62 idx: usize,
63 perf: Option<perf::PerfMap>,
64}
65
66impl Runtime {
67 /// Create a new [Runtime] with a code buffer of 1 page.
68 ///
69 /// # Panics
70 ///
71 /// Panics if the `mmap` call fails.
72 pub fn new() -> Runtime {
73 Runtime::with_capacity(1)
74 }
75
76 /// Create a new [Runtime] with a code buffer of NPAGES pages.
77 ///
78 /// # Panics
79 ///
80 /// Panics if the length calculation overflows or the `mmap` call fails.
81 pub fn with_capacity(npages: usize) -> Runtime {
82 // Allocate a single page.
83 let len = npages.checked_mul(4096).unwrap();
84 let buf = unsafe {
85 libc::mmap(
86 std::ptr::null_mut(),
87 len,
88 libc::PROT_NONE,
89 libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
90 0, /* fd */
91 0, /* off */
92 ) as *mut u8
93 };
94 assert_ne!(
95 buf.cast(),
96 libc::MAP_FAILED,
97 "Failed to mmap runtime code page"
98 );
99
100 Runtime {
101 buf,
102 len,
103 idx: 0,
104 perf: None,
105 }
106 }
107
108 /// Create a new [Runtime] which also generates static perf metat data.
109 ///
110 /// For each function added to the [Runtime], an entry will be generated in the
111 /// `/tmp/perf-<PID>.map` file, which `perf report` uses to symbolicate unknown addresses.
112 /// This is applicable for static runtimes only.
113 ///
114 /// # Panics
115 ///
116 /// Panics if the `mmap` call fails.
117 pub fn with_profile() -> Runtime {
118 let mut rt = Runtime::new();
119 rt.perf = Some(perf::PerfMap::new());
120 rt
121 }
122
123 /// Add the block of `code` to the runtime and a get function pointer of type `F`.
124 ///
125 /// # Panics
126 ///
127 /// Panics if the `code` does not fit on the `mmap`ed pages or is empty.
128 ///
129 /// # Safety
130 ///
131 /// The code added must fulfill the ABI of the specified function `F` and the returned function
132 /// pointer is only valid until the [`Runtime`] is dropped.
133 ///
134 /// # Examples
135 ///
136 /// ```
137 /// let mut rt = juicebox_asm::Runtime::new();
138 ///
139 /// let code = [ 0x90 /* nop */, 0xc3 /* ret */ ];
140 /// let nop = unsafe { rt.add_code::<extern "C" fn()>(&code) };
141 ///
142 /// nop();
143 /// ```
144 pub unsafe fn add_code<F>(&mut self, code: impl AsRef<[u8]>) -> F {
145 // Get pointer to start of next free byte.
146 assert!(self.idx < self.len, "Runtime code page full");
147 let fn_start = self.buf.add(self.idx);
148
149 // Copy over code.
150 let code = code.as_ref();
151 assert!(!code.is_empty(), "Adding empty code not supported");
152 assert!(
153 code.len() <= (self.len - self.idx),
154 "Code does not fit on the runtime code page"
155 );
156 self.unprotect();
157 unsafe { std::ptr::copy_nonoverlapping(code.as_ptr(), fn_start, code.len()) };
158 self.protect();
159
160 // Increment index to next free byte.
161 self.idx += code.len();
162
163 // Add perf map entry.
164 if let Some(map) = &mut self.perf {
165 map.add_entry(fn_start as usize, code.len());
166 }
167
168 // Return function to newly added code.
169 unsafe { Self::as_fn::<F>(fn_start) }
170 }
171
172 /// Disassemble the code currently added to the runtime, using
173 /// [`ndisasm`](https://nasm.us/index.php) and print it to _stdout_. If
174 /// `ndisasm` is not available on the system this prints a warning and
175 /// becomes a nop.
176 ///
177 /// # Panics
178 ///
179 /// Panics if anything goes wrong with spawning, writing to or reading from
180 /// the `ndisasm` child process.
181 pub fn disasm(&self) {
182 assert!(self.idx <= self.len);
183 crate::disasm::disasm(unsafe { core::slice::from_raw_parts(self.buf, self.idx) });
184 }
185
186 /// Reinterpret the block of code pointed to by `fn_start` as `F`.
187 #[inline]
188 unsafe fn as_fn<F>(fn_start: *mut u8) -> F {
189 unsafe { std::mem::transmute_copy(&fn_start) }
190 }
191
192 /// Add write protection the underlying code page(s).
193 ///
194 /// # Panics
195 ///
196 /// Panics if the `mprotect` call fails.
197 fn protect(&mut self) {
198 unsafe {
199 // Remove write permissions from code page and allow to read-execute from it.
200 let ret = libc::mprotect(self.buf.cast(), self.len, libc::PROT_READ | libc::PROT_EXEC);
201 assert_eq!(ret, 0, "Failed to RX mprotect runtime code page");
202 }
203 }
204
205 /// Remove write protection the underlying code page(s).
206 ///
207 /// # Panics
208 ///
209 /// Panics if the `mprotect` call fails.
210 fn unprotect(&mut self) {
211 unsafe {
212 // Add write permissions to code page.
213 let ret = libc::mprotect(self.buf.cast(), self.len, libc::PROT_WRITE);
214 assert_eq!(ret, 0, "Failed to W mprotect runtime code page");
215 }
216 }
217}
218
219impl Drop for Runtime {
220 /// Unmaps the code page. This invalidates all the function pointer returned by
221 /// [`Runtime::add_code`].
222 fn drop(&mut self) {
223 unsafe {
224 let ret = libc::munmap(self.buf.cast(), self.len);
225 assert_eq!(ret, 0, "Failed to munmap runtime");
226 }
227 }
228}
229
230#[cfg(test)]
231mod test {
232 use super::*;
233
234 #[test]
235 fn test_code_max_size() {
236 let mut rt = Runtime::new();
237 let code = [0u8; 4096];
238 unsafe {
239 rt.add_code::<extern "C" fn()>(code);
240 }
241 }
242
243 #[test]
244 #[should_panic]
245 fn test_code_max_size_plus_1() {
246 let mut rt = Runtime::new();
247 let code = [0u8; 4097];
248 unsafe {
249 rt.add_code::<extern "C" fn()>(code);
250 }
251 }
252
253 #[test]
254 #[should_panic]
255 fn test_code_max_size_plus_1_2() {
256 let mut rt = Runtime::new();
257 let code = [0u8; 4096];
258 unsafe {
259 rt.add_code::<extern "C" fn()>(code);
260 }
261
262 let code = [0u8; 1];
263 unsafe {
264 rt.add_code::<extern "C" fn()>(code);
265 }
266 }
267
268 #[test]
269 fn test_capacity_code_max_size() {
270 let mut rt = Runtime::with_capacity(2);
271 let code = [0u8; 2 * 4096];
272 unsafe {
273 rt.add_code::<extern "C" fn()>(code);
274 }
275 }
276
277 #[test]
278 #[should_panic]
279 fn test_capacity_code_max_size_plus_1() {
280 let mut rt = Runtime::with_capacity(2);
281 let code = [0u8; 2 * 4096 + 1];
282 unsafe {
283 rt.add_code::<extern "C" fn()>(code);
284 }
285 }
286
287 #[test]
288 #[should_panic]
289 fn test_capacity_code_max_size_plus_1_2() {
290 let mut rt = Runtime::with_capacity(2);
291 let code = [0u8; 2 * 4096];
292 unsafe {
293 rt.add_code::<extern "C" fn()>(code);
294 }
295
296 let code = [0u8; 1];
297 unsafe {
298 rt.add_code::<extern "C" fn()>(code);
299 }
300 }
301
302 #[test]
303 #[should_panic]
304 fn test_empty_code() {
305 let mut rt = Runtime::new();
306 let code = [0u8; 0];
307 unsafe {
308 rt.add_code::<extern "C" fn()>(code);
309 }
310 }
311}