引言:Linux系统启动的"最后一公里"
当我们按下电源键,BIOS进行自检,引导加载程序(GRUB)将内核映像载入内存,然后内核开始执行。从start_kernel到rest_init,内核完成了大量的初始化工作:初始化中断、内存管理、调度器、文件系统......然而,直到用户空间的第一个进程 ------ init 进程被启动之前,系统仍然处于"内核态独占"的状态。用户空间的第一个进程的诞生,标志着操作系统真正走向可用。
那么,内核是如何从内核线程转变为执行用户空间/sbin/init的呢?execve系统调用在这个过程里扮演了什么角色?ELF二进制文件又是如何被识别、加载并最终执行的?本文将以Linux内核源码(以当前主流版本v6.x为例)中的关键函数为线索,逐层深入解析从kernel_init到load_elf_binary的完整流程。
我们将重点剖析以下几个核心环节:
-
kernel_init:作为init内核线程的主体函数,负责释放初始化内存、尝试启动用户态init。 -
run_init_process/try_to_run_init_process:封装内核态的execve调用。 -
kernel_execve:内核空间执行用户程序的总入口。 -
bprm_execve→exec_binprm→search_binary_handler:二进制格式处理器的查找与执行。 -
load_elf_binary:ELF加载器,承担将ELF可执行文件映射到进程地址空间的核心工作。
通过本文,你将不仅了解init进程的启动细节,还将掌握execve在内核中的实现脉络,以及ELF格式在Linux内核中是如何被解析和处理的。
一、kernel_init:init线程的诞生
Linux内核启动的最后阶段,rest_init()函数会创建两个内核线程:init(PID=1)和kthreadd(PID=2)。其中init线程的主体函数正是kernel_init。该函数定义在init/main.c中,我们看到的源码片段正是其核心逻辑。
c
static int __ref kernel_init(void *unused)
{
int ret;
/* Wait until kthreadd is all set-up. */
wait_for_completion(&kthreadd_done);
kernel_init_freeable();
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
system_state = SYSTEM_FREEING_INITMEM;
kprobe_free_init_mem();
ftrace_free_init_mem();
kgdb_free_init_mem();
exit_boot_config();
free_initmem();
mark_readonly();
/*
* Kernel mappings are now finalized - update the userspace page-table
* to finalize PTI.
*/
pti_finalize();
system_state = SYSTEM_RUNNING;
numa_default_policy();
rcu_end_inkernel_boot();
do_sysctl_args();
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d)\n",
ramdisk_execute_command, ret);
}
/* ... fallback to other init paths ... */
}
1.1 等待kthreadd准备就绪
wait_for_completion(&kthreadd_done); 确保内核的kthreadd守护线程(负责创建其他内核线程)已经完全初始化。这是一个典型的"完成量"(completion)同步机制,保证后续可能依赖kthreadd的功能不会在它未就绪时被调用。
1.2 kernel_init_freeable():可抢占的初始化
kernel_init_freeable() 是一个简化的初始化阶段,它释放了大部分"可抢占"的资源,例如:
-
启动
init进程前挂载根文件系统(prepare_namespace())。 -
初始化SMP、ACPI等。
-
最终会调用
do_basic_setup()来完成各种驱动和子系统的初始化。
该函数本身可能调用schedule(),因此它被标记为__init(代码段在初始化后会被释放),并且可以被抢占。这就与kernel_init的其他部分区分开来。
1.3 异步__init代码同步与内存释放
async_synchronize_full(); 会等待所有异步初始化的__init函数执行完毕。一些驱动或子系统使用async_schedule()进行并发初始化,这里必须等待它们完成后,才能安全地释放__init所占用的内存。
接着,系统状态被设为SYSTEM_FREEING_INITMEM,随后依次调用:
-
kprobe_free_init_mem()、ftrace_free_init_mem()、kgdb_free_init_mem():释放特定子系统(kprobe、ftrace、kgdb)的初始化代码和数据段。 -
exit_boot_config():处理引导配置(bootconfig)的清理。 -
free_initmem():释放所有__init、__initdata等标记的内存区域。这些内存页一旦释放,就可以被系统重新利用。 -
mark_readonly():将内核只读数据段标记为只读,保护内核关键数据不被修改。
1.4 PTI最终化与系统运行状态
pti_finalize() 完成**页表隔离(Page Table Isolation,PTI)**的最终设置。PTI是缓解Meltdown漏洞的一种机制,这里确保用户态页表不再包含内核敏感信息。
system_state = SYSTEM_RUNNING; 标志着内核已经完全过渡到正常运行状态。在这之后,内核大部分子系统都认为系统已经"running"。
numa_default_policy(); 设置NUMA内存访问默认策略为"本地分配优先"。
rcu_end_inkernel_boot(); 通知RCU子系统内核引导阶段结束,允许RCU进入更高效的运作模式。
do_sysctl_args(); 解析内核命令行中sysctl相关的参数(如sysctl.vm.swappiness=...),并应用到ProcFS的/proc/sys目录下。
1.5 尝试启动init进程:多个途径
ramdisk_execute_command 变量存储了通过命令行参数rdinit=指定的init程序。大多数发行版不显式设置,则默认为/init(位于initrd中)。如果指定且执行成功,则kernel_init返回0,init线程退出(实际上由于run_init_process内部调用了kernel_execve,当前内核线程会蜕变成用户态进程,不再返回)。如果失败,会输出错误日志。
接下来,如果execute_command存在(由init=命令行参数指定,如init=/sbin/myinit),则尝试执行它。如果也失败,则panic ------ 因为这是用户显式要求的init程序,失败意味着系统无法继续。
如果没指定init=,内核会尝试一系列默认路径:
-
CONFIG_DEFAULT_INIT(编译时配置,通常为空) -
/sbin/init -
/etc/init -
/bin/init -
/bin/sh
所有这些尝试都通过run_init_process或try_to_run_init_process完成。
最后如果全部失败,调用panic,系统挂起。
关键点 :run_init_process内部调用的是kernel_execve,这是一个内核态版本的execve。它不会返回(除非出错)。因此,一旦成功,PID=1的线程就会变成用户空间的init进程,内核的init线程就"消失"了,取而代之的是用户空间进程。
二、run_init_process与try_to_run_init_process:封装内核execve
这两个函数非常简单,但它们的作用是设置好argv和envp然后调用kernel_execve。
c
static int run_init_process(const char *init_filename)
{
const char *const *p;
argv_init[0] = init_filename;
pr_info("Run %s as init process\n", init_filename);
pr_debug(" with arguments:\n");
for (p = argv_init; *p; p++)
pr_debug(" %s\n", *p);
pr_debug(" with environment:\n");
for (p = envp_init; *p; p++)
pr_debug(" %s\n", *p);
return kernel_execve(init_filename, argv_init, envp_init);
}
argv_init是一个全局的char *数组,通常只包含第一个元素:init文件的路径,并以NULL结尾。envp_init是环境变量数组,包含了HOME=/、TERM=linux等基本环境。在kernel_init_freeable()阶段,这些环境变量被初始化。
注意到try_to_run_init_process仅仅是对run_init_process的一层封装,区别在于当返回-ENOENT(文件不存在)时不会打印错误信息,只有文件存在但无法执行时才报警。这是为了避免在尝试多个默认路径时输出大量误导性的错误。
三、kernel_execve:内核空间的execve
kernel_execve是用户态execve系统调用的内核态版本。它直接接收内核空间的文件名、argv、envp(均位于内核内存),构造struct linux_binprm,然后调用底层的加载逻辑。
代码片段如下:
c
int kernel_execve(const char *kernel_filename,
const char *const *argv, const char *const *envp)
{
struct filename *filename;
struct linux_binprm *bprm;
int fd = AT_FDCWD;
int retval;
/* It is non-sense for kernel threads to call execve */
if (WARN_ON_ONCE(current->flags & PF_KTHREAD))
return -EINVAL;
filename = getname_kernel(kernel_filename);
if (IS_ERR(filename))
return PTR_ERR(filename);
bprm = alloc_bprm(fd, filename, 0);
if (IS_ERR(bprm)) {
retval = PTR_ERR(bprm);
goto out_ret;
}
retval = count_strings_kernel(argv);
if (WARN_ON_ONCE(retval == 0))
retval = -EINVAL;
if (retval < 0)
goto out_free;
bprm->argc = retval;
retval = count_strings_kernel(envp);
if (retval < 0)
goto out_free;
bprm->envc = retval;
retval = bprm_stack_limits(bprm);
if (retval < 0)
goto out_free;
retval = copy_string_kernel(bprm->filename, bprm);
if (retval < 0)
goto out_free;
bprm->exec = bprm->p;
retval = copy_strings_kernel(bprm->envc, envp, bprm);
if (retval < 0)
goto out_free;
retval = copy_strings_kernel(bprm->argc, argv, bprm);
if (retval < 0)
goto out_free;
retval = bprm_execve(bprm);
out_free:
free_bprm(bprm);
out_ret:
putname(filename);
return retval;
}
3.1 禁止内核线程调用execve
WARN_ON_ONCE(current->flags & PF_KTHREAD)确保内核线程不会执行execve。因为内核线程没有用户态地址空间,执行用户程序没有意义。而我们的init线程虽然名义上是内核线程,但它被特殊处理------在kernel_init中,它还未成为真正的"内核线程"?实际上init线程是由kthread_create创建的,PF_KTHREAD标志是存在的。但仔细观察:kernel_init在调用run_init_process之前,已经执行了大量初始化,且current->flags包含PF_KTHREAD。内核为何允许此处执行execve?这是因为在execve过程中,如果发现当前是内核线程,会调用unshare_files等方式转换状态,并且最终会清除PF_KTHREAD标志。但代码片段中的WARN_ON_ONCE提示"non-sense",实际上这个检查是合理的,因为正常情况下内核线程不应当调用execve。不过对于init线程,它是一个特例,实际上是先调用kernel_execve,然后在bprm_execve的prepare_bprm_creds等函数中会处理这种情况。实际上,在较新的内核中,该检查确实存在 ,但init会被特殊处理吗?我们继续看bprm_execve内部的prepare_bprm_creds和begin_new_exec,它们会调用unshare_files、flush_old_exec,并最终清除PF_KTHREAD标志。但无论如何,kernel_execve一开始的检查会触发警告,实际上我看到的内核源码中该检查是存在的 ,但kernel_init确实是一个内核线程,却成功调用了kernel_execve。可能有如下原因:kernel_init函数指针被__ref修饰,表明它可能引用被释放的内存,但更重要的是,在调用kernel_execve时,当前进程已经被标记为"可执行的",或者在较早的版本中没有这个检查。查阅最新内核,kernel_execve已经改为直接调用do_execve,而没有这个警告。我们分析的代码片段可能是历史版本或特定架构的修改。但为了尊重提供的源码,我们保留该描述:这是一个防御性检查,对于合法的内核态execve(如init)可以忽略,但若真触发,也只是警告。
3.2 获取文件名与分配bprm
getname_kernel从内核字符串创建一个struct filename对象,用于后续的文件操作。alloc_bprm分配并初始化linux_binprm结构体。这个结构体是execve过程的核心数据容器,保存了参数、环境变量、文件描述符、内存布局信息等。
3.3 计数参数和环境变量
count_strings_kernel遍历argv/envp数组,计算字符串个数。注意:argv/envp数组必须以NULL指针结尾。计数结果存入bprm->argc和bprm->envc。
3.4 检查栈空间限制
bprm_stack_limits检查参数和环境变量是否会超出当前进程的栈大小限制(rlim[RLIMIT_STACK])。防止恶意构造的巨大参数导致栈溢出。
3.5 复制参数和环境变量到bprm
copy_string_kernel将文件名(bprm->filename)复制到bprm内部的页中(通常是栈顶附近)。copy_strings_kernel分别复制环境变量和命令行参数。这些字符串最终会被放置在用户态栈的顶端。bprm->exec记录了参数/envp的起始位置。
3.6 调用核心加载器
bprm_execve(bprm) 是所有二进制加载的核心函数,它负责安全检查和调用真正的二进制格式处理器。
四、bprm_execve:执行前的准备与转向
c
static int bprm_execve(struct linux_binprm *bprm)
{
int retval;
retval = prepare_bprm_creds(bprm);
if (retval)
return retval;
check_unsafe_exec(bprm);
current->in_execve = 1;
sched_mm_cid_before_execve(current);
sched_exec();
retval = security_bprm_creds_for_exec(bprm);
if (retval)
goto out;
retval = exec_binprm(bprm);
if (retval < 0)
goto out;
sched_mm_cid_after_execve(current);
current->fs->in_exec = 0;
current->in_execve = 0;
rseq_execve(current);
user_events_execve(current);
acct_update_integrals(current);
task_numa_free(current, false);
return retval;
out:
if (bprm->point_of_no_return && !fatal_signal_pending(current))
force_fatal_sig(SIGSEGV);
sched_mm_cid_after_execve(current);
current->fs->in_exec = 0;
current->in_execve = 0;
return retval;
}
4.1 准备凭证
prepare_bprm_creds 为新的程序准备凭据(uid、gid、能力集等)。它复制当前进程的凭据到bprm->cred,并做一些预设置。
4.2 检查不安全执行状态
check_unsafe_exec 检查当前进程是否处于不安全的状态(例如线程共享某些资源、文件描述符未关闭等)。这涉及到CLONE_FILES、CLONE_FS等标志。如果存在不安全因素,可能需要重新设置相应资源。
4.3 标志位与调度
设置current->in_execve = 1,表示进程正在执行execve,一些内核路径可以据此特殊处理。sched_mm_cid_before_execve和sched_mm_cid_after_execve是调度器相关的上下文ID处理。sched_exec通知调度器即将执行新的程序,调度器可能会调整优先级或负载均衡。
4.4 安全性钩子
security_bprm_creds_for_exec 调用LSM(Linux Security Module)钩子,允许SELinux、AppArmor等安全模块对新程序的凭证进行最终调整。
4.5 执行二进制文件
exec_binprm(bprm) 是真正寻找二进制格式处理器并执行它的函数。
4.6 清理与point_of_no_return
如果执行成功,后续进行清理,包括重置in_execve标志、更新rseq、用户事件、NUMA内存等。如果失败,且bprm->point_of_no_return已经设置,意味着已经修改了进程状态无法回滚,那么需要强制发送SIGSEGV终止进程。point_of_no_return标志在加载二进制文件的某个阶段(如开始替换内存映射)后被置位。
五、exec_binprm与search_binary_handler:二进制格式的匹配
exec_binprm处理二进制格式的循环解析,支持解释器(如脚本的#!)的多级嵌套。
c
static int exec_binprm(struct linux_binprm *bprm)
{
pid_t old_pid, old_vpid;
int ret, depth;
old_pid = current->pid;
rcu_read_lock();
old_vpid = task_pid_nr_ns(current, task_active_pid_ns(current->parent));
rcu_read_unlock();
for (depth = 0;; depth++) {
struct file *exec;
if (depth > 5)
return -ELOOP;
ret = search_binary_handler(bprm);
if (ret < 0)
return ret;
if (!bprm->interpreter)
break;
exec = bprm->file;
bprm->file = bprm->interpreter;
bprm->interpreter = NULL;
allow_write_access(exec);
if (unlikely(bprm->have_execfd)) {
if (bprm->executable) {
fput(exec);
return -ENOEXEC;
}
bprm->executable = exec;
} else
fput(exec);
}
audit_bprm(bprm);
trace_sched_process_exec(current, old_pid, bprm);
ptrace_event(PTRACE_EVENT_EXEC, old_vpid);
proc_exec_connector(current);
return 0;
}
5.1 记录旧PID
读取当前进程的PID和父进程命名空间内的vpid,用于审计和ptrace事件。由于execve会保留PID,但会清空很多状态,记录下来是为了事件通知。
5.2 循环处理解释器
Linux支持多层二进制格式,例如一个ELF可执行文件可以通过#!指定解释器,而解释器本身又是一个ELF文件,甚至解释器的解释器。为了防止无限递归,限制最大嵌套深度为5层。
search_binary_handler 尝试找到能处理当前bprm->file的二进制格式处理器。如果成功且bprm->interpreter非空,说明当前二进制需要一个解释器(比如脚本的第一行#!/bin/bash)。于是用interpreter替换原来的file,继续循环。
注意:bprm->interpreter是在load_elf_binary中针对动态链接器(ELF INTERP段)设置的,或在binfmt_script中针对#!设置的。
5.3 文件引用计数管理
allow_write_access、fput等负责正确释放原文件的引用,并将解释器文件设为新的执行文件。
5.4 成功后的事件
audit_bprm记录审计日志,trace_sched_process_exec触发调度器追踪点,ptrace_event让调试器捕获PTRACE_EVENT_EXEC,proc_exec_connector通知连接器(connector)进程执行事件。
六、search_binary_handler:二进制格式处理器查找
c
static int search_binary_handler(struct linux_binprm *bprm)
{
bool need_retry = IS_ENABLED(CONFIG_MODULES);
struct linux_binfmt *fmt;
int retval;
retval = prepare_binprm(bprm);
if (retval < 0)
return retval;
retval = security_bprm_check(bprm);
if (retval)
return retval;
retval = -ENOENT;
retry:
read_lock(&binfmt_lock);
list_for_each_entry(fmt, &formats, lh) {
if (!try_module_get(fmt->module))
continue;
read_unlock(&binfmt_lock);
retval = fmt->load_binary(bprm);
read_lock(&binfmt_lock);
put_binfmt(fmt);
if (bprm->point_of_no_return || (retval != -ENOEXEC)) {
read_unlock(&binfmt_lock);
return retval;
}
}
read_unlock(&binfmt_lock);
if (need_retry) {
if (printable(bprm->buf[0]) && printable(bprm->buf[1]) &&
printable(bprm->buf[2]) && printable(bprm->buf[3]))
return retval;
if (request_module("binfmt-%04x", *(ushort *)(bprm->buf + 2)) < 0)
return retval;
need_retry = false;
goto retry;
}
return retval;
}
6.1 prepare_binprm
在调用处理器之前,需要做一些准备工作:读取文件的前128(或BINPRM_BUF_SIZE)字节到bprm->buf中,这通常用于识别文件类型(如ELF头部、脚本的#!)。同时会检查文件的执行权限,并设置bprm->cred中的euid/egid(如果文件设置了setuid/setgid位)。
6.2 安全钩子
security_bprm_check调用LSM钩子,允许安全模块基于bprm内容决定是否允许执行。
6.3 遍历格式链表
formats是一个全局链表,通过register_binfmt注册的二进制格式处理器(如elf_format、binfmt_script、binfmt_misc等)。每个处理器有一个load_binary函数指针。
内核持有binfmt_lock读锁遍历链表。对每个格式,尝试增加模块引用计数(防止模块在加载过程中被卸载),然后释放锁(避免长时间持锁),调用fmt->load_binary(bprm)。
-
如果
load_binary返回-ENOEXEC(格式不识别),则继续尝试下一个处理器。 -
如果返回其他错误,或已经设置了
bprm->point_of_no_return(无法回滚),则直接返回该结果。 -
如果返回0(成功),同样返回。
6.4 模块自动加载
如果遍历完所有已注册的处理器仍然没有找到合适的(所有都是-ENOEXEC),并且need_retry为真(即支持模块且尚未尝试过模块加载),且文件的头4个字节不全是可打印字符(意味着可能是一种未知的二进制格式,而不是文本脚本),则调用request_module请求内核模块加载程序尝试加载名为binfmt-xxxx的模块(xxxx是文件头第2-3字节的值,比如binfmt-004c对应ELF)。如果加载成功,则goto retry再次遍历。这样可以动态加载如binfmt_aout等模块。
七、load_elf_binary:ELF加载器的全解析
ELF(Executable and Linkable Format)是Linux上最常用的二进制格式。load_elf_binary函数是ELF格式处理器的核心,长度超过500行,我们将其分解为若干阶段。
7.1 初步检查与ELF头解析
c
static int load_elf_binary(struct linux_binprm *bprm)
{
struct file *interpreter = NULL;
unsigned long load_bias = 0, phdr_addr = 0;
int first_pt_load = 1;
unsigned long error;
struct elf_phdr *elf_ppnt, *elf_phdata, *interp_elf_phdata = NULL;
struct elf_phdr *elf_property_phdata = NULL;
unsigned long elf_brk;
int retval, i;
unsigned long elf_entry;
unsigned long e_entry;
unsigned long interp_load_addr = 0;
unsigned long start_code, end_code, start_data, end_data;
unsigned long reloc_func_desc __maybe_unused = 0;
int executable_stack = EXSTACK_DEFAULT;
struct elfhdr *elf_ex = (struct elfhdr *)bprm->buf;
struct elfhdr *interp_elf_ex = NULL;
struct arch_elf_state arch_state = INIT_ARCH_ELF_STATE;
struct mm_struct *mm;
struct pt_regs *regs;
retval = -ENOEXEC;
if (memcmp(elf_ex->e_ident, ELFMAG, SELFMAG) != 0)
goto out;
if (elf_ex->e_type != ET_EXEC && elf_ex->e_type != ET_DYN)
goto out;
if (!elf_check_arch(elf_ex))
goto out;
if (elf_check_fdpic(elf_ex))
goto out;
if (!bprm->file->f_op->mmap)
goto out;
-
首先验证ELF魔数(
\177ELF)、文件类型(必须是可执行ET_EXEC或动态库ET_DYN,即PIE),以及架构支持和是否支持mmap。 -
elf_check_arch是一个架构相关的宏,检查ELF头中的e_machine字段是否与当前CPU匹配。 -
elf_check_fdpic用于禁用某些嵌入式系统使用的FDPIC ABI。 -
文件必须支持
mmap操作,因为ELF加载依赖于内存映射。
7.2 加载程序头表(Program Header Table)
c
elf_phdata = load_elf_phdrs(elf_ex, bprm->file);
if (!elf_phdata)
goto out;
load_elf_phdrs 从文件中读取所有的程序头(Elf_Phdr)到内核内存中,返回指针。每个程序头描述一个段(segment),如PT_LOAD(可加载段)、PT_INTERP(解释器路径)、PT_GNU_STACK(栈权限)等。
7.3 遍历程序头,处理解释器与属性
后续的代码遍历elf_phdata,查找PT_INTERP段。如果存在,表示该ELF文件是一个动态链接的可执行文件,需要指定动态链接器(如/lib64/ld-linux-x86-64.so.2)。
-
检查解释器路径长度,分配内存读取路径字符串。
-
调用
open_exec打开解释器文件,获取struct file *interpreter。 -
读取解释器的ELF头到
interp_elf_ex。 -
加载解释器的程序头表到
interp_elf_phdata。 -
同时记录
PT_GNU_PROPERTY段(用于属性如x86的GNU_PROPERTY_X86_FEATURE_1_IBT等)。
同时,还会处理PT_GNU_STACK段决定栈的可执行性(executable_stack),以及架构相关的PT_LOPROC到PT_HIPROC段。
7.4 解析属性与架构检查
parse_elf_properties解析PT_GNU_PROPERTY段中的具体属性,例如是否支持Intel CET(控制流强制技术)。arch_check_elf允许架构特定的额外检查(如ARM64的PAC、BTI等)。
7.5 begin_new_exec:替换当前进程映像
c
retval = begin_new_exec(bprm);
if (retval)
goto out_free_dentry;
这是最关键的一步:它开始废弃当前进程的地址空间(释放旧的内存映射、文件描述符、信号处理等),并准备新的执行上下文。如果成功,bprm->point_of_no_return将被置位(表示无法回滚)。begin_new_exec内部调用flush_old_exec,其中会执行de_thread清除线程组、调用exec_mmap释放旧内存描述符并分配新的mm_struct。
7.6 设置进程Personality和随机化标志
c
SET_PERSONALITY2(*elf_ex, &arch_state);
if (elf_read_implies_exec(*elf_ex, executable_stack))
current->personality |= READ_IMPLIES_EXEC;
if (!(current->personality & ADDR_NO_RANDOMIZE) && randomize_va_space)
current->flags |= PF_RANDOMIZE;
-
SET_PERSONALITY2设置进程的执行域(personality),影响内存布局和系统调用行为。例如,对于32位ELF,可能会设置PER_LINUX32。 -
如果ELF头指示该程序期望可执行栈(或
PT_GNU_STACK要求),则设置READ_IMPLIES_EXEC位,使得mmap分配的内存默认可执行(一般为了兼容老程序)。 -
如果允许地址空间随机化(
randomize_va_space),则设置PF_RANDOMIZE标志,后续加载时会对基地址进行随机偏移。
7.7 设置新执行环境:参数栈
c
retval = setup_arg_pages(bprm, randomize_stack_top(STACK_TOP),
executable_stack);
if (retval < 0)
goto out_free_dentry;
setup_arg_pages在用户地址空间的顶端(STACK_TOP,通常是TASK_SIZE - 页大小)建立栈映射,并将bprm中已经复制好的参数和环境变量字符串复制到栈上。同时设置栈的权限(如果executable_stack为EXSTACK_ENABLE_X,则栈页允许执行;否则为PROT_READ|PROT_WRITE,不允许执行)。randomize_stack_top对栈顶进行随机偏移,增强ASLR。
7.8 加载所有PT_LOAD段
这是最复杂的部分:遍历程序头,对每个PT_LOAD类型段,计算虚拟地址、映射到内存。
c
for(i = 0, elf_ppnt = elf_phdata;
i < elf_ex->e_phnum; i++, elf_ppnt++) {
if (elf_ppnt->p_type != PT_LOAD)
continue;
elf_prot = make_prot(elf_ppnt->p_flags, &arch_state,
!!interpreter, false);
elf_flags = MAP_PRIVATE;
vaddr = elf_ppnt->p_vaddr;
if (!first_pt_load) {
elf_flags |= MAP_FIXED;
} else if (elf_ex->e_type == ET_EXEC) {
elf_flags |= MAP_FIXED_NOREPLACE;
} else if (elf_ex->e_type == ET_DYN) {
/* 计算 load_bias 用于ET_DYN */
if (interpreter) {
load_bias = ELF_ET_DYN_BASE;
if (current->flags & PF_RANDOMIZE)
load_bias += arch_mmap_rnd();
alignment = maximum_alignment(elf_phdata, elf_ex->e_phnum);
if (alignment)
load_bias &= ~(alignment - 1);
elf_flags |= MAP_FIXED_NOREPLACE;
} else
load_bias = 0;
load_bias = ELF_PAGESTART(load_bias - vaddr);
total_size = total_mapping_size(...);
}
error = elf_load(bprm->file, load_bias + vaddr, elf_ppnt,
elf_prot, elf_flags, total_size);
if (BAD_ADDR(error)) {
retval = IS_ERR_VALUE(error) ? PTR_ERR((void*)error) : -EINVAL;
goto out_free_dentry;
}
if (first_pt_load) {
first_pt_load = 0;
if (elf_ex->e_type == ET_DYN) {
load_bias += error - ELF_PAGESTART(load_bias + vaddr);
reloc_func_desc = load_bias;
}
}
// 记录代码段、数据段的边界...
}
7.8.1 内存保护标志
make_prot将ELF段标志(PF_R、PF_W、PF_X)转换为mmap的prot标志(PROT_READ、PROT_WRITE、PROT_EXEC),同时考虑架构状态(如是否需要设置PROT_BTI等)。
7.8.2 映射标志
MAP_PRIVATE表示写时复制(COW),因为可执行文件通常不希望修改被其他进程共享。对于第一个PT_LOAD段,处理方式不同:
-
ET_EXEC :传统可执行文件,有固定加载地址(如
0x400000)。使用MAP_FIXED_NOREPLACE,要求内核精确映射到指定地址,但如果该地址已被占用则失败(避免覆盖)。 -
ET_DYN :即位置无关可执行文件(PIE)。如果存在解释器(即该文件是需要动态链接的程序),则计算加载偏移
load_bias:基址为ELF_ET_DYN_BASE(通常为/proc/sys/vm/mmap_rnd_bits的随机值),并确保对齐。如果没有解释器(即该文件本身就是解释器,如ld.so),则load_bias为0,允许内核自由选择映射地址(通过后续elf_load不使用MAP_FIXED)。 -
第一个段映射成功后,如果之前计算了
load_bias(针对ET_DYN),还需要根据实际映射地址(error)调整load_bias以便后续段使用相同偏移。
total_size仅用于ET_DYN的第一次映射,目的是预先映射所有连续可加载段的总范围,减少后续mmap调用的次数,提高效率并保证连续性。
7.8.3 段映射的实际执行
elf_load内部调用vm_mmap或elf_map。它根据传入的地址、大小、偏移量和标志执行内存映射。对于PT_LOAD段,文件内容被映射到内存。要注意,p_memsz可能大于p_filesz(例如.bss段),多余的部分会被初始化为0。这通过mmap后对超出文件大小的部分调用padzero实现。
7.8.4 记录地址范围
在循环中,还更新了程序入口点e_entry、phdr_addr(程序头在内存中的地址,供辅助向量使用)、代码段起始结束(start_code、end_code)、数据段起始结束(start_data、end_data)、以及elf_brk(程序break的初始位置)。
7.9 加载解释器(如果有)
如果interpreter非空,则调用load_elf_interp来加载解释器自身的PT_LOAD段。这个过程与加载主体ELF类似,但是解释器必须以MAP_FIXED的方式加载到已经计算好的load_bias位置上(或者根据主体程序的内核随机偏移)。load_elf_interp返回解释器的加载偏移,用于计算其入口点。
c
if (interpreter) {
elf_entry = load_elf_interp(interp_elf_ex, interpreter,
load_bias, interp_elf_phdata,
&arch_state);
if (!IS_ERR_VALUE(elf_entry)) {
interp_load_addr = elf_entry;
elf_entry += interp_elf_ex->e_entry;
}
// ...
reloc_func_desc = interp_load_addr;
allow_write_access(interpreter);
fput(interpreter);
} else {
elf_entry = e_entry;
}
注意:动态链接模式下,最终的用户态入口点不是主体ELF的e_entry,而是动态链接器的e_entry(即ld.so的入口)。主体程序的入口地址会由动态链接器在初始化完成后跳转过去。
7.10 创建辅助向量(ELF Auxiliary Table)
create_elf_tables将各种信息(程序头地址、入口点、随机化种子、平台字符串等)以辅助向量的形式放在用户栈上。这些信息会被C运行时库(如glibc)的启动代码使用,例如AT_PHDR、AT_PHENT、AT_ENTRY等。
7.11 设置内存描述符的字段
c
mm = current->mm;
mm->end_code = end_code;
mm->start_code = start_code;
mm->start_data = start_data;
mm->end_data = end_data;
mm->start_stack = bprm->p;
这些字段用于/proc/pid/maps以及内核某些模块的地址范围判断。
7.12 随机化堆和brk
如果启用了ASLR且PF_RANDOMIZE被设置,对于直接加载的解释器(ET_DYN且无解释器),mm->brk将被重置为ELF_ET_DYN_BASE,然后调用arch_randomize_brk进一步随机。
c
if ((current->flags & PF_RANDOMIZE) && (randomize_va_space > 1)) {
if (IS_ENABLED(CONFIG_ARCH_HAS_ELF_RANDOMIZE) &&
elf_ex->e_type == ET_DYN && !interpreter) {
mm->brk = mm->start_brk = ELF_ET_DYN_BASE;
}
mm->brk = mm->start_brk = arch_randomize_brk(mm);
}
7.13 处理MAP_PAGE_ZERO
对于某些古老的personality(如PER_SVR4),需要映射第0页为只读可执行(为了兼容一些依赖空指针解引用实际访问0页的程序)。这通常用于Wine或某些模拟环境。
7.14 设置寄存器并启动用户进程
c
regs = current_pt_regs();
#ifdef ELF_PLAT_INIT
ELF_PLAT_INIT(regs, reloc_func_desc);
#endif
finalize_exec(bprm);
START_THREAD(elf_ex, regs, elf_entry, bprm->p);
-
ELF_PLAT_INIT是一个架构相关的宏,用于设置特定寄存器(例如x86的%rdx指向DT_FINI)。 -
finalize_exec完成最后的清理(如将文件引用释放,将bprm中的凭证提交给current->cred)。 -
START_THREAD设置regs->ip(指令指针)为elf_entry,regs->sp为用户栈指针bprm->p,然后调用start_thread切换到用户态。该函数不会返回,最终CPU会从用户态入口点开始执行指令。
至此,新的用户程序开始运行。
八、总结与思考
8.1 内核启动到用户态init的完整路径
本文从kernel_init线程出发,逐步深入:
-
kernel_init完成最后的内核初始化,释放__init内存,然后尝试run_init_process。 -
run_init_process调用kernel_execve,后者在内核内部构造执行上下文。 -
kernel_execve调用bprm_execve,进行凭证准备、安全检查,然后进入exec_binprm。 -
exec_binprm循环调用search_binary_handler寻找合适的二进制格式处理器。 -
ELF格式处理器
load_elf_binary被选中,它读取ELF程序头,映射段,加载解释器,设置辅助向量,最终切换到用户态执行。 -
如果成功,PID=1的进程变成了
/sbin/init(或其他指定的init),内核的init线程彻底转换成用户进程。
8.2 设计与技巧的赏析
-
模块化的二进制格式处理 :通过
formats链表和register_binfmt,内核可以轻松支持ELF、a.out、脚本、甚至自定义格式(如binfmt_misc)。这种设计体现了Linux内核的可扩展性。 -
谨慎的错误回滚 :
bprm->point_of_no_return标志用于区分哪些阶段已经不可撤销。在begin_new_exec之后,任何失败都必须强制终止进程,因为旧地址空间已经被释放。 -
ASLR的精细控制 :内核对ET_EXEC、ET_DYN、解释器、stack、brk都分别进行了随机化支持,并通过
PF_RANDOMIZE和randomize_va_space控制粒度。 -
对动态链接的支持 :通过
PT_INTERP和解释器的递归加载,完美支持了动态链接程序,使得共享库成为可能。 -
安全性考虑 :包括
MAP_FIXED_NOREPLACE防止覆盖已有映射,check_unsafe_exec避免特权提升,LSM钩子无处不在等。
8.3 进一步探索
本文仅涉及了execve路径的路标。感兴趣的读者还可以深入:
-
脚本的加载 :
binfmt_script如何处理#!。 -
解释器的内部 :
ld.so如何加载共享库、重定位符号。 -
ELF装载的架构相关部分 :如
arch_elf_pt_proc、ELF_PLAT_INIT在不同CPU上的实现。 -
coredump:当程序崩溃时,内核如何转储ELF core文件。
Linux内核的这些代码经过几十年的演进,既稳定又高效,是操作系统设计的典范。理解init和execve的流程,能够帮助我们更好地调试系统启动问题、分析安全漏洞以及开发底层系统软件。
##源码
cpp
static int __ref kernel_init(void *unused)
{
int ret;
/*
* Wait until kthreadd is all set-up.
*/
wait_for_completion(&kthreadd_done);
kernel_init_freeable();
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
system_state = SYSTEM_FREEING_INITMEM;
kprobe_free_init_mem();
ftrace_free_init_mem();
kgdb_free_init_mem();
exit_boot_config();
free_initmem();
mark_readonly();
/*
* Kernel mappings are now finalized - update the userspace page-table
* to finalize PTI.
*/
pti_finalize();
system_state = SYSTEM_RUNNING;
numa_default_policy();
rcu_end_inkernel_boot();
do_sysctl_args();
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d)\n",
ramdisk_execute_command, ret);
}
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) {
ret = run_init_process(execute_command);
if (!ret)
return 0;
panic("Requested init %s failed (error %d).",
execute_command, ret);
}
if (CONFIG_DEFAULT_INIT[0] != '\0') {
ret = run_init_process(CONFIG_DEFAULT_INIT);
if (ret)
pr_err("Default init %s failed (error %d)\n",
CONFIG_DEFAULT_INIT, ret);
else
return 0;
}
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
return 0;
panic("No working init found. Try passing init= option to kernel. "
"See Linux Documentation/admin-guide/init.rst for guidance.");
}
static int run_init_process(const char *init_filename)
{
const char *const *p;
argv_init[0] = init_filename;
pr_info("Run %s as init process\n", init_filename);
pr_debug(" with arguments:\n");
for (p = argv_init; *p; p++)
pr_debug(" %s\n", *p);
pr_debug(" with environment:\n");
for (p = envp_init; *p; p++)
pr_debug(" %s\n", *p);
return kernel_execve(init_filename, argv_init, envp_init);
}
static int try_to_run_init_process(const char *init_filename)
{
int ret;
ret = run_init_process(init_filename);
if (ret && ret != -ENOENT) {
pr_err("Starting init: %s exists but couldn't execute it (error %d)\n",
init_filename, ret);
}
return ret;
}
int kernel_execve(const char *kernel_filename,
const char *const *argv, const char *const *envp)
{
struct filename *filename;
struct linux_binprm *bprm;
int fd = AT_FDCWD;
int retval;
/* It is non-sense for kernel threads to call execve */
if (WARN_ON_ONCE(current->flags & PF_KTHREAD))
return -EINVAL;
filename = getname_kernel(kernel_filename);
if (IS_ERR(filename))
return PTR_ERR(filename);
bprm = alloc_bprm(fd, filename, 0);
if (IS_ERR(bprm)) {
retval = PTR_ERR(bprm);
goto out_ret;
}
retval = count_strings_kernel(argv);
if (WARN_ON_ONCE(retval == 0))
retval = -EINVAL;
if (retval < 0)
goto out_free;
bprm->argc = retval;
retval = count_strings_kernel(envp);
if (retval < 0)
goto out_free;
bprm->envc = retval;
retval = bprm_stack_limits(bprm);
if (retval < 0)
goto out_free;
retval = copy_string_kernel(bprm->filename, bprm);
if (retval < 0)
goto out_free;
bprm->exec = bprm->p;
retval = copy_strings_kernel(bprm->envc, envp, bprm);
if (retval < 0)
goto out_free;
retval = copy_strings_kernel(bprm->argc, argv, bprm);
if (retval < 0)
goto out_free;
retval = bprm_execve(bprm);
out_free:
free_bprm(bprm);
out_ret:
putname(filename);
return retval;
}
static int bprm_execve(struct linux_binprm *bprm)
{
int retval;
retval = prepare_bprm_creds(bprm);
if (retval)
return retval;
/*
* Check for unsafe execution states before exec_binprm(), which
* will call back into begin_new_exec(), into bprm_creds_from_file(),
* where setuid-ness is evaluated.
*/
check_unsafe_exec(bprm);
current->in_execve = 1;
sched_mm_cid_before_execve(current);
sched_exec();
/* Set the unchanging part of bprm->cred */
retval = security_bprm_creds_for_exec(bprm);
if (retval)
goto out;
retval = exec_binprm(bprm);
if (retval < 0)
goto out;
sched_mm_cid_after_execve(current);
/* execve succeeded */
current->fs->in_exec = 0;
current->in_execve = 0;
rseq_execve(current);
user_events_execve(current);
acct_update_integrals(current);
task_numa_free(current, false);
return retval;
out:
/*
* If past the point of no return ensure the code never
* returns to the userspace process. Use an existing fatal
* signal if present otherwise terminate the process with
* SIGSEGV.
*/
if (bprm->point_of_no_return && !fatal_signal_pending(current))
force_fatal_sig(SIGSEGV);
sched_mm_cid_after_execve(current);
current->fs->in_exec = 0;
current->in_execve = 0;
return retval;
}
/* binfmt handlers will call back into begin_new_exec() on success. */
static int exec_binprm(struct linux_binprm *bprm)
{
pid_t old_pid, old_vpid;
int ret, depth;
/* Need to fetch pid before load_binary changes it */
old_pid = current->pid;
rcu_read_lock();
old_vpid = task_pid_nr_ns(current, task_active_pid_ns(current->parent));
rcu_read_unlock();
/* This allows 4 levels of binfmt rewrites before failing hard. */
for (depth = 0;; depth++) {
struct file *exec;
if (depth > 5)
return -ELOOP;
ret = search_binary_handler(bprm);
if (ret < 0)
return ret;
if (!bprm->interpreter)
break;
exec = bprm->file;
bprm->file = bprm->interpreter;
bprm->interpreter = NULL;
allow_write_access(exec);
if (unlikely(bprm->have_execfd)) {
if (bprm->executable) {
fput(exec);
return -ENOEXEC;
}
bprm->executable = exec;
} else
fput(exec);
}
audit_bprm(bprm);
trace_sched_process_exec(current, old_pid, bprm);
ptrace_event(PTRACE_EVENT_EXEC, old_vpid);
proc_exec_connector(current);
return 0;
}
/*
* cycle the list of binary formats handler, until one recognizes the image
*/
static int search_binary_handler(struct linux_binprm *bprm)
{
bool need_retry = IS_ENABLED(CONFIG_MODULES);
struct linux_binfmt *fmt;
int retval;
retval = prepare_binprm(bprm);
if (retval < 0)
return retval;
retval = security_bprm_check(bprm);
if (retval)
return retval;
retval = -ENOENT;
retry:
read_lock(&binfmt_lock);
list_for_each_entry(fmt, &formats, lh) {
if (!try_module_get(fmt->module))
continue;
read_unlock(&binfmt_lock);
retval = fmt->load_binary(bprm);
read_lock(&binfmt_lock);
put_binfmt(fmt);
if (bprm->point_of_no_return || (retval != -ENOEXEC)) {
read_unlock(&binfmt_lock);
return retval;
}
}
read_unlock(&binfmt_lock);
if (need_retry) {
if (printable(bprm->buf[0]) && printable(bprm->buf[1]) &&
printable(bprm->buf[2]) && printable(bprm->buf[3]))
return retval;
if (request_module("binfmt-%04x", *(ushort *)(bprm->buf + 2)) < 0)
return retval;
need_retry = false;
goto retry;
}
return retval;
}
static struct linux_binfmt elf_format = {
.module = THIS_MODULE,
.load_binary = load_elf_binary,
.load_shlib = load_elf_library,
#ifdef CONFIG_COREDUMP
.core_dump = elf_core_dump,
.min_coredump = ELF_EXEC_PAGESIZE,
#endif
};
static int load_elf_binary(struct linux_binprm *bprm)
{
struct file *interpreter = NULL; /* to shut gcc up */
unsigned long load_bias = 0, phdr_addr = 0;
int first_pt_load = 1;
unsigned long error;
struct elf_phdr *elf_ppnt, *elf_phdata, *interp_elf_phdata = NULL;
struct elf_phdr *elf_property_phdata = NULL;
unsigned long elf_brk;
int retval, i;
unsigned long elf_entry;
unsigned long e_entry;
unsigned long interp_load_addr = 0;
unsigned long start_code, end_code, start_data, end_data;
unsigned long reloc_func_desc __maybe_unused = 0;
int executable_stack = EXSTACK_DEFAULT;
struct elfhdr *elf_ex = (struct elfhdr *)bprm->buf;
struct elfhdr *interp_elf_ex = NULL;
struct arch_elf_state arch_state = INIT_ARCH_ELF_STATE;
struct mm_struct *mm;
struct pt_regs *regs;
retval = -ENOEXEC;
/* First of all, some simple consistency checks */
if (memcmp(elf_ex->e_ident, ELFMAG, SELFMAG) != 0)
goto out;
if (elf_ex->e_type != ET_EXEC && elf_ex->e_type != ET_DYN)
goto out;
if (!elf_check_arch(elf_ex))
goto out;
if (elf_check_fdpic(elf_ex))
goto out;
if (!bprm->file->f_op->mmap)
goto out;
elf_phdata = load_elf_phdrs(elf_ex, bprm->file);
if (!elf_phdata)
goto out;
elf_ppnt = elf_phdata;
for (i = 0; i < elf_ex->e_phnum; i++, elf_ppnt++) {
char *elf_interpreter;
if (elf_ppnt->p_type == PT_GNU_PROPERTY) {
elf_property_phdata = elf_ppnt;
continue;
}
if (elf_ppnt->p_type != PT_INTERP)
continue;
/*
* This is the program interpreter used for shared libraries -
* for now assume that this is an a.out format binary.
*/
retval = -ENOEXEC;
if (elf_ppnt->p_filesz > PATH_MAX || elf_ppnt->p_filesz < 2)
goto out_free_ph;
retval = -ENOMEM;
elf_interpreter = kmalloc(elf_ppnt->p_filesz, GFP_KERNEL);
if (!elf_interpreter)
goto out_free_ph;
retval = elf_read(bprm->file, elf_interpreter, elf_ppnt->p_filesz,
elf_ppnt->p_offset);
if (retval < 0)
goto out_free_interp;
/* make sure path is NULL terminated */
retval = -ENOEXEC;
if (elf_interpreter[elf_ppnt->p_filesz - 1] != '\0')
goto out_free_interp;
interpreter = open_exec(elf_interpreter);
kfree(elf_interpreter);
retval = PTR_ERR(interpreter);
if (IS_ERR(interpreter))
goto out_free_ph;
/*
* If the binary is not readable then enforce mm->dumpable = 0
* regardless of the interpreter's permissions.
*/
would_dump(bprm, interpreter);
interp_elf_ex = kmalloc(sizeof(*interp_elf_ex), GFP_KERNEL);
if (!interp_elf_ex) {
retval = -ENOMEM;
goto out_free_file;
}
/* Get the exec headers */
retval = elf_read(interpreter, interp_elf_ex,
sizeof(*interp_elf_ex), 0);
if (retval < 0)
goto out_free_dentry;
break;
out_free_interp:
kfree(elf_interpreter);
goto out_free_ph;
}
elf_ppnt = elf_phdata;
for (i = 0; i < elf_ex->e_phnum; i++, elf_ppnt++)
switch (elf_ppnt->p_type) {
case PT_GNU_STACK:
if (elf_ppnt->p_flags & PF_X)
executable_stack = EXSTACK_ENABLE_X;
else
executable_stack = EXSTACK_DISABLE_X;
break;
case PT_LOPROC ... PT_HIPROC:
retval = arch_elf_pt_proc(elf_ex, elf_ppnt,
bprm->file, false,
&arch_state);
if (retval)
goto out_free_dentry;
break;
}
/* Some simple consistency checks for the interpreter */
if (interpreter) {
retval = -ELIBBAD;
/* Not an ELF interpreter */
if (memcmp(interp_elf_ex->e_ident, ELFMAG, SELFMAG) != 0)
goto out_free_dentry;
/* Verify the interpreter has a valid arch */
if (!elf_check_arch(interp_elf_ex) ||
elf_check_fdpic(interp_elf_ex))
goto out_free_dentry;
/* Load the interpreter program headers */
interp_elf_phdata = load_elf_phdrs(interp_elf_ex,
interpreter);
if (!interp_elf_phdata)
goto out_free_dentry;
/* Pass PT_LOPROC..PT_HIPROC headers to arch code */
elf_property_phdata = NULL;
elf_ppnt = interp_elf_phdata;
for (i = 0; i < interp_elf_ex->e_phnum; i++, elf_ppnt++)
switch (elf_ppnt->p_type) {
case PT_GNU_PROPERTY:
elf_property_phdata = elf_ppnt;
break;
case PT_LOPROC ... PT_HIPROC:
retval = arch_elf_pt_proc(interp_elf_ex,
elf_ppnt, interpreter,
true, &arch_state);
if (retval)
goto out_free_dentry;
break;
}
}
retval = parse_elf_properties(interpreter ?: bprm->file,
elf_property_phdata, &arch_state);
if (retval)
goto out_free_dentry;
/*
* Allow arch code to reject the ELF at this point, whilst it's
* still possible to return an error to the code that invoked
* the exec syscall.
*/
retval = arch_check_elf(elf_ex,
!!interpreter, interp_elf_ex,
&arch_state);
if (retval)
goto out_free_dentry;
/* Flush all traces of the currently running executable */
retval = begin_new_exec(bprm);
if (retval)
goto out_free_dentry;
/* Do this immediately, since STACK_TOP as used in setup_arg_pages
may depend on the personality. */
SET_PERSONALITY2(*elf_ex, &arch_state);
if (elf_read_implies_exec(*elf_ex, executable_stack))
current->personality |= READ_IMPLIES_EXEC;
if (!(current->personality & ADDR_NO_RANDOMIZE) && randomize_va_space)
current->flags |= PF_RANDOMIZE;
setup_new_exec(bprm);
/* Do this so that we can load the interpreter, if need be. We will
change some of these later */
retval = setup_arg_pages(bprm, randomize_stack_top(STACK_TOP),
executable_stack);
if (retval < 0)
goto out_free_dentry;
elf_brk = 0;
start_code = ~0UL;
end_code = 0;
start_data = 0;
end_data = 0;
/* Now we do a little grungy work by mmapping the ELF image into
the correct location in memory. */
for(i = 0, elf_ppnt = elf_phdata;
i < elf_ex->e_phnum; i++, elf_ppnt++) {
int elf_prot, elf_flags;
unsigned long k, vaddr;
unsigned long total_size = 0;
unsigned long alignment;
if (elf_ppnt->p_type != PT_LOAD)
continue;
elf_prot = make_prot(elf_ppnt->p_flags, &arch_state,
!!interpreter, false);
elf_flags = MAP_PRIVATE;
vaddr = elf_ppnt->p_vaddr;
/*
* The first time through the loop, first_pt_load is true:
* layout will be calculated. Once set, use MAP_FIXED since
* we know we've already safely mapped the entire region with
* MAP_FIXED_NOREPLACE in the once-per-binary logic following.
*/
if (!first_pt_load) {
elf_flags |= MAP_FIXED;
} else if (elf_ex->e_type == ET_EXEC) {
/*
* This logic is run once for the first LOAD Program
* Header for ET_EXEC binaries. No special handling
* is needed.
*/
elf_flags |= MAP_FIXED_NOREPLACE;
} else if (elf_ex->e_type == ET_DYN) {
/*
* This logic is run once for the first LOAD Program
* Header for ET_DYN binaries to calculate the
* randomization (load_bias) for all the LOAD
* Program Headers.
*
* There are effectively two types of ET_DYN
* binaries: programs (i.e. PIE: ET_DYN with INTERP)
* and loaders (ET_DYN without INTERP, since they
* _are_ the ELF interpreter). The loaders must
* be loaded away from programs since the program
* may otherwise collide with the loader (especially
* for ET_EXEC which does not have a randomized
* position). For example to handle invocations of
* "./ld.so someprog" to test out a new version of
* the loader, the subsequent program that the
* loader loads must avoid the loader itself, so
* they cannot share the same load range. Sufficient
* room for the brk must be allocated with the
* loader as well, since brk must be available with
* the loader.
*
* Therefore, programs are loaded offset from
* ELF_ET_DYN_BASE and loaders are loaded into the
* independently randomized mmap region (0 load_bias
* without MAP_FIXED nor MAP_FIXED_NOREPLACE).
*/
if (interpreter) {
load_bias = ELF_ET_DYN_BASE;
if (current->flags & PF_RANDOMIZE)
load_bias += arch_mmap_rnd();
alignment = maximum_alignment(elf_phdata, elf_ex->e_phnum);
if (alignment)
load_bias &= ~(alignment - 1);
elf_flags |= MAP_FIXED_NOREPLACE;
} else
load_bias = 0;
/*
* Since load_bias is used for all subsequent loading
* calculations, we must lower it by the first vaddr
* so that the remaining calculations based on the
* ELF vaddrs will be correctly offset. The result
* is then page aligned.
*/
load_bias = ELF_PAGESTART(load_bias - vaddr);
/*
* Calculate the entire size of the ELF mapping
* (total_size), used for the initial mapping,
* due to load_addr_set which is set to true later
* once the initial mapping is performed.
*
* Note that this is only sensible when the LOAD
* segments are contiguous (or overlapping). If
* used for LOADs that are far apart, this would
* cause the holes between LOADs to be mapped,
* running the risk of having the mapping fail,
* as it would be larger than the ELF file itself.
*
* As a result, only ET_DYN does this, since
* some ET_EXEC (e.g. ia64) may have large virtual
* memory holes between LOADs.
*
*/
total_size = total_mapping_size(elf_phdata,
elf_ex->e_phnum);
if (!total_size) {
retval = -EINVAL;
goto out_free_dentry;
}
}
error = elf_load(bprm->file, load_bias + vaddr, elf_ppnt,
elf_prot, elf_flags, total_size);
if (BAD_ADDR(error)) {
retval = IS_ERR_VALUE(error) ?
PTR_ERR((void*)error) : -EINVAL;
goto out_free_dentry;
}
if (first_pt_load) {
first_pt_load = 0;
if (elf_ex->e_type == ET_DYN) {
load_bias += error -
ELF_PAGESTART(load_bias + vaddr);
reloc_func_desc = load_bias;
}
}
/*
* Figure out which segment in the file contains the Program
* Header table, and map to the associated memory address.
*/
if (elf_ppnt->p_offset <= elf_ex->e_phoff &&
elf_ex->e_phoff < elf_ppnt->p_offset + elf_ppnt->p_filesz) {
phdr_addr = elf_ex->e_phoff - elf_ppnt->p_offset +
elf_ppnt->p_vaddr;
}
k = elf_ppnt->p_vaddr;
if ((elf_ppnt->p_flags & PF_X) && k < start_code)
start_code = k;
if (start_data < k)
start_data = k;
/*
* Check to see if the section's size will overflow the
* allowed task size. Note that p_filesz must always be
* <= p_memsz so it is only necessary to check p_memsz.
*/
if (BAD_ADDR(k) || elf_ppnt->p_filesz > elf_ppnt->p_memsz ||
elf_ppnt->p_memsz > TASK_SIZE ||
TASK_SIZE - elf_ppnt->p_memsz < k) {
/* set_brk can never work. Avoid overflows. */
retval = -EINVAL;
goto out_free_dentry;
}
k = elf_ppnt->p_vaddr + elf_ppnt->p_filesz;
if ((elf_ppnt->p_flags & PF_X) && end_code < k)
end_code = k;
if (end_data < k)
end_data = k;
k = elf_ppnt->p_vaddr + elf_ppnt->p_memsz;
if (k > elf_brk)
elf_brk = k;
}
e_entry = elf_ex->e_entry + load_bias;
phdr_addr += load_bias;
elf_brk += load_bias;
start_code += load_bias;
end_code += load_bias;
start_data += load_bias;
end_data += load_bias;
current->mm->start_brk = current->mm->brk = ELF_PAGEALIGN(elf_brk);
if (interpreter) {
elf_entry = load_elf_interp(interp_elf_ex,
interpreter,
load_bias, interp_elf_phdata,
&arch_state);
if (!IS_ERR_VALUE(elf_entry)) {
/*
* load_elf_interp() returns relocation
* adjustment
*/
interp_load_addr = elf_entry;
elf_entry += interp_elf_ex->e_entry;
}
if (BAD_ADDR(elf_entry)) {
retval = IS_ERR_VALUE(elf_entry) ?
(int)elf_entry : -EINVAL;
goto out_free_dentry;
}
reloc_func_desc = interp_load_addr;
allow_write_access(interpreter);
fput(interpreter);
kfree(interp_elf_ex);
kfree(interp_elf_phdata);
} else {
elf_entry = e_entry;
if (BAD_ADDR(elf_entry)) {
retval = -EINVAL;
goto out_free_dentry;
}
}
kfree(elf_phdata);
set_binfmt(&elf_format);
#ifdef ARCH_HAS_SETUP_ADDITIONAL_PAGES
retval = ARCH_SETUP_ADDITIONAL_PAGES(bprm, elf_ex, !!interpreter);
if (retval < 0)
goto out;
#endif /* ARCH_HAS_SETUP_ADDITIONAL_PAGES */
retval = create_elf_tables(bprm, elf_ex, interp_load_addr,
e_entry, phdr_addr);
if (retval < 0)
goto out;
mm = current->mm;
mm->end_code = end_code;
mm->start_code = start_code;
mm->start_data = start_data;
mm->end_data = end_data;
mm->start_stack = bprm->p;
if ((current->flags & PF_RANDOMIZE) && (randomize_va_space > 1)) {
/*
* For architectures with ELF randomization, when executing
* a loader directly (i.e. no interpreter listed in ELF
* headers), move the brk area out of the mmap region
* (since it grows up, and may collide early with the stack
* growing down), and into the unused ELF_ET_DYN_BASE region.
*/
if (IS_ENABLED(CONFIG_ARCH_HAS_ELF_RANDOMIZE) &&
elf_ex->e_type == ET_DYN && !interpreter) {
mm->brk = mm->start_brk = ELF_ET_DYN_BASE;
}
mm->brk = mm->start_brk = arch_randomize_brk(mm);
#ifdef compat_brk_randomized
current->brk_randomized = 1;
#endif
}
if (current->personality & MMAP_PAGE_ZERO) {
/* Why this, you ask??? Well SVr4 maps page 0 as read-only,
and some applications "depend" upon this behavior.
Since we do not have the power to recompile these, we
emulate the SVr4 behavior. Sigh. */
error = vm_mmap(NULL, 0, PAGE_SIZE, PROT_READ | PROT_EXEC,
MAP_FIXED | MAP_PRIVATE, 0);
}
regs = current_pt_regs();
#ifdef ELF_PLAT_INIT
/*
* The ABI may specify that certain registers be set up in special
* ways (on i386 %edx is the address of a DT_FINI function, for
* example. In addition, it may also specify (eg, PowerPC64 ELF)
* that the e_entry field is the address of the function descriptor
* for the startup routine, rather than the address of the startup
* routine itself. This macro performs whatever initialization to
* the regs structure is required as well as any relocations to the
* function descriptor entries when executing dynamically links apps.
*/
ELF_PLAT_INIT(regs, reloc_func_desc);
#endif
finalize_exec(bprm);
START_THREAD(elf_ex, regs, elf_entry, bprm->p);
retval = 0;
out:
return retval;
/* error cleanup */
out_free_dentry:
kfree(interp_elf_ex);
kfree(interp_elf_phdata);
out_free_file:
allow_write_access(interpreter);
if (interpreter)
fput(interpreter);
out_free_ph:
kfree(elf_phdata);
goto out;
}