第七周:可执行程序的装载
2016-04-10 21:50
225 查看
范闻泽 原创作品转载请注明出处 《Linux内核分析》MOOC课程 http://mooc.study.163.com/course/USTC-1000029000
一、预处理,编译,链接和目标文件格式
1.可执行程序是怎么得来的
c代码->预处理->汇编代码->汇编器->目标代码->链接成可执行文件->加载到内核执行
2.目标文件的格式ELF
符号修饰标准、变量内层布局、函数调用方式等这些跟可执行代码二进制兼容性相关的内容称为ABI(Application Binary Interface)常见的ABI格式:
3.静态链接的ELF可执行文件和进程的地址空间
一般静态链接将会把所有代码放在同一个代码段。
动态连接的进程会有多个代码段。
二、可执行程序、共享库和动态链接
1.可执行程序的执行环境
命令行参数和shell环境,一般我们执行一个程序的Shell环境,我们的实验直接使用execve系统调用。 Shell本身不限制命令行参数的个数, 命令行参数的个数受限于命令自身。 shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数。 命令行参数和环境串都放在用户态堆栈中。
2.装载时动态链接和运行时动态链接应用举例
动态链接分为可执行程序装载时动态链接和运行时动态链接
三、可执行程序的装载
1. sys_execve内核处理过程
(1)新的可执行程序起点
一般是地址空间为0x8048000或0x8048300
(2)execve与fork
execve和fork都是特殊一点的系统调用:一般的都是陷入到内核态再返回到用户态。
fork两次返回,第一次返回到父进程继续向下执行,第二次是子进程返回到ret_from_fork然后正常返回到用户态。
execve执行的时候陷入到内核态,用execve中加载的程序把当前正在执行的程序覆盖掉,当系统调用返回的时候也就返回到新的可执行程序起点。
execve
- 执行到可执行程序 -> 陷入内核 - 构造新的可执行文件 -> 覆盖掉原可执行程序 - 返回到新的可执行程序,作为起点(也就是main函数) - 需要构造其执行环境;
Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数,先函数调用参数传递,再系统调用参数传递。
(3)静态链接的可执行程序和动态链接的可执行程序execve系统调用返回时不同
静态链接:elf_entry指向可执行文件的头部,一般是main函数,是新程序执行的起点。2.动态链接的可执行程序的装载
动态链接库的依赖关系会形成一个图。load_elf_interp实际加载动态链接器,entry返回的是动态链接器的入口,根据需求加载动态链接库,根据库的需要再加载更多的库。
四、实验 Linux内核如何装载和启动一个可执行程序
1.环境搭建:
rm menu -rf git clone https://github.com/megnning/menu.git cd menu ls mv test_exec.c test.c vi test.c // 可以看到增加了一个exec的程序,只比fork程序多了一个execlp vi Makefile // 查看Makefile的更改,加入了hello make rootfs
执行exec后,出现了hello world,这其实是加载了一个新的程序hello。
2.使用gdb跟踪
qemu -kernel ../linux-3.18.6/arch/x86/boot/bzImage -initrd ../rootfs.img -s -S gdb file ../linux-3.18.6/vmlinux target remote:1234 b sys_execve //可以先停在sys_execve然后再设置其他断点 b load_elf_binary b start_thread
以上两图为设置断点。
先设置sys_execve后,在menuOS里执行系统调用exec,会停在如图所示代码,找到第一个断点
找到了第二个断点
给新栈的栈底指针赋值
找到了第三个断点start_thread
查看附近的代码:给新栈的赋值。
执行到start_thread的时候有一个问题:
new ip到底是指向哪里的?
用po(print object)指令:
po new_ip 可以看到一个地址:0x80495ba
readelf -h hello 找到hello这个可执行程序的入口地址。 这是一个静态编译的可执行文件。
new ip是返回用户态的第一条指令的地址。
3.实验总结
1. execve()系统调用,实质是运行的sys_execve()函数。 2. sys_execve中有do_execve(),读取128个字节的文件头部。 3. 调用search_binary_handle()去搜索和匹配合适的可执行文件装载处理过程。 4. ELF被load_elf_binary()装载。 load_elf_binary函数中调用了start_thread函数,创建新进程的堆栈,有pt_regs栈底指针,更重要的是修改eip,即程序执行入口。 5. 函数返回至do_execve(),又返回至sys_execve()。 6. 新的程序开始执行,ELF可执行文件装载完成。
相关文章推荐
- K-近邻算法
- C++中new和malloc的区别
- HDU2043
- 西邮Linux兴趣小组2016免试题
- 欢迎使用CSDN-markdown编辑器
- Compressing Neural Networks with the Hashing Trick
- 《软件测试技术》
- MySQL 入门(三)—— MySQL数据类型
- 双指针的使用与函数返回数组
- 项目过程中的心得
- Linux内核分析-Linux内核如何装载和启动一个可执行程序
- CSS常用属性和值
- Android笔记 关掉两个Activity之间的Activity
- Google开源 Volley 网络框架使用
- MySQL优化
- swift语言中子类继承UITableViewController需要注意的点
- Spring内核研究-通过工厂注入
- Struts2 整合 Hibernate 框架
- 贪婪算法-最小生成树-Kruskal算法
- 从源代码理解调用OnCreate的路径