深入分析ELF文件结构及其载入过程

前言

一般程序符号和数据,包括:全局变量,静态全局变量,全局函数,静态全局函数,外部符号(函数/变量),局部变量,局部静态变量,字面量(常量)等。程序从源码(如:C语言)到ELF二进制可执行文件,一般需要通过编译器和链接器来处理并生产。

ELF文件由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)。实际上,一个文件中不一定包含全部内容,而且它们的位置也未必如同所示这样安排,只有ELF头的位置是固定的,其余各部分的位置、大小等信息由ELF头中的各项值来决定。

ELF目标文件类型

(1)可重定位的对象文件(Relocatable file)

Linux中.o文件。这类文件包含了代码和数据,可以用来链接生成可执行或共享目标文件,静态链接库也可以归为这一类。

(3)可执行的对象文件(Executable file)

ELF可执行文件。

(3)可共享库文件(Shared object file)

Linux中.so文件。这类文件可以跟其他的重定位文件和.so文件链接,产生新的.so文件。第二种是动态链接器可以将几个这种.so文件与可执行文件结合,作为进程映像的一部分来运行。

(4) Linux下的核心转存文件(Core Dump File)

当进程意外终止时,系统可以将该进程的地址空间的内容及终止时的一些其它信息转存到此Dump File。

以下面例子深入分析ELF

以下面的C程序为例:

#include <pthread.h>
#include <stdio.h>

const char *FLAG = "[INFO]";

char *infoprefixstr = "ThdID:";

const int const_num = 111;
int gbl_num = 222;

static void * static_func(){
    printf("static_func be called.\n");
    return NULL;
}

void *thread_start(void *args) {
    printf("%s%s%ld. const_num:%d. gbl_num:%d\n",
           FLAG, infoprefixstr, *((pthread_t *) args),
           const_num, gbl_num);
    return static_func();
}

int main(int argc, char **argv) {
    pthread_t thds[argc - 1];
    for (int i = 0; i < argc; i++) {
        pthread_create(&thds[i], NULL, &thread_start, &thds[i]);
    }
    for (int i = 0; i < argc; i++) {
        pthread_join(thds[i], NULL);
    }

    static int static_scope_var = 333;
    printf("main exitting......static_scope_var:%d\n",static_scope_var);
    return 0;
}

CMakeList.txt配置如下:

cmake_minimum_required(VERSION 3.15)
project(test1 C)

set(CMAKE_C_STANDARD 99)

add_executable(test1 main.c)
target_link_libraries(test1 PUBLIC -lpthread)

编译构建产生test1二进制程序。

详解file命令结果的各个部分

使用file命令查看test1的文件详情,得到如下结果:

$ file test1/cmake-build-debug/test1
test1/cmake-build-debug/test1: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=45d6523007a7906dfb699d5f6fc66f3f4b7ec720, with debug_info, not stripped

ELF 64-bit表示文件是64位ELF格式的。

LSB shared object表示ELF文件是一个共享对象。

注:“LSB executable”(ET_EXEC)和"LSB shared object"(ET_DYN)的区别是什么?

  • 在Linux内核/动态加载程序中ET_EXEC与ET_DYN的主要作用是通知可执行文件是否可以通过ASLR放置在随机存储器中。GCC在编译时,默认会增加-pie选项,使得生成的ELF是ET_DYN的。PIE可执行文件是DYN的,它们可以被地址随机化,就像共享库so一样。

