1. 这里看到,c语言用函数的简单封装,实现面向对象的重载效果(这样说似乎也不准确?),
do_execve_file(file, argv, envp)
=>__do_execve_file
do_execve(filename, argv, envp) / do_execveat(fd, filename, argv, envp, flags)
=> do_execveat_common
==>__do_execve_file
2. 可执行文件的加载
__do_execve_file(int fd, struct filename *filename,
struct user_arg_ptr argv,
struct user_arg_ptr envp,
int flags, struct file *file)
== //如果文件没打开, 打开,这里体现了兼容多种场景
if (!file)
file = do_open_execat(fd, filename, flags);
== //这里有一次做负载均衡的调用机会
sched_exec();
== 这部分就是建立环境,执行
retval = bprm_mm_init(bprm);
if (retval)
goto out_unmark;
retval = prepare_arg_pages(bprm, argv, envp);
if (retval < 0)
goto out;
/* Set the unchanging part of bprm->cred */
retval = security_bprm_creds_for_exec(bprm);
if (retval)
goto out;
//这里的copy操作最终会memcpy()
retval = copy_string_kernel(bprm->filename, bprm);
if (retval < 0)
goto out;
bprm->exec = bprm->p;
retval = copy_strings(bprm->envc, envp, bprm);
if (retval < 0)
goto out;
//这里的copy操作最终会copy_from_user(),将数据拷贝进内核
retval = copy_strings(bprm->argc, argv, bprm);
if (retval < 0)
goto out;
retval = exec_binprm(bprm);
=== //到支持列表匹配可执行文件格式
search_binary_handler(struct linux_binprm *bprm)
===//formats是一个在内核中已经注册的可支持的可执行文件的模块的链表头, 调用__register_binfmt()会注册新的可执行文件
list_for_each_entry(fmt, &formats, lh)
fmt->load_binary(bprm)
3. 5.8内核支持的可执行文件
aout:
static struct linux_binfmt aout_format = {
.module = THIS_MODULE,
.load_binary = load_aout_binary,
.load_shlib = load_aout_library,
};
misc:
static struct linux_binfmt misc_format = {
.module = THIS_MODULE,
.load_binary = load_misc_binary,
};
em86:
static struct linux_binfmt em86_format = {
.module = THIS_MODULE,
.load_binary = load_em86,
};
script:
static struct linux_binfmt script_format = {
.module = THIS_MODULE,
.load_binary = load_script,
};
flat:
static struct linux_binfmt flat_format = {
.module = THIS_MODULE,
.load_binary = load_flat_binary,
.core_dump = flat_core_dump,
.min_coredump = PAGE_SIZE
};
elf_fdpic:
static struct linux_binfmt elf_fdpic_format = {
.module = THIS_MODULE,
.load_binary = load_elf_fdpic_binary,
#ifdef CONFIG_ELF_CORE
.core_dump = elf_fdpic_core_dump,
#endif
.min_coredump = ELF_EXEC_PAGESIZE,
};
elf:
static struct linux_binfmt elf_format = {
.module = THIS_MODULE,
.load_binary = load_elf_binary,
.load_shlib = load_elf_library,
.core_dump = elf_core_dump,
.min_coredump = ELF_EXEC_PAGESIZE,
};
4.执行elf
load_binary()=>load_elf_binary()
==解析elf头部
==加载文件
==价值解释器
==执行, start_thread(),就是把elf文件的入口地址(下一条要执行指令的地址放入IP)、堆、栈放入寄存器
在load_elf_binary()函数中,加载的可执行文件将把当前正在执行的进程的内存空间完全覆盖
a)如果可执行文件是静态链接的文件,进程的IP寄存器值将被设置为main函数的入口地址,从而开始新的进程
b)如果可执行文件是动态链接的,IP的值将被设置为加载器ld的入口地址,是程序的运行由该加载器接管,ld会处理一些依赖的动态链接库相关的处理工作,使程序继续往下执行,
当前的进程都会被新加载进来的程序完全替换掉,这也是我们最早的那个程序中第19行的信息没有在终端上显示的原因
总结:
1)sys_execve()系统调用,进入内核空间
2) 加载新的可执行文件,进行可执行性检查
3)将新的可执行文件映射(map)到当前运行进程的进程空间中,覆盖原来的进程数据
4)将EIP的值设置为新的可执行程序的入口地址。
如果可执行程序是静态链接的程序,或不需要其他的动态链接库,则新的入口地址就是新的可执行文件的main函数地址
如果可执行程序还需要其他的动态链接库,则入口地址是加载器ld的入口地址
5)返回用户态,程序从新的EIP出开始继续往下执行。
老进程的上下文已经被新的进程完全替代了,但是进程的PID还是原来的。
从这个角度来看,新的运行进程中已经找不到原来的对execve调用的代码了
所以execve函数的一个特别之处是他从来不会成功返回,而总是实现了一次完全的变身