
从execve到新入口Linux 如何装载并启动 ELF 程序课程《Linux内核原理与分析》**实验**实验七第八周**姓名**叶丁再**学号**20262809**日期**2026 年 9 月 27 日一、问题执行新程序时旧程序去了哪里在 shell 或程序中执行一个命令看起来像“启动了另一个程序”。Linux 的关键动作是execve内核检查并装载新的可执行文件用新的地址空间、代码、数据和初始用户栈替换调用进程原来的用户态映像。成功时execve不会返回到旧程序的下一条指令失败时才返回错误。本实验围绕三个问题展开ELF 文件怎样描述装载需求execve内核路径如何建立新程序的入口和栈静态链接与动态链接程序开始运行时有什么不同二、实验环境与证据实验在实验楼环境中完成内核调试对象为 Linux 3.18.6 的vmlinuxQEMU 通过bzImage和rootfs.img启动 MenuOS。用户态测试覆盖了静态、动态 ELF装载时动态链接、运行时dlopen以及通过execv和 MenuOS 菜单执行程序。下文将截图直接显示的内容称为“实验观察”由 Linux 3.18.6 源码与 ELF 规则得出的内容称为“机制分析”。GDB 在sys_execve的栈回溯曾出现无法继续展开的提示因此不把用户态调用栈说成已经完整动态跟踪。三、先看 ELF入口地址和解释器信息ELF 文件包含 ELF header、程序头表、节区等信息。装载器主要依据程序头表中的PT_LOAD将文件内容映射到进程地址空间。ELF header 中的e_entry给出该 ELF 映像的入口地址若存在PT_INTERP它还指出运行该映像所需的 ELF 解释器。实验中的hello-static是 32 位 i386 静态 ELF入口为0x8048d0a没有PT_INTERP。hello-dynamic同样是 32 位 i386 ELF应用入口为0x8048350并带有/lib/ld-linux.so.2解释器段动态节还声明依赖libc.so.6。两种动态库使用方式实验还用libgreet.so对比了两类动态链接。greet-linked的动态节中有NEEDED: libgreet.so并通过$ORIGIN/../lib查找库程序启动装载时动态链接器会解析这个依赖。greet-dlopen的动态节没有libgreet.so这一项而是由程序运行到相应代码后调用dlopen打开库再通过dlsym找到greet函数。两种程序都实际输出了问候信息。四、用户态execv测试execv(path, argv)是 libc 提供的执行接口之一底层请求内核执行指定路径。下面是实验使用的执行驱动程序的核心逻辑整理了截图中因终端编码显示异常的提示字符串路径选择、参数构造和execv调用与实验一致#include stdio.h #include string.h #include unistd.h int main(int argc, char *argv[]) { const char *path; char *child_argv[3]; if (argc ! 2 || (strcmp(argv[1], static) ! 0 strcmp(argv[1], dynamic) ! 0)) { fprintf(stderr, usage: %s static|dynamic\n, argv[0]); return 1; } path (strcmp(argv[1], static) 0) ? ./bin/hello-static : ./bin/hello-dynamic; child_argv[0] (char *)path; child_argv[1] from-execv; child_argv[2] NULL; printf(execv - %s\n, path); fflush(stdout); execv(path, child_argv); /* 只有 execv 失败控制流才会到达这里。 */ perror(execv); return 1; }运行截图中静态和动态目标程序都输出Hello from the new executable!并显示argc 2、argv[0]为程序路径、argv[1] from-execv。新程序读取到的参数说明内核在建立新用户栈时也带入了调用者提供的参数向量。MenuOS 测试采用fork后由子进程执行execv父进程等待子进程结束。QEMU 中分别执行静态和动态目标二者都输出argv[1] from-MenuOS子进程退出码为 0MenuOS 提示符恢复。这同时验证了 initramfs 中程序及动态解释器、共享库等依赖可被找到。五、从系统调用到start_threadGDB 使用与 QEMU 内核对应的vmlinux设置sys_execve、load_elf_binary和start_thread断点。截图显示的主要调用路径为用户态 execv() └─ execve 系统调用 └─ SYSCALL_DEFINE3(execve) / Sys_execve └─ do_execve() └─ exec_binprm() / search_binary_handler() └─ load_elf_binary() └─ start_thread(regs, new_ip, new_sp) └─ 系统调用退出并返回用户态新映像这条路径体现了内核的分工execve系统调用把文件名、参数和环境向量交给执行子系统二进制格式处理器识别 ELFload_elf_binary检查 ELF 头和程序头、映射程序段、处理解释器并准备新地址空间最后start_thread更新保存的用户态寄存器现场让系统调用返回路径回到新程序的入口。实验截图中静态目标在start_thread处得到new_ip 0x8048d0a、new_sp 0xbfbf3080。new_ip与静态 ELF 的e_entry一致说明无解释器时处理器从应用映像入口开始执行。new_sp是传给start_thread的用户栈指针值截图没有转储该地址附近的内存因此不能把栈中每个字段说成现场直接读出。六、动态 ELF 为什么先进入解释器动态 ELF 的PT_INTERP指定动态链接器。实验确认解释器路径为/lib/ld-linux.so.2程序依赖libc.so.6。Linux 3.18.6 的 ELF 装载路径会同时安排主程序和解释器映像当存在解释器时start_thread的new_ip指向解释器入口而不是直接等于应用程序的e_entry。本次 GDB 截图中动态 ELF 的new_ip 0xb77d90d0new_sp 0xbfed8be0。应用本身的e_entry是0x8048350两者不同结合PT_INTERP可以解释为处理器先进入动态链接器。动态链接器随后完成共享对象映射和必要的重定位再把控制权交给应用程序入口。因而execve返回用户态时静态程序通常已在自身入口开始运行动态程序则先从 ELF 解释器入口运行之后才进入应用入口。动态程序的入口与栈地址和静态程序不同也可能受地址空间布局随机化及映射布局影响。这里记录的是本次截图中的具体数值不应当视为每次运行都固定的地址。七、初始用户栈承载什么无论静态还是动态程序内核都要为新映像准备参数、环境变量和辅助向量等初始信息。对 32 位 x86 用户态初始栈顶可概念化为低地址 / 初始 ESP 指向 ┌─────────────────────────────┐ │ argc │ ├─────────────────────────────┤ │ argv[0] 指针 │ │ argv[1] 指针 │ │ ... │ │ NULL │ ├─────────────────────────────┤ │ envp[0]、envp[1] ... │ │ NULL │ ├─────────────────────────────┤ │ auxv类型/值对以 AT_NULL 结束│ ├─────────────────────────────┤ │ 参数字符串、环境字符串等 │ └─────────────────────────────┘ 高地址这表示程序无需沿用旧程序的调用栈帧它从内核建立的全新启动栈和入口寄存器现场开始。程序运行时的 C 运行库启动代码再根据argc、argv、envp和辅助向量完成初始化并调用main。此图是根据 ELF 用户态启动约定和 Linux 装载机制整理的概念图本次实验只观察了new_sp数值和目标程序打印出的参数没有用 GDB 逐字节检查栈内存。八、装载与启动流程否静态 ELF是动态 ELF静态动态用户程序调用 execv / execve进入内核系统调用处理do_execve 与二进制格式识别load_elf_binary 检查 ELF 与程序头映射 PT_LOAD 段并建立新地址空间是否有 PT_INTERP入口设为应用 e_entry映射动态解释器建立参数、环境变量、auxv 初始栈start_thread 设置 new_ip 与 new_sp系统调用退出返回新用户态映像静态 / 动态直接执行应用入口解释器装入依赖并重定位跳转至应用 e_entry九、总结计算机怎样开始执行一个新程序这次实验让我看到“执行一个程序”不是简单地把文件读进内存再跳过去。用户态先通过系统调用提出请求内核识别 ELF 格式验证文件并按程序头建立映射准备新的地址空间、入口寄存器和初始栈随后处理器从start_thread设置的用户态入口继续执行。系统调用边界连接了受保护的内核机制和用户程序的运行现场。静态程序的入口直接来自 ELF 的e_entry动态程序还要经过PT_INTERP指定的动态链接器由解释器准备共享库和重定位后再进入应用入口。argv、环境变量和辅助向量则通过初始用户栈传给启动代码。由此我理解计算机“开始工作”依赖的不只是指令地址还包括地址空间、寄存器现场、栈中约定的数据以及内核与用户态之间清晰的交接。参考与版权说明Linux 3.18.6 内核源码fs/exec.c、fs/binfmt_elf.c、arch/x86/kernel/process_32.c。System V ABIELF 文件格式与进程初始化约定。叶丁再学号20262809。原创作品转载请注明出处《Linux 内核分析》MOOC 课程http://mooc.study.163.com/course/USTC-100002900。本文为博客草稿发布后请将实际博客 URL 提交到课程平台不要把本地文件路径当作发布链接。