Linux内核如何装载和启动一个可执行程序
2016-04-10 00:22
519 查看
罗冲 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
其中hellpStatic的代码如下:
然后将它编译成静态文件:
[root@localhost menu]# gcc -o hellostatic hello.c -static
编译完成后,将静态文件hellostatic与init拷贝到rootfs中,并在rootfs中执行命令:
find . | cpio -o -Hnewc |gzip -9 > ../rootfs.img
按照课件描述,启动linux跟踪。
在弹出窗口中输入相应的命令:
而do_execve()最终是调用到函数do_execve_common,在此函数中,有两条比较关键的代码:
其中exec_binprm函数为其中比较关键代码,而在这一函数中,比较关键的代码为:
再次查看search_binary_handler:
这里需要注意一个问题。 linux对于linux_binfmt,有多种不同的定义,因而最终实际调用load_binary也不同,系统会根据加载文件读取128个字节的文件头部后,决定了linux_binfmt的实际定义。a.out可执行文件的装载过程叫做load_aout_binary;而装载可执行脚本程序的处理过程叫做load_script()。在本例中,elf格式的文件的定义:
因此程序也就是调用load_elf_binary将程序加载起来。其中load_elf_binary()的主要步骤是:
1. 检查ELF可执行文件格式的有效性,比如魔数、程序头表中段(segment)的数量
2. 寻找动态链接”.interp”段,设置动态链接器路径(与动态链接有关)
3. 根据ELF可执行文件的程序头表的描述,对ELF文件进行映射,比如代码、数据、只读数据。
4. 初始化ELF进程环境,比如进程启动时EDX寄存器的地址应该是DT_FINI的地址
5. 将系统调用返回地址修改成ELF可执行文件的入口点,这个入口点取决于程序的链接方式,对于静态链接的ELF可执行文件,这个程序入口就是ELF文件的文件头中e_entry所指的地址;对于动态链接的ELF可执行文件,程序入口点是动态链接器。
注:摘自《程序员的自我修养–链接、装载与库》
其中第5步是能过函数start_thread()实现的
1) 首查找被执行的文件,如果找到文件,则读取文件的前128个字节。 用于判断文件的格式。 开头的4个字节,称为魔数。通过对魔数的判断可以确定文件的格式和类型。
2) 读取128个字节的文件头部后,调用search_binary_handle去搜索和匹配合适的可执行文件装载处理过程。通过文件头部的魔数确定文件的格式,并调用相应的装载处理过程。比如ELF可执行文件的装载处理过程叫做load_elf_binary();a.out可执行文件的装载过程叫做load_aout_binary;而装载可执行脚本程序的处理过程叫做load_script()
3)当load_elf_binary()执行完毕,返回至do_execve()再返回至sys_execve()时, 上面的第5步已经把系统调用返回地址改成了被装载的ELF程序的入口地址了。所以当sys_execve()系统调用从内核态返回到用户态时,EIP寄存器直接跳转到ELF程序的入口地址,于是新的程序开始执行,ELF可执行文件装载完成。
1. 实验准备
下载课件中准备的代码。int Execstaic(int argc, char *argv[]) { int pid; /* fork another process */ asm volatile( "mov $0x78, %%eax\n\t" "int $0x80\n\t" "mov %%eax, %0\n\t" :"=m"(pid) : ); if (pid < 0) { /* error occurred */ fprintf(stderr,"Fork Failed!"); exit(-1); } else if (pid == 0) { /* child process */ printf("This is Child Process!\n"); execlp("/hellostaic","hello",NULL); } else { /* parent process */ printf("This is Parent Process!\n"); /* parent will wait for the child to complete*/ wait(NULL); printf("Child Complete!\n"); } return 0; } int main() { PrintMenuOS(); SetPrompt("MenuOS>>"); MenuConfig("version","MenuOS V1.0(Based on Linux 3.18.6)",NULL); MenuConfig("quit","Quit from MenuOS",Quit); MenuConfig("time","Show System Time",Time); MenuConfig("exec","exec progress",Exec); MenuConfig("execStaic","exec static progress",Execstaic); ExecuteMenu(); }
其中hellpStatic的代码如下:
#include <stdio.h> int main() { printf("hello world.\n"); return 0; }
然后将它编译成静态文件:
[root@localhost menu]# gcc -o hellostatic hello.c -static
编译完成后,将静态文件hellostatic与init拷贝到rootfs中,并在rootfs中执行命令:
find . | cpio -o -Hnewc |gzip -9 > ../rootfs.img
按照课件描述,启动linux跟踪。
在弹出窗口中输入相应的命令:
2. 代码分析
当输入命令后,代码进入跟踪:而do_execve()最终是调用到函数do_execve_common,在此函数中,有两条比较关键的代码:
static int do_execve_common(...) { //1. 打开对应的二进制文件 file = do_open_exec(filename); //2. 创建一个结构体,在这个结构体中保存了需要加载的二进制文件的信息 bprm = kzalloc(sizeof(*bprm), GFP_KERNEL); //3. 加载二进制文件,并设置eip等参数 retval = exec_binprm(bprm); }
其中exec_binprm函数为其中比较关键代码,而在这一函数中,比较关键的代码为:
static int exec_binprm(struct linux_binprm *bprm) { ... ... //查找二进制文件的加载方式 ret = search_binary_handler(bprm); ... ... }
再次查看search_binary_handler:
int search_binary_handler(struct linux_binprm *bprm) { struct linux_binfmt *fmt; ... ... retry: read_lock(&binfmt_lock); //循环遍历查找可以解析需要加载的文件的代码。fmt list_for_each_entry(fmt, &formats, lh) { if (!try_module_get(fmt->module)) continue; read_unlock(&binfmt_lock); bprm->recursion_depth++; //加载文件,而此函数最终通过函数指针调用到load_elf_binary retval = fmt->load_binary(bprm); ... .... }
这里需要注意一个问题。 linux对于linux_binfmt,有多种不同的定义,因而最终实际调用load_binary也不同,系统会根据加载文件读取128个字节的文件头部后,决定了linux_binfmt的实际定义。a.out可执行文件的装载过程叫做load_aout_binary;而装载可执行脚本程序的处理过程叫做load_script()。在本例中,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, };
因此程序也就是调用load_elf_binary将程序加载起来。其中load_elf_binary()的主要步骤是:
1. 检查ELF可执行文件格式的有效性,比如魔数、程序头表中段(segment)的数量
2. 寻找动态链接”.interp”段,设置动态链接器路径(与动态链接有关)
3. 根据ELF可执行文件的程序头表的描述,对ELF文件进行映射,比如代码、数据、只读数据。
4. 初始化ELF进程环境,比如进程启动时EDX寄存器的地址应该是DT_FINI的地址
5. 将系统调用返回地址修改成ELF可执行文件的入口点,这个入口点取决于程序的链接方式,对于静态链接的ELF可执行文件,这个程序入口就是ELF文件的文件头中e_entry所指的地址;对于动态链接的ELF可执行文件,程序入口点是动态链接器。
注:摘自《程序员的自我修养–链接、装载与库》
其中第5步是能过函数start_thread()实现的
void start_thread(struct pt_regs *regs, unsigned long new_ip, unsigned long new_sp) { set_user_gs(regs, 0); regs->fs = 0; regs->ds = __USER_DS; regs->es = __USER_DS; regs->ss = __USER_DS; regs->cs = __USER_CS; regs->ip 4000 = new_ip; //修改相应的值 regs->sp = new_sp; regs->flags = X86_EFLAGS_IF; /* * force it to the iret return path by making it look as if there was * some work pending. */ set_thread_flag(TIF_NOTIFY_RESUME); }
3. 总结
程序加载通过do_execve()完成,而do_execve()执行过程:1) 首查找被执行的文件,如果找到文件,则读取文件的前128个字节。 用于判断文件的格式。 开头的4个字节,称为魔数。通过对魔数的判断可以确定文件的格式和类型。
2) 读取128个字节的文件头部后,调用search_binary_handle去搜索和匹配合适的可执行文件装载处理过程。通过文件头部的魔数确定文件的格式,并调用相应的装载处理过程。比如ELF可执行文件的装载处理过程叫做load_elf_binary();a.out可执行文件的装载过程叫做load_aout_binary;而装载可执行脚本程序的处理过程叫做load_script()
3)当load_elf_binary()执行完毕,返回至do_execve()再返回至sys_execve()时, 上面的第5步已经把系统调用返回地址改成了被装载的ELF程序的入口地址了。所以当sys_execve()系统调用从内核态返回到用户态时,EIP寄存器直接跳转到ELF程序的入口地址,于是新的程序开始执行,ELF可执行文件装载完成。
相关文章推荐
- linux查看系统启动时间
- linux下用户和组及权限详解
- 常用Linux shell命令汇总,总有一条你需要
- centos系统安装aria2下载工具
- linux中的重定向
- “Linux内核分析”实验报告(七)Linux 操作系统如何装载链接并执行程序
- centos linux 配置gtk+
- Linux 中SSH限制与更改端口、限制ROOT方式登录
- CentOS7 安装配置iptables防火墙
- Linux内核如何装载和启动一个可执行程序-----实验7
- linux中清除cache的方法
- linux中清除cache的方法
- perf学习-linux自带性能分析工具
- perf学习-linux自带性能分析工具
- Linux系统-目录的创建,移动或重命名和删除
- centos系统下忘记了root密码怎么办?
- Android socket AsyncTask和linux服务端通讯
- linux指定系统搜索头文件的路径
- 20135327郭皓--Linux内核分析第七周 可执行程序的装载
- Linux文本三剑客之awk的使用