自制 x86-64 内核(47):动态链接器基础

这一章实现了对 PIE(Position-Independent Executable)程序的支持,也就是不加 -static 编译出来的"动态"程序。 问题:PIE 和静态 ELF 的区别 用 musl-gcc 编译一个简单的 hello world: # 静态(之前一直用的) musl-gcc -static -O2 -o hello hello.c # 动态(PIE) musl-gcc -O2 -o hello_pie hello.c file 查看: hello: ELF 64-bit LSB executable, x86-64, statically linked hello_pie: ELF 64-bit LSB pie executable, x86-64, dynamically linked 区别在于: 属性 静态 (ET_EXEC) PIE (ET_DYN) e_type 2 3 虚地址 固定(如 0x400000) 从 0 开始,需加载时指定 base 重定位 无 .rela.dyn 表 外部依赖 无 通常有(但 musl PIE 已内嵌 libc) 为什么 musl PIE 没有"真正的"动态链接? musl-gcc 默认把所有 libc 代码静态链接进去,只是编译为 PIE 格式(支持 ASLR)。所以运行时: ...

June 3, 2026 · 1 min · 大飞

从零写OS(十九):exec 与用户程序

之前所有代码都跑在内核态(ring0)。这一章第一次让用户程序在 ring3 里运行——加载 ELF 文件,跳到用户态执行,用户程序通过 syscall 和内核通信。 这是操作系统最核心的一个边界:内核和用户程序之间的隔离。 exec 做什么 exec 的语义是"用一个新程序替换当前进程": 从文件系统读 ELF 文件 解析 ELF header,找到各个段的加载地址 创建新的用户页表,把各段加载进去 分配用户栈 设置进程的 rip = 入口地址,rsp = 栈顶,cs/ss = 用户段,放入就绪队列 调度器下次选中这个进程,就会跳到用户态开始执行。 进入用户态:iretq 第一次进入用户态不能用 sysret——没有对应的 syscall。用 iretq: static void enter_usermode(uint64_t rip, uint64_t cs, uint64_t rflags, uint64_t rsp, uint64_t ss) { __asm__ volatile( "push %4\n" // ss "push %3\n" // rsp "push %2\n" // rflags(IF=1,开中断) "push %1\n" // cs(0x23,ring3 代码段) "push %0\n" // rip(entry point) "iretq" :: "r"(rip), "r"(cs), "r"(rflags), "r"(rsp), "r"(ss) ); } iretq 弹出这 5 个字段,CPU 检查 cs 的 RPL 发现是 ring3,自动完成特权级切换。 ...

May 9, 2026 · 2 min · 大飞

从零写OS(十六):ELF 加载器,运行第一个用户程序

这一章做完,我们的系统就有了完整的用户程序执行链路:写一个独立的程序,编译成 ELF,放到磁盘上,Shell 里输入 run hello.elf,内核加载并执行它。 ELF 是什么 ELF(Executable and Linkable Format)是 Linux 可执行文件的格式。你编译出来的每个程序、/bin/ls、/usr/bin/python 都是 ELF 文件。 结构很简单: ELF Header → 魔数、架构、入口地址 Program Headers → 每个段加载到内存哪里、从文件哪里读 .text → 代码 .data → 数据 .bss → 未初始化数据(文件里不占空间,加载时清零) 加载一个 ELF,本质上就是:按 Program Header 的指示,把文件里的数据复制到内存里,然后跳到入口地址。 先写用户程序 用户程序不能调内核函数,只能通过 syscall 和内核通信: BITS 64 section .text global _start _start: mov rax, 1 ; syscall 1 = print lea rdi, [rel msg] ; RIP 相对寻址 syscall mov rax, 2 ; syscall 2 = exit syscall section .data msg: db "Hello from ELF!", 13, 10, 0 链接时指定加载地址 0x400000(避开内核占用的低地址区域): ENTRY(_start) SECTIONS { . = 0x400000; .text : { *(.text) } .data : { *(.data) } } ELF 加载器 加载流程: ...

May 6, 2026 · 2 min · 大飞
京ICP备14031575号-3