首页 > 代码库 > 实验七 可执行程序的装载

实验七 可执行程序的装载

王康 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

1,预处理,编译,链接和目标文件的格式:

1,

技术分享

技术分享

预处理为cpp预处理文件---hello.s汇编代码--hello.o二进制目标文件(已经含有了机器指令)--链接为可执行文件如下图,hello为二进制文件(hello和hello.o都是ELF格式文件)

技术分享

均是使用共享库的,如print使用lib.c

如果静态编译就加一个-static,是把所有需要的依赖文件都放入文件内部:从文件大小可以看出

技术分享,2,文件格式

技术分享

技术分享

目标文件一般也叫abi,已经是二进制兼容的格式(兼容指已经适用到了某一种cpu体系结构)

ELF主要有3种主要文件:

技术分享

第一个是.o文件,第二个就是操作系统如何加载可执行文件以及从哪里开始执行

技术分享

很多section和segment;header保存roadmap

技术分享

TYPE指明是三种ELF文件中哪一种;入口地址;最后是数据代码段的源数据

技术分享

Text segment拷贝到进程地址空间时,要映射拷贝到进程的0x8048100,这样可执行文件和进程地址空间就有了映射关系。

技术分享

技术分享

x86是4G进程地址空间,最上1G是内核用,0xc000之上的是内核访问,之下就是用户可访问;

一般来讲头部大小入口点位置是0x8048到0x8048300,启动一个刚加载过新的可执行文件进程就是从这里开始执行(因为不同于fork的只是从原进程开始执行);

技术分享

技术分享

技术分享

2,可执行程序,共享库和动态链接

技术分享shell所做的工作:

技术分享

技术分享

实际ls就是一个可执行程序;envp还可接收shell的环境变量

技术分享

为了防止shell被覆盖所以先fork一个子进程,然后execlp加载可执行程序ls,这里环境变量是NULL(取决于你main函数接收与否环境变量)。

技术分享

技术分享

fork一个子进程完全复制父进程,调用exec时候要加载的可执行程序把原来进程堆栈覆盖掉,用户态堆栈也被清空,那么argv和envp是如何进入新的可执行程序堆栈呢?

技术分享

传给内核处理函数execve后,内核处理函数会在创建一个新的可执行程序用户态堆栈时会帮我们拷贝到新的可执行程序的上下文环境。

技术分享

即先execve再sys_execve

技术分享

技术分享

.so文件(windows为.dll),对于动态链接库来讲因为实现都是一样的如上图两种编译,所以在装载和运行时都可以加载它。

技术分享

技术分享

技术分享

技术分享

技术分享

dlfcn是动态加载

技术分享

加载动态装载库就需要dlopen,然后声明一个函数指针,根据函数名找到然后使用func()

技术分享

编译执行main:

技术分享

-L指明.h文件目录,-l指明库名;export把当前目录加入,否则要放入默认的lib和usrlib下;

-ldl动态加载器加载进来

3,可执行程序的装载

1,技术分享

可执行程序装载也是系统调用,但是execve内核处理过程和fork一样比较特殊。(fork两次返回,子进程从特定的ret_from_fork开始执行然后返回用户态;执行execve陷入内核态,加载可执行文件,覆盖当前进程的可执行程序覆盖掉,当execve返回已经不是原来的可执行而是新的可执行程序了,执行起点也到了main)

技术分享

技术分享

最后根据给出的可执行文件,search_binary_hadler加载文件头部,需要能够解析文件格式例如ELF格式的内核模块

技术分享

fmt是链表一个节点,可以load_binary(bprm)解析ELF文件位置

技术分享

load_binary对应的函数是全局变量elf_format,把load_elf_binary赋给了函数指针load_binary(结构体的一个成员);再init_elf_binfmt,把elf_format变量注册进了内核链表&fmt

当出现ELF格式文件时候,观察者可以自动执行

技术分享

被观察者

技术分享

技术分享

这里有一个start_thread:

技术分享

pt_regs

技术分享

用newip替换掉,这个newip是如何来的呢?

技术分享

对于静态文件,elf_entry就是可执行文件格式头里的entry

技术分享

2,sys_execve的内部处理过程

技术分享

有获得文件名和参数,环境变量

技术分享

技术分享

技术分享

技术分享

创建了结构体bprm,还把环境变量和参数copy进去

技术分享

之后是可执行文件的处理过程:

技术分享

技术分享

寻找可执行文件处理函数

技术分享

技术分享

下边是赋值的地方,结构体变量elf_format加载到链表register

技术分享

技术分享

技术分享

技术分享

技术分享

interpreter如果需要动态链接,那么会加载interp动态链接器的起点,把它作为文件起点;

如果是静态链接直接把elf_entry作为起点

技术分享

所以start_thread有2种可能

技术分享

技术分享

所以start_kernel作用就是把下一条的0x80位置变成我们新加载可执行文件的entry的位置

3,

技术分享

技术分享

技术分享

技术分享

在makefile中:编译了hello.c,然后把hello和init都放入rootfs里了,就自动加载hello可执行文件

技术分享

技术分享

下面开始跟踪:

技术分享

技术分享

发现执行到chile process停止了,

技术分享

技术分享技术分享

技术分享

技术分享

发现newip和hello的entry地址是一样的

技术分享

4,

技术分享

技术分享技术分享

技术分享

技术分享

技术分享

返回的就是lld的地址了

技术分享

一般是广度遍历

技术分享

4,实验

技术分享

技术分享

技术分享

技术分享

5,总结

1、可执行程序的产生:C语言代码-->编译器预处理-->编译成汇编代码-->汇编器编译成目标代码-->链接成可执行文件,再由操作系统加载到内存中执行。

2、ELF格式中主要有3种可执行文件:可重定位文件.o,可执行文件,共享目标文件。

3、ELF可执行文件会被默认映射到0x8048000这个地址。

4、命令行参数和环境变量是如何进入新程序的堆栈的?

Shell程序-->execve-->sys_execve,然后在初始化新程序堆栈时拷贝进去。

先函数调用参数传递,再系统调用参数传递。

5、当前程序执行到execve系统调用时陷入内核态,在内核中用execve加载可执行文件,把当前进程的可执行文件覆盖掉,execve系统调用返回到新的可执行程序的起点。

6、动态链接库的装载过程是一个图的遍历过程,

ELF格式中的.interp和.dynamic需要依赖动态链接器来解析,entry返回到用户态时不是返回到可执行程序规定的起点,返回到动态链接器的程序入口。

实验七 可执行程序的装载