Linux内核分析之可执行程序的装载和启动
一、内容分析
1.可执行文件的创建
(1)预处理阶段
预处理过程读入源代码,检查包含预处理指令的语句和宏定义,并对源代码进行相应的转换,预处理过程还会删除程序中的注释和多余的空白字符。其中预处理指令主要包括以下四个方面:
宏定义指令——预处理过程会把源代码中出现的宏标识符替换成宏定义时的值,常用的两种宏定义:
//声明一个标识符,全部用大写字母来定义宏
#define MAX_NUM 10 //带参数的#define指令(宏函数)
#define Cube(x) ((x)*(x))
int i,num=;
i=Cube(num);
条件编译指令——定义不同的宏来决定编译程序对哪些代码进行处理,条件编译指令将决定哪些代码被编译,而哪些是不被编译的。
头文件包含指令——#include预处理指令的作用是在指令处展开被包含的文件。程序中包含头文件有两种格式:#include <my.h>和#include "my.h"
特殊符号——预编译程序可以识别一些特殊的符号。预编译程序对于在源程序中出现的这些串将用合适的值进行替换,__FILE__,__LINE__,__TIME__ 等。
上述阶段对应 gcc -E -o hello.cpp hello.c -m32
(2)编译阶段
在这个阶段中,Gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,Gcc把代码翻译成汇编语言。
对应gcc -x cpp-output -S -o hello.s hello.cpp -m32
(3) 汇编阶段
把编译阶段生成的”.s”文件转成目标文件,得到一个二进制文件。
对应 gcc -x assembler -c hello.s -o hello.o -m32
(4)链接阶段
函数库一般分为静态库和动态库两种。静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。动态库在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库。动态库一般后缀名为”.so”,Gcc在编译时默认使用动态库。
gcc -o hello hello.o -m32 (动态编译)
gcc -o hello.static hello.o -m32 -static(静态编译)
2.ELF可执行文件
(1)目标文件有三类:
可重定位文件——文件保存着代码和适当的数据,用来和其他的object文件一起来创建一个可执行文件或者是一个共享文件。
可执行文件:一个可执行(executable)文件保存着一个用来执行的程序;该文件指出了exec(BA_OS)如何来创建程序进程映象。
共享object文件:一个共享object文件保存着代码和合适的数据,用来被两个链接器链接,分别是连接编辑器[ld(SD_CMD)]和动态链接器。
(2)ELF文件格式
用readelf -h hello看看elf文件内容。
ELF文件头
ELF文件默认从0x8048000开始加载,上图的ELF头中Entry point address的内容为程序实际入口,当启动一个刚加载过可执行文件的进程时,就从此处执行。
3.可执行程序的执行环境,动态链接方式
实验直接使用execve系统调用。$ ls -l /usr/bin 列出/usr/bin下的目录信息。
Shell本身不限制命令行参数的个数,命令行参数的个数受限于命令自身。例如,int main(int argc, char *argv[]);又如, int main(int argc, char *argv[], char *envp[])。
Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数,int execve(const char * filename,char * const argv[ ],char * const envp[ ]),库函数exec*都是execve的封装例程。

