当前位置:网站首页>ELF:加载过程
ELF:加载过程
2022-07-30 21:02:00 【坤昱】
Executable and Linkable Format,中文名称“可执行可链接格式”,简称为ELF。ELF是Linux类系统的主要可执行文件,参数格式对应架构Application Binary Interface(ABI)文档。
init_elf_binfmt
init_elf_binfmt属于elf初始化加载函数,通过core_initcall(init_elf_binfmt)启动:
tatic int __init init_elf_binfmt(void)
{
register_binfmt(&elf_format);
return 0;
}
register_binfmt函数分析,继续看elf_format定义:
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
};
load_elf_binary函数分析,load_elf_library函数分析,elf_core_dump函数分析,ELF_EXEC_PAGESIZE在x86_64架构中为4096。
register_binfmt
注册linux_binfmt结构到链表中
static inline void register_binfmt(struct linux_binfmt *fmt)
{
__register_binfmt(fmt, 0); // 1表示linux_binfmt结构加入到链表最前面
}
||
\/
insert ? list_add(&fmt->lh, &formats) :
list_add_tail(&fmt->lh, &formats);
load_elf_binary
加载elf可执行文件到内存中
static int load_elf_binary(struct linux_binprm *bprm)
{
if (memcmp(elf_ex->e_ident, ELFMAG, SELFMAG) != 0) // 如果不是ELF格式
// #define ELFMAG "\177ELF"
goto out;
if (elf_ex->e_type != ET_EXEC && elf_ex->e_type != ET_DYN) // 如果不是可执行程序,也不是动态库
// #define ET_EXEC 2
// #define ET_DYN 3
goto out;
if (!elf_check_arch(elf_ex)) // 如果不是x86_64架构
// ((x)->e_machine == EM_X86_64)
// #define EM_X86_64 62 /* AMD x86-64 */
goto out;
if (elf_check_fdpic(elf_ex)) // 这里不做检查
// #define elf_check_fdpic(ex) false
goto out;
if (!bprm->file->f_op->mmap) // 已传入mmap函数
goto out;
/* 将二进制文件elf_file中的ELF程序头加载到一个新分配的数组中,该文件的ELF头由elf_ex指向 */
/* elf_ex: 加载程序头的二进制文件的ELF头 */
/* elf_file: 打开的ELF二进制文件 */
elf_phdata = load_elf_phdrs(elf_ex, bprm->file);
if (!elf_phdata)
goto out;
elf_ppnt = elf_phdata;
// 程序头表中的条目数量,如果文件没有程序头表,e_phnum将值保留为零
for (i = 0; i < elf_ex->e_phnum; i++, elf_ppnt++) {
char *elf_interpreter;
if (elf_ppnt->p_type == PT_GNU_PROPERTY) {
// PT_GNU_PROPERTY由链接器生成,用于描述.note.gnu.property 部分
// #define PT_GNU_PROPERTY (PT_LOOS + 0x474e553)
// #define PT_LOOS 0x60000000
elf_property_phdata = elf_ppnt;
continue;
}
if (elf_ppnt->p_type != PT_INTERP)
// 指向了一个以 "NULL"结尾的字符串,这种段类型只对可执行程序有意义
// #define PT_INTERP 3
continue;
/* 这是用于共享库的程序解释器-现在假设这是一个a.out格式的二进制文件 */
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);
...
/* 确保路径以NULL结尾 */
if (elf_interpreter[elf_ppnt->p_filesz - 1] != '\0')
goto out_free_interp;
interpreter = open_exec(elf_interpreter); // 打开可执行文件
open_exec函数分析,继续往下看:
kfree(elf_interpreter); // 释放内存
retval = PTR_ERR(interpreter); // 检查返回值
if (IS_ERR(interpreter)) // 判断指针指向实际地址还是错误信息
goto out_free_ph;
would_dump(bprm, interpreter); // 确保mm->user_ns包含可执行文件
/* 获取exec标头 */
retval = elf_read(interpreter, interp_elf_ex,
sizeof(*interp_elf_ex), 0);
if (retval < 0)
goto out_free_dentry;
break;
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;
}
if (interpreter) { // 一些简单的解释器一致性检查
retval = -ELIBBAD;
/* Not an ELF interpreter */
if (memcmp(interp_elf_ex->e_ident, ELFMAG, SELFMAG) != 0) // 如果不是ELF格式
goto out_free_dentry;
// 验证解释器有(支持)一个有效的架构
if (!elf_check_arch(interp_elf_ex) ||
elf_check_fdpic(interp_elf_ex))
goto out_free_dentry;
/* 加载解释器程序头 */
interp_elf_phdata = load_elf_phdrs(interp_elf_ex,
interpreter);
if (!interp_elf_phdata)
goto out_free_dentry;
}
...
retval = begin_new_exec(bprm); // 启动可执行文件
if (retval)
goto out_free_dentry;
begin_new_exec函数分析,继续往下看:
/* 立即执行此操作,因为setup_arg_pages中使用的STACK_TOP可能取决于personality */
SET_PERSONALITY2(*elf_ex, &arch_state);
if (elf_read_implies_exec(*elf_ex, executable_stack)) // x86 或 x86_64模拟IA32并且默认stack
current->personality |= READ_IMPLIES_EXEC;
if (!(current->personality & ADDR_NO_RANDOMIZE) && randomize_va_space)
current->flags |= PF_RANDOMIZE; // 随机虚拟地址
/* 这个函数在创建新进程VM映像的早期调用,它设置要使用的VM布局函数 */
setup_new_exec(bprm);
/* 完成vm_area_struct堆栈,更新标志和权限,可选地重新定位堆栈,并添加一些额外的空间*/
retval = setup_arg_pages(bprm, randomize_stack_top(STACK_TOP),
executable_stack);
elf_bss = 0;
elf_brk = 0;
start_code = ~0UL;
end_code = 0;
start_data = 0;
end_data = 0;
/* 现在,我们通过将ELF映像映射到内存中的正确位置来做一些繁琐的工作 */
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;
...
/* 在此之前有一个PT_LOAD段p_memsz > p_filesz。如果需要,映射匿名页面,并清除该区域 */
/* 映射bss段的最后一个,如果头请求这些页面是可执行的,遵守(ppc32需要这个) */
retval = set_brk(elf_bss + load_bias,
elf_brk + load_bias,
bss_prot);
nbyte = ELF_PAGEOFFSET(elf_bss); // 对齐页
if (nbyte) {
nbyte = ELF_MIN_ALIGN - nbyte;
if (nbyte > elf_brk - elf_bss)
nbyte = elf_brk - elf_bss;
if (clear_user((void __user *)elf_bss +
load_bias, nbyte)) { // 清零
/* 如果ELF文件指定了奇怪的保护,这种bss清零可能会失败。所以我们不检查返回值 */
}
...
elf_prot = make_prot(elf_ppnt->p_flags, &arch_state,
!!interpreter, false); // 根据程序头表获得vma的读写执行权限
/* 第一次执行循环时,first_pt_load为真:layout将被计算
设置好之后,使用MAP_FIXED,
因为我们知道我们已经在每个二进制逻辑中安全地使用
MAP_FIXED_NOREPLACE映射了整个区域 */
if (!first_pt_load) {
elf_flags |= MAP_FIXED;
} else if (elf_ex->e_type == ET_EXEC) {
/* 这个逻辑对ET_EXEC二进制文件的第一个LOAD Program Header
运行一次,不需要特殊处理。*/
elf_flags |= MAP_FIXED_NOREPLACE;
} else if (elf_ex->e_type == ET_DYN) {
/* 此逻辑对ET_DYN二进制文件的第一个LOAD程序头运行一次,
以计算所有LOAD程序头的随机化 (load_bias) */
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 用于所有后续加载计算,
因此我们必须将其降低第一个 vaddr,
以便基于 ELF vaddr 的剩余计算将正确偏移
然后结果是页面对齐的 */
load_bias = ELF_PAGESTART(load_bias - vaddr);
/* 计算用于初始映射的 ELF 映射的整个大小 (total_size),
因为 load_addr_set 在执行初始映射后会设置为 true */
/* 这仅在 LOAD 段是连续(或重叠)时才有意义
如果用于相距很远的 LOAD,这将导致 LOAD 之间的孔被映射,
从而冒着映射失败的风险,因为它会比 ELF 文件本身大 */
/* 只有 ET_DYN 执行此操作,
因为某些 ET_EXEC(例如 ia64)可能在 LOAD 之间
存在较大的虚拟内存漏洞 */
total_size = total_mapping_size(elf_phdata,
elf_ex->e_phnum);
if (!total_size) {
retval = -EINVAL;
goto out_free_dentry;
}
}
error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt,
elf_prot, elf_flags, total_size); // 映射elf image
...
e_entry = elf_ex->e_entry + load_bias;
phdr_addr += load_bias;
elf_bss += load_bias;
elf_brk += load_bias;
start_code += load_bias;
end_code += load_bias;
start_data += load_bias;
end_data += load_bias;
/* 调用 set_brk 可以有效地映射 bss 和 break 部分所需的页面
我们必须在映射到解释器之前执行此操作,
以确保它不会被放置在 bss 需要去的地方 */
retval = set_brk(elf_bss, elf_brk, bss_prot);
if (interpreter) { // 解释器
/* 这比库例程读取函数更通用,因此我们将其分开
从技术上讲,提供库读取功能只是为了我们可以读取具有 ELF 标头的 a.out 库 */
// 映射解释器
elf_entry = load_elf_interp(interp_elf_ex,
interpreter,
load_bias, interp_elf_phdata,
&arch_state);
...
reloc_func_desc = interp_load_addr;
...
retval = create_elf_tables(bprm, elf_ex, interp_load_addr,
e_entry, phdr_addr);
create_elf_tables函数分析,继续往下看:
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)) {
/* 对于具有 ELF 随机化的体系结构,
当直接执行加载程序时(即 ELF 标头中没有列出解释器),
将 brk 区域移出 mmap 区域(因为它长大了,并且可能会与堆栈增长的早期发生冲突),
并进入 未使用的 ELF_ET_DYN_BASE 区域 */
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);
// andomize_page(mm->brk, 0x02000000);
// start:调用者将采用的最小可接受地址 -> mm->brk
// range:区域的大小,从start 开始,随机地址必须在该区域内
#ifdef compat_brk_randomized
current->brk_randomized = 1;
#endif
}
if (current->personality & MMAP_PAGE_ZERO) { // MMAP_PAGE_ZERO = 0x0100000
// personality 用于模拟以前 Linux 版本的 ABI 行为
/* SVr4 将页面 0 映射为只读,
并且某些应用程序“依赖”此行为
由于我们没有能力重新编译这些,我们模拟了 SVr4 的行为 */
error = vm_mmap(NULL, 0, PAGE_SIZE, PROT_READ | PROT_EXEC,
MAP_FIXED | MAP_PRIVATE, 0);
}
regs = current_pt_regs(); // 获取pt_regs指针
// __ptr += THREAD_SIZE - TOP_OF_KERNEL_STACK_PADDING;
// #define THREAD_SIZE (PAGE_SIZE (4096) << THREAD_SIZE_ORDER (2 + KASAN_STACK_ORDER (0)))
/* #ifdef CONFIG_X86_32
# ifdef CONFIG_VM86
# define TOP_OF_KERNEL_STACK_PADDING 16
# else
# define TOP_OF_KERNEL_STACK_PADDING 8
# endif
#else
# define TOP_OF_KERNEL_STACK_PADDING 0
#endif */
#ifdef ELF_PLAT_INIT
/* ABI 可以指定以特殊方式设置某些寄存器
(例如,在 i386 上,%edx 是 DT_FINI 函数的地址
此外,它还可以指定(例如,PowerPC64 ELF)e_entry 字段是 启动例程的函数描述符,
而不是启动例程本身的地址
此宏执行所需的 regs 结构初始化以及在执行动态链接应用程序时对函数描述符条目的任何重定位 */
ELF_PLAT_INIT(regs, reloc_func_desc);
#endif
finalize_exec(bprm); // 在 start_thread() 接管之前立即运行
// 在启动线程之前存储任何堆栈rlimit更改
START_THREAD(elf_ex, regs, elf_entry, bprm->p); // 设置pt_regs结构中的寄存器状态
retval = 0;
out:
return retval;
...
}
load_elf_library
加载elf库文件到内存中
tatic int load_elf_library(struct file *file)
{
struct elf_phdr *elf_phdata;
struct elf_phdr *eppnt;
unsigned long elf_bss, bss, len;
int retval, error, i, j;
struct elfhdr elf_ex;
error = -ENOEXEC;
retval = elf_read(file, &elf_ex, sizeof(elf_ex), 0); // 读取ELF头
...
j = sizeof(struct elf_phdr) * elf_ex.e_phnum;
// e_phnum 程序头表中的条目数量
retval = elf_read(file, eppnt, j, elf_ex.e_phoff); // 读取程序头中所有的条目
/* 现在使用 mmap 将库映射到内存中 */
error = vm_mmap(file,
ELF_PAGESTART(eppnt->p_vaddr),
(eppnt->p_filesz +
ELF_PAGEOFFSET(eppnt->p_vaddr)),
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_FIXED_NOREPLACE | MAP_PRIVATE,
(eppnt->p_offset -
ELF_PAGEOFFSET(eppnt->p_vaddr)));
elf_bss = eppnt->p_vaddr + eppnt->p_filesz;
...
len = ELF_PAGEALIGN(eppnt->p_filesz + eppnt->p_vaddr); // 按页对齐,如15对齐后是4096, 5015对齐后8192
bss = ELF_PAGEALIGN(eppnt->p_memsz + eppnt->p_vaddr);
if (bss > len) {
error = vm_brk(len, bss - len); // 分配用户内存
// 用户线程在访问给定的用户虚拟地址那一刻,它将获取将要访问的页面
if (error)
goto out_free_ph;
}
...
out_free_ph:
kfree(elf_phdata);
out:
return error;
}
elf_core_dump
elf核心转储
static int elf_core_dump(struct coredump_params *cprm)
{
/* 段数作为 16 位值记录到 ELF 标头中
此处修改时请检查 DEFAULT MAX_MAP_COUNT 定义 */
segs = cprm->vma_count + elf_core_extra_phdrs();
segs++;
/* 如果 segs > PN_XNUM(0xffff),则 e_phnum 溢出
为了避免这种情况,内核支持扩展编号 */
e_phnum = segs > PN_XNUM ? PN_XNUM : segs;
/* 收集有关进程的所有非内存信息
这也设置了文件头 */
if (!fill_note_info(&elf, e_phnum, &info, cprm))
goto end_coredump;
has_dumped = 1;
fill_note(&info->psinfo, "CORE", NT_PRPSINFO, sizeof(*psinfo), psinfo); // 填充记录
/* 弄清楚每个线程需要多少记录 */
info->thread_notes = 0;
for (i = 0; i < view->n; ++i)
if (view->regsets[i].core_note_type != 0)
++info->thread_notes;
/* 完整性检查
我们依赖于 NT_PRSTATUS 中的 regset 0,因为这是我们的一种特殊情况 */
if (unlikely(info->thread_notes == 0) ||
unlikely(view->regsets[0].core_note_type != NT_PRSTATUS)) {
WARN_ON(1);
return 0;
}
/* 初始化ELF文件头 */
fill_elf_header(elf, phdrs,
view->e_machine, view->e_flags);
/* 写 phdr 条目记录 */
{
size_t sz = get_note_info_size(&info);
/* For cell spufs */
sz += elf_coredump_extra_notes_size();
phdr4note = kmalloc(sizeof(*phdr4note), GFP_KERNEL);
if (!phdr4note)
goto end_coredump;
fill_elf_note_phdr(phdr4note, sz, offset);
offset += sz;
}
if (e_phnum == PN_XNUM) { // 头表中的条目数量
// #define PN_XNUM 0xffff
shdr4extnum = kmalloc(sizeof(*shdr4extnum), GFP_KERNEL);
if (!shdr4extnum)
goto end_coredump;
fill_extnum_info(&elf, shdr4extnum, e_shoff, segs); // 填充头段信息
}
if (!dump_emit(cprm, &elf, sizeof(elf))) // elf写入cprm->file
goto end_coredump;
if (!dump_emit(cprm, phdr4note, sizeof(*phdr4note))) // phdr4note写入cprm->file
goto end_coredump;
/* 为段转储编写程序头 */
for (i = 0; i < cprm->vma_count; i++) {
struct core_vma_metadata *meta = cprm->vma_meta + i;
struct elf_phdr phdr;
phdr.p_type = PT_LOAD;
phdr.p_offset = offset;
phdr.p_vaddr = meta->start;
phdr.p_paddr = 0;
phdr.p_filesz = meta->dump_size;
phdr.p_memsz = meta->end - meta->start;
offset += phdr.p_filesz;
phdr.p_flags = 0;
if (meta->flags & VM_READ)
phdr.p_flags |= PF_R;
if (meta->flags & VM_WRITE)
phdr.p_flags |= PF_W;
if (meta->flags & VM_EXEC)
phdr.p_flags |= PF_X;
phdr.p_align = ELF_EXEC_PAGESIZE;
if (!dump_emit(cprm, &phdr, sizeof(phdr))) // phdr写入cprm->file
goto end_coredump;
}
if (!elf_core_write_extra_phdrs(cprm, offset)) // 如果vsyscall_ehdr不为真
goto end_coredump;
...
/* 页面对齐 */
dump_skip_to(cprm, dataoff);
for (i = 0; i < cprm->vma_count; i++) {
struct core_vma_metadata *meta = cprm->vma_meta + i;
if (!dump_user_range(cprm, meta->start, meta->dump_size))
goto end_coredump;
}
if (!elf_core_write_extra_data(cprm)) // 如果vsyscall_ehdr不为真
goto end_coredump;
...
end_coredump:
free_note_info(&info);
kfree(shdr4extnum);
kfree(phdr4note);
return has_dumped;
}
open_exec
打开可执行文件
struct file *open_exec(const char *name)
{
struct filename *filename = getname_kernel(name); // 将名称filename结构,并将名称添加到此上下文的审核名称列表中
struct file *f = ERR_CAST(filename);
if (!IS_ERR(filename)) {
f = do_open_execat(AT_FDCWD, filename, 0);
// AT_FDCWD 指定路径为当前路径(./)
putname(filename);
}
return f;
}
||
\/
static struct file *do_open_execat(int fd, struct filename *name, int flags)
{
struct file *file;
int err;
struct open_flags open_exec_flags = {
.open_flag = O_LARGEFILE | O_RDONLY | __FMODE_EXEC,
.acc_mode = MAY_EXEC,
.intent = LOOKUP_OPEN,
.lookup_flags = LOOKUP_FOLLOW,
};
...
file = do_filp_open(fd, name, &open_exec_flags); // 获取文件描述结构
...
err = deny_write_access(file); // 拒绝别的进程以可写的方式打开
if (err)
goto exit;
if (name->name[0] != '\0')
fsnotify_open(file); // 文件被打开(发送到fsnotify_group)
out:
return file;
exit:
fput(file);
return ERR_PTR(err);
}
begin_new_exec
启动可执行文件
int begin_new_exec(struct linux_binprm * bprm)
{
struct task_struct *me = current;
int retval;
retval = bprm_creds_from_file(bprm); // 基于最终二进制计算brpm->cred
if (retval)
return retval;
...
/* 使其成为线程组中的唯一线程 */
retval = de_thread(me);
if (retval)
goto out;
/* 取消execve中的任何io_uring活动 */
io_uring_task_cancel();
/* 确保files表没有被共享 */
retval = unshare_files();
if (retval)
goto out;
/* 改变mm的可执行文件(显示为符号link /proc/[pid]/exe) */
retval = set_mm_exe_file(bprm->mm, bprm->file);
would_dump(bprm, bprm->file); // // 确保mm->user_ns包含可执行文件
if (bprm->have_execfd)
would_dump(bprm, bprm->executable);
acct_arg_size(bprm, 0);
retval = exec_mmap(bprm->mm); // 将mm_struct mm映射到current结构体。如果成功,这个函数返回exec_update_lock用于写入
#ifdef CONFIG_POSIX_TIMERS
exit_itimers(me);
// 只有当其他人不能修改signal->posix_timers列表时,
// do_exit或de_thread才会调用此函数。
//我们需要sighand->siglock来防止与/proc/pid/timers的竞争
flush_itimer_signals(); // 刷新间隔定时器信号
#endif
/* 将信号表设置为私有 */
retval = unshare_sighand(me);
if (retval)
goto out_unlock;
me->flags &= ~(PF_RANDOMIZE | PF_FORKNOEXEC |
PF_NOFREEZE | PF_NO_SETAFFINITY);
// 清除标志
flush_thread(); // 刷新线程
me->personality &= ~bprm->per_clear;
...
/* 在更改进程是否可转储(在setup_new_exec中)之前,
我们必须应用CLOEXEC,
以避免与用户空间中试图访问执行exec进程的本应关闭的文件描述符的进程竞争 */
do_close_on_exec(me->files);
...
perf_event_exec(); // 启用已标记为“在可执行时启用”的所有任务事件
/* 这些函数清除当前运行的可执行文件的所有痕迹(如参数),以便启动新的可执行文件 */
__set_task_comm(me, kbasename(bprm->filename), true);
...
flush_signal_handlers(me, 0); // 将信号恢复为默认
retval = set_cred_ucounts(bprm->cred); // 更新ucounts
if (retval < 0)
goto out_unlock;
/* 为这个可执行文件安装新的凭据 */
security_bprm_committing_creds(bprm);
/* 使用RCU替换旧的凭据,将新的凭据安装到当前任务,客观和主观凭证指针都会更新 */
commit_creds(bprm->cred);
bprm->cred = NULL;
...
/* 为了防止ptrace_attach()改变对任务凭证的判断,
必须至少持有Cred_guard_mutex;在此之后的任何时间,它可能被解锁 */
security_bprm_committed_creds(bprm);
/* 将打开的二进制文件传递给解释器 */
if (bprm->have_execfd) {
retval = get_unused_fd_flags(0);
if (retval < 0)
goto out_unlock;
fd_install(retval, bprm->executable); // 在fd数组(fdtable)中安装一个文件指针
bprm->executable = NULL;
bprm->execfd = retval;
}
return 0;
out_unlock:
up_write(&me->signal->exec_update_lock);
out:
return retval;
}
create_elf_tables
创建elf标签,可通过/proc/PID/auxv查看相关信息
create_elf_tables(struct linux_binprm *bprm, const struct elfhdr *exec,
unsigned long interp_load_addr,
unsigned long e_entry, unsigned long phdr_addr)
{
struct mm_struct *mm = current->mm;
unsigned long p = bprm->p;
int argc = bprm->argc;
int envc = bprm->envc;
elf_addr_t __user *sp;
elf_addr_t __user *u_platform;
elf_addr_t __user *u_base_platform;
elf_addr_t __user *u_rand_bytes;
const char *k_platform = ELF_PLATFORM; // (utsname()->machine)
const char *k_base_platform = ELF_BASE_PLATFORM; // NULL
unsigned char k_rand_bytes[16];
int items;
elf_addr_t *elf_info;
elf_addr_t flags = 0;
int ei_index;
const struct cred *cred = current_cred(); // current的主观凭证
struct vm_area_struct *vma;
/* 在某些情况下(例如超线程),
我们希望避免运行在同一个包上的进程的L1回收
我们可以做的一件事是为它们洗牌初始堆栈 */
p = arch_align_stack(p); // 获取栈对齐后的栈顶指针,低四位清零,随机化开启时 减去随机数整除8192
/* 如果此架构具有平台功能字符串,请将其复制到用户空间
在某些情况下(Sparc),用户空间不可能以任何其他方式获得此信息,
而在其他情况下(i386)则很难 */
u_platform = NULL;
if (k_platform) {
size_t len = strlen(k_platform) + 1;
u_platform = (elf_addr_t __user *)STACK_ALLOC(p, len); // 用户空间内存分配
if (copy_to_user(u_platform, k_platform, len)) // 硬件架构类型数据拷贝到用户内存
return -EFAULT;
}
/* 如果此架构具有“基础”平台功能字符串,请将其复制到用户空间 */
u_base_platform = NULL;
if (k_base_platform) { // NULL
size_t len = strlen(k_base_platform) + 1;
u_base_platform = (elf_addr_t __user *)STACK_ALLOC(p, len);
if (copy_to_user(u_base_platform, k_base_platform, len))
return -EFAULT;
}
/* 为用户空间 PRNG 播种生成 16 个随机字节 */
get_random_bytes(k_rand_bytes, sizeof(k_rand_bytes));
u_rand_bytes = (elf_addr_t __user *)
STACK_ALLOC(p, sizeof(k_rand_bytes));
if (copy_to_user(u_rand_bytes, k_rand_bytes, sizeof(k_rand_bytes)))
return -EFAULT;
/* 创建 ELF 解释器信息 */
elf_info = (elf_addr_t *)mm->saved_auxv; // /proc/PID/auxv
/* 如果 NEW_AUX_ENT() 的数量发生变化,则更新 AT_VECTOR_SIZE_BASE */
#define NEW_AUX_ENT(id, val) \
do { \
*elf_info++ = id; \
*elf_info++ = val; \
} while (0)
#ifdef ARCH_DLINFO
/* ARCH_DLINFO 必须先出现,
这样 PPC 才能对 AUXV 进行特殊对齐
如果 ARCH_DLINFO 中 NEW_AUX_ENT() 的数量发生变化,
则更新 AT_VECTOR_SIZE_ARCH */
ARCH_DLINFO;
#endif
NEW_AUX_ENT(AT_HWCAP, ELF_HWCAP); // 架构相关的信息
// #define ELF_HWCAP (boot_cpu_data.x86_capability[CPUID_1_EDX])
NEW_AUX_ENT(AT_PAGESZ, ELF_EXEC_PAGESIZE); // 系统页面大小
// #define ELF_EXEC_PAGESIZE 4096
NEW_AUX_ENT(AT_CLKTCK, CLOCKS_PER_SEC); // times() 递增的频率
// # define CLOCKS_PER_SEC (USER_HZ) 在像 times() 这样的“滴答声”中
// define USER_HZ 100 有些用户界面是
NEW_AUX_ENT(AT_PHDR, phdr_addr); // 程序头
NEW_AUX_ENT(AT_PHENT, sizeof(struct elf_phdr)); // 程序头条目的大小
NEW_AUX_ENT(AT_PHNUM, exec->e_phnum); // 程序头数
NEW_AUX_ENT(AT_BASE, interp_load_addr); // 解释器的基地址
if (bprm->interp_flags & BINPRM_FLAGS_PRESERVE_ARGV0) // 为解释器保留 argv0
flags |= AT_FLAGS_PRESERVE_ARGV0;
NEW_AUX_ENT(AT_FLAGS, flags); // 标志
NEW_AUX_ENT(AT_ENTRY, e_entry); // 程序的入口点
NEW_AUX_ENT(AT_UID, from_kuid_munged(cred->user_ns, cred->uid)); // 真实的uid
NEW_AUX_ENT(AT_EUID, from_kuid_munged(cred->user_ns, cred->euid)); // 有效用户标识符
NEW_AUX_ENT(AT_GID, from_kgid_munged(cred->user_ns, cred->gid)); // 真实的gid
NEW_AUX_ENT(AT_EGID, from_kgid_munged(cred->user_ns, cred->egid)); // 有效的gid
NEW_AUX_ENT(AT_SECURE, bprm->secureexec); // 安全模式布尔值
NEW_AUX_ENT(AT_RANDOM, (elf_addr_t)(unsigned long)u_rand_bytes); // 16 个随机字节的地址
/* HWCAP2 提供具有内核启用 CPU 功能的掩码,以便应用程序可以发现它可以安全地使用它们 */
#ifdef ELF_HWCAP2
NEW_AUX_ENT(AT_HWCAP2, ELF_HWCAP2); // AT_HWCAP的扩展
#endif
NEW_AUX_ENT(AT_EXECFN, bprm->exec); // 程序文件名
if (k_platform) { // 硬件架构类型数据
NEW_AUX_ENT(AT_PLATFORM,
(elf_addr_t)(unsigned long)u_platform); // 字符串标识 CPU 进行优化
}
if (k_base_platform) { // NULL
NEW_AUX_ENT(AT_BASE_PLATFORM,
(elf_addr_t)(unsigned long)u_base_platform); // 标识真实平台的字符串,可能与 AT_PLATFORM 不同
}
if (bprm->have_execfd) {
NEW_AUX_ENT(AT_EXECFD, bprm->execfd); // 程序的文件描述符
}
#undef NEW_AUX_ENT
/* AT_NULL 为零; 清除其余部分 */
memset(elf_info, 0, (char *)mm->saved_auxv +
sizeof(mm->saved_auxv) - (char *)elf_info);
/* 通过 AT_NULL 条目 */
elf_info += 2;
ei_index = elf_info - (elf_addr_t *)mm->saved_auxv; // 头信息索引(偏移值)
sp = STACK_ADD(p, ei_index); // 栈偏移地址
items = (argc + 1) + (envc + 1) + 1; // 参数(加环境变量)数量
bprm->p = STACK_ROUND(sp, items); // 栈指针
/* 将 sp 指向栈的最低地址 */
#ifdef CONFIG_STACK_GROWSUP
sp = (elf_addr_t __user *)bprm->p - items - ei_index;
bprm->exec = (unsigned long)sp; /* XXX: PARISC HACK */
#else
sp = (elf_addr_t __user *)bprm->p;
#endif
/* 手动增加堆栈;为了增加堆栈,一些体系结构对用户空间的访问有一个限制 */
if (mmap_read_lock_killable(mm))
return -EINTR;
vma = find_extend_vma(mm, bprm->p);
// 扩充vma
// 在 vma->vm_start 或 vma->vm_prev->vm_end 值更改后更新增强的 rbtree rb_subtree_gap 值,而不修改 vma 在 rbtree 中的位置
mmap_read_unlock(mm);
/* 现在,让我们将argc(以及argv, envp(如果合适的话) )放到堆栈中 */
if (put_user(argc, sp++))
return -EFAULT;
/* 将 argv 指针列表填充回 argv 字符串 */
p = mm->arg_end = mm->arg_start;
while (argc-- > 0) {
size_t len;
if (put_user((elf_addr_t)p, sp++))
return -EFAULT;
len = strnlen_user((void __user *)p, MAX_ARG_STRLEN);
if (!len || len > MAX_ARG_STRLEN)
return -EINVAL;
p += len;
}
if (put_user(0, sp++))
return -EFAULT;
mm->arg_end = p;
/* 将elf_info放在堆栈的正确位置 */
mm->env_end = mm->env_start = p;
while (envc-- > 0) {
size_t len;
if (put_user((elf_addr_t)p, sp++))
return -EFAULT;
len = strnlen_user((void __user *)p, MAX_ARG_STRLEN);
if (!len || len > MAX_ARG_STRLEN)
return -EINVAL;
p += len;
}
if (put_user(0, sp++))
return -EFAULT;
mm->env_end = p;
/* 将elf_info放在堆栈的正确位置 */
if (copy_to_user(sp, mm->saved_auxv, ei_index * sizeof(elf_addr_t)))
return -EFAULT;
return 0;
}
边栏推荐
猜你喜欢

