LINUX内核分析第七周学习总结:可执行程序的装载
LINUX内核分析第七周学习总结:可执行程序的装载
韩玉琪 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
内容提要

一、得到一个可执行程序
1. 预处理、编译、链接
gcc hello.c -o hello.exe
gcc编译源代码生成最终可执行的二进制程序,GCC后台隐含执行了四个阶段步骤。
预处理 => 编译 => 汇编 => 链接
预处理:编译器将C源代码中包含的头文件编译进来和执行宏替换等工作。
gcc -E hello.c -o hello.i
编译:gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc把代码翻译成汇编语言。
gcc –S hello.i –o hello.s
-S:该选项只进行编译而不进行汇编,生成汇编代码。
汇编:把编译阶段生成的
.s文件转成二进制目标代码.gcc –c hello.s –o hello.o
链接:将编译输出
.o文件链接成最终的可执行文件。gcc hello.o –o hello
运行:若链接没有-o指明,则生成可执行文件默认为
a.out./hello
2. 目标文件格式
(1)文件格式

- a.out是最早的可执行文件格式
注:ABI——应用程序二进制接口
(2)ELF分类
可重定位文件:保存着代码和适当的数据,用来和其他的object文件一起来创建一个可执行文件或者是一个共享文件。
可执行文件:保存着一个用来执行的程序;该文件指出了exec(BA_OS)如何来创建程序进程映象。
共享文件:保存着代码和合适的数据,用来被下面的两个链接器链接。
- 第一个是连接编辑器[请参看ld(SD_CMD)],可以和其他的可重定位和共享object文件来创建其他的object。
- 第二个是动态链接器,联合一个可执行文件和其他的共享object文件来创建一个进程映象。
object文件参与程序的链接(创建)和执行。
(3)ELF头
查看ELF文件的头部:
readelf在文件开始保存了:
- 路线图:描述该文件组织情况
- 程序头表:告诉系统如何创建一个进程的内存映像
- section头表:描述文件的section信息。(每个section在这个表中有一个入口,给出该section信息)
当创建或增加一个进程映像时,系统在理论上将拷贝一个文件的段到一个虚拟的内存段。
3. 静态链接的ELF可执行文件和进程的地址空间

- 入口点:程序从0x804800开始。
- 可执行文件加载到内存中开始执行的第一行代码。
- 一般静态链接将会把所有代码放在同一个代码段。
- 动态连接的进程会有多个代码段。
二、可执行程序的执行环境
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环境变量的保存与传递
shell程序 => execve => sys_execve
命令行参数和环境串都放在用户态堆栈中
初始化新程序堆栈时拷贝进去

3. 可执行程序动态链接
(1)动态链接
关注:
load_elf_binaryload_elf_binary(...)
{
...
kernel_read();//其实就是文件解析
...
//映射到进程空间 0x804 8000地址
elf_map();//
...
if(elf_interpreter) //依赖动态库的话
{
...
//装载ld的起点 #获得动态连接器的程序起点
elf_entry=load_elf_interp(...);
...
}
else //静态链接
{
...
elf_entry = loc->elf_ex.e_entry;
...
}
...
//static exe: elf_entry: 0x804 8000
//exe with dyanmic lib: elf_entry: ld.so addr
start_thread(regs,elf_entry,bprm->p);
}
实际上,装载过程是一个广度遍历,遍历的对象是“依赖树”。
主要过程是动态链接器完成、用户态完成。
(2)装载时动态链接
/*准备.so文件*/
shlibexample.h (1.3 KB) - Interface of Shared Lib Example
shlibexample.c (1.2 KB) - Implement of Shared Lib Example
/*编译成libshlibexample.so文件*/
$ gcc -shared shlibexample.c -o libshlibexample.so -m32
/*使用库文件(因为已经包含了头文件所以可以直接调用函数)*/
SharedLibApi();
(3)运行时动态链接
dllibexample.h (1.3 KB) - Interface of Dynamical Loading Lib Example
dllibexample.c (1.3 KB) - Implement of Dynamical Loading Lib Example
/*编译成libdllibexample.so文件*/
$ gcc -shared dllibexample.c -o libdllibexample.so -m32
/*使用库文件*/
void * handle = dlopen("libdllibexample.so",RTLD_NOW);//先加载进来
int (*func)(void);//声明一个函数指针
func = dlsym(handle,"DynamicalLoadingLibApi");//根据名称找到函数指针
func(); //调用已声明函数
(4)运行
$ gcc main.c -o main -L/path/to/your/dir -lshlibexample -ldl -m32
$ export LD_LIBRARY_PATH=$PWD
/*将当前目录加入默认路径,否则main找不到依赖的库文件,当然也可以将库文件copy到默认路径下。*/
三、可执行程序的装载
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函数,是新程序执行的起点。
- 动态链接:elf_entry指向ld(动态链接器)的起点,加载
load_elf_interp
2. 庄周梦蝶
庄周(调用execve的可执行程序)入睡(调用execve陷入内核),醒来(系统调用execve返回用户态)发现自己是蝴蝶(被execve加载的可执行程序)。
3. 动态链接的可执行程序的装载
(1)可执行文件开始执行的起点在哪里?如何才能让execve系统调用返回到用户态时执行新程序?
- 修改int 0x80压入内核堆栈的EIP,通过修改内核堆栈中EIP的值作为新程序的起点。
(2)Linux内核是如何支持多种不同的可执行文件格式
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,
};
static int __iit init_elf_binfmt(void)
{n
register_binfmt(&elf_format);//把变量注册进内核链表,在链表里查找文件的格式
return 0;
}
(3)动态链接
- 可执行程序需要依赖动态链接库,而这个动态链接库可能会依赖其他的库,这样形成了一个关系图——动态链接库会生成依赖树。
- 依赖动态链接器进行加载库并进行解析(这就是一个图的遍历),装载所有需要的动态链接库;之后ld将CPU的控制权交给可执行程序
- 动态链接的过程主要是动态链接器在起作用,而不是内核完成的。
四、实验部分
1. GDB跟踪sys_execve内核函数处理过程
准备内核