int Exec(int argc, char * argv[])
{
int pid;
/* fork another process */
pid = fork();
if (pid<)
{
/* error occurred */
fprintf(stderr,"Fork Failed!");
exit(-);
}
else if (pid==)
{
/* child process */
execlp("/bin/ls","ls",NULL);
}
else
{
/* parent process */
/* parent will wait for the child to complete*/
wait(NULL);
printf("Child Complete!");
exit();
}
}
在Makefile中做了修改。编译的时候执行了hello.c,并把init 和hello放到了rootfs.img目录下,所以在执行exec命令的时候就相当于自动了加载了hello这个程序。
gdb调试前的准备可总结为:
$ cd LinuxKernel/ $ rm menu -rf $ git clone https://github.com/mengning/menu.git $ move test_exec.c test.c //查看Makefile文件可知道实验采用的是静态编译
rootfs:
gcc -o init linktable.c menu.c test.c -m32 -static -lpthread
gcc -o hello hello.c -m32 -static
之后通过gdb进行跟踪分析。
设置b sys_execve ,b load_elf_binary 等断点后,在MenuOS中输入exec进行调试分析。gdb首先在sys_execve处停下来。
然后是start_thread函数。
此时可以通过po new_ip和新窗口的 readelf -h helloc查看入口地址是否一致。
可以看到hello的入口地址和new_ip的值都是0x8048d0a 。说明对hello程序链接到了执行程序中。
继续单步执行可以看到把new_ip复制给了regs-> ip,之后继续执行看可以看到MenuOS界面输出了相应的hello world。
三、总结
可执行文件的创建包括预处理、编译、汇编、链接四个阶段。
学习并了解了ELF文件的格式,了解了ELF文件头、段头表、text节等各个组成部分。
通过对源码的追踪分析,理解了可执行程序加载的大致流程。当执行到execve系统调用时,陷入内核态,用execve加载的可执行文件覆盖当前进程的可执行程序,当execve系统调用返回时,返回新的可执行程序的执行起点。
如果是静态链接,elf_entry指向可执行文件规定的头部(main函数对应的位置0x8048***)如果需要依赖动态链接库,elf_entry指向动态链接器的起点。动态链接主要是由动态链接器ld来完成的。
可执行文件描述了如何初始化一个新的执行上下文,也就是如何开始一个新的计算。可执行文件类别有很多,在内核中有一个链表,在init的时候会将支持的可执行程序解析程序注册添加到链表中,那么在对可执行文件进行解析时,就从链表头开始找,找到匹配的处理函数就可以对其进行解析。
在shell中启动一个可执行程序时,会创建一个新进程,它通过覆盖父进程(也就是shell进程)的进程环境,并将用户态堆栈清空,获得所需要的执行上下文环境。
命令行参数和环境变量会通过shell传递给execve,excve通过系统调用参数传递,传递给sys_execve,最后sys_execve在初始化新进程堆栈的时候拷贝进去。
load_elf_binary->start_thread(…)通过修改内核堆栈中EIP的值作为新程序的起点。 如果新程序的动态链接的,那么就需要加载所需要的库函数,动态连接器ld会负责加载过程,动态链接库的装载过程类似于一个图的广度优先遍历过程,装载完成后,ld将CPU控制权交给可执行程序,继续执行可执行程序。
刘帅
原创作品转载请注明出处
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
Linux内核分析之可执行程序的装载和启动的更多相关文章
- linux内核学习之七 可执行程序的装载和运行
一 程序的装载和运行的基本知识补充 1 当进程开始执行一个新的程序时,从父进程继承的所有页被释放,以便在新的用户地址空间开始执行新的计算,甚至进程的特权都可能发生改变,但是,进程的PID不会改变 ...
- LINUX内核分析第七周学习总结:可执行程序的装载
LINUX内核分析第七周学习总结:可执行程序的装载 韩玉琪 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/cours ...
- Linux内核分析作业7:Linux内核如何装载和启动一个可执行程序
1.可执行文件的格式 在 Linux 平台下主要有以下三种可执行文件格式: 1.a.out(assembler and link editor output 汇编器和链接编辑器的输出) ...
- 《Linux内核分析》第七周 可执行程序的装载
[刘蔚然 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] WEEK SEVEN ...
- 《Linux内核分析》第七周笔记 可执行程序的装载
20135132陈雨鑫 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ...
- Linux内核分析——可执行程序的装载
链接的过程 首先运行C预处理器cpp,将C的源程序(a.c)翻译成ASCII码的中间文件(a.i) 接着C编译器ccl,将a.i翻译成ASCII汇编语言文件a.s 接着运行汇编器as,将a.s翻译成可 ...
- Linux内核分析 笔记七 可执行程序的装载 ——by王玥
一.预处理.编译.链接和目标文件的格式 (一)可执行程序是怎么得来的? 1. 2.可执行文件的创建——预处理.编译和链接 shiyanlou:~/ $ cd Code ...
- LINUX内核分析第七周学习总结——可执行程序的装载
LINUX内核分析第六周学习总结——进程的描述和进程的创建 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/cours ...
- Linux内核分析 第七周 可执行程序的装载
张嘉琪 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 Linux内核分析 第七 ...
随机推荐
- Git项目存放位置在导入Eclipse前不能存放在Eclipse Workspace
这篇帖子的背景: 本人想将一个git项目导入至Eclipse的Workspace中,并且该项目的所有git信息.但是,该git项目在导入之前,就已经存放在Eclipse的Workspace中.在将该g ...
- windows与linux之间文件的传输方式总结(转)
当然,windows与linux之间文件的传输的两种方式有很多,这里就仅仅列出工作中遇到的,作为笔记: 方法一:安装SSH Secure Shell Client客户端 安装即可登录直接拖拉到linu ...
- http之100-continue(转)
1.http 100-continue用于客户端在发送POST数据给服务器前,征询服务器情况,看服务器是否处理POST的数据,如果不处理,客户端则不上传POST数据,如果处理,则POST上传数据.在现 ...
- Masonry控制台打印约束冲突问题解决
不知道你是不是视图的布局也是用的第三方Masonry,在使用中是不是也遇到了控制台约束冲突的警告打印,看下图: 从输出的信息可以知道,有的控件的约束明显重复了设置,所以指出了是哪个控件,重复设置了哪些 ...
- [原创]NIOS小知识总结
本文记录了在使用NIOS中遇到的一些问题和相关的原因分析及解决办法,做个总结方便以后查阅.也希望可以帮到有同样问题的攻城狮.本文长期更新,遇到了就写下. 本人使用软件版本:QuartusII 13.0 ...
- jQuery的开始
一.下载 jQuery http://jquery.com/download/ 二.什么是jQuery: 1.jQuery 是一个 JavaScript 库. 2.jQuery 极大地简化了 Java ...
- DNX SDK版本 “dnx-clr-win-x86.1.0.0-beta5”无法安装
打开VS2015,出现 “DNX SDK版本 “dnx-clr-win-x86.1.0.0-beta5”无法安装,该解决方案将对此会话使用DNX SDK版本“dnx-clr-win-x86.1.0.0 ...
- (转)C#中键值对类型Hashtable与Dictionary比较和相关用法
最近在使用C#中的Hashtable与Dictionary的时候,想知道其区别,通过查找网络相关博客资料,作出下列总结. Hashtable与Dictionary虽然都是作为键值对的载体,但是采用的是 ...
- 添加了有道生词本的 chrome google翻译扩展和有道翻译扩展
在chrome发布项目,需要先花美金认证,还得要美国ID,无奈. 直接上源码,需手动导入. 原始项目源码并未开源,个人是从chrome本地文件里拿出来的,拓展来的,侵删(本来想着自已写一个,业余时间, ...
- Office组件之Spire.XLS的DotNet操作
Overview 在项目中,我们经常需要将程序中获得的大量数据导出到Excel表格中,打印报表:进一步,还可能生成其折线图,对数据的变化趋势进行分析,从而更好地开展项目工作. 最近,我发现了一个对于D ...