`

[转]linux中ELF加载过程分析

阅读更多

 

导读:


sys_execve

   | - do_execve

|

| - search_binary_handler        

           |- linux_binfmt= elf_format

       |- elf_format-> load_elf_binary

| - elf_entry = load_elf_interp()

          |-  

| if  (BAD_ADDR(elf_entry))

                |     force_sig(SIGSEGV, current);

|     retval =-EINVAL;



 

binfmt_elf.c: line 1024

              elf_entry = loc->elf_ex.e_entry;

              if (BAD_ADDR(elf_entry)) {

                     force_sig(SIGSEGV, current);

                     retval = -EINVAL;

                     goto out_free_dentry;

              }



ELF可行档的载入:



内核中实际执行execv()或execve()系统调用的程序是do_execve(),这个函数先打开目标映像文件,并从目标文件的头部(从 第一个字节开始)读入若干(128)字节,然后调用另一个函数search_binary_handler(),在那里面让各种可执行程序的处理程序前来 认领和处理。内核所支持的每种可执行程序都有个struct linux_binfmt数据结构,通过向内核登记挂入一个队列。而search_binary_handler(),则扫描这个队列,让各个数据结构所 提供的处理程序、即各种映像格式、逐一前来认领。如果某个格式的处理程序发现特征相符而,便执行该格式映像的装入和启动。
我们从ELF格式映像的linux_binfmt数据结构开始:

[Copy to clipboard]

CODE:

#define load_elf_binary load_elf32_binary

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
};



ELF格式的二进制映像的认领、装入和启动是由load_elf_binary()完成的。而“共享库”、即动态连接库映像的装入则由 load_elf_library()完成。实际上共享库的映像也是二进制的,但是一般说“二进制”映像是指带有main()函数的、可以独立运行并构成 一个进程主体的可执行程序的二进制映像。



CODE:

[sys_execve() > do_execve() > search_binary_handler() > load_elf_binary()]

整个ELF映像就是由文件头、区段头表、程序头表、一定数量的区段、以及一定数量的部构成

而ELF映像的装入/启动过程,则就是在各种头部信息的指引下将某些部或区段装入一个进程的用户空间,并为其运行做好准备(例如装入所需的共享库),最后(在目标进程首次受调度运行时)让CPU进入其程序入口的过程。



接着是对elf_bss 、elf_brk、start_code、end_code等等变量的初始化。这些变量分别纪录着当前(到此刻为止)目标映像的bss段、代码段、数据 段、以及动态分配“堆” 在用户空间的位置。除start_code的初始值为0xffffffff外,其余均为0。随着映像内容的装入,这些变量也会逐步得到调整,读者不妨自己 留意这些变量在整个过程中的变化。
读入了程序头表,并对start_code等变量进行初始化以后,下面的第一步就是在程序头表中寻找“解释器”部、并加以处理的过程。



ELF格式的二进制映像在装入和启动的过程中需要得到一个工具软件的协助,其主要的目的在于为目标映像建立起跟共享库的动态连接。这个工具称为 “解释器”。一个ELF映像在装入时需要用什么解释器是在编译/连接是就决定好了的,这信息就保存在映像的“解释器”部中。“解释器”部的类型为 PT_INTERP,找到后就根据其位置p_offset和大小p_filesz把整个“解释器”部读入缓冲区。整个“解释器”部实际上只是一个字符串, 即解释器的文件名,例如“/lib/ld-linux.so.2”。有了解释器的文件名以后,就通过open_exec()打开这个文件,再通过 kernel_read()读入其开头128个字节,这就是映像的头部。早期的解释器映像是a.out格式的,现在已经都是ELF格式的了,/lib /ld-linux.so.2就是个ELF映像。





程序段的载入:

还是从目标映像的程序头表中搜索,这一次是寻找类型为PT_LOAD的部(Segment)。在二进制映像中,只有类型为PT_LOAD的部才是需要装入的。
找到一个PT_LOAD片以后,先要确定其装入地址。正如代码前面的注释所述,这里先假定装入地址是固定的,然后再根据映像是否允许浮动而作出调 整。具体片头数据结构中的p_vaddr提供了映像在连接时确定的装入地址vaddr。如果映像的类型为ET_EXEC,(或者 load_addr_set已经被设置成1,见下)那么装入地址就是固定的。而若类型为ET_DYN、即共享库,那么即使装入地址固定也要加上一个偏移 量,代码中给出了计算方法,其中ELF_ET_DYN_BASE对于x86定义为(TASK_SIZE / 3 * 2),所以这是2GB边界,而ELF_PAGESTART表示按页面边界对齐。
确定了装入地址以后,就通过elf_map()、实际上是elf32_map()、建立用户空间虚存区间与目标映像文件中某个连续区间之间的映 射。这个函数基本上就是do_mmap(),其返回值就是实际映射的(起始)地址。对于类型为ET_EXEC的可执行程序映像而言,代码中的 load_bias是0,所以装入的起点就是映像自己提供的地址vaddr。另一方面,对于ET_EXEC,由于参数中的elf_flags中的 MAP_FIXED标志位为1,所以给定的映射地址是刚性的而不容许变通,如果与已经映射的区间有冲突就以失败告终。不过,目标映像的映射是从一片空白开 始的,所以实际上不可能失败。顺便提一下,现在又多了一种ELF格式的目标映像,称为FDPIC,其装入地址就是可浮动的。
即使总的装入地址是浮动的,一旦装入了第一个Segment以后,下一个Segment的装入地址就应该是固定的了,所以这里一方面把load_addr_set设置成1,







[sys_execve() > do_execve() > search_binary_handler() > load_elf_binary()]

if (elf_interpreter) {
if (interpreter_type == INTERPRETER_AOUT)
elf_entry = load_aout_interp(&loc->interp_ex, interpreter);
else
elf_entry = load_elf_interp(&loc->interp_elf_ex, interpreter, &interp_load_addr);
. . . . . .
reloc_func_desc = interp_load_addr;

allow_write_access(interpreter);
fput(interpreter);
kfree(elf_interpreter);
} else {
elf_entry = loc->elf_ex.e_entry;
}


这段程序的逻辑很简单:如果需要装入解释器,并且解释器的映像是ELF格式的,就通过load_elf_interp()装入其映像,并把将来进 入用户空间时的入口地址设置成load_elf_interp()的返回值,那显然是解释器的程序入口。而若不装入解释器,那么这个地址就是目标映像本身 的程序入口。
显然,关键的操作是由load_elf_interp()完成的,所以我们追下去看load_elf_interp()的代码。



do_brk()从用户空间分配一段空间。这段代码总体上与前面映射目标映像的那一段相似。注意解释器映像的类型一般都是ET_DYN,所以load_addr可能不等于0。





进程可使用exec系统调用执行新的命令。exec系统调用将一次性释放全部虚拟内存空间,之后生成新的空间并将新的命令影射入内。

    do_execve(文件路径,参数。环境)
        打开文件(open_namei函数)
        计算exec后的UID/GID,读入文件头(prepare_binprm函数)
        读入命令名,环境变量,起动参数(copy_strings函数)
        呼叫各种不同二进制文件的操作函数(search_binary_handler函数)

    ELF格式的话,经由search_binary_handler函数呼叫load_elf_binary函数。如果是动态联结,同时影射动态联结器(ld*.so)

    load_elf_binary(linux_binprm* bprm,pt_regs* regs)
        分析ELF文件头
        读入程序的头部分(kernel_read函数)
        if(存在解释器头部){
                读入解释器名(ld*.so)(kernel_read函数)  |(zalem note:可用
                打开解释器文件(open_exec函数)                | objdump -s -j .interp xxx
            读入解释器文件的头部(kernel_read函数)   |命令查看,
        )                                                          |linux下是/lib/ld-linux.so.x)
        释放空间,清楚信号,关闭指定了close-on-exec标识的文件(flush_old_exec函数)
        生成堆栈空间,塞入环境变量/参数部分(setup_arg_pages函数)
        for(可引导的所有的程序头){
                将文件影射入内存空间(elf_map,do_mmap 函数)
        }
        if(为动态联结){
                影射动态联结器(load_elf_interp函数)
        }
        释放文件(sys_close函数)
        确定执行中的UID,GID(compute_creds函数)
        生成bss领域(set_brk函数)
        bss领域清零(padzero函数)
        设定从exec返回时的IP,SP(start_thread函数)(动态联结时的IP指向解释器的入口)

本文转自
http://blog.csdn.net/suacker/archive/2007/02/15/1510557.aspx
分享到:
评论

相关推荐

    Linux下ELF可执行文件载入过程源码分析

    通过以上对各个关键函数的源码分析,我们可以看出Linux内核在处理ELF可执行文件加载过程中涉及到了许多复杂的操作。从简单的系统调用入口到具体的文件读取、内存分配和寄存器状态设置,每个环节都紧密相连,共同完成...

    分析ELF文件加载过程

    在 Linux 内核中,ELF 文件的加载过程主要由 `load_elf_binary` 函数实现,该函数接收 `struct linux_binprm` 结构体作为参数,其中包含了 ELF 文件的相关信息: 1. **buf**:预读入的 ELF 文件头部。 2. **file**...

    ELF文件的加载和动态链接过程

    这涉及到Linux内核空间加载ELF文件的过程,以及在程序运行过程中符号的动态解析。这些过程是在指定的实验平台上进行的,该平台包括了Ubuntu 7.04操作系统、Linux内核版本2.6.20、gcc编译器版本4.1.2、glibc版本2.5、...

    Linux系统下的ELF文件分析.pdf

    动态分析可以帮助开发者了解到ELF文件的执行过程,了解到程序是如何加载和执行的。同时,动态分析也可以帮助开发者优化ELF文件的执行效率和性能。 ELF文件分析是一个非常重要的主题,在Linux系统下,ELF文件分析...

    linux中elf手册翻译

    ELF(Executable and Linkable Format)是Unix和类Unix系统(例如Linux)中用于二进制文件的标准文件格式,主要用于存储可执行文件、可重定位目标文件、核心转储文件和共享库。 ELF文件结构的设计使得它既适用于32位...

    linux下的elf 文件分析

    程序加载过程涉及到将ELF文件从磁盘加载到内存中,并根据程序头部的信息设置相应的内存区域。 ##### 3.8.3 动态链接 动态链接允许程序在运行时链接共享库,这种方式可以节省内存并加速程序启动。 ##### 3.8.4 全局...

    分析ELF的加载过程1

    ELF (Executable and Linkable ...总之,ELF加载过程涉及到内核、动态链接器、程序段表和共享库等多个组件的协作。通过理解这一过程,开发者可以更好地调试和优化程序,尤其是在涉及动态链接和共享库使用的情景下。

    linux的elf手册

    ### Linux的ELF手册知识点详解 #### 一、概述 ELF (Executable and Linkable Format) 是一种标准的文件...通过深入了解ELF文件格式,开发者可以更好地理解和优化程序的加载过程,同时也有助于故障排查和性能分析。

    MMU、ELF的加载、linux的启动

    在Linux系统启动过程中,ELF文件被加载到内存中执行。加载器首先解析ELF头,获取程序的入口点、依赖的动态库等信息,然后将各个节区映射到内存,并处理重定位信息,使得程序可以在指定的内存位置正确执行。 Linux的...

    linux内核模块加载顺序

    Linux内核模块加载顺序控制是Linux操作系统中一项关键技术,它决定了内核模块按照何种顺序被加载到内核中。本文将从多个角度阐述内核模块加载顺序的控制机制,包括模块的顺序声明、内核模块加载顺序控制的原理、内核...

    《ELF文件格式分析.pdf》与elf解析代码

    ELF(Executable and Linkable Format)是Linux和其他类UNIX系统中广泛使用的可执行文件、共享库和对象文件的标准格式。这个格式提供了丰富的信息,包括程序的入口点、符号表、重定位信息等,使得编译器、链接器、...

    Intel平台下Linux中ELF文件动态链接的加载、解析及实例分析(一)-加载1

    本文将深入探讨ELF文件在Linux下的动态链接加载过程,主要关注`dl_open`函数及其相关组件。 动态链接的概念可以追溯到上世纪五十年代,当时的设想是将常用代码集中存储,其他程序通过调用即可使用。随着时间的发展...

    ELF 文件格式分析(北京大学实验室出的标准版)

    ELF (Executable and Linkable Format) 是一种广泛用于类UNIX操作系统,如Linux、Solaris等的可执行文件、共享库和核心映像的文件格式。它由电气电子工程师协会(IEEE)和UNIX System Laboratories共同开发,并在1990...

    Intel平台下Linux中ELF文件动态链接的加载1

    在Intel平台下的Linux系统中,ELF(Executable and Linkable Format)文件是一种广泛使用的可执行文件格式,它支持静态和动态链接。动态链接是Linux程序加载和执行的关键机制,允许程序在运行时加载所需的共享库(....

    Intel平台下linux中ELF文件动态链接的加载、解析及实例分析(二)-函数解析与卸载1

    在Intel平台下的Linux系统中,ELF(Executable and Linking Format)文件是一种广泛使用的可执行文件格式。动态链接是ELF文件在运行时加载库的关键步骤。本篇将深入探讨函数解析与卸载的过程,特别是在处理动态链接...

    Android/Linux 32位elf自动修复工具

    总的来说,"Android/Linux 32位elf自动修复工具"是一个高效且方便的解决方案,它减少了手动分析和修复ELF文件的复杂性,提高了问题诊断和修复的效率。在处理32位ELF文件问题时,开发者和系统管理员可以借助这个工具...

    elf for linux.rar_elf_linux elf

    动态链接器(如ld-linux.so)负责解析ELF文件中的动态链接信息,将所需的共享库加载到内存,并完成符号解析和重定位。 五、调试信息 ELF文件还可以包含调试信息,如DWARF格式,这对于GDB等调试工具至关重要。这些...

    ELF Format + 链接器与加载器

    在Linux环境中,ELF格式是核心组件,因为它承载了程序的生命周期,从编译到运行的每一个步骤。通过理解ELF格式和链接加载过程,开发者可以更好地优化程序性能,调试问题,甚至实现自定义的链接器和加载器。 《链接...

    elf文件转换为hex文件

    转换过程中,ELF文件的机器代码被提取并转换成HEX格式。这个过程可能包括地址映射、数据类型转换等步骤,以确保HEX文件能够正确地在目标硬件上运行。转换后的`my_program.hex`文件可以使用编程器或固件更新工具加载...

    elf转换bin的工具

    这类工具通常会读取ELF文件的所有节区,并将其内容合并到一个连续的BIN文件中,删除ELF头信息和其他非必要数据,从而得到可以直接加载到内存并执行的二进制文件。 `DolTool.txt`可能是该工具的使用指南或帮助文档,...

Global site tag (gtag.js) - Google Analytics