Why am I not get desired output from a elf file execution?
Posted: Thu Jun 04, 2026 6:51 pm
repo
I want to execute a hello.elf file from an user shell and expecting output like "Hello from exec'd ELF!" but got
exec hello.elf creates a child process and copy buffer from elf file and set process register and schedule run the process Here kernelside code
int_syscall.c:
I want to execute a hello.elf file from an user shell and expecting output like "Hello from exec'd ELF!" but got
Code: Select all
/ $ exec hello.elf[SYSCALL_WRITE] fd=1 ubuf=0x410ECC size=1
[SYSCALL_WRITE] fd=1 ubuf=0x7FFFFFFFD7A0 size=29
[shell] spawning: /hello.elf
[SPAWN] User stack mapped from 0x7FFFFFFEE000 to 0x7FFFFFFFE000, RSP set to 0x0
[SPAWN] ELF load_and_exec returned with err=0
[SPAWN] new_proc->regs: rip=0x410000 cs=0x1B rflags=0x202 rsp=0x7FFFFFFFDFF8 ss=0x23
[SYSCALL_WRITE] fd=1 ubuf=0x7FFFFFFFD7A0 size=22
[shell] spawned pid=7
[SYSCALL] Process '/hello.elf' (pid=7) exited with code 0
[SYSCALL_WRITE] fd=1 ubuf=0x7FFFFFFFD7A0 size=4
int_syscall.c:
Code: Select all
case SYSCALL_SPAWN: {
const char *upath = (const char *)regs->rdi;
if (!upath || !IS_USER_ADDR((uint64_t)upath)) {
regs->rax = (uint64_t)-EFAULT; break;
}
char kpath[KEBLA_PATH_MAX];
if (copy_from_user_bytes(kpath, upath, sizeof(kpath)) < 0) {
regs->rax = (uint64_t)-EFAULT; break;
}
char fullpath[KEBLA_PATH_MAX];
resolve_path(fullpath, kpath);
extern ext2_fs_t fs;
int fd = ext2_open(&fs, fullpath, EXT2_O_RDONLY);
if (fd < 0) {
printf("[SPAWN] Failed to open ELF file\n");
regs->rax = (uint64_t)-ENOENT;
break;
}
// Allocate fresh process with its own pml4 — NOT current_proc
process_t *new_proc = spawn_user_process(kpath);
if (!new_proc) {
printf("[SPAWN] Failed to allocate new process\n");
ext2_close(fd);
regs->rax = (uint64_t)-ENOMEM;
break;
}
// Load ELF into new_proc's address space, writes new_proc->regs
int err = set_process_with_elf(fd, &new_proc->regs, new_proc);
printf("[SPAWN] ELF load_and_exec returned with err=%d\n", err);
printf("[SPAWN] new_proc->regs: rip=%llx cs=%llx rflags=%llx rsp=%llx ss=%llx\n",
new_proc->regs.iret_rip, new_proc->regs.iret_cs, new_proc->regs.iret_rflags,
new_proc->regs.iret_rsp, new_proc->regs.iret_ss);
ext2_close(fd);
if (err < 0) {
printf("[SPAWN] ELF load failed with error %d\n", err);
delete_user_process(new_proc);
regs->rax = (uint64_t)(int64_t)err;
break;
}
// Hand off to scheduler
enqueue_process(new_proc);
// Shell continues — return child pid
regs->rax = (uint64_t)new_proc->pid;
break;
}
Code: Select all
process_t *spawn_user_process(const char *name) {
process_t *process = kmalloc(sizeof(process_t));
if (!process) return NULL;
memset(process, 0, sizeof(process_t));
signal_init(process);
spinlock_acquire(&pid_lock);
process->pid = next_pid++;
spinlock_release(&pid_lock);
strncpy(process->name, name, PROCESS_NAME_MAX_LEN - 1);
process->status = READY;
// Kernel stack — used as TSS.RSP0 when ring-3 takes an interrupt
uint8_t *kstack = kmalloc(USER_KSTACK_SIZE);
if (!kstack) { kfree(process); return NULL; }
memset(kstack, 0, USER_KSTACK_SIZE);
process->kernel_stack_top = ((uint64_t)(kstack + USER_KSTACK_SIZE)) & ~0xFULL;
process->kernel_stack_base = (uint64_t)kstack;
// Fresh address space — kernel upper-half copied, lower half empty
process->pml4 = clone_kernel_pml4();
if (!process->pml4) {
kfree(kstack);
kfree(process);
return NULL;
}
// ── User stack ────────────────────────────────────────────────
for (uint64_t va = USER_STACK_TOP - USER_STACK_SIZE;
va < USER_STACK_TOP;
va += 4096)
{
uint64_t phys = pmm_get_free_frame_addr();
if (!phys) {
kfree(kstack);
kfree(process->pml4);
kfree(process);
return NULL;
}
// zero the page so BSS-like data starts clean
memset((void *)pm_to_vm(phys), 0, 4096);
map_vaddr(process->pml4, va, phys,
PAGE_PRESENT | PAGE_WRITE | PAGE_USER | PAGE_NX);
}
printf("[SPAWN] User stack mapped from %llx to %llx, RSP set to %llx\n",
( USER_STACK_TOP - USER_STACK_SIZE), USER_STACK_TOP, process->regs.iret_rsp);
// RSP starts at top, 16-byte aligned, with 8-byte gap (ABI)
process->regs.iret_rsp = (USER_STACK_TOP - 8) & ~0xFULL;
process->regs.iret_cs = 0x1B; // user code segment selector (GDT entry 3, RPL=3)
process->regs.iret_ss = 0x23; // user data segment selector (GDT entry 4, RPL=3)
process->regs.iret_rflags = 0x202; // IF=1, reserved=1
process->regs.ds = 0x23;
process->regs.es = 0x23;
// iret_rip left 0 — elf_load_and_exec fills it from e_entry
uheap_init(process);
signal_map_trampoline(process, SYSCALL_SIGRETURN);
// regs are left zeroed — elf_load_and_exec fills iret_rip/cs/rsp/ss/rflags
// The scheduler will iretq directly into ring 3 using those values.
return process; // NOT enqueued yet
}
Code: Select all
/*
* elf_load_and_exec()
*
* Loads an ELF from an already-open fd into a new process's address space,
* then rewrites the interrupted register frame so that when the syscall
* handler returns via iret, the CPU jumps directly into the ELF entry point
* in ring 3.
*
* Called from SYSCALL_EXECVE handler:
* int err = elf_load_and_exec(fd, regs, current_proc);
*
* Returns: 0 on success (regs modified in-place, process is ready to run)
* negative errno on failure (caller must NOT modify regs on error)
*/
#include <kebla/generic/elf/parse_elf.h>
#include <kebla/generic/fs/ext2.h>
#include <kebla/generic/mm/pmm.h>
#include <kebla/generic/mm/kheap.h>
#include <kebla/arch/x86_64/paging.h>
#include <kebla/arch/x86_64/process_manager.h>
#include <kebla/generic/lib/stdio.h>
#include <kebla/generic/lib/string.h>
#include <kebla/generic/lib/errno.h>
#include <kebla/generic/elf/elf_load.h>
#define USER_STACK_TOP 0x00007FFFFFFFE000ULL // top of user stack VA
#define USER_STACK_PAGES 4 // 16 KiB stack
#define USER_STACK_BOTTOM (USER_STACK_TOP - (USER_STACK_PAGES * PAGE_SIZE))
// Segment flags from ELF program header p_flags
#define PF_X 0x1
#define PF_W 0x2
#define PF_R 0x4
// Before — RPL=0 (ring 0), causes GPF on iret to ring 3
// #define USER_DS 0x18
// #define USER_CS 0x20
// After — RPL=3 (ring 3), correct for userspace
#define USER_CS 0x1B // 0x18 | 3
#define USER_DS 0x23 // 0x20 | 3
static uint64_t vaddr_to_phys(pml4_t *pml4, uint64_t va) {
uint64_t pml4_i = (va >> 39) & 0x1FF;
uint64_t pdpt_i = (va >> 30) & 0x1FF;
uint64_t pd_i = (va >> 21) & 0x1FF;
uint64_t pt_i = (va >> 12) & 0x1FF;
dir_entry_t *pml4e = &pml4->entries[pml4_i];
if (!pml4e->present) return 0;
pdpt_t *pdpt = (pdpt_t *)pm_to_vm((uint64_t)pml4e->base_addr << 12);
dir_entry_t *pdpte = &pdpt->entries[pdpt_i];
if (!pdpte->present) return 0;
pd_t *pd = (pd_t *)pm_to_vm((uint64_t)pdpte->base_addr << 12);
dir_entry_t *pde = &pd->entries[pd_i];
if (!pde->present) return 0;
pt_t *pt = (pt_t *)pm_to_vm((uint64_t)pde->base_addr << 12);
page_t *page = &pt->pages[pt_i];
if (!page->present) return 0;
return (uint64_t)page->frame << 12;
}
int set_process_with_elf(int fd, registers_t *regs, process_t *proc)
{
/* ── 1. Read ELF header ───────────────────────────────────────────────── */
Elf64_Ehdr ehdr;
ext2_seek(fd, 0, 0); // SEEK_SET — rewind; caller may have read it
long n = ext2_read(fd, &ehdr, sizeof(Elf64_Ehdr));
if (n < (long)sizeof(Elf64_Ehdr)) {
printf("[ELF] Header read failed (%ld)\n", n);
return -EIO;
}
/* ── 2. Validate magic ────────────────────────────────────────────────── */
if (ehdr.e_ident[0] != 0x7F ||
ehdr.e_ident[1] != 'E' ||
ehdr.e_ident[2] != 'L' ||
ehdr.e_ident[3] != 'F') {
printf("[ELF] Bad magic\n");
return -ENOEXEC;
}
if (ehdr.e_ident[4] != 2) { // EI_CLASS = ELFCLASS64
printf("[ELF] Not a 64-bit ELF\n");
return -ENOEXEC;
}
if (ehdr.e_type != 2) { // ET_EXEC
printf("[ELF] Not an executable (type=%u)\n", ehdr.e_type);
return -ENOEXEC;
}
if (ehdr.e_phnum == 0 || ehdr.e_phentsize < sizeof(Elf64_Phdr)) {
printf("[ELF] No usable program headers\n");
return -ENOEXEC;
}
/* ── 3. Read all program headers ─────────────────────────────────────── */
// Heap-allocate the program header table so we don't blow the kernel stack
size_t phtab_size = (size_t)ehdr.e_phnum * sizeof(Elf64_Phdr);
Elf64_Phdr *phdrs = (Elf64_Phdr *)kmalloc(phtab_size);
if (!phdrs) {
printf("[ELF] kmalloc failed for phdr table\n");
return -ENOMEM;
}
ext2_seek(fd, (long)ehdr.e_phoff, 0);
n = ext2_read(fd, phdrs, phtab_size);
if (n < (long)phtab_size) {
printf("[ELF] phdr read failed (%ld wanted %zu)\n", n, phtab_size);
kfree(phdrs);
return -EIO;
}
/* ── 4. Switch to target process's page table ─────────────────────────
*
* We switch to proc->pml4 NOW so that the memcpy/memset calls below
* write directly into the target address space.
* The kernel is identity-mapped in the top half of every pml4,
* so kernel code keeps running normally after the CR3 switch.
*/
/* ── 5. Load PT_LOAD segments ─────────────────────────────────────────── */
uint64_t load_end = 0; // track highest byte loaded (for heap base)
for (uint16_t i = 0; i < ehdr.e_phnum; i++) {
Elf64_Phdr *ph = &phdrs[i];
if (ph->p_type != PT_LOAD) continue;
if (ph->p_memsz == 0) continue;
/* Page-align the mapping range */
uint64_t seg_start = ph->p_vaddr & ~0xFFFULL;
uint64_t seg_end = (ph->p_vaddr + ph->p_memsz + 0xFFFULL) & ~0xFFFULL;
/* Build PTE flags from ELF segment flags:
* PF_R always → PAGE_PRESENT | PAGE_USER
* PF_W → PAGE_WRITE
* !PF_X → PAGE_NX (data/bss are non-executable)
*/
uint64_t pte_flags = PAGE_PRESENT | PAGE_USER;
if (ph->p_flags & PF_W) pte_flags |= PAGE_WRITE;
if (!(ph->p_flags & PF_X)) pte_flags |= PAGE_NX;
/* Map each page and back it with a physical frame */
for (uint64_t va = seg_start; va < seg_end; va += PAGE_SIZE) {
uint64_t phys = pmm_get_free_frame_addr();
if (!phys) {
printf("[ELF] OOM at va=%llx\n", va);
kfree(phdrs);
return -ENOMEM;
}
map_vaddr(proc->pml4, va, phys, pte_flags);
}
/* Copy file data into the mapped VA */
if (ph->p_filesz > 0) {
ext2_seek(fd, (long)ph->p_offset, 0);
uint64_t remaining = ph->p_filesz;
uint64_t va = ph->p_vaddr;
while (remaining > 0) {
// Find physical frame backing this VA in proc's page table
uint64_t phys = vaddr_to_phys(proc->pml4, va);
if (!phys) { kfree(phdrs); return -EFAULT; }
uint64_t page_offset = va & 0xFFF;
uint64_t chunk = PAGE_SIZE - page_offset;
if (chunk > remaining) chunk = remaining;
// Write through kernel's direct-map window
uint8_t *kdst = (uint8_t *)pm_to_vm(phys) + page_offset;
long n = ext2_read(fd, kdst, chunk);
if (n < (long)chunk) { kfree(phdrs); return -EIO; }
va += chunk;
remaining -= chunk;
}
}
/* Zero BSS through physical addresses too */
if (ph->p_memsz > ph->p_filesz) {
uint64_t bss_va = ph->p_vaddr + ph->p_filesz;
uint64_t bss_len = ph->p_memsz - ph->p_filesz;
while (bss_len > 0) {
uint64_t phys = vaddr_to_phys(proc->pml4, bss_va);
if (!phys) break;
uint64_t page_offset = bss_va & 0xFFF;
uint64_t chunk = PAGE_SIZE - page_offset;
if (chunk > bss_len) chunk = bss_len;
memset((uint8_t *)pm_to_vm(phys) + page_offset, 0, chunk);
bss_va += chunk;
bss_len -= chunk;
}
}
/* Track highest loaded address for heap placement */
uint64_t end = ph->p_vaddr + ph->p_memsz;
if (end > load_end) load_end = end;
// printf("[ELF] seg[%u] vaddr=%llx memsz=%llx filesz=%llx flags=%x\n",
// i, ph->p_vaddr, ph->p_memsz, ph->p_filesz, ph->p_flags);
}
kfree(phdrs);
/* ── 6. Map a fresh user stack ────────────────────────────────────────── */
for (uint64_t va = USER_STACK_BOTTOM; va < USER_STACK_TOP; va += PAGE_SIZE) {
uint64_t phys = pmm_get_free_frame_addr();
if (!phys) { printf("[ELF] OOM mapping stack\n"); return -ENOMEM; }
map_vaddr(proc->pml4, va, phys, PAGE_PRESENT | PAGE_WRITE | PAGE_USER | PAGE_NX);
memset((void *)pm_to_vm(phys), 0, PAGE_SIZE);
}
/* ── 7. Set up the heap region in process_t ───────────────────────────
*
* Align heap base to the next 2 MiB boundary above the last loaded
* segment so there's a natural guard gap between code/data and heap.
*/
uint64_t heap_base = (load_end + 0x1FFFFFULL) & ~0x1FFFFFULL;
proc->heap_start = heap_base;
proc->heap_end = heap_base; // empty heap; grows via SYSCALL_SBRK
/* ── 8. Rewrite the register frame ───────────────────────────────────
*
* When int_systemcall_handler() returns, the CPU executes IRET which
* pops RIP, CS, RFLAGS, RSP, SS from the kernel stack.
* We overwrite those fields here so IRET lands in userspace ELF code.
*
* regs layout must match your pushad/interrupt stub in _int_manager.asm.
*/
regs->iret_rip = ehdr.e_entry; // jump to ELF entry point
regs->iret_cs = 0x1B; // user code segment selector (GDT entry 3, RPL=3)
regs->iret_rflags = 0x202; // IF=1 (interrupts on), reserved bit
regs->iret_rsp = USER_STACK_TOP - 8; // point just below top (16-byte aligned)
regs->iret_ss = 0x23; // user stack segment selector (GDT entry 4, RPL=3)
// General-purpose registers — clear to zero (clean slate for new image)
regs->rax = 0;
regs->rbx = 0;
regs->rcx = 0;
regs->rdx = 0;
regs->rsi = 0;
regs->rdi = 0;
regs->rbp = 0;
regs->r8 = 0;
regs->r9 = 0;
regs->r10 = 0;
regs->r11 = 0;
regs->r12 = 0;
regs->r13 = 0;
regs->r14 = 0;
regs->r15 = 0;
return 0; // success — caller must NOT touch regs again
}