注:-pie、-fpie、-fPIE、-fpie、fPIC的区别是什么?

  • -fPIE与-fpie是等价的。

  • -pie,往往和-fpie或-fPIE配合使用,用于在目标机器上生成与位置无关的可执行文件。-pie选项在链接时指定,-fpie或-fPIE选项在编译时指定。PIE(Position-Independent-Executable)是Binutils,glibc和gcc的一个功能,能用来创建能像共享库一样可重分配地址的程序,这种程序须连接到Scrt1.o。标准的可执行程序需要固定的地址,并且只有被装载到这个地址时,程序才能正确执行。PIE能使程序像共享库一样在主存任何位置装载,这需要将程序编译成位置无关,并链接为ELF共享对象。

  • -fpic,使用于在目标机支持编译共享库时使用。编译出的代码将通过全局偏移表(Global Offset Table)中的常数地址访存,动态装载器将在程序开始执行时解析GOT表项(注意,动态装载器操作系统的一部分,连接器是GCC的一部分)。而gcc中的-fPIC选项则是针对某些特殊机型做了特殊处理,比如适合动态链接并能避免超出GOT大小限制之类的错误。

  • -fPIC与-fpic都是在编译时加入的选项,用于生成位置无关的代码(Position-Independent-Code)。这两个选项都是可以使代码在加载到内存时使用相对地址,所有对固定地址的访问都通过全局偏移表(GOT)来实现。-fPIC和-fpic最大的区别在于是否对GOT的大小有限制。-fPIC对GOT表大小无限制,所以如果在不确定的情况下,使用-fPIC是更好的选择。

x86-64表示目标机CPU指令集架构。

version 1 (SYSV)表示操作系统和ABI标识符,ELF规范中包含如下几类:

Table 5. Operating System and ABI Identifiers, e_ident[EI_OSABI]
Name                 Value   Meaning
ELFOSABI_SYSV          0     System V ABI
ELFOSABI_HPUX          1     HP-UX operating system
ELFOSABI_STANDALONE   255    Standalone (embedded) application

dynamically linked表示ELF是动态链接的。

interpreter /lib64/ld-linux-x86-64.so.2表示程序的加载器。

for GNU/Linux 3.2.0表示操作系版本号。

BuildID[sha1]=45d6523007a7906dfb699d5f6fc66f3f4b7ec720表示文件的构建码。个人理解是m

with debug_info表示ELF文件带有debug信息。

not stripped表示保留ELF的所有符号表信息,未删除一些符号表信息。如果输出的是stripped表示已经删除了ELF中一些符号表信息。

注:一般编译出来的ELF中都有符号表(symbol table),该表中包括所有的符号(程序的入口点还有变量的地址等等)。这些符号表可以用 strip工具去除,这样的话这个文件就无法让debug程序跟踪了,但是会生成比较小的可执行文件。ELF可执行文件中的符号表可以部分去除,由于部分符号在加载运行时起着重要的作用,所以用strip永远不可能完全去除elf格式文件中的符号表。对未连接的目标文件来说如果用strip去掉符号表的话,会导致连接器无法连接。

ELF文件中除了包含指令、数据,还包括符号表、调试信息、字符串等,如果是可重定位对象文件还包含链接时所须的一些信息。一般目标文件将这些信息按不同的属性以Section(节)的形式存储,有时候也叫Segment(段),在一般情况下,它们都表示一个一定长度的区域,基本上不加以区别。后面将统一称为“段”。

ELF的文件结构

基本结构如下所示:

