一.ELF文件格式

ELF(Executable and Linking Format)是x86 Linux系统下常用的目标文件格式,有三种主要类型:

  • 适于连接的可重定位文件,可与其他目标文件一起创建可执行文件和共享目标文件。
  • 适于执行的可执行文件,用于提供程序的进程映像,加载的内存执行。
  • 共享目标文件,连接器可将它与其他可重定位文件和共享目标文件连接成其他目标文件。

文件格式

ELF header在文件开始处描述了整个文件的组织,Section提供了目标文件的各项信息,Program header table指出怎样创建进程映像,含有每个program header的入口,section header table包含每个Section的入口,给出名字、大小等信息。

二.ELF文件的加载过程

从编译/链接和运行的角度看,应用程序和库程序的连接有两种方式。一种是固定的、静态的链接,将所需要的库函数的目标代码从程序库中抽取出来,链接进应用软件的目标映像中;另一种是动态链接,库函数的代码不进入应用软件的目标映像,而是将函数库的映像也交给用户,到启动应用软件时才把程序库的映像装入用户空间。

Linux内核既支持静态链接的ELF映像,也支持动态链接的ELF映像,而且装入/启动映像必须由内核完成,而动态链接的实现既可以在内核中完成,也可以在用户空间完成。

内核空间的加载过程

  内核中实际执行execve()系统调用的程序do_execve(),这个函数先打开目标文件映像,并从读入目标文件的头部(即ELF头部字段),然后调用另一个函数seach_binary_handler(),在此函数里,它会搜索Linux可支持的可执行文件类型队列,寻找与之匹配的可执行程序的处理程序。如果类型匹配,则调用load_binary函数指针所指向的处理函数来处理目标映像文件。对于ELF文件格式中,处理函数是load_elf_binary函数。

内核对所支持的每种可执行的程序类型都有个struct linux_binfmt的数据结构。定义如下:

struct linux_binfmt{
struct linux_binfmt* next;
struct module* module;
int (*load_binary)(struct linux_binprm*,struct pt_regs* regs);
int (*load_shlib)(struct file*);
int (*core_dump)(long signr,struct pt_regs* regs,struct file* file);
unsigned long min_coredump;
int hasvdso;
}

其中load_binary函数指针指向的就是一个可执行程序的处理函数。

ELF文件格式的定义如下:

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,
.hasvdso =
};

search_binary_handler寻找文件格式对应的解析模块,如下:

.....
list_for_each_entry(fmt, &formats, lh) {
if (!try_module_get(fmt->module))
continue;
read_unlock(&binfmt_lock);
bprm->recursion_depth++;
retval = fmt->load_binary(bprm);
read_lock(&binfmt_lock);
put_binfmt(fmt);
bprm->recursion_depth--;
if (retval < && !bprm->mm) {
/* we got to flush_old_exec() and failed after it */
read_unlock(&binfmt_lock);
force_sigsegv(SIGSEGV, current);
return retval;
}
if (retval != -ENOEXEC || !bprm->file) {
read_unlock(&binfmt_lock);
return retval;
}
}
.....

load_elf_binary函数主要就是对ELF文件的解析过程了.

 elf_ppnt = elf_phdata;
……
for (i = ; i < loc->elf_ex.e_phnum; i++) {
if (elf_ppnt->p_type == PT_INTERP) {
……
elf_interpreter = kmalloc(elf_ppnt->p_filesz, GFP_KERNEL);
……
retval = kernel_read(bprm->file, elf_ppnt->p_offset,
elf_interpreter,
elf_ppnt->p_filesz);
……
interpreter = open_exec(elf_interpreter);
……
retval = kernel_read(interpreter, , bprm->buf,
BINPRM_BUF_SIZE);
……
/* Get the exec headers */
……
loc->interp_elf_ex = *((struct elfhdr *)bprm->buf);
break;
}
elf_ppnt++;
}

其中的for循环的目的在于寻找和处理目标映像的"解释器"段。“解释器"段的类型为PT_INTERP,读到后就根据其位置的p_offset和大小p_offsize把整个"解释器"的内容读入缓冲区,解释器的内容只是一个字符串,例如"/lib/ld-linux.so.2",然后就通过open_exec函数打开这个解释器文件。

814   for(i = , elf_ppnt = elf_phdata;
i < loc->elf_ex.e_phnum; i++, elf_ppnt++) {
……
if (elf_ppnt->p_type != PT_LOAD)
continue;
……
error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt,
elf_prot, elf_flags);
……
}

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

     if (elf_interpreter) {
……
elf_entry = load_elf_interp(&loc->interp_elf_ex,
interpreter,
&interp_load_addr);
……
} else {
elf_entry = loc->elf_ex.e_entry;
……
}

当是动态链接时,需要装入解释器,就通过load_elf_interp装入映像,返回解释器映像的入口地址。而对于静态链接时,则不需要装入解释器,那么这个入口地址就是目标映像本身的入口地址。

        create_elf_tables(bprm, &loc->elf_ex,
(interpreter_type == INTERPRETER_AOUT),
load_addr, interp_load_addr);
……
start_thread(regs, elf_entry, bprm->p);

