Skip to main content

air_sys_syscall/
raw_syscall.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! `raw_syscall` — **trappe de sortie (escape hatch) syscall générique**.
6//!
7//! # ⚠️ EXCEPTION ASSUMÉE — À RETIRER (cf. `docs/EXCEPTIONS.md`, ADR-087)
8//!
9//! Cette fonction est une **entorse délibérée et temporaire** à deux règles :
10//! - **ADR-021 conv. 3** (« pas de wrapper générique pour syscalls multiplexés ;
11//!   chaque opération = une fonction dédiée typée ») ;
12//! - **la règle « la libc binde la couche 1, jamais la couche 0 »** (le seul
13//!   consommateur, le shim libc `syscall`, tape ici **directement**).
14//!
15//! **Raison d'être** : la libc C-ABI d'Air **doit** fournir la fonction POSIX/Linux
16//! `long syscall(long number, ...)`, que le **pal `unix` de Rust `std`** appelle **en
17//! direct** pour deux services **sans** fonction libc typée : `futex` (aucune fonction
18//! libc n'a jamais existé — base des `Mutex`/`Condvar`/`park` de std) et `gettid`
19//! (pas de wrapper garanti avant glibc 2.30). `syscall(2)` est, **par nature**, une
20//! trappe **non typée** — on ne peut pas la rendre typée sans la dénaturer.
21//!
22//! **Plan de retrait (action item amont)** : faire que le pal `std` n'ait **pas
23//! besoin** d'un `syscall` générique sur `*-linux-air` (router `futex`/`gettid` vers
24//! des fonctions typées, ou pal Air dédié) — **avec l'équipe Rust**. Cette fonction
25//! disparaît dès que l'amont est résolu.
26//!
27//! **Périmètre exercé par M5** : `hello-std` étant mono-thread, `futex` n'est **jamais
28//! appelé** au runtime (invoqué uniquement sur contention de verrou) ; seul `gettid`
29//! l'est éventuellement au démarrage. Le symbole doit néanmoins **exister** pour lier.
30
31use core::ffi::c_long;
32
33/// Exécute un **syscall arbitraire** : `number` dans le registre de numéro, `a1..a6`
34/// dans les six registres d'arguments (ABI System V Linux). Rend la valeur **brute** du
35/// noyau (une erreur est un `-errno` dans `[-4095, -1]` — la **conversion** en
36/// `-1`/`errno` est laissée à l'appelant libc, comme le `__syscall_ret` de musl).
37///
38/// # ⚠️ Escape hatch — cf. en-tête de module (exception à retirer)
39///
40/// # Safety
41///
42/// L'appelant garantit que `(number, a1..a6)` forment un **appel syscall valide** : tout
43/// argument interprété par le noyau comme un pointeur doit être valide pour l'accès
44/// (lecture/écriture) qu'implique `number`, et l'invariant mémoire du syscall visé doit
45/// être respecté. Aucune vérification n'est possible ici (numéro et sémantique opaques).
46#[cfg(target_arch = "x86_64")]
47#[inline]
48pub unsafe fn raw_syscall(
49    number: c_long,
50    a1: c_long,
51    a2: c_long,
52    a3: c_long,
53    a4: c_long,
54    a5: c_long,
55    a6: c_long,
56) -> c_long {
57    let ret: c_long;
58    // SAFETY: instruction `syscall` (x86_64). `number`→rax, `a1..a6`→rdi/rsi/rdx/r10/r8/r9
59    // (ABI syscall Linux). Le syscall peut lire/écrire la mémoire utilisateur selon `number`
60    // (aucune option `readonly`/`nomem`, ni `preserves_flags` : impossible à garantir pour un
61    // appel générique). `rcx`/`r11` sont clobberés par l'instruction. Préconditions =
62    // contrat de sûreté de l'appelant (cf. `# Safety`).
63    unsafe {
64        core::arch::asm!(
65            "syscall",
66            inout("rax") number => ret,
67            in("rdi") a1,
68            in("rsi") a2,
69            in("rdx") a3,
70            in("r10") a4,
71            in("r8") a5,
72            in("r9") a6,
73            lateout("rcx") _,
74            lateout("r11") _,
75            options(nostack),
76        );
77    }
78    ret
79}
80
81/// Exécute un **syscall arbitraire** (aarch64) — cf. la variante x86_64 pour le contrat.
82///
83/// # ⚠️ Escape hatch — cf. en-tête de module (exception à retirer)
84///
85/// # Safety
86///
87/// Identique à la variante x86_64 : `(number, a1..a6)` forment un appel syscall valide.
88#[cfg(target_arch = "aarch64")]
89#[inline]
90pub unsafe fn raw_syscall(
91    number: c_long,
92    a1: c_long,
93    a2: c_long,
94    a3: c_long,
95    a4: c_long,
96    a5: c_long,
97    a6: c_long,
98) -> c_long {
99    let ret: c_long;
100    // SAFETY: instruction `svc 0` (aarch64). `number`→x8, `a1..a6`→x0..x5 (ABI syscall
101    // Linux). Comme sur x86_64, aucune option mémoire/flags n'est posée (appel générique) ;
102    // les préconditions relèvent du contrat de sûreté de l'appelant (cf. `# Safety`).
103    unsafe {
104        core::arch::asm!(
105            "svc 0",
106            in("x8") number,
107            inout("x0") a1 => ret,
108            in("x1") a2,
109            in("x2") a3,
110            in("x3") a4,
111            in("x4") a5,
112            in("x5") a6,
113            options(nostack),
114        );
115    }
116    ret
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122    use crate::process::getpid;
123
124    /// `SYS_getpid` (sans argument, effet nul, valeur connue) — numéro par arche.
125    #[cfg(target_arch = "x86_64")]
126    const SYS_GETPID: c_long = 39;
127    #[cfg(target_arch = "aarch64")]
128    const SYS_GETPID: c_long = 172;
129
130    #[test]
131    fn raw_syscall_getpid_matches_typed_wrapper() {
132        // `getpid` : syscall sans argument, sans effet de bord, à valeur stable.
133        // SAFETY: `SYS_getpid` n'interprète aucun argument comme pointeur ⇒ `a1..a6`
134        // ignorés ; appel toujours valide.
135        let raw = unsafe { raw_syscall(SYS_GETPID, 0, 0, 0, 0, 0, 0) };
136        let expected = c_long::from(getpid().as_raw());
137        assert_eq!(raw, expected);
138        assert!(raw > 0, "pid strictement positif");
139    }
140}