+====================+
+     ELF header     + // 包含了整个文件的基本属性,如:文件版本,目标机器型号,入口地址。
+====================+
+Program header table+ // 程序标头表是一组程序标头,它们定义了运行时程序的内存布局。对于.obj文件可选的
+====================+
+      .interp       + // 可执行文件所需要的动态链接器的位置。
+--------------------+
+   .note.ABI-tag    + // 用于声明ELF的预期运行时ABI。包括操作系统名称及其运行时版本。
+--------------------+
+ .note.gnu.build-id + // 表示唯一的构建ID位串。
+--------------------+
+     .gnu.hash      + // 符号hash表。若段名是.hash,则使用的是SYSV hash,其比gnu hash性能差。
+--------------------+
+      .dynsym       + // 动态符号表用来保存与动态链接相关的导入导出符号,不包括模块内部的符号。
+--------------------+
+      .dynstr       + // 动态符号字符串表,用于保存符号名的字符串表。静态链接时为.strtab。
+--------------------+
+    .gnu.version    + // 表中条目与.dynsym动态符号表相同。每个条目指定了相应动态符号定义或版本要求。
+--------------------+
+   .gnu.version_r   + // 版本定义。
+--------------------+
+     .rela.dyn      + // 包含共享库(PLT除外)所有部分的RELA类型重定位信息。
+--------------------+
+     .rela.plt      + // 包含共享库或动态链接的应用程序的PLT节的RELA类型重定位信息。
+--------------------+
+       .init        + // 程序初始化段。
+--------------------+
+        .plt        + // 过程链接表(Procedure Linkage Table),用来实现延迟绑定。
+--------------------+
+      .plt.got      + // 暂无。。。。。
+--------------------+
+       .text        + // 代码段
+--------------------+
+       .fini        + // 程序结束段
+--------------------+
+      .rodata       + // 只读变量(const修饰的)和字符串变量。
+--------------------+
+      .rodata1      + // 据我所知,.rodata和.rodata1是相同的。一些编译器会.rodata分为2个部分。
+--------------------+
+   .eh_frame_hdr    + // 包含指针和二分查找表,(一般在C++)运行时可以有效地从eh_frame中检索信息。
+--------------------+
+     .eh_frame      + // 它包含异常解除和源语言信息。此部分中每个条目都由单个CFI(呼叫帧信息)表示。
+--------------------+
+    .init_array     + // 包含指针指向了一些初始化代码。初始化代码一般是在main函数之前执行的。
+--------------------+
+    .fini_array     + // 包含指针指向了一些结束代码。结束代码一般是在main函数之后执行的。
+--------------------+
+      .dynamic      + // 保存动态链接器所需的基本信息。
+--------------------+
+        .got        + // 全局偏移表,存放所有对于外部变量引用的地址。
+--------------------+
+      .got.plt      + // 保存所有对于外部函数引用的地址。延迟绑定主要使用.got.plt表。
+--------------------+
+       .data        + // 全局变量和静态局部变量。
+--------------------+
+       .data1       + // 据我所知,.data和.data1是相同的。一些编译器会.data分为2个部分。
+--------------------+
+        .bss        + // 未初始化的全局变量和局部局部变量。
+--------------------+
+      .comment      + // 存放编译器版本信息
+--------------------+
+   .debug_aranges   + // 内存地址和编译之间的映射
+--------------------+
+    .debug_info     + // 包含DWARF调试信息项(DIE)的核心DWARF数据
+--------------------+
+   .debug_abbrev    + // .debug_info部分中使用的缩写
+--------------------+
+    .debug_line     + // 程序行号
+--------------------+
+     .debug_str     + // .debug_info使用的字符串表
+--------------------+
+      .symtab       + // 静态链接时的符号表,保存了所有关于该目标文件的符号的定义和引用。
+--------------------+
+      .strtab       + // 默认字符串表。
+--------------------+
+     .shstrtab      + // 字符串表。
+====================+
+Section header table+ // 用于引用Sections的位置和大小,并且主要用于链接和调试目的。对于Exec文件可选
+====================+

ELF知识扩展

关于ELF格式说明的更多信息,点击查看ELF SpecificationObject File Format

关于Program Header Table的更多信息,点击查看Program HeaderProgram Header Table

关于Section header table的更多信息,点击查看Section header table

