📄
字号:
*/
k = load_addr + eppnt->p_vaddr + eppnt->p_filesz;
if (k > elf_bss)
elf_bss = k;
/*
* Do the same thing for the memory mapping - between
* elf_bss and last_bss is the bss section.
*/
k = load_addr + eppnt->p_memsz + eppnt->p_vaddr;
if (k > last_bss)
last_bss = k;
} //end if
} //end for
/*
* Now fill out the bss section. First pad the last page up
* to the page boundary, and then perform a mmap to make sure
* that there are zero-mapped pages up to and including the
* last bss page.
*/
if (padzero(elf_bss)) {
error = -EFAULT;
goto out_close;
}
elf_bss = ELF_PAGESTART(elf_bss + ELF_MIN_ALIGN - 1);
* What we have mapped so far */
/* Map the last of the bss segment */
if (last_bss > elf_bss) {
down_write(¤t->mm->mmap_sem);
error = do_brk(elf_bss, last_bss - elf_bss);
up_write(¤t->mm->mmap_sem);
if (BAD_ADDR(error))
goto out_close;
}
*interp_load_addr = load_addr;
error = ((unsigned long) interp_elf_ex->e_entry) + load_addr;
out_close:
kfree(elf_phdata);
out:
return error;
}[/code]
代码中的do_brk()从用户空间分配一段空间。这段代码总体上与前面映射目标映像的那一段相似,就把它留给读者细细研究吧。注意解释器映像的类型一般都是ET_DYN,所以load_addr可能不等于0。
回到load_elf_binary()的代码中。
[code][sys_execve() > do_execve() > search_binary_handler() > load_elf_binary()]
compute_creds(bprm);
current->flags &= ~PF_FORKNOEXEC;
create_elf_tables(bprm, &loc->elf_ex, (interpreter_type == INTERPRETER_AOUT),
load_addr, interp_load_addr);
/* N.B. passed_fileno might not be initialized? */
if (interpreter_type == INTERPRETER_AOUT)
current->mm->arg_start += strlen(passed_fileno) + 1;
current->mm->end_code = end_code;
current->mm->start_code = start_code;
current->mm->start_data = start_data;
current->mm->end_data = end_data;
current->mm->start_stack = bprm->p;[/code]
在完成装入,启动用户空间的映像运行之前,还需要为目标映像和解释器准备好一些有关的信息,这些信息包括常规的argc、argv[]、envc、envp[]、还有一些所谓的“辅助向量(Auxiliary Vector)”。这些信息已经存在于内核中,但是需要把它们复制到用户空间,使它们在CPU进入解释器或目标映像的程序入口时出现在用户空间堆栈上。这里的create_elf_tables()就起着这个作用。
[code][sys_execve() > do_execve() > search_binary_handler() > load_elf_binary() > create_elf_tables()]
static int create_elf_tables(struct linux_binprm *bprm, struct elfhdr * exec, int interp_aout,
unsigned long load_addr, unsigned long interp_load_addr)
{
unsigned long p = bprm->p;
int argc = bprm->argc;
int envc = bprm->envc;
elf_addr_t __user *argv;
elf_addr_t __user *envp;
elf_addr_t __user *sp;
elf_addr_t __user *u_platform;
const char *k_platform = ELF_PLATFORM;
int items;
elf_addr_t *elf_info;
int ei_index = 0;
struct task_struct *tsk = current;
. . . . . .
/* Create the ELF interpreter info */
elf_info = (elf_addr_t *) current->mm->saved_auxv;
#define NEW_AUX_ENT(id, val) \
do { elf_info[ei_index++] = id; elf_info[ei_index++] = val; } while (0)
NEW_AUX_ENT(AT_HWCAP, ELF_HWCAP);
NEW_AUX_ENT(AT_PAGESZ, ELF_EXEC_PAGESIZE);
NEW_AUX_ENT(AT_CLKTCK, CLOCKS_PER_SEC);
NEW_AUX_ENT(AT_PHDR, load_addr + exec->e_phoff);
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);
NEW_AUX_ENT(AT_FLAGS, 0);
NEW_AUX_ENT(AT_ENTRY, exec->e_entry);
NEW_AUX_ENT(AT_UID, (elf_addr_t) tsk->uid);
NEW_AUX_ENT(AT_EUID, (elf_addr_t) tsk->euid);
NEW_AUX_ENT(AT_GID, (elf_addr_t) tsk->gid);
NEW_AUX_ENT(AT_EGID, (elf_addr_t) tsk->egid);
NEW_AUX_ENT(AT_SECURE, (elf_addr_t) security_bprm_secureexec(bprm));
. . . . . .
if (bprm->interp_flags & BINPRM_FLAGS_EXECFD) {
NEW_AUX_ENT(AT_EXECFD, (elf_addr_t) bprm->interp_data);
}
#undef NEW_AUX_ENT
/* AT_NULL is zero; clear the rest too */
memset(&elf_info[ei_index], 0,
sizeof current->mm->saved_auxv - ei_index * sizeof elf_info[0]);
/* And advance past the AT_NULL entry. */
ei_index += 2;
sp = STACK_ADD(p, ei_index); //实际上是(p - ei_index),因为堆栈向下伸展。
items = (argc + 1) + (envc + 1);
if (interp_aout) {
items += 3; /* a.out interpreters require argv & envp too */
} else {
items += 1; /* ELF interpreters only put argc on the stack */
}
bprm->p = STACK_ROUND(sp, items); //计算(sp - items)并与16字节边界对齐。
/* Point sp at the lowest address on the stack */
#ifdef CONFIG_STACK_GROWSUP
. . . . . .
#else
sp = (elf_addr_t __user *)bprm->p;
#endif
/* Now, let's put argc (and argv, envp if appropriate) on the stack */
if (__put_user(argc, sp++))
return -EFAULT;
if (interp_aout) {
. . . . . .
} else {
argv = sp; //用户空间堆栈上的argv[]从这里开始
envp = argv + argc + 1; //用户空间堆栈上的envp[]从这里开始
}
/* Populate argv and envp */
p = current->mm->arg_end = current->mm->arg_start;
while (argc-- > 0) {
size_t len;
__put_user((elf_addr_t)p, argv++);
len = strnlen_user((void __user *)p, PAGE_SIZE*MAX_ARG_PAGES);
if (!len || len > PAGE_SIZE*MAX_ARG_PAGES)
return 0;
p += len;
}
if (__put_user(0, argv))
return -EFAULT;
current->mm->arg_end = current->mm->env_start = p;
while (envc-- > 0) {
size_t len;
__put_user((elf_addr_t)p, envp++);
len = strnlen_user((void __user *)p, PAGE_SIZE*MAX_ARG_PAGES);
if (!len || len > PAGE_SIZE*MAX_ARG_PAGES)
return 0;
p += len;
}
if (__put_user(0, envp))
return -EFAULT;
current->mm->env_end = p;
/* Put the elf_info on the stack in the right place. */
sp = (elf_addr_t __user *)envp + 1; //用户空间堆栈上的elf_info[]从这里开始
if (copy_to_user(sp, elf_info, ei_index * sizeof(elf_addr_t)))
return -EFAULT;
return 0;
}[/code]
这个函数的代码大体上可以分成前后两半。
前一半是准备阶段,特别是对诸多辅助向量的准备。辅助向量是以编号加值的形式成对出现的。例如,AT_PHDR是个编号,表示目标映像中程序头数组在用户空间的位置(可想而知这是解释器需要的信息),而(load_addr + exec->e_phoff)是它的值,二者占据相继的两个32位长字。同样,AT_PHNUM是个编号,而exec->e_phnum是它的值,余类推。当然,这些编号对于内核和解释器都有着相同的意义。这里先把这些向量准备好在一个数组elf_info[]中,最后以编号AT_NULL即0作为数组的结尾。
后一半则是复制阶段,从代码中的注释行“/* Now, let's put argc (and argv, envp if appropriate) on the stack */”开始。这个阶段的目的是把这些信息复制到用户空间,把它们“种”在堆栈上,为解释器和目标映像的运行做好准备。代码中的变量bprm->p实质上是个指针,它代表着用户空间的堆栈指针。进入create_elf_tables()以后,就把bprm->p的值赋给了这里的变量p,所以在这里p也代表着用户空间的堆栈指针。注意这里通过__put_user()写入用户空间堆栈上的argv[]和envp[]中的只是一些指针,而相应的字符串则已经由do_execve()通过copy_strings()从用户空间拷贝到内核空间的某些页面中,后来这些页面又被映射到了用户空间(新的地址上),这里写入用户空间argv[]和envp[]中的那些指针就是指向各个字符串在用户空间的新的起点。函数strnlen_user()的作用是获取用户空间字符串的长度。
再回到load_elf_binary()的代码,剩下的只是“临门一脚”了。
[code][sys_execve() > do_execve() > search_binary_handler() > load_elf_binary()]
. . . . . .
start_thread(regs, elf_entry, bprm->p);
retval = 0;
. . . . . .
}[/code]
最后的start_thread()是个宏操作,其定义如下:
[code]#define start_thread(regs, new_eip, new_esp) do { \
__asm__("movl %0,%%fs ; movl %0,%%gs": :"r" (0)); \
set_fs(USER_DS); \
regs->xds = __USER_DS; \
regs->xes = __USER_DS; \
regs->xss = __USER_DS; \
regs->xcs = __USER_CS; \
regs->eip = new_eip; \
regs->esp = new_esp; \
} while (0)[/code]
这几条指令把作为参数传下来的用户空间程序入口和堆栈指针设置到regs数据结构中,这个数据结构实际上在系统堆栈中,是在当前进程通过系统调用进入内核时由SAVE_ALL形成的,而指向所保存现场的指针regs则作为参数传给了sys_execve(),并逐层传了下来。把所保存现场中的eip和esp改成了新的地址,就使得CPU在返回用户空间时进入新的程序入口。如果有解释器映像存在,那么这就是解释器映像的程序入口,否则就是目标映像的程序入口。那么什么情况下有解释器映像存在,什么情况下没有呢?如果目标映像与各种库的连接是静态连接,因而无需依靠共享库、即动态连接库,那就不需要解释器映像,启动目标映像运行的条件已经具备;否则就一定要有解释器映像存在。现代的二进制映像一般都使用共享库,所以一般都需要有解释器映像。
现在,对于需要动态连接的目标映像,目标映像和解释器映像都已映射到了当前进程的用户空间,并且“井水不犯河水”、同时并存。但是要启动目标映像的运行则条件还不具备。因为还需要装入(映射)某些共享库的映像,并使目标映像与这些共享库映像之间建立起动态连接,而这需要由解释器在用户空间完成,好在启动解释器运行的条件已经具备了。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -