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