关于.debug_xxx段的更多信息,点击查看[DWARF调试格式介绍](http://www.dwarfstd.org/doc/Debugging using DWARF-2012.pdf)。

Linux系统装载ELF的过程

用户层面

bash进程会调用fork()系统调用创建一个新的进程,然后在新的进程调用execve()系统调用执行指定的ELF文件。进入execve()系统调用之后,Linux内核就开始进行真正的装载工作。

系统层面

注:以下分析将使用linux-3.18.6的内核,其他版本大同小异。

在内核中execve()系统调用相应的入口是sys_execve(),它被定义在linux-3.18.6/include/linux/syscalls.h。sys_execve()函数将调用linux-3.18.6/fs/exec.c文件中第1430行的do_execve_common函数进行处理

1427 /*
1428  * sys_execve() executes a new program.
1429  */
1430 static int do_execve_common(struct filename *filename,
1431                 struct user_arg_ptr argv,
1432                 struct user_arg_ptr envp)
1433 {
...
1474     file = do_open_exec(filename); // 打开可执行文件
1475     retval = PTR_ERR(file);
1476     if (IS_ERR(file))
1477         goto out_unmark;
1478 
1479     sched_exec(); // 是一个宝贵的平衡机会,因为此时任务具有最小的有效内存和高速缓存占用空间。
1480 
1481     bprm->file = file;
1482     bprm->filename = bprm->interp = filename->name;
1483 
1484     retval = bprm_mm_init(bprm); // 创建一个新的mm_struct(将赋值给bprm->mm字段),并使用临时堆栈vm_area_struct填充它。 此时我们没有足够的上下文来设置堆栈标志,权限和偏移量,因此我们使用临时值。稍后将在setup_arg_pages()中对其进行更新。
1485     if (retval)
1486         goto out_unmark;
1487 
1488     bprm->argc = count(argv, MAX_ARG_STRINGS); // 参数个数
1489     if ((retval = bprm->argc) < 0)
1490         goto out;
1491 
1492     bprm->envc = count(envp, MAX_ARG_STRINGS); // 环境变量
1493     if ((retval = bprm->envc) < 0)
1494         goto out;
1495 
1496     retval = prepare_binprm(bprm); // 检查文件权限,并读取文件前128个byte确定文件格式和类型
1497     if (retval < 0)
1498         goto out;
...
1513     retval = exec_binprm(bprm); // 执行
1514     if (retval < 0)
1515         goto out;
...
1547 }

do_execve_common中1496行,将调用linux-3.18.6/fs/exec.c文件中prepare_binprm函数,读取文件首128个字节来判断文件格式。(注:每种可执行文件格式的开头几个字节都是很特殊的,特别是开头的魔数Magic Number,通过对魔数的判断可以确定文件的格式和类型。)如下:

1253 /*
1254  * Fill the binprm structure from the inode.
1255  * Check permissions, then read the first 128 (BINPRM_BUF_SIZE) bytes
1256  *
1257  * This may be called multiple times for binary chains (scripts for example).
1258  */
1259 int prepare_binprm(struct linux_binprm *bprm)
1260 {
1261     struct inode *inode = file_inode(bprm->file);
1262     umode_t mode = inode->i_mode;
1263     int retval;
1264 
1265 
1266     /* clear any previous set[ug]id data from a previous binary */
1267     bprm->cred->euid = current_euid(); // 清除之前的信任证
1268     bprm->cred->egid = current_egid(); // 清除之前的信任证
...
1292     /* fill in binprm security blob */
1293     retval = security_bprm_set_creds(bprm); // 设置安全信任证
1294     if (retval)
1295         return retval;
1296     bprm->cred_prepared = 1;
1297 
1298     memset(bprm->buf, 0, BINPRM_BUF_SIZE);
1299     return kernel_read(bprm->file, 0, bprm->buf, BINPRM_BUF_SIZE); // BINPRM_BUF_SIZE定义为128
1300 }

do_execve_common中1513行,调用exec_binprm函数执行文件。exec_binprm函数中1416行调用search_binary_handler函数,来搜索和匹配合适的可执行文件装载处理程序。

1405 static int exec_binprm(struct linux_binprm *bprm)
1406 {
1407     pid_t old_pid, old_vpid;
1408     int ret;
1409 
1410     /* 需要在load_binary更改之前获取pid */
1411     old_pid = current->pid;
1412     rcu_read_lock();
1413     old_vpid = task_pid_nr_ns(current, task_active_pid_ns(current->parent));
1414     rcu_read_unlock();
1415 
1416     ret = search_binary_handler(bprm); // 搜索和匹配合适的可执行文件装载处理过程。
1417     if (ret >= 0) {
1418         audit_bprm(bprm);
1419         trace_sched_process_exec(current, old_pid, bprm);
1420         ptrace_event(PTRACE_EVENT_EXEC, old_vpid);
1421         proc_exec_connector(current);
1422     }
1423 
1424     return ret;
1425 }

看一下search_binary_handler函数是如何搜索匹配,并执行加载的。search_binary_handler函数。

注意:

  • search_binary_handler函数第1369行中的formats是一个静态全局变量,formats是struct list_head类型。实际上formats的作用是一个列表的头,列表中每个struct linux_binfmt元素是经过register_binfmt/insert_binfmt函数注册/插入进列表的。linux-3.18.6内核版本中注册的文件加载器有:

    1. register_binfmt(&elf_fdpic_format); // 将fdpic二进制文件加载到内存。load an fdpic binary into various bits of memory
    2. register_binfmt(&aout_format); // 这些是用于加载a.out样式的可执行文件和共享库的函数。 在其他任何地方都没有二进制相关代码。These are the functions used to load a.out style executables and shared libraries. There is no binary dependent code anywhere else.
    3. register_binfmt(&elf_format); // 加载elf二进制文件。load elf binary
    4. register_binfmt(&em86_format); //
    5. register_binfmt(&som_format); // 这些是用于加载SOM可执行文件和共享库的功能。 在其他任何地方都没有二进制相关代码。These are the functions used to load SOM executables and shared libraries. There is no binary dependent code anywhere else.
    6. **register_binfmt(&script_format); ** // 加载脚本文件。load script file
    7. register_binfmt(&flat_format); // 这些是用于加载flat样式可执行文件和共享库的函数。 在其他任何地方都没有二进制相关代码。These are the functions used to load flat style executables and shared libraries. There is no binary dependent code anywhere else.

代码如下:

1349 /*
1350  * cycle the list of binary formats handler, until one recognizes the image
1351  */
1352 int search_binary_handler(struct linux_binprm *bprm)
1353 {
1354     bool need_retry = IS_ENABLED(CONFIG_MODULES);
1355     struct linux_binfmt *fmt;
...
1367  retry:
1368     read_lock(&binfmt_lock);
1369     list_for_each_entry(fmt, &formats, lh) { // 循环便利formats列表,fmt是每个元素的指针
1370         if (!try_module_get(fmt->module))
1371             continue;
1372         read_unlock(&binfmt_lock);
1373         bprm->recursion_depth++;
1374         retval = fmt->load_binary(bprm); // load_binary是struct linux_binfmt结构体中的一个成员,指定加载函数的指针。
1375         read_lock(&binfmt_lock);
...
1388     }
1389     read_unlock(&binfmt_lock);
...
1400 
1401     return retval;
1402 }
1403 EXPORT_SYMBOL(search_binary_handler);

最终search_binary_handler函数将在1374行调用./linux-3.18.6/fs/binfmt_elf.c文件中571行的load_elf_binary函数,load_elf_binary函数将对ELF文件进行装载。

load_elf_binary函数主要做的事情包括:

  1. 检查ELF可执行文件格式的有效性,比如魔数、程序头表中段(Segment)的数量。
  2. 寻找动态链接的.interp段,设置动态链接器路径(与动态链接器有关)。
  3. 根据ELF可执行文件的程序头表的描述,对ELF文件进行映射,比如代码、数据、只读数据。
  4. 初始化ELF进程环境,比如进程启动时EDX寄存器的地址应该是DT_FINI的地址(参照动态链接)。
  5. 将系统调用的返回地址修改成ELF可执行文件的入口点,这个入口点取决于程序的链接方式,对于静态链接的ELF可执行文件,这个程序入口就是ELF文件头中e_entry所指的地址;对于动态链接的ELF可执行文件,程序入口点是动态链接器。
 571 static int load_elf_binary(struct linux_binprm *bprm)
 572 {
...
 599     /* Get the exec-header */
 600     loc->elf_ex = *((struct elfhdr *)bprm->buf); // 获取ELF程序头部信息
 601 
 602     retval = -ENOEXEC;
 603     /* 一些简单的一致性检查 */
 604     if (memcmp(loc->elf_ex.e_ident, ELFMAG, SELFMAG) != 0)
 605         goto out;
 606 
 607     if (loc->elf_ex.e_type != ET_EXEC && loc->elf_ex.e_type != ET_DYN) // 类型检查
 608         goto out;
 609     if (!elf_check_arch(&loc->elf_ex)) // 指令集架构检查
 610         goto out;
 611     if (!bprm->file->f_op->mmap) // 与文件关联的mmap操作有效性检查
 612         goto out;
 613 
 614     /* 读取所有头部信息 */
 615     if (loc->elf_ex.e_phentsize != sizeof(struct elf_phdr))
 616         goto out;
 617     if (loc->elf_ex.e_phnum < 1 ||
 618         loc->elf_ex.e_phnum > 65536U / sizeof(struct elf_phdr))
 619         goto out;
 620     size = loc->elf_ex.e_phnum * sizeof(struct elf_phdr);
 621     retval = -ENOMEM;
 622     elf_phdata = kmalloc(size, GFP_KERNEL); // elf程序头表(program header table)数据
 623     if (!elf_phdata)
 624         goto out;
 625 
 626     retval = kernel_read(bprm->file, loc->elf_ex.e_phoff,
 627                  (char *)elf_phdata, size); // 读取elf程序头表数据
 628     if (retval != size) {
 629         if (retval >= 0)
 630             retval = -EIO;
 631         goto out_free_ph;
 632     }
 633 
 634     elf_ppnt = elf_phdata;
 635     elf_bss = 0;
 636     elf_brk = 0;
 637 
 638     start_code = ~0UL;
 639     end_code = 0;
 640     start_data = 0;
 641     end_data = 0;
 642 
 643     for (i = 0; i < loc->elf_ex.e_phnum; i++) {
 644         if (elf_ppnt->p_type == PT_INTERP) { // 寻找动态链接的.interp段
 645             /* This is the program interpreter used for
 646              * shared libraries - for now assume that this
 647              * is an a.out format binary
 648              */
 649             retval = -ENOEXEC;
 650             if (elf_ppnt->p_filesz > PATH_MAX ||
 651                 elf_ppnt->p_filesz < 2)
 652                 goto out_free_ph;
 653 
 654             retval = -ENOMEM;
 655             elf_interpreter = kmalloc(elf_ppnt->p_filesz,
 656                           GFP_KERNEL);
 657             if (!elf_interpreter)
 658                 goto out_free_ph;
 659 
 660             retval = kernel_read(bprm->file, elf_ppnt->p_offset,
 661                          elf_interpreter,
 662                          elf_ppnt->p_filesz); // 读取并设置动态链接器路径。
 663             if (retval != elf_ppnt->p_filesz) {
 664                 if (retval >= 0)
 665                     retval = -EIO;
 666                 goto out_free_interp;
 667             }
 668             /* make sure path is NULL terminated */
 669             retval = -ENOEXEC;
 670             if (elf_interpreter[elf_ppnt->p_filesz - 1] != '\0') // 检查动态链接器路径
 671                 goto out_free_interp;
 672 
 673             interpreter = open_exec(elf_interpreter); // 打开执行动态链接器
 674             retval = PTR_ERR(interpreter);
 675             if (IS_ERR(interpreter))
 676                 goto out_free_interp;
 677 
 678             /*
 679              * If the binary is not readable then enforce
 680              * mm->dumpable = 0 regardless of the interpreter's
 681              * permissions.
 682              */
 683             would_dump(bprm, interpreter); // 如果二进制文件不可读,则不管解释器的权限如何,都强制执行mm->dumpable = 0。
 684 
 685             retval = kernel_read(interpreter, 0, bprm->buf,
 686                          BINPRM_BUF_SIZE);
 687             if (retval != BINPRM_BUF_SIZE) {
 688                 if (retval >= 0)
 689                     retval = -EIO;
 690                 goto out_free_dentry;
 691             }
 692 
 693             /* 获取ELF Header */
 694             loc->interp_elf_ex = *((struct elfhdr *)bprm->buf);
 695             break;
 696         }
 697         elf_ppnt++;
 698     }
 699 
 700     elf_ppnt = elf_phdata;
 701     for (i = 0; i < loc->elf_ex.e_phnum; i++, elf_ppnt++)
 702         if (elf_ppnt->p_type == PT_GNU_STACK) { // 根据ELF文件中的GNU_STACK段的存在性,以及对应的标识位来确定是否要使栈可执行。
 703             if (elf_ppnt->p_flags & PF_X)
 704                 executable_stack = EXSTACK_ENABLE_X;
 705             else
 706                 executable_stack = EXSTACK_DISABLE_X;
 707             break;
 708         }
 709 
 710     /* 解释器的一些简单一致性检查 */
 711     if (elf_interpreter) {
 712         retval = -ELIBBAD;
 713         /* 检查不是ELFinterpreter */
 714         if (memcmp(loc->interp_elf_ex.e_ident, ELFMAG, SELFMAG) != 0)
 715             goto out_free_dentry;
 716         /* 验证解释器是否具有有效的ch */
 717         if (!elf_check_arch(&loc->interp_elf_ex))
 718             goto out_free_dentry;
 719     }
...
 746     /* 现在,我们通过一些繁琐的工作来将ELF文件映射到内存中的正确位置。
 747      */
 748     for(i = 0, elf_ppnt = elf_phdata;
 749         i < loc->elf_ex.e_phnum; i++, elf_ppnt++) {
 750         int elf_prot = 0, elf_flags;
 751         unsigned long k, vaddr;
 752 
 753         if (elf_ppnt->p_type != PT_LOAD) // 筛选出程序头中指定的可加载的段
 754             continue;
...
 791         vaddr = elf_ppnt->p_vaddr;
 792         if (loc->elf_ex.e_type == ET_EXEC || load_addr_set) {
 793             elf_flags |= MAP_FIXED;
 794         } else if (loc->elf_ex.e_type == ET_DYN) {
 795             /* Try and get dynamic programs out of the way of the
 796              * default mmap base, as well as whatever program they
 797              * might try to exec.  This is because the brk will
 798              * follow the loader, and is not movable.  */
 799 #ifdef CONFIG_ARCH_BINFMT_ELF_RANDOMIZE_PIE  // 使内存地址映射随机化
 800             /* Memory randomization might have been switched off
 801              * in runtime via sysctl or explicit setting of
 802              * personality flags.
 803              * If that is the case, retain the original non-zero
 804              * load_bias value in order to establish proper
 805              * non-randomized mappings.
 806              */
 807             if (current->flags & PF_RANDOMIZE)
 808                 load_bias = 0; // 装入的起点就是映像自己提供的地址vaddr。
 809             else
 810                 load_bias = ELF_PAGESTART(ELF_ET_DYN_BASE - vaddr);
 811 #else
 812             load_bias = ELF_PAGESTART(ELF_ET_DYN_BASE - vaddr);
 813 #endif
 814         }
 815 
 816         error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt,
 817                 elf_prot, elf_flags, 0); // 建立用户空间虚存区间与目标映像文件中某个连续区间之间的映射。
 818         if (BAD_ADDR(error)) {
 819             retval = IS_ERR((void *)error) ?
 820                 PTR_ERR((void*)error) : -EINVAL;
 821             goto out_free_dentry;
 822         }
...
 864     }
 865 
 866     loc->elf_ex.e_entry += load_bias;
 867     elf_bss += load_bias;
 868     elf_brk += load_bias;
 869     start_code += load_bias;
 870     end_code += load_bias;
 871     start_data += load_bias;
 872     end_data += load_bias;
 873 
 874     /* Calling set_brk effectively mmaps the pages that we need
 875      * for the bss and break sections.  We must do this before
 876      * mapping in the interpreter, to make sure it doesn't wind
 877      * up getting placed where the bss needs to go.
 878      */
 879     retval = set_brk(elf_bss, elf_brk); // 调用set_brk有效地映射了我们用于bss和break部分的页面。 我们必须在解释器中进行映射之前执行此操作,以确保不会将其放置在需要放bss的位置。
 880     if (retval)
 881         goto out_free_dentry;
 882     if (likely(elf_bss != elf_brk) && unlikely(padzero(elf_bss))) {
 883         retval = -EFAULT; /* Nobody gets to see this, but.. */
 884         goto out_free_dentry;
 885     }
 886 
 887     if (elf_interpreter) {
 888         unsigned long interp_map_addr = 0;
 889 
 890         elf_entry = load_elf_interp(&loc->interp_elf_ex,
 891                         interpreter,
 892                         &interp_map_addr,
 893                         load_bias); //仅读取具有ELF标头的ld.so库。
 894         if (!IS_ERR((void *)elf_entry)) {
 895             /*
 896              * load_elf_interp() returns relocation
 897              * adjustment
 898              */
 899             interp_load_addr = elf_entry;
 900             elf_entry += loc->interp_elf_ex.e_entry; // 调整入口地址
 901         }
 902         if (BAD_ADDR(elf_entry)) {
 903             retval = IS_ERR((void *)elf_entry) ?
 904                     (int)elf_entry : -EINVAL;
 905             goto out_free_dentry;
 906         }
 907         reloc_func_desc = interp_load_addr;
 908 
 909         allow_write_access(interpreter);
 910         fput(interpreter);
 911         kfree(elf_interpreter);
 912     } else {
 913         elf_entry = loc->elf_ex.e_entry;
 914         if (BAD_ADDR(elf_entry)) {
 915             retval = -EINVAL;
 916             goto out_free_dentry;
 917         }
 918     }
 919 
 920     kfree(elf_phdata);
 921 
 922     set_binfmt(&elf_format);
 923 
 924 #ifdef ARCH_HAS_SETUP_ADDITIONAL_PAGES
 925     retval = arch_setup_additional_pages(bprm, !!elf_interpreter);
 926     if (retval < 0)
 927         goto out;
 928 #endif /* ARCH_HAS_SETUP_ADDITIONAL_PAGES */
 929 
 930     install_exec_creds(bprm); // 安装新的信任证
 931     retval = create_elf_tables(bprm, &loc->elf_ex,
 932               load_addr, interp_load_addr);
 933     if (retval < 0)
 934         goto out;
 935     /* N.B. passed_fileno might not be initialized? */
 936     current->mm->end_code = end_code;
 937     current->mm->start_code = start_code;
 938     current->mm->start_data = start_data;
 939     current->mm->end_data = end_data;
 940     current->mm->start_stack = bprm->p;
 941 
 942 #ifdef arch_randomize_brk
 943     if ((current->flags & PF_RANDOMIZE) && (randomize_va_space > 1)) {
 944         current->mm->brk = current->mm->start_brk =
 945             arch_randomize_brk(current->mm);
 946 #ifdef CONFIG_COMPAT_BRK
 947         current->brk_randomized = 1;
 948 #endif
 949     }
 950 #endif
 951 
 952     if (current->personality & MMAP_PAGE_ZERO) {
 953         /* Why this, you ask???  Well SVr4 maps page 0 as read-only,
 954            and some applications "depend" upon this behavior.
 955            Since we do not have the power to recompile these, we
 956            emulate the SVr4 behavior. Sigh. */
 957         error = vm_mmap(NULL, 0, PAGE_SIZE, PROT_READ | PROT_EXEC,
 958                 MAP_FIXED | MAP_PRIVATE, 0);
 959     }
 960 
 961 #ifdef ELF_PLAT_INIT // 初始化进程启动环境,进程启动时EDX寄存器的地址应该是DT_FINI的地址
 962     /*
 963      * The ABI may specify that certain registers be set up in special
 964      * ways (on i386 %edx is the address of a DT_FINI function, for
 965      * example.  In addition, it may also specify (eg, PowerPC64 ELF)
 966      * that the e_entry field is the address of the function descriptor
 967      * for the startup routine, rather than the address of the startup
 968      * routine itself.  This macro performs whatever initialization to
 969      * the regs structure is required as well as any relocations to the
 970      * function descriptor entries when executing dynamically links apps.
 971      */
 972     ELF_PLAT_INIT(regs, reloc_func_desc);
 973 #endif
 974 
 975     start_thread(regs, elf_entry, bprm->p); // 调用start_thread()函数修改保存在内核态堆栈但属于用户态寄存器的EIP和ESP的值,以使它们分别指向DL的入口(如果没有获得DL则指向ELF的入口)和新的用户态栈的栈顶;
 976     retval = 0;
 977 out:
 978     kfree(loc);
 979 out_ret:
 980     return retval;
 981 
 982     /* error cleanup */
 983 out_free_dentry:
 984     allow_write_access(interpreter);
 985     if (interpreter)
 986         fput(interpreter);
 987 out_free_interp:
 988     kfree(elf_interpreter);
 989 out_free_ph:
 990     kfree(elf_phdata);
 991     goto out;
 992 }

当load_elf_binary()执行完毕,返回到do_execve_common函数,再返回到sys_execve()函数时,load_elf_binary()中已经把系统调用的返回地址改成了被装载的ELF程序的入口地址了。

所以当sys_execve()系统调用从内核态返回到用户态时,RIP寄存器直接跳到了ELF程序的入口地址,于是新的程序开始执行,ELF可执行文件加载完成。

  • 7
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值