LINUX内核分析第七周学习总结:可执行程序的装载

一、得到一个可执行程序

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
    命令行参数和环境串都放在用户态堆栈中

初始化新程序堆栈时拷贝进去

  1. 可执行程序动态链接

(1)动态链接

实际上,装载过程是一个广度遍历,遍历的对象是“依赖树”。

主要过程是动态链接器完成、用户态完成。

(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

  1. 动态链接的可执行程序的装载

(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的控制权交给可执行程序
动态链接的过程主要是动态链接器在起作用,而不是内核完成的。

四,实验

初始化环境:

跟踪:

五,感想

本周学习的内容上学期在娄老师中有所涉及,所以还比较亲切,可执行文件的生成的过程,还学习了静态库,动态库。其中预处理,编译成汇编指令,变成二进制代码,最后执行可执行文件这四步已经深深的印入我们的脑海。

时间: 2024-07-31 14:33:42

LINUX内核分析第七周学习总结:可执行程序的装载的相关文章

LINUX内核分析第七周学习总结——可执行程序的装载

LINUX内核分析第六周学习总结——进程的描述和进程的创建 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.知识概要 (一)预处理.编译.链接和目标文件的格式 1.可执行程序是怎么得来的 2.目标文件的格式ELF 3.静态链接的ELF可执行文件和进程的地址空间 (二)可执行程序.共享库和动态加载 1.装载可执行程序之前的工作 2.装载时动态链接和运行时动态链接应用举例 (三)

Linux内核分析——第七周学习笔记20135308

第七周 可执行程序的装载 一.预处理.编译.链接和目标文件的格式 1.可执行程序是怎么来的 C代码—>预处理—>汇编代码—>目标代码—>可执行文件 .asm汇编代码 .o目标码 a.out可执行文件 预处理负责把include的文件包含进来及宏替换工作. 2.目标文件的格式ELF (1)常见的ELF格式文件: (2)ABI——应用程序二进制接口 在目标文件中,他已经是二进制兼容,即适应二进制指令. (3)ELF中三种目标文件: 一个可重定位(relocatable)文件保存着代码和

LINUX内核分析第六周学习总结——进程的描述和进程的创建

LINUX内核分析第六周学习总结——进程的描述和进程的创建 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.知识概要 进程的描述 进程描述符task_struct数据结构(一) 进程描述符task_struct数据结构(二) 进程的创建 进程的创建概览及fork一个进程的用户态代码 理解进程创建过程复杂代码的方法 浏览进程创建过程相关的关键代码 创建的新进程是从哪里开始执行的

LINUX内核分析第三周学习总结——构造一个简单的Linux系统MenuOS

LINUX内核分析第三周学习总结——构造一个简单的Linux系统MenuOS 黄韧(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 回顾: [计算机三个法宝] 1)存储程序计算机 2)函数调用堆栈 3)中断 [操作系统两把宝剑] 1)中断上下文的切换:保存现场和恢复现场 2)进程上下文的切换 一.使用gdb跟踪调试内核从start_kernel到init进程启动 使用实验楼的虚拟机打开

LINUX内核分析第八周学习总结——进程的切换和系统的一般执行过程

LINUX内核分析第八周学习总结——进程的切换和系统的一般执行过程 黄韧(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.知识概要 Linux中进程调度的基本概念与相关知识 schedule函数如何实现进程调度 Linux进程的执行过程(一般情况与特殊情况) 宏观描述Linux系统执行 二.学习笔记 (一)进程切换的关键代码switch_to分析 进程进度与进程调度的时机分析 1.

《Linux内核分析》第一周学习小结 计算机是如何工作的?

<Linux内核分析>第一周.计算机是如何工作的? 20135204 郝智宇  一.存储程序计算机工作模型 1.      冯诺依曼体系结构: 数字计算机的数制采用二进制:计算机应该按照程序顺序执行. 内存保存指令和数据,CPU负责解释和执行这些指令. 2.    API: 程序员与计算机的接口界面. 3.    EIP: CALL  RET  IMP   二.X86汇编基础 1.      X86CPU的寄存器 E开头32位,R开头64位.   堆栈是计算机中非常基础性的东西. 2.    

20135327郭皓--Linux内核分析第七周 可执行程序的装载

第七周 可执行程序的装载 郭皓 原创作品转载请注明出处 <Linux内核分析>MOOC课程 http://mooc.study.163.com/course/USTC-1000029000 一.预处理,编译,链接和目标文件格式 1.可执行程序是怎么得来的 c代码->预处理->汇编代码->汇编器->目标代码->链接成可执行文件->加载到内核执行 2.目标文件的格式ELF 符号修饰标准.变量内层布局.函数调用方式等这些跟可执行代码二进制兼容性相关的内容称为ABI

&quot;Linux内核分析&quot;第七周

可执行程序的装载 张文俊+原创作品转载请注明出处+<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.预处理.编译.链接和目标文件的格式 可执行程序是怎么得来的? 首先,编译器预处理:1.将头文件加载进来:2.将宏替换 gcc -E -o hello.cpp hello.c (-m32)//c预处理成cpp文件 第二步,将cpp(预处理后的文件)编译成汇编代码 gcc -x cpp-output -S -o h

Linux内核分析第七周作业

Linux内核如何装载和启动一个可执行程序 有了上次的教训,这次直接用vmware完成 (- ̄3 ̄)- 先观察MenuOS新增的函数 1 int Exec(int argc, char *argv[]) 2 { 3 int pid; 4 /* fork another process */ 5 pid = fork(); 6 if (pid < 0) 7 { 8 /* error occurred */ 9 fprintf(stderr,"Fork Failed!"); 10 e