百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 优雅编程 > 正文

Linux系统下ELF文件装入与执行过程分析

sinye56 2024-11-27 20:32 1 浏览 0 评论

Linux系统下ELF文件装入与执行过程分析

平时在linux控制台下执行gcc编译过的文件时,经常在想:为什么下./hello后程序会自动运行?这个elf可执行文件是如何装载到内存中的? 运行的流程是什么样的呢? 建议作为一名程序员还是需要从原理上了解其执行过程,对于以后问题的排查一定的帮助作用。

Linux系统下ELF文件的装载和执行过程是一个比较漫长和复杂的过程,涉及到用户空间和内核空间的工作和切换,以下以在bash控制台下运行hello程序为为例具体分析其流程:

#./hello

1. bash进程启动shell做如下几件事:

---命令解析:Shell会解析你输入的命令,包括带的参数;

---程序查找:Shell会在文件系统中指定路径下查找对应的程序文件;

---权限检查:Shell会检查文件是否有执行该程序的权限。如果你没有执行权限,shell将不会启动该程序

2. 通过检查后,bash进程调用fork()系统调用创建一个新的进程;

可以查看bash源码execute_cmd.c中,流程如下:

execute_command -->execute_simple_command --->fork 创建新的子进程用于来处理程序;

3. 新的进程调用execve()系统调用执行指定路径的ELF文件

1) 新的进程调用execve()系统调用执行指定路径的ELF文件,原先的bash进程继续返回等待刚才启动的新进程结束,然后继续等待用户输入命令。

2)execve()系统调用相应的入口是sys_execve(),sys_execve()进行一些参数的检查复制后,调用do_execve(),最终会调用到通用函数do_execveat_comman。

3)大致的函数调用流程如下(先有个大概的了解):

4. 具体的系统调用流程: ----kernel/fs/exec.c

sys_execve -->do_execve() --->do_execveat_common 最终都会调用到这里:

---file = do_open_execat(fd, filename, flags); //打开指定路径的elf文件

---retval = bprm_mm_initibprm); //初始化二进制elf加载器

---retval = prepare_binprm(bprm); //填充bin的binprm参数,权限检查,读取文件头的128个字节

---retval = copy_strings_kernel(1, &bprm->filename, bprm); //将文件名从用户空间复制到内核空间

---retval = copy_strings(bprm->envc, envp, bprm); //将用户空间的环境变量复制到内核空间中

---retval = copy_strings(bprm->argc, argv, bprm); ////将用户空间的运行命令行参数复制到内核空间中

---would_dump(bprm, bprm->file); //不太清楚,应该与权限处理有关

---retval = exec_binprm(bprm); //执行可执行文件,并进行文件格式的遍历

5. 轮询所有注册过的文件格式(如:elf,a.out, flat,script等)加载适合的elf载器;

inux提供来了一种可执行文件类型的注册机制,核心数据结构是struct linux_binfmt :

1)a.out文件格式;

2)elf文件格式:

所有的可执行文件格式通过register函数注册后信息都存放在formats全局结构体中进行管理;

3)script脚本程序:

retval = fmt->load_binary(bprm);

search_binary_handle() 通过魔数确定文件格式,并调用相应的装载过程;如果魔数不匹配,直接返回尝试下一个;

6. ELF文件解析和加载(重点分析!!) load_elf_binary()----/fs/binfmt_elf.c中

具体的elf文件格式说明请参考另一篇文章介绍说明:

stepwalker:Linux系统下ELF文件格式学习笔记

---概括起来可以分以下几步:

1).读取并检查目标可执行程序的头信息,检查完成后加载目标程序的程序头表;

2).如果需要解释器则读取并检查解释器的头信息,检查完成后加载解释器的程序头表;

3).装入目标程序的段segment, 这是目标程序二进制代码中的真正可执行映像;

4).修改程序的入口地址,如果有解释器则填入解释器的入口地址, 否则直接填入可执行程序的入口地址;

5). 调用create_elf_tables填写目标文件的参数环境变量等必要信息;

6). start_kernel宏准备进入新的程序入口;


---详细分析如下:

1)先读取elf文件头数据:

loc->elf_ex = *((struct elfhdr *)bprm->buf);

2)检查ELF文件的前4个字节魔数是否匹配;

