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 import viperlib import string import asm # Do NOT import KERN_STACK_SIZE/USER_STACK_SIZE from process - CDefine cross-module import is broken # Redefine them here to ensure correct values define KERN_STACK_SIZE = 8192 define USER_STACK_SIZE = 65536 define ELFMAG0 = 0x7F define ELFMAG1 = 0x45 define ELFMAG2 = 0x4C define ELFMAG3 = 0x46 define ELFCLASS64 = 2 # app/.so 装载总开关:1 = 正常装载;置 0 可整体跳过(隔离内核问题、去掉 app 侧噪声)。 # 排查内核问题时把这个开关临时置 0 很好用,平时保持 1。 define ELF_LOAD = 1 define ELFDATA2LSB = 1 define ET_EXEC = 2 define ET_DYN = 3 define EM_X86_64 = 62 define PT_LOAD = 1 define PT_DYNAMIC = 2 define PF_X = 1 define PF_W = 2 define PF_R = 4 define DT_NULL = 0 define DT_HASH = 4 define DT_STRTAB = 5 define DT_SYMTAB = 6 define DT_STRSZ = 10 define DT_SYMENT = 11 define DT_RELA = 7 define DT_RELASZ = 8 define DT_RELAENT = 9 define DT_PLTRELSZ = 2 define DT_JMPREL = 23 define R_X86_64_64 = 1 define R_X86_64_GLOB_DAT = 6 define R_X86_64_JUMP_SLOT = 7 define R_X86_64_RELATIVE = 8 define STB_GLOBAL = 1 define STB_WEAK = 2 @__attribute__(packed) class Elf64_Ehdr: e_ident: [16]uint8 e_type: uint16 e_machine: uint16 e_version: uint32 e_entry: uint64 e_phoff: uint64 e_shoff: uint64 e_flags: uint32 e_ehsize: uint16 e_phentsize: uint16 e_phnum: uint16 e_shentsize: uint16 e_shnum: uint16 e_shstrndx: uint16 @__attribute__(packed) class Elf64_Phdr: p_type: uint32 p_flags: uint32 p_offset: uint64 p_vaddr: uint64 p_paddr: uint64 p_filesz: uint64 p_memsz: uint64 p_align: uint64 define MAX_PHDRS = 16 @__attribute__(packed) class Elf64_Shdr: sh_name: uint32 sh_type: uint32 sh_flags: uint64 sh_addr: uint64 sh_offset: uint64 sh_size: uint64 sh_link: uint32 sh_info: uint32 sh_addralign: uint64 sh_entsize: uint64 @__attribute__(packed) class Elf64_Sym: st_name: uint32 st_info: uint8 st_other: uint8 st_shndx: uint16 st_value: uint64 st_size: uint64 @__attribute__(packed) class Elf64_Rela: r_offset: uint64 r_info: uint64 r_addend: int64 @__attribute__(packed) class Elf64_Dyn: d_tag: int64 d_val: uint64 define MAX_SHDRS = 64 define MAX_DYNS = 64 define MAX_RELAS = 256 def load_elf(path: const str, do_map: int = 1) -> rawptr: fp: fat32_types.fileobj* = fat32.open(path, fat32_types.FA_READ) if fp is None: err: uint32 = fat32.last_error() eb: [64]char viperlib.snprintf(&eb, 64, "[elf] failed to open file err=%d\n", err) serial.puts(eb) return None ehdr_buf: [64]uint8 string.memset(&ehdr_buf, 0, 64) read_len: uint32 = fat32.read(fp, &ehdr_buf, 64) # [DIAG] read_len 实际值 + call 后 RAX 快照:区分"call 返回错"(rl=0 rax=0) # vs "store 后 RAX 被覆盖"(rl=0 rax=64) _rax_v2: uint64 = 0 # 读 RAX 未声明输入:至少须声明 CLOBBER_RAX,让编译器知道本块不保留 # RAX(此前只声明 ~{memory},编译器可假设 RAX 存活 → 读到的值不保证是 # 紧邻 call 的返回值)。N16。(本块为 [DIAG] 探针,问题定位后建议删除。) __asm__(f"""mov {asmout(_rax_v2, ASM_DESCR.OUTPUT_REG)}, rax""", op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX]) _dbrl: [96]char viperlib.snprintf(&_dbrl, 96, "[elf] rl=%lu rax=0x%lx\n", read_len, _rax_v2) serial.puts(&_dbrl) if read_len < 64: serial.puts("[elf] failed to read ehdr\n") fat32.close(fp) return None ehdr: Elf64_Ehdr* = &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") # [DIAG] 一次性定位 bad magic:dump ehdr_buf 前 16 字节 + read_len, # 区分全 0(read 未写)/ 目录数据(sector 错)/ 垃圾(seek 或缓冲错)。 _dbg_mg: [120]char viperlib.snprintf(&_dbg_mg, 120, "[elf] mg=%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x rl=%lu fp=%lu/%lu/%lu/%lu\n", ehdr_buf[0], ehdr_buf[1], ehdr_buf[2], ehdr_buf[3], ehdr_buf[4], ehdr_buf[5], ehdr_buf[6], ehdr_buf[7], ehdr_buf[8], ehdr_buf[9], ehdr_buf[10], ehdr_buf[11], ehdr_buf[12], ehdr_buf[13], ehdr_buf[14], ehdr_buf[15], read_len, fp.first_cluster, fp.current_cluster, fp.file_pos, fp.file_size) serial.puts(&_dbg_mg) 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") # [DIAG] e_machine 错:dump ehdr 前 24 字节(read_len=64 正确但内容错) _dbg_em: [128]char viperlib.snprintf(&_dbg_em, 128, "[elf] ehdr=%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x rl=%lu\n", ehdr_buf[0], ehdr_buf[1], ehdr_buf[2], ehdr_buf[3], ehdr_buf[4], ehdr_buf[5], ehdr_buf[6], ehdr_buf[7], ehdr_buf[8], ehdr_buf[9], ehdr_buf[10], ehdr_buf[11], ehdr_buf[12], ehdr_buf[13], ehdr_buf[14], ehdr_buf[15], ehdr_buf[16], ehdr_buf[17], ehdr_buf[18], ehdr_buf[19], read_len) serial.puts(&_dbg_em) fat32.close(fp) return None entry_point: uint64 = ehdr.e_entry phoff: uint64 = ehdr.e_phoff phentsize: uint16 = ehdr.e_phentsize phnum: uint16 = 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: [80]char viperlib.snprintf(&cr, 80, "[elf] entry=0x%lx phoff=%lu phentsize=%u phnum=%u\n", entry_point, phoff, phentsize, phnum) serial.puts(cr) phdr_bufs: [MAX_PHDRS]Elf64_Phdr string.memset(&phdr_bufs, 0, MAX_PHDRS * 56) for pi in range(phnum): phdr_off: uint64 = phoff + uint64(pi) * uint64(phentsize) fat32.seek(fp, uint32(phdr_off)) fat32.read(fp, &phdr_bufs[pi], 56) found_load: int = 0 lowest_vaddr: uint64 = 0 highest_vaddr_end: uint64 = 0 for pi in range(phnum): if phdr_bufs[pi].p_type == PT_LOAD: seg_end: uint64 = 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: uint64 = highest_vaddr_end - lowest_vaddr total_pages: uint64 = (total_memsz + 0xFFF) // 0x1000 cr2: [80]char viperlib.snprintf(&cr2, 80, "[elf] vaddr 0x%lx-0x%lx pages=%lu\n", lowest_vaddr, highest_vaddr_end, total_pages) serial.puts(cr2) raw_alloc: rawptr = mm.malloc(total_pages * 0x1000 + 0x1000) _db_ra: [80]char viperlib.snprintf(&_db_ra, 80, "[elf] malloc ret raw_alloc=0x%lx\n", uint64(raw_alloc)) serial.puts(_db_ra) if not raw_alloc: serial.puts("[elf] malloc failed\n") fat32.close(fp) return None phys_base_aligned: uint64 = (uint64(raw_alloc) + uint64(0xFFF)) & ~uint64(0xFFF) phys_base: rawptr = rawptr(phys_base_aligned) string.memset(phys_base, 0, total_pages * 0x1000) for pi in range(phnum): if phdr_bufs[pi].p_type == PT_LOAD: vaddr: uint64 = phdr_bufs[pi].p_vaddr file_off: uint64 = phdr_bufs[pi].p_offset filesz: uint64 = phdr_bufs[pi].p_filesz memsz: uint64 = phdr_bufs[pi].p_memsz dest_off: uint64 = vaddr - lowest_vaddr dest: rawptr = rawptr(uint64(phys_base) + dest_off) if filesz > 0: fat32.seek(fp, uint32(file_off)) fat32.read(fp, dest, uint32(filesz)) if memsz > filesz: bss_start: rawptr = rawptr(uint64(dest) + filesz) bss_len: uint64 = memsz - filesz string.memset(bss_start, 0, bss_len) fat32.close(fp) virt_page_start: uint64 = lowest_vaddr & ~uint64(0xFFF) phys_page_start: uint64 = uint64(phys_base) & ~uint64(0xFFF) map_end: uint64 = (highest_vaddr_end + 0xFFF) & ~uint64(0xFFF) num_map_pages: uint64 = (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: uint64 = virt_page_start + mi * 0x1000 phys_addr: uint64 = phys_page_start + mi * 0x1000 page_flags: uint64 = paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER paging.MapPage(virt_addr, phys_addr, page_flags) entry_addr: rawptr = rawptr(entry_point) return entry_addr def run_elf(path: const str) -> int: entry: rawptr = load_elf(path) if entry is None: serial.puts("[elf] load failed\n") return -1 serial.puts("[elf] jumping to entry...\n") # 被调方是任意 ELF 入口(外部代码):`call` 破坏全部 caller-saved 寄存器, # 除 rax/rcx/rdx/r8~r11 外必须声明 rsi/rdi(N20)。 __asm__(f"""call {asminp(entry, ASM_DESCR.REG_ANY)}""", op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX, ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RDX, ASM_DESCR.CLOBBER_RSI, ASM_DESCR.CLOBBER_RDI, ASM_DESCR.CLOBBER_R8, ASM_DESCR.CLOBBER_R9, ASM_DESCR.CLOBBER_R10, ASM_DESCR.CLOBBER_R11]) serial.puts("[elf] returned from entry\n") return 0 define MAX_SPAWNED = 8 _elf_entries: [MAX_SPAWNED]rawptr # User stack virtual address: top of 48-bit user space, 64KB below define USER_STACK_VTOP = 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: uint64 = 0 _last_elf_virt_page_start: uint64 = 0 _last_elf_num_map_pages: uint64 = 0 def _elf_entry_wrapper(arg: rawptr) -> int: pid: int = int(uint64(arg)) _db1: [64]char viperlib.snprintf(&_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* = proc.ProcessManager.current() entry_fn: rawptr = cur_p.entry # [DIAG] 定位用户进程首个指令 PF:打印 entry 实际值(PF cr2=ELF 物理区最后一页, # 疑似 entry 被写成 phys 地址而非虚拟 0x400000)。 _dbw: [160]char viperlib.snprintf(&_dbw, 160, "[wrap] pid=%d cur=0x%lx ent=0x%lx ebase=0x%lx ks=0x%lx us=0x%lx\n", pid, uint64(cur_p), uint64(entry_fn), cur_p.elf_base, cur_p.kern_stack, cur_p.user_stack) serial.puts(&_dbw) if entry_fn is not None: user_stack_vtop: uint64 = USER_STACK_VTOP us_sz: uint64 = USER_STACK_SIZE user_stack_vbottom: uint64 = USER_STACK_VTOP - us_sz # Physical pages of the user stack (allocated by mm.malloc, identity-mapped) us_size: uint64 = USER_STACK_SIZE phys_bottom: uint64 = (cur_p.user_stack - us_size) & ~uint64(0xFFF) # Map each page of the user stack at the virtual address with PTE_USER num_stack_pages: uint64 = 16 # USER_STACK_SIZE(65536) / PAGE_SIZE(4096) = 16 si: uint64 for si in range(num_stack_pages): vaddr: uint64 = (user_stack_vbottom & ~uint64(0xFFF)) + si * uint64(0x1000) paddr: uint64 = phys_bottom + si * uint64(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* = proc.ProcessManager.current() cur.exit() return 0 def spawn_elf(path: const str) -> int: if ELF_LOAD == 0: return -1 # [临时] 见文件头 ELF_LOAD 开关说明 fat32.lock() name_start: int = 0 si: int = 0 while True: ch: char = path[si] if ch == 0: break if ch == 47: name_start = si + 1 si += 1 lowest_vaddr: uint64 = 0 highest_vaddr_end: uint64 = 0 fp_scan: fat32_types.fileobj* = fat32.open(path, fat32_types.FA_READ) if fp_scan is None: serr: uint32 = fat32.last_error() seb: [64]char viperlib.snprintf(&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: [64]uint8 string.memset(&ehdr_buf_s, 0, 64) fat32.read(fp_scan, &ehdr_buf_s, 64) ehdr_s: Elf64_Ehdr* = &ehdr_buf_s if ehdr_s.e_type == ET_EXEC or ehdr_s.e_type == ET_DYN: phoff_s: uint64 = ehdr_s.e_phoff phentsize_s: uint16 = ehdr_s.e_phentsize phnum_s: uint16 = ehdr_s.e_phnum if phentsize_s > 0 and phnum_s > 0 and phnum_s <= MAX_PHDRS: phdr_bufs_s: [MAX_PHDRS]Elf64_Phdr string.memset(&phdr_bufs_s, 0, MAX_PHDRS * 56) found_load_s: int = 0 for pi_s in range(phnum_s): fat32.seek(fp_scan, uint32(phoff_s + uint64(pi_s) * uint64(phentsize_s))) fat32.read(fp_scan, &phdr_bufs_s[pi_s], 56) if phdr_bufs_s[pi_s].p_type == PT_LOAD: seg_end_s: uint64 = 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: uint64 = 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: rawptr = 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: uint64 = paging.clone_for_process(isolate_va) slog1: [80]char viperlib.snprintf(&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: uint64 = 0 __asm__(f"""mov {asmout(_kernel_cr3, ASM_DESCR.OUTPUT_REG)}, cr3""", op=[ASM_DESCR.CLOBBER_MEMORY]) __asm__(f"mov cr3, {asminp(proc_pml4, ASM_DESCR.REG_ANY)}", op=[ASM_DESCR.CLOBBER_MEMORY]) paging.set_active_pml4(proc_pml4) mi: uint64 for mi in range(_last_elf_num_map_pages): _va: uint64 = _last_elf_virt_page_start + mi * 0x1000 _pa: uint64 = _last_elf_phys_page_start + mi * 0x1000 paging.MapPage(_va, _pa, paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER) __asm__(f"mov cr3, {asminp(_kernel_cr3, ASM_DESCR.REG_ANY)}", op=[ASM_DESCR.CLOBBER_MEMORY]) paging.set_active_pml4(_kernel_cr3) asm.sti() # path[name_start:] 切片表达式 translate_subscript 不支持 Slice(返回 None)→ # 作为实参丢 name、entry 错位成第一参数(VPC 已修,此处指针算术双保险) nm_ptr: const str = str(uint64(path) + uint64(name_start)) p: proc.Process* = proc.ProcessManager.create_process(nm_ptr, entry) if p is None: serial.puts("[elf] spawn: create_process failed\n") fat32.unlock() return -2 p.elf_base = uint64(entry) p.pml4_root = proc_pml4 if p.pid >= 0 and p.pid < MAX_SPAWNED: _elf_entries[p.pid] = entry th: sched.Thread* = sched.Scheduler.create_thread(&_elf_entry_wrapper, rawptr(uint64(p.pid)), p.pid) if th is None: serial.puts("[elf] spawn: create_thread failed\n") p.exit() fat32.unlock() return -3 # Ring3 → Ring0 落栈点按**线程**切换(见 sched._yield):主线程也必须登记进程内核栈, # 否则同进程的额外用户线程会把它当成"没有内核栈的线程"而沿用上一个线程的栈。 th.kern_stack = p.kern_stack tid: int = th.tid p.addThread(tid) slog: [80]char viperlib.snprintf(&slog, 80, "[elf] spawn: pid=%d tid=%d entry=0x%lx pml4=0x%lx\n", p.pid, tid, uint64(entry), p.pml4_root) serial.puts(&slog) # [DIAG] 确认 return p.pid 的实际值(main 收到 pid=0 而此处打印 3) _dbw2: [64]char viperlib.snprintf(&_dbw2, 64, "[elf] spawn ret pid=%d\n", p.pid) serial.puts(&_dbw2) fat32.unlock() return p.pid def _resolve_symbol(name: const str) -> rawptr: nm: int = 0 while name[nm] != 0: nm += 1 cmp_name: const str = name cmp_len: int = nm if nm > 9: sha1_len2: int = 0 for si2 in range(nm): ch2: char = name[si2] if ch2 == 46: if sha1_len2 >= 8: cmp_name = str(uint64(name) + si2 + 1) 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: Matched: int = 1 for i in range(cmp_len): if cmp_name[i] != "serial_puts"[i]: Matched = 0 if Matched != 0: return serial.puts return None _so_strtab: rawptr _so_symtab: rawptr _so_syment: uint64 _so_sym_count: int def load_so(path: const str) -> rawptr: if ELF_LOAD == 0: return None # [临时] 见文件头 ELF_LOAD 开关说明 fat32.lock() fp: fat32_types.fileobj* = 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: [64]uint8 string.memset(&ehdr_buf, 0, 64) read_len: uint32 = fat32.read(fp, &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* = &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: uint64 = ehdr.e_phoff phentsize: uint16 = ehdr.e_phentsize phnum: uint16 = ehdr.e_phnum # 程序头数量越界保护(phdr_bufs 仅 MAX_PHDRS 项):此前缺此校验, # 异常/恶意 e_phnum > MAX_PHDRS 会越界写栈(load_elf 已有同款校验)。 if phnum > MAX_PHDRS: serial.puts("[so] phnum exceeds MAX_PHDRS\n") fat32.close(fp) fat32.unlock() return None phdr_bufs: [MAX_PHDRS]Elf64_Phdr string.memset(&phdr_bufs, 0, MAX_PHDRS * 56) for pi in range(phnum): phdr_off: uint64 = phoff + uint64(pi) * uint64(phentsize) fat32.seek(fp, uint32(phdr_off)) fat32.read(fp, &phdr_bufs[pi], 56) found_load: int = 0 lowest_vaddr: uint64 = 0 highest_vaddr_end: uint64 = 0 for pi in range(phnum): if phdr_bufs[pi].p_type == PT_LOAD: seg_end: uint64 = 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: uint64 = highest_vaddr_end - lowest_vaddr total_pages: uint64 = (total_memsz + 0xFFF) // 0x1000 so_load_addr: uint64 = 0x600000 raw_alloc: rawptr = 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: uint64 = (uint64(raw_alloc) + 0xFFF) & ~uint64(0xFFF) phys_base: rawptr = rawptr(phys_base_aligned) string.memset(phys_base, 0, total_pages * 0x1000) for pi in range(phnum): if phdr_bufs[pi].p_type == PT_LOAD: vaddr: uint64 = phdr_bufs[pi].p_vaddr file_off: uint64 = phdr_bufs[pi].p_offset filesz: uint64 = phdr_bufs[pi].p_filesz memsz: uint64 = phdr_bufs[pi].p_memsz dest_off: uint64 = vaddr - lowest_vaddr dest: rawptr = rawptr(uint64(phys_base) + dest_off) if filesz > 0: fat32.seek(fp, uint32(file_off)) fat32.read(fp, dest, uint32(filesz)) if memsz > filesz: bss_start: rawptr = rawptr(uint64(dest) + filesz) bss_len: uint64 = memsz - filesz string.memset(bss_start, 0, bss_len) virt_page_start: uint64 = (so_load_addr + lowest_vaddr) & ~uint64(0xFFF) phys_page_start: uint64 = uint64(phys_base) & ~uint64(0xFFF) map_end: uint64 = (so_load_addr + highest_vaddr_end + 0xFFF) & ~uint64(0xFFF) num_map_pages: uint64 = (map_end - virt_page_start) // 0x1000 for mi in range(num_map_pages): virt_addr: uint64 = virt_page_start + mi * 0x1000 phys_addr: uint64 = phys_page_start + mi * 0x1000 paging.MapPage(virt_addr, phys_addr, paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER) base_vaddr: uint64 = so_load_addr + lowest_vaddr dyn_buf: [MAX_DYNS]Elf64_Dyn string.memset(&dyn_buf, 0, MAX_DYNS * 16) dyn_count: int = 0 for pi in range(phnum): if phdr_bufs[pi].p_type == PT_DYNAMIC: fat32.seek(fp, uint32(phdr_bufs[pi].p_offset)) # p_filesz 无上界,直接读会越界写 dyn_buf(MAX_DYNS*16 字节)→ 夹取 filesz_d: uint32 = uint32(phdr_bufs[pi].p_filesz) max_dyn_bytes: uint32 = uint32(MAX_DYNS * 16) if filesz_d > max_dyn_bytes: filesz_d = max_dyn_bytes dyn_read: uint32 = fat32.read(fp, &dyn_buf, filesz_d) dyn_count = int(dyn_read) // 16 strtab_vaddr: uint64 = 0 symtab_vaddr: uint64 = 0 hash_vaddr: uint64 = 0 syment: uint64 = 0 rela_vaddr: uint64 = 0 relasz: uint64 = 0 jmprel_vaddr: uint64 = 0 pltrelsz: uint64 = 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: rawptr = rawptr(strtab_vaddr + so_load_addr) symtab_ptr: rawptr = rawptr(symtab_vaddr + so_load_addr) if rela_vaddr != 0 and relasz > 0: rela_count: uint64 = relasz // 24 for ri in range(rela_count): rela: Elf64_Rela* = Elf64_Rela*(uint64(rela_vaddr) + so_load_addr + ri * 24) r_type: uint32 = uint32(rela.r_info & 0xFFFFFFFF) r_sym: uint32 = uint32(rela.r_info >> 32) target_addr: rawptr = rawptr(rela.r_offset + so_load_addr) if r_type == R_X86_64_RELATIVE: val: uint64 = uint64(rela.r_addend) + so_load_addr string.memcpy(target_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* = Elf64_Sym*(uint64(symtab_ptr) + uint64(r_sym) * syment) sym_name: const str = str(uint64(strtab_ptr) + sym.st_name) resolved: rawptr = _resolve_symbol(sym_name) if resolved is not None: val2: uint64 = uint64(resolved) + uint64(rela.r_addend) string.memcpy(target_addr, &val2, 8) elif sym.st_shndx != 0: # 本 .so 内定义的符号:按 st_value 定位。用 st_shndx 判定义(st_value 可为 0, # 见 get_so_symbol 的说明)—— 否则指向段内偏移 0 的符号不会被重定位。 val3: uint64 = sym.st_value + so_load_addr + uint64(rela.r_addend) string.memcpy(target_addr, &val3, 8) if jmprel_vaddr != 0 and pltrelsz > 0: jmprel_count: uint64 = pltrelsz // 24 for ji in range(jmprel_count): rela2: Elf64_Rela* = Elf64_Rela*(jmprel_vaddr + so_load_addr + ji * 24) r_type2: uint32 = uint32(rela2.r_info & 0xFFFFFFFF) r_sym2: uint32 = uint32(rela2.r_info >> 32) target2: rawptr = rawptr(rela2.r_offset + so_load_addr) if r_type2 == R_X86_64_JUMP_SLOT or r_type2 == R_X86_64_GLOB_DAT: sym2: Elf64_Sym* = Elf64_Sym*(uint64(symtab_ptr) + uint64(r_sym2) * syment) sym_name2: const str = str(uint64(strtab_ptr) + sym2.st_name) resolved2: rawptr = _resolve_symbol(sym_name2) if resolved2 is not None: val4: uint64 = uint64(resolved2) + uint64(rela2.r_addend) string.memcpy(target2, &val4, 8) elif sym2.st_shndx != 0: # 同上:本 .so 内定义的符号按 st_value 定位(st_value 可以为 0) val5: uint64 = sym2.st_value + so_load_addr + uint64(rela2.r_addend) string.memcpy(target2, &val5, 8) else: cr3: [80]char viperlib.snprintf(&cr3, 80, "[so] WARN: unresolved PLT sym offset=%lu\n", sym2.st_name) serial.puts(&cr3) fat32.close(fp) global _so_strtab, _so_symtab, _so_syment, _so_sym_count _so_strtab = strtab_ptr _so_symtab = symtab_ptr _so_syment = syment _so_sym_count = 0 if hash_vaddr != 0: hash_ptr: rawptr = rawptr(hash_vaddr + so_load_addr) # 宽度只由指针的元素类型决定:nchain 是 4 字节字段,必须给出 u32* # (i8* 现在恒读 1 字节)。地址口径不变(hash_ptr + 4 字节)。 nchain: uint32 = *(uint32*(uint64(hash_ptr) + 4)) _so_sym_count = int(nchain) elif syment > 0 and strtab_vaddr > symtab_vaddr and symtab_vaddr != 0: sym_tab_size: uint64 = strtab_vaddr - symtab_vaddr _so_sym_count = int(sym_tab_size // syment) cr: [80]char viperlib.snprintf(&cr, 80, "[so] loaded at 0x%lx\n", base_vaddr) serial.puts(cr) fat32.unlock() return rawptr(base_vaddr) def get_so_symbol(so_base: rawptr, name: const str, so_path: const str) -> rawptr: 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* = Elf64_Sym*(uint64(_so_symtab) + uint64(symi) * _so_syment) # 判“已定义”只能看 st_shndx(SHN_UNDEF=0 才是未定义)。**不能**把 st_value==0 当未定义: # 导出函数在自己的段内偏移可以为 0(实测 slog.so 的 log_info:st_value=0x0、st_shndx=.text), # 用 st_value!=0 过滤会把它整条跳过 → 恒报 “[so] symbol not found”。 if sb2.st_shndx != 0: sym_name_off2: uint32 = sb2.st_name cmp_off2: uint32 = sym_name_off2 match2: int = 0 ni2: int = 0 while True: sc3: char = *(char*(uint64(_so_strtab) + cmp_off2 + ni2)) nc3: char = *(name + ni2) if sc3 != nc3: break if sc3 == 0: match2 = 1 break ni2 += 1 if match2 == 0: dot_pos3: uint32 = sym_name_off2 is_sha1_2: int = 0 sha1_len_2: uint32 = 0 while True: dc4: char = *(char*(uint64(_so_strtab) + dot_pos3)) 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: char = *(char*(uint64(_so_strtab) + cmp_off2 + ni2)) nc4: char = *(name + ni2) if sc4 != nc4: match2 = 0 break if sc4 == 0: break ni2 += 1 if match2: result_addr: uint64 = uint64(so_base) + sb2.st_value dbg2: [80]char viperlib.snprintf(&dbg2, 80, "[so] found '%s' at 0x%lx\n", rawptr(uint64(_so_strtab) + sym_name_off2), result_addr) serial.puts(&dbg2) return rawptr(result_addr) serial.puts("[so] symbol not found\n") return None