summaryrefslogtreecommitdiff
path: root/na/kernel/exec.cc
diff options
context:
space:
mode:
authorRichard Qin <richard.qin.x@gmail.com>2026-07-10 11:24:56 +0800
committerRichard Qin <richard.qin.x@gmail.com>2026-07-10 11:24:56 +0800
commit1767d390fbf3d0c65389de46caf0b6d43ef3b6ad (patch)
tree0f5e01cd7c7a05abb6b1c7472d7f00f49caf02cc /na/kernel/exec.cc
parent63dc025705f6533b91fb1a7d26e6ed864efd9900 (diff)
downloadlume-1767d390fbf3d0c65389de46caf0b6d43ef3b6ad.tar.gz
lume-1767d390fbf3d0c65389de46caf0b6d43ef3b6ad.tar.xz
feat: establish bootable new architecture checkpoint
Replace the compressed boot path with a relocatable higher-half RISC-V entry, expand memory and SMP support, and add the driver, TTY, VFS, exec, signal, syscall, initramfs, and self-test infrastructure needed for a bootable kernel checkpoint.
Diffstat (limited to 'na/kernel/exec.cc')
-rw-r--r--na/kernel/exec.cc659
1 files changed, 659 insertions, 0 deletions
diff --git a/na/kernel/exec.cc b/na/kernel/exec.cc
new file mode 100644
index 0000000..616c0da
--- /dev/null
+++ b/na/kernel/exec.cc
@@ -0,0 +1,659 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+/*
+ * exec.cc — ELF64 binary loader and execve implementation
+ *
+ * Loads a static ELF64 executable into a fresh address space,
+ * constructs the user stack (argc/argv/envp/auxv), and redirects
+ * the current task's trapframe to begin executing at the ELF entry.
+ *
+ * Reference: Linux fs/binfmt_elf.c, fs/exec.c
+ */
+
+#include <lume/elf.h>
+#include <lume/types.h>
+#include <lume/task.h>
+#include <lume/vmm.h>
+#include <lume/addr.h>
+#include <lume/config.h>
+#include <lume/errno.h>
+#include <lume/pmm.h>
+#include <lume/kprintf.h>
+#include <lume/klog.h>
+#include <lume/file.h>
+#include <lume/fs.h>
+#include <lume/dcache.h>
+#include <lume/namei.h>
+#include <lume/fdtable.h>
+#include <lume/mm_layout.h>
+#include <lume/signal_types.h>
+#include <arch/pmap.h>
+#include <arch/trapframe.h>
+#include <arch/mmu.h>
+#include <arch/cpu.h>
+#include <arch/trap.h>
+
+/* Assembly entry points (trap_vector.S) */
+extern "C" void trap_vector();
+extern "C" [[noreturn]] void trap_dispatch(arch::TrapFrame*);
+extern "C" void userret(arch::TrapFrame* tf);
+
+/* ================================================================== */
+/* Constants */
+/* ================================================================== */
+
+/* User stack layout comes from mm_layout.h (MI layer).
+ * kUserStackTop, kUserStackBase, kUserStackSize are defined there,
+ * derived from kUserVaLimit so they automatically adapt to the
+ * page table mode (SV39/SV48). */
+
+/* ================================================================== */
+/* Helper: kernel_read — read from a file into a kernel buffer */
+/* ================================================================== */
+
+static int64 kernel_read(file* fp, void* buf, uint64 count, int64 offset)
+{
+ int64 pos = offset;
+ if (!fp->f_op || !fp->f_op->read) return -EINVAL;
+ return fp->f_op->read(fp, static_cast<char*>(buf), count, &pos);
+}
+
+/* ================================================================== */
+/* Helper: copy data to a user page via kernel direct map */
+/* ================================================================== */
+
+static void memcpy_kernel(void* dst, const void* src, uint64 n)
+{
+ auto* d = static_cast<uint8*>(dst);
+ auto* s = static_cast<const uint8*>(src);
+ for (uint64 i = 0; i < n; i++)
+ d[i] = s[i];
+}
+
+static void memset_kernel(void* dst, uint8 val, uint64 n)
+{
+ auto* d = static_cast<uint8*>(dst);
+ for (uint64 i = 0; i < n; i++)
+ d[i] = val;
+}
+
+static uint64 strlen_kernel(const char* s)
+{
+ uint64 n = 0;
+ while (s[n]) n++;
+ return n;
+}
+
+/* ================================================================== */
+/* Helper: allocate and map a page at a specific user VA */
+/* ================================================================== */
+
+static int map_page_at(VmSpace* mm, uint64 va, uint64 pte_perm)
+{
+ Page* pg = pmm_alloc_page();
+ if (!pg) return -ENOMEM;
+
+ uint64 pa = page_to_pa(pg).raw;
+
+ /* Zero the page */
+ void* kva = reinterpret_cast<void*>(pa_to_va(PhysAddr{pa}).raw);
+ memset_kernel(kva, 0, kPageSize);
+
+ int ret = pmap::map(mm->root_pa, va, pa, pte_perm);
+ if (ret != 0) {
+ pmm_free_page(pg);
+ return -ENOMEM;
+ }
+ page_inc_mapcount(pg);
+ return 0;
+}
+
+/* ================================================================== */
+/* load_elf_binary — parse ELF and map segments */
+/* ================================================================== */
+
+struct elf_load_info {
+ uint64 entry; /* ELF entry point (after base offset) */
+ uint64 phdr_addr; /* User VA where phdr was loaded */
+ uint16 phnum; /* Number of program headers */
+ uint16 phentsize; /* Size of each phdr */
+ uint64 brk_start; /* End of highest PT_LOAD segment (for brk) */
+ uint64 load_bias; /* PIE load bias (0 for ET_EXEC) */
+};
+
+static int load_elf_binary(file* fp, VmSpace* mm, elf_load_info* info)
+{
+ /* Read ELF header */
+ Elf64_Ehdr ehdr{};
+ int64 n = kernel_read(fp, &ehdr, sizeof(ehdr), 0);
+ if (n < static_cast<int64>(sizeof(ehdr))) {
+ printk(KERN_ERR "exec: failed to read ELF header (got %lld)\n", n);
+ return -ENOEXEC;
+ }
+
+ /* Validate magic */
+ if (ehdr.e_ident[EI_MAG0] != ELFMAG0 ||
+ ehdr.e_ident[EI_MAG1] != ELFMAG1 ||
+ ehdr.e_ident[EI_MAG2] != ELFMAG2 ||
+ ehdr.e_ident[EI_MAG3] != ELFMAG3) {
+ printk(KERN_ERR "exec: bad ELF magic\n");
+ return -ENOEXEC;
+ }
+
+ /* Validate class (64-bit) and endianness (little-endian) */
+ if (ehdr.e_ident[EI_CLASS] != ELFCLASS64) return -ENOEXEC;
+ if (ehdr.e_ident[EI_DATA] != ELFDATA2LSB) return -ENOEXEC;
+
+ /* Validate type and machine */
+ if (ehdr.e_type != ET_EXEC && ehdr.e_type != ET_DYN) return -ENOEXEC;
+ if (ehdr.e_machine != EM_RISCV) return -ENOEXEC;
+
+ if (ehdr.e_phnum == 0 || ehdr.e_phentsize < sizeof(Elf64_Phdr))
+ return -ENOEXEC;
+
+ /* Read program headers */
+ uint64 phdr_total = static_cast<uint64>(ehdr.e_phnum) * ehdr.e_phentsize;
+ if (phdr_total > kPageSize) return -ENOEXEC; /* Sanity limit */
+
+ /* Allocate kernel buffer for program headers */
+ uint8 phdr_buf[512]; /* Max ~8 phdrs * 56 bytes each */
+ if (phdr_total > sizeof(phdr_buf)) return -ENOEXEC;
+
+ n = kernel_read(fp, phdr_buf, phdr_total, static_cast<int64>(ehdr.e_phoff));
+ if (n < static_cast<int64>(phdr_total)) return -ENOEXEC;
+
+ info->phnum = ehdr.e_phnum;
+ info->phentsize = ehdr.e_phentsize;
+ info->phdr_addr = 0;
+ info->brk_start = 0;
+ info->load_bias = 0;
+
+ /*
+ * PIE (ET_DYN): segments have p_vaddr starting near 0.
+ * We must choose a load base and add it to all addresses.
+ * Linux uses ELF_ET_DYN_BASE (~2/3 of TASK_SIZE); we use 0x400000.
+ */
+ constexpr uint64 kPieLoadBase = 0x400000;
+ uint64 load_bias = (ehdr.e_type == ET_DYN) ? kPieLoadBase : 0;
+ info->load_bias = load_bias;
+ info->entry = ehdr.e_entry + load_bias;
+
+ /* Process each PT_LOAD segment */
+ for (uint16 i = 0; i < ehdr.e_phnum; i++) {
+ auto* phdr = reinterpret_cast<Elf64_Phdr*>(
+ phdr_buf + i * ehdr.e_phentsize);
+
+ if (phdr->p_type == PT_PHDR) {
+ /* Remember where program headers are mapped */
+ info->phdr_addr = phdr->p_vaddr + load_bias;
+ continue;
+ }
+
+ if (phdr->p_type != PT_LOAD) continue;
+ if (phdr->p_memsz == 0) continue;
+
+ /* Compute permissions */
+ uint64 perm = pmap::PTE_V | pmap::PTE_U;
+ if (phdr->p_flags & PF_R) perm |= pmap::PTE_R;
+ if (phdr->p_flags & PF_W) perm |= pmap::PTE_W;
+ if (phdr->p_flags & PF_X) perm |= pmap::PTE_X;
+
+ /* Page-align the segment boundaries (apply load bias for PIE) */
+ uint64 seg_start = page_align_down(phdr->p_vaddr + load_bias);
+ uint64 seg_end = page_align_up(phdr->p_vaddr + phdr->p_memsz + load_bias);
+
+ /* Create VMA for this segment */
+ uint64 vm_perm = VM_USER;
+ if (phdr->p_flags & PF_R) vm_perm |= VM_READ;
+ if (phdr->p_flags & PF_W) vm_perm |= VM_WRITE;
+ if (phdr->p_flags & PF_X) vm_perm |= VM_EXEC;
+
+ int err = vmm_map_user(mm, seg_start, seg_end - seg_start, vm_perm);
+ if (err) {
+ printk(KERN_ERR "exec: vmm_map_user failed for segment %d: %d\n",
+ i, err);
+ return err;
+ }
+
+ /* Map and fill pages */
+ for (uint64 va = seg_start; va < seg_end; va += kPageSize) {
+ err = map_page_at(mm, va, perm);
+ if (err) return err;
+
+ /* Determine how much file data goes into this page */
+ uint64 file_seg_start = phdr->p_vaddr + load_bias;
+ uint64 file_seg_end = phdr->p_vaddr + phdr->p_filesz + load_bias;
+
+ uint64 page_start = va;
+ uint64 page_end = va + kPageSize;
+
+ /* Overlap between [page_start, page_end) and [file_seg_start, file_seg_end) */
+ if (page_start < file_seg_end && page_end > file_seg_start) {
+ uint64 copy_start = (page_start > file_seg_start) ? page_start : file_seg_start;
+ uint64 copy_end = (page_end < file_seg_end) ? page_end : file_seg_end;
+ uint64 copy_len = copy_end - copy_start;
+
+ /* File offset for this data */
+ uint64 file_off = phdr->p_offset + (copy_start - phdr->p_vaddr - load_bias);
+
+ /* Get kernel VA of the mapped page */
+ uint64 pa;
+ if (!pmap::lookup(mm->root_pa, va, &pa)) return -EFAULT;
+ uint8* kva = reinterpret_cast<uint8*>(pa_to_va(PhysAddr{pa}).raw);
+
+ /* Read file data into the page */
+ uint64 page_offset = copy_start - va;
+ n = kernel_read(fp, kva + page_offset, copy_len,
+ static_cast<int64>(file_off));
+ if (n < static_cast<int64>(copy_len)) {
+ printk(KERN_ERR "exec: short read at offset %llu\n", file_off);
+ return -EIO;
+ }
+ }
+ /* BSS region (memsz > filesz) is already zeroed by map_page_at */
+ }
+
+ /* Track highest loaded address for brk */
+ if (seg_end > info->brk_start)
+ info->brk_start = seg_end;
+
+ /* Detect which PT_LOAD contains the program header table.
+ * Linux: fs/binfmt_elf.c line ~1209 */
+ if (info->phdr_addr == 0 &&
+ phdr->p_offset <= ehdr.e_phoff &&
+ ehdr.e_phoff < phdr->p_offset + phdr->p_filesz) {
+ info->phdr_addr = ehdr.e_phoff - phdr->p_offset
+ + phdr->p_vaddr + load_bias;
+ }
+ }
+
+ /* phdr_addr should have been set by the PT_LOAD loop above
+ * or by a PT_PHDR entry. If still zero, something is wrong. */
+ if (info->phdr_addr == 0) {
+ printk(KERN_ERR "exec: unable to determine AT_PHDR address\n");
+ return -ENOEXEC;
+ }
+
+ return 0;
+}
+
+/* ================================================================== */
+/* setup_user_stack — build argc/argv/envp/auxv on the user stack */
+/* ================================================================== */
+
+static int setup_user_stack(VmSpace* mm, const char* const* argv,
+ const char* const* envp,
+ const elf_load_info* info,
+ uint64* out_sp)
+{
+ /* Create stack VMA */
+ uint64 vm_perm = VM_USER | VM_READ | VM_WRITE;
+ int err = vmm_map_user(mm, kUserStackBase, kUserStackSize, vm_perm);
+ if (err) return err;
+
+ /* We only need the top few pages for initial stack content.
+ * Map 2 pages at the top for argc/argv/envp/auxv. */
+ uint64 perm = pmap::PTE_V | pmap::PTE_U | pmap::PTE_R | pmap::PTE_W;
+ for (uint64 va = kUserStackTop - 2 * kPageSize; va < kUserStackTop; va += kPageSize) {
+ err = map_page_at(mm, va, perm);
+ if (err) return err;
+ }
+
+ /* Count arguments and environment */
+ int argc = 0;
+ int envc = 0;
+ if (argv) { while (argv[argc]) argc++; }
+ if (envp) { while (envp[envc]) envc++; }
+
+ /*
+ * Build the stack from top down using kernel VA access.
+ * Layout (top to bottom):
+ * 1. String data (argv strings, envp strings)
+ * 2. Padding (16-byte align)
+ * 3. auxv[]
+ * 4. NULL (end of envp)
+ * 5. envp[] pointers
+ * 6. NULL (end of argv)
+ * 7. argv[] pointers
+ * 8. argc
+ */
+
+ /* First pass: calculate total string space */
+ uint64 str_total = 0;
+ for (int i = 0; i < argc; i++)
+ str_total += strlen_kernel(argv[i]) + 1;
+ for (int i = 0; i < envc; i++)
+ str_total += strlen_kernel(envp[i]) + 1;
+
+ /* String area starts at top of stack */
+ uint64 str_pos = kUserStackTop - str_total;
+ str_pos &= ~15ULL; /* 16-byte align */
+
+ /* Copy strings and record user addresses */
+ uint64 argv_addrs[32]; /* Max 32 args */
+ uint64 envp_addrs[32];
+ if (argc > 32) argc = 32;
+ if (envc > 32) envc = 32;
+
+ uint64 cur = str_pos;
+ for (int i = 0; i < argc; i++) {
+ uint64 len = strlen_kernel(argv[i]) + 1;
+ argv_addrs[i] = cur;
+
+ /* Copy string bytes to user stack via kernel VA */
+ uint64 pa;
+ if (!pmap::lookup(mm->root_pa, page_align_down(cur), &pa))
+ return -EFAULT;
+ uint8* kva = reinterpret_cast<uint8*>(pa_to_va(PhysAddr{pa}).raw);
+ uint64 off = cur - page_align_down(cur);
+ memcpy_kernel(kva + off, argv[i], len);
+ cur += len;
+ }
+ for (int i = 0; i < envc; i++) {
+ uint64 len = strlen_kernel(envp[i]) + 1;
+ envp_addrs[i] = cur;
+
+ uint64 pa;
+ if (!pmap::lookup(mm->root_pa, page_align_down(cur), &pa))
+ return -EFAULT;
+ uint8* kva = reinterpret_cast<uint8*>(pa_to_va(PhysAddr{pa}).raw);
+ uint64 off = cur - page_align_down(cur);
+ memcpy_kernel(kva + off, envp[i], len);
+ cur += len;
+ }
+
+ /* Build auxv, envp[], argv[], argc below the strings */
+ /* Count entries: argc + argv_ptrs + NULL + envp_ptrs + NULL + auxv (6 pairs + terminator) */
+ int auxv_count = 8; /* AT_PHDR, AT_PHENT, AT_PHNUM, AT_PAGESZ, AT_BASE, AT_ENTRY, AT_RANDOM, AT_NULL */
+ uint64 table_size = sizeof(uint64) * (1 + argc + 1 + envc + 1 + auxv_count * 2);
+ uint64 table_start = str_pos - table_size;
+ table_start &= ~15ULL;
+
+ /* Write the table entries.
+ * We need to handle page boundaries, but for simplicity we assume
+ * the table fits in the already-mapped pages. */
+ uint64 sp = table_start;
+
+ /* Helper: write a uint64 to user stack */
+ auto write_u64 = [&](uint64 addr, uint64 val) -> int {
+ uint64 pa;
+ if (!pmap::lookup(mm->root_pa, page_align_down(addr), &pa))
+ return -EFAULT;
+ uint8* kva = reinterpret_cast<uint8*>(pa_to_va(PhysAddr{pa}).raw);
+ uint64 off = addr - page_align_down(addr);
+ *reinterpret_cast<uint64*>(kva + off) = val;
+ return 0;
+ };
+
+ /* argc */
+ err = write_u64(sp, static_cast<uint64>(argc));
+ if (err) return err;
+ sp += 8;
+
+ /* argv[] */
+ for (int i = 0; i < argc; i++) {
+ err = write_u64(sp, argv_addrs[i]);
+ if (err) return err;
+ sp += 8;
+ }
+ err = write_u64(sp, 0); /* NULL terminator */
+ if (err) return err;
+ sp += 8;
+
+ /* envp[] */
+ for (int i = 0; i < envc; i++) {
+ err = write_u64(sp, envp_addrs[i]);
+ if (err) return err;
+ sp += 8;
+ }
+ err = write_u64(sp, 0); /* NULL terminator */
+ if (err) return err;
+ sp += 8;
+
+ /* auxv[] */
+ auto write_auxv = [&](uint64 type, uint64 val) -> int {
+ int r = write_u64(sp, type);
+ if (r) return r;
+ sp += 8;
+ r = write_u64(sp, val);
+ if (r) return r;
+ sp += 8;
+ return 0;
+ };
+
+ write_auxv(AT_PHDR, info->phdr_addr);
+ write_auxv(AT_PHENT, info->phentsize);
+ write_auxv(AT_PHNUM, info->phnum);
+ write_auxv(AT_PAGESZ, kPageSize);
+ write_auxv(AT_BASE, info->load_bias);
+ write_auxv(AT_ENTRY, info->entry);
+ write_auxv(AT_RANDOM, str_pos - 16); /* Point to 16 bytes of zeros for now */
+ write_auxv(AT_NULL, 0);
+
+ *out_sp = table_start;
+ return 0;
+}
+
+/* ================================================================== */
+/* do_execve — main entry point */
+/* ================================================================== */
+
+int do_execve(const char* pathname, const char* const* argv,
+ const char* const* envp)
+{
+ TaskControlBlock* curr = current_task();
+ if (!curr) return -EINVAL;
+
+ /* 1. Look up the file */
+ path p{};
+ int err = filename_lookup(AT_FDCWD, pathname, 0, &p);
+ if (err) {
+ printk(KERN_ERR "exec: file not found: %s (err=%d)\n", pathname, err);
+ return err;
+ }
+
+ inode* ip = p.de->d_inode;
+ if (!ip || !S_ISREG(ip->i_mode)) {
+ printk(KERN_ERR "exec: not a regular file: %s\n", pathname);
+ return -EACCES;
+ }
+
+ /* Open a kernel file handle */
+ file* fp = alloc_empty_file(O_RDONLY);
+ if (!fp) {
+ return -ENOMEM;
+ }
+ fp->f_inode = ip;
+ fp->f_path = p;
+ fp->f_op = ip->i_fop;
+ fp->f_pos = 0;
+
+ if (fp->f_op && fp->f_op->open) {
+ err = fp->f_op->open(ip, fp);
+ if (err) {
+ fput(fp);
+ return err;
+ }
+ }
+
+ /* 2. Create new address space */
+ VmSpace* new_mm = VmSpace::create();
+ if (!new_mm) {
+ fput(fp);
+ return -ENOMEM;
+ }
+
+ /* 3. Load ELF segments */
+ elf_load_info info{};
+ err = load_elf_binary(fp, new_mm, &info);
+ if (err) {
+ printk(KERN_ERR "exec: load_elf_binary failed: %d\n", err);
+ new_mm->put();
+ fput(fp);
+ dput(p.de);
+ return err;
+ }
+
+ /* 4. Set up user stack */
+ uint64 user_sp = 0;
+ err = setup_user_stack(new_mm, argv, envp, &info, &user_sp);
+ if (err) {
+ printk(KERN_ERR "exec: setup_user_stack failed: %d\n", err);
+ new_mm->put();
+ fput(fp);
+ return err;
+ }
+
+ /* Set brk */
+ new_mm->brk_start = page_align_up(info.brk_start);
+ new_mm->brk_end = new_mm->brk_start;
+
+ /* Set mmap base — Linux: arch_pick_mmap_layout() sets mm->mmap_base
+ * to TASK_UNMAPPED_BASE = PAGE_ALIGN(TASK_SIZE / 3).
+ * This ensures mmap(NULL) returns addresses well above the ELF
+ * text/data segments. */
+ new_mm->mmap_base = page_align_up(kUserVaLimit / 3);
+
+ fput(fp);
+
+ /* =============================================================== */
+ /* Point of no return — replace the current process */
+ /* =============================================================== */
+
+ /* 5a. Replace address space */
+ VmSpace* old_mm = curr->mm;
+ curr->mm = new_mm;
+ pmap::activate(new_mm->root_pa);
+ if (old_mm) old_mm->put();
+
+ /* 5a'. Transition from kernel thread to user process.
+ * Linux: begin_new_exec() in fs/exec.c:
+ * current->flags &= ~PF_KTHREAD; (line 1173)
+ * tsk->exit_signal = SIGCHLD; (line 1021)
+ * Without this, a kernel_execve'd task stays marked as a kthread
+ * and would be auto-reaped (exit_signal=-1) instead of becoming
+ * a proper zombie for parent wait(). */
+ curr->flags &= ~TF_KTHREAD;
+ curr->exit_signal = SIGCHLD;
+
+ /* 5b. Reset signal handlers to SIG_DFL (POSIX requirement).
+ *
+ * Linux fs/exec.c:flush_signal_handlers() runs under sighand->siglock.
+ * Even though after fork our sighand is no longer shared with the
+ * parent (we copy in task_clone), Linux still locks here — and so
+ * must we, because the same sighand may be shared with CLONE_SIGHAND
+ * sibling threads of the execing task. Holding siglock here also
+ * keeps the per-task blocked mask reset atomic w.r.t. concurrent
+ * sigprocmask / signal delivery on this very task. */
+ if (curr->sighand) {
+ LockGuard guard(curr->sighand->siglock);
+ for (int i = 0; i < _NSIG; i++) {
+ if (curr->sighand->action[i].sa_handler != SIG_IGN)
+ curr->sighand->action[i].sa_handler = SIG_DFL;
+ curr->sighand->action[i].sa_flags = 0;
+ curr->sighand->action[i].sa_mask.clear();
+ }
+ curr->blocked.clear();
+ } else {
+ curr->blocked.clear();
+ }
+
+ /* 5c. Close O_CLOEXEC file descriptors */
+ /* TODO: implement close-on-exec */
+
+ /* 6. Set up trapframe to enter user space */
+ arch::TrapFrame* tf = curr->trapframe;
+ if (!tf) {
+ printk(KERN_ERR "exec: no trapframe!\n");
+ task_kill_current(SIGKILL);
+ return -EINVAL; /* not reached */
+ }
+
+ /* Initialize trapframe for user-mode return.
+ * This is the MI equivalent of Linux's start_thread() + kernel
+ * metadata setup. All arch-specific details (sstatus, FP state,
+ * kernel_satp, kernel_sp, trap handler address, etc.) are handled
+ * by the MD layer. MI exec never touches arch registers. */
+ arch::trapframe_init_user(tf, info.entry, user_sp);
+
+ pr_debug_s(KLOG_SUBSYS_EXEC,
+ "exec: pid %d entry=%llx sp=%llx phdr=%llx\n",
+ curr->pid, info.entry, user_sp, info.phdr_addr);
+
+ /* Verify loaded data at entry point — debug-level, ring buffer only. */
+ {
+ uint64 pa;
+ if (pmap::lookup(curr->mm->root_pa, page_align_down(info.entry), &pa)) {
+ uint8* kva = reinterpret_cast<uint8*>(pa_to_va(PhysAddr{pa}).raw);
+ uint64 off = info.entry - page_align_down(info.entry);
+ pr_debug_s(KLOG_SUBSYS_EXEC,
+ "exec: entry bytes @%llx: %02x %02x %02x %02x %02x %02x %02x %02x\n",
+ info.entry,
+ kva[off+0], kva[off+1], kva[off+2], kva[off+3],
+ kva[off+4], kva[off+5], kva[off+6], kva[off+7]);
+ }
+ for (uint64 va = 0x10000; va < 0x14000; va += kPageSize) {
+ if (pmap::lookup(curr->mm->root_pa, va, &pa)) {
+ uint8* kva2 = reinterpret_cast<uint8*>(pa_to_va(PhysAddr{pa}).raw);
+ pr_debug_s(KLOG_SUBSYS_EXEC,
+ "exec: page %llx: %02x%02x%02x%02x %02x%02x%02x%02x\n",
+ va, kva2[0], kva2[1], kva2[2], kva2[3],
+ kva2[4], kva2[5], kva2[6], kva2[7]);
+ } else {
+ pr_debug_s(KLOG_SUBSYS_EXEC,
+ "exec: page %llx: NOT MAPPED\n", va);
+ }
+ }
+ }
+
+ /* Flush instruction cache after loading executable code into memory.
+ * Without this, the I-cache may contain stale data for the pages
+ * we just loaded, causing the CPU to execute garbage. */
+ arch::mmu::flush_icache();
+
+ return 0; /* Will return to user mode via trap return */
+}
+
+/* ================================================================== */
+/* sys_execve_wrapper — syscall entry point */
+/* ================================================================== */
+
+int64 sys_execve_wrapper(uint64 a0, uint64 a1, uint64 a2,
+ uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/)
+{
+ const char* pathname = reinterpret_cast<const char*>(a0);
+ const char* const* argv = reinterpret_cast<const char* const*>(a1);
+ const char* const* envp = reinterpret_cast<const char* const*>(a2);
+
+ return do_execve(pathname, argv, envp);
+}
+
+/* ================================================================== */
+/* kernel_execve — call execve from kernel context (e.g. init) */
+/* ================================================================== */
+
+int kernel_execve(const char* pathname, const char* const* argv,
+ const char* const* envp)
+{
+ TaskControlBlock* curr = current_task();
+
+ int ret = do_execve(pathname, argv, envp);
+ if (ret != 0)
+ return ret;
+
+ /*
+ * do_execve succeeded: the trapframe is set up with entry point,
+ * user stack, and sstatus (SPP=0 for U-mode). We must NOT return
+ * normally — that would go back through the kthread return path
+ * and jump to address 0. Instead, jump directly to user mode via
+ * the same userret() path that fork_child_entry uses.
+ *
+ * Switch stvec from kernelvec to trap_vector so that traps from
+ * U-mode are handled by the user trap entry path (which loads
+ * kernel context from the trapframe via sscratch).
+ */
+ arch::trap::set_vector(reinterpret_cast<uint64>(trap_vector));
+ userret(curr->trapframe);
+ __builtin_unreachable();
+}