Why am I not get desired output from a elf file execution?

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
User avatar
gamingjam60
Member
Member
Posts: 70
Joined: Sat Aug 24, 2024 10:06 pm
Libera.chat IRC: gamingjam60
Location: India
GitHub: https://github.com/baponkar
Contact:

Why am I not get desired output from a elf file execution?

Post by gamingjam60 »

repo
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
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:

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
}


Octocontrabass
Member
Member
Posts: 6275
Joined: Mon Mar 25, 2013 7:01 pm

Re: Why am I not get desired output from a elf file execution?

Post by Octocontrabass »

What does your debugger say the ELF file is doing?
Post Reply