exec函数运行

test.c文件,更新命令部分

在exec函数中执行了动态链接代码

makefile中添加对hello程序的处理代码

gdb调试,设置断点到sys_exec

进入函数内部发现调用了do_execve()函数

gdb调试过程中,执行到start_ thread,查看new_ ip的内容

readelf -h hello.c查看入口地址为0x8048d0a与new_ ip查看到的相同。
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 动态链接库动态链接文件),动态链接器的起点。如果它是一个静态链接,可直接将文件地址入口进行赋值。
LINUX内核分析第七周学习总结:可执行程序的装载的更多相关文章
- LINUX内核分析第七周学习总结——可执行程序的装载
LINUX内核分析第六周学习总结——进程的描述和进程的创建 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/cours ...
- LINUX内核分析第七周学习总结
LINUX内核分析第七周学习总结 标签(空格分隔): 20135328陈都 陈都 原创作品转载请注明出处 <Linux内核分析>MOOC课程 http://mooc.study.163.c ...
- Linux内核分析第七周学习笔记——Linux内核如何装载和启动一个可执行程序
Linux内核分析第七周学习笔记--Linux内核如何装载和启动一个可执行程序 zl + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study. ...
- Linux内核分析——第七周学习笔记20135308
第七周 可执行程序的装载 一.预处理.编译.链接和目标文件的格式 1.可执行程序是怎么来的 C代码—>预处理—>汇编代码—>目标代码—>可执行文件 .asm汇编代码 .o目标码 ...
- LINUX内核分析第八周学习总结——进程的切换和系统的一般执行过程
LINUX内核分析第八周学习总结——进程的切换和系统的一般执行过程 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/c ...
- Linux内核分析 第七周 可执行程序的装载
张嘉琪 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 Linux内核分析 第七 ...
- LINUX内核分析第六周学习总结——进程的描述与创建
LINUX内核分析第六周学习总结--进程的描述与创建 标签(空格分隔): 20135321余佳源 余佳源 原创作品转载请注明出处 <Linux内核分析>MOOC课程 http://mooc ...
- LINUX内核分析第五周学习总结——扒开系统调用的“三层皮”(下)
LINUX内核分析第五周学习总结--扒开系统调用的"三层皮"(下) 标签(空格分隔): 20135321余佳源 余佳源 原创作品转载请注明出处 <Linux内核分析>M ...
- LINUX内核分析第八周学习总结
LINUX内核分析第八周学习总结 标签(空格分隔): 20135328陈都 陈都 原创作品转载请注明出处 <Linux内核分析>MOOC课程 http://mooc.study.163.c ...
随机推荐
- 数论 UVA 11752
题目大意是在1~2^64-1的范围内找到所有符合条件的数,条件要求这个数字是两个或两个以上不同数字的幂,例如64=8^2=4^3. 对于这一题,分析是:如果一个满足这个条件的数字一定可以转换成i^k, ...
- 1. webservice在输入命令的时候wsimport的时候会出现如下错误: wsimport不是内部或者外部命令。 2. javac不是内部或者外部命令 3 java 就可以显示配置成功。
问题: webservice在输入命令的时候wsimport的时候会出现如下错误: wsimport不是内部或者外部命令. javac不是内部或者外部命令 3 java 就可以显示配置成功. 网上搜了 ...
- [原创]cocos2d-x研习录-第二阶 基本框架
了解完Cocos2D-x的基本概念和概念类之后,是不是有一种蠢蠢欲动的冲动,想要探究Cocos2D-x是如何完成这一切的.接着我将通过对Cocos2D-x自代的HelloCpp项目进行分析,初步了解C ...
- (转)神经网络和深度学习简史(第一部分):从感知机到BP算法
深度|神经网络和深度学习简史(第一部分):从感知机到BP算法 2016-01-23 机器之心 来自Andrey Kurenkov 作者:Andrey Kurenkov 机器之心编译出品 参与:chen ...
- libvirt
http://libvirt.org/index.html libvirt supports: The KVM/QEMU Linux hypervisor The Xen hypervisor on ...
- python 之post、get与cookie实战
项目名称:登陆考勤管理系统爬取个人考勤信息并写入excel表格 编写目的: 公司经常要统计员工的考勤信息,而员工每次都要登陆考勤系统,再复制相关信息出来,贴到EXCEL,再转给统计人员,统计人员再挨个 ...
- 在package.json里面的script设置环境变量,区分开发及生产环境。注意mac与windows的设置方式不一样
在package.json里面的script设置环境变量,区分开发及生产环境. 注意mac与windows的设置方式不一样. "scripts": { "publish- ...
- 在word里插入图片,并设置图片的格式
由于公司业务需要,需要在生成的word里插入图片(公司印章),仔细想了下,还是在word模板里添加一个书签,然后再该书签的位置插入图片,并设置图片的格式方便些: 代码如下: using System; ...
- LeetCode "Design Tic-Tac-Toe"
We don't have to keep a complete chess board.. just counters! class TicTacToe { vector<int> cn ...
- 黄聪:WordPress: 使用 wp_insert_attachment 上传附件
我们来研究一下如何在主题或者插件的开发中上传附件: 主要参考这个函数:https://codex.wordpress.org/Function_Reference/wp_insert_attachme ...