Skip to main content

strat9_kernel/process/
elf.rs

1//! ELF64 loader for Strat9-OS.
2//!
3//! Parses ELF64 headers and loads PT_LOAD segments into a user address space,
4//! then creates a kernel task that trampolines into Ring 3 via IRETQ.
5//!
6//! Supports :
7//!   - ET_EXEC
8//!   - ET_DYN (PIE/static-PIE)
9//!   - ELF64 little-endian x86_64 binaries.
10//!
11//!
12//! Does not support (or need future fix) :
13//!
14//!   - FIx TODO : allocation heap during ELF loading
15//!     program_headers(elf_data, &header).collect() → Vec<Elf64Phdr>, Vec::new() for interp_phdrs, etc. The kernel uses alloc so it's normal, but allocation errors are not handled (no try_collect, no fallible GlobalAlloc).
16//!
17//!   - Fix TODO : find_free_vma_range : fallback hardcoded 0x1000_0000
18//!     If PIE_BASE_ADDR (0x1_0000_0000) fails, fallback to 0x1000_0000. This value is arbitrary and could overlap existing mappings if many libraries are loaded.
19//!
20//! Security:
21//!   - User stack has a guard page (USER_STACK_BASE - 4096) that is intentionally
22//!     left unmapped.  Stack underflows hit it and page-fault.
23//!
24use alloc::{sync::Arc, vec::Vec};
25use x86_64::{
26    structures::paging::{Mapper, Page, Size4KiB},
27    VirtAddr,
28};
29
30use crate::{
31    capability::Capability,
32    memory::address_space::{AddressSpace, VmaFlags, VmaPageSize, VmaType},
33    process::{
34        task::{CpuContext, KernelStack, ResumeKind, SyncUnsafeCell, Task},
35        TaskId, TaskPriority, TaskState,
36    },
37};
38
39// ---------------------------------------------------------------------------
40// ELF64 constants (relocation & dynamic tags — not covered by xmas-elf)
41// ---------------------------------------------------------------------------
42
43const ET_EXEC: u16 = 2;
44const ET_DYN: u16 = 3;
45const PT_LOAD: u32 = 1;
46const PT_DYNAMIC: u32 = 2;
47const PT_INTERP: u32 = 3;
48const PT_TLS: u32 = 7;
49const PF_X: u32 = 1;
50const PF_W: u32 = 2;
51const PF_R: u32 = 4;
52const DT_NULL: i64 = 0;
53const DT_RELA: i64 = 7;
54const DT_RELASZ: i64 = 8;
55const DT_RELAENT: i64 = 9;
56const DT_STRTAB: i64 = 5;
57const DT_SYMTAB: i64 = 6;
58const DT_SYMENT: i64 = 11;
59const DT_JMPREL: i64 = 23;
60const DT_PLTRELSZ: i64 = 2;
61const DT_PLTREL: i64 = 20;
62const DT_RELACOUNT: i64 = 0x6fff_fff9;
63const DT_RELR: i64 = 36;
64const DT_RELRSZ: i64 = 35;
65const DT_RELRENT: i64 = 37;
66const R_X86_64_RELATIVE: u32 = 8;
67const R_X86_64_64: u32 = 1;
68const R_X86_64_COPY: u32 = 5;
69const R_X86_64_GLOB_DAT: u32 = 6;
70const R_X86_64_JUMP_SLOT: u32 = 7;
71const R_X86_64_TPOFF64: u32 = 18;
72const R_X86_64_IRELATIVE: u32 = 37;
73
74/// Maximum virtual address we accept for user-space mappings.
75pub const USER_ADDR_MAX: u64 = 0x0000_8000_0000_0000;
76/// Preferred base when placing ET_DYN (PIE) images.
77const PIE_BASE_ADDR: u64 = 0x0000_0001_0000_0000;
78
79/// User stack location (below the non-canonical gap).
80pub const USER_STACK_BASE: u64 = 0x0000_7FFF_F000_0000;
81/// Number of 4 KiB pages for the user stack (16 pages = 64 KiB).
82pub const USER_STACK_PAGES: usize = 16;
83/// Top of the user stack (stack grows down).
84pub const USER_STACK_TOP: u64 = USER_STACK_BASE + (USER_STACK_PAGES as u64) * 4096;
85/// Guard page below the user stack : unmapped, catches stack underflows.
86pub const USER_STACK_GUARD: u64 = USER_STACK_BASE - 4096;
87
88/// Result of loading an ELF image into an address space.
89#[derive(Debug, Clone, Copy)]
90pub struct LoadedElfInfo {
91    pub runtime_entry: u64,
92    pub program_entry: u64,
93    pub phdr_vaddr: u64,
94    pub phent: u16,
95    pub phnum: u16,
96    pub interp_base: Option<u64>,
97    pub tls_vaddr: u64,
98    pub tls_filesz: u64,
99    pub tls_memsz: u64,
100    pub tls_align: u64,
101}
102
103// ---------------------------------------------------------------------------
104// ELF64 structures for kernel-internal use
105// ---------------------------------------------------------------------------
106
107/// Parsed ELF64 file header (copy-friendly, no borrows).
108#[derive(Debug, Clone, Copy)]
109struct Elf64Header {
110    e_type: u16,
111    e_entry: u64,
112    e_phoff: u64,
113    e_phentsize: u16,
114    e_phnum: u16,
115}
116
117/// Parsed ELF64 program header (copy-friendly, packed for raw byte reading).
118#[repr(C, packed)]
119#[derive(Debug, Clone, Copy)]
120struct Elf64Phdr {
121    p_type: u32,
122    p_flags: u32,
123    p_offset: u64,
124    p_vaddr: u64,
125    p_paddr: u64,
126    p_filesz: u64,
127    p_memsz: u64,
128    p_align: u64,
129}
130
131#[repr(C, packed)]
132#[derive(Debug, Clone, Copy)]
133struct Elf64Dyn {
134    d_tag: i64,
135    d_val: u64,
136}
137
138#[repr(C, packed)]
139#[derive(Debug, Clone, Copy)]
140struct Elf64Rela {
141    r_offset: u64,
142    r_info: u64,
143    r_addend: i64,
144}
145
146#[repr(C, packed)]
147#[derive(Debug, Clone, Copy)]
148struct Elf64Sym {
149    st_name: u32,
150    st_info: u8,
151    st_other: u8,
152    st_shndx: u16,
153    st_value: u64,
154    st_size: u64,
155}
156
157// ---------------------------------------------------------------------------
158// Parsing (uses xmas-elf for header validation)
159// ---------------------------------------------------------------------------
160
161/// Parse and validate the ELF64 file header from raw bytes.
162///
163/// Uses `xmas-elf` for magic/class/machine/version validation, then copies
164/// the fields we need into a local `Copy` struct.
165fn parse_header(data: &[u8]) -> Result<Elf64Header, &'static str> {
166    let elf = xmas_elf::ElfFile::new(data).map_err(|_| "Invalid ELF header")?;
167
168    let hdr = elf.header.pt2;
169
170    // Type: executable or shared object (PIE/static PIE)
171    let e_type = hdr.type_().0;
172    if e_type != ET_EXEC && e_type != ET_DYN {
173        return Err("Unsupported ELF type (expected ET_EXEC or ET_DYN)");
174    }
175
176    let e_entry = hdr.entry_point();
177    // Entry point must be canonical user space (for ET_DYN this is relative and
178    // validated again after relocation). ET_EXEC with e_entry=0 is handled later.
179    if e_entry >= USER_ADDR_MAX {
180        return Err("Entry point outside user address range");
181    }
182
183    let e_phentsize = hdr.ph_entry_size();
184    let e_phoff = hdr.ph_offset();
185    let e_phnum = hdr.ph_count();
186
187    // Sanity check program headers
188    if e_phentsize as usize != core::mem::size_of::<xmas_elf::program::ProgramHeader>() {
189        return Err("Unexpected phentsize");
190    }
191
192    let ph_end = (e_phoff as usize)
193        .checked_add((e_phnum as usize) * (e_phentsize as usize))
194        .ok_or("Program header table overflows")?;
195    if ph_end > data.len() {
196        return Err("Program headers extend past file");
197    }
198
199    Ok(Elf64Header {
200        e_type,
201        e_entry,
202        e_phoff,
203        e_phentsize,
204        e_phnum,
205    })
206}
207
208/// Iterate over program headers in the ELF.
209fn program_headers<'a>(
210    data: &'a [u8],
211    header: &Elf64Header,
212) -> impl Iterator<Item = Elf64Phdr> + 'a {
213    let phoff = header.e_phoff as usize;
214    let phsize = header.e_phentsize as usize;
215    let phnum = header.e_phnum as usize;
216
217    (0..phnum).map(move |i| {
218        let offset = phoff + i * phsize;
219        // SAFETY: parse_header already validated that all program headers fit
220        // within `data`, and Elf64Phdr is packed (align 1).
221        unsafe { core::ptr::read_unaligned(data.as_ptr().add(offset) as *const Elf64Phdr) }
222    })
223}
224
225/// Parses interp path.
226fn parse_interp_path<'a>(
227    elf_data: &'a [u8],
228    phdrs: &[Elf64Phdr],
229) -> Result<Option<&'a str>, &'static str> {
230    let Some(interp) = phdrs.iter().find(|ph| ph.p_type == PT_INTERP) else {
231        return Ok(None);
232    };
233    if interp.p_filesz == 0 {
234        return Err("PT_INTERP has empty path");
235    }
236    let start = interp.p_offset as usize;
237    let end = start
238        .checked_add(interp.p_filesz as usize)
239        .ok_or("PT_INTERP range overflow")?;
240    if end > elf_data.len() {
241        return Err("PT_INTERP extends past file");
242    }
243    let raw = &elf_data[start..end];
244    let nul = raw
245        .iter()
246        .position(|&b| b == 0)
247        .ok_or("PT_INTERP path is not NUL terminated")?;
248    let s = core::str::from_utf8(&raw[..nul]).map_err(|_| "PT_INTERP path is not UTF-8")?;
249    if s.is_empty() {
250        return Err("PT_INTERP path is empty");
251    }
252    Ok(Some(s))
253}
254
255/// Performs the find relocated phdr vaddr operation.
256fn find_relocated_phdr_vaddr(
257    header: &Elf64Header,
258    phdrs: &[Elf64Phdr],
259    load_bias: u64,
260) -> Result<u64, &'static str> {
261    let phoff = header.e_phoff;
262    for ph in phdrs {
263        if ph.p_type != PT_LOAD || ph.p_filesz == 0 {
264            continue;
265        }
266        let file_start = ph.p_offset;
267        let file_end = ph
268            .p_offset
269            .checked_add(ph.p_filesz)
270            .ok_or("PHDR location overflow")?;
271        if phoff >= file_start && phoff < file_end {
272            let delta = phoff - file_start;
273            let vaddr = ph
274                .p_vaddr
275                .checked_add(delta)
276                .and_then(|v| v.checked_add(load_bias))
277                .ok_or("Relocated PHDR address overflow")?;
278            if vaddr >= USER_ADDR_MAX {
279                return Err("Relocated PHDR outside user address space");
280            }
281            return Ok(vaddr);
282        }
283    }
284    Err("Program headers are not covered by a PT_LOAD segment")
285}
286
287/// Reads elf from vfs.
288fn read_elf_from_vfs(path: &str) -> Result<Vec<u8>, &'static str> {
289    const MAX_ELF_SIZE: usize = 64 * 1024 * 1024;
290    let resolved_path =
291        crate::vfs::resolve_and_check_path_for_current_task(path, true, false, true)
292            .map_err(|_| "PT_INTERP execute denied")?;
293    let fd = crate::vfs::open(&resolved_path, crate::vfs::OpenFlags::READ)
294        .map_err(|_| "PT_INTERP open failed")?;
295    let mut out = Vec::new();
296    let mut buf = [0u8; 4096];
297
298    // Read the first chunk to validate ELF magic before loading the whole file.
299    let n = match crate::vfs::read(fd, &mut buf) {
300        Ok(0) => {
301            let _ = crate::vfs::close(fd);
302            return Err("PT_INTERP file is empty");
303        }
304        Ok(n) => n,
305        Err(_) => {
306            let _ = crate::vfs::close(fd);
307            return Err("PT_INTERP read failed");
308        }
309    };
310    if n < 4 || buf[..4] != [0x7F, b'E', b'L', b'F'] {
311        let _ = crate::vfs::close(fd);
312        return Err("PT_INTERP file is not an ELF");
313    }
314    out.extend_from_slice(&buf[..n]);
315
316    // Continue reading the rest of the file.
317    loop {
318        let n = match crate::vfs::read(fd, &mut buf) {
319            Ok(0) => break,
320            Ok(n) => n,
321            Err(_) => {
322                let _ = crate::vfs::close(fd);
323                return Err("PT_INTERP read failed");
324            }
325        };
326        if out.len().saturating_add(n) > MAX_ELF_SIZE {
327            let _ = crate::vfs::close(fd);
328            return Err("PT_INTERP file too large");
329        }
330        out.extend_from_slice(&buf[..n]);
331    }
332    let _ = crate::vfs::close(fd);
333    Ok(out)
334}
335
336/// Compute total mapped bounds for all PT_LOAD segments.
337fn compute_load_bounds(phdrs: &[Elf64Phdr]) -> Result<(u64, u64), &'static str> {
338    let mut min_vaddr = u64::MAX;
339    let mut max_vaddr = 0u64;
340    let mut saw_load = false;
341
342    for phdr in phdrs {
343        if phdr.p_type != PT_LOAD {
344            continue;
345        }
346        if phdr.p_memsz == 0 {
347            continue;
348        }
349        saw_load = true;
350
351        if phdr.p_memsz < phdr.p_filesz {
352            return Err("PT_LOAD memsz < filesz");
353        }
354
355        // ELF requires p_vaddr % page == p_offset % page for PT_LOAD.
356        if ((phdr.p_vaddr ^ phdr.p_offset) & 0xFFF) != 0 {
357            return Err("PT_LOAD alignment mismatch (vaddr/offset)");
358        }
359
360        let seg_end = phdr
361            .p_vaddr
362            .checked_add(phdr.p_memsz)
363            .ok_or("PT_LOAD vaddr+memsz overflow")?;
364        if seg_end > USER_ADDR_MAX {
365            return Err("PT_LOAD exceeds user address space");
366        }
367
368        let seg_start_page = phdr.p_vaddr & !0xFFF;
369        let seg_end_page = (seg_end + 0xFFF) & !0xFFF;
370        min_vaddr = min_vaddr.min(seg_start_page);
371        max_vaddr = max_vaddr.max(seg_end_page);
372    }
373
374    if !saw_load {
375        return Err("ELF has no PT_LOAD segments");
376    }
377    Ok((min_vaddr, max_vaddr))
378}
379
380/// Compute load bias and relocated entry for ET_EXEC / ET_DYN.
381fn compute_load_bias_and_entry(
382    user_as: &AddressSpace,
383    header: &Elf64Header,
384    phdrs: &[Elf64Phdr],
385) -> Result<(u64, u64), &'static str> {
386    let (min_vaddr, max_vaddr) = compute_load_bounds(phdrs)?;
387    let span = max_vaddr
388        .checked_sub(min_vaddr)
389        .ok_or("Invalid PT_LOAD bounds")?;
390
391    let load_bias = if header.e_type == ET_EXEC {
392        0
393    } else {
394        let n_pages = (span as usize).div_ceil(4096);
395        let load_base = user_as
396            .find_free_vma_range(PIE_BASE_ADDR, n_pages, VmaPageSize::Small)
397            .or_else(|| {
398                user_as.find_free_vma_range(0x0000_0000_1000_0000, n_pages, VmaPageSize::Small)
399            })
400            .ok_or("No virtual range for ET_DYN image")?;
401        load_base
402            .checked_sub(min_vaddr)
403            .ok_or("ET_DYN load bias underflow")?
404    };
405
406    let relocated_end = max_vaddr
407        .checked_add(load_bias)
408        .ok_or("Relocated PT_LOAD range overflow")?;
409    if relocated_end > USER_ADDR_MAX {
410        return Err("Relocated PT_LOAD range exceeds user space");
411    }
412
413    let entry_raw = if header.e_type == ET_EXEC && header.e_entry == 0 {
414        let fallback = phdrs
415            .iter()
416            .find(|ph| ph.p_type == PT_LOAD && ph.p_memsz != 0 && (ph.p_flags & PF_X) != 0)
417            .map(|ph| ph.p_vaddr)
418            .ok_or("ET_EXEC has null entry and no executable PT_LOAD")?;
419        log::warn!(
420            "[elf] ET_EXEC has null entry, using fallback executable segment vaddr={:#x}",
421            fallback
422        );
423        fallback
424    } else {
425        header.e_entry
426    };
427
428    let relocated_entry = entry_raw
429        .checked_add(load_bias)
430        .ok_or("Relocated entry overflow")?;
431    if relocated_entry == 0 || relocated_entry >= USER_ADDR_MAX {
432        return Err("Relocated entry outside user space");
433    }
434
435    Ok((load_bias, relocated_entry))
436}
437
438/// Performs the apply segment permissions operation.
439fn apply_segment_permissions(
440    user_as: &AddressSpace,
441    page_start: u64,
442    page_count: usize,
443    flags: VmaFlags,
444) -> Result<(), &'static str> {
445    use x86_64::registers::control::Cr3;
446
447    let pte_flags = flags.to_page_flags();
448    // SAFETY: loader owns this AddressSpace during image construction.
449    let mut mapper = unsafe { user_as.mapper() };
450    for i in 0..page_count {
451        let vaddr = page_start
452            .checked_add((i as u64) * 4096)
453            .ok_or("Permission update address overflow")?;
454        let page = Page::<Size4KiB>::from_start_address(VirtAddr::new(vaddr))
455            .map_err(|_| "Invalid page while updating segment flags")?;
456        // SAFETY: the page is already mapped by map_region for this segment.
457        let _ = unsafe {
458            mapper
459                .update_flags(page, pte_flags)
460                .map_err(|_| "Failed to update segment page flags")?
461        };
462        // We ignore flush here and do a targeted flush decision below.
463    }
464
465    // During ELF loading we update a freshly-created user address space that is
466    // not active on other CPUs.  Cross-CPU shootdowns here only add boot-time
467    // latency and can timeout while APs are not yet servicing IPIs.
468    // If this address space is currently active on this CPU, local invalidation
469    // is enough for the loader path.
470    let (current_cr3, _) = Cr3::read();
471    if current_cr3.start_address() == user_as.cr3() {
472        let end = page_start + (page_count as u64) * 4096;
473        crate::arch::x86_64::tlb::local_range(VirtAddr::new(page_start), VirtAddr::new(end));
474    }
475
476    Ok(())
477}
478
479/// Reads user mapped bytes.
480fn read_user_mapped_bytes(
481    user_as: &AddressSpace,
482    mut vaddr: u64,
483    out: &mut [u8],
484) -> Result<(), &'static str> {
485    let end = vaddr
486        .checked_add(out.len() as u64)
487        .ok_or("Read range overflow")?;
488    if end > USER_ADDR_MAX {
489        return Err("Read range outside user space");
490    }
491    let mut copied = 0usize;
492    while copied < out.len() {
493        let page_off = (vaddr & 0xFFF) as usize;
494        let chunk = core::cmp::min(out.len() - copied, 4096 - page_off);
495        let phys = user_as
496            .translate(VirtAddr::new(vaddr))
497            .ok_or("Failed to translate mapped user bytes")?;
498        let paddr = phys.as_u64();
499        if paddr == 0 {
500            return Err("Translated physical address is null");
501        }
502        let src = crate::memory::phys_to_virt(paddr) as *const u8;
503        if src.is_null() {
504            return Err("HHDM-mapped source is null");
505        }
506        // SAFETY: src points to mapped physical memory via HHDM.
507        // The address was just validated non-null, and the translate()
508        // call guarantees the virtual address is backed by a valid frame.
509        unsafe { core::ptr::copy_nonoverlapping(src, out.as_mut_ptr().add(copied), chunk) };
510        copied += chunk;
511        vaddr = vaddr
512            .checked_add(chunk as u64)
513            .ok_or("Virtual address overflow while reading mapped bytes")?;
514    }
515    Ok(())
516}
517
518/// Writes user mapped bytes.
519fn write_user_mapped_bytes(
520    user_as: &AddressSpace,
521    mut vaddr: u64,
522    src: &[u8],
523) -> Result<(), &'static str> {
524    let end = vaddr
525        .checked_add(src.len() as u64)
526        .ok_or("Write range overflow")?;
527    if end > USER_ADDR_MAX {
528        return Err("Write range outside user space");
529    }
530    let mut written = 0usize;
531    while written < src.len() {
532        let page_off = (vaddr & 0xFFF) as usize;
533        let chunk = core::cmp::min(src.len() - written, 4096 - page_off);
534        let phys = user_as
535            .translate(VirtAddr::new(vaddr))
536            .ok_or("Failed to translate relocation target")?;
537        let paddr = phys.as_u64();
538        if paddr == 0 {
539            return Err("Translated physical address is null");
540        }
541        let dst = crate::memory::phys_to_virt(paddr) as *mut u8;
542        if dst.is_null() {
543            return Err("HHDM-mapped destination is null");
544        }
545        // SAFETY: destination points to mapped user frame through HHDM.
546        // The address was just validated non-null, and the translate()
547        // call guarantees the virtual address is backed by a valid frame.
548        unsafe { core::ptr::copy_nonoverlapping(src.as_ptr().add(written), dst, chunk) };
549        written += chunk;
550        vaddr = vaddr
551            .checked_add(chunk as u64)
552            .ok_or("Virtual address overflow while writing mapped bytes")?;
553    }
554    Ok(())
555}
556
557/// Reads user u64.
558fn read_user_u64(user_as: &AddressSpace, vaddr: u64) -> Result<u64, &'static str> {
559    let mut raw = [0u8; 8];
560    read_user_mapped_bytes(user_as, vaddr, &mut raw)?;
561    Ok(u64::from_le_bytes(raw))
562}
563
564/// Writes user u64.
565fn write_user_u64(user_as: &AddressSpace, vaddr: u64, value: u64) -> Result<(), &'static str> {
566    write_user_mapped_bytes(user_as, vaddr, &value.to_le_bytes())
567}
568
569/// Calls a user-space IFUNC resolver function and returns its result.
570///
571/// The resolver is located at `resolver_vaddr` in the user address space.
572/// All RELATIVE relocations for this binary must have been applied first so
573/// that the resolver's own calls/addresses are correct.
574fn call_ifunc_resolver(user_as: &AddressSpace, resolver_vaddr: u64) -> Result<u64, &'static str> {
575    if resolver_vaddr >= USER_ADDR_MAX {
576        return Err("IFUNC resolver address outside user space");
577    }
578    let phys = user_as
579        .translate(VirtAddr::new(resolver_vaddr))
580        .ok_or("IFUNC resolver page not mapped")?;
581    let hhdm_ptr = crate::memory::phys_to_virt(phys.as_u64());
582    // SAFETY: hhdm_ptr points to a user page containing executable code.
583    // The resolver is a simple function that returns a u64; it must not
584    // access kernel state.  All RELATIVE relocations for this binary have
585    // already been applied, so the resolver's own target addresses are valid.
586    let resolver: extern "C" fn() -> u64 = unsafe { core::mem::transmute(hhdm_ptr as *const ()) };
587    Ok(resolver())
588}
589
590/// Performs the apply relr relocations operation.
591fn apply_relr_relocations(
592    user_as: &AddressSpace,
593    load_bias: u64,
594    relr_base: u64,
595    relr_size: usize,
596    relr_ent: usize,
597) -> Result<usize, &'static str> {
598    if relr_size == 0 {
599        return Ok(0);
600    }
601    if relr_ent != core::mem::size_of::<u64>() {
602        return Err("Unsupported DT_RELRENT size");
603    }
604    if relr_size % relr_ent != 0 {
605        return Err("DT_RELR table size is not aligned");
606    }
607
608    let count = relr_size / relr_ent;
609    let mut applied = 0usize;
610    let mut where_addr = 0u64;
611
612    for i in 0..count {
613        let entry_addr = relr_base
614            .checked_add((i * relr_ent) as u64)
615            .ok_or("DT_RELR walk overflow")?;
616        let entry = read_user_u64(user_as, entry_addr)?;
617
618        if (entry & 1) == 0 {
619            where_addr = load_bias
620                .checked_add(entry)
621                .ok_or("DT_RELR absolute relocation overflow")?;
622            if where_addr >= USER_ADDR_MAX {
623                return Err("DT_RELR target outside user space");
624            }
625            let cur = read_user_u64(user_as, where_addr)?;
626            write_user_u64(
627                user_as,
628                where_addr,
629                cur.checked_add(load_bias)
630                    .ok_or("DT_RELR relocated value overflow")?,
631            )?;
632            where_addr = where_addr
633                .checked_add(8)
634                .ok_or("DT_RELR where pointer overflow")?;
635            applied += 1;
636        } else {
637            let mut bitmap = entry >> 1;
638            for bit in 0..63u64 {
639                if (bitmap & 1) != 0 {
640                    let slot = where_addr
641                        .checked_add(bit * 8)
642                        .ok_or("DT_RELR bitmap target overflow")?;
643                    if slot >= USER_ADDR_MAX {
644                        return Err("DT_RELR bitmap target outside user space");
645                    }
646                    let cur = read_user_u64(user_as, slot)?;
647                    write_user_u64(
648                        user_as,
649                        slot,
650                        cur.checked_add(load_bias)
651                            .ok_or("DT_RELR bitmap relocated value overflow")?,
652                    )?;
653                    applied += 1;
654                }
655                bitmap >>= 1;
656                if bitmap == 0 {
657                    break;
658                }
659            }
660            where_addr = where_addr
661                .checked_add(63 * 8)
662                .ok_or("DT_RELR where advance overflow")?;
663        }
664    }
665    Ok(applied)
666}
667
668/// Performs the apply dynamic relocations operation.
669fn apply_dynamic_relocations(
670    user_as: &AddressSpace,
671    phdrs: &[Elf64Phdr],
672    elf_type: u16,
673    load_bias: u64,
674) -> Result<(), &'static str> {
675    if elf_type != ET_DYN {
676        return Ok(());
677    }
678
679    let dynamic = phdrs.iter().find(|ph| ph.p_type == PT_DYNAMIC);
680    let Some(dynamic_ph) = dynamic else {
681        return Ok(());
682    };
683    if dynamic_ph.p_filesz == 0 {
684        return Ok(());
685    }
686
687    let dyn_addr = dynamic_ph
688        .p_vaddr
689        .checked_add(load_bias)
690        .ok_or("PT_DYNAMIC relocated address overflow")?;
691    let dyn_file_size = dynamic_ph.p_filesz as usize;
692    let dyn_count = dyn_file_size / core::mem::size_of::<Elf64Dyn>();
693    // Read the entire .dynamic section at once to avoid O(n) page-table walks.
694    let mut dyn_buf = alloc::vec![0u8; dyn_file_size];
695    read_user_mapped_bytes(user_as, dyn_addr, &mut dyn_buf)?;
696    let dyn_slice: &[Elf64Dyn] =
697        unsafe { core::slice::from_raw_parts(dyn_buf.as_ptr() as *const Elf64Dyn, dyn_count) };
698
699    let mut rela_addr: Option<u64> = None;
700    let mut rela_size: usize = 0;
701    let mut rela_ent: usize = core::mem::size_of::<Elf64Rela>();
702    let mut jmprel_addr: Option<u64> = None;
703    let mut jmprel_size: usize = 0;
704    let mut pltrel_kind: Option<u64> = None;
705    let mut symtab_addr: Option<u64> = None;
706    let mut sym_ent: usize = core::mem::size_of::<Elf64Sym>();
707    let _strtab_addr: Option<u64> = None;
708    let mut rela_count_hint: Option<usize> = None;
709    let mut relr_addr: Option<u64> = None;
710    let mut relr_size: usize = 0;
711    let mut relr_ent: usize = 0;
712
713    for i in 0..dyn_count {
714        let dyn_entry = &dyn_slice[i];
715
716        match dyn_entry.d_tag {
717            DT_NULL => break,
718            DT_RELA => {
719                rela_addr = Some(
720                    dyn_entry
721                        .d_val
722                        .checked_add(load_bias)
723                        .ok_or("DT_RELA relocated address overflow")?,
724                )
725            }
726            DT_RELASZ => rela_size = dyn_entry.d_val as usize,
727            DT_RELAENT => rela_ent = dyn_entry.d_val as usize,
728            DT_RELACOUNT => rela_count_hint = Some(dyn_entry.d_val as usize),
729            DT_JMPREL => {
730                jmprel_addr = Some(
731                    dyn_entry
732                        .d_val
733                        .checked_add(load_bias)
734                        .ok_or("DT_JMPREL relocated address overflow")?,
735                )
736            }
737            DT_PLTRELSZ => jmprel_size = dyn_entry.d_val as usize,
738            DT_PLTREL => pltrel_kind = Some(dyn_entry.d_val),
739            DT_SYMTAB => {
740                symtab_addr = Some(
741                    dyn_entry
742                        .d_val
743                        .checked_add(load_bias)
744                        .ok_or("DT_SYMTAB relocated address overflow")?,
745                )
746            }
747            DT_SYMENT => sym_ent = dyn_entry.d_val as usize,
748            DT_STRTAB => {
749                let _ = dyn_entry
750                    .d_val
751                    .checked_add(load_bias)
752                    .ok_or("DT_STRTAB relocated address overflow")?;
753            }
754            DT_RELR => {
755                relr_addr = Some(
756                    dyn_entry
757                        .d_val
758                        .checked_add(load_bias)
759                        .ok_or("DT_RELR relocated address overflow")?,
760                )
761            }
762            DT_RELRSZ => relr_size = dyn_entry.d_val as usize,
763            DT_RELRENT => relr_ent = dyn_entry.d_val as usize,
764            _ => {}
765        }
766    }
767
768    let mut relr_applied = 0usize;
769    if let Some(relr_base) = relr_addr {
770        relr_applied = apply_relr_relocations(user_as, load_bias, relr_base, relr_size, relr_ent)?;
771    } else if relr_size != 0 || relr_ent != 0 {
772        return Err("DT_RELR metadata present without DT_RELR base");
773    }
774    if rela_ent != core::mem::size_of::<Elf64Rela>() {
775        return Err("Unsupported DT_RELAENT size");
776    }
777    if sym_ent != core::mem::size_of::<Elf64Sym>() {
778        return Err("Unsupported DT_SYMENT size");
779    }
780    if pltrel_kind.is_some() && pltrel_kind != Some(DT_RELA as u64) {
781        return Err("Only DT_PLTREL=DT_RELA is supported");
782    }
783
784    let read_sym_entry = |sym_idx: u32| -> Result<Elf64Sym, &'static str> {
785        let symtab = symtab_addr.ok_or("Missing DT_SYMTAB for symbol relocations")?;
786        let sym_addr = symtab
787            .checked_add((sym_idx as u64) * (sym_ent as u64))
788            .ok_or("Symbol table address overflow")?;
789        let mut raw = [0u8; core::mem::size_of::<Elf64Sym>()];
790        read_user_mapped_bytes(user_as, sym_addr, &mut raw)?;
791        Ok(unsafe { core::ptr::read_unaligned(raw.as_ptr() as *const Elf64Sym) })
792    };
793
794    let resolve_sym =
795        |sym_idx: u32, with_bias: bool, check_def: bool| -> Result<u64, &'static str> {
796            if sym_idx == 0 {
797                return Ok(0);
798            }
799            let sym = read_sym_entry(sym_idx)?;
800            if check_def && sym.st_shndx == 0 {
801                return Err("Undefined symbol relocation not supported");
802            }
803            if with_bias {
804                sym.st_value
805                    .checked_add(load_bias)
806                    .ok_or("Symbol value relocation overflow")
807            } else {
808                Ok(sym.st_value)
809            }
810        };
811
812    let resolve_size = |sym_idx: u32| -> Result<u64, &'static str> {
813        if sym_idx == 0 {
814            return Ok(0);
815        }
816        let sym = read_sym_entry(sym_idx)?;
817        Ok(sym.st_size)
818    };
819
820    let apply_rela_table = |table_base: u64,
821                            table_size: usize,
822                            count_hint: Option<usize>|
823     -> Result<usize, &'static str> {
824        if table_size == 0 {
825            return Ok(0);
826        }
827        // Use the table size as the authoritative entry count.  DT_RELACOUNT is
828        // a *hint* from the linker that may undercount; honouring it with min()
829        // silently drops valid relocations.  We only validate the hint as a
830        // sanity bound (if provided).
831        let count = table_size / rela_ent;
832        if let Some(hint) = count_hint {
833            if hint > count {
834                return Err("DT_RELACOUNT exceeds actual RELA table size");
835            }
836        }
837        let mut applied = 0usize;
838        for i in 0..count {
839            let rela_addr_i = table_base
840                .checked_add((i * rela_ent) as u64)
841                .ok_or("Rela table overflow")?;
842            let mut raw = [0u8; core::mem::size_of::<Elf64Rela>()];
843            read_user_mapped_bytes(user_as, rela_addr_i, &mut raw)?;
844            // SAFETY: raw has exact size of Elf64Rela.
845            let rela = unsafe { core::ptr::read_unaligned(raw.as_ptr() as *const Elf64Rela) };
846
847            let r_type = (rela.r_info & 0xffff_ffff) as u32;
848            let r_sym = (rela.r_info >> 32) as u32;
849            let target = rela
850                .r_offset
851                .checked_add(load_bias)
852                .ok_or("Relocation target overflow")?;
853            if target >= USER_ADDR_MAX {
854                return Err("Relocation target outside user space");
855            }
856
857            let value = match r_type {
858                R_X86_64_RELATIVE => {
859                    if r_sym != 0 {
860                        return Err("R_X86_64_RELATIVE with non-zero symbol");
861                    }
862                    (load_bias as i128)
863                        .checked_add(rela.r_addend as i128)
864                        .ok_or("Relocation value overflow")?
865                }
866                R_X86_64_GLOB_DAT | R_X86_64_JUMP_SLOT | R_X86_64_64 => {
867                    let sym_val = resolve_sym(r_sym, true, true)? as i128;
868                    sym_val
869                        .checked_add(rela.r_addend as i128)
870                        .ok_or("Relocation value overflow")?
871                }
872                R_X86_64_COPY => {
873                    let sym_val = resolve_sym(r_sym, true, true)?;
874                    if sym_val == 0 {
875                        continue;
876                    }
877                    let sym_sz = resolve_size(r_sym)?;
878                    if sym_sz > 0 && sym_val < USER_ADDR_MAX {
879                        let mut tmp = [0u8; 256];
880                        let mut off = 0usize;
881                        while off < sym_sz as usize {
882                            let chunk = core::cmp::min(256, sym_sz as usize - off);
883                            read_user_mapped_bytes(
884                                user_as,
885                                sym_val + off as u64,
886                                &mut tmp[..chunk],
887                            )?;
888                            write_user_mapped_bytes(user_as, target + off as u64, &tmp[..chunk])?;
889                            off += chunk;
890                        }
891                    }
892                    applied += 1;
893                    continue;
894                }
895                R_X86_64_TPOFF64 => {
896                    let sym_val = if r_sym != 0 {
897                        resolve_sym(r_sym, false, false)? as i128
898                    } else {
899                        0i128
900                    };
901                    sym_val
902                        .checked_add(rela.r_addend as i128)
903                        .ok_or("TPOFF64 value overflow")?
904                }
905                R_X86_64_IRELATIVE => {
906                    // IRELATIVE: the target is a resolver function that must be
907                    // *called* to obtain the final value.  All RELATIVE
908                    // relocations for this binary have already been applied, so
909                    // the resolver's own addresses are correct.
910                    let resolver_vaddr = (load_bias as i128)
911                        .checked_add(rela.r_addend as i128)
912                        .ok_or("IRELATIVE resolver address overflow")?;
913                    if resolver_vaddr < 0 || resolver_vaddr as u64 >= USER_ADDR_MAX {
914                        return Err("IRELATIVE resolver outside user space");
915                    }
916                    let resolved = call_ifunc_resolver(user_as, resolver_vaddr as u64)?;
917                    resolved as i128
918                }
919                _ => {
920                    log::warn!("[elf] Unsupported relocation type {}", r_type);
921                    continue;
922                }
923            };
924            if value < 0 || value > u64::MAX as i128 {
925                return Err("Relocation value out of range");
926            }
927            let val_u64 = value as u64;
928            // Read back before write for diagnosis
929            if applied < 5 {
930                let r_addend_copy = rela.r_addend; // copy packed field to local
931                let mut before = [0u8; 8];
932                let _ = read_user_mapped_bytes(user_as, target, &mut before);
933                let before_val = u64::from_le_bytes(before);
934                crate::e9_println!(
935                    "[reloc] [{i}] r_type={} target={:#x} r_addend={:#x} value={:#x} before={:#x}",
936                    r_type,
937                    target,
938                    r_addend_copy,
939                    val_u64,
940                    before_val
941                );
942            }
943            write_user_mapped_bytes(user_as, target, &val_u64.to_le_bytes())?;
944            // Read back after write for diagnosis
945            if applied < 5 {
946                let mut after = [0u8; 8];
947                let _ = read_user_mapped_bytes(user_as, target, &mut after);
948                let after_val = u64::from_le_bytes(after);
949                crate::e9_println!(
950                    "[reloc] [{i}] after_write={:#x} (expected={:#x})",
951                    after_val,
952                    val_u64
953                );
954            }
955            // Catch any relocation that writes a kernel-range address into user space.
956            if val_u64 >= 0xffff_8000_0000_0000 {
957                let r_addend_copy = rela.r_addend;
958                crate::e9_println!(
959                    "[reloc-KERNEL-ADDR] [{i}] r_type={} target={:#x} r_addend={:#x} val={:#x} bias={:#x}",
960                    r_type, target, r_addend_copy, val_u64, load_bias
961                );
962            }
963            applied += 1;
964        }
965        Ok(applied)
966    };
967
968    let mut total_applied = 0usize;
969    crate::e9_println!(
970        "[reloc] apply_dynamic_relocations: bias={:#x} rela_addr={:?} rela_size={} rela_count={:?}",
971        load_bias,
972        rela_addr,
973        rela_size,
974        rela_count_hint
975    );
976    if let Some(rela_base) = rela_addr {
977        total_applied += apply_rela_table(rela_base, rela_size, rela_count_hint)?;
978    }
979    if let Some(jmprel_base) = jmprel_addr {
980        total_applied += apply_rela_table(jmprel_base, jmprel_size, None)?;
981    }
982
983    if total_applied > 0 {
984        crate::e9_println!(
985            "[reloc] applied {} RELA relocations (bias={:#x})",
986            total_applied,
987            load_bias
988        );
989    }
990    if relr_applied > 0 {
991        log::debug!("[elf] Applied {} RELR relocations", relr_applied);
992    }
993    Ok(())
994}
995
996// ---------------------------------------------------------------------------
997// Loading
998// ---------------------------------------------------------------------------
999
1000/// Convert ELF p_flags to VmaFlags.
1001fn elf_flags_to_vma(p_flags: u32) -> VmaFlags {
1002    VmaFlags {
1003        readable: p_flags & PF_R != 0,
1004        writable: p_flags & PF_W != 0,
1005        executable: p_flags & PF_X != 0,
1006        user_accessible: true,
1007    }
1008}
1009
1010/// Load a single PT_LOAD segment into the given address space.
1011///
1012/// Allocates physical frames, maps them with appropriate permissions, and
1013/// copies file data into the mapping. BSS (memsz > filesz) is already
1014/// zero-filled because `map_region` zeroes newly allocated frames.
1015fn load_segment(
1016    user_as: &AddressSpace,
1017    elf_data: &[u8],
1018    phdr: &Elf64Phdr,
1019    load_bias: u64,
1020) -> Result<(), &'static str> {
1021    let vaddr = phdr
1022        .p_vaddr
1023        .checked_add(load_bias)
1024        .ok_or("PT_LOAD relocated vaddr overflow")?;
1025    let memsz = phdr.p_memsz;
1026    let filesz = phdr.p_filesz;
1027    let offset = phdr.p_offset;
1028
1029    // Validate addresses are in user space
1030    if vaddr >= USER_ADDR_MAX {
1031        return Err("PT_LOAD vaddr outside user space");
1032    }
1033    let end = vaddr
1034        .checked_add(memsz)
1035        .ok_or("PT_LOAD vaddr+memsz overflows")?;
1036    if end > USER_ADDR_MAX {
1037        return Err("PT_LOAD segment extends past user space");
1038    }
1039
1040    // Validate file region
1041    let file_end = (offset as usize)
1042        .checked_add(filesz as usize)
1043        .ok_or("PT_LOAD offset+filesz overflows")?;
1044    if file_end > elf_data.len() {
1045        return Err("PT_LOAD file data extends past ELF");
1046    }
1047
1048    // Calculate page-aligned mapping
1049    let page_start = vaddr & !0xFFF;
1050    let page_end = (end + 0xFFF) & !0xFFF;
1051    let page_count = ((page_end - page_start) / 4096) as usize;
1052
1053    // Map writable during copy, then restore final ELF flags.
1054    let actual_flags = elf_flags_to_vma(phdr.p_flags);
1055    let load_flags = VmaFlags {
1056        readable: true,
1057        writable: true, // Need write access to copy data in
1058        executable: actual_flags.executable,
1059        user_accessible: true,
1060    };
1061
1062    let vma_type = if actual_flags.executable {
1063        VmaType::Code
1064    } else {
1065        VmaType::Anonymous
1066    };
1067    log::debug!(
1068        "[elf] map PT_LOAD: start={:#x} pages={} filesz={:#x}",
1069        page_start,
1070        page_count,
1071        filesz
1072    );
1073    user_as.map_region(
1074        page_start,
1075        page_count,
1076        load_flags,
1077        vma_type,
1078        VmaPageSize::Small,
1079    )?;
1080
1081    // Copy file data into the mapped pages.
1082    // Batch-translate all pages at once to avoid page-table walks per-chunk.
1083    if filesz > 0 {
1084        let src = &elf_data[offset as usize..file_end];
1085        let mut copied = 0usize;
1086
1087        // Collect physical addresses for all pages in the range.
1088        let n_vaddrs = ((page_end - page_start) / 4096) as usize;
1089        let mut phys_pages = alloc::vec::Vec::with_capacity(n_vaddrs);
1090        for i in 0..n_vaddrs {
1091            let vaddr = page_start + (i as u64) * 4096;
1092            let phys = user_as
1093                .translate(VirtAddr::new(vaddr))
1094                .ok_or("Failed to translate user page after mapping")?;
1095            phys_pages.push(phys);
1096        }
1097
1098        while copied < src.len() {
1099            let dst_vaddr = vaddr + copied as u64;
1100            let page_idx = ((dst_vaddr - page_start) / 4096) as usize;
1101            let page_offset = (dst_vaddr & 0xFFF) as usize;
1102            let chunk = core::cmp::min(src.len() - copied, 4096 - page_offset);
1103
1104            let phys = phys_pages[page_idx];
1105            let hhdm_ptr = crate::memory::phys_to_virt(phys.as_u64()) as *mut u8;
1106            // SAFETY: hhdm_ptr points to a freshly mapped, zeroed frame via HHDM.
1107            unsafe {
1108                core::ptr::copy_nonoverlapping(
1109                    src.as_ptr().add(copied),
1110                    hhdm_ptr.add(page_offset),
1111                    chunk,
1112                );
1113            }
1114            copied += chunk;
1115        }
1116    }
1117
1118    // Tighten PTE permissions after copy.
1119    apply_segment_permissions(user_as, page_start, page_count, actual_flags)?;
1120
1121    log::debug!(
1122        "  PT_LOAD: {:#x}..{:#x} ({} pages, file {:#x}+{:#x}, flags {:?})",
1123        page_start,
1124        page_end,
1125        page_count,
1126        offset,
1127        filesz,
1128        actual_flags,
1129    );
1130
1131    Ok(())
1132}
1133
1134// ---------------------------------------------------------------------------
1135// Task creation with IRETQ trampoline
1136// ---------------------------------------------------------------------------
1137
1138/// Parameters for the Ring 3 trampoline, stored in a static so the
1139/// Trampoline that switches to user address space and does IRETQ to Ring 3.
1140///
1141/// Parameters (entry point, stack top, arg0, address space) are read from the
1142/// *current task* so that each ELF task carries its own copy.  This makes the
1143/// trampoline safe under SMP: two tasks can run their trampolines concurrently
1144/// on different CPUs without any shared mutable state.
1145extern "C" fn elf_ring3_trampoline() -> ! {
1146    use crate::arch::x86_64::gdt;
1147    use core::sync::atomic::Ordering;
1148
1149    crate::e9_println!("[trace][elf] ring3_trampoline before current_task");
1150    let task = crate::process::scheduler::current_task_clone_spin_debug("ring3_trampoline")
1151        .expect("elf_ring3_trampoline: no current task");
1152    crate::e9_println!(
1153        "[trace][elf] ring3_trampoline enter tid={} name={}",
1154        task.id.as_u64(),
1155        task.name
1156    );
1157    crate::serial_println!(
1158        "[trace][elf] ring3_trampoline enter tid={} name={}",
1159        task.id.as_u64(),
1160        task.name
1161    );
1162    task.set_resume_kind(crate::process::task::ResumeKind::IretFrame);
1163
1164    let user_rip = task.trampoline_entry.load(Ordering::Acquire);
1165    let user_rsp = task.trampoline_stack_top.load(Ordering::Acquire);
1166    let user_arg0 = task.trampoline_arg0.load(Ordering::Acquire);
1167    crate::e9_println!(
1168        "[trace][elf] ring3_trampoline args tid={} rip={:#x} rsp={:#x} arg0={:#x}",
1169        task.id.as_u64(),
1170        user_rip,
1171        user_rsp,
1172        user_arg0
1173    );
1174    crate::serial_println!(
1175        "[trace][elf] ring3_trampoline args tid={} rip={:#x} rsp={:#x}",
1176        task.id.as_u64(),
1177        user_rip,
1178        user_rsp
1179    );
1180
1181    // Probe: read GOT entries via HHDM before switching to user AS.
1182    // This is the last kernel-owned moment before user execution begins.
1183    // If values here are wrong, the bug is in load/relocation, not in
1184    // something that happens after this point.
1185    {
1186        // SAFETY: Kernel still holds the boot/kernel CR3. HHDM is valid.
1187        unsafe {
1188            let as_ref = task.process.address_space_arc();
1189            let task_name: &str = &task.name;
1190            for test_off in [0x12920u64, 0x12928u64, 0x12930u64] {
1191                let vaddr = 0x100000000u64.wrapping_add(test_off);
1192                if let Some(phys) = as_ref.translate(VirtAddr::new(vaddr)) {
1193                    let ptr = crate::memory::phys_to_virt(phys.as_u64()) as *const u64;
1194                    let val = core::ptr::read_unaligned(ptr);
1195                    crate::e9_println!(
1196                        "[trampoline-got] tid={} name={} GOT[{:#x}]=phys={:#x} val={:#x}",
1197                        task.id.as_u64(),
1198                        task_name,
1199                        vaddr,
1200                        phys.as_u64(),
1201                        val
1202                    );
1203                } else {
1204                    crate::e9_println!(
1205                        "[trampoline-got] tid={} name={} GOT[{:#x}]=<not mapped>",
1206                        task.id.as_u64(),
1207                        task_name,
1208                        vaddr
1209                    );
1210                }
1211            }
1212        }
1213    }
1214
1215    // Switch to the user address space stored in the task.
1216    // SAFETY: The address space was set up during task creation and is valid.
1217    unsafe {
1218        let as_ref = task.process.address_space_arc();
1219        as_ref.switch_to();
1220    }
1221    crate::e9_println!(
1222        "[trace][elf] ring3_trampoline switch_to done tid={}",
1223        task.id.as_u64()
1224    );
1225    crate::serial_println!(
1226        "[trace][elf] ring3_trampoline switch_to done tid={}",
1227        task.id.as_u64()
1228    );
1229
1230    let user_cs = gdt::user_code_selector().0 as u64;
1231    let user_ss = gdt::user_data_selector().0 as u64;
1232    let user_rflags: u64 = 0x202; // IF=1, reserved bit 1 = 1
1233    crate::e9_println!(
1234        "[trace][elf] ring3_trampoline iret tid={} cs={:#x} ss={:#x} rflags={:#x}",
1235        task.id.as_u64(),
1236        user_cs,
1237        user_ss,
1238        user_rflags
1239    );
1240    crate::serial_println!(
1241        "[trace][elf] ring3_trampoline iret tid={} rip={:#x} rsp={:#x}",
1242        task.id.as_u64(),
1243        user_rip,
1244        user_rsp
1245    );
1246
1247    // ----- Pre-iret LAPIC timer diagnostic -----
1248    // Verify that the APIC timer is actually running on this CPU before we
1249    // enter Ring 3 (if it is not, no timer tick = no heartbeat = silent hang).
1250    unsafe {
1251        let lvt = crate::arch::x86_64::apic::read_reg(crate::arch::x86_64::apic::REG_LVT_TIMER);
1252        let init_cnt =
1253            crate::arch::x86_64::apic::read_reg(crate::arch::x86_64::apic::REG_TIMER_INIT);
1254        let cur_cnt =
1255            crate::arch::x86_64::apic::read_reg(crate::arch::x86_64::apic::REG_TIMER_CURRENT);
1256        let rflags_now: u64;
1257        core::arch::asm!("pushfq; pop {}", out(reg) rflags_now, options(nostack));
1258        crate::e9_println!(
1259            "[trace][elf] pre-iret LAPIC: LVT={:#x} init={} cur={} IF={}",
1260            lvt,
1261            init_cnt,
1262            cur_cnt,
1263            (rflags_now >> 9) & 1
1264        );
1265        if lvt & (1 << 16) != 0 {
1266            crate::e9_println!(
1267                "[trace][elf] WARNING: LAPIC timer is MASKED (bit 16 set) : no ticks will fire!"
1268            );
1269        }
1270        if init_cnt == 0 {
1271            crate::e9_println!(
1272                "[trace][elf] WARNING: LAPIC timer init_count=0 : timer not started!"
1273            );
1274        }
1275    }
1276
1277    crate::arch::x86_64::ring3_diag::validate_ring3_state(
1278        user_rip,
1279        user_rsp,
1280        user_cs as u16,
1281        user_ss as u16,
1282    );
1283
1284    crate::e9_println!(
1285        "[elf] PRE-IRETQ tid={} rip={:#x} rsp={:#x} rflags={:#x}",
1286        task.id.as_u64(),
1287        user_rip,
1288        user_rsp,
1289        user_rflags
1290    );
1291
1292    // Probe E9 Rust : validate_ring3_state passé, on entre dans l'asm.
1293    // Si '0' est visible mais pas '1', le compilateur a inséré du code entre
1294    // les deux qui a planté (peu probable, mais élimine cette hypothèse).
1295    crate::e9_println!(
1296        "E9[0] pre-asm rip={:#x} rsp={:#x} cs={:#x} ss={:#x}",
1297        user_rip,
1298        user_rsp,
1299        user_cs,
1300        user_ss,
1301    );
1302
1303    // SAFETY: Valid user mappings have been set up. IRETQ switches to Ring 3.
1304    //
1305    // Interrupts must be masked in the final kernel instructions before
1306    // `swapgs ; iretq`. Otherwise a timer IRQ can land after `swapgs` but
1307    // before `iretq`, with `CS=0x8` and `GS=user`, and the first `gs:[..]`
1308    // access in the handler faults in the swapgs->iretq window.
1309    //
1310    // E9-hack probes
1311    // Each `out 0xe9, al` writes an ASCII character to QEMU's E9 port
1312    unsafe {
1313        core::arch::asm!(
1314            // Close the IRQ window before touching GS. `iretq` restores IF=1
1315            // from the user RFLAGS frame, so user mode still starts with
1316            // interrupts enabled.
1317            "cli",
1318
1319            //  Probe 1 : entrée dans le bloc asm ================================================================================
1320            // Les registres d'entrée sont déjà alloués par le compilateur ;
1321            // push/pop rax les laisse intacts.
1322            "push rax",
1323            "mov al, 0x31",     // '1'
1324            "out 0xe9, al",
1325            "pop rax",
1326
1327            //  Construction de la frame iretq ==========================================================================================
1328            // Ordre requis par IRETQ (dépilé dans l'ordre inverse) :
1329            //   [RSP+32] SS
1330            //   [RSP+24] user RSP
1331            //   [RSP+16] RFLAGS
1332            //   [RSP+8]  CS
1333            //   [RSP+0]  RIP  <--- RSP ici après les 5 push
1334            "push {ss}",
1335            "push {rsp_val}",
1336            "push {rflags}",
1337            "push {cs}",
1338            "push {rip}",
1339
1340            //  Probe 2 : frame iretq complète ==========================================================================================
1341            "push rax",
1342            "mov al, 0x32",     // '2'
1343            "out 0xe9, al",
1344            "pop rax",
1345
1346            //  Pre-fault the user code page ====================================================================
1347            // Touch the first byte at user_rip to trigger a demand page fault
1348            // while GS is still the kernel per-CPU block. Without this, the
1349            // iretq instruction itself can fault in the SWAPGS→Ring3 window,
1350            // producing a SWAPGS-WINDOW page fault (CS=Ring0 but GS=user).
1351            "mov rax, {rip}",
1352            "movzx rax, byte ptr [rax]",
1353
1354            //  Chargement de arg0 dans RDI ====================================================================================================
1355            "mov rdi, {arg0}",
1356
1357            //  Probe 3 : RDI chargé, juste avant SWAPGS ==================================================
1358            "push rax",
1359            "mov al, 0x33",     // '3'
1360            "out 0xe9, al",
1361            "pop rax",
1362
1363            //  SWAPGS : GS.base kernel <---> GS.base user ============================================================
1364            // Après cette instruction, GS pointe vers le bloc per-thread user.
1365            // Le push/pop ci-dessous ne touche pas GS, il est sûr.
1366            "swapgs",
1367
1368            //  Probe 4 : SWAPGS réussi, IRETQ imminent ============================================================
1369            // Si le double-fault survient sur iretq, '4' sera le DERNIER
1370            // caractère visible dans la console E9.
1371            "push rax",
1372            "mov al, 0x34",     // '4'
1373            "out 0xe9, al",
1374            "pop rax",
1375
1376            //  IRETQ : point de non-retour ==================================
1377            "iretq",
1378
1379            ss      = in(reg) user_ss,
1380            rsp_val = in(reg) user_rsp,
1381            rflags  = in(reg) user_rflags,
1382            cs      = in(reg) user_cs,
1383            rip     = in(reg) user_rip,
1384            arg0    = in(reg) user_arg0,
1385            options(noreturn),
1386        );
1387    }
1388}
1389
1390// ---------------------------------------------------------------------------
1391// Public API
1392// ---------------------------------------------------------------------------
1393/// Load an ELF64 binary and schedule it as a Ring 3 user task.
1394///
1395/// # Arguments
1396/// * `elf_data` : raw ELF file bytes (must remain valid until load completes).
1397/// * `name` : name for the task (debugging purposes).
1398///
1399/// # Returns
1400/// `Ok(())` on success, `Err` with a static error message on failure.
1401pub fn load_and_run_elf(elf_data: &[u8], name: &'static str) -> Result<TaskId, &'static str> {
1402    load_and_run_elf_with_caps(elf_data, name, &[])
1403}
1404
1405/// Load an ELF64 binary with command-line arguments and schedule it as a Ring 3 task.
1406///
1407/// `extra_args` maps to `argv[1..]`; `argv[0]` is always `name`.
1408pub fn load_and_run_elf_with_args(
1409    elf_data: &[u8],
1410    name: &'static str,
1411    extra_args: &[&str],
1412) -> Result<TaskId, &'static str> {
1413    let task = load_elf_task_inner(elf_data, name, extra_args, &[])?;
1414    let task_id = task.id;
1415    crate::process::add_task(task);
1416    Ok(task_id)
1417}
1418
1419/// Thin public wrapper that keeps the existing API stable.
1420pub fn load_elf_task_with_caps(
1421    elf_data: &[u8],
1422    name: &'static str,
1423    seed_caps: &[Capability],
1424) -> Result<Arc<Task>, &'static str> {
1425    load_elf_task_inner(elf_data, name, &[], seed_caps)
1426}
1427
1428/// Performs the load and run elf with caps operation.
1429pub fn load_and_run_elf_with_caps(
1430    elf_data: &[u8],
1431    name: &'static str,
1432    seed_caps: &[Capability],
1433) -> Result<TaskId, &'static str> {
1434    crate::e9_println!(
1435        "[trace][elf] load_and_run_elf enter name={} size={}",
1436        name,
1437        elf_data.len()
1438    );
1439    let task = load_elf_task_inner(elf_data, name, &[], seed_caps)?;
1440    let task_id = task.id;
1441    let runtime_entry = task
1442        .trampoline_entry
1443        .load(core::sync::atomic::Ordering::Acquire);
1444    crate::e9_println!(
1445        "[trace][elf] load_and_run_elf add_task begin tid={} entry={:#x}",
1446        task_id.as_u64(),
1447        runtime_entry
1448    );
1449    crate::process::add_task(task);
1450    crate::e9_println!(
1451        "[trace][elf] load_and_run_elf add_task done tid={}",
1452        task_id.as_u64()
1453    );
1454
1455    log::info!(
1456        "[elf] Task '{}' created: entry={:#x}, stack_top={:#x}",
1457        name,
1458        runtime_entry,
1459        USER_STACK_TOP,
1460    );
1461
1462    Ok(task_id)
1463}
1464
1465const AT_PHDR: u64 = 3;
1466const AT_PHENT: u64 = 4;
1467const AT_PHNUM: u64 = 5;
1468const AT_PAGESZ: u64 = 6;
1469const AT_BASE: u64 = 7;
1470const AT_ENTRY: u64 = 9;
1471const AT_RANDOM: u64 = 25;
1472
1473fn generate_aux_random_seed() -> [u8; 16] {
1474    let mut seed = [0u8; 16];
1475    crate::entropy::fill_random(&mut seed);
1476    seed
1477}
1478
1479/// Performs the push auxv operation.
1480fn push_auxv(user_as: &AddressSpace, sp: &mut u64, tag: u64, val: u64) -> Result<(), &'static str> {
1481    *sp -= 8;
1482    write_user_u64(user_as, *sp, val)?;
1483    *sp -= 8;
1484    write_user_u64(user_as, *sp, tag)?;
1485    Ok(())
1486}
1487
1488/// Performs the setup boot user stack operation.
1489/// Sets up the initial user-space stack for a freshly loaded ELF task.
1490///
1491/// Stack layout (low addr at bottom = first word read by `_start`):
1492/// ```
1493/// [sp+0]             argc
1494/// [sp+8]             argv[0] ptr  (program name)
1495/// [sp+8*(2..=argc)]  argv[1..] ptrs  (extra_args)
1496/// [sp+8*(argc+1)]    NULL  (argv terminator)
1497/// [sp+8*(argc+2)]    NULL  (envp terminator)
1498/// ...                auxv pairs
1499/// ```
1500fn setup_boot_user_stack(
1501    user_as: &AddressSpace,
1502    name: &str,
1503    extra_args: &[&str],
1504    phdr_vaddr: u64,
1505    phent: u16,
1506    phnum: u16,
1507    program_entry: u64,
1508    interp_base: Option<u64>,
1509) -> Result<u64, &'static str> {
1510    let mut sp = USER_STACK_TOP;
1511
1512    // Write argv[0] = program name (null-terminated)
1513    let name_nul_len = (name.len() + 1) as u64;
1514    sp -= name_nul_len;
1515    let argv0_ptr = sp;
1516    write_user_mapped_bytes(user_as, sp, name.as_bytes())?;
1517    write_user_mapped_bytes(user_as, sp + name.len() as u64, &[0])?;
1518
1519    // Write extra arg strings and record their user-space pointers
1520    let mut extra_ptrs: alloc::vec::Vec<u64> = alloc::vec::Vec::with_capacity(extra_args.len());
1521    for &arg in extra_args.iter() {
1522        let arg_nul_len = (arg.len() + 1) as u64;
1523        sp -= arg_nul_len;
1524        extra_ptrs.push(sp);
1525        write_user_mapped_bytes(user_as, sp, arg.as_bytes())?;
1526        write_user_mapped_bytes(user_as, sp + arg.len() as u64, &[0])?;
1527    }
1528
1529    sp -= 16;
1530    let random_ptr = sp;
1531    let random_seed = generate_aux_random_seed();
1532    write_user_mapped_bytes(user_as, sp, &random_seed)?;
1533
1534    sp &= !0xF;
1535    let auxv_pairs = if interp_base.is_some() { 8u64 } else { 7u64 };
1536    // argc(1) + argv[0..=N](1+N) + argv_NULL(1) + envp_NULL(1) + auxv(pairs*2)
1537    let stack_words = 4u64 + extra_args.len() as u64 + auxv_pairs * 2;
1538    let align_pad = (0u64.wrapping_sub(stack_words * 8)) & 0xF;
1539    sp -= align_pad;
1540
1541    // Auxv (written high-to-low since push_auxv decrements sp)
1542    push_auxv(user_as, &mut sp, 0, 0)?; // AT_NULL
1543    push_auxv(user_as, &mut sp, AT_RANDOM, random_ptr)?;
1544    push_auxv(user_as, &mut sp, AT_ENTRY, program_entry)?;
1545    if let Some(base) = interp_base {
1546        push_auxv(user_as, &mut sp, AT_BASE, base)?;
1547    }
1548    push_auxv(user_as, &mut sp, AT_PAGESZ, 4096)?;
1549    push_auxv(user_as, &mut sp, AT_PHNUM, phnum as u64)?;
1550    push_auxv(user_as, &mut sp, AT_PHENT, phent as u64)?;
1551    push_auxv(user_as, &mut sp, AT_PHDR, phdr_vaddr)?;
1552
1553    // envp NULL terminator
1554    sp -= 8;
1555    write_user_u64(user_as, sp, 0)?;
1556
1557    // argv NULL terminator
1558    sp -= 8;
1559    write_user_u64(user_as, sp, 0)?;
1560
1561    // Extra argv pointers in reverse (last arg highest in stack, first arg lowest)
1562    for &ptr in extra_ptrs.iter().rev() {
1563        sp -= 8;
1564        write_user_u64(user_as, sp, ptr)?;
1565    }
1566
1567    // argv[0] = program name
1568    sp -= 8;
1569    write_user_u64(user_as, sp, argv0_ptr)?;
1570
1571    // argc = 1 (name) + extra_args
1572    sp -= 8;
1573    write_user_u64(user_as, sp, 1u64 + extra_args.len() as u64)?;
1574
1575    debug_assert_eq!(sp & 0xF, 0);
1576    Ok(sp)
1577}
1578
1579/// Internal ELF task builder used by all public loading APIs.
1580/// `extra_args` are written to the user stack as argv[1..] after the program name.
1581fn load_elf_task_inner(
1582    elf_data: &[u8],
1583    name: &'static str,
1584    extra_args: &[&str],
1585    seed_caps: &[Capability],
1586) -> Result<Arc<Task>, &'static str> {
1587    crate::e9_println!(
1588        "[trace][elf] load_elf_task enter name={} size={}",
1589        name,
1590        elf_data.len()
1591    );
1592    log::info!("[elf] Loading ELF '{}'...", name);
1593
1594    // Step 1: Parse and validate ELF header
1595    crate::e9_println!("[trace][elf] load_elf_task parse_header begin");
1596    let header = parse_header(elf_data)?;
1597    crate::e9_println!(
1598        "[trace][elf] load_elf_task parse_header ok type={}",
1599        if header.e_type == ET_DYN {
1600            "ET_DYN"
1601        } else {
1602            "ET_EXEC"
1603        }
1604    );
1605    // Step 2: Create user address space
1606    crate::e9_println!("[trace][elf] load_elf_task user_as begin");
1607    let user_as = Arc::new(AddressSpace::new_user()?);
1608    crate::e9_println!("[trace][elf] load_elf_task user_as done");
1609
1610    let phdrs: Vec<Elf64Phdr> = program_headers(elf_data, &header).collect();
1611    let interp_path = parse_interp_path(elf_data, &phdrs)?;
1612    let (load_bias, entry) = compute_load_bias_and_entry(&user_as, &header, &phdrs)?;
1613    let phdr_vaddr = find_relocated_phdr_vaddr(&header, &phdrs, load_bias)?;
1614
1615    let phnum = header.e_phnum;
1616    crate::e9_println!(
1617        "[trace][elf] load_elf_task layout entry={:#x} bias={:#x} phdrs={}",
1618        entry,
1619        load_bias,
1620        phnum
1621    );
1622    log::info!(
1623        "[elf] ELF '{}': type={}, entry={:#x}, bias={:#x}, {} program headers",
1624        name,
1625        if header.e_type == ET_DYN {
1626            "ET_DYN"
1627        } else {
1628            "ET_EXEC"
1629        },
1630        entry,
1631        load_bias,
1632        phnum,
1633    );
1634
1635    // Step 3: Load all PT_LOAD segments
1636    let mut load_count = 0u32;
1637    for phdr in phdrs.iter() {
1638        if phdr.p_type == PT_LOAD && phdr.p_memsz != 0 {
1639            load_segment(&user_as, elf_data, phdr, load_bias)?;
1640            load_count += 1;
1641        }
1642    }
1643    if interp_path.is_none() {
1644        apply_dynamic_relocations(&user_as, &phdrs, header.e_type, load_bias)?;
1645    }
1646
1647    crate::e9_println!(
1648        "[trace][elf] load_elf_task segments_done count={} has_interp={}",
1649        load_count,
1650        interp_path.is_some()
1651    );
1652    log::info!("[elf] Loaded {} PT_LOAD segment(s)", load_count);
1653
1654    let mut runtime_entry = entry;
1655    let mut interp_base: Option<u64> = None;
1656    if let Some(path) = interp_path {
1657        let interp_data = read_elf_from_vfs(path)?;
1658        let interp_header = parse_header(&interp_data)?;
1659        let interp_phdrs: Vec<Elf64Phdr> = program_headers(&interp_data, &interp_header).collect();
1660        if parse_interp_path(&interp_data, &interp_phdrs)?.is_some() {
1661            return Err("Nested PT_INTERP is not supported");
1662        }
1663        let (interp_bias, interp_entry) =
1664            compute_load_bias_and_entry(&user_as, &interp_header, &interp_phdrs)?;
1665        let (interp_min_vaddr, _) = compute_load_bounds(&interp_phdrs)?;
1666        let mut interp_load_count = 0u32;
1667        for phdr in interp_phdrs.iter() {
1668            if phdr.p_type == PT_LOAD && phdr.p_memsz != 0 {
1669                load_segment(&user_as, &interp_data, phdr, interp_bias)?;
1670                interp_load_count += 1;
1671            }
1672        }
1673        apply_dynamic_relocations(&user_as, &interp_phdrs, interp_header.e_type, interp_bias)?;
1674        runtime_entry = interp_entry;
1675        interp_base = Some(interp_min_vaddr.saturating_add(interp_bias));
1676        log::info!(
1677            "[elf] PT_INTERP '{}' loaded: {} PT_LOAD, entry={:#x}",
1678            path,
1679            interp_load_count,
1680            runtime_entry
1681        );
1682    }
1683
1684    // TLS setup (Variant II: data at negative offsets from FS:0)
1685    let mut user_fs_base_val = 0u64;
1686    if let Some(tls) = phdrs.iter().find(|p| p.p_type == PT_TLS) {
1687        let tls_memsz = tls.p_memsz;
1688        let tls_filesz = tls.p_filesz;
1689        let tls_align = core::cmp::max(tls.p_align, 8).next_power_of_two();
1690        let aligned_memsz = (tls_memsz + tls_align - 1) & !(tls_align - 1);
1691        let total_size = aligned_memsz + 8;
1692        let n_tls_pages = ((total_size + 4095) / 4096) as usize;
1693        let tls_flags = VmaFlags {
1694            readable: true,
1695            writable: true,
1696            executable: false,
1697            user_accessible: true,
1698        };
1699        let tls_base = user_as
1700            .find_free_vma_range(0x7FFF_E000_0000, n_tls_pages, VmaPageSize::Small)
1701            .ok_or("No space for TLS block")?;
1702        user_as.map_region(
1703            tls_base,
1704            n_tls_pages,
1705            tls_flags,
1706            VmaType::Anonymous,
1707            VmaPageSize::Small,
1708        )?;
1709        if tls_filesz > 0 {
1710            let src_off = tls.p_offset as usize;
1711            let src_end = src_off
1712                .checked_add(tls_filesz as usize)
1713                .ok_or("PT_TLS offset+filesz overflows")?;
1714            if src_end > elf_data.len() {
1715                return Err("PT_TLS file data extends past ELF");
1716            }
1717            write_user_mapped_bytes(&user_as, tls_base, &elf_data[src_off..src_end])?;
1718        }
1719        let tp = tls_base + aligned_memsz;
1720        write_user_u64(&user_as, tp, tp)?;
1721        user_fs_base_val = tp;
1722    }
1723
1724    // Step 4: Map user stack
1725    let stack_flags = VmaFlags {
1726        readable: true,
1727        writable: true,
1728        executable: false,
1729        user_accessible: true,
1730    };
1731    user_as.map_region(
1732        USER_STACK_BASE,
1733        USER_STACK_PAGES,
1734        stack_flags,
1735        VmaType::Stack,
1736        VmaPageSize::Small,
1737    )?;
1738    // Guard page: a single unmapped page below the stack.  Any stack
1739    // underflow (push past the bottom) hits this and faults.  The page
1740    // is intentionally left unmapped : no VMA, no PTE.
1741    log::debug!(
1742        "[elf] User stack: {:#x}..{:#x} ({} pages), guard at {:#x}",
1743        USER_STACK_BASE,
1744        USER_STACK_TOP,
1745        USER_STACK_PAGES,
1746        USER_STACK_GUARD,
1747    );
1748
1749    let boot_sp = setup_boot_user_stack(
1750        &user_as,
1751        name,
1752        extra_args,
1753        phdr_vaddr,
1754        header.e_phentsize,
1755        header.e_phnum,
1756        entry,
1757        interp_base,
1758    )?;
1759
1760    // Step 5: Create kernel task : trampoline params are stored inside the task
1761    // itself so that concurrent SMP execution of multiple trampolines is safe.
1762    crate::e9_println!(
1763        "[trace][elf] load_elf_task kstack_begin size={}",
1764        Task::DEFAULT_STACK_SIZE
1765    );
1766    let kernel_stack = KernelStack::allocate(Task::DEFAULT_STACK_SIZE)?;
1767    crate::e9_println!(
1768        "[trace][elf] load_elf_task kstack_done virt={:#x} top={:#x}",
1769        kernel_stack.virt_base.as_u64(),
1770        kernel_stack.virt_base.as_u64() + kernel_stack.size as u64
1771    );
1772    let context = CpuContext::new(elf_ring3_trampoline as *const () as u64, &kernel_stack);
1773    let (pid, tid, tgid) = Task::allocate_process_ids();
1774    let fpu_state = crate::process::task::ExtendedState::new();
1775    let xcr0_mask = fpu_state.xcr0_mask;
1776
1777    let task = Arc::new(Task {
1778        id: TaskId::new(),
1779        pid,
1780        tid,
1781        tgid,
1782        pgid: core::sync::atomic::AtomicU32::new(pid),
1783        sid: core::sync::atomic::AtomicU32::new(pid),
1784        uid: core::sync::atomic::AtomicU32::new(0),
1785        euid: core::sync::atomic::AtomicU32::new(0),
1786        gid: core::sync::atomic::AtomicU32::new(0),
1787        egid: core::sync::atomic::AtomicU32::new(0),
1788        state: core::sync::atomic::AtomicU8::new(TaskState::Ready as u8),
1789        priority: TaskPriority::Normal,
1790        context: SyncUnsafeCell::new(context),
1791        resume_kind: SyncUnsafeCell::new(ResumeKind::RetFrame),
1792        interrupt_rsp: core::sync::atomic::AtomicU64::new(0),
1793        kernel_stack,
1794        user_stack: None,
1795        name,
1796        process: Arc::new(crate::process::process::Process::new(pid, user_as)),
1797        pending_signals: super::signal::SignalSet::new(),
1798        blocked_signals: super::signal::SignalSet::new(),
1799        irq_signal_delivery_blocked: core::sync::atomic::AtomicBool::new(false),
1800        signal_stack: SyncUnsafeCell::new(None),
1801        itimers: super::timer::ITimers::new(),
1802        wake_pending: core::sync::atomic::AtomicBool::new(false),
1803        wake_deadline_ns: core::sync::atomic::AtomicU64::new(0),
1804        trampoline_entry: core::sync::atomic::AtomicU64::new(runtime_entry),
1805        trampoline_stack_top: core::sync::atomic::AtomicU64::new(boot_sp),
1806        trampoline_arg0: core::sync::atomic::AtomicU64::new(0),
1807        ticks: core::sync::atomic::AtomicU64::new(0),
1808        sched_policy: crate::process::task::SyncUnsafeCell::new(Task::default_sched_policy(
1809            TaskPriority::Normal,
1810        )),
1811        home_cpu: core::sync::atomic::AtomicUsize::new(usize::MAX),
1812        vruntime: core::sync::atomic::AtomicU64::new(0),
1813        fair_rq_generation: core::sync::atomic::AtomicU64::new(0),
1814        fair_on_rq: core::sync::atomic::AtomicBool::new(false),
1815        clear_child_tid: core::sync::atomic::AtomicU64::new(0),
1816        robust_list_head: core::sync::atomic::AtomicU64::new(0),
1817        robust_list_len: core::sync::atomic::AtomicUsize::new(0),
1818        user_fs_base: core::sync::atomic::AtomicU64::new(user_fs_base_val),
1819        fpu_state: crate::process::task::SyncUnsafeCell::new(fpu_state),
1820        xcr0_mask: core::sync::atomic::AtomicU64::new(xcr0_mask),
1821        rt_link: intrusive_collections::LinkedListLink::new(),
1822    });
1823
1824    crate::e9_println!(
1825        "[trace][elf] load_elf_task task_built tid={} pid={} entry={:#x} sp={:#x}",
1826        task.id.as_u64(),
1827        task.pid,
1828        runtime_entry,
1829        boot_sp
1830    );
1831    // Seed capabilities into the new task (before scheduling).
1832    let mut bootstrap_handle: Option<u64> = None;
1833    if !seed_caps.is_empty() {
1834        let caps = unsafe { &mut *task.process.capabilities.get() };
1835        for cap in seed_caps {
1836            let id = caps.insert(cap.clone());
1837            if bootstrap_handle.is_none()
1838                && cap.resource_type == crate::capability::ResourceType::Volume
1839            {
1840                bootstrap_handle = Some(id.as_u64());
1841            }
1842        }
1843    }
1844
1845    // Setup stdin/stdout/stderr (fd 0/1/2) pointing to /dev/console
1846    // SAFETY: task is not yet scheduled, exclusive access to fd_table
1847    {
1848        let fd_table = unsafe { &mut *task.process.fd_table.get() };
1849        crate::vfs::console_scheme::setup_stdio(fd_table);
1850    }
1851
1852    if let Some(h) = bootstrap_handle {
1853        // Program entry will see this in its first argument register (RDI).
1854        task.trampoline_arg0
1855            .store(h, core::sync::atomic::Ordering::Release);
1856    }
1857
1858    task.seed_interrupt_frame(crate::syscall::SyscallFrame {
1859        r15: 0,
1860        r14: 0,
1861        r13: 0,
1862        r12: 0,
1863        rbp: 0,
1864        rbx: 0,
1865        r11: 0x202,
1866        r10: 0,
1867        r9: 0,
1868        r8: 0,
1869        rsi: 0,
1870        rdi: task
1871            .trampoline_arg0
1872            .load(core::sync::atomic::Ordering::Acquire),
1873        rdx: 0,
1874        rcx: runtime_entry,
1875        rax: 0,
1876        iret_rip: runtime_entry,
1877        iret_cs: crate::arch::x86_64::gdt::user_code_selector().0 as u64,
1878        iret_rflags: 0x202,
1879        iret_rsp: boot_sp,
1880        iret_ss: crate::arch::x86_64::gdt::user_data_selector().0 as u64,
1881    });
1882
1883    {
1884        let arc_data_ptr = alloc::sync::Arc::as_ptr(&task) as usize;
1885        let fpu_ptr = task.fpu_state.get() as usize;
1886        if let Some(cur) = crate::process::scheduler::current_task_clone() {
1887            let cur_data_ptr = alloc::sync::Arc::as_ptr(&cur) as usize;
1888            let cur_strong = alloc::sync::Arc::strong_count(&cur);
1889            log::info!(
1890                "[elf] Task '{}' prepared: entry={:#x}, stack_top={:#x} \
1891                 new_arc={:#x} new_fpu={:#x} cur_arc={:#x} cur_strong={}",
1892                name,
1893                runtime_entry,
1894                boot_sp,
1895                arc_data_ptr,
1896                fpu_ptr,
1897                cur_data_ptr,
1898                cur_strong,
1899            );
1900        } else {
1901            log::info!(
1902                "[elf] Task '{}' prepared: entry={:#x}, stack_top={:#x} \
1903                 new_arc={:#x} new_fpu={:#x} (no current task)",
1904                name,
1905                runtime_entry,
1906                boot_sp,
1907                arc_data_ptr,
1908                fpu_ptr,
1909            );
1910        }
1911    }
1912
1913    Ok(task)
1914}
1915
1916/// Load an ELF binary into the provided address space.
1917/// Returns the entry point address.
1918pub fn load_elf_image(
1919    elf_data: &[u8],
1920    user_as: &AddressSpace,
1921) -> Result<LoadedElfInfo, &'static str> {
1922    let header = parse_header(elf_data)?;
1923    let phdrs: Vec<Elf64Phdr> = program_headers(elf_data, &header).collect();
1924    let interp_path = parse_interp_path(elf_data, &phdrs)?;
1925    let (load_bias, entry) = compute_load_bias_and_entry(user_as, &header, &phdrs)?;
1926    let phdr_vaddr = find_relocated_phdr_vaddr(&header, &phdrs, load_bias)?;
1927
1928    for phdr in phdrs.iter() {
1929        if phdr.p_type == PT_LOAD && phdr.p_memsz != 0 {
1930            load_segment(user_as, elf_data, phdr, load_bias)?;
1931        }
1932    }
1933    if interp_path.is_none() {
1934        apply_dynamic_relocations(user_as, &phdrs, header.e_type, load_bias)?;
1935    }
1936
1937    let (tls_vaddr, tls_filesz, tls_memsz, tls_align) =
1938        if let Some(tls) = phdrs.iter().find(|ph| ph.p_type == PT_TLS) {
1939            let align = core::cmp::max(tls.p_align, 1).next_power_of_two();
1940            (
1941                tls.p_vaddr.saturating_add(load_bias),
1942                tls.p_filesz,
1943                tls.p_memsz,
1944                align,
1945            )
1946        } else {
1947            (0, 0, 0, 1)
1948        };
1949
1950    let mut runtime_entry = entry;
1951    let mut interp_base = None;
1952    if let Some(path) = interp_path {
1953        let interp_data = read_elf_from_vfs(path)?;
1954        let interp_header = parse_header(&interp_data)?;
1955        let interp_phdrs: Vec<Elf64Phdr> = program_headers(&interp_data, &interp_header).collect();
1956        if parse_interp_path(&interp_data, &interp_phdrs)?.is_some() {
1957            return Err("Nested PT_INTERP is not supported");
1958        }
1959        let (interp_bias, interp_entry) =
1960            compute_load_bias_and_entry(user_as, &interp_header, &interp_phdrs)?;
1961        let (interp_min_vaddr, _) = compute_load_bounds(&interp_phdrs)?;
1962        for phdr in interp_phdrs.iter() {
1963            if phdr.p_type == PT_LOAD && phdr.p_memsz != 0 {
1964                load_segment(user_as, &interp_data, phdr, interp_bias)?;
1965            }
1966        }
1967        apply_dynamic_relocations(user_as, &interp_phdrs, interp_header.e_type, interp_bias)?;
1968        runtime_entry = interp_entry;
1969        interp_base = Some(interp_min_vaddr.saturating_add(interp_bias));
1970    }
1971
1972    Ok(LoadedElfInfo {
1973        runtime_entry,
1974        program_entry: entry,
1975        phdr_vaddr,
1976        phent: header.e_phentsize,
1977        phnum: header.e_phnum,
1978        interp_base,
1979        tls_vaddr,
1980        tls_filesz,
1981        tls_memsz,
1982        tls_align,
1983    })
1984}
1985
1986/// Reads user mapped bytes pub.
1987pub fn read_user_mapped_bytes_pub(
1988    user_as: &AddressSpace,
1989    vaddr: u64,
1990    out: &mut [u8],
1991) -> Result<(), &'static str> {
1992    read_user_mapped_bytes(user_as, vaddr, out)
1993}
1994
1995/// Writes user mapped bytes pub.
1996pub fn write_user_mapped_bytes_pub(
1997    user_as: &AddressSpace,
1998    vaddr: u64,
1999    src: &[u8],
2000) -> Result<(), &'static str> {
2001    write_user_mapped_bytes(user_as, vaddr, src)
2002}
2003
2004/// Writes user u64 pub.
2005pub fn write_user_u64_pub(
2006    user_as: &AddressSpace,
2007    vaddr: u64,
2008    value: u64,
2009) -> Result<(), &'static str> {
2010    write_user_u64(user_as, vaddr, value)
2011}