Skip to main content

async_ebpf/
program.rs

1//! This module contains the core logic for eBPF program execution. A lot of
2//! `unsafe` code is used - be careful when making changes here.
3
4use std::{
5  any::{Any, TypeId},
6  cell::{Cell, RefCell},
7  collections::HashMap,
8  ffi::CStr,
9  marker::PhantomData,
10  mem::ManuallyDrop,
11  ops::{Deref, DerefMut},
12  pin::Pin,
13  ptr::NonNull,
14  rc::Rc,
15  sync::{
16    atomic::{compiler_fence, AtomicBool, AtomicU64, Ordering},
17    Arc, Once,
18  },
19  task::{Context, Poll},
20  thread::ThreadId,
21  time::{Duration, Instant},
22};
23
24use corosensei::{
25  stack::{DefaultStack, Stack},
26  Coroutine, CoroutineResult, ScopedCoroutine, Yielder,
27};
28use futures::{task::noop_waker_ref, Future, FutureExt};
29use memmap2::{MmapOptions, MmapRaw};
30use parking_lot::{Condvar, Mutex};
31use rand::prelude::SliceRandom;
32
33use crate::{
34  error::{Error, RuntimeError},
35  helpers::Helper,
36  linker::{link_elf, validate_local_call_graph},
37  pointer_cage::PointerCage,
38  util::nonnull_bytes_overlap,
39};
40
41const NATIVE_STACK_SIZE: usize = 16384;
42const SHADOW_STACK_SIZE: usize = 32768;
43const MAX_CALLDATA_SIZE: usize = 512;
44const MAX_MUTABLE_DEREF_REGIONS: usize = 4;
45const MAX_IMMUTABLE_DEREF_REGIONS: usize = 16;
46
47/// Per-invocation storage for helper state during a program run.
48pub struct InvokeScope {
49  data: HashMap<TypeId, Box<dyn Any + Send>>,
50}
51
52impl InvokeScope {
53  /// Gets or creates typed data scoped to this invocation.
54  pub fn data_mut<T: Default + Send + 'static>(&mut self) -> &mut T {
55    let ty = TypeId::of::<T>();
56    self
57      .data
58      .entry(ty)
59      .or_insert_with(|| Box::new(T::default()))
60      .downcast_mut()
61      .expect("InvokeScope::data_mut: downcast failed")
62  }
63}
64
65/// Context passed to helpers while a program is executing.
66pub struct HelperScope<'a, 'b> {
67  /// The program being executed.
68  pub program: &'a Program,
69  /// Mutable per-invocation data for helpers.
70  pub invoke: RefCell<&'a mut InvokeScope>,
71  resources: RefCell<&'a mut [&'b mut dyn Any]>,
72  memory: &'a JitMemory,
73  mutable_dereferenced_regions: [Cell<Option<NonNull<[u8]>>>; MAX_MUTABLE_DEREF_REGIONS],
74  immutable_dereferenced_regions: [Cell<Option<NonNull<[u8]>>>; MAX_IMMUTABLE_DEREF_REGIONS],
75  can_post_task: bool,
76}
77
78/// A validated mutable view into user memory.
79pub struct MutableUserMemory<'a, 'b, 'c> {
80  _scope: &'c HelperScope<'a, 'b>,
81  region: NonNull<[u8]>,
82}
83
84impl<'a, 'b, 'c> Deref for MutableUserMemory<'a, 'b, 'c> {
85  type Target = [u8];
86
87  fn deref(&self) -> &Self::Target {
88    unsafe { self.region.as_ref() }
89  }
90}
91
92impl<'a, 'b, 'c> DerefMut for MutableUserMemory<'a, 'b, 'c> {
93  fn deref_mut(&mut self) -> &mut Self::Target {
94    unsafe { self.region.as_mut() }
95  }
96}
97
98impl<'a, 'b> HelperScope<'a, 'b> {
99  /// Posts an async task to be run between timeslices.
100  pub fn post_task(
101    &self,
102    task: impl Future<Output = impl FnOnce(&HelperScope) -> Result<u64, ()> + 'static> + 'static,
103  ) {
104    if !self.can_post_task {
105      panic!("HelperScope::post_task() called in a context where posting task is not allowed");
106    }
107
108    PENDING_ASYNC_TASK.with(|x| {
109      let mut x = x.borrow_mut();
110      if x.is_some() {
111        panic!("post_task called while another task is pending");
112      }
113      *x = Some(async move { Box::new(task.await) as AsyncTaskOutput }.boxed_local());
114    });
115  }
116
117  /// Calls `callback` with a mutable resource of type `T`, if present.
118  pub fn with_resource_mut<'c, T: 'static, R>(
119    &'c self,
120    callback: impl FnOnce(Result<&mut T, ()>) -> R,
121  ) -> R {
122    let mut resources = self.resources.borrow_mut();
123    let Some(res) = resources
124      .iter_mut()
125      .filter_map(|x| x.downcast_mut::<T>())
126      .next()
127    else {
128      tracing::warn!(resource_type = ?TypeId::of::<T>(), "resource not found");
129      return callback(Err(()));
130    };
131
132    callback(Ok(res))
133  }
134
135  /// Validates and returns an immutable view into user memory.
136  pub fn user_memory(&self, ptr: u64, size: u64) -> Result<&[u8], ()> {
137    let Some(region) = self.memory.safe_deref_for_read(ptr as usize, size as usize) else {
138      tracing::warn!(ptr, size, "invalid read");
139      return Err(());
140    };
141
142    if size != 0 {
143      // The region must not overlap with any previously dereferenced mutable regions
144      if self
145        .mutable_dereferenced_regions
146        .iter()
147        .filter_map(|x| x.get())
148        .any(|x| nonnull_bytes_overlap(x, region))
149      {
150        tracing::warn!(ptr, size, "read overlapped with previous write");
151        return Err(());
152      }
153
154      // Find a slot to record this dereference
155      let Some(slot) = self
156        .immutable_dereferenced_regions
157        .iter()
158        .find(|x| x.get().is_none())
159      else {
160        tracing::warn!(ptr, size, "too many reads");
161        return Err(());
162      };
163      slot.set(Some(region));
164    }
165
166    Ok(unsafe { region.as_ref() })
167  }
168
169  /// Validates and returns a mutable view into user memory.
170  pub fn user_memory_mut<'c>(
171    &'c self,
172    ptr: u64,
173    size: u64,
174  ) -> Result<MutableUserMemory<'a, 'b, 'c>, ()> {
175    let Some(region) = self
176      .memory
177      .safe_deref_for_write(ptr as usize, size as usize)
178    else {
179      tracing::warn!(ptr, size, "invalid write");
180      return Err(());
181    };
182
183    if size != 0 {
184      // The region must not overlap with any other previously dereferenced mutable or immutable regions
185      if self
186        .mutable_dereferenced_regions
187        .iter()
188        .chain(self.immutable_dereferenced_regions.iter())
189        .filter_map(|x| x.get())
190        .any(|x| nonnull_bytes_overlap(x, region))
191      {
192        tracing::warn!(ptr, size, "write overlapped with previous read/write");
193        return Err(());
194      }
195
196      // Find a slot to record this dereference
197      let Some(slot) = self
198        .mutable_dereferenced_regions
199        .iter()
200        .find(|x| x.get().is_none())
201      else {
202        tracing::warn!(ptr, size, "too many writes");
203        return Err(());
204      };
205      slot.set(Some(region));
206    }
207
208    Ok(MutableUserMemory {
209      _scope: self,
210      region,
211    })
212  }
213}
214
215#[derive(Copy, Clone)]
216struct AssumeSend<T>(T);
217unsafe impl<T> Send for AssumeSend<T> {}
218
219struct ExecContext {
220  native_stack: DefaultStack,
221  guest_stack: Box<[u8; SHADOW_STACK_SIZE]>,
222}
223
224impl ExecContext {
225  fn new() -> Self {
226    Self {
227      native_stack: DefaultStack::new(NATIVE_STACK_SIZE)
228        .expect("failed to initialize native stack"),
229      guest_stack: Box::new([0u8; SHADOW_STACK_SIZE]),
230    }
231  }
232}
233
234#[repr(C)]
235struct JitMemory {
236  stack_guest_bottom: usize,
237  stack_guest_top: usize,
238  stack_native_base: usize,
239  data_guest_bottom: usize,
240  data_guest_top: usize,
241  data_native_base: usize,
242}
243
244impl JitMemory {
245  fn checked_region(
246    guest: usize,
247    size: usize,
248    guest_bottom: usize,
249    guest_top: usize,
250    native_base: usize,
251  ) -> Option<NonNull<[u8]>> {
252    if size == 0 {
253      return Some(NonNull::slice_from_raw_parts(NonNull::dangling(), 0));
254    }
255
256    let end = guest.checked_add(size)?;
257    if guest < guest_bottom || end > guest_top {
258      return None;
259    }
260    let native = native_base.checked_add(guest - guest_bottom)? as *mut u8;
261    unsafe {
262      Some(NonNull::new_unchecked(std::ptr::slice_from_raw_parts_mut(
263        native, size,
264      )))
265    }
266  }
267
268  fn safe_deref_for_write(&self, guest: usize, size: usize) -> Option<NonNull<[u8]>> {
269    Self::checked_region(
270      guest,
271      size,
272      self.stack_guest_bottom,
273      self.stack_guest_top,
274      self.stack_native_base,
275    )
276  }
277
278  fn safe_deref_for_read(&self, guest: usize, size: usize) -> Option<NonNull<[u8]>> {
279    Self::checked_region(
280      guest,
281      size,
282      self.stack_guest_bottom,
283      self.stack_guest_top,
284      self.stack_native_base,
285    )
286    .or_else(|| {
287      Self::checked_region(
288        guest,
289        size,
290        self.data_guest_bottom,
291        self.data_guest_top,
292        self.data_native_base,
293      )
294    })
295  }
296}
297
298/// A pending async task spawned by a helper.
299pub type PendingAsyncTask = Pin<Box<dyn Future<Output = AsyncTaskOutput>>>;
300/// The callback produced by a helper async task when it resumes.
301pub type AsyncTaskOutput = Box<dyn FnOnce(&HelperScope) -> Result<u64, ()>>;
302
303static NEXT_PROGRAM_ID: AtomicU64 = AtomicU64::new(1);
304
305#[derive(Copy, Clone, Debug)]
306enum PreemptionState {
307  Inactive,
308  Armed(usize),
309  Shutdown,
310}
311
312type PreemptionStateSignal = (Mutex<PreemptionState>, Condvar);
313
314thread_local! {
315  static RUST_TID: ThreadId = std::thread::current().id();
316  static SIGUSR1_COUNTER: Cell< u64> = Cell::new(0);
317  static ACTIVE_JIT_CODE_ZONE: ActiveJitCodeZone = ActiveJitCodeZone::default();
318  static EXEC_CONTEXT_POOL: RefCell<Vec<ExecContext>> = Default::default();
319  static PENDING_ASYNC_TASK: RefCell<Option<PendingAsyncTask>> = RefCell::new(None);
320  static PREEMPTION_STATE: Arc<PreemptionStateSignal> = Arc::new((Mutex::new(PreemptionState::Inactive), Condvar::new()));
321  static LOADING_PROGRAM_LOADER: Cell<*const ProgramLoader> = const { Cell::new(std::ptr::null()) };
322}
323
324struct BorrowedExecContext {
325  ctx: ManuallyDrop<ExecContext>,
326}
327
328impl BorrowedExecContext {
329  fn new() -> Self {
330    let mut me = Self {
331      ctx: ManuallyDrop::new(
332        EXEC_CONTEXT_POOL.with(|x| x.borrow_mut().pop().unwrap_or_else(ExecContext::new)),
333      ),
334    };
335    me.ctx.guest_stack.fill(0x8e);
336    me
337  }
338}
339
340impl Drop for BorrowedExecContext {
341  fn drop(&mut self) {
342    let ctx = unsafe { ManuallyDrop::take(&mut self.ctx) };
343    EXEC_CONTEXT_POOL.with(|x| x.borrow_mut().push(ctx));
344  }
345}
346
347#[derive(Default)]
348struct ActiveJitCodeZone {
349  valid: AtomicBool,
350  code_range: Cell<(usize, usize)>,
351  pointer_cage_protected_range: Cell<(usize, usize)>,
352  yielder: Cell<Option<NonNull<Yielder<u64, Dispatch>>>>,
353}
354
355/// Hooks for observing program execution events.
356pub trait ProgramEventListener: Send + Sync + 'static {
357  /// Called after an async preemption is triggered.
358  fn did_async_preempt(&self, _scope: &HelperScope) {}
359  /// Called after yielding back to the async runtime.
360  fn did_yield(&self) {}
361  /// Called after throttling a program's execution.
362  fn did_throttle(&self, _scope: &HelperScope) -> Option<Pin<Box<dyn Future<Output = ()>>>> {
363    None
364  }
365}
366
367/// No-op event listener implementation.
368pub struct DummyProgramEventListener;
369impl ProgramEventListener for DummyProgramEventListener {}
370
371/// Default limit for the total JIT-compiled native code size of one program.
372pub const DEFAULT_CODE_SIZE_LIMIT: usize = 1 << 20;
373
374/// Prepares helper tables and loads eBPF programs.
375pub struct ProgramLoader {
376  helpers_inverse: HashMap<&'static str, i32>,
377  event_listener: Arc<dyn ProgramEventListener>,
378  helper_id_xor: u16,
379  helpers: Arc<Vec<(u16, &'static str, Helper)>>,
380  code_size_limit: usize,
381  require_static_regions: bool,
382}
383
384/// A loaded program that is not yet pinned to a thread.
385pub struct UnboundProgram {
386  id: u64,
387  _code_mem: MmapRaw,
388  cage: PointerCage,
389  helper_id_xor: u16,
390  helpers: Arc<Vec<(u16, &'static str, Helper)>>,
391  event_listener: Arc<dyn ProgramEventListener>,
392  entrypoints: HashMap<String, Entrypoint>,
393}
394
395/// A program pinned to a specific thread and ready to execute.
396pub struct Program {
397  unbound: UnboundProgram,
398  data: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
399  t: ThreadEnv,
400}
401
402#[derive(Copy, Clone)]
403struct Entrypoint {
404  code_ptr: usize,
405  code_len: usize,
406}
407
408/// Time limits used to yield or throttle execution.
409#[derive(Clone, Debug)]
410pub struct TimesliceConfig {
411  /// Maximum runtime before yielding to the async scheduler.
412  pub max_run_time_before_yield: Duration,
413  /// Maximum runtime before a throttle sleep is forced.
414  pub max_run_time_before_throttle: Duration,
415  /// Duration of the throttle sleep once triggered.
416  pub throttle_duration: Duration,
417}
418
419/// Async runtime integration for yielding and sleeping.
420pub trait Timeslicer {
421  /// Sleep for the provided duration.
422  fn sleep(&self, duration: Duration) -> impl Future<Output = ()>;
423  /// Yield to the async scheduler.
424  fn yield_now(&self) -> impl Future<Output = ()>;
425}
426
427/// Global runtime environment for signal handlers.
428#[derive(Copy, Clone)]
429pub struct GlobalEnv(());
430
431/// Per-thread runtime environment for preemption handling.
432#[derive(Copy, Clone)]
433pub struct ThreadEnv {
434  _not_send_sync: std::marker::PhantomData<*const ()>,
435}
436
437impl GlobalEnv {
438  /// Initializes global state and installs signal handlers.
439  ///
440  /// # Safety
441  /// Must be called in a process that can install SIGUSR1/SIGSEGV handlers.
442  pub unsafe fn new() -> Self {
443    static INIT: Once = Once::new();
444
445    // SIGUSR1 must be blocked during exception handling
446    // Otherwise it seems that Linux gives up and throws an uncatchable SI_KERNEL SIGSEGV:
447    //
448    // [pid 517110] tgkill(517109, 517112, SIGUSR1 <unfinished ...>
449    // [pid 517112] --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_ACCERR, si_addr=0x793789be227e} ---
450    // [pid 517109] write(15, "\1\0\0\0\0\0\0\0", 8 <unfinished ...>
451    // [pid 517110] <... tgkill resumed>)      = 0
452    // [pid 517109] <... write resumed>)       = 8
453    // [pid 517112] --- SIGUSR1 {si_signo=SIGUSR1, si_code=SI_TKILL, si_pid=517109, si_uid=1000} ---
454    // [pid 517109] recvfrom(57,  <unfinished ...>
455    // [pid 517110] futex(0x79378abff5a8, FUTEX_WAIT_PRIVATE, 1, {tv_sec=0, tv_nsec=5999909} <unfinished ...>
456    // [pid 517109] <... recvfrom resumed>"GET /write_rodata HTTP/1.1\r\nHost"..., 8192, 0, NULL, NULL) = 61
457    // [pid 517112] --- SIGSEGV {si_signo=SIGSEGV, si_code=SI_KERNEL, si_addr=NULL} ---
458
459    INIT.call_once(|| {
460      let sa_mask = get_blocked_sigset();
461
462      for (sig, handler) in [
463        (libc::SIGUSR1, sigusr1_handler as *const () as usize),
464        (libc::SIGSEGV, sigsegv_handler as *const () as usize),
465      ] {
466        let act = libc::sigaction {
467          sa_sigaction: handler,
468          sa_flags: libc::SA_SIGINFO,
469          sa_mask,
470          sa_restorer: None,
471        };
472        if libc::sigaction(sig, &act, std::ptr::null_mut()) != 0 {
473          panic!("failed to setup handler for signal {}", sig);
474        }
475      }
476    });
477
478    Self(())
479  }
480
481  /// Initializes per-thread state and starts the async preemption watcher.
482  pub fn init_thread(self, async_preemption_interval: Duration) -> ThreadEnv {
483    struct DeferDrop(Arc<PreemptionStateSignal>);
484    impl Drop for DeferDrop {
485      fn drop(&mut self) {
486        let x = &self.0;
487        *x.0.lock() = PreemptionState::Shutdown;
488        x.1.notify_one();
489      }
490    }
491
492    thread_local! {
493      static WATCHER: RefCell<Option<DeferDrop>> = RefCell::new(None);
494    }
495
496    if WATCHER.with(|x| x.borrow().is_some()) {
497      return ThreadEnv {
498        _not_send_sync: PhantomData,
499      };
500    }
501
502    let preemption_state = PREEMPTION_STATE.with(|x| x.clone());
503
504    unsafe {
505      let tgid = libc::getpid();
506      let tid = libc::gettid();
507
508      std::thread::Builder::new()
509        .name("preempt-watcher".to_string())
510        .spawn(move || {
511          let mut state = preemption_state.0.lock();
512          loop {
513            match *state {
514              PreemptionState::Shutdown => break,
515              PreemptionState::Inactive => {
516                preemption_state.1.wait(&mut state);
517              }
518              PreemptionState::Armed(_) => {
519                let timeout = preemption_state.1.wait_while_for(
520                  &mut state,
521                  |x| matches!(x, PreemptionState::Armed(_)),
522                  async_preemption_interval,
523                );
524                if timeout.timed_out() {
525                  match *state {
526                    PreemptionState::Armed(0) => {
527                      *state = PreemptionState::Inactive;
528                    }
529                    PreemptionState::Armed(_) => {
530                      let ret = libc::syscall(libc::SYS_tgkill, tgid, tid, libc::SIGUSR1);
531                      if ret != 0 {
532                        break;
533                      }
534                    }
535                    PreemptionState::Inactive => {}
536                    PreemptionState::Shutdown => break,
537                  }
538                }
539              }
540            }
541          }
542        })
543        .expect("failed to spawn preemption watcher");
544
545      WATCHER.with(|x| {
546        x.borrow_mut()
547          .replace(DeferDrop(PREEMPTION_STATE.with(|x| x.clone())));
548      });
549
550      ThreadEnv {
551        _not_send_sync: PhantomData,
552      }
553    }
554  }
555}
556
557impl UnboundProgram {
558  /// Pins the program to the current thread using a prepared `ThreadEnv`.
559  pub fn pin_to_current_thread(self, t: ThreadEnv) -> Program {
560    Program {
561      unbound: self,
562      data: RefCell::new(HashMap::new()),
563      t,
564    }
565  }
566}
567
568pub struct PreemptionEnabled(());
569
570impl PreemptionEnabled {
571  pub fn new(_: ThreadEnv) -> Self {
572    PREEMPTION_STATE.with(|x| {
573      let mut notify = false;
574      {
575        let mut st = x.0.lock();
576        let next = match *st {
577          PreemptionState::Inactive => {
578            notify = true;
579            PreemptionState::Armed(1)
580          }
581          PreemptionState::Armed(n) => PreemptionState::Armed(n + 1),
582          PreemptionState::Shutdown => unreachable!(),
583        };
584        *st = next;
585      }
586
587      if notify {
588        x.1.notify_one();
589      }
590    });
591    Self(())
592  }
593}
594
595impl Drop for PreemptionEnabled {
596  fn drop(&mut self) {
597    PREEMPTION_STATE.with(|x| {
598      let mut st = x.0.lock();
599      let next = match *st {
600        PreemptionState::Armed(1) => PreemptionState::Armed(0),
601        PreemptionState::Armed(n) => {
602          assert!(n > 1);
603          PreemptionState::Armed(n - 1)
604        }
605        PreemptionState::Inactive | PreemptionState::Shutdown => unreachable!(),
606      };
607      *st = next;
608    });
609  }
610}
611
612impl Program {
613  /// Returns the unique program identifier.
614  pub fn id(&self) -> u64 {
615    self.unbound.id
616  }
617
618  pub fn thread_env(&self) -> ThreadEnv {
619    self.t
620  }
621
622  /// Gets or creates shared typed data for this program instance.
623  pub fn data<T: Default + 'static>(&self) -> Rc<T> {
624    let mut data = self.data.borrow_mut();
625    let entry = data.entry(TypeId::of::<T>());
626    let entry = entry.or_insert_with(|| Rc::new(T::default()));
627    entry.clone().downcast().unwrap()
628  }
629
630  pub fn has_section(&self, name: &str) -> bool {
631    self.unbound.entrypoints.contains_key(name)
632  }
633
634  /// Runs the program entrypoint with the provided resources and calldata.
635  pub async fn run(
636    &self,
637    timeslice: &TimesliceConfig,
638    timeslicer: &impl Timeslicer,
639    entrypoint: &str,
640    resources: &mut [&mut dyn Any],
641    calldata: &[u8],
642    preemption: &PreemptionEnabled,
643  ) -> Result<i64, Error> {
644    self
645      ._run(
646        timeslice, timeslicer, entrypoint, resources, calldata, preemption,
647      )
648      .await
649      .map_err(Error)
650  }
651
652  async fn _run(
653    &self,
654    timeslice: &TimesliceConfig,
655    timeslicer: &impl Timeslicer,
656    entrypoint: &str,
657    resources: &mut [&mut dyn Any],
658    calldata: &[u8],
659    _: &PreemptionEnabled,
660  ) -> Result<i64, RuntimeError> {
661    let Some(entrypoint) = self.unbound.entrypoints.get(entrypoint).copied() else {
662      return Err(RuntimeError::InvalidArgument("entrypoint not found"));
663    };
664
665    let entry = unsafe {
666      std::mem::transmute::<
667        _,
668        unsafe extern "C" fn(
669          ctx: usize,
670          mem_len: usize,
671          stack: usize,
672          stack_len: usize,
673          reserved: usize,
674          memory: usize,
675        ) -> u64,
676      >(entrypoint.code_ptr)
677    };
678    struct CoDropper<'a, Input, Yield, Return, DefaultStack: Stack>(
679      ScopedCoroutine<'a, Input, Yield, Return, DefaultStack>,
680    );
681    impl<'a, Input, Yield, Return, DefaultStack: Stack> Drop
682      for CoDropper<'a, Input, Yield, Return, DefaultStack>
683    {
684      fn drop(&mut self) {
685        // Prevent the coroutine library from attempting to unwind the stack of the coroutine
686        // and run destructors, because this stack might be running a signal handler and
687        // it's not allowed to unwind from there.
688        //
689        // SAFETY: The coroutine stack only contains stack frames for JIT-compiled code and
690        // carefully chosen Rust code that do not hold Droppable values, so it's safe to
691        // skip destructors.
692        unsafe {
693          self.0.force_reset();
694        }
695      }
696    }
697
698    let mut ectx = BorrowedExecContext::new();
699
700    if calldata.len() > MAX_CALLDATA_SIZE {
701      return Err(RuntimeError::InvalidArgument("calldata too large"));
702    }
703    ectx.ctx.guest_stack[SHADOW_STACK_SIZE - calldata.len()..].copy_from_slice(calldata);
704    let calldata_len = calldata.len();
705
706    let program_ret: u64 = {
707      let guest_stack_top = self.unbound.cage.stack_top();
708      let guest_stack_bottom = self.unbound.cage.stack_bottom();
709      let ctx = &mut *ectx.ctx;
710      let memory = JitMemory {
711        stack_guest_bottom: guest_stack_bottom,
712        stack_guest_top: guest_stack_top,
713        stack_native_base: ctx.guest_stack.as_mut_ptr() as usize,
714        data_guest_bottom: self.unbound.cage.data_bottom(),
715        data_guest_top: self.unbound.cage.data_top(),
716        data_native_base: self.unbound.cage.data_native_base(),
717      };
718      let memory_ptr = &memory as *const JitMemory as usize;
719
720      let mut co = AssumeSend(CoDropper(Coroutine::with_stack(
721        &mut ctx.native_stack,
722        move |yielder, _input| unsafe {
723          ACTIVE_JIT_CODE_ZONE.with(|x| {
724            x.yielder.set(NonNull::new(yielder as *const _ as *mut _));
725          });
726          let calldata_start = guest_stack_top - calldata_len;
727          let stack_top = calldata_start & !0x7;
728          let stack_len = stack_top - guest_stack_bottom;
729          entry(
730            calldata_start,
731            calldata_start,
732            guest_stack_bottom,
733            stack_len,
734            0,
735            memory_ptr,
736          )
737        },
738      )));
739
740      let mut last_yield_time = Instant::now();
741      let mut last_throttle_time = Instant::now();
742      let mut yielder: Option<AssumeSend<NonNull<Yielder<u64, Dispatch>>>> = None;
743      let mut resume_input: u64 = 0;
744      let mut did_throttle = false;
745      let mut rust_tid_sigusr1_counter = (RUST_TID.with(|x| *x), SIGUSR1_COUNTER.with(|x| x.get()));
746      let mut prev_async_task_output: Option<(&'static str, AsyncTaskOutput)> = None;
747      let mut invoke_scope = InvokeScope {
748        data: HashMap::new(),
749      };
750
751      loop {
752        ACTIVE_JIT_CODE_ZONE.with(|x| {
753          x.code_range.set((
754            entrypoint.code_ptr,
755            entrypoint.code_ptr + entrypoint.code_len,
756          ));
757          x.yielder.set(yielder.map(|x| x.0));
758          x.pointer_cage_protected_range.set((0, 4096));
759          compiler_fence(Ordering::Release);
760          x.valid.store(true, Ordering::Relaxed);
761        });
762
763        // If the previous iteration wants to write back to machine state
764        if let Some((helper_name, prev_async_task_output)) = prev_async_task_output.take() {
765          resume_input = prev_async_task_output(&HelperScope {
766            program: self,
767            invoke: RefCell::new(&mut invoke_scope),
768            resources: RefCell::new(resources),
769            memory: &memory,
770            mutable_dereferenced_regions: unsafe { std::mem::zeroed() },
771            immutable_dereferenced_regions: unsafe { std::mem::zeroed() },
772            can_post_task: false,
773          })
774          .map_err(|_| RuntimeError::AsyncHelperError(helper_name))?;
775        }
776
777        let ret = co.0 .0.resume(resume_input);
778        ACTIVE_JIT_CODE_ZONE.with(|x| {
779          x.valid.store(false, Ordering::Relaxed);
780          compiler_fence(Ordering::Release);
781          yielder = x.yielder.get().map(AssumeSend);
782        });
783
784        let dispatch: Dispatch = match ret {
785          CoroutineResult::Return(x) => break x,
786          CoroutineResult::Yield(x) => x,
787        };
788
789        // restore signal mask of current thread
790        if dispatch.memory_access_error.is_some() || dispatch.async_preemption {
791          unsafe {
792            let unblock = get_blocked_sigset();
793            libc::sigprocmask(libc::SIG_UNBLOCK, &unblock, std::ptr::null_mut());
794          }
795        }
796
797        if let Some(si_addr) = dispatch.memory_access_error {
798          let vaddr = if si_addr >= memory.stack_native_base
799            && si_addr < memory.stack_native_base + SHADOW_STACK_SIZE
800          {
801            memory.stack_guest_bottom + (si_addr - memory.stack_native_base)
802          } else if si_addr >= memory.data_native_base
803            && si_addr
804              < memory.data_native_base + (memory.data_guest_top - memory.data_guest_bottom)
805          {
806            memory.data_guest_bottom + (si_addr - memory.data_native_base)
807          } else {
808            0
809          };
810          return Err(RuntimeError::MemoryFault(vaddr));
811        }
812
813        // Clear pending task if something else has set it
814        PENDING_ASYNC_TASK.with(|x| x.borrow_mut().take());
815        let mut helper_name: &'static str = "";
816
817        let mut helper_scope = HelperScope {
818          program: self,
819          invoke: RefCell::new(&mut invoke_scope),
820          resources: RefCell::new(resources),
821          memory: &memory,
822          mutable_dereferenced_regions: unsafe { std::mem::zeroed() },
823          immutable_dereferenced_regions: unsafe { std::mem::zeroed() },
824          can_post_task: false,
825        };
826
827        if dispatch.async_preemption {
828          self
829            .unbound
830            .event_listener
831            .did_async_preempt(&mut helper_scope);
832        } else {
833          // validator should ensure all helper indexes are present in the table
834          let Some((_, got_helper_name, helper)) = self
835            .unbound
836            .helpers
837            .get(
838              ((dispatch.index & 0xffff) as u16 ^ self.unbound.helper_id_xor).wrapping_sub(1)
839                as usize,
840            )
841            .copied()
842          else {
843            panic!("unknown helper index: {}", dispatch.index);
844          };
845          helper_name = got_helper_name;
846
847          helper_scope.can_post_task = true;
848          resume_input = helper(
849            &mut helper_scope,
850            dispatch.arg1,
851            dispatch.arg2,
852            dispatch.arg3,
853            dispatch.arg4,
854            dispatch.arg5,
855          )
856          .map_err(|()| RuntimeError::HelperError(helper_name))?;
857          helper_scope.can_post_task = false;
858        }
859
860        let pending_async_task = PENDING_ASYNC_TASK.with(|x| x.borrow_mut().take());
861
862        // Fast path: do not read timestamp if no thread migration or async preemption happened
863        let new_rust_tid_sigusr1_counter =
864          (RUST_TID.with(|x| *x), SIGUSR1_COUNTER.with(|x| x.get()));
865        if new_rust_tid_sigusr1_counter == rust_tid_sigusr1_counter && pending_async_task.is_none()
866        {
867          continue;
868        }
869
870        rust_tid_sigusr1_counter = new_rust_tid_sigusr1_counter;
871
872        let now = Instant::now();
873        let should_throttle = now > last_throttle_time
874          && now.duration_since(last_throttle_time) >= timeslice.max_run_time_before_throttle;
875        let should_yield = now > last_yield_time
876          && now.duration_since(last_yield_time) >= timeslice.max_run_time_before_yield;
877        if should_throttle || should_yield || pending_async_task.is_some() {
878          // we are now free to give up control of current thread to other async tasks
879
880          if should_throttle {
881            if !did_throttle {
882              did_throttle = true;
883              tracing::warn!("throttling program");
884            }
885            timeslicer.sleep(timeslice.throttle_duration).await;
886            let now = Instant::now();
887            last_throttle_time = now;
888            last_yield_time = now;
889            let task = self.unbound.event_listener.did_throttle(&mut helper_scope);
890            if let Some(task) = task {
891              task.await;
892            }
893          } else if should_yield {
894            timeslicer.yield_now().await;
895            let now = Instant::now();
896            last_yield_time = now;
897            self.unbound.event_listener.did_yield();
898          }
899
900          // Now we have released all exclusive resources and can safely execute the async task.
901          if let Some(mut pending_async_task) = pending_async_task {
902            // Fast path: helpers that merely compute a value (the common case)
903            // complete on the first poll without ever suspending. Poll once and,
904            // if it's ready, skip the `Instant::now()` reads used for run-budget
905            // compensation below. This matters because `clock_gettime` can cost
906            // ~200ns on virtualized hosts, and this path runs on every async
907            // helper invocation.
908            let output =
909              match pending_async_task.poll_unpin(&mut Context::from_waker(noop_waker_ref())) {
910                Poll::Ready(output) => output,
911                Poll::Pending => {
912                  // The task genuinely suspended; account for the wall-clock time
913                  // it took so it isn't charged against the program's run budget.
914                  let async_start = Instant::now();
915                  let output = pending_async_task.await;
916                  let async_dur = async_start.elapsed();
917                  last_throttle_time += async_dur;
918                  last_yield_time += async_dur;
919                  output
920                }
921              };
922            prev_async_task_output = Some((helper_name, output));
923          }
924        }
925      }
926    };
927
928    Ok(program_ret as i64)
929  }
930}
931
932struct Vm(NonNull<crate::ubpf::ubpf_vm>);
933
934impl Vm {
935  fn new(cage: &PointerCage) -> Self {
936    let vm = NonNull::new(unsafe { crate::ubpf::ubpf_create() }).expect("failed to create ubpf_vm");
937    unsafe {
938      crate::ubpf::ubpf_toggle_bounds_check(vm.as_ptr(), false);
939      crate::ubpf::ubpf_set_jit_pointer_mask_and_offset(vm.as_ptr(), cage.mask(), cage.offset());
940    }
941    Self(vm)
942  }
943}
944
945impl Drop for Vm {
946  fn drop(&mut self) {
947    unsafe {
948      crate::ubpf::ubpf_destroy(self.0.as_ptr());
949    }
950  }
951}
952
953struct LoaderValidationScope {
954  previous: *const ProgramLoader,
955}
956
957impl LoaderValidationScope {
958  fn new(loader: &ProgramLoader) -> Self {
959    let previous = LOADING_PROGRAM_LOADER.with(|x| {
960      let previous = x.get();
961      x.set(loader as *const _);
962      previous
963    });
964    Self { previous }
965  }
966}
967
968impl Drop for LoaderValidationScope {
969  fn drop(&mut self) {
970    LOADING_PROGRAM_LOADER.with(|x| x.set(self.previous));
971  }
972}
973
974impl ProgramLoader {
975  /// Creates a new `ProgramLoader` to load eBPF code.
976  pub fn new(
977    rng: &mut impl rand::Rng,
978    event_listener: Arc<dyn ProgramEventListener>,
979    raw_helpers: &[&[(&'static str, Helper)]],
980  ) -> Self {
981    let helper_id_xor = rng.gen::<u16>();
982    let mut helpers_inverse: HashMap<&'static str, i32> = HashMap::new();
983    // Collect first to a HashMap then to a Vec to deduplicate
984    let mut shuffled_helpers = raw_helpers
985      .iter()
986      .flat_map(|x| x.iter().copied())
987      .collect::<HashMap<_, _>>()
988      .into_iter()
989      .collect::<Vec<_>>();
990    shuffled_helpers.shuffle(rng);
991    let mut helpers: Vec<(u16, &'static str, Helper)> = Vec::with_capacity(shuffled_helpers.len());
992
993    assert!(shuffled_helpers.len() <= 65535);
994
995    for (i, (name, helper)) in shuffled_helpers.into_iter().enumerate() {
996      let entropy = rng.gen::<u16>() & 0x7fff;
997      helpers.push((entropy, name, helper));
998      helpers_inverse.insert(
999        name,
1000        (((entropy as usize) << 16) | ((i + 1) ^ (helper_id_xor as usize))) as i32,
1001      );
1002    }
1003
1004    tracing::info!(?helpers_inverse, "generated helper table");
1005    Self {
1006      helper_id_xor,
1007      helpers: Arc::new(helpers),
1008      helpers_inverse,
1009      event_listener,
1010      code_size_limit: DEFAULT_CODE_SIZE_LIMIT,
1011      require_static_regions: false,
1012    }
1013  }
1014
1015  /// Requires every guest memory access to be statically routable to a single
1016  /// region (stack or read-only data). When enabled, loading fails if the
1017  /// region analysis cannot classify any load, store, or atomic — i.e. no
1018  /// access falls back to the dual-region runtime probe. Memory accesses
1019  /// reached only through unmodeled control flow (e.g. an argument pointer in a
1020  /// local function) count as unresolved and are rejected.
1021  pub fn require_static_region_analysis(mut self, require: bool) -> Self {
1022    self.require_static_regions = require;
1023    self
1024  }
1025
1026  /// Sets the maximum total size of JIT-compiled native code per program.
1027  ///
1028  /// Must be a non-zero multiple of 64 KiB so the code region stays
1029  /// page-aligned on all supported page sizes.
1030  ///
1031  /// Note for arm64: conditional branches and literal loads emitted by the
1032  /// JIT have a ±1 MiB range, and a section's helper calls reference a
1033  /// literal pool at the end of that section's code. Any single ELF section
1034  /// whose jitted code exceeds ~1 MiB is therefore rejected at load time
1035  /// ("ubpf: code translation failed") regardless of this limit; raising it
1036  /// past 1 MiB only adds room for more or larger sections within that
1037  /// per-section ceiling. x86-64 has no such per-section constraint.
1038  pub fn with_code_size_limit(mut self, limit: usize) -> Self {
1039    assert!(
1040      limit > 0 && limit % (64 * 1024) == 0,
1041      "code size limit must be a non-zero multiple of 64 KiB"
1042    );
1043    assert!(
1044      limit <= u32::MAX as usize,
1045      "code size limit must fit in u32"
1046    );
1047    self.code_size_limit = limit;
1048    self
1049  }
1050
1051  /// Loads an ELF image into a new `UnboundProgram`.
1052  pub fn load(&self, rng: &mut impl rand::Rng, elf: &[u8]) -> Result<UnboundProgram, Error> {
1053    self._load(rng, elf).map_err(Error)
1054  }
1055
1056  fn _load(&self, rng: &mut impl rand::Rng, elf: &[u8]) -> Result<UnboundProgram, RuntimeError> {
1057    let start_time = Instant::now();
1058    let cage = PointerCage::new(rng, SHADOW_STACK_SIZE, elf.len())?;
1059    let vm = Vm::new(&cage);
1060
1061    // Relocate ELF
1062    let code_sections = {
1063      // XXX: Although we are writing to the data region, we need to use `safe_deref_for_read`
1064      // here because the `_write` variant checks that the requested region is within the
1065      // stack. It's safe here because `freeze_data` is not yet called.
1066      let mut data = cage
1067        .safe_deref_for_read(cage.data_bottom(), elf.len())
1068        .unwrap();
1069      let data = unsafe { data.as_mut() };
1070      data.copy_from_slice(elf);
1071
1072      link_elf(data, cage.data_bottom(), &self.helpers_inverse).map_err(RuntimeError::Linker)?
1073    };
1074    cage.freeze_data();
1075
1076    let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
1077    if page_size < 0 {
1078      return Err(RuntimeError::PlatformError("failed to get page size"));
1079    }
1080    let page_size = page_size as usize;
1081
1082    // Allocate code memory
1083    let guard_size_before = rng.gen_range(16..128) * page_size;
1084    let mut guard_size_after = rng.gen_range(16..128) * page_size;
1085
1086    let code_len_allocated = self.code_size_limit;
1087    let code_mem = MmapRaw::from(
1088      MmapOptions::new()
1089        .len(code_len_allocated + guard_size_before + guard_size_after)
1090        .map_anon()
1091        .map_err(|_| RuntimeError::PlatformError("failed to allocate code memory"))?,
1092    );
1093
1094    unsafe {
1095      if crate::ubpf::ubpf_register_external_dispatcher(
1096        vm.0.as_ptr(),
1097        Some(tls_dispatcher),
1098        Some(std_validator),
1099      ) != 0
1100      {
1101        return Err(RuntimeError::PlatformError(
1102          "ubpf: failed to register external dispatcher",
1103        ));
1104      }
1105      if libc::mprotect(
1106        code_mem.as_mut_ptr() as *mut _,
1107        guard_size_before,
1108        libc::PROT_NONE,
1109      ) != 0
1110        || libc::mprotect(
1111          code_mem
1112            .as_mut_ptr()
1113            .offset((guard_size_before + code_len_allocated) as isize) as *mut _,
1114          guard_size_after,
1115          libc::PROT_NONE,
1116        ) != 0
1117      {
1118        return Err(RuntimeError::PlatformError("failed to protect guard pages"));
1119      }
1120    }
1121
1122    let mut entrypoints: HashMap<String, Entrypoint> = HashMap::new();
1123
1124    unsafe {
1125      // Translate eBPF to native code
1126      let mut code_slice = std::slice::from_raw_parts_mut(
1127        code_mem.as_mut_ptr().offset(guard_size_before as isize),
1128        code_len_allocated,
1129      );
1130      for (section_name, code_vaddr_size) in code_sections {
1131        if code_slice.is_empty() {
1132          return Err(RuntimeError::InvalidArgument(
1133            "no space left for jit compilation",
1134          ));
1135        }
1136
1137        crate::ubpf::ubpf_unload_code(vm.0.as_ptr());
1138
1139        let mut errmsg_ptr = std::ptr::null_mut();
1140        let code = cage
1141          .safe_deref_for_read(code_vaddr_size.0, code_vaddr_size.1)
1142          .unwrap();
1143        let code_bytes = std::slice::from_raw_parts(code.as_ptr() as *const u8, code.len());
1144        validate_local_call_graph(code_bytes).map_err(|err| {
1145          RuntimeError::InvalidArgumentOwned(format!(
1146            "local call graph validation failed in {section_name}: {err}"
1147          ))
1148        })?;
1149        let ret = {
1150          let validation_scope = LoaderValidationScope::new(self);
1151          let ret = crate::ubpf::ubpf_load(
1152            vm.0.as_ptr(),
1153            code.as_ptr() as *const _,
1154            code.len() as u32,
1155            &mut errmsg_ptr,
1156          );
1157          drop(validation_scope);
1158          ret
1159        };
1160        if ret != 0 {
1161          let errmsg = if errmsg_ptr.is_null() {
1162            "".to_string()
1163          } else {
1164            CStr::from_ptr(errmsg_ptr).to_string_lossy().into_owned()
1165          };
1166          if !errmsg_ptr.is_null() {
1167            libc::free(errmsg_ptr as _);
1168          }
1169          tracing::error!(section_name, error = errmsg, "failed to load code");
1170          return Err(RuntimeError::InvalidArgumentOwned(format!(
1171            "ubpf: code load failed: {errmsg}"
1172          )));
1173        }
1174
1175        // Statically classify each load's pointer region so the JIT can emit a
1176        // single-region bounds check instead of probing both. Misclassification
1177        // is safe: the retained single-region check turns it into a spurious
1178        // fault, never a cross-region access. The hint buffer must outlive the
1179        // translate call below.
1180        let region_analysis = crate::region_analysis::analyze(
1181          code_bytes,
1182          cage.data_bottom() as u64,
1183          cage.data_top() as u64,
1184        );
1185        if self.require_static_regions && !region_analysis.unresolved.is_empty() {
1186          return Err(RuntimeError::InvalidArgumentOwned(format!(
1187            "static region analysis failed in {section_name}: {} memory access(es) could not be \
1188             routed to a single region (instruction slots {:?})",
1189            region_analysis.unresolved.len(),
1190            region_analysis.unresolved,
1191          )));
1192        }
1193        let region_hints = region_analysis.hints;
1194        crate::ubpf::ubpf_set_region_hints(
1195          vm.0.as_ptr(),
1196          region_hints.as_ptr(),
1197          region_hints.len(),
1198        );
1199
1200        let mut written_len = code_slice.len();
1201        let ret = crate::ubpf::ubpf_translate_ex(
1202          vm.0.as_ptr(),
1203          code_slice.as_mut_ptr(),
1204          &mut written_len,
1205          &mut errmsg_ptr,
1206          crate::ubpf::JitMode_ExtendedJitMode,
1207        );
1208        // Clear the borrowed pointer before `region_hints` is dropped so the VM
1209        // never retains a dangling reference between sections.
1210        crate::ubpf::ubpf_set_region_hints(vm.0.as_ptr(), std::ptr::null(), 0);
1211        if ret != 0 {
1212          let errmsg = if errmsg_ptr.is_null() {
1213            "".to_string()
1214          } else {
1215            CStr::from_ptr(errmsg_ptr).to_string_lossy().into_owned()
1216          };
1217          if !errmsg_ptr.is_null() {
1218            libc::free(errmsg_ptr as _);
1219          }
1220          tracing::error!(section_name, error = errmsg, "failed to translate code");
1221          return Err(RuntimeError::InvalidArgumentOwned(format!(
1222            "ubpf: code translation failed: {errmsg}"
1223          )));
1224        }
1225
1226        assert!(written_len <= code_slice.len());
1227        entrypoints.insert(
1228          section_name,
1229          Entrypoint {
1230            code_ptr: code_mem.as_ptr() as usize + guard_size_before + code_len_allocated
1231              - code_slice.len(),
1232            code_len: written_len,
1233          },
1234        );
1235        code_slice = &mut code_slice[written_len..];
1236      }
1237
1238      // Align up code_len to page size
1239      let unpadded_code_len = code_len_allocated - code_slice.len();
1240      if std::env::var("JIT_DUMP").is_ok() {
1241        eprintln!(
1242          "[JITSIZE] elf={} native_unpadded={} buffer={}",
1243          elf.len(),
1244          unpadded_code_len,
1245          code_len_allocated
1246        );
1247        let native = std::slice::from_raw_parts(
1248          code_mem.as_ptr().offset(guard_size_before as isize),
1249          unpadded_code_len,
1250        );
1251        let mut hex = String::with_capacity(native.len() * 2);
1252        for b in native {
1253          hex.push_str(&format!("{b:02x}"));
1254        }
1255        eprintln!("[JITHEX] {hex}");
1256      }
1257      let code_len = (unpadded_code_len + page_size - 1) & !(page_size - 1);
1258      assert!(code_len <= code_len_allocated);
1259
1260      // RW- -> R-X
1261      // Also make the unused part of the pre-allocated code region PROT_NONE
1262      if libc::mprotect(
1263        code_mem.as_mut_ptr().offset(guard_size_before as isize) as *mut _,
1264        code_len,
1265        libc::PROT_READ | libc::PROT_EXEC,
1266      ) != 0
1267        || (code_len < code_len_allocated
1268          && libc::mprotect(
1269            code_mem
1270              .as_mut_ptr()
1271              .offset((guard_size_before + code_len) as isize) as *mut _,
1272            code_len_allocated - code_len,
1273            libc::PROT_NONE,
1274          ) != 0)
1275      {
1276        return Err(RuntimeError::PlatformError("failed to protect code memory"));
1277      }
1278
1279      guard_size_after += code_len_allocated - code_len;
1280
1281      tracing::info!(
1282        elf_size = elf.len(),
1283        native_code_addr = ?code_mem.as_ptr(),
1284        native_code_size = code_len,
1285        native_code_size_unpadded = unpadded_code_len,
1286        guard_size_before,
1287        guard_size_after,
1288        duration = ?start_time.elapsed(),
1289        cage_ptr = ?cage.region().as_ptr(),
1290        cage_mapped_size = cage.region().len(),
1291        "jit compiled program"
1292      );
1293
1294      Ok(UnboundProgram {
1295        id: NEXT_PROGRAM_ID.fetch_add(1, Ordering::Relaxed),
1296        _code_mem: code_mem,
1297        cage,
1298        helper_id_xor: self.helper_id_xor,
1299        helpers: self.helpers.clone(),
1300        event_listener: self.event_listener.clone(),
1301        entrypoints,
1302      })
1303    }
1304  }
1305}
1306
1307#[derive(Default)]
1308struct Dispatch {
1309  async_preemption: bool,
1310  memory_access_error: Option<usize>,
1311
1312  index: u32,
1313  arg1: u64,
1314  arg2: u64,
1315  arg3: u64,
1316  arg4: u64,
1317  arg5: u64,
1318}
1319
1320unsafe extern "C" fn tls_dispatcher(
1321  arg1: u64,
1322  arg2: u64,
1323  arg3: u64,
1324  arg4: u64,
1325  arg5: u64,
1326  index: std::os::raw::c_uint,
1327  _cookie: *mut std::os::raw::c_void,
1328) -> u64 {
1329  let yielder = ACTIVE_JIT_CODE_ZONE
1330    .with(|x| x.yielder.get())
1331    .expect("no yielder");
1332  let yielder = yielder.as_ref();
1333  let ret = yielder.suspend(Dispatch {
1334    async_preemption: false,
1335    memory_access_error: None,
1336    index,
1337    arg1,
1338    arg2,
1339    arg3,
1340    arg4,
1341    arg5,
1342  });
1343  ret
1344}
1345
1346unsafe extern "C" fn std_validator(
1347  index: std::os::raw::c_uint,
1348  _vm: *const crate::ubpf::ubpf_vm,
1349) -> bool {
1350  let loader = LOADING_PROGRAM_LOADER.with(|x| x.get());
1351  if loader.is_null() {
1352    return false;
1353  }
1354  let loader = &*loader;
1355  let entropy = (index >> 16) & 0xffff;
1356  let index = (((index & 0xffff) as u16) ^ loader.helper_id_xor).wrapping_sub(1);
1357  loader.helpers.get(index as usize).map(|x| x.0) == Some(entropy as u16)
1358}
1359
1360#[cfg(all(target_arch = "x86_64", target_os = "linux"))]
1361unsafe fn program_counter(uctx: *mut libc::ucontext_t) -> usize {
1362  (*uctx).uc_mcontext.gregs[libc::REG_RIP as usize] as usize
1363}
1364
1365#[cfg(all(target_arch = "aarch64", target_os = "linux"))]
1366unsafe fn program_counter(uctx: *mut libc::ucontext_t) -> usize {
1367  (*uctx).uc_mcontext.pc as usize
1368}
1369
1370unsafe extern "C" fn sigsegv_handler(
1371  _sig: i32,
1372  siginfo: *mut libc::siginfo_t,
1373  uctx: *mut libc::ucontext_t,
1374) {
1375  let fail = || restore_default_signal_handler(libc::SIGSEGV);
1376
1377  let Some((jit_code_zone, pointer_cage, yielder)) = ACTIVE_JIT_CODE_ZONE.with(|x| {
1378    if x.valid.load(Ordering::Relaxed) {
1379      compiler_fence(Ordering::Acquire);
1380      Some((
1381        x.code_range.get(),
1382        x.pointer_cage_protected_range.get(),
1383        x.yielder.get(),
1384      ))
1385    } else {
1386      None
1387    }
1388  }) else {
1389    return fail();
1390  };
1391
1392  let pc = program_counter(uctx);
1393
1394  if pc < jit_code_zone.0 || pc >= jit_code_zone.1 {
1395    return fail();
1396  }
1397
1398  // SEGV_MAPERR or SEGV_ACCERR.
1399  if (*siginfo).si_code != 1 && (*siginfo).si_code != 2 {
1400    return fail();
1401  }
1402
1403  let si_addr = (*siginfo).si_addr() as usize;
1404  if si_addr < pointer_cage.0 || si_addr >= pointer_cage.1 {
1405    return fail();
1406  }
1407
1408  let yielder = yielder.expect("no yielder").as_ref();
1409  yielder.suspend(Dispatch {
1410    memory_access_error: Some(si_addr),
1411    ..Default::default()
1412  });
1413}
1414
1415unsafe extern "C" fn sigusr1_handler(
1416  _sig: i32,
1417  _siginfo: *mut libc::siginfo_t,
1418  uctx: *mut libc::ucontext_t,
1419) {
1420  SIGUSR1_COUNTER.with(|x| x.set(x.get() + 1));
1421
1422  let Some((jit_code_zone, yielder)) = ACTIVE_JIT_CODE_ZONE.with(|x| {
1423    if x.valid.load(Ordering::Relaxed) {
1424      compiler_fence(Ordering::Acquire);
1425      Some((x.code_range.get(), x.yielder.get()))
1426    } else {
1427      None
1428    }
1429  }) else {
1430    return;
1431  };
1432  let pc = program_counter(uctx);
1433  if pc < jit_code_zone.0 || pc >= jit_code_zone.1 {
1434    return;
1435  }
1436
1437  let yielder = yielder.expect("no yielder").as_ref();
1438  yielder.suspend(Dispatch {
1439    async_preemption: true,
1440    ..Default::default()
1441  });
1442}
1443
1444unsafe fn restore_default_signal_handler(signum: i32) {
1445  let act = libc::sigaction {
1446    sa_sigaction: libc::SIG_DFL,
1447    sa_flags: libc::SA_SIGINFO,
1448    sa_mask: std::mem::zeroed(),
1449    sa_restorer: None,
1450  };
1451  if libc::sigaction(signum, &act, std::ptr::null_mut()) != 0 {
1452    libc::abort();
1453  }
1454}
1455
1456fn get_blocked_sigset() -> libc::sigset_t {
1457  unsafe {
1458    let mut s: libc::sigset_t = std::mem::zeroed();
1459    libc::sigaddset(&mut s, libc::SIGUSR1);
1460    libc::sigaddset(&mut s, libc::SIGSEGV);
1461    s
1462  }
1463}