Linux的执行elf可执行文件流程

execve族系统调用在Linux中用于运行新进程,替换当前进程的内存映像。execvp和execv是其变体,分别处理路径查找和执行。内核通过do_execveat_common函数调用elf加载器,解析ELF文件,设置参数并启动新进程,最终通过修改ARM的pc和sp指针执行程序。

execve族系统调用用于运行一个新的进程,并且替换当前进程的内存映像(代码,数据,栈)为新进程的内存映像。这个系统调用会在指定的进程空间内加载一个新的程序,同时将参数和环境变量传递给新程序。这个调用函数可以用于实现重载进程代码、切换用户权限、启动新的进程等功能。

execvp和execv是execve族的两个变体,它们允许你在新进程中运行可执行程序文件。 execvp使用PATH环境变量执行程序,而execv需要指定完整路径。

execve族的调用可以让用户免于开发自己的shell或者cli程序,用现有的程序交互式地对系统进行操作,甚至可以在shell中启动其他进程。

下面我们来跟踪一下Linux内核是如何实现该族函数的

内核中的定义

SYSCALL_DEFINE3(execve,
		const char __user *, filename,
		const char __user *const __user *, argv,
		const char __user *const __user *, envp)
{
	return do_execve(getname(filename), argv, envp);
}

SYSCALL_DEFINE5(execveat,
		int, fd, const char __user *, filename,
		const char __user *const __user *, argv,
		const char __user *const __user *, envp,
		int, flags)
{
	int lookup_flags = (flags & AT_E
Linux 系统中,**ELF 文件是由内核(Kernel)直接加载并执行的**,但整个过程涉及多个组件协作完成。我们可以从用户输入命令开始,逐步拆解“谁”最终执行ELF 文件。 --- ### ✅ 最终答案: > **Linux 内核中的 ELF 解析器负责加载和启动 ELF 可执行文件**,而 `execve()` 系统调用是触发这一过程的关键机制。 但为了完整理解,我们来看整个流程是如何工作的: --- ## 🔧 执行 ELF 文件的全过程 假设你在终端运行: ```bash ./myprogram ``` 其中 `myprogram` 是一个 ELF 格式的可执行文件。 ### 步骤 1:shell 调用系统调用 `execve()` Shell(如 bash)会调用系统调用: ```c execve("./myprogram", argv, envp); ``` 这个系统调用将控制权交给 **Linux 内核**。 ### 步骤 2:内核读取文件头部,识别格式 内核首先读取文件的前几个字节(魔数): - ELF 文件的魔数是 `\x7fELF`(即十六进制 `0x7F 45 4C 46`) - 内核通过这个判断这是一个 ELF 文件 然后内核调用 **`load_elf_binary()`** 函数(定义在 `fs/binfmt_elf.c` 中)来处理该文件。 📌 这个函数属于内核的一部分,专门用于解析和加载 ELF 文件。 ### 步骤 3:内核设置内存布局,映射段(Segments) 内核根据 ELF 文件中的 **Program Headers** 做以下事情: - 分配虚拟内存空间 - 将 `.text` 段(代码)映射为只读+可执行 - 将 `.data` 段(数据)映射为可读写 - 设置栈空间 - 处理动态链接需求(如果存在 `INTERP` 段) 例如,如果程序使用了共享库(如 `libc.so`),则还需要启动 `dynamic linker`(如 `/lib64/ld-linux-x86-64.so.2`)。 ### 步骤 4:跳转到入口点(Entry Point) 最后,内核将 CPU 的指令指针(RIP 寄存器)设置为 ELF 头中指定的 **入口地址(e_entry)**,开始执行程序的第一条指令。 --- ## 📌 关键角色总结 | 组件 | 角色 | |------|------| | **用户进程(如 shell)** | 发起 `execve()` 系统调用 | | **系统调用接口 `execve()`** | 用户态与内核态之间的桥梁 | | **Linux 内核(`fs/binfmt_elf.c`)** | ✅ 真正执行 ELF 加载的核心部分 | | **动态链接器 `/lib/ld-linux.so`** | 如果是动态可执行文件,则由它解析依赖库(但它本身也是 ELF) | | **CPU** | 最终执行机器指令 | > 💡 所以说:“**是内核执行ELF 文件**”,更准确地说,是 **内核内置的 ELF 二进制处理模块** 完成了加载和启动。 --- ## 🧩 特殊情况说明 ### 1. 静态链接 ELF ```bash readelf -l my_static_program | grep INTERP ``` 如果没有输出,说明是静态链接,**不需要动态链接器**,内核直接加载后就跳转到入口点。 ### 2. 动态链接 ELF 如果有 `INTERP` 段: ``` [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] ``` 那么内核会先加载这个解释器(它本身也是一个 ELF),然后把控制权交给它,由它加载主程序和共享库。 👉 类似于“接力”: ``` kernel → ld-linux.so → your_program(main) ``` 但这一切仍然是由内核启动的。 --- ## 🔍 查看一个 ELF 文件的信息示例 ```bash # 查看是否为 ELF file ./a.out # 输出:a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, ... # 查看入口点 readelf -h ./a.out | grep "Entry point" # 输出:Entry point address: 0x401000 # 查看程序头(segments) readelf -l ./a.out ``` --- ## 🛠 内核源码参考(可选了解) 在 Linux 内核源码中,ELF 加载逻辑位于: ``` fs/binfmt_elf.c ``` 核心函数是: ```c static int load_elf_binary(struct linux_binprm *bprm) ``` 这是真正解析 ELF 并创建用户进程映像的函数。 --- ## ✅ 总结一句话: > **Linux 内核通过 `execve()` 系统调用识别并加载 ELF 文件,利用其内置的 ELF 解析器(`load_elf_binary`)完成内存映射和执行环境设置,最终将控制权交给程序入口点——因此,是内核执行 ELF 文件。** --- ### ❓相关问题❓: 1. `execve()` 系统调用的具体作用是什么?它和 `fork()` 有什么关系? 2. 如何编写一个最简单的 ELF 文件(手工构造)并在 Linux运行? 3. 当内核发现一个文件ELF 时,它是如何决定使用哪个二进制处理模块的? 4. 为什么有些 ELF 文件有 `PT_INTERP` 段?它的作用是什么?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值