]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
feat: dynamic linking infrastructure - PT_INTERP support, ET_DYN validation, elf32_lo...
authorTulio A M Mendes <[email protected]>
Thu, 12 Feb 2026 02:19:39 +0000 (23:19 -0300)
committerTulio A M Mendes <[email protected]>
Fri, 13 Feb 2026 02:20:50 +0000 (23:20 -0300)
include/elf.h
src/arch/x86/elf.c

index 07f0b92be06dbbc9b5ca73f58c76e15dbf974ccc..31b474a4a9c0e65a47bcf8bf088dbee414fe4262 100644 (file)
@@ -45,12 +45,82 @@ typedef struct {
 #define ET_EXEC 2
 #define EM_386 3
 
-#define PT_LOAD 1
+#define PT_NULL    0
+#define PT_LOAD    1
+#define PT_DYNAMIC 2
+#define PT_INTERP  3
+#define PT_NOTE    4
+#define PT_PHDR    6
+
+#define ET_DYN  3
 
 #define PF_X 0x1
 #define PF_W 0x2
 #define PF_R 0x4
 
+/* Dynamic section entry */
+typedef struct {
+    int32_t  d_tag;
+    uint32_t d_val;
+} elf32_dyn_t;
+
+#define DT_NULL    0
+#define DT_NEEDED  1
+#define DT_HASH    4
+#define DT_STRTAB  5
+#define DT_SYMTAB  6
+#define DT_RELA    7
+#define DT_RELASZ  8
+#define DT_RELAENT 9
+#define DT_STRSZ   10
+#define DT_SYMENT  11
+#define DT_REL     17
+#define DT_RELSZ   18
+#define DT_RELENT  19
+#define DT_JMPREL  23
+#define DT_PLTRELSZ 2
+#define DT_PLTREL  20
+
+/* Relocation entry (without addend) */
+typedef struct {
+    uint32_t r_offset;
+    uint32_t r_info;
+} elf32_rel_t;
+
+#define ELF32_R_SYM(i)   ((i) >> 8)
+#define ELF32_R_TYPE(i)  ((unsigned char)(i))
+
+#define R_386_NONE      0
+#define R_386_32        1
+#define R_386_PC32      2
+#define R_386_GLOB_DAT  6
+#define R_386_JMP_SLOT  7
+#define R_386_RELATIVE  8
+
+/* Symbol table entry */
+typedef struct {
+    uint32_t st_name;
+    uint32_t st_value;
+    uint32_t st_size;
+    uint8_t  st_info;
+    uint8_t  st_other;
+    uint16_t st_shndx;
+} elf32_sym_t;
+
+/* Auxiliary vector (passed on user stack after envp) */
+typedef struct {
+    uint32_t a_type;
+    uint32_t a_val;
+} elf32_auxv_t;
+
+#define AT_NULL   0
+#define AT_PHDR   3
+#define AT_PHENT  4
+#define AT_PHNUM  5
+#define AT_PAGESZ 6
+#define AT_BASE   7   /* Interpreter base address */
+#define AT_ENTRY  9   /* Program entry point */
+
 int elf32_load_user_from_initrd(const char* filename, uintptr_t* entry_out, uintptr_t* user_stack_top_out, uintptr_t* addr_space_out, uintptr_t* heap_break_out);
 
 #endif
index 14c6907ca349577bcceda138ba7a46665e8edfe6..f7dc4b9d0a462689df876ab88d4361b34a231fb9 100644 (file)
@@ -39,7 +39,7 @@ static int elf32_validate(const elf32_ehdr_t* eh, size_t file_len) {
     if (eh->e_ident[4] != ELFCLASS32) return -EINVAL;
     if (eh->e_ident[5] != ELFDATA2LSB) return -EINVAL;
 
-    if (eh->e_type != ET_EXEC) return -EINVAL;
+    if (eh->e_type != ET_EXEC && eh->e_type != ET_DYN) return -EINVAL;
     if (eh->e_machine != EM_386) return -EINVAL;
 
     if (eh->e_phentsize != sizeof(elf32_phdr_t)) return -EINVAL;
@@ -102,6 +102,92 @@ static int elf32_map_user_range(uintptr_t as, uintptr_t vaddr, size_t len, uint3
     return 0;
 }
 
+/* Load ELF segments at base_offset (0 for ET_EXEC, non-zero for interpreter).
+ * Operates in the target address space (caller must have activated it).
+ * Returns 0 on success, fills highest_end. */
+static int elf32_load_segments(const uint8_t* file, uint32_t file_len,
+                               uintptr_t as, uintptr_t base_offset,
+                               uintptr_t* highest_end) {
+    const elf32_ehdr_t* eh = (const elf32_ehdr_t*)file;
+    const elf32_phdr_t* ph = (const elf32_phdr_t*)(file + eh->e_phoff);
+    uintptr_t seg_max = 0;
+
+    for (uint16_t i = 0; i < eh->e_phnum; i++) {
+        if (ph[i].p_type != PT_LOAD) continue;
+        if (ph[i].p_memsz == 0) continue;
+
+        uintptr_t vaddr = (uintptr_t)ph[i].p_vaddr + base_offset;
+        if (vaddr == 0 || vaddr >= hal_mm_kernel_virt_base()) return -EINVAL;
+
+        uint32_t seg_end = (uint32_t)vaddr + ph[i].p_memsz;
+        if (seg_end < vaddr || seg_end >= hal_mm_kernel_virt_base()) return -EINVAL;
+
+        if ((uint64_t)ph[i].p_offset + (uint64_t)ph[i].p_filesz > (uint64_t)file_len)
+            return -EINVAL;
+
+        int mrc = elf32_map_user_range(as, vaddr, (size_t)ph[i].p_memsz, VMM_FLAG_RW);
+        if (mrc < 0) return mrc;
+
+        if (ph[i].p_filesz)
+            memcpy((void*)vaddr, file + ph[i].p_offset, ph[i].p_filesz);
+        if (ph[i].p_memsz > ph[i].p_filesz)
+            memset((void*)(vaddr + ph[i].p_filesz), 0, ph[i].p_memsz - ph[i].p_filesz);
+
+        if (seg_end > seg_max) seg_max = seg_end;
+    }
+
+    if (highest_end) *highest_end = seg_max;
+    return 0;
+}
+
+/* Load an interpreter ELF (ld.so) at INTERP_BASE.
+ * Returns 0 on success, sets *interp_entry. */
+#define INTERP_BASE 0x40000000U
+
+static int elf32_load_interp(const char* interp_path, uintptr_t as,
+                              uintptr_t* interp_entry, uintptr_t* interp_base_out) {
+    if (!interp_path || !interp_entry) return -EINVAL;
+
+    fs_node_t* node = vfs_lookup(interp_path);
+    if (!node) {
+        uart_print("[ELF] interp not found: ");
+        uart_print(interp_path);
+        uart_print("\n");
+        return -ENOENT;
+    }
+
+    uint32_t flen = node->length;
+    if (flen < sizeof(elf32_ehdr_t)) return -EINVAL;
+
+    uint8_t* fbuf = (uint8_t*)kmalloc(flen);
+    if (!fbuf) return -ENOMEM;
+
+    if (vfs_read(node, 0, flen, fbuf) != flen) {
+        kfree(fbuf);
+        return -EIO;
+    }
+
+    const elf32_ehdr_t* eh = (const elf32_ehdr_t*)fbuf;
+    int vrc = elf32_validate(eh, flen);
+    if (vrc < 0) {
+        kfree(fbuf);
+        return vrc;
+    }
+
+    uintptr_t dummy = 0;
+    int rc = elf32_load_segments(fbuf, flen, as, INTERP_BASE, &dummy);
+    if (rc < 0) {
+        kfree(fbuf);
+        return rc;
+    }
+
+    *interp_entry = (uintptr_t)eh->e_entry + INTERP_BASE;
+    if (interp_base_out) *interp_base_out = INTERP_BASE;
+
+    kfree(fbuf);
+    return 0;
+}
+
 int elf32_load_user_from_initrd(const char* filename, uintptr_t* entry_out, uintptr_t* user_stack_top_out, uintptr_t* addr_space_out, uintptr_t* heap_break_out) {
     if (!filename || !entry_out || !user_stack_top_out || !addr_space_out) return -EFAULT;
     if (!fs_root) return -EINVAL;
@@ -140,7 +226,6 @@ int elf32_load_user_from_initrd(const char* filename, uintptr_t* entry_out, uint
         return -EIO;
     }
 
-    // Switch into the new address space only after the ELF image is fully buffered.
     vmm_as_activate(new_as);
 
     const elf32_ehdr_t* eh = (const elf32_ehdr_t*)file;
@@ -153,73 +238,44 @@ int elf32_load_user_from_initrd(const char* filename, uintptr_t* entry_out, uint
         return vrc;
     }
 
-    const elf32_phdr_t* ph = (const elf32_phdr_t*)(file + eh->e_phoff);
     uintptr_t highest_seg_end = 0;
+    int lrc = elf32_load_segments(file, file_len, new_as, 0, &highest_seg_end);
+    if (lrc < 0) {
+        uart_print("[ELF] segment load failed\n");
+        kfree(file);
+        vmm_as_activate(old_as);
+        vmm_as_destroy(new_as);
+        return lrc;
+    }
 
-    for (uint16_t i = 0; i < eh->e_phnum; i++) {
-        if (ph[i].p_type != PT_LOAD) continue;
-
-        if (ph[i].p_memsz == 0) continue;
-        if (ph[i].p_vaddr == 0) {
-            uart_print("[ELF] PT_LOAD with vaddr=0 rejected\n");
-            kfree(file);
-            vmm_as_activate(old_as);
-            vmm_as_destroy(new_as);
-            return -EINVAL;
-        }
-        if (ph[i].p_vaddr >= hal_mm_kernel_virt_base()) {
-            uart_print("[ELF] PT_LOAD in kernel range rejected\n");
-            kfree(file);
-            vmm_as_activate(old_as);
-            vmm_as_destroy(new_as);
-            return -EINVAL;
-        }
-
-        uint32_t seg_end = ph[i].p_vaddr + ph[i].p_memsz;
-        if (seg_end < ph[i].p_vaddr) {
-            kfree(file);
-            vmm_as_activate(old_as);
-            vmm_as_destroy(new_as);
-            return -EINVAL;
-        }
-        if (seg_end >= hal_mm_kernel_virt_base()) {
-            kfree(file);
-            vmm_as_activate(old_as);
-            vmm_as_destroy(new_as);
-            return -EINVAL;
-        }
-
-        if ((uint64_t)ph[i].p_offset + (uint64_t)ph[i].p_filesz > (uint64_t)file_len) {
-            uart_print("[ELF] segment outside file\n");
-            kfree(file);
-            vmm_as_activate(old_as);
-            vmm_as_destroy(new_as);
-            return -EINVAL;
-        }
-
-        const uint32_t map_flags = VMM_FLAG_RW;
-
-        int mrc = elf32_map_user_range(new_as, (uintptr_t)ph[i].p_vaddr, (size_t)ph[i].p_memsz, map_flags);
-        if (mrc < 0) {
-            uart_print("[ELF] OOM mapping user segment\n");
-            kfree(file);
-            vmm_as_activate(old_as);
-            vmm_as_destroy(new_as);
-            return mrc;
-        }
-
-        if (ph[i].p_filesz) {
-            memcpy((void*)(uintptr_t)ph[i].p_vaddr, file + ph[i].p_offset, ph[i].p_filesz);
-        }
-
-        if (ph[i].p_memsz > ph[i].p_filesz) {
-            memset((void*)(uintptr_t)(ph[i].p_vaddr + ph[i].p_filesz), 0, ph[i].p_memsz - ph[i].p_filesz);
-        }
+    /* Check for PT_INTERP — if present, load the dynamic linker */
+    const elf32_phdr_t* ph = (const elf32_phdr_t*)(file + eh->e_phoff);
+    uintptr_t real_entry = (uintptr_t)eh->e_entry;
+    int has_interp = 0;
 
-        if (seg_end > highest_seg_end) {
-            highest_seg_end = seg_end;
+    for (uint16_t i = 0; i < eh->e_phnum; i++) {
+        if (ph[i].p_type == PT_INTERP) {
+            if (ph[i].p_filesz == 0 || ph[i].p_filesz > 256) break;
+            if (ph[i].p_offset + ph[i].p_filesz > file_len) break;
+
+            char interp_path[256];
+            memcpy(interp_path, file + ph[i].p_offset, ph[i].p_filesz);
+            interp_path[ph[i].p_filesz - 1] = '\0';
+
+            uintptr_t interp_entry = 0;
+            uintptr_t interp_base = 0;
+            int irc = elf32_load_interp(interp_path, new_as, &interp_entry, &interp_base);
+            if (irc == 0) {
+                real_entry = interp_entry;
+                has_interp = 1;
+                uart_print("[ELF] loaded interp: ");
+                uart_print(interp_path);
+                uart_print("\n");
+            }
+            break;
         }
     }
+    (void)has_interp;
 
     const uintptr_t user_stack_base = 0x00800000U;
     const size_t user_stack_size = 0x1000;
@@ -233,7 +289,7 @@ int elf32_load_user_from_initrd(const char* filename, uintptr_t* entry_out, uint
         return src2;
     }
 
-    *entry_out = (uintptr_t)eh->e_entry;
+    *entry_out = real_entry;
     *user_stack_top_out = user_stack_base + user_stack_size;
     *addr_space_out = new_as;
     if (heap_break_out) {