在完成装入,启动用户空间的映像运行之前,还需要为目标映像和解释器准备好一些有关的信息,这些信息例如常规的argc、envc等,需要复制到用户空间,使它们进入解释器或目标映像的程序入口时出现在用户空间堆栈上。这就是create_elf_tables的作用。

最后,start_thread()这个宏操作会将eip和esp改成新的地址,就使CPU在返回用户空间时进入新的入口地址。

三.ELF文件加载和链接的实验总结

用户通过shell执行程序,shell通过execve进入系统调用.sys_execve经过一系列过程,并最终通过ELF文件的处理函数load_elf_binary将用户程序和ELF解释器加载进内存,并将控制权交给解释器。ELF解释器进行相关库的加载,并最终把控制权交给用户程序。

《Linux内核分析》 week8作业-Linux加载和启动一个可执行程序的更多相关文章

  1. linux内核裁剪及编译可加载模块

    一:linux内核裁剪: 1:编译内核源码: 今天的重点内容是内核驱动的编写,在编写驱动之前首先的了解linux内核源码,linux主要是由五个子系统组成:进程调度,内存管理,文件系统,网络接口以及进 ...

  2. 《linux内核分析》作业一:分析汇编代码

    通过汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的(王海宁) 姓名:王海宁                             学号:20135103 课程:<Linux内核分析& ...

  3. Linux内核和根文件系统引导加载程序

    续博文<u-boot之u-boot-2009.11启动过程分析> Linux内核启动及文件系统载入过程 当u-boot開始运行bootcmd命令.就进入Linux内核启动阶段,与u-boo ...

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

    韩玉琪 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.Linux内 ...

  5. 《Linux内核分析》第三周学习小结 构造一个简单的Linux系统OS

    郝智宇 无转载 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 第三周 构造一个简单的Linux系统Me ...

  6. 从内存中加载并启动一个exe

    windows似乎只提供了一种启动进程的方法:即必须从一个可执行文件中加载并启动.而下面这段代码就是提供一种可以直接从内存中启动一个exe的变通办法.用途嘛, 也许可以用来保护你的exe,你可以对要保 ...

  7. linux内核及其模块的查询,加载,卸载 lsusb等

    http://blog.sina.com.cn/s/blog_53e81e2a0100zkxi.html 1,/sbin/update-modules文件,他是一个linux通用的模块管理脚本程序. ...

  8. Linux内核分析——第一章 Linux内核简介

    第一章   Linux内核简介 一.Unix的历史 1.Unix系统成为一个强大.健壮和稳定的操作系统的根本原因: (1)简洁 (2)在Unix中,很多东西都被当做文件对待.这种抽象使对数据和对设备的 ...

  9. Linux内核分析笔记 与Linux内核开发理论

    http://www.cnblogs.com/hanyan225/category/308793.html

随机推荐

  1. 转载——web前端相关资源总结

    前端牛人博客:张克军.阮一峰.拔赤(李晶).拔赤(李晶)2.张鑫旭.梦想天空.阿当.泽飞.刘杰(嗷嗷).为之漫笔(李松峰).goddyzhao.hax的技术部落.周爱民.随网之舞.子鼠.司徒正美.ju ...

  2. angular-utils-pagination 使用案例

    angular-utils-pagination是基于angular,bootstrap,jquery的一个分页插件,详细介绍以及使用方法参照: Git:https://github.com/mich ...

  3. 其实,SSL也不是配通了就什么都不管的~~

    其中太多的中间人攻击需要去加强加固~~ 测试过A级是必须的!! https://www.ssllabs.com/ssltest/ 这网址两年前,我写过的哈

  4. ISO14443标准详细介绍

    这篇文章从各方面详细介绍了ISO/IEC14443标准.第一部分:物理特性1.范围ISO/IEC14443的这一部分规定了邻近卡(PICC)的物理特性.它应用于在耦合设备附近操作的ID-1型识别卡.I ...

  5. static用法一

    #include "stdafx.h" #include "string.h" struct student { int num; ]; char sex; } ...

  6. cppunit官方文档浅析

    使用doxygen生成官方文档 cppunit使用了doxygen作为它的文档建设工具,所以我们要找的“官方文档”,其实就在cppunit的代码里面. 请先参考博文<下载doxygen>( ...

  7. DOL版USB Loader的下载和运行

    下载 在Wii上面玩硬盘版游戏,自然少不了USB Loader,相关教程和下载资源在网上一搜就有. 我在官网(地址:http://gwht.wikidot.com/usb-loader)上找到了一个下 ...

  8. Intel 英特尔

    英特尔 英特尔 基本资料   公司名称:英特尔(集成电路公司)    外文名称:Intel Corporation(Integrated Electronics Corporation)    总部地 ...

  9. C#实现数据结构——线性表(上)

    什么是线性表 数据结构中最常用也最简单的应该就是线性表,它是一种线性结构(废话,不是线性结构怎么会叫线性表?当然不是废话,古人公孙龙就说白马非马,现代生物学家也说鲸鱼不是鱼). 那什么是线性结构? 按 ...

  10. fedora19安装jdk

    1.下载安装包 去oracle下载jdk-7u45-linux-i586.rpm 2. 更改权限 #chmod 777jdk-7u45-linux-i586.rpm 3. 安装 #rpm -ivh j ...