if (memcmp(loc->elf_ex.e_ident, ELFMAG, SELFMAG) != 0)

3)检查ELF文件类型是否为ET_EXEC和ET_DYN(可执行和动态库);

if (loc->elf_ex.e_type != ET_EXEC && loc->elf_ex.e_type != ET_DYN)

4)检查支持的系统架构:

if (!elf_check_arch(&loc->elf_ex))

5)检查文件内存映射的函数是否存在,如果文件属于ext文件系统,那么该函数指针最终指向generic_file_mmap()函数;

if (!bprm->file->f_op->mmap)

6)再读取程序头数据:

elf_phdata = load_elf_phdrs(&loc->elf_ex, bprm->file);

7)寻找和处理PT_INTERP解释器段(动态链接相关)

---分配虚拟内存空间,大小为动态库路径的字符长度;

elf_interpreter = kmalloc(elf_ppnt->p_filesz, GFP_KERNEL);

---从elf文件中读取对应的字段;

kernel_read(bprm->file, elf_ppnt->p_offset, elf_interpreter, elf_ppnt->p_filesz);

---打开动态库文件:

interpreter = open_exec(elf_interpreter);

---把动态库数据填充到loc结构体中的interp_elf_ex地址待用;

retval = kernel_read(interpreter, 0,

(void *)&loc->interp_elf_ex,

sizeof(loc->interp_elf_ex));

8)检查并读取解释器的程序表头:

9)设置虚拟内存空间中的内存映射区,并初始化栈地址,设置栈底;

至此我们已经把目标执行程序和其所需要的解释器加载初始化,并且完成检查工作,也加载了程序头表program header table,下面开始加载程序的段信息;

10)装入目标程序的段segment

这段代码以目标映像的程序头中搜索类型为PT LOAD的段Sement) ,在二进制像中,只有类型为PT LOAD的段才是需要装入的,当然在装入之前,需要确定装入的地址,只要考虑的就是页面对齐,还有该段的D vadd域的值(上面省路汶部分内容),确定了装入地后,就通过e ma 建立用户空拟地让空间与目标决文件中某个连续区间之间的映射,其返回值就是实际映射的起始地址。

---搜索需要装入的PT_LOAD段 :

---检查地址和页面的信息,包括虚拟地址和大小;

---虚拟地址空间与目标文件的映射确定了装入地址后,就通过elf map()建立用户空间虚拟地址空间与目标映像文件中某个连续区间之间的映射,其返回值就是实际映射的起始地址

error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt, elf_prot, elf_flags, total_size);

---检查代码段和数据段的大小 有没有超过内存大小,必须保证映射的内存大小大于合并的段大小;

----调用set_brk可以有效地映射我们需要的页面,用于BSS和break部分

retval = set_brk(elf_bss, elf_brk, bss_prot);

10)装载动态库:

---如果需要装入解释器,就通过load_elf_intep装入其映像,并把将来进入用户空间的入口地址设置成ad ef intep的返回值,即解释器映像的入口地址;

---若不装入解释器,那么这个入口地址就是目标映像本身的入口地址

以下即为整个文件装载到进程中的内存映射区的布局图:

11)填写目标文件的参数环境变量等必要信息:

在完成装入,启动用户空间的映像运行之前,还需要为目标映像和解释器准备好一些有关的信息,这些信息包括常规的agcEVC等等,还有一些”辅助向量(AuxilaryVector)"这些信息需要复制到用户空间, 使它们在CPU进入解释器或目标映像的程序入口时出现在用户空间堆找上,这里的就通过load_elf_intep就起着这作用;

12)启动start_thread宏准备进入新的程序入,

start_thread(regs, elf_entry, bprm->p);这个函数操作会将eip和esp改成新的地址,

就使得CPU在返回用户空间时就进入新的程序入口。如果存在动态库镜像,那么这就是动态库镜像的

入口地址,否则就是目标镜像文件的程序入口;

regs参数对应的内容及栈顺序如下图:

7. 调用结束,返回用户态:

上述步骤执行完,返回do_execve再返回至sys_execve()时,系统调用的返回地址改成了被装载的ELF程序的入口地址了,所以当sys_execve()系统调用从内核态返回到用户态时,EIP寄存器直接跳转到了ELF程序的入口地址,于是新的程序开始执行,ELF可执行文件装载完成。

