《Linux内核分析》第七周学习总结
2016-04-09 13:11
302 查看
《Linux内核分析》第七周学习总结
——可执行程序的装载
姓名:王玮怡 学号:20135116
[b]一、理论部分总结[/b]
(一)可执行程序的装载
1、预处理、编译、链接和目标文件的格式
C代码经过编译器的预处理(.cpp),然后编译成汇编代码(.asm/.s),由汇编器成目标代码(.o,二进制文件),再链接成可执行文件,最后由操作系统加载到内存中执行。
预处理:编译器将C源代码中包含的头文件编译进来和执行宏替换等工作
gcc -E hello.c -o hello.i
编译:gcc首先要检查代码后,把代码翻译成汇编语言
gcc –S hello.i –o hello.s
汇编:把编译阶段生成的
gcc –c hello.s –o hello.o
链接:将编译输出
gcc hello.o –o hello
2、目标文件的格式ELF
(1)目标文件格式分类
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160409103722656-207736586.png)
(2)ABI和目标文件
ABI:应用程序二进制接口,在目标文件中二进制兼容模式
(3)ELF中三种目标文件
可重定位文件 .o文件
可执行文件
共享目标文件 .so文件
(4)ELF头
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160409104424703-956333813.png)
*当创建或增加一个进程映像时,系统在理论上将拷贝一个文件的段到一个虚拟的内存段
3、静态链接的ELF可执行文件与进程的地址空间
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160409110428203-874805734.png)
可执行文件加载到内存中开始执行的第一行
代码一般静态链接会将所有代码放在一个代码段
动态链接的进程会有多个代码段
(二)可执行程序、共享库和动态进程
1、装载可执行程序之前的工作
(1)命令行参数和shell环境
列出/usr/bin下的目录信息:
$ ls -l /usr/bin
Shell本身不限制命令行参数的个数,命令行参数的个数受限于命令自身
int main(int argc, char *argv[], char *envp[])
Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数:
int execve(const char * filename,char * const argv[ ],char * const envp[ ])
库函数exec*都是execve的封装例程
(2)命令行参数和环境变量是如何保存和传递的
shell程序 —> execve —> sys_execve
命令行参数和环境串都放在用户态堆栈中
初始化新程序堆栈时拷贝进去
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160409112522297-791514155.png)
先函数调用参数传递,再系统调用参数传递
2、装载时动态链接和运行时动态链接应用
动态链接分为可执行程序装载时动态链接和运行时动态链接
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160409114942812-348901967.png)
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160409115100765-563227605.png)
(三)可执行程序的装载
1、execve系统调用的内核处理过程
(1)新的可执行程序起点——一般是地址空间为0x8048000或0x8048300
(2)execve和fork都是特殊的系统调用——一般的都是陷入到内核态再返回到用户态
fork两次返回,第一次返回到父进程继续向下执行,第二次是子进程返回到ret_from_fork然后正常返回到用户态。
execve执行的时候陷入到内核态,用execve中加载的程序把当前正在执行的程序覆盖掉,当系统调用返回的时候也就返回到新的可执行程序起点。
sys_execve内部会解析可执行文件格式
do_execve —> do_execve_common —> exec_binprm
search_binary_handler符合寻找文件格式对应的解析模块
对于ELF格式的可执行文件fmt->load_binary(bprm);执行的应该是load_elf_binary其内部是和ELF文件格式解析的部分需要和ELF文件格式标准结合起来阅读
*load_elf_binary中,调用了start_thread()函数,通过修改内核堆栈中EIP的值作为新程序的起点
2、sys_execve的内部处理过程
系统调用的入口:do_execve
return do_execve(getname(filename), argv, envp);
转到do _ execve _ common函数
return do_execve_common(filename, argv, envp);
file = do_ open_exec(filename); //打开要加载的可执行文件,加载它的文件头部
bprm->file = file;
bprm->filename = bprm->interp = filename->name; //创建了一个结构体bprm,把环境变量和命令行参数都copy到结构体中
exec_binprm:
ret = search_binary_handler(bprm); //寻找此可执行文件的处理函数 在其中关键的代码
list_for_each_entry(fmt, &formats, lh);
retval = fmt->load_binary(bprm); //在这个循环中寻找能够解析当前可执行文件的代码并加载出来,实际调用的是load_elf_binary函数
文件解析相关模块:核心的工作就是把文件映射到进程的空间,对于ELF可执行文件会被默认映射到0x8048000。
需要动态链接的可执行文件先加载链接器ld(load _ elf _ interp 动态链接库动态链接文件),动态链接器的起点
如果它是一个静态链接,可直接将文件地址入口进行赋值
3、可执行程序的装载与“庄生梦蝶”
庄周(调用execve的可执行程序)入睡(调用execve陷入内核),醒来(系统调用execve返回用户态)发现自己是蝴蝶(被execve加载的可执行程序)
4、动态链接的可执行程序的装载
实际上动态链接库的依赖关系会形成一个“依赖树”
动态链接库的装载过程一般是一个图的广度遍历
动态链接是由动态链接器完成而不是内核
*静态链接:直接执行可执行程序的入口
*动态链接:装载和链接之后ld将CPU的控制权交给可执行程序
[b]二[b]、[/b]实验部分 ——Linux内核如何装载和启动一个可执行程序[/b]
(一)搭建环境
(查看代码时,可以使用shift+G直接跳到文件末尾)
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160407143556656-456562826.png)
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160407143807375-1728451499.png)
(生成根文件系统时,将init hello放入rootfs地址中,这样在执行exec文件时,就自动加载hello文件)
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160407143616937-117587175.png)
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160407143908437-26470702.png)
(二)使用gdb跟踪sys_execve内核函数的处理过程
1、加载符号表,并连接到端口1234
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160407144039234-618203365.png)
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160407143946609-2136355540.png)
2、设置断点
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160407144108093-1220293900.png)
3、执行
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160407144124328-669241339.png)
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160409124422093-1070549461.png)
输入c继续运行,进入到sys_execve系统调用:
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160409124552328-409479025.png)
输入s进行跟踪:
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160409124759750-1476767055.png)
new_ip是返回到用户态的第一条指令的地址:
——可执行程序的装载
姓名:王玮怡 学号:20135116
[b]一、理论部分总结[/b]
(一)可执行程序的装载
1、预处理、编译、链接和目标文件的格式
C代码经过编译器的预处理(.cpp),然后编译成汇编代码(.asm/.s),由汇编器成目标代码(.o,二进制文件),再链接成可执行文件,最后由操作系统加载到内存中执行。
预处理:编译器将C源代码中包含的头文件编译进来和执行宏替换等工作
gcc -E hello.c -o hello.i
编译:gcc首先要检查代码后,把代码翻译成汇编语言
gcc –S hello.i –o hello.s
汇编:把编译阶段生成的
.s文件转成二进制目标代码
gcc –c hello.s –o hello.o
链接:将编译输出
.o文件链接成最终的可执行文件(hello也是一个二进制文件)
gcc hello.o –o hello
2、目标文件的格式ELF
(1)目标文件格式分类
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160409103722656-207736586.png)
(2)ABI和目标文件
ABI:应用程序二进制接口,在目标文件中二进制兼容模式
(3)ELF中三种目标文件
可重定位文件 .o文件
可执行文件
共享目标文件 .so文件
(4)ELF头
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160409104424703-956333813.png)
*当创建或增加一个进程映像时,系统在理论上将拷贝一个文件的段到一个虚拟的内存段
3、静态链接的ELF可执行文件与进程的地址空间
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160409110428203-874805734.png)
可执行文件加载到内存中开始执行的第一行
代码一般静态链接会将所有代码放在一个代码段
动态链接的进程会有多个代码段
(二)可执行程序、共享库和动态进程
1、装载可执行程序之前的工作
(1)命令行参数和shell环境
列出/usr/bin下的目录信息:
$ ls -l /usr/bin
Shell本身不限制命令行参数的个数,命令行参数的个数受限于命令自身
int main(int argc, char *argv[], char *envp[])
Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数:
int execve(const char * filename,char * const argv[ ],char * const envp[ ])
库函数exec*都是execve的封装例程
(2)命令行参数和环境变量是如何保存和传递的
shell程序 —> execve —> sys_execve
命令行参数和环境串都放在用户态堆栈中
初始化新程序堆栈时拷贝进去
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160409112522297-791514155.png)
先函数调用参数传递,再系统调用参数传递
2、装载时动态链接和运行时动态链接应用
动态链接分为可执行程序装载时动态链接和运行时动态链接
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160409114942812-348901967.png)
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160409115100765-563227605.png)
(三)可执行程序的装载
1、execve系统调用的内核处理过程
(1)新的可执行程序起点——一般是地址空间为0x8048000或0x8048300
(2)execve和fork都是特殊的系统调用——一般的都是陷入到内核态再返回到用户态
fork两次返回,第一次返回到父进程继续向下执行,第二次是子进程返回到ret_from_fork然后正常返回到用户态。
execve执行的时候陷入到内核态,用execve中加载的程序把当前正在执行的程序覆盖掉,当系统调用返回的时候也就返回到新的可执行程序起点。
sys_execve内部会解析可执行文件格式
do_execve —> do_execve_common —> exec_binprm
search_binary_handler符合寻找文件格式对应的解析模块
对于ELF格式的可执行文件fmt->load_binary(bprm);执行的应该是load_elf_binary其内部是和ELF文件格式解析的部分需要和ELF文件格式标准结合起来阅读
*load_elf_binary中,调用了start_thread()函数,通过修改内核堆栈中EIP的值作为新程序的起点
2、sys_execve的内部处理过程
系统调用的入口:do_execve
return do_execve(getname(filename), argv, envp);
转到do _ execve _ common函数
return do_execve_common(filename, argv, envp);
file = do_ open_exec(filename); //打开要加载的可执行文件,加载它的文件头部
bprm->file = file;
bprm->filename = bprm->interp = filename->name; //创建了一个结构体bprm,把环境变量和命令行参数都copy到结构体中
exec_binprm:
ret = search_binary_handler(bprm); //寻找此可执行文件的处理函数 在其中关键的代码
list_for_each_entry(fmt, &formats, lh);
retval = fmt->load_binary(bprm); //在这个循环中寻找能够解析当前可执行文件的代码并加载出来,实际调用的是load_elf_binary函数
文件解析相关模块:核心的工作就是把文件映射到进程的空间,对于ELF可执行文件会被默认映射到0x8048000。
需要动态链接的可执行文件先加载链接器ld(load _ elf _ interp 动态链接库动态链接文件),动态链接器的起点
如果它是一个静态链接,可直接将文件地址入口进行赋值
3、可执行程序的装载与“庄生梦蝶”
庄周(调用execve的可执行程序)入睡(调用execve陷入内核),醒来(系统调用execve返回用户态)发现自己是蝴蝶(被execve加载的可执行程序)
4、动态链接的可执行程序的装载
实际上动态链接库的依赖关系会形成一个“依赖树”
动态链接库的装载过程一般是一个图的广度遍历
动态链接是由动态链接器完成而不是内核
*静态链接:直接执行可执行程序的入口
*动态链接:装载和链接之后ld将CPU的控制权交给可执行程序
[b]二[b]、[/b]实验部分 ——Linux内核如何装载和启动一个可执行程序[/b]
(一)搭建环境
(查看代码时,可以使用shift+G直接跳到文件末尾)
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160407143556656-456562826.png)
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160407143807375-1728451499.png)
(生成根文件系统时,将init hello放入rootfs地址中,这样在执行exec文件时,就自动加载hello文件)
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160407143616937-117587175.png)
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160407143908437-26470702.png)
(二)使用gdb跟踪sys_execve内核函数的处理过程
1、加载符号表,并连接到端口1234
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160407144039234-618203365.png)
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160407143946609-2136355540.png)
2、设置断点
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160407144108093-1220293900.png)
3、执行
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160407144124328-669241339.png)
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160409124422093-1070549461.png)
输入c继续运行,进入到sys_execve系统调用:
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160409124552328-409479025.png)
输入s进行跟踪:
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160409124759750-1476767055.png)
new_ip是返回到用户态的第一条指令的地址:
![](https://images2015.cnblogs.com/blog/744838/201604/744838-20160409125038015-1529578308.png)
相关文章推荐
- linux命令(9)设定固定ip
- CentOS6.x生产环境下一键安装mono+jexus的脚本,自启动,带服务,版本号自控
- linux中的装*神器
- centos6.5下使用yum安装mysql
- 初识linux
- 第七周linux学习
- centOS 操作记录二
- centOS 操作记录二
- centOS 操作记录二
- centOS 操作记录二
- centOS 操作记录二
- 16 个 Linux 服务器监控命令
- Linux文件时间属性
- Linux安装mysql——源码安装
- linux文件系统命令(6)---touch和mkdir
- LINUX内核分析第七周学习总结——可执行程序的装载
- Linux命令行:mv命令
- Linux命令行:rmdir命令
- Linux命令行:rm命令
- linux下MySQL源码安装