import drivers.serial.uart.serial as serial import drivers.fs.fat32.fat32 as fat32 import drivers.fs.fat32.fat32_types as fat32_types import drivers.core.cpu.cpu as cpu import mm.mm as mm import paging.paging as paging import sched.process as proc import sched.sched as sched import intr.gdt as gdt # Do NOT import KERN_STACK_SIZE/USER_STACK_SIZE from process - CDefine cross-module import is broken # Redefine them here to ensure correct values KERN_STACK_SIZE: t.CDefine = 8192 USER_STACK_SIZE: t.CDefine = 65536 import viperlib import string import asm import t, c ELFMAG0: t.CDefine = 0x7F ELFMAG1: t.CDefine = 0x45 ELFMAG2: t.CDefine = 0x4C ELFMAG3: t.CDefine = 0x46 ELFCLASS64: t.CDefine = 2 ELFDATA2LSB: t.CDefine = 1 ET_EXEC: t.CDefine = 2 ET_DYN: t.CDefine = 3 EM_X86_64: t.CDefine = 62 PT_LOAD: t.CDefine = 1 PT_DYNAMIC: t.CDefine = 2 PF_X: t.CDefine = 1 PF_W: t.CDefine = 2 PF_R: t.CDefine = 4 DT_NULL: t.CDefine = 0 DT_HASH: t.CDefine = 4 DT_STRTAB: t.CDefine = 5 DT_SYMTAB: t.CDefine = 6 DT_STRSZ: t.CDefine = 10 DT_SYMENT: t.CDefine = 11 DT_RELA: t.CDefine = 7 DT_RELASZ: t.CDefine = 8 DT_RELAENT: t.CDefine = 9 DT_PLTRELSZ: t.CDefine = 2 DT_JMPREL: t.CDefine = 23 R_X86_64_64: t.CDefine = 1 R_X86_64_GLOB_DAT: t.CDefine = 6 R_X86_64_JUMP_SLOT: t.CDefine = 7 R_X86_64_RELATIVE: t.CDefine = 8 STB_GLOBAL: t.CDefine = 1 STB_WEAK: t.CDefine = 2 @c.Attribute(t.attr.packed) class Elf64_Ehdr: e_ident: t.CArray[t.CUInt8T, 16] e_type: t.CUInt16T e_machine: t.CUInt16T e_version: t.CUInt32T e_entry: t.CUInt64T e_phoff: t.CUInt64T e_shoff: t.CUInt64T e_flags: t.CUInt32T e_ehsize: t.CUInt16T e_phentsize: t.CUInt16T e_phnum: t.CUInt16T e_shentsize: t.CUInt16T e_shnum: t.CUInt16T e_shstrndx: t.CUInt16T @c.Attribute(t.attr.packed) class Elf64_Phdr: p_type: t.CUInt32T p_flags: t.CUInt32T p_offset: t.CUInt64T p_vaddr: t.CUInt64T p_paddr: t.CUInt64T p_filesz: t.CUInt64T p_memsz: t.CUInt64T p_align: t.CUInt64T MAX_PHDRS: t.CDefine = 16 @c.Attribute(t.attr.packed) class Elf64_Shdr: sh_name: t.CUInt32T sh_type: t.CUInt32T sh_flags: t.CUInt64T sh_addr: t.CUInt64T sh_offset: t.CUInt64T sh_size: t.CUInt64T sh_link: t.CUInt32T sh_info: t.CUInt32T sh_addralign: t.CUInt64T sh_entsize: t.CUInt64T @c.Attribute(t.attr.packed) class Elf64_Sym: st_name: t.CUInt32T st_info: t.CUInt8T st_other: t.CUInt8T st_shndx: t.CUInt16T st_value: t.CUInt64T st_size: t.CUInt64T @c.Attribute(t.attr.packed) class Elf64_Rela: r_offset: t.CUInt64T r_info: t.CUInt64T r_addend: t.CInt64T @c.Attribute(t.attr.packed) class Elf64_Dyn: d_tag: t.CInt64T d_val: t.CUInt64T MAX_SHDRS: t.CDefine = 64 MAX_DYNS: t.CDefine = 64 MAX_RELAS: t.CDefine = 256 def load_elf(path: t.CConst | str, do_map: t.CInt = 1) -> t.CVoid | t.CPtr: fp: fat32_types.fileobj | t.CPtr = fat32.open(path, fat32_types.FA_READ) if fp is None: err: t.CUInt32T = fat32.last_error() eb: t.CArray[t.CChar, 64] viperlib.snprintf(c.Addr(eb), 64, "[elf] failed to open file err=%d\n", err) serial.puts(eb) return None ehdr_buf: t.CArray[t.CUInt8T, 64] string.memset(c.Addr(ehdr_buf), 0, 64) read_len: t.CUInt32T = fat32.read(fp, c.Addr(ehdr_buf), 64) if read_len < 64: serial.puts("[elf] failed to read ehdr\n") fat32.close(fp) return None ehdr: Elf64_Ehdr | t.CPtr = c.Addr(ehdr_buf) if ehdr.e_ident[0] != ELFMAG0 or ehdr.e_ident[1] != ELFMAG1 or ehdr.e_ident[2] != ELFMAG2 or ehdr.e_ident[3] != ELFMAG3: serial.puts("[elf] bad magic\n") fat32.close(fp) return None if ehdr.e_ident[4] != ELFCLASS64: serial.puts("[elf] not ELF64\n") fat32.close(fp) return None if ehdr.e_ident[5] != ELFDATA2LSB: serial.puts("[elf] not little-endian\n") fat32.close(fp) return None if ehdr.e_type != ET_EXEC and ehdr.e_type != ET_DYN: serial.puts("[elf] not ET_EXEC or ET_DYN\n") fat32.close(fp) return None if ehdr.e_machine != EM_X86_64: serial.puts("[elf] not x86_64\n") fat32.close(fp) return None entry_point: t.CUInt64T = ehdr.e_entry phoff: t.CUInt64T = ehdr.e_phoff phentsize: t.CUInt16T = ehdr.e_phentsize phnum: t.CUInt16T = ehdr.e_phnum if phentsize == 0 or phnum == 0 or phnum > MAX_PHDRS: serial.puts("[elf] invalid phdr count\n") fat32.close(fp) return None cr: t.CArray[t.CChar, 80] viperlib.snprintf(c.Addr(cr), 80, "[elf] entry=0x%lx phoff=%lu phentsize=%u phnum=%u\n", entry_point, phoff, phentsize, phnum) serial.puts(cr) phdr_bufs: t.CArray[Elf64_Phdr, MAX_PHDRS] string.memset(c.Addr(phdr_bufs), 0, MAX_PHDRS * 56) for pi in range(phnum): phdr_off: t.CUInt64T = phoff + t.CUInt64T(pi) * t.CUInt64T(phentsize) fat32.seek(fp, t.CUInt32T(phdr_off)) fat32.read(fp, c.Addr(phdr_bufs[pi]), 56) found_load: t.CInt = 0 lowest_vaddr: t.CUInt64T = 0 highest_vaddr_end: t.CUInt64T = 0 for pi in range(phnum): if phdr_bufs[pi].p_type == PT_LOAD: seg_end: t.CUInt64T = phdr_bufs[pi].p_vaddr + phdr_bufs[pi].p_memsz if found_load == 0: lowest_vaddr = phdr_bufs[pi].p_vaddr highest_vaddr_end = seg_end found_load = 1 else: if phdr_bufs[pi].p_vaddr < lowest_vaddr: lowest_vaddr = phdr_bufs[pi].p_vaddr if seg_end > highest_vaddr_end: highest_vaddr_end = seg_end if found_load == 0: serial.puts("[elf] no LOAD segments\n") fat32.close(fp) return None total_memsz: t.CUInt64T = highest_vaddr_end - lowest_vaddr total_pages: t.CUInt64T = (total_memsz + 0xFFF) // 0x1000 cr2: t.CArray[t.CChar, 80] viperlib.snprintf(c.Addr(cr2), 80, "[elf] vaddr 0x%lx-0x%lx pages=%lu\n", lowest_vaddr, highest_vaddr_end, total_pages) serial.puts(cr2) raw_alloc: t.CVoid | t.CPtr = mm.malloc(total_pages * 0x1000 + 0x1000) _db_ra: t.CArray[t.CChar, 80] viperlib.snprintf(c.Addr(_db_ra), 80, "[elf] malloc ret raw_alloc=0x%lx\n", t.CUInt64T(raw_alloc)) serial.puts(_db_ra) if not raw_alloc: serial.puts("[elf] malloc failed\n") fat32.close(fp) return None phys_base_aligned: t.CUInt64T = (t.CUInt64T(raw_alloc) + t.CUInt64T(0xFFF)) & ~t.CUInt64T(0xFFF) phys_base: t.CVoid | t.CPtr = t.CVoid(phys_base_aligned, t.CPtr) string.memset(phys_base, 0, total_pages * 0x1000) for pi in range(phnum): if phdr_bufs[pi].p_type == PT_LOAD: vaddr: t.CUInt64T = phdr_bufs[pi].p_vaddr file_off: t.CUInt64T = phdr_bufs[pi].p_offset filesz: t.CUInt64T = phdr_bufs[pi].p_filesz memsz: t.CUInt64T = phdr_bufs[pi].p_memsz dest_off: t.CUInt64T = vaddr - lowest_vaddr dest: t.CVoid | t.CPtr = t.CVoid(t.CUInt64T(phys_base) + dest_off, t.CPtr) if filesz > 0: fat32.seek(fp, t.CUInt32T(file_off)) fat32.read(fp, dest, t.CUInt32T(filesz)) if memsz > filesz: bss_start: t.CVoid | t.CPtr = t.CVoid(t.CUInt64T(dest) + filesz, t.CPtr) bss_len: t.CUInt64T = memsz - filesz string.memset(bss_start, 0, bss_len) fat32.close(fp) virt_page_start: t.CUInt64T = lowest_vaddr & ~t.CUInt64T(0xFFF) phys_page_start: t.CUInt64T = t.CUInt64T(phys_base) & ~t.CUInt64T(0xFFF) map_end: t.CUInt64T = (highest_vaddr_end + 0xFFF) & ~t.CUInt64T(0xFFF) num_map_pages: t.CUInt64T = (map_end - virt_page_start) // 0x1000 # Store mapping info for spawn_elf (used when do_map=0) global _last_elf_phys_page_start, _last_elf_virt_page_start, _last_elf_num_map_pages _last_elf_phys_page_start = phys_page_start _last_elf_virt_page_start = virt_page_start _last_elf_num_map_pages = num_map_pages if do_map != 0: for mi in range(num_map_pages): virt_addr: t.CUInt64T = virt_page_start + mi * 0x1000 phys_addr: t.CUInt64T = phys_page_start + mi * 0x1000 page_flags: t.CUInt64T = paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER paging.MapPage(virt_addr, phys_addr, page_flags) entry_addr: t.CVoid | t.CPtr = t.CVoid(entry_point, t.CPtr) return entry_addr def run_elf(path: t.CConst | str) -> t.CInt: entry: t.CVoid | t.CPtr = load_elf(path) if entry is None: serial.puts("[elf] load failed\n") return -1 serial.puts("[elf] jumping to entry...\n") c.Asm(f"""call {c.AsmInp(entry, t.ASM_DESCR.REG_ANY)}""", op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX, t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9, t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11]) serial.puts("[elf] returned from entry\n") return 0 MAX_SPAWNED: t.CDefine = 8 _elf_entries: t.CArray[t.CVoid | t.CPtr, MAX_SPAWNED] # User stack virtual address: top of 48-bit user space, 64KB below USER_STACK_VTOP: t.CDefine = 0x7FFFFFE00000 # Globals for ELF mapping info (set by load_elf when do_map=0, used by spawn_elf # to map ELF pages in the cloned PML4 instead of kernel PML4). # ROOT CAUSE: previously load_elf mapped ELF vaddr (e.g. 0x400000) into the # kernel PML4, overwriting the identity mapping of buddy system memory_block # headers at 0x400000. This corrupted buddy metadata -> malloc failures, # pid=0, and #GP in split_block. Fix: load_elf does NOT map; spawn_elf maps # in the cloned PML4 after clone_for_process. _last_elf_phys_page_start: t.CUInt64T = 0 _last_elf_virt_page_start: t.CUInt64T = 0 _last_elf_num_map_pages: t.CUInt64T = 0 def _elf_entry_wrapper(arg: t.CVoid | t.CPtr) -> t.CInt: pid: t.CInt = t.CInt(t.CUInt64T(arg)) _db1: t.CArray[t.CChar, 64] viperlib.snprintf(c.Addr(_db1), 64, "[elf] wrapper pid=%d\n", pid) serial.puts(_db1) # Disable interrupts around switch_pid + MapPage + TSS setup: @pml4 is a global # that switch_pid modifies. A timer interrupt mid-section would make the scheduler # call switch_pid for another thread, corrupting @pml4 for our MapPage calls. asm.cli() proc.switch_pid(pid) cur_p: proc.Process | t.CPtr = proc.ProcessManager.current() entry_fn: t.CVoid | t.CPtr = cur_p.entry if entry_fn is not None: user_stack_vtop: t.CUInt64T = USER_STACK_VTOP us_sz: t.CUInt64T = USER_STACK_SIZE user_stack_vbottom: t.CUInt64T = USER_STACK_VTOP - us_sz # Physical pages of the user stack (allocated by mm.malloc, identity-mapped) us_size: t.CUInt64T = USER_STACK_SIZE phys_bottom: t.CUInt64T = (cur_p.user_stack - us_size) & ~t.CUInt64T(0xFFF) # Map each page of the user stack at the virtual address with PTE_USER num_stack_pages: t.CUInt64T = 16 # USER_STACK_SIZE(65536) / PAGE_SIZE(4096) = 16 si: t.CUInt64T for si in range(num_stack_pages): vaddr: t.CUInt64T = (user_stack_vbottom & ~t.CUInt64T(0xFFF)) + si * t.CUInt64T(0x1000) paddr: t.CUInt64T = phys_bottom + si * t.CUInt64T(0x1000) paging.MapPage(vaddr, paddr, paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER) # Set TSS RSP0 so interrupts from Ring 3 use kernel stack gdt.set_tss_rsp0(cur_p.kern_stack) # Set GS kernel_rsp0 for syscall entry from Ring 3 cpu.set_kernel_rsp0(cur_p.kern_stack) # Keep interrupts disabled until iretq: drop_to_user_mode's iretq # will set RFLAGS.IF from the saved frame (0x202) for user mode. # This prevents timer interrupt preemption between TSS setup and iretq. # Drop to Ring 3 user mode via iretq proc.drop_to_user_mode(entry_fn, user_stack_vtop, cur_p.kern_stack) cur: proc.Process | t.CPtr = proc.ProcessManager.current() cur.exit() return 0 def spawn_elf(path: t.CConst | str) -> t.CInt: fat32.lock() name_start: t.CInt = 0 si: t.CInt = 0 while True: ch: t.CChar = path[si] if ch == 0: break if ch == 47: name_start = si + 1 si += 1 lowest_vaddr: t.CUInt64T = 0 highest_vaddr_end: t.CUInt64T = 0 fp_scan: fat32_types.fileobj | t.CPtr = fat32.open(path, fat32_types.FA_READ) if fp_scan is None: serr: t.CUInt32T = fat32.last_error() seb: t.CArray[t.CChar, 64] viperlib.snprintf(c.Addr(seb), 64, "[elf] spawn scan open failed err=%d\n", serr) serial.puts(seb) fat32.unlock() return -1 if fp_scan is not None: ehdr_buf_s: t.CArray[t.CUInt8T, 64] string.memset(c.Addr(ehdr_buf_s), 0, 64) fat32.read(fp_scan, c.Addr(ehdr_buf_s), 64) ehdr_s: Elf64_Ehdr | t.CPtr = c.Addr(ehdr_buf_s) if ehdr_s.e_type == ET_EXEC or ehdr_s.e_type == ET_DYN: phoff_s: t.CUInt64T = ehdr_s.e_phoff phentsize_s: t.CUInt16T = ehdr_s.e_phentsize phnum_s: t.CUInt16T = ehdr_s.e_phnum if phentsize_s > 0 and phnum_s > 0 and phnum_s <= MAX_PHDRS: phdr_bufs_s: t.CArray[Elf64_Phdr, MAX_PHDRS] string.memset(c.Addr(phdr_bufs_s), 0, MAX_PHDRS * 56) found_load_s: t.CInt = 0 for pi_s in range(phnum_s): fat32.seek(fp_scan, t.CUInt32T(phoff_s + t.CUInt64T(pi_s) * t.CUInt64T(phentsize_s))) fat32.read(fp_scan, c.Addr(phdr_bufs_s[pi_s]), 56) if phdr_bufs_s[pi_s].p_type == PT_LOAD: seg_end_s: t.CUInt64T = phdr_bufs_s[pi_s].p_vaddr + phdr_bufs_s[pi_s].p_memsz if found_load_s == 0: lowest_vaddr = phdr_bufs_s[pi_s].p_vaddr highest_vaddr_end = seg_end_s found_load_s = 1 else: if phdr_bufs_s[pi_s].p_vaddr < lowest_vaddr: lowest_vaddr = phdr_bufs_s[pi_s].p_vaddr if seg_end_s > highest_vaddr_end: highest_vaddr_end = seg_end_s fat32.close(fp_scan) isolate_va: t.CUInt64T = lowest_vaddr if isolate_va == 0: isolate_va = 0x400000 # Load ELF to physical memory WITHOUT mapping in kernel PML4. # do_map=0: load_elf stores mapping info in globals for spawn_elf to use. # ROOT CAUSE FIX: previously load_elf mapped ELF vaddr (e.g. 0x400000) into # kernel PML4, overwriting identity mapping of buddy memory_block headers. entry: t.CVoid | t.CPtr = load_elf(path, 0) if entry is None: serial.puts("[elf] spawn: load failed\n") fat32.unlock() return -1 # Clone kernel PML4 (identity mapping intact, no ELF mapping yet) proc_pml4: t.CUInt64T = paging.clone_for_process(isolate_va) slog1: t.CArray[t.CChar, 80] viperlib.snprintf(c.Addr(slog1), 80, "[elf] spawn: clone pml4=0x%lx va=0x%lx\n", proc_pml4, isolate_va) serial.puts(slog1) if proc_pml4 == 0: serial.puts("[elf] spawn: clone_for_process failed\n") fat32.unlock() return -1 # Map ELF pages in the CLONED PML4 (not kernel's). # This prevents overwriting kernel identity mapping at 0x400000 which # would corrupt buddy system memory_block headers. # Must disable interrupts: @pml4 global is used by MapPage and a timer # interrupt mid-section would call switch_pid, corrupting @pml4. asm.cli() _kernel_cr3: t.CUInt64T = 0 c.Asm(f"""mov {c.AsmOut(_kernel_cr3, t.ASM_DESCR.OUTPUT_REG)}, cr3""", op=[t.ASM_DESCR.CLOBBER_MEMORY]) c.Asm(f"mov cr3, {c.AsmInp(proc_pml4, t.ASM_DESCR.REG_ANY)}", op=[t.ASM_DESCR.CLOBBER_MEMORY]) paging.set_active_pml4(proc_pml4) mi: t.CUInt64T for mi in range(_last_elf_num_map_pages): _va: t.CUInt64T = _last_elf_virt_page_start + mi * 0x1000 _pa: t.CUInt64T = _last_elf_phys_page_start + mi * 0x1000 paging.MapPage(_va, _pa, paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER) c.Asm(f"mov cr3, {c.AsmInp(_kernel_cr3, t.ASM_DESCR.REG_ANY)}", op=[t.ASM_DESCR.CLOBBER_MEMORY]) paging.set_active_pml4(_kernel_cr3) asm.sti() p: proc.Process | t.CPtr = proc.ProcessManager.create_process(path[name_start:], entry) if p is None: serial.puts("[elf] spawn: create_process failed\n") fat32.unlock() return -2 p.elf_base = t.CUInt64T(entry) p.pml4_root = proc_pml4 if p.pid >= 0 and p.pid < MAX_SPAWNED: _elf_entries[p.pid] = entry th: sched.Thread | t.CPtr = sched.Scheduler.create_thread(_elf_entry_wrapper, t.CVoid(t.CUInt64T(p.pid), t.CPtr), p.pid) if th is None: serial.puts("[elf] spawn: create_thread failed\n") p.exit() fat32.unlock() return -3 tid: t.CInt = th.tid p.addThread(tid) slog: t.CArray[t.CChar, 80] viperlib.snprintf(c.Addr(slog), 80, "[elf] spawn: pid=%d tid=%d entry=0x%lx pml4=0x%lx\n", p.pid, tid, t.CUInt64T(entry), p.pml4_root) serial.puts(slog) fat32.unlock() return p.pid def _resolve_symbol(name: t.CConst | t.CChar | t.CPtr) -> t.CVoid | t.CPtr: nm: t.CInt = 0 while c.Deref(name + nm) != 0: nm += 1 cmp_name: t.CConst | t.CChar | t.CPtr = name cmp_len: t.CInt = nm if nm > 9: sha1_len2: t.CInt = 0 for si2 in range(nm): ch2: t.CChar = c.Deref(name + si2) if ch2 == 46: if sha1_len2 >= 8: cmp_name = t.CConst(t.CUInt64T(name) + si2 + 1, t.CPtr) cmp_len = nm - si2 - 1 break if ch2 == 0: break if (ch2 >= 48 and ch2 <= 57) or (ch2 >= 97 and ch2 <= 102): sha1_len2 += 1 else: break if cmp_len == 11: match: t.CInt = 1 for i in range(cmp_len): if c.Deref(cmp_name + i) != c.Deref("serial_puts" + i): match = 0 if match: return t.CVoid(c.Addr(serial.puts), t.CPtr) return None _so_strtab: t.CVoid | t.CPtr _so_symtab: t.CVoid | t.CPtr _so_syment: t.CUInt64T _so_sym_count: t.CInt def load_so(path: t.CConst | str) -> t.CVoid | t.CPtr: fat32.lock() fp: fat32_types.fileobj | t.CPtr = fat32.open(path, fat32_types.FA_READ) if fp is None: serial.puts("[so] failed to open file\n") fat32.unlock() return None ehdr_buf: t.CArray[t.CUInt8T, 64] string.memset(c.Addr(ehdr_buf), 0, 64) read_len: t.CUInt32T = fat32.read(fp, c.Addr(ehdr_buf), 64) if read_len < 64: serial.puts("[so] failed to read ehdr\n") fat32.close(fp) fat32.unlock() return None ehdr: Elf64_Ehdr | t.CPtr = c.Addr(ehdr_buf) if ehdr.e_ident[0] != ELFMAG0 or ehdr.e_ident[1] != ELFMAG1 or ehdr.e_ident[2] != ELFMAG2 or ehdr.e_ident[3] != ELFMAG3: serial.puts("[so] bad magic\n") fat32.close(fp) fat32.unlock() return None if ehdr.e_ident[4] != ELFCLASS64: serial.puts("[so] not ELF64\n") fat32.close(fp) fat32.unlock() return None if ehdr.e_type == ET_DYN: pass elif ehdr.e_type == ET_EXEC: pass else: serial.puts("[so] not ET_DYN or ET_EXEC\n") fat32.close(fp) fat32.unlock() return None phoff: t.CUInt64T = ehdr.e_phoff phentsize: t.CUInt16T = ehdr.e_phentsize phnum: t.CUInt16T = ehdr.e_phnum phdr_bufs: t.CArray[Elf64_Phdr, MAX_PHDRS] string.memset(c.Addr(phdr_bufs), 0, MAX_PHDRS * 56) for pi in range(phnum): phdr_off: t.CUInt64T = phoff + t.CUInt64T(pi) * t.CUInt64T(phentsize) fat32.seek(fp, t.CUInt32T(phdr_off)) fat32.read(fp, c.Addr(phdr_bufs[pi]), 56) found_load: t.CInt = 0 lowest_vaddr: t.CUInt64T = 0 highest_vaddr_end: t.CUInt64T = 0 for pi in range(phnum): if phdr_bufs[pi].p_type == PT_LOAD: seg_end: t.CUInt64T = phdr_bufs[pi].p_vaddr + phdr_bufs[pi].p_memsz if found_load == 0: lowest_vaddr = phdr_bufs[pi].p_vaddr highest_vaddr_end = seg_end found_load = 1 else: if phdr_bufs[pi].p_vaddr < lowest_vaddr: lowest_vaddr = phdr_bufs[pi].p_vaddr if seg_end > highest_vaddr_end: highest_vaddr_end = seg_end if found_load == 0: serial.puts("[so] no LOAD segments\n") fat32.close(fp) fat32.unlock() return None total_memsz: t.CUInt64T = highest_vaddr_end - lowest_vaddr total_pages: t.CUInt64T = (total_memsz + 0xFFF) // 0x1000 so_load_addr: t.CUInt64T = 0x600000 raw_alloc: t.CVoid | t.CPtr = mm.malloc(total_pages * 0x1000 + 0x1000) if not raw_alloc: serial.puts("[so] malloc failed\n") fat32.close(fp) fat32.unlock() return None phys_base_aligned: t.CUInt64T = (t.CUInt64T(raw_alloc) + 0xFFF) & ~t.CUInt64T(0xFFF) phys_base: t.CVoid | t.CPtr = t.CVoid(phys_base_aligned, t.CPtr) string.memset(phys_base, 0, total_pages * 0x1000) for pi in range(phnum): if phdr_bufs[pi].p_type == PT_LOAD: vaddr: t.CUInt64T = phdr_bufs[pi].p_vaddr file_off: t.CUInt64T = phdr_bufs[pi].p_offset filesz: t.CUInt64T = phdr_bufs[pi].p_filesz memsz: t.CUInt64T = phdr_bufs[pi].p_memsz dest_off: t.CUInt64T = vaddr - lowest_vaddr dest: t.CVoid | t.CPtr = t.CVoid(t.CUInt64T(phys_base) + dest_off, t.CPtr) if filesz > 0: fat32.seek(fp, t.CUInt32T(file_off)) fat32.read(fp, dest, t.CUInt32T(filesz)) if memsz > filesz: bss_start: t.CVoid | t.CPtr = t.CVoid(t.CUInt64T(dest) + filesz, t.CPtr) bss_len: t.CUInt64T = memsz - filesz string.memset(bss_start, 0, bss_len) virt_page_start: t.CUInt64T = (so_load_addr + lowest_vaddr) & ~t.CUInt64T(0xFFF) phys_page_start: t.CUInt64T = t.CUInt64T(phys_base) & ~t.CUInt64T(0xFFF) map_end: t.CUInt64T = (so_load_addr + highest_vaddr_end + 0xFFF) & ~t.CUInt64T(0xFFF) num_map_pages: t.CUInt64T = (map_end - virt_page_start) // 0x1000 for mi in range(num_map_pages): virt_addr: t.CUInt64T = virt_page_start + mi * 0x1000 phys_addr: t.CUInt64T = phys_page_start + mi * 0x1000 paging.MapPage(virt_addr, phys_addr, paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER) base_vaddr: t.CUInt64T = so_load_addr + lowest_vaddr dyn_buf: t.CArray[Elf64_Dyn, MAX_DYNS] string.memset(c.Addr(dyn_buf), 0, MAX_DYNS * 16) dyn_count: t.CInt = 0 for pi in range(phnum): if phdr_bufs[pi].p_type == PT_DYNAMIC: fat32.seek(fp, t.CUInt32T(phdr_bufs[pi].p_offset)) dyn_read: t.CUInt32T = fat32.read(fp, c.Addr(dyn_buf), t.CUInt32T(phdr_bufs[pi].p_filesz)) dyn_count = t.CInt(dyn_read) // 16 strtab_vaddr: t.CUInt64T = 0 symtab_vaddr: t.CUInt64T = 0 hash_vaddr: t.CUInt64T = 0 syment: t.CUInt64T = 0 rela_vaddr: t.CUInt64T = 0 relasz: t.CUInt64T = 0 jmprel_vaddr: t.CUInt64T = 0 pltrelsz: t.CUInt64T = 0 for di in range(dyn_count): if dyn_buf[di].d_tag == DT_NULL: break if dyn_buf[di].d_tag == DT_STRTAB: strtab_vaddr = dyn_buf[di].d_val elif dyn_buf[di].d_tag == DT_SYMTAB: symtab_vaddr = dyn_buf[di].d_val elif dyn_buf[di].d_tag == DT_HASH: hash_vaddr = dyn_buf[di].d_val elif dyn_buf[di].d_tag == DT_SYMENT: syment = dyn_buf[di].d_val elif dyn_buf[di].d_tag == DT_RELA: rela_vaddr = dyn_buf[di].d_val elif dyn_buf[di].d_tag == DT_RELASZ: relasz = dyn_buf[di].d_val elif dyn_buf[di].d_tag == DT_JMPREL: jmprel_vaddr = dyn_buf[di].d_val elif dyn_buf[di].d_tag == DT_PLTRELSZ: pltrelsz = dyn_buf[di].d_val strtab_ptr: t.CVoid | t.CPtr = t.CVoid(strtab_vaddr + so_load_addr, t.CPtr) symtab_ptr: t.CVoid | t.CPtr = t.CVoid(symtab_vaddr + so_load_addr, t.CPtr) if rela_vaddr != 0 and relasz > 0: rela_count: t.CUInt64T = relasz // 24 for ri in range(rela_count): rela: Elf64_Rela | t.CPtr = t.CVoid(t.CUInt64T(rela_vaddr) + so_load_addr + ri * 24, t.CPtr) r_type: t.CUInt32T = t.CUInt32T(rela.r_info & 0xFFFFFFFF) r_sym: t.CUInt32T = t.CUInt32T(rela.r_info >> 32) target_addr: t.CVoid | t.CPtr = t.CVoid(rela.r_offset + so_load_addr, t.CPtr) if r_type == R_X86_64_RELATIVE: val: t.CUInt64T = t.CUInt64T(rela.r_addend) + so_load_addr string.memcpy(target_addr, c.Addr(val), 8) elif r_type == R_X86_64_64 or r_type == R_X86_64_GLOB_DAT or r_type == R_X86_64_JUMP_SLOT: sym: Elf64_Sym | t.CPtr = t.CVoid(t.CUInt64T(symtab_ptr) + t.CUInt64T(r_sym) * syment, t.CPtr) sym_name: t.CConst | t.CChar | t.CPtr = t.CConst(t.CUInt64T(strtab_ptr) + sym.st_name, t.CPtr) resolved: t.CVoid | t.CPtr = _resolve_symbol(sym_name) if resolved is not None: val2: t.CUInt64T = t.CUInt64T(resolved) + t.CUInt64T(rela.r_addend) string.memcpy(target_addr, c.Addr(val2), 8) elif sym.st_value != 0: val3: t.CUInt64T = sym.st_value + so_load_addr + t.CUInt64T(rela.r_addend) string.memcpy(target_addr, c.Addr(val3), 8) if jmprel_vaddr != 0 and pltrelsz > 0: jmprel_count: t.CUInt64T = pltrelsz // 24 for ji in range(jmprel_count): rela2: Elf64_Rela | t.CPtr = t.CVoid(jmprel_vaddr + so_load_addr + ji * 24, t.CPtr) r_type2: t.CUInt32T = t.CUInt32T(rela2.r_info & 0xFFFFFFFF) r_sym2: t.CUInt32T = t.CUInt32T(rela2.r_info >> 32) target2: t.CVoid | t.CPtr = t.CVoid(rela2.r_offset + so_load_addr, t.CPtr) if r_type2 == R_X86_64_JUMP_SLOT or r_type2 == R_X86_64_GLOB_DAT: sym2: Elf64_Sym | t.CPtr = t.CVoid(t.CUInt64T(symtab_ptr) + t.CUInt64T(r_sym2) * syment, t.CPtr) sym_name2: t.CConst | t.CChar | t.CPtr = t.CConst(t.CUInt64T(strtab_ptr) + sym2.st_name, t.CPtr) resolved2: t.CVoid | t.CPtr = _resolve_symbol(sym_name2) if resolved2 is not None: val4: t.CUInt64T = t.CUInt64T(resolved2) + t.CUInt64T(rela2.r_addend) string.memcpy(target2, c.Addr(val4), 8) elif sym2.st_value != 0: val5: t.CUInt64T = sym2.st_value + so_load_addr + t.CUInt64T(rela2.r_addend) string.memcpy(target2, c.Addr(val5), 8) else: cr3: t.CArray[t.CChar, 80] viperlib.snprintf(c.Addr(cr3), 80, "[so] WARN: unresolved PLT sym offset=%lu\n", sym2.st_name) serial.puts(cr3) fat32.close(fp) _so_strtab = strtab_ptr _so_symtab = symtab_ptr _so_syment = syment _so_sym_count = 0 if hash_vaddr != 0: hash_ptr: t.CVoid | t.CPtr = t.CVoid(hash_vaddr + so_load_addr, t.CPtr) nchain: t.CUInt32T = c.Deref(t.CVoid(t.CUInt64T(hash_ptr) + 4, t.CPtr)) _so_sym_count = t.CInt(nchain) elif syment > 0 and strtab_vaddr > symtab_vaddr and symtab_vaddr != 0: sym_tab_size: t.CUInt64T = strtab_vaddr - symtab_vaddr _so_sym_count = t.CInt(sym_tab_size // syment) cr: t.CArray[t.CChar, 80] viperlib.snprintf(c.Addr(cr), 80, "[so] loaded at 0x%lx\n", base_vaddr) serial.puts(cr) fat32.unlock() return t.CVoid(base_vaddr, t.CPtr) def get_so_symbol(so_base: t.CVoid | t.CPtr, name: t.CConst | t.CChar | t.CPtr, so_path: t.CConst | t.CChar | t.CPtr) -> t.CVoid | t.CPtr: if _so_symtab is None or _so_strtab is None or _so_syment == 0: return None if name is None: return None for symi in range(_so_sym_count): sb2: Elf64_Sym | t.CPtr = t.CVoid(t.CUInt64T(_so_symtab) + t.CUInt64T(symi) * _so_syment, t.CPtr) if sb2.st_value != 0 and sb2.st_shndx != 0: sym_name_off2: t.CUInt32T = sb2.st_name cmp_off2: t.CUInt32T = sym_name_off2 match2: t.CInt = 0 ni2: t.CInt = 0 while True: sc3: t.CChar = c.Deref(t.CVoid(t.CUInt64T(_so_strtab) + cmp_off2 + ni2, t.CPtr)) nc3: t.CChar = c.Deref(name + ni2) if sc3 != nc3: break if sc3 == 0: match2 = 1 break ni2 += 1 if match2 == 0: dot_pos3: t.CUInt32T = sym_name_off2 is_sha1_2: t.CInt = 0 sha1_len_2: t.CUInt32T = 0 while True: dc4: t.CChar = c.Deref(t.CVoid(t.CUInt64T(_so_strtab) + dot_pos3, t.CPtr)) if dc4 == 46: if sha1_len_2 >= 8: is_sha1_2 = 1 break if dc4 == 0: break if (dc4 >= 48 and dc4 <= 57) or (dc4 >= 97 and dc4 <= 102): sha1_len_2 += 1 else: break dot_pos3 += 1 if is_sha1_2: cmp_off2 = dot_pos3 + 1 match2 = 1 ni2 = 0 while True: sc4: t.CChar = c.Deref(t.CVoid(t.CUInt64T(_so_strtab) + cmp_off2 + ni2, t.CPtr)) nc4: t.CChar = c.Deref(name + ni2) if sc4 != nc4: match2 = 0 break if sc4 == 0: break ni2 += 1 if match2: result_addr: t.CUInt64T = t.CUInt64T(so_base) + sb2.st_value dbg2: t.CArray[t.CChar, 80] viperlib.snprintf(c.Addr(dbg2), 80, "[so] found '%s' at 0x%lx\n", t.CVoid(t.CUInt64T(_so_strtab) + sym_name_off2, t.CPtr), result_addr) serial.puts(dbg2) return t.CVoid(result_addr, t.CPtr) serial.puts("[so] symbol not found\n") return None