补充说明:

1)bash进程:就是shell的进程,每一个已登录的用户都有bash这个进程,当用户在终端上面登录后,Linux系统就会给这个用户一个shell交互框,这个shell就是bash进程;

2)如何判断是否有动态链镜像?

如果目标镜像与各种库的链接是静态链接,就无需依靠动态共享库,那就不需要解释器镜像; 否则就一定要有解释器映像存在。也可通过ldd命令查看关联的动态库。

相关推荐

Linux基础知识之修改root用户密码

现象:Linux修改密码出现:Authenticationtokenmanipulationerror。故障解决办法:进入单用户,执行pwconv,再执行passwdroot。...

Linux如何修改远程访问端口

对于Linux服务器而言,其默认的远程访问端口为22。但是,出于安全方面的考虑,一般都会修改该端口。下面我来简答介绍一下如何修改Linux服务器默认的远程访问端口。对于默认端口而言,其相关的配置位于/...

如何批量更改文件的权限

如果你发觉一个目录结构下的大量文件权限(读、写、可执行)很乱时,可以执行以下两个命令批量修正:批量修改文件夹的权限chmod755-Rdir_name批量修改文件的权限finddir_nam...

CentOS「linux」学习笔记10:修改文件和目录权限

?linux基础操作:主要介绍了修改文件和目录的权限及chown和chgrp高级用法6.chmod修改权限1:字母方式[修改文件或目录的权限]u代表所属者,g代表所属组,o代表其他组的用户,a代表所有...

Linux下更改串口的权限

问题描述我在Ubuntu中使用ArduinoIDE,并且遇到串口问题。它过去一直有效,但由于可能不必要的原因,我觉得有必要将一些文件的所有权从root所有权更改为我的用户所有权。...

Linux chown命令:修改文件和目录的所有者和所属组

chown命令,可以认为是"changeowner"的缩写,主要用于修改文件(或目录)的所有者,除此之外,这个命令也可以修改文件(或目录)的所属组。当只需要修改所有者时,可使用...

chmod修改文件夹及子目录权限的方法

chmod修改文件夹及子目录权限的方法打开终端进入你需要修改的目录然后执行下面这条命令chmod777*-R全部子目录及文件权限改为777查看linux文件的权限:ls-l文件名称查看li...

Android 修改隐藏设置项权限

在Android系统中,修改某些隐藏设置项或权限通常涉及到系统级别的操作,尤其是针对非标准的、未在常规用户界面显示的高级选项。这些隐藏设置往往与隐私保护、安全相关的特殊功能有关,或者涉及开发者选项、权...

完蛋了!我不小心把Linux所有的文件权限修改了!在线等修复!

最近一个客户在群里说他一不小心把某台业务服务器的根目录权限给改了,本来想修改当前目录,结果执行成了根目录。...

linux改变安全性设置-改变所属关系

CentOS7.3学习笔记总结(五十八)-改变安全性设置-改变所属关系在以前的文章里,我介绍过linux文件权限,感兴趣的朋友可以关注我,阅读一下这篇文章。这里我们不在做过的介绍,注重介绍改变文件或者...

Python基础到实战一飞冲天(一)--linux基础(七)修改权限chmod

#07_Python基础到实战一飞冲天(一)--linux基础(七)--修改权限chmod-root-groupadd-groupdel-chgrp-username-passwd...

linux更改用户权限为root权限方法大全

背景在使用linux系统时,经常会遇到需要修改用户权限为root权限。通过修改用户所属群组groupid为root,此操作只能使普通用户实现享有部分root权限,普通用户仍不能像root用户一样享有超...

怎么用ip命令在linux中添加路由表项?

在Linux中添加路由表项,可以使用ip命令的route子命令。添加路由表项的基本语法如下:sudoiprouteadd<network>via<gateway>这...

Linux配置网络

1、网卡名配置相关文件回到顶部网卡名命名规则文件:/etc/udev/rules.d/70-persistent-net.rules#PCIdevice0x8086:0x100f(e1000)...

Linux系列---网络配置文件

1.网卡配置文件在/etc/sysconfig/network-scripts/下:[root@oldboynetwork-scripts]#ls/etc/sysconfig/network-s...

取消回复欢迎 发表评论: