fern_jit/
executable_memory.rs

1// Copyright (C) 2024 Ethan Uppal and Utku Melemetci. All rights reserved.
2
3use std::{io, ptr, slice};
4
5/// Memory mapped with execute permissions.
6pub struct ExecutableMemory {
7    start: *mut u8,
8    length: usize,
9}
10
11impl ExecutableMemory {
12    /// Maps executable memory of size at least `length`.
13    pub fn of_size(length: usize) -> io::Result<ExecutableMemory> {
14        unsafe {
15            let page_size = {
16                let result = libc::sysconf(libc::_SC_PAGESIZE);
17                if result == -1 {
18                    Err(io::Error::last_os_error())
19                } else {
20                    Ok(result as usize)
21                }
22            }?;
23            let aligned_length = (length + page_size - 1) & !(page_size - 1);
24
25            #[cfg(target_os = "macos")]
26            let flags = libc::MAP_JIT | libc::MAP_ANON | libc::MAP_PRIVATE;
27
28            #[cfg(not(target_os = "macos"))]
29            let flags = libc::MAP_ANON | libc::MAP_PRIVATE;
30
31            let start = libc::mmap(
32                ptr::null_mut(),
33                aligned_length,
34                libc::PROT_READ | libc::PROT_WRITE | libc::PROT_EXEC,
35                flags,
36                -1,
37                0,
38            );
39            if start == libc::MAP_FAILED {
40                return Err(io::Error::last_os_error());
41            }
42            // doesn't work on apple silicon :(
43            // if libc::mprotect(
44            //     start,
45            //     aligned_length,
46            //     libc::PROT_READ | libc::PROT_WRITE | libc::PROT_EXEC,
47            // ) == -1
48            // {
49            //     return Err(io::Error::last_os_error());
50            // }
51            Ok(ExecutableMemory {
52                start: start as *mut u8,
53                length: aligned_length,
54            })
55        }
56    }
57
58    pub fn start(&mut self) -> *mut u8 {
59        self.start
60    }
61
62    pub fn length(&self) -> usize {
63        self.length
64    }
65
66    pub fn as_slice_mut(&mut self) -> &mut [u8] {
67        unsafe { slice::from_raw_parts_mut(self.start, self.length) }
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use std::mem;
74
75    use super::ExecutableMemory;
76
77    #[cfg(target_arch = "x86_64")]
78    #[test]
79    fn simple_func() {
80        let mut jit_mem = ExecutableMemory::of_size(1024 * 1024)
81            .expect("failed to map memory");
82
83        // mov eax, 42
84        // ret
85        for (i, byte) in [0xB8, 0x2A, 0x00, 0x00, 0x00, 0xC3].iter().enumerate()
86        {
87            jit_mem.as_slice_mut()[i] = *byte;
88        }
89
90        unsafe {
91            let ret_42: unsafe fn() -> i32 = mem::transmute(jit_mem.start());
92            assert_eq!(42, ret_42());
93        };
94    }
95}