use crate::loader::CodeMemory;
use crate::vm::Ctx;
use std::collections::BTreeMap;
use std::fmt;
use std::ptr::NonNull;
use std::sync::Mutex;
use std::{mem, slice};
lazy_static! {
static ref GET_CONTEXT: extern "C" fn () -> *const CallContext = {
static CODE: &'static [u8] = &[
0x48, 0x0f, 0x7e, 0xc0,
0xc3,
];
let mut mem = CodeMemory::new(4096);
mem[..CODE.len()].copy_from_slice(CODE);
mem.make_executable();
let ptr = mem.as_ptr();
mem::forget(mem);
unsafe {
mem::transmute(ptr)
}
};
static ref TRAMPOLINES: TrampBuffer = TrampBuffer::new(64 * 1048576);
}
struct TrampBuffer {
buffer: CodeMemory,
alloc: Mutex<AllocState>,
}
struct AllocState {
blocks: BTreeMap<usize, usize>,
}
impl TrampBuffer {
fn new(size: usize) -> TrampBuffer {
let mem = CodeMemory::new(size);
mem.make_writable_executable();
TrampBuffer {
buffer: mem,
alloc: Mutex::new(AllocState {
blocks: BTreeMap::new(),
}),
}
}
unsafe fn remove(&self, start: NonNull<u8>) {
let start = start.as_ptr() as usize - self.buffer.get_backing_ptr() as usize;
let mut alloc = self.alloc.lock().unwrap();
alloc
.blocks
.remove(&start)
.expect("TrampBuffer::remove(): Attempting to remove a non-existent allocation.");
}
fn insert(&self, buf: &[u8]) -> Option<NonNull<u8>> {
let mut assumed_start: usize = 0;
let mut alloc = self.alloc.lock().unwrap();
let mut found = false;
for (&start, &end) in &alloc.blocks {
if start - assumed_start < buf.len() {
assumed_start = end;
} else {
found = true;
break;
}
}
if !found {
if self.buffer.len() - assumed_start < buf.len() {
return None;
}
}
let start = assumed_start;
alloc.blocks.insert(start, start + buf.len());
let slice = unsafe {
std::slice::from_raw_parts_mut(
self.buffer.get_backing_ptr().offset(start as _),
buf.len(),
)
};
slice.copy_from_slice(buf);
Some(NonNull::new(slice.as_mut_ptr()).unwrap())
}
}
pub enum CallTarget {}
pub enum CallContext {}
pub enum Trampoline {}
pub struct TrampolineBufferBuilder {
code: Vec<u8>,
offsets: Vec<usize>,
}
pub struct TrampolineBuffer {
code: CodeMemory,
offsets: Vec<usize>,
}
fn value_to_bytes<T: Copy>(ptr: &T) -> &[u8] {
unsafe { slice::from_raw_parts(ptr as *const T as *const u8, mem::size_of::<T>()) }
}
pub fn get_context() -> *const CallContext {
GET_CONTEXT()
}
impl TrampolineBufferBuilder {
pub fn new() -> TrampolineBufferBuilder {
TrampolineBufferBuilder {
code: vec![],
offsets: vec![],
}
}
pub fn add_context_trampoline(
&mut self,
target: *const CallTarget,
context: *const CallContext,
) -> usize {
let idx = self.offsets.len();
self.offsets.push(self.code.len());
self.code.extend_from_slice(&[
0x48, 0xb8,
]);
self.code.extend_from_slice(value_to_bytes(&context));
self.code.extend_from_slice(&[
0x48, 0x0f, 0x6e, 0xc0,
]);
self.code.extend_from_slice(&[
0x48, 0xb8,
]);
self.code.extend_from_slice(value_to_bytes(&target));
self.code.extend_from_slice(&[
0xff, 0xe0,
]);
idx
}
pub fn add_context_rsp_state_preserving_trampoline(
&mut self,
target: unsafe extern "C" fn(&mut Ctx, *const CallContext, *const u64),
context: *const CallContext,
) -> usize {
let idx = self.offsets.len();
self.offsets.push(self.code.len());
self.code.extend_from_slice(&[
0x53,
0x41, 0x54,
0x41, 0x55,
0x41, 0x56,
0x41, 0x57,
]);
self.code.extend_from_slice(&[
0x48, 0xbe,
]);
self.code.extend_from_slice(value_to_bytes(&context));
self.code.extend_from_slice(&[
0x48, 0x89, 0xe2,
]);
self.code.extend_from_slice(&[
0x48, 0xb8,
]);
self.code.extend_from_slice(value_to_bytes(&target));
self.code.extend_from_slice(&[
0xff, 0xd0,
]);
self.code.extend_from_slice(&[
0x48, 0x81, 0xc4,
]);
self.code.extend_from_slice(value_to_bytes(&40i32));
self.code.extend_from_slice(&[
0xc3,
]);
idx
}
pub fn add_callinfo_trampoline(
&mut self,
target: unsafe extern "C" fn(*const CallContext, *const u64) -> u64,
context: *const CallContext,
num_params: u32,
) -> usize {
let idx = self.offsets.len();
self.offsets.push(self.code.len());
let mut stack_offset: u32 = num_params.checked_mul(8).unwrap();
if stack_offset % 16 == 0 {
stack_offset += 8;
}
self.code.extend_from_slice(&[0x48, 0x81, 0xec]);
self.code.extend_from_slice(value_to_bytes(&stack_offset));
for i in 0..num_params {
match i {
0..=5 => {
let prefix: &[u8] = match i {
0 => &[0x48, 0x89, 0xbc, 0x24],
1 => &[0x48, 0x89, 0xb4, 0x24],
2 => &[0x48, 0x89, 0x94, 0x24],
3 => &[0x48, 0x89, 0x8c, 0x24],
4 => &[0x4c, 0x89, 0x84, 0x24],
5 => &[0x4c, 0x89, 0x8c, 0x24],
_ => unreachable!(),
};
self.code.extend_from_slice(prefix);
self.code.extend_from_slice(value_to_bytes(&(i * 8u32)));
}
_ => {
self.code.extend_from_slice(&[
0x48, 0x8b, 0x84, 0x24,
]);
self.code.extend_from_slice(value_to_bytes(
&((i - 6) * 8u32 + stack_offset + 8),
));
self.code.extend_from_slice(&[0x48, 0x89, 0x84, 0x24]);
self.code.extend_from_slice(value_to_bytes(&(i * 8u32)));
}
}
}
self.code.extend_from_slice(&[
0x48, 0xbf,
]);
self.code.extend_from_slice(value_to_bytes(&context));
self.code.extend_from_slice(&[
0x48, 0x89, 0xe6,
]);
self.code.extend_from_slice(&[
0x48, 0xb8,
]);
self.code.extend_from_slice(value_to_bytes(&target));
self.code.extend_from_slice(&[
0xff, 0xd0,
]);
self.code.extend_from_slice(&[
0x48, 0x81, 0xc4,
]);
self.code.extend_from_slice(value_to_bytes(&stack_offset));
self.code.extend_from_slice(&[
0xc3,
]);
idx
}
pub fn insert_global(self) -> Option<NonNull<u8>> {
TRAMPOLINES.insert(&self.code)
}
pub unsafe fn remove_global(ptr: NonNull<u8>) {
TRAMPOLINES.remove(ptr);
}
pub fn code(&self) -> &[u8] {
&self.code
}
pub fn build(self) -> TrampolineBuffer {
get_context();
let mut code = CodeMemory::new(self.code.len());
code[..self.code.len()].copy_from_slice(&self.code);
code.make_executable();
TrampolineBuffer {
code,
offsets: self.offsets,
}
}
}
impl TrampolineBuffer {
pub fn get_trampoline(&self, idx: usize) -> *const Trampoline {
&self.code[self.offsets[idx]] as *const u8 as *const Trampoline
}
}
impl fmt::Debug for TrampolineBuffer {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "TrampolineBuffer {{}}")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_context_trampoline() {
struct TestContext {
value: i32,
}
extern "C" fn do_add(a: i32, b: f32) -> f32 {
let ctx = unsafe { &*(get_context() as *const TestContext) };
a as f32 + b + ctx.value as f32
}
let mut builder = TrampolineBufferBuilder::new();
let ctx = TestContext { value: 3 };
let idx = builder.add_context_trampoline(
do_add as usize as *const _,
&ctx as *const TestContext as *const _,
);
let buf = builder.build();
let t = buf.get_trampoline(idx);
let ret = unsafe { mem::transmute::<_, extern "C" fn(i32, f32) -> f32>(t)(1, 2.0) as i32 };
assert_eq!(ret, 6);
}
#[test]
fn test_callinfo_trampoline() {
struct TestContext {
value: i32,
}
unsafe extern "C" fn do_add(ctx: *const CallContext, args: *const u64) -> u64 {
let ctx = &*(ctx as *const TestContext);
let args: &[u64] = slice::from_raw_parts(args, 8);
(args.iter().map(|x| *x as i32).fold(0, |a, b| a + b) + ctx.value) as u64
}
let mut builder = TrampolineBufferBuilder::new();
let ctx = TestContext { value: 100 };
let idx =
builder.add_callinfo_trampoline(do_add, &ctx as *const TestContext as *const _, 8);
let buf = builder.build();
let t = buf.get_trampoline(idx);
let ret = unsafe {
mem::transmute::<_, extern "C" fn(i32, i32, i32, i32, i32, i32, i32, i32) -> i32>(t)(
1, 2, 3, 4, 5, 6, 7, 8,
) as i32
};
assert_eq!(ret, 136);
}
#[test]
fn test_many_global_trampolines() {
unsafe extern "C" fn inner(n: *const CallContext, args: *const u64) -> u64 {
let n = n as usize;
let mut result: u64 = 0;
for i in 0..n {
result += *args.offset(i as _);
}
result
}
let buffer = TrampBuffer::new(4096);
let mut prev: Option<(NonNull<u8>, u64)> = None;
for i in 0..5000usize {
let mut builder = TrampolineBufferBuilder::new();
let n = i % 8;
builder.add_callinfo_trampoline(inner, n as _, n as _);
let ptr = buffer
.insert(builder.code())
.expect("cannot insert new code into global buffer");
if let Some((ptr, expected)) = prev.take() {
use std::mem::transmute;
unsafe {
match expected {
0 => {
let f = transmute::<_, extern "C" fn() -> u64>(ptr);
assert_eq!(f(), 0);
}
1 => {
let f = transmute::<_, extern "C" fn(u64) -> u64>(ptr);
assert_eq!(f(1), 1);
}
3 => {
let f = transmute::<_, extern "C" fn(u64, u64) -> u64>(ptr);
assert_eq!(f(1, 2), 3);
}
6 => {
let f = transmute::<_, extern "C" fn(u64, u64, u64) -> u64>(ptr);
assert_eq!(f(1, 2, 3), 6);
}
10 => {
let f = transmute::<_, extern "C" fn(u64, u64, u64, u64) -> u64>(ptr);
assert_eq!(f(1, 2, 3, 4), 10);
}
15 => {
let f =
transmute::<_, extern "C" fn(u64, u64, u64, u64, u64) -> u64>(ptr);
assert_eq!(f(1, 2, 3, 4, 5), 15);
}
21 => {
let f = transmute::<
_,
extern "C" fn(u64, u64, u64, u64, u64, u64) -> u64,
>(ptr);
assert_eq!(f(1, 2, 3, 4, 5, 6), 21);
}
28 => {
let f = transmute::<
_,
extern "C" fn(u64, u64, u64, u64, u64, u64, u64) -> u64,
>(ptr);
assert_eq!(f(1, 2, 3, 4, 5, 6, 7), 28);
}
_ => unreachable!(),
}
buffer.remove(ptr);
}
}
let expected = (0..=n as u64).sum();
prev = Some((ptr, expected))
}
}
}