MySQL (2)

【元胞自动机】基于元胞自动机模拟生命演化、病毒感染等实例附matlab代码

MySQL的Replace用法详解

opencv,numpy,tensor格式转换
![[Limited Time Bonus] 21-Day Learning Challenge - MySQL from entry to mastery](/img/12/f9fe60c7fc3d376aa95a4756541b61.png)
[Limited Time Bonus] 21-Day Learning Challenge - MySQL from entry to mastery

GPGGA NTRIP RTCM Notes

2.网络资源访问工具:requests

Use the map function to operate on each element in the list It seems that you don't need a map

【考研词汇训练营】Day18 —— amount,max,consider,account,actual,eliminate,letter,significant,embarrass,collapse

excel数字显示e+17怎么恢复?excel数字变成了小数点+E+17的解决方法
随机推荐
Deep Non-Local Kalman Network for VideoCompression Artifact Reduction
【软件工程之美 - 专栏笔记】31 | 软件测试要为产品质量负责吗?
[Machine Learning] The Beauty of Mathematics Behind Gradient Descent
MySQL 高级(进阶) SQL 语句 (一)
Deep Kalman Filter Network for Video Compression Artifact Removal
想要写出好的测试用例,先要学会测试设计
Redis数据更新,是先更新数据库还是先更新缓存?
MySQL (2)
excel数字如何转换成文本?excel表格数据转换成文本的方法
MySQL Workbench 安装及使用
Simple configuration of three-tier architecture
What is the common factor
MySQL 删除表数据,重置自增 id 为 0 的两个方式
flyway的快速入门教程
[Nuxt 3] (十四) Nuxt 生命周期
WeChat reading, export notes
Motion Tuned Spatio-temporal Quality Assessmentof Natural Videos
DPW-SDNet: Dual Pixel-Wavelet Domain Deep CNNsfor Soft Decoding of JPEG-Compressed Images
《快速掌握QML》第六章 动画
KingbaseES V8R6备份恢复案例之---同一数据库创